visitFuncExpr method
Implementation
@override
Object? visitFuncExpr(FuncExpr expr) {
// Before we determine what the name of this function is, and whether
// or not it should follow local function conventions or not, we can
// construct a closure.
closure() {
Object? ret;
final int len = expr.body.length;
for (int i = 0; i < len; i++) {
final Stmt stmt = expr.body[i];
try {
ret = stmt.accept(this);
} catch (e) {
if (e is LuaReturnValueException) {
ret = e.value;
break;
} else {
addError(e.toString());
}
}
}
if (ret is LuaObject) {
// Unpack the result if its arity is one.
if (ret.length == 1) ret = ret.readField('1');
}
return ret;
}
// Spec reference: https://www.lua.org/manual/5.4/manual.html#3.4.11
// We need to "build" the function if it's a method on an existing object.
// This means the first node must be the object when node length is more than one
// or the function name if the node length is exactly one.
// We must manually check if the object is not-null in the case that we are adding a field.
// For example. Given an object `t = {}`, we can define `function t.f() end`, however
// we cannot define `function t.a.f() end` without the existence of `t.a = {}` beforehand.
final List<RawExpr> idParts = [...expr.idParts];
final String id = expr.id;
final String linePos = lineInfo(expr.token);
LuaObject luaObj;
if (idParts.length > 1) {
// Object methods cannot be local. This is a syntax error that we will prevent
// here rather than in the parser for simple implementation.
// TODO: move this into the parser.
if (expr.local) {
addError('$linePos Object methods cannot be defined locally.');
return null;
}
// Walk the nodes and create a new method on the object.
LuaObject? parent;
// By virtue of entering this branch in the conditionals,
// we know that idParts.length > 1, therefore, a well-formed
// function declaration must consist of the form `t0.t1. ... .tn`.
// In other words, there's a parent object which this method
// will live inside of as a field.
String field = '';
while (idParts.isNotEmpty) {
field = idParts.removeAt(0).token.lexeme;
final obj = findVar(field);
if (obj == null) break;
parent = obj;
}
// The very last id part should have been consumed.
// If there are still parts left, there was a term
// in the chain that was not defined. This is not alllowed in lua.
if (idParts.isNotEmpty) {
if (parent != null) {
final parentId = parent.id;
addError('$linePos No such field "$field" in "$parentId".');
} else {
// The parser should have prevented this.
addError(
'$linePos Impossible grammar not caught by Parser. Please report!',
);
}
return null;
}
// If we got here, then we have a parent and an object.
// If the object is null, it will be created.
// If the object is not null, it will be overwritten.
luaObj = LuaObject.func(id, expr, closure);
parent!.writeField(field, luaObj);
} else {
// Case: This is a function, not a "method" on an object.
luaObj = LuaObject.func(id, expr, closure);
// Only non-anonymous functions can populate
// the environment with their name.
if (idParts.isNotEmpty) {
if (expr.local) {
defLocal(luaObj);
} else {
defGlobal(luaObj);
}
}
}
return luaObj;
}