visitMemoryAccess method
Implementation
@override
Object? visitMemoryAccess(MemoryAccess memoryAccess) {
// Debugger print info
final StreamPos linePos = memoryAccess.op.pos;
final String lineTag = this.lineTag(memoryAccess.op);
// Recursively descends to the deepest lhs node expression.
// The result must be a lua object in order to be correct.
// Otherwise a value is incorrect and an error can be thrown.
Object? callee = memoryAccess.callee.accept(this);
if (callee is! LuaObject) {
throw '$linePos Expected lua object for operator "${memoryAccess.op.lexeme}", found value: $callee.';
}
final bool indexedTable = memoryAccess.type == MemoryAccessType.table;
final bool funcInvocation = memoryAccess.type == MemoryAccessType.call;
final bool fwdSelfArg = memoryAccess.op.type == TokenType.kColon;
if (callee.skipSemanitcs) {
// Check if special case of skipping semantics and evaluation.
// Regardless is this is a method or field, we don't process it.
// Visit args and return early.
//
// Note that it's not necessary to forward "self"
// b/c no function body will be executed in this case.
if (!fwdSelfArg) {
for (MathExpr expr in memoryAccess.args) {
expr.accept(this);
}
}
final ret = LuaObjectNoSemantics('ret_nosemantic$lineTag');
return ret;
} else if (indexedTable) {
if (memoryAccess.args.length > 1) {
addError('$linePos Multiple indexes on "$callee".');
return null;
}
Object? idx = memoryAccess.field?.accept(this);
if (idx == null || (idx is LuaObject && idx.value == null)) {
addError('$linePos Indexing on "$callee" with nil index.');
return null;
}
// Unpack first.
if (idx is LuaObject) {
idx = idx.value;
}
if (callee.isTable) {
final String? key = switch (idx) {
final String s => s,
final num n => n.toInt().toString(),
final Object o => o.toString(),
null => null,
};
if (key == null) {
addError('$linePos Attempt to index a table with a nil key.');
return null;
}
if (callee.hasField(key)) {
return callee.readField(key);
} else {
return callee.writeField(key, LuaObject.nil(key.toString()));
}
}
throw '$linePos Indexing on "$callee" with index "$idx".';
} else if (funcInvocation) {
// Depending on whether or not this is a normal function call
// using the dot "." notation or if this is a special function call
// using the colon ":" notation, we may need to peak into the rhs
// which will contain the special (latter) case. If so, we want to
// use these supplied arguments for invocation.
LuaObject? callable = callee;
int argsInLen = memoryAccess.args.length;
List<MathExpr> args = memoryAccess.args;
String callableId = callee.id;
// This indicates the node is two parts: (lhs, (functioncall))
// where the lhs is the lua object and the functioncall is a
// callable property on the object. This will forward lhs
// as a new first argument.
if (fwdSelfArg) {
final rhsMemoryAccess = switch (memoryAccess.field) {
final MemoryAccess ma => ma,
_ =>
throw '$linePos Expected function call after colon ":" operator.',
};
// Update the callsite context and fetch the new callableId.
callableId = switch (rhsMemoryAccess.callee) {
final RawExpr r => r.token.lexeme,
final Object? obj =>
throw '$linePos Expected name after colon ":" operator. Found $obj.',
};
// This must be a method on the original callee (lhs).
callable = switch (callee.deref().readField(callableId)) {
final LuaObject lua => lua,
_ => null,
};
// Use the rhs args for invocation.
args = rhsMemoryAccess.args;
// +1 to include implied self.
argsInLen = rhsMemoryAccess.args.length + 1;
}
final func = switch (callable) {
final LuaObject lua => lua.funcDef,
_ => null,
};
if (func == null) {
throw '$linePos Attempt to call a nil value (field "$callableId").';
}
final int defInLen = func.args.length;
final String funcId = switch (func.id) {
'' => '<anonymous>',
final String s => s,
};
// The earlier parser stage would catch if this wasn't true.
final bool isVariadic =
func.args.lastOrNull?.id.type == TokenType.kSpread;
if (!isVariadic && argsInLen != defInLen) {
// There are a few functions that have "overloads".
// This means there is acceptable behavior in the lua routine
// even with less the max number of args.
// This warning can be supressed on a case-by-case basis.
final suppressList = [global.findVar('table')?.readField('insert')];
if (!suppressList.contains(callable)) {
addWarning(
'$linePos Function "$funcId" has $defInLen arguments but received $argsInLen.',
);
}
}
final int fwdArgCount = fwdSelfArg ? 1 : 0;
while (args.length + fwdArgCount < defInLen) {
args.add(NilLiteral(Token.synthesized('nil')));
}
pushScope(/*context: callee*/);
if (fwdSelfArg) {
defLocal(LuaObject.variable('self', callee));
}
final List<LuaObject> varg = [];
final int argCount = switch (isVariadic) {
true => args.length - fwdArgCount,
false => defInLen - fwdArgCount,
};
bool buildVarArgTable = false;
for (int i = 0; i < argCount; i++) {
// Var args are bundled under a hidden variable
// named `arg`. They do not count towards the
// function definition parameter list.
String lexeme = 'arg${i + fwdArgCount}';
if (i + fwdArgCount < func.args.length) {
final arg = func.args.elementAt(i + fwdArgCount);
if (arg.id.type == TokenType.kSpread) {
buildVarArgTable = true;
} else {
lexeme = arg.lexeme;
}
}
final expr = args.elementAt(i);
final next = LuaObject.variable(lexeme, expr.accept(this));
if (buildVarArgTable) {
varg.add(next);
} else {
defLocal(next);
}
}
defLocal(
LuaObject.table('arg', {
for (int i = 0; i < varg.length; i++) '${i + 1}': varg[i],
}),
);
Object? ret;
try {
ret = callable!.call();
} catch (e) {
throw '$linePos ${e.toString()}';
} finally {
popScope();
}
return ret?.toLua('ret');
}
// Else this must be a field on an object.
assert(
memoryAccess.type == MemoryAccessType.field,
'Codepath expected function invocation.',
);
final String fieldName = switch (memoryAccess.field) {
final RawExpr r => r.token.lexeme,
final Object? other =>
throw '$linePos Expected a valid property name on $callee, found: $other',
};
// Primitives cannot have fields.
if (callee.type == LuaType.value) {
throw '$linePos "$callee" is a ${callee.luaTypeInfo} and cannot have fields.';
}
if (callee.hasField(fieldName)) return callee.readField(fieldName);
// Else, create the field.
return callee.writeField(fieldName, LuaObject.nil(fieldName));
}