tech

[English] [Japanese]

Easier Lua development with the transcompiler LuneScript!!

Lua is a very compact language, yet a language with high potential.

I think it's safe to say that it is one of the easiest languages to use when it comes to programming.

However, although it is "easy to use as a language to be incorporated into a program", it is also true that there are various things to worry about when compared to modern languages.

On the other hand, functional evolution to bring Lua closer to the modern language may be a trade-off with one of the major features of Lua, which is "compactness".

Therefore, I would like to introduce a transcompiler LuneScript that can cover the parts of Lua that you are interested in without modifying Lua itself.

What is LuneScript

LuneScript is a language that covers the concerns of Lua as mentioned above, and is a transcompiler that can convert the code developed in LuneScript into Lua code.

LuneScript has the following characteristics.

  • null safety.
  • Since it is a statically typed language, simple mistakes can be found at compile time through type checking.
  • Minimize the effort of type declaration by type inference.
  • generics allows processing while preserving type information.
  • Support class definition as language grammar.
  • Supports pattern matching. (match)
  • Faster load times with lazy loading.
  • Conversion between structured and unstructured data. (Mapping)
  • Macros can realize designs that do not rely on dynamic processing such as polymorphism.
  • Detect missing initialization of variables
  • Error delegation of functions with the ! operator

  • Transcompiling to Lua and go.
  • Supports data representation compatible with JSON.
  • Transcompiled Lua code can work independently without any external libraries.
  • Transcompiled Lua code is output as it is written in LuneScript, so there is no performance degradation.
  • Existing Lua external modules can be used from LuneScript.
  • LuneScript runs on Lua and does not require anything other than Lua standard modules, so it is easy to introduce.

    • Transcompile time can be reduced to 1/20 by using the go version of LuneScript.
  • Lua modules converted from LuneScript can be used from other Lua modules.
  • Supports Lua5.1 to 5.4.

  • LuneScript is developed by self-hosting.
  • Supports code completion in emacs
  • support tag jumping with lnstags

  • Supports automatic generation of glue code
  • Learning cost is low because it is based on Lua and C syntax.

How to use LuneScript

LuneScript is developed on github.

<https://github.com/ifritJP/LuneScript>

Please refer to the following for the installation method.

command

Installing LuneScript installs the lnsc command.

For information on how to use the lnsc command, please refer to the following article:

Cross-compiling between Lua versions

LuneScript supports cross-compilation between versions of Lua. Please refer to the following article.

LuneScript specification

This section describes the specifications of LuneScript.

value and type

Please refer to the following article for values and types handled in LuneScript.

comment

Comments use C++ style. Single-line comment // and multi-line comment /* */ can be specified.

// @lnsFront: skip
// 行末までコメント
/* ここから〜
ここまでコメント*/

operator

In principle, operators use the same ones as Lua.

Note that Lua5.3's // (truncated division) is a one-line comment in LuneScript.

In LuneScript, / between ints is automatically rounded down and divided.

Variable declaration

Please refer to the following article for LuneScript variables.

General control statement

Please refer to the following for LuneScript control statements.

function declaration

For LuneScript functions, please refer to the following.

nilable

LuneScript is a nil-safe (NULL-safe) language.

Please refer to the following for nilable which realizes nil safety of LuneScript.

class

LuneScript supports classes for object-oriented programming.

Classes in LuneScript have the following constraints:

  • Does not support multiple inheritance.
  • generics are not supported.
  • All are overridable methods.

    • You cannot suppress overrides.
  • A method with the same name with different arguments cannot be defined between inheritances.

    • The only exception is that the constructor has the same name ( __init ).

Please refer to the following article.

prototype declaration

LuneScript parses the script from top to bottom.

Symbols referenced in scripts must be previously defined. For example, before declaring a variable of type TEST, the class TEST must be defined.

Also, to define classes that refer to each other, one of them must be prototyped.

The following is an example when ClassA and ClassB cross-reference each other.

// @lnsFront: ok
pub class Super {
}
pub proto class ClassB extend Super;
pub class ClassA {
  let val: ClassB;
}
pub class ClassB extend Super{
  let val: ClassA;
}

Declare proto as above.

The prototype declaration and the actual definition must declare the same things such as pub and extend.

Mapping

LuneScript class instances can be converted to and from Map objects.

This is called Mapping.

Please refer to the following for Mapping.

Generics

LuneScript supports generics.

See below for details.

nil conditional operator

It supports the nil conditional operator as an easy way to handle nilable values.

module

For LuneScript module management, please refer to the following.

build

Please refer to the following for how to build a project using LuneScript.

_lune.lua module

As mentioned above, files transcompiled to Lua with LuneScript can be executed as-is with the Lua command. At this time, no external module is required.

This indicates that you have included all the code necessary for processing within the transcompiled Lua code.

For example, if you transcompile the following processing code:

// @lnsFront: ok
fn func( val:int! ):int {
   return 1 + unwrap val default 0;
}

The Lua code is much longer, like this:

--mini.lns
local _moduleObj = {}
local __mod__ = 'mini'
if not _ENV._lune then
   _lune = {}
end
function _lune.unwrap( val )
   if val == nil then
      __luneScript:error( 'unwrap val is nil' )
   end
   return val
end 
function _lune.unwrapDefault( val, defval )
   if val == nil then
      return defval
   end
   return val
end

local function func( val )
   return 1 + _lune.unwrapDefault( val, 0)
end

return _moduleObj

Lines 4 to 18 are the processing required for unwrap. Note that this code is output to all Lua files.

Since this code itself is a common process, by specifying the -r option when transcompiling, you can require it as a separate module and combine common processes.

Specifically, specify the -r option as follows.

$ lua lune/base/base.lua -r src.lns save

With this -r option, the above code would be transformed into the following, much cleaner.

--mini.lns
local _moduleObj = {}
local __mod__ = 'mini'
_lune = require( "lune.base._lune" )
local function func( val )
   return 1 + _lune.unwrapDefault( val, 0)
end

return _moduleObj

Note that require( "lune.base._lune" ) will be inserted, so you need to set this module so that it can be loaded. You don't need to be aware of this if the transcompiler operates in an environment, but you need to be careful when executing the converted Lua source in some other environment.

macro

LuneScript employs simple macros.

Significance of macro

Macros have some restrictions compared to ordinary functions. In addition, most of the processing that can be performed by macros can be realized by making full use of object orientation.

So what's the point of using macros?

That is, "the operation is statically determined by using macros".

If the same processing is implemented in an object-oriented manner, it becomes dynamic processing. On the other hand, if implemented by a macro, it becomes a static process.

What do you enjoy about this?

It's the same advantage statically typed languages have over dynamically typed languages.

Static analysis is possible by statically processing statically determined information.

For example, most object-oriented function overrides can be resolved statically through the use of macros. By using static function calls instead of dynamic function overrides, it becomes easier to follow the source code.

It is not good to use macros recklessly, but it is also not ideal to make dynamic processing such as function overriding easily.

Dynamic processing and macros need to be used properly.

macro definition

Please refer to the following article for macro definition.

supplement

Links to supplementary articles will be added here.

  • Introduction to Lua Transcompiler LuneScript 2

    • Introducing subfile, module and nil conditional operators
    • introduce2
  • Let's have more fun with Lua's transcompiler LuneScript's modern development environment

    • Completion, syntax checking, subfile search
    • completion

For articles not linked from this page, follow them from the sidebar.