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