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)?.unpack();
if (callee is! LuaObject) {
final v = debugLuaTypeInfo(callee);
throw '$linePos Expected lua object for operator "${memoryAccess.op.lexeme}". Was $v.';
}
// After the parser, fields and table keys behave the same.
final bool indexedTable = switch (memoryAccess.type) {
MemoryAccessType.table || MemoryAccessType.field => true,
_ => false,
};
final bool funcInvocation = memoryAccess.type == MemoryAccessType.call;
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) {
throw '$linePos Multiple indexes on "$callee".';
}
final Object? idx = memoryAccess.field?.accept(this);
if (callee.isTable) {
getValue(v) => switch (v) {
final LuaObject lo => getValue(lo.value),
final Object o => o,
null => null,
};
final Object key = switch (memoryAccess.type) {
MemoryAccessType.field => memoryAccess.field!.token.lexeme,
_ => getValue(idx),
};
if (callee.hasField(key)) {
return callee.readField(key);
} else {
final midx = callee.readMetatable('__index');
if (midx == null) {
final res = callee.writeField(key, LuaObject.nil(key.toString()))!;
return res;
}
if (midx is! LuaObject) {
throw '$linePos Metamethod __index was an invalid type "${debugLuaTypeInfo(midx)}"';
}
if (midx.isFunc) {
return callLuaFunction(midx, args: [callee, key]);
}
// Else, expect table for __index.
return midx.readField(key);
}
}
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;
List<LuaObject> 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.visitArgPack(this);
// +1 to include implied self.
argsInLen = args.length + 1;
} else {
args = memoryAccess.args.visitArgPack(this);
argsInLen = args.length;
}
final mcall = switch (callable?.readMetatable('__call')) {
final LuaObject lo => lo,
_ => null,
};
FuncExpr? func = callable?.funcDef ?? mcall?.funcDef;
Scope? pscope = callable?.scope ?? mcall?.scope;
// The first argument to __call is self.
if (mcall != null) {
fwdSelfArg = true;
}
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 fn>',
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 prevScope = scope;
pushScope(parent: pscope);
Object? ret;
try {
final List<LuaObject> varg = [];
final int argCount = switch (isVariadic) {
true => args.length,
false => defInLen,
};
if (fwdSelfArg && argCount > 0) {
args.insert(0, LuaObject.variable(func.args.first.lexeme, callee));
}
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';
if (i < func.args.length) {
final arg = func.args.elementAt(i);
if (arg.id.type == TokenType.kSpread) {
buildVarArgTable = true;
} else {
lexeme = arg.lexeme;
}
}
final arg = switch (i < args.length) {
true => args.elementAt(i),
false => null,
};
final next = LuaObject.variable(lexeme, arg);
if (buildVarArgTable) {
varg.add(next);
} else {
defLocal(next);
}
}
defLocal(
LuaObject.table('arg', {
for (int i = 0; i < varg.length; i++) '${i + 1}': varg[i],
}),
);
ret = mcall?.call() ?? callable!.call();
} on LuaReturnValueException {
rethrow;
} catch (e) {
throw '$linePos ${e.toString()}';
} finally {
restoreScope(prevScope);
}
return ret;
}
throw 'Unexpected code path while accessing memory on $callee.';
}