visitAssignStmt method

  1. @override
Object? visitAssignStmt(
  1. AssignStmt assignStmt
)
override

Visits rhs first and then evaluate lhs.

Implementation

@override
Object? visitAssignStmt(AssignStmt assignStmt) {
  final rhs = assignStmt.rhs.accept(this)?.unpack();
  Object? lhs;

  // Special behavior happens in lua when we update a value
  // in a table by key. In order to avoid triggering __index
  // by visiting the memory access node, we partially inspect
  // it here. We only need enough information to obtain:
  // 1. the table
  // 2. the key
  // 3. and we have the incoming value (rhs).
  if (assignStmt.lhs is MemoryAccess) {
    // We need to check if this is a table update operation by key.
    final MemoryAccess mem = assignStmt.lhs as MemoryAccess;
    final StreamPos linePos = mem.op.pos;

    if (mem.type == MemoryAccessType.table) {
      lhs = mem.callee.accept(this)?.unpack();
      final key = mem.args.firstOrNull?.accept(this);

      // We need to check if it's actually a table.
      if (lhs is LuaObject && lhs.isTable) {
        final newindex = lhs.readMetatable('__newindex');

        // Now check if we have __newindex defined.
        if (newindex is LuaObject) {
          if (newindex.isFunc) {
            return callLuaFunction(newindex, args: [lhs, key, rhs]);
          } else if (newindex.isTable) {
            // TODO: table lookup shortcut
          }

          throw '$linePos Object for __newindex is not a valid type. Was "${debugLuaTypeInfo(newindex)}".';
        } else if (newindex == null) {
          // Regular table update operation by key.
          lhs.writeField(key ?? LuaObject.nil('key'), rhs);
        } else {
          throw '$linePos Indexing on non-table type "${debugLuaTypeInfo(lhs)}".';
        }
      }
    }
    // No further inspection required.
    // Fallthrough to codepath below.
  }

  lhs = assignStmt.lhs.accept(this);

  if (lhs == null && assignStmt.lhs is RawExpr) {
    // If this variable is not defined, it is now
    // and is also in the global scope.
    final id = (assignStmt.lhs as RawExpr).token.lexeme;
    return defGlobal(LuaObject.variable(id, rhs));
  } else if (lhs is LuaObject) {
    // Check if this object has a <const> attribute.
    if(lhs.attr == 'const') {
      final StreamPos linePos = assignStmt.token.pos;
      throw '$linePos Attempt to re-assign a constant variable ${lhs.id}.';
    }
    if (rhs is LuaObject) {
      if (lhs.deref() != rhs.deref()) {
        // EDGE CASE. Promote functions up to the lhs value.
        // This is because the `fieldValueAs<Function>()` method
        // expects the [LuaObject] with a field name for a function
        // to be resolved correclty.
        if (rhs.isFunc) {
          lhs.value = rhs.value;
          lhs.funcDef = rhs.funcDef;
          lhs.scope = rhs.scope;
        } else {
          lhs.value = rhs;
        }
      }
    } else {
      lhs.value = rhs;
    }
  }

  return lhs;
}