README ¶
# -*- coding:utf-8 -*- #+AUTHOR: ifritJP #+STARTUP: nofold #+OPTIONS: ^:{} [[https://github.com/ifritJP/LuneScript/actions][https://github.com/ifritJP/LuneScript/workflows/Test/badge.svg]] #+TITLE: LuneScript Latest document (for Japanese) https://ifritjp.github.io/documents/lunescript/ ** What's LuneScript LuneScript is a transcompiler for Lua. ** feature of LuneScript - Learning cost is low because it is based on Lua and C syntax. - Because LuneScript is a statically typed language, simple checks can be found at compile time by type checking. - Minimize the effort of type declaration by type inference. - Null safety. - Generics (partly only) allows processing while preserving type information. - Corresponds to class definition as grammar of language. - Macros can realize designs that do not depend on dynamic processing such as polymorphism. - Supports data representation compatible with JSON. - Transformed Lua code can be operated as a single unit without assuming external libraries. - Since the process written in LuneScript is output as is, the transcoded Lua code, There is no performance deterioration. - Existing Lua external modules can be used from LuneScript. - LuneScript runs on Lua and is easy to install as it requires no Lua standard modules. - Supports Lua 5.1, 5.2, 5.3. - LuneScript is developed by self hosting. ** Setup LuneScript LuneScript provides 2 version. - go version - lua script version *** go version - download lnsc file. - [[https://drive.google.com/drive/folders/1S5NgeM6qIOIUC0rkBHqnWZcuhmsTqB2w][google drive]] (latest release) - execute the following commands. : $ chmod +x lnsc : $ sudo install lnsc /usr/local/bin *** lua script version - In case of lua5.1 #+BEGIN_SRC sh $ luarocks install lunescript51 #+END_SRC - In case of lua5.2, lua5.3 #+BEGIN_SRC sh $ luarocks install lunescript #+END_SRC ** Usage of lnsc command 'lnsc' command is LuneScript compiler. *** hello world - create hello.lns. #+BEGIN_SRC lns $ echo 'print( "Hello world." );' > hello.lns #+END_SRC - execute hello.lns with 'lnsc' command. #+BEGIN_SRC sh $ lnsc hello.lns exe Hello world. #+END_SRC - transcompile #+BEGIN_SRC sh $ lnsc hello.lns SAVE #+END_SRC This command generates files (hello.lua and hello.meta). - execute hello.lua with 'lua' command. #+BEGIN_SRC sh $ lua hello.lua Hello world. #+END_SRC ** Specification *** Values and types LuneScript handles the following values. - nil - Integer (int) - real (real) - String (str) - Boolean (bool) - List - Map (Map) - Sequence - Class - macro - Function - stem **** nil nil is the same as nil in Lua. You can also use null in LuneScript. null is nil's alias. With null support, JSON can be handled as it is in LuneScript. **** integer, real number LuneScript handles integers and real numbers separately. As a result, 10/3 becomes 3, and 10 / 3.0 becomes 3.3333 .... ***** Numeric literals The numerical literal is C89-like things adopted. - Integer supports decimal and hexadecimal representation - Real numbers are decimal and exponential representation by 'e'. Additional ASCII character code representation is possible. #+BEGIN_SRC lns let val = ?a; // 0x61 #+END_SRC Expand the characters following ~?~ Into ACSII code as described above. Characters other than ASCII are not supported. Characters such as 'and' must be quoted with \ as '? \'. ***** Arithmetic operation We adopt the same arithmetic as the four arithmetic operations of numerical values for Lua. The result of binomial operation changes type as follows. - int and int result in int. - The result of real and real is real. - The operation result of int and real is real. However, if the result of int and int operation fall outside the range of int, It will be real as an internal value at runtime, but the type on LuneScript will remain int. To round the result to int, you need to cast it with ~@@int~. ***** Bit operation Supports bit operation. It can also be used with Lua 5.2. The bit length is 32 bits for Lua 5.2. The bit length of Lua 5.3 depends on the environment. - Logical AND (&) #+BEGIN_SRC lns 1 & 3 == 1 #+END_SRC - Logical OR (|) #+BEGIN_SRC lns 1 | 2 == 3 #+END_SRC - XOR (~) #+BEGIN_SRC lns 1 ~ 3 == 2 #+END_SRC - Logical Left Shift (|<<) #+BEGIN_SRC lns 1 |<< 2 == 4 #+END_SRC - Logical Right Shift (|>>) #+BEGIN_SRC lns 0x10 |>> 2 == 4 #+END_SRC - Bit inversion (~) #+BEGIN_SRC lns ~2 == 0xfffffffd #+END_SRC The character string is the same as Lua and has no terminator. String literals are enclosed in "or '. Multiple line literals are enclosed in ```. Use =txt[N]= to access the Nth character in the string. However, =txt[N]= is read-only and characters can not be rewritten. if =N= of =txt[N]= is over the length of =txt=, its operation is *undefined*. #+BEGIN_SRC lns let txt = "1234"; txt[2] // ?2 #+END_SRC Also, a format format similar to Python is available. #+BEGIN_SRC lns ``` from here ... So far the string ``` "10 + %s = %d" ("1", 11) // "10 + 1 = 11" #+END_SRC ***** String concatenation String concatenation uses the same .. as Lua. **** Boolean (bool) It has =true= and =false=. **** List, array, map In LuneScript, Lua's table is divided into =List=, =Array=, and =Map=. The =List= is a sequence of Lua, The =Array= are fixed-length lists, The =Map= is Lua's table. Each literal is declared as follows. #+BEGIN_SRC lns let list = [ 1, 2, 3 ]; let array = [@ 'a', 'b', 'c' ]; let map = { "A": 10, "B": 11, "C": 12 }; #+END_SRC ***** List Objects in the list order values and manage values. #+BEGIN_SRC lns let name : List<itemType>; #+END_SRC The type of value that can be held in the list is limited to one. However, if it is a list of stem! Type described later, all values can be retained. For example, the following will be a list with elements of type int. #+BEGIN_SRC lns let name : List<int>; #+END_SRC Objects in the list have insert and remove methods. #+BEGIN_SRC lns let mut list:List<int> = []; list.insert( 1 ); // [ 1 ] list.insert( 2 ); // [ 1, 2 ] list.insert( 3 ); // [ 1, 2, 3 ] list.remove(##); // [ 1, 2 ] #+END_SRC To access the elements of the list, Specify the index of the element with [N] as follows. #+BEGIN_SRC lns let list = ['a','b','c']; print( list[ 1 ] ); // 'a' #+END_SRC Processing when the index of an element specifies out of the range of the list is *undefined*. ***** Array Array objects are fixed-length lists. It is the same as the list except that the size is fixed. #+BEGIN_SRC lns let mut list = [@ 1, 2 ]; list.insert( 1 ); // error #+END_SRC Because it is fixed in size, you can not insert or remove. ***** Map Objects on the map manage key / value ties. #+BEGIN_SRC lns let name : Map<keyType,valType>; #+END_SRC Map type is declared with keyType and valType as described above. For example, the following declaration is a map whose key is int type and value is str type. #+BEGIN_SRC lns let val : Map<int,str>; #+END_SRC To access the value, specify as follows: #+BEGIN_SRC lns let map = { "A": 10, "B": 11, "C": 12 }; print( map[ "A" ], map.B ); #+END_SRC If the key is a character string, You can access it as a member of the map object like =map.B=. You can not set nil for the key and value of the map object. ***** constructor of List and Map #+BEGIN_SRC lns let list = [ 1, 2, 3 ]; let map = { "A": 10, "B": 11, "C": 12 }; #+END_SRC List, Map can declare literals as described above. The types of List and Map generated at this time are determined by the values to be configured. If the keys or values used in the map constructor are all of the same type, The key of the map, the type of the value will be the type of that key, value. If one is different, it becomes stem type. Specifically, it is as follows. #+BEGIN_SRC lns let list1 = [ 1, 2, 3 ]; // List<int> let list2 = [ 'a', 'b', 'c' ]; // List<str> let list3 = [ 'a', 1, 'c' ]; // List<stem> let map1 = { "A": 10, "B": 11, "C": 12 }; // Map<str,int> let map2 = { "A": 10, "B": 11, "C": 12 }; // Map<str,int> let map3 = { "a": 'z', "b": 'y', "c": 'x' }; // Map<str,str> let map4 = { "a": 1, "b": 'Z' }; // Map<str,stem> #+END_SRC **** stem stem is a type that can hold all values except nil. LuneScript is a static typed language, If a value different from the assumed type is given, a compile error will occur. On the other hand, the stem type is a type that can handle all types except nil, No compilation error will occur no matter what value other than nil is given. stem! is a type that can handle all values including nil. There is no problem considering it as the Lua variable itself. **** ! Type (nilable) nilable is a type that can hold nil. Conversely, if it is not nilable, nil can not be retained. As a result, while dealing with non-nilable type, You do not have to worry about runtime errors with nil. *** Type conversion Values of some types can convert types. To convert, use the following format. #+BEGIN_SRC lns val@@type val@@@type val@@=type #+END_SRC This declares converting the value of val to type. For example, the following converts val to int. #+BEGIN_SRC lns val@@int #+END_SRC **** ~@@~, ~@@@~, ~@@=~ ~@@~, ~@@=~ are forced converting. This method is very dangerous. ~@@~ can't use for class type. ~@@@~ check matching the type dynamically. The result of ~@@@~ is nilable, when occuring type unmatch returns nil. **** Numeric type conversion Numeric type values can be converted to different types. Rounding occurs in the conversion. - int to real - Convert from integer to real number - real to int - Convert real numbers to integers - Equivalent to calling math.floor(). **** Type conversion with stem type Any type can be converted to stem type and interchangeable. - Convert from arbitrary type to stem type - You can implicitly convert without specifying with ~@@stem~. - Convert from stem type to arbitrary type - It is necessary to explicitly indicate with ~@@type~. - At this time, it does not judge what type of conversion source value was. - The behavior when the source value type and the destination type do not match *undefined* *** Comment Comment adopts C ++ style. Single line comment ~//~, multiple line comment ~/* */~ can be specified. #+BEGIN_SRC lns // Comment up to end of line /* from here~ Comment so far */ #+END_SRC *** operator In principle, the same operator as Lua is used. Note that // (truncate division) of Lua 5.3 will be a one-line comment. In LuneScript, integers / are automatically rounded down. *** Variable declaration #+BEGIN_SRC lns [ pub | global ] let name [: type] = evp; #+END_SRC Variable declaration is done with =let=. Specify variable followed by =let=. Type the variable with type: followed by the variable name. However, if the type can be inferred from the value of the variable declaration initialization, the type specification can be omitted. For example, the following declares an val variable of type int. #+BEGIN_SRC lns let val: int; #+END_SRC All variables are local. However, by defining it as the top level scope, It becomes global data within that module. If pub is specified before the =let= of the variable defined in the top level scope, It is a variable that can be referred to from an external module. Also, declaring global instead of pub is a global variable in the VM. However, it is registered on a global basis, It is the timing to import a module containing this declaration. The operation when the global symbol of the same name is defined is undefined. It is not possible to declare variables of the same name within the same scope. **** mutable control Mutable control is indispensable for variables. Be sure to refer to here. https://qiita.com/dwarfJP/items/29540d0767d50cfce896 **** Variable declaration of nilable It becomes nilable by appending! To the type to be declared. For example, the following val is a nilable type of int, Where int and nil can be set, val2 is a variable that can not be set to nil. Substituting nil for a non-nilable variable results in a compile error. #+BEGIN_SRC lns let val: int! = 1; let val2: int = nil; // error #+END_SRC Although nilable may be nil, Non-nilable types will not be nil. In other words, while using non-nilable type, You can guarantee that nil access errors will not occur at unintended timing. A nilable type value can not be used as it is as it is. In the following example, val of type int! Can not be used as an operation as an operation, resulting in a compile error. #+BEGIN_SRC lns let val: int! = 1; let val2 = val + 1; // error #+END_SRC To return to the original value from the nilable type, use one of the following syntax. - unwrap - unwrap! - let! - if! - if! let *** nilable related specification This section describes nilable related specifications. **** Acquiring map type value When accessing an element of map type, the result is always nilable type. For example, the following map.B is int! #+BEGIN_SRC lns let map = { "A": 10, "B": 11, "C": 12 }; let val = map.B; // int! #+END_SRC **** unwrap unwrap is an expression that converts the immediately following expression from nilable to non-nilable. #+BEGIN_SRC lns unwrap exp [ default insexp ] #+END_SRC The evaluation result of unwrap is the type which is not nilable of exp. Exp must be passed an expression whose evaluation result is nilable. Insexp will pass an alternative expression when exp is nil. The type of insexp must be a non-nilable type of exp. For example, if exp is int!, Insexp must be of type int. If default is omitted and exp is nil, the program terminates with an error. If exp is not nilable, a compile error will occur. #+BEGIN_SRC lns { let val: int! = nil; let val2 = unwrap val default 0; print( "%d" ( val2 ) ); // 0 } { let val: int! = 1; let val2 = unwrap val default 0; print( "%d" ( val2 ) ); // 1 } #+END_SRC In the above example, Since val is nil in the first unwrap, default evaluation result is returned, In the second unwrap, val is 1, so 1 is returned. **** unwrap! unwrap! performs the above unwrap processing and assignment to a variable at the same time. #+BEGIN_SRC lns unwrap! symbol {, symbol } = exp[, exp ] block [then thenblock]; #+END_SRC If exp is not nil, assign the result of unwrap to symbol. If any of exp is nil, execute block block. Within this block you need to do one of the following: - Set an appropriate value for symbol Exit the scope defining - symbol. If the above processing is not performed, the subsequent operation is undefined. Also in the block block, with the symbol _exp% d, You can access the unwrap result of exp. % d is a number starting from 1, corresponding to the order of symbol. Within this block block, the value of symbol is undefined. then the block is executed if exp is not all nil. You can access symbol from within this block. #+BEGIN_SRC lns fn test( arg:int! ) { let mut val = 0; unwrap! val = arg { print( 0 ); return; } then { val = val + 1; }; print( val ); } test( 1 ); // print( 2 ); test( 2 ); // print( 3 ); test( nil ); // print( 0 ); #+END_SRC **** let! =let!= performs variable declaration and unwrap at the same time. #+BEGIN_SRC lns let! symbol {, symbol } = exp[, exp ] block [ then thenblock ]; #+END_SRC The treatment of block and thenblock is the same as unwrap !. If proper processing is not done, the value of symbol is undefined. Within the block block you can refer to the unwrap result of exp with the name '_' + symbol. Within the then block block you can refer to the value with symbol. #+BEGIN_SRC lns fn test( arg:int! ) { let! mut val = arg { print( 0 ); return; } then { val = val + 1; }; print( val ); } test( 1 ); // print( 2 ); test( 2 ); // print( 3 ); test( nil ); // print( 0 ); #+END_SRC ***** if! if! is a conditional branch by unwrap processing. #+BEGIN_SRC lns if! exp block [ else elseblock ]; #+END_SRC exp specifies a nilable expression. If exp is not nil, execute block. If exp is nil, execute elseblock. In the processing in block you can access _exp, the result of unwrap of exp. ***** if! let =if! let= is a conditional branch by unwrap processing. #+BEGIN_SRC lns if! let var[,var,...] = exp[,exp,...] block [ else elseblock ]; #+END_SRC exp specifies a nilable expression. If exp is not nil, execute block. If exp is nil, execute elseblock. Processing within block can access variables declared with var. The variable of var contains the unwrap result of exp. *** General control statement Supports the same control statements (if, while, for, repeat) as Lua. Like Lua, there is no continue. **** if #+BEGIN_SRC lns if exp { } elseif exp { } else { } #+END_SRC if has the same syntax as Lua. However, blocks are declared with {}. This block is mandatory. You can not write only one sentence without declaring blocks like C. **** switch #+BEGIN_SRC lns switch exp { case condexp [, condexp ] { } case condexp { } default { } } #+END_SRC switch searches for condexp matching the result of exp and executes the matching block. If it does not match any condexp, execute default block. Multiple condexp can be specified, separated by. If more than one is specified, execute a block that matches one of them. **** while, repeat #+BEGIN_SRC lns while exp { } repeat { } exp; #+END_SRC while, repeat has the same syntax as Lua. However, blocks are declared with {}. This block is mandatory. You can not write only one sentence without declaring blocks like C. **** for #+BEGIN_SRC lns for name = exp1, exp2, exp3 { } #+END_SRC =for= is a type control that does not use an iterator. Each type that uses an iterator shall be each. Declare blocks as {}. This block is mandatory. You can not write only one sentence without declaring blocks like C. exp1, exp2, exp3 are evaluated only once. exp3 is omittable. if exp3 was omitted, exp3 is be =1=. =for= is same as following C-lang code. #+BEGIN_SRC c { int init = exp1; int goal = exp2; int inc = exp3; for ( name = init; count <= goal; count += inc ) { } } #+END_SRC **** foreach #+BEGIN_SRC lns foreach val [, index ] in listObj { } foreach val [ , index ] in arrayObj { } foreach val [, key ] in mapObj { } #+END_SRC foreach processes elements held by objects of List, Array, and Map. Val holds the elements held by each object, and body is executed. index is the index of the element, and key is the key associated with the element. index, key can be omitted. **** apply #+BEGIN_SRC lns apply val {,val2 } of exp { } #+END_SRC apply is a type for using an iterator. Declare blocks as {}. This block is mandatory. You can not write only one sentence without declaring blocks like C. val contains the values enumerated by the iterator. If the iterator enumerates multiple values, declare val2, val3 ... that store that value. The specification of exp is the same as that of Lua's for. **** goto Do not support goto *** Function declaration #+BEGIN_SRC lns [ pub | global ] fn name( arglist ) : retTypeList { } #+END_SRC Function declaration is performed with =fn= as described above, and function name is specified by name. name is optional. Declare the argument with arglist and declare it by omitting the =let= of variable declaration. The return type is declared with retTypeList. Type declaration is the same as after variable declaration. Functions can return multiple values. retTypeList declares a minute type of value to return. When exposing a function to an external module, declare pub before fn. The publicly available function, however, must be a function defined in the top level scope. For example, functions defined in blocks such as if and while can not be published. In the function defined in the top level scope, If global is specified instead of pub, it becomes global within the VM. However, it is the timing of importing the module containing this declaration, which is registered. The behavior when a global symbol of the same name is defined is * undefined *. With respect to function declaration, it has the following restrictions. - Does not support function overloading - Do not support operator overloading #+BEGIN_SRC lns fn plus( val1: int, val2: int ) : int { return val1 + val2; } fn plus1( val1: int, val2: int ) : int, int { return val1 + 1, val2 + 1; } #+END_SRC **** Variable length argument The variable length argument uses Lua's .... Each value of ... is handled as stem! Type. #+BEGIN_SRC lns fn hoge( ... ) : stem! { let val: stem! = ...; return val; } #+END_SRC For example, the above function returns the first argument given to the argument, The type at this time is stem! **** Function call Function calls are made with function object (). *** Class declaration Supports classes for object oriented programming. Regarding classes, it has the following restrictions. - Multiple inheritance is not supported. - generics (template) is not supported. - All are overridable methods. - Override can not be suppressed. - The same name method with different arguments between inheritance can not be defined. - However, the constructor is an exception and the same name (__init). Indicates the minimum sample of the class declaration. #+BEGIN_SRC lns class Hoge { } #+END_SRC This sample declares a class named Hoge. Because we do not have members and methods, It will not be used realistically, but this is the smallest as a class declaration. When publishing class to an external module, declare it with pub as follows. #+BEGIN_SRC lns pub class Hoge { } #+END_SRC **** Members, methods A class can have members (variables) and methods (functions). For example, the following have members of val1, val2 and methods of func (). #+BEGIN_SRC lns class Hoge { let val1:int; let val2:int; pub fn func( val:int ): int { return val + self.val1 + self.val2; } } #+END_SRC When accessing your own instance from the processing of a method, Use self (for C ++ this). In C ++, when accessing its own members and methods from method processing, It is possible to directly access the method via the this pointer as follows, as it is. #+BEGIN_SRC cpp this->val = 1; val = 1; #+END_SRC On the other hand, in LuneScript you must use self. ***** Access control In LuneScript, you can control access of members and methods. For access control, specify 'pub', 'pro', 'pri'. The meaning of each is as follows. (Same as C ++) - pub - Accessible from anywhere - pro - Accessible from subclass - pri - Accessible only from within this class If access control is not specified, the default pri is used. In the following example, val1 is pri, val2 is pro, and func is pub. #+BEGIN_SRC lns class Hoge { pri let val1:int; pro let val2:int; pub fn func( val:int ): int { return val + self.val1 + self.val2; } } #+END_SRC **** Instance generation Use new to instantiate the class. Next, we create an instance of Hoge class. #+BEGIN_SRC lns class Hoge { } let hoge = new Hoge(); #+END_SRC After the new operator, specify the class. If a class has members, The value of the member to be set as follows is specified by () of the class after new. #+BEGIN_SRC lns class Hoge { let val1:int; let val2:int; } let hoge = new Hoge(1,2); #+END_SRC **** Constructor A class can have a constructor. The constructor initializes all members of the class. For example, in the following cases, val1 and val2 are initialized in the constructor. #+BEGIN_SRC lns class Hoge { let val1:int; let val2:int; pub fn __init() { self.val1 = 0; self.val2 = 0; } } let hoge = new Hoge(); #+END_SRC At this time, do not specify a value for the argument specified after the class name following new. The argument of new is an argument of that class, Since the constructor of the class of this example does not have arguments, value is not specified for new. If you do not create a constructor on your own, A constructor that automatically has all members as arguments is generated. The argument of the constructor generated at this time is the order of declaration of the member. When creating a constructor on its own, there are the following restrictions. - All members must be initialized. - Do not declare members after constructor declaration. - Do not use return. To call the constructor of the superclass, use super (). super () needs to be called at the beginning of the constructor. If you inherit a class, you must create the constructor yourself. **** static By adding static when declaring members and methods, You can create static members and methods. The following is a sample of a class with the static member val, method func (). #+BEGIN_SRC lns class Hoge { pub static let val:int; __init { Hoge.val = 1; } pub static fn func():int { return 2; } } print( Hoge.val, Hoge.func() ); // 1, 2 #+END_SRC Static members and methods can be used without creating instances. ***** __init block It is a block that initializes static members. A class with a static member must declare an __init block. The __init block has the following restrictions. - All static members must be initialized. - Do not declare static members after the __init block. **** Accessor You can simultaneously declare accessors when declaring members. This accessor declares getter and setter in this order, Specify the access authority (pub / pro / pri) in the declaration part. For example, Pub's getter and pri's setter are created for member val. #+BEGIN_SRC lns let pri val : int { pub, pri }; #+END_SRC The getter and setter created are methods of get_val (), set_val (). If a method with the same name exists, this declaration is ignored. If accessor declaration {} is omitted, no accessor is created. If only getter is specified and setter is omitted, only getter is created. ***** getter access When accessing member getters, You can access not only .get_member () but also. $ member. If the member member itself is a pub instead of an accessor Can be accessed with $ member. #+BEGIN_SRC lns class Test { pri let val: int { pub }; } let test = new Test( 10 ); print( test.$val ); // 10 #+END_SRC **** advertise LuneScript, You can transparently use member methods as your own methods. It is explained in the following example. #+BEGIN_SRC lns class Hoge { pub fn func() { print( "Hoge.func()" ); } } class Foo { pri let hoge:Hoge; pub fn __init() { self.hoge = new Hoge(); } advertise hoge; } let foo = new Foo(); foo.func(); // Hoge.func() #+END_SRC In the above example, the class Foo has the member Hoge class hoge. And class Foo has advertise member hoge. As a result, the class Foo has the method func () of the Hoge class, When foo.func () is executed, Foo.hoge.func () is executed internally. If advertise has a method with the same name in the class being advertized, We prioritize those methods. For example, in the following example, class Hoge has methods func1 () and func2 () Class Foo has method func1 (). In this case, method func1 () of class Foo takes precedence. #+BEGIN_SRC lns class Hoge { pub fn func1() { print( "Hoge.func1()" ); } pub fn func2() { print( "Hoge.func2()" ); } } class Foo { pri let hoge:Hoge; pub fn __init() { self.hoge = new Hoge(); } pub fn func1() { print( "Foo.func1()" ); } advertise hoge; } let foo = new Foo(); foo.func1(); // Foo.func() foo.func2(); // Hoge.func() #+END_SRC **** inheritance LuneScript supports class inheritance. However, multiple inheritance is not supported. Instead, it supports interfaces. Declare inheritance with extend as follows. #+BEGIN_SRC lns class Super { } class Sub extend Super { pub fn __init() { super(); } } #+END_SRC In this example, the Sub class inherits the Super class. **** override All methods can be overridden. When overriding a method, you must declare override as follows. #+BEGIN_SRC lns class Super { pub fn func() { } } class Sub extend Super { pub fn __init() { super(); } pub override fn func() { } } #+END_SRC **** Interface An interface is a class that can declare only the type of a method. It is impossible to have members and define the processing of methods. The following example implements interface IF in class Test. #+BEGIN_SRC lns interface IF { pub fn func(); } class Test extend (IF) { pub fn func() { print( "Test.func" ); } } fn sub( obj:IF ) { obj.func(); } sub( new Test() ); #+END_SRC **** Method invocation Method calls are done as follows. #+BEGIN_SRC cpp Hoge hoge; Hoge.sub(); hoge.func(); #+END_SRC Hoge.sub () is a class method, hoge.func () is an instance method. Class method is *classSymbol.Method()*, The method is called with *instance.Method()*. Instead of using ':' and '.' Like Lua, both use '.'. **** prototype declaration LuneScript analyzes in order from the top of the script. The symbols referenced in the script must be predefined. For example, to declare a variable of class TEST type, it is necessary to define the class TEST in advance. Also, to define alternate classes to reference, It is necessary to prototype either one. The following is an example when Class A and Class B refer to each. #+BEGIN_SRC lns class Super { } proto class ClassB extend Super; class ClassA { let val: ClassB; } class ClassB extend Super{ let val: ClassA; } #+END_SRC Proto is declared as above. In prototype declaration and actual definition, You must declare the same things like pub and extend. *** macro LuneScript adopts a simple macro. It is not an original macro such as Lisp, it is a simple function to the last. The macro is defined as follows. #+BEGIN_SRC lns macro _name ( decl-arg-list ) { { macro-statement } expand-statement } #+END_SRC Macro definition begins with reserved word macro. Then specify the macro name _name. The macro name must begin with _. decl-arg-list declares arguments to be used in macros. The argument of the macro must be a primitive. The macro - statement describes the process of setting the variable to be used in the expand - statement. The contents written in expand-statement are expanded by macro. The following is an example of a simple macro. #+BEGIN_SRC lns macro _hello( word: str ) { print( "hello " .. ,,word ); } _hello( "world" ); // print( "hello " .. "world" ); #+END_SRC In this example there is no macro-statement, there is only expand-statement, The print of expand - statement is expanded. Within a macro, you can write the process just like any other function. However, only part of the standard function can be used within the macro-statement. Macros can not be used to name constants like C. Use enum if you want to use it like that. **** Additional syntax available with macro-statement Within a macro-statement, you can use the following special syntax additionally. - ,,,, - ,,, - ,, - ~`{}~ ',,,,' are operators that convert the immediately following *symbol* to the *character string*. ',,,' is an operator that converts a character string obtained by evaluating immediately following *expression* into a symbol. ~`{}~ can write a statement written in ~`{}~ as it is. Statements written in ~`{}~ in macro, it can be expanded by macro-expand. Within ~`{}~ you write variable reference or function execution, It is not evaluated in the macro-statement. It is evaluated when expanded by macro-expand. ',,' is an operator that evaluates immediately following *expression*. ',,' ',,,' ',,,,' are used within ~`{}~ of macro-statement, Expressions can be evaluated. With macro-expand, use ',,' to expand the immediately following variable. In macro-expand, variables are expanded, not evaluation of expressions. For example, in the next macro, #+BEGIN_SRC lns macro _test2( val:int, funcxx:sym ) { { fn func(val2:int):str { return "mfunc%d" (val2); } let message = "hello %d %s" ( val, ,,,,funcxx ); let stat = `{ print( "macro stat" ); }; let stat2 = `{ for index = 1, 10 { print( "hoge %d" ( index ) ); } }; let mut stat3:stat[] = []; for index = 1, 4 { stat3.insert( `{ print( "foo %d" ( ,,index ) ); } ); } let stat4 = ,,,func( 1 ); } print( ,,message ); ,,funcxx( "macro test2" ); ,,stat; ,,stat2; ,,stat3; ,,stat4( 10 ); } fn mfunc1( val: int ) { print( "mfunc1", val ); } _test2( 1, print ); #+END_SRC It is expanded as follows by macro expansion. #+BEGIN_SRC lns print( "hello 1 print" ); // print( ,,message ); print( "macro test2" ); // ,,funcxx( "macro test2" ); print( "macro stat" ); // ,,stat for index = 1, 10 { // ,,stat2 print( "hoge %d" ( index ) ); } print( "foo %d" ( 1 ) ); // ,,stat3 print( "foo %d" ( 2 ) ); print( "foo %d" ( 3 ) ); print( "foo %d" ( 4 ) ); mfunc1( 10 ); // ,,stat4( 10 ); #+END_SRC The points to pay attention to here are the following points. - print is passed by macro call of _test 2 (1, print) This does not pass function objects held by print, I pass the print symbol itself. - stat 2 expands the for statement itself, stat3 expands the statement list created by the for statement. As mentioned above, the following types can be used in macros in addition to the usual types. - sym type to store symbols - Stat type to store the statement A macro can be called anywhere as long as it defines a statement. It is also possible to define classes and functions in macros. **** Significance of macro There are some restrictions on macros compared to normal functions. Also, the processing that can be performed with macros can be realized by combining functions and the like. So what is the significance of using macros? It is "to decide the motion statically by using a macro". When the same processing is realized by a function, it becomes dynamic processing. On the other hand, if it is realized by a macro, it becomes static processing. What's pleased about this? It is the same as the static typed language is better than the dynamically typed language. Statically analyze information by statically processing it. For example, most of object-oriented function overrides, It can be solved statically by using macros. By making static function calls rather than dynamic function overrides, It becomes easy to follow the source code. It is not good to use macros extensively, It is not ideal to make dynamic processing such as function override easily. Dynamic processing and macros need to be translated appropriately. *** module LuneScript is one file and one module. Each module has a different namespace. For example lune / base / Parser.lns, It becomes the namespace of lune.base.Parser. Functions and classes declared pub in the script file are Accessible from external module. **** import When declaring import when using external module. import must be declared at the top level scope of the script. #+BEGIN_SRC lns import hoge.foo.module1; #+END_SRC In the above, search hoge / foo / module1.lns from the search path and make it available. To access the class and function of module1 Access it like module1.class, module1.func. Imported symbols (in the above case, module 1) can not be treated as variables. Modules can not be cross-referenced. For example, when there is Module A, Module B, Import Module B from Module A, Module A can not be imported from Module B. **** require Declare when using Lua's external module. #+BEGIN_SRC lns let mod: stem! = require( 'module' ); #+END_SRC The result of require is stem! type. Modules can not be cross-referenced. *** _ lune.lua module As mentioned above, files that were trans-compiled into Lua with LuneScript, It can be executed as it is with the Lua command. At this time, no external module is required. This means that within the transcoded Lua code, Indicates that all code necessary for processing is included. For example, if you transcompile the following processing code, #+BEGIN_SRC lns fn func( val:int! ):int { return 1 + unwrap val default 0; } #+END_SRC Lua code will be very long as follows. #+BEGIN_SRC lua -n --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 #+END_SRC The 4th to 18th lines are required for unwrap. This code is output to all Lua files. Since this code itself is common processing, By specifying the * -r * option when transcoding, It is possible to summarize common processing by requiring as separate module. Specifically, specify the -r option as follows. #+BEGIN_SRC txt $ lua lune/base/base.lua -r src.lns save #+END_SRC When this -r option is specified, the above code is converted as follows, It clears considerably. #+BEGIN_SRC lua --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 #+END_SRC Since require ("lune.base._lune") is inserted, It is necessary to set this module so that it can be loaded. It is not necessary to be conscious of it in the environment where the trans compiler operates, Care should be taken when executing the converted Lua source somewhere in another environment. ** emacs correspondence We have prepared a major mode lns - mode.el of emacs for LuneScript editing. https://github.com/ifritJP/LuneScript Please use emacs user. ** Self hosting LuneScript transcompiler is developed with LuneScript except for a few parts. Specifically, within the LuneScript source code size of about 385 KB, 99.99% is developed with LuneScript. The remaining 0.01% is Lua. Developing with self hosting has the following advantages. - Can be used in a script of a certain scale. - Minimize the script creation for testing only. - Because you will be beat down that language, you can realize the strengths and weaknesses of that language. - Disadvantages can be found at an early stage, so you can consider improvement measures immediately. If there are people thinking about designing and developing languages by themselves, I would like to develop with self-hosting.
Click to show internal directories.
Click to hide internal directories.