tech

[English] [Japanese]

A. LuneScript running on a web browser

A LuneScript environment that runs on a web browser is provided for checking LuneScript operations.

The following two LuneScript environments are available.

  • Environment using fengari
  • Environment using wasm of golang (latest)

Environment using fengari

https://ifritjp.github.io/LuneScript-webFront/lnsc_web_frontend/for_fengari/

The above link has three textareas:

  • For LuneScript code entry
  • For execution result output
  • For converting result output to Lua

Enter LuneScript code in the textarea for entering LuneScript code and press the execute button to convert it to Lua and execute it.

All processing of LuneScript is running on the browser.

However, it has the following restrictions:

  • File operations such as io.open() are not possible
  • Cannot load modules such as import()

Since all processing of LuneScript is running on the browser, it takes time for the first execution on smartphones.

Technical commentary

I use fengari to run Lua VM on the browser and run the LuneScript compiler on that Lua VM.

Please refer to the following article for fengari.

../../lua/fengari/

fengari's Lua VM loads the module with XMLHttpRequest when you require it on Lua. Since LuneScript consists of 30 Lua files, it loads 30 Lua files sequentially. Sequential loading of 30 files is inefficient, so I load them asynchronously with XMLHttpRequest in advance, and switch the process so that the pre-loaded files are loaded during require processing.

Then after loading the LuneScript compiler, it converts the input user's LuneScript code with LuneScript and executes it.

Loading the LuneScript compiler takes some time. Less than 10 seconds on my smartphone, less than 1 second on my PC. Once loaded, LuneScript does not need to be loaded until the browser is reloaded, allowing the user's LuneScript code to be translated and executed.

In addition, as a countermeasure when the user's LuneScript code runs out of control due to a bug, it is forcibly stopped after 2 seconds have passed after execution.

Once loaded, this LuneScript compiler runs completely within the browser, so there is no load on the server side. The server only needs to be able to host static content.

LuneScript fengari support

In order to run LuneScript on fengari Lua VM, the following processing of LuneScript has been modified.

"Switch foreach processing of List<X> type from pairs() to ipairs()"

In the original Lua VM, pairs() and ipairs() are enumerated in the same order in the sequence table, whereas in fengari Lua VM, pairs() seems to be in random order. LuneScript used pairs() instead of ipairs() for simplicity and reliability, but fengari Lua VM switched to ipairs().

This switch is controlled by the –use-ipairs compile option. If –use-ipairs is specified, it will be ipairs(). Currently, pairs() is used when no option is specified, but we are considering reversing the default state in the future.

Run reference sample code

I use this technique to run the example code in the LuneScript reference.

The embedding method is simple, load the following JavaScript,

https://ifritjp.github.io/LuneScript-webFront/lnsc_web_frontend/for_fengari/lunescript-front.js

Just run the lnsFront.setup(), lnsFront.compile() functions.

lnsFront.setup()

lnsFront.setup() loads fengari and LuneScript, associates each HTML element, compiles and executes the LuneScript code stored in the textarea.

setup() is a function of the type

lnsFront.setup( consoleId, luaCodeId, lnsCodeId, executeId )
argument meaning Required/Option
consoleId id of the textarea that stores the console output result Required
luaCodeId the id of the textarea to store the converted Lua code Option
lnsCodeId the id of the textarea containing the lns code Required
executeId the id of the trigger button that starts the conversion Option

If you do not use the above Option element, specify an empty string for the element ID.

For example, if you don't need the converted Lua code, run:

var frontId = lnsFront.setup( consoleId, "", lnsCodeId, executeId )

In addition, this function collectively manages the consoleId given as an argument, issues an ID, and associates it. And that ID will be the return value.

After executing lnsFront.setup(), when you click the button specified by executeId or execute lnsFront.compile(), the LuneScript code in the textarea of the registered lnsCodeId is converted and executed, and the execution result is stored in each textarea.

If you have multiple textareas to enter LuneScript code, run lnsFront.setup() on each.

Notes on lnsFront.setup()

Do not run lnsFront.setup() multiple times for a single textarea.

After executing lnsFront.setup() once, execute the LuneScript code in the registered textarea by clicking the button registered with executeId or by executing lnsFront.compile() .

lnsFront.compile()

lnsFront.compile() compiles and executes the LuneScript code in the textarea registered in lnsFront.setup().

lnsFront.compile( frontId, maxTime )
argument meaning Required/Option
frontId lnsFront.setup() return value of Required
maxTime User LuneScript execution time limit (seconds) Option

If you omit maxTime, the default limit is 2 seconds. If you specify more than 10 seconds, it will be treated as an invalid value and the default value will be set.

Environment using wasm of golang

https://ifritjp.github.io/LuneScript-webFront/lnsc_web_frontend/for_wasm/

The configuration of the link above differs from the configuration of fengari in the following.

  • Uses wasm of golang instead of fengari to run LuneScript
  • Using monaco instead of textarea for editor

Technical commentary

Here is a sample code that uses LuneScript's golang wasm.

<script type="text/javascript" src="./lnsc_frontend.js?symbol=getLnsFrontEnd"></script>
<script>
  addEventListener("load", function( event ) {
    getLnsFrontEnd().then( (frontend) => {
      let result = await frontend.conv2lua( `print( "hello world" );`, {}, true, 4 );
  
      console.log( result.console );
      console.log( result.execLog );
      console.log( result.luaCode );
  } );
} );
</script>

First, load https://ifritjp.github.io/LuneScript-webFront/lnsc_web_frontend/for_wasm/lnsc_frontend.js.

At this time, the query of ?symbol=getLnsFrontEnd is specified.

This query specifies the following symbolic name (getLnsFrontEnd) when registering a function to get the front end.

getLnsFrontEnd().then( (frontend) =>

Specify the getLnsFrontEnd part above in the query.

This script controls golang wasm in LuneScript.

Loading this script will add a function with the symbolic name specified in the query above. If no query is specified, it will be registered with the symbolic name of __getLnsFrontEnd. For convenience, the symbol name __getLnsFrontEnd is used here for explanation.

Running this function will load the LuneScript golang wasm. lnsc_frontend.js runs LuneScript's golang wasm as a worker.

__getLnsFrontEnd() is an async function. When done, the LuneScript frontend object is returned.

This frontend object has the following methods.

  • async conv2lua( lnsCode, execLua, timeoutSec )
  • async getIndent( lnsCode, targetLineNo, endLineNo )
  • async complete( lnsCode, name2code, lineNo, column )
  • async diag( lnsCode, name2code )
  • async runLnsc( name2code, args )

async conv2lua( lnsCode, name2code, andExec, timeoutSec )

This method transcompiles from the given LuneScript to Lua.

Each argument is

  • lnsCode

    • LuneScript code to be transcompiled
  • name2code

    • Module information loading from lnsCode.
    • An Object from a pathname to a module source code string.
  • execLua

    • A bool for whether to run Lua after transcompiling.
    • true to run;
    • If lnsCode imports another module, it currently does not work properly.
  • timeoutSec

    • Processing wait timeout (seconds) from transcompilation to execution.
    • If the process does not finish within the specified time, it will be forced to stop.
    • It is used to deal with infinite loop problems in the LuneScript code specified by lnsCode.

This method returns an object with the following members.

  • luaCode

    • lua code resulting from transcompiling lnsCode
  • console

    • Console output when transpiling (warning and error information)
  • execLog

    • console output when running lua code
    • Disabled if false is specified for execLog

async getIndent( lnsCode, targetLineNo, endLineNo )

This method returns the indentation information for the specified line of the specified LuneScript code.

Each argument is

  • lnsCode

    • LuneScript code to be transcompiled
    • Add the string " ___LNS___" (with a leading space) to the end of endLineNo.
  • targetLineNo

    • Start line to get indent amount (starts with 1 )
  • endLineNo

    • end line to get indent amount (starts with 1 )
    • When calculating only one line, set targetLineNo and endLineNo to the same value.

This method returns an object like this:

{"indent": {"lines": [
     {"info": {"column": 7,"lineNo": 255}},
     {"info": {"column": 10,"lineNo": 256}},
     {"info": {"column": 10,"lineNo": 257}}]}}

where each item indicates:

  • info

    • Indicates the amount to indent the line between targetLineNo and endLineNo
  • lineNo

    • Target line number (starts with 1 )
  • column

    • Indent amount

async complete( lnsCode, name2code, lineNo, column )

This method returns completion information for the specified position in the specified LuneScript code.

Each argument is

  • lnsCode

    • LuneScript code to be transcompiled.
    • Add the string "lune" to the complement position.
  • name2code

    • Module information loading from lnsCode.
    • An Object from a pathname to a module source code string.
  • lineNo

    • line number (starting with 1 ) at which to do completion
  • column

    • column (starting with 1 ) at the position to do completion

As a prerequisite, the process up to lineNo of the target lnsCode must be built normally.

This method returns an object like this:

{ "complete": {"lunescript": {
    "prefix": "pr",
    "candidateList": [
        {"candidate": {"type": "SymbolKind.Fun","displayTxt": "print(&...)"}}
    ]}}}

where each item indicates:

  • prefix

    • Indicates the string from which completion is based.
    • For example, if "print" is detected as a candidate to complement "pr", prefix will contain "pr"
  • candidate

    • Shows completion candidate information
  • type

    • Indicates the type of symbol
  • displayTxt

    • indicates the string to complete

async diag( lnsCode, name2code )

This method retrieves build error information.

Each argument is

  • lnsCode

    • LuneScript code to be transcompiled.
  • name2code

    • Module information loading from lnsCode.
    • An Object from a pathname to a module source code string.

This method returns an object like this:

{ "console" : "" }

where each item indicates:

  • console

    • lnsc build error message

async runLnsc( name2code, args )

This method runs lnsc with command line options args.

This method accepts arbitrary command line options.

Each argument is

  • name2code

    • Module information loading from lnsCode.
    • An Object from a pathname to a module source code string.
  • args

    • String array of command line options

This method returns an object like this:

{ "console" : "" }

where each item indicates:

  • console

    • console output of lnsc