4
$\begingroup$

The most popular language that transpiles to Lua is MoonScript. One problem with the MoonScript's compiler is that when a runtime error occurs on e.g. a table access the diagnostic source location is relative to the transpiled Lua and not to the source language.

E.g. this MoonScript program:

class C
x!

Compiles into:

local C
do
  local _class_0
  local _base_0 = { }
  _base_0.__index = _base_0
  _class_0 = setmetatable({
    __init = function() end,
    __base = _base_0,
    __name = "C"
  }, {
    __index = _base_0,
    __call = function(cls, ...)
      local _self_0 = setmetatable({}, _base_0)
      cls.__init(_self_0, ...)
      return _self_0
    end
  })
  _base_0.__class = _class_0
  C = _class_0
end
return x()

When you run it, you get:

input:21: attempt to call a nil value (global 'x')

See, line 21? Should be line 2.

The Lua interpreter supports receiving bytecode, .luac. It supports source locations in instructions. As per https://www.lua.org/manual/5.1/luac.html, you can even strip source locations when compiling Lua to .luac:

-s strip debug information before writing the output file. This saves some space in very large chunks, but if errors occur when running a stripped chunk, then the error messages may not contain the full information they usually do. For instance, line numbers and names of local variables are lost.

Lua has no source maps like in PHP and JavaScript. I thought one way to adding source locations into the output code is by outputting bytecode, but I also found out someone got a hacky solution that probably involves use of pcall to handle exceptions and replace source locations.

So the question is, is there any language that compiles to Lua bytecode with source maps (or locations)?

Unofficial bytecode reference

https://the-ravi-programming-language.readthedocs.io/en/latest/lua_bytecode_reference.html

If you search for "debug" in this page, you can find:

GETUPVAL and SETUPVAL instructions use internally-managed upvalue lists. The list of upvalue name strings that are found in a function prototype is for debugging purposes; it is not used by the Lua virtual machine and can be stripped by luac.

Error call stack

Calling Lua's error() also needs source mapping, e.g.:

error: illegal argument
  at foo/lib.lua:1:1
  at caller.lua:1:1
$\endgroup$
5
  • 2
    $\begingroup$ Hi Could you clarify the title of the question to match what you're asking about specifically? E.g., "Do any approaches exist for adding source locations to Lua bytecode from transpiled languages" $\endgroup$ May 26, 2023 at 21:44
  • $\begingroup$ @RydwolfPrograms Done! $\endgroup$
    – Hydroper
    May 26, 2023 at 21:52
  • 2
    $\begingroup$ Many languages do directly compile to lua bytecode, though I don't know why moonscript specifically chose not to $\endgroup$
    – mousetail
    May 27, 2023 at 9:56
  • 2
    $\begingroup$ I'm voting to close this question because it isn't very focused. It's well-researched and thought-out, but I can't understand what you're actually asking us. Do you want examples of languages that compile to Lua bytecode and include source maps? If so, please edit the question to make that clearer. $\endgroup$
    – Ginger
    May 31, 2023 at 17:34
  • 1
    $\begingroup$ @Ginger I edited the last paragraph to clarify $\endgroup$
    – Hydroper
    May 31, 2023 at 17:36

1 Answer 1

3
$\begingroup$

My language compiles to Lua, not bytecode, but I think the approach I use can be extended to bytecode as well. My transpiled code doesn't have many possible failure points (compile-time type checking handles most of that for me), but when there is one, I make sure to insert a "failure function" with the source code location.

For example, my language has nullable types, and with that comes the null assertion operator !. The "intrinsics file" that has the Lua code needed for the runtime to work has the following function:

function assertNonNull(value, location)
    if value == nil then
        if location ~= nil then
            error("Expression is null: " .. location, 2)
        else
            error("Expression is null", 2)
        end
    else
        return value
    end
end

The standard library has this function:

fun readLine(): String {
    return extern { io.read("*l") }!
}

The only thing you need to pay attention to here is the ! at the end of the expression. The function gets transpiled into the following code (prettified for your sanity):

rol_readNumber_5982b = function ()
    return assertNonNull(
        io.read("*n"), 
        "file 'rio.rol', line 25, column 11, statement 'extern { io.read(\"*n\") }!'"
    )
end

Note how the ! is transpiled into the function call assertNonNull. Also note now the source information is directly compiled into the result. I imagine you could do a similar thing with bytecode.

$\endgroup$
2
  • $\begingroup$ Considering this kind of output is only neccessary for debugging a program, it looks useful! Though what if the language is dynamically-typed (e.g. MoonScript) and you have to compile the expression o.x? There can be an extra overhead if you add an error-checking step before that o.x. $\endgroup$
    – Hydroper
    Jun 1, 2023 at 23:25
  • 1
    $\begingroup$ @MatheusDiasdeSouza thats why I opted for static typing, among other reasons $\endgroup$
    – Seggan
    Jun 2, 2023 at 1:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .