evaluate method
Evaluate this line and return the value that would be stored into the lhs.
Implementation
Value? evaluate(Context context) {
if (op == LineOp.assignA ||
op == LineOp.returnA ||
op == LineOp.assignImplicit) {
// Assignment is a bit of a special case. It's EXTREMELY common
// in TAC, so needs to be efficient, but we have to watch out for
// the case of a RHS that is a list or map. This means it was a
// literal in the source, and may contain references that need to
// be evaluated now.
if (rhsA is ValList || rhsA is ValMap) {
return rhsA!.fullEval(context);
} else if (rhsA == null) {
return null;
} else {
return rhsA!.val(context);
}
}
if (op == LineOp.copyA) {
// This opcode is used for assigning a literal. We actually have
// to copy the literal, in the case of a mutable object like a
// list or map, to ensure that if the same code executes again,
// we get a new, unique object.
if (rhsA is ValList) {
return (rhsA as ValList).evalCopy(context);
} else if (rhsA is ValMap) {
return (rhsA as ValMap).evalCopy(context);
} else if (rhsA == null) {
return null;
} else {
return rhsA!.val(context);
}
}
final opA = rhsA?.val(context);
final opB = rhsB?.val(context);
if (op == LineOp.aIsaB) {
if (opA == null) return ValNumber.truth(opB == null);
return ValNumber.truth(opA.isA(opB, context.vm!));
}
if (op == LineOp.newA) {
// Create a new map, and set __isa on it to operand A (after
// verifying that this is a valid map to subclass).
if (opA is! ValMap) {
throw RuntimeException("argument to 'new' must be a map");
} else if (opA == context.vm!.stringType) {
throw RuntimeException(
"invalid use of 'new'; to create a string, use quotes, e.g. \"foo\"");
} else if (opA == context.vm!.listType) {
throw RuntimeException(
"invalid use of 'new'; to create a list, use square brackets, e.g. [1,2]");
} else if (opA == context.vm!.numberType) {
throw RuntimeException(
"invalid use of 'new'; to create a number, use a numeric literal, e.g. 42");
} else if (opA == context.vm!.functionType) {
throw RuntimeException(
"invalid use of 'new'; to create a function, use the 'function' keyword");
}
final ValMap newMap = ValMap();
newMap.setElem(ValString.magicIsA, opA);
return newMap;
}
if (op == LineOp.elemBofA && opB is ValString) {
// You can now look for a string in almost anything...
// and we have a convenient (and relatively fast) method for it:
return ValSeqElem.resolve(opA!, (opB).value, context, ValuePointer());
}
// check for special cases of comparison to null (works with any type)
if (op == LineOp.aEqualB && (opA == null || opB == null)) {
if ((opA == null || opA is ValNull) && (opB == null || opB is ValNull)) {
return ValNumber.one;
}
return ValNumber.truth(opA == opB);
}
if (op == LineOp.aNotEqualB && (opA == null || opB == null)) {
if ((opA == null || opA is ValNull) && (opB == null || opB is ValNull)) {
return ValNumber.zero;
}
return ValNumber.truth(opA != opB);
}
// check for implicit coersion of other types to string; this happens
// when either side is a string and the operator is addition.
if ((opA is ValString || opB is ValString) && op == LineOp.aPlusB) {
if (opA == null) return opB;
if (opB == null) return opA;
final String sA = opA.toStringWithVM(context.vm!);
final String sB = opB.toStringWithVM(context.vm!);
if (sA.length + sB.length > ValString.maxSize) {
throw LimitExceededException("string too large");
}
return ValString(sA + sB);
}
if (opA is ValNumber) {
final double fA = (opA).value;
switch (op) {
case LineOp.gotoA:
context.lineNum = fA.toInt();
return null;
case LineOp.gotoAifB:
if (opB != null && opB.boolValue()) context.lineNum = fA.toInt();
return null;
case LineOp.gotoAifTrulyB:
// Unlike GotoAifB, which branches if B has any nonzero
// value (including 0.5 or 0.001), this branches only if
// B is TRULY true, i.e., its integer value is nonzero.
// (Used for short-circuit evaluation of "or".)
int i = 0;
if (opB != null) i = opB.intValue();
if (i != 0) context.lineNum = fA.toInt();
return null;
case LineOp.gotoAifNotB:
if (opB == null || !opB.boolValue()) context.lineNum = fA.toInt();
return null;
case LineOp.callIntrinsicA:
// NOTE: intrinsics do not go through NextFunctionContext. Instead
// they execute directly in the current context. (But usually, the
// current context is a wrapper function that was invoked via
// Op.CallFunction, so it got a parameter context at that time.)
final result = Intrinsic.execute(
fA.toInt(),
context,
context.partialResult,
);
if (result.done) {
context.partialResult = null;
return result.result;
}
// OK, this intrinsic function is not yet done with its work.
// We need to stay on this same line and call it again with
// the partial result, until it reports that its job is complete.
context.partialResult = result;
context.lineNum--;
return null;
case LineOp.notA:
return ValNumber(1.0 - _absClamp01(fA));
default:
if (opB is ValNumber || opB == null) {
final double fB = opB != null ? (opB as ValNumber).value : 0;
switch (op) {
case LineOp.aPlusB:
return ValNumber(fA + fB);
case LineOp.aMinusB:
return ValNumber(fA - fB);
case LineOp.aTimesB:
return ValNumber(fA * fB);
case LineOp.aDividedByB:
return ValNumber(fA / fB);
case LineOp.aModB:
return ValNumber(fA % fB);
case LineOp.aPowB:
return ValNumber(pow(fA, fB).toDouble());
case LineOp.aEqualB:
return ValNumber.truth(fA == fB);
case LineOp.aNotEqualB:
return ValNumber.truth(fA != fB);
case LineOp.aGreaterThanB:
return ValNumber.truth(fA > fB);
case LineOp.aGreatOrEqualB:
return ValNumber.truth(fA >= fB);
case LineOp.aLessThanB:
return ValNumber.truth(fA < fB);
case LineOp.aLessOrEqualB:
return ValNumber.truth(fA <= fB);
case LineOp.aAndB:
final double effectiveFB;
if (opB is! ValNumber) {
effectiveFB = opB != null && opB.boolValue() ? 1 : 0;
} else {
effectiveFB = fB;
}
return ValNumber(_absClamp01(fA * effectiveFB));
case LineOp.aOrB:
final double effectiveFB;
if (opB is! ValNumber) {
effectiveFB = opB != null && opB.boolValue() ? 1 : 0;
} else {
effectiveFB = fB;
}
return ValNumber(
_absClamp01(fA + effectiveFB - fA * effectiveFB));
default:
break;
}
}
// Handle equality testing between a number (opA) and a non-number (opB).
// These are always considered unequal.
if (op == LineOp.aEqualB) return ValNumber.zero;
if (op == LineOp.aNotEqualB) return ValNumber.one;
}
} else if (opA is ValString) {
final sA = opA.value;
if (op == LineOp.aTimesB || op == LineOp.aDividedByB) {
var factor = 0.0;
if (op == LineOp.aTimesB) {
Check.type<ValNumber>(opB, "string replication");
factor = (opB as ValNumber).value;
} else {
Check.type<ValNumber>(opB, "string division");
factor = 1.0 / (opB as ValNumber).value;
}
if (factor.isNaN || factor.isInfinite) return null;
if (factor <= 0) return ValString.empty;
final repeats = factor.toInt();
if (repeats * sA.length > ValString.maxSize) {
throw LimitExceededException("string too large");
}
final result = StringBuffer();
for (var i = 0; i < repeats; i++) {
result.write(sA);
}
final extraChars = (sA.length * (factor - repeats)).toInt();
if (extraChars > 0) result.write(sA.substring(0, extraChars));
return ValString(result.toString());
}
if (op == LineOp.elemBofA || op == LineOp.elemBofIterA) {
return (opA).getElem(opB!);
}
if (opB == null || opB is ValString) {
final sB = opB == null ? null : (opB as ValString).value;
switch (op) {
case LineOp.aMinusB:
if (opB == null) return opA;
if (sA.endsWith(sB!)) {
return ValString(sA.substring(0, sA.length - sB.length));
}
return opA;
case LineOp.notA:
return ValNumber.truth(sA.isEmpty);
case LineOp.aEqualB:
return ValNumber.truth(sA == sB);
case LineOp.aNotEqualB:
return ValNumber.truth(sA != sB);
case LineOp.aGreaterThanB:
return ValNumber.truth(sA.compareTo(sB!) > 0);
case LineOp.aGreatOrEqualB:
return ValNumber.truth(sA.compareTo(sB!) >= 0);
case LineOp.aLessThanB:
return ValNumber.truth(sA.compareTo(sB!) < 0);
case LineOp.aLessOrEqualB:
return ValNumber.truth(sA.compareTo(sB!) <= 0);
case LineOp.lengthOfA:
return ValNumber(sA.length.toDouble());
default:
break;
}
} else {
// RHS is neither null nor a string.
// We no longer automatically coerce in all these cases; about
// all we can do is equal or unequal testing.
// (Note that addition was handled way above here.)
if (op == LineOp.aEqualB) return ValNumber.zero;
if (op == LineOp.aNotEqualB) return ValNumber.one;
}
} else if (opA is ValList) {
final list = opA.values;
if (op == LineOp.elemBofA || op == LineOp.elemBofIterA) {
// list indexing
return opA.getElem(opB!);
} else if (op == LineOp.lengthOfA) {
return ValNumber(list.length.toDouble());
} else if (op == LineOp.aEqualB) {
return ValNumber(opA.equality(opB!));
} else if (op == LineOp.aNotEqualB) {
return ValNumber(1.0 - opA.equality(opB!));
} else if (op == LineOp.aPlusB) {
// List concatenation
Check.type<ValList>(opB, "list concatenation");
final list2 = (opB as ValList).values;
if (list.length + list2.length > ValList.maxSize) {
throw LimitExceededException("list too large");
}
final result = <Value?>[];
for (final v in list) {
result.add(context.valueInContext(v));
}
for (final v in list2) {
result.add(context.valueInContext(v));
}
return ValList(result);
} else if (op == LineOp.aTimesB || op == LineOp.aDividedByB) {
// list replication (or division)
var factor = 0.0;
if (op == LineOp.aTimesB) {
Check.type<ValNumber>(opB, "list replication");
factor = (opB as ValNumber).value;
} else {
Check.type<ValNumber>(opB, "list division");
factor = 1.0 / (opB as ValNumber).value;
}
if (factor.isNaN || factor.isInfinite) return null;
if (factor <= 0) return ValList();
final finalCount = (list.length * factor).toInt();
if (finalCount > ValList.maxSize) {
throw LimitExceededException("list too large");
}
final result = <Value?>[];
for (var i = 0; i < finalCount; i++) {
result.add(context.valueInContext(list[i % list.length]));
}
return ValList(result);
} else if (op == LineOp.notA) {
return ValNumber.truth(!opA.boolValue());
}
} else if (opA is ValMap) {
if (op == LineOp.elemBofA) {
// map lookup
// (note, cases where opB is a string are handled above, along with
// all the other types; so we'll only get here for non-string cases)
final ValSeqElem se = ValSeqElem(opA, opB);
return se.val(context);
// (This ensures we walk the "__isa" chain in the standard way.)
} else if (op == LineOp.elemBofIterA) {
// With a map, ElemBofIterA is different from ElemBofA. This one
// returns a mini-map containing a key/value pair.
return (opA).getKeyValuePair(opB!.intValue());
} else if (op == LineOp.lengthOfA) {
return ValNumber((opA).count.toDouble());
} else if (op == LineOp.aEqualB) {
return ValNumber((opA).equality(opB!));
} else if (op == LineOp.aNotEqualB) {
return ValNumber(1.0 - (opA).equality(opB!));
} else if (op == LineOp.aPlusB) {
// map combination
final map = opA.map;
Check.type<ValMap>(opB, "map combination");
final map2 = (opB as ValMap).map;
final ValMap result = ValMap();
for (final kv in map.entries) {
result.map[kv.key] = context.valueInContext(kv.value);
}
for (final kv in map2.entries) {
result.map[kv.key] = context.valueInContext(kv.value);
}
return result;
} else if (op == LineOp.notA) {
return ValNumber.truth(!opA.boolValue());
}
} else if (opA is ValFunction && opB is ValFunction) {
final fA = (opA).function;
final fB = (opB).function;
switch (op) {
case LineOp.aEqualB:
return ValNumber.truth(identical(fA, fB));
case LineOp.aNotEqualB:
return ValNumber.truth(!identical(fA, fB));
default:
break;
}
} else {
// opA is something else... perhaps null
switch (op) {
case LineOp.bindAssignA:
context.variables ??= ValMap();
final ValFunction valFunc = opA as ValFunction;
return valFunc.bindAndCopy(context.variables!);
case LineOp.notA:
return opA != null && opA.boolValue()
? ValNumber.zero
: ValNumber.one;
case LineOp.elemBofA:
if (opA == null) {
throw TypeException(
"Null Reference Exception: can't index into null");
} else {
throw TypeException("Type Exception: can't index into this type");
}
default:
break;
}
}
if (op == LineOp.aAndB || op == LineOp.aOrB) {
// We already handled the case where opA was a number above;
// this code handles the case where opA is something else.
final double fA = opA != null && opA.boolValue() ? 1 : 0;
double fB;
if (opB is ValNumber) {
fB = (opB).value;
} else {
fB = opB != null && opB.boolValue() ? 1 : 0;
}
double result;
if (op == LineOp.aAndB) {
result = _absClamp01(fA * fB);
} else {
result = _absClamp01(fA + fB - fA * fB);
}
return ValNumber(result);
}
return null;
}