visitBinaryExpr method
Implementation
@override
Object? visitBinaryExpr(BinaryExpr expr) {
final String lineInfo = this.lineInfo(expr.op);
final op = expr.op.type;
LuaObject asLua(Object? obj, String id) =>
obj?.makeLuaRef() ?? LuaObject.variable(id, obj);
Object? metamethod(String name, Object? lhs, Object? rhs) {
final l = asLua(lhs, 'lhs');
final r = asLua(rhs, 'rhs');
LuaObject? mm = l.readMetatable(name)?.as<LuaObject>();
if (mm != null) {
return callLuaFunction(mm, args: [l, r]);
}
mm = r.readMetatable(name)?.as<LuaObject>();
if (mm != null) {
return callLuaFunction(mm, args: [l, r]);
}
// Metamethod not handled by operands.
return null;
}
num coerceString(String str) {
final i = num.tryParse(str);
if (i == null) throw 'Math operation on non-coercible string.';
return i;
}
num asNum(Object? obj) {
if (obj == null) throw 'Operand was null for binary $op.';
if (obj is LuaObject) {
final s = obj.value;
if (s is String) {
return coerceString(s);
}
final value = obj.valueAs<num>();
if (value == null) {
throw 'Failed to coerce lua type "${obj.luaTypeInfo}" to "number".';
}
return value;
} else if (obj is num) {
return obj;
} else if (obj is String) {
return coerceString(obj);
}
throw 'Unexpected type while coercing to "number". Found "${obj.runtimeType}".';
}
int asInt(Object? obj) => asNum(obj).toInt();
String strConcat(Object? lhs, Object? rhs) {
check(LuaObject obj) {
final mm = switch (obj.readMetatable('__concat')) {
final LuaObject o => o,
_ => null,
};
if (!(obj.valueAsInt() is int || obj.value is String)) {
if (mm != null) return mm;
throw 'Attempt to concat ${obj.luaTypeInfo} value.';
}
return null;
}
final luaLhs = asLua(lhs, 'lhs');
final mmLhs = check(luaLhs);
final luaRhs = asLua(rhs, 'rhs');
final mmRhs = check(luaRhs);
if (mmLhs != null) {
return callLuaFunction(
mmLhs,
args: [luaLhs, luaRhs],
).firstOrNull?.toString() ??
'nil';
}
if (mmRhs != null) {
return callLuaFunction(
mmRhs,
args: [luaLhs, luaRhs],
).firstOrNull?.toString() ??
'nil';
}
final strL = luaLhs.toString();
final strR = luaRhs.toString();
return strL + strR;
}
bool isEqual(LuaObject? lhs, LuaObject? rhs) {
if ((lhs?.isTable ?? false) && (rhs?.isTable ?? false)) {
final ok = metamethod('__eq', lhs, rhs)?.unpack();
if (ok != null) return ok.isTruthy;
}
Object? lval = lhs;
if (lhs is LuaObject) {
lval = lhs.value;
}
Object? rval = rhs;
if (rhs is LuaObject) {
rval = rhs.value;
}
return lval == rval;
}
bool isLessThan(LuaObject? lhs, LuaObject? rhs) {
final ok = metamethod('__lt', lhs, rhs)?.unpack();
if (ok != null) return ok.isTruthy;
return switch ((lhs?.value, rhs?.value)) {
(final String s, final String t) => s.compareTo(t) < 0,
(final num n, final num m) => n < m,
(final Object? l, final Object? r) =>
throw 'Operation ${debugLuaTypeInfo(l)} < ${debugLuaTypeInfo(r)} failed.',
};
}
bool isLessThanOrEqual(LuaObject? lhs, LuaObject? rhs) {
if ((lhs?.isTable ?? false) || (rhs?.isTable ?? false)) {
final ok = metamethod('__le', lhs, rhs)?.unpack();
if (ok != null) return ok.isTruthy;
}
return switch ((lhs?.value, rhs?.value)) {
(final String s, final String t) => s.compareTo(t) <= 0,
(final num n, final num m) => n <= m,
(final Object? l, final Object? r) =>
throw 'Operation ${debugLuaTypeInfo(l)} <= ${debugLuaTypeInfo(r)} failed.',
};
}
try {
final lhs = expr.lhs.accept(this)?.unpack();
final rhs = expr.rhs.accept(this)?.unpack();
switch (op) {
case TokenType.kConcat:
return strConcat(lhs, rhs);
case TokenType.kMod:
final ok = metamethod('__mod', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) % asInt(rhs);
case TokenType.kAnd:
if (lhs.isTruthy) return rhs;
return lhs;
case TokenType.kOr:
if (lhs.isTruthy) return lhs;
return rhs;
case TokenType.kBitNot:
final ok = metamethod('__bxor', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) ^ asInt(rhs);
case TokenType.kBitAnd:
final ok = metamethod('__band', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) & asInt(rhs);
case TokenType.kBitOr:
final ok = metamethod('__bor', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) | asInt(rhs);
case TokenType.kBitLShift:
final ok = metamethod('__shl', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) << asInt(rhs);
case TokenType.kBitRShift:
final ok = metamethod('__shr', lhs, rhs);
if (ok != null) return ok;
return asInt(lhs) >> asInt(rhs);
case TokenType.kCarrot:
final ok = metamethod('__pow', lhs, rhs);
if (ok != null) return ok;
return math.pow(asNum(lhs), asNum(rhs));
case TokenType.kDiv:
final ok = metamethod('__div', lhs, rhs);
if (ok != null) return ok;
return asNum(lhs) /
switch (asNum(rhs)) {
== 0.0 => throw 'Divide by zero.',
final num n => n,
};
case TokenType.kDivFloor:
final ok = metamethod('__idiv', lhs, rhs);
if (ok != null) return ok;
final d =
asNum(lhs) /
switch (asNum(rhs)) {
== 0.0 => throw 'Divide by zero.',
final num n => n,
};
return d.floor();
case TokenType.kSub:
final ok = metamethod('__sub', lhs, rhs);
if (ok != null) return ok;
return asNum(lhs) - asNum(rhs);
case TokenType.kAdd:
final ok = metamethod('__add', lhs, rhs);
if (ok != null) return ok;
return asNum(lhs) + asNum(rhs);
case TokenType.kMult:
final ok = metamethod('__mul', lhs, rhs);
if (ok != null) return ok;
return asNum(lhs) * asNum(rhs);
case TokenType.kLTE:
return isLessThanOrEqual(lhs, rhs);
case TokenType.kLT:
return isLessThan(lhs, rhs);
case TokenType.kGT:
return isLessThan(rhs, lhs);
case TokenType.kGTE:
return isLessThanOrEqual(rhs, lhs);
case TokenType.kEQ:
return isEqual(lhs, rhs);
case TokenType.kNEQ:
return !isEqual(lhs, rhs);
default:
throw 'Unsupported binary operation $op.';
}
} catch (e) {
throw '$lineInfo ${e.toString()}';
}
}