5
$\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$ Commented May 26, 2023 at 21:44
  • $\begingroup$ @RydwolfPrograms Done! $\endgroup$ Commented 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$ Commented 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$ Commented May 31, 2023 at 17:34
  • 1
    $\begingroup$ @Ginger I edited the last paragraph to clarify $\endgroup$ Commented 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$ Commented Jun 1, 2023 at 23:25
  • 1
    $\begingroup$ @MatheusDiasdeSouza thats why I opted for static typing, among other reasons $\endgroup$ Commented Jun 2, 2023 at 1:50

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.