visitFuncExpr method

  1. @override
Object? visitFuncExpr(
  1. FuncExpr expr
)
override

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;
}