src/languages/smalltalk/domain-object-model/operator-usage.ts
// tom-weatherhead/thaw-grammar/src/languages/smalltalk/domain-object-model/operator-usage.ts
import { EvaluationException, Name } from 'thaw-interpreter-core';
import { EnvironmentFrame } from '../../../common/domain-object-model/environment-frame';
import { IVariable } from '../../../common/domain-object-model/variable';
import {
// ISmalltalkClass,
ISmalltalkEnvironmentFrame,
ISmalltalkEvaluateOptions,
ISmalltalkExpression,
ISmalltalkFunctionDefinition,
ISmalltalkGlobalInfo,
ISmalltalkStringValue,
ISmalltalkValue
} from './interfaces/iexpression';
import { SmalltalkArray } from './data-types/array';
import { SmalltalkBlock, unblockValue } from './data-types/block';
import { isSmalltalkCharacter } from './data-types/character';
import { SmalltalkFloat } from './data-types/float';
import { SmalltalkInteger } from './data-types/integer';
import { isSmalltalkString, SmalltalkString } from './data-types/string';
import { isSmalltalkSymbol, SmalltalkSymbol } from './data-types/symbol';
import { SmalltalkUserValue } from './data-types/user-value';
import { selfVar } from './bootstrap';
// import { SmalltalkEnvironmentFrame } from './environment-frame';
import { isSmalltalkVariable } from './variable';
// export class SmalltalkOperatorUsage implements ISmalltalkExpression {
//
// public toString(): string {
// if (this.expressionList.value.length === 0) {
// return `(${this.operatorName})`;
// }
//
// return `(${this.operatorName} ${this.expressionList})`;
// }
//
// // This is virtual because Scheme.PrimOp overrides it.
//
// public evaluate(
// localEnvironment: ISmalltalkEnvironmentFrame | undefined,
// receiver: ISmalltalkValue,
// c: ISmalltalkClass | undefined,
// globalInfo: ISmalltalkGlobalInfo
// ): ISmalltalkValue {
// const actualNumArgs = this.expressionList.value.length;
// const expectedNumArgs = this.tryGetExpectedNumArgs(globalInfo);
//
// if (expectedNumArgs === undefined) {
// throw new EvaluationException(
// `OperatorUsage : Unknown operator name '${this.operatorName.value}`,
// this.operatorName.line,
// this.operatorName.column
// );
// } else if (expectedNumArgs >= 0 && actualNumArgs !== expectedNumArgs) {
// throw new EvaluationException(
// `OperatorUsage : Expected ${expectedNumArgs} argument(s) for operator '${this.operatorName.value}', instead of the actual ${actualNumArgs} argument(s)`,
// this.operatorName.line,
// this.operatorName.column
// );
// }
//
// // T macroResult;
//
// // if (TryInvokeMacro(expressionList.value, localEnvironment, globalInfo, out macroResult))
// // {
// // return macroResult;
// // }
//
// const evaluatedArguments = this.expressionList.value.map(
// (expr: ISmalltalkExpression) => expr.evaluate(localEnvironment, ?, ?, globalInfo)
// );
// // var argTypesErrorMessage = CheckArgTypes(evaluatedArguments);
//
// // if (!string.IsNullOrEmpty(argTypesErrorMessage))
// // {
// // throw new EvaluationException(
// // string.Format("Operator '{0}': {1}", operatorName.Value, argTypesErrorMessage), this.operatorName.line, this.operatorName.column);
// // }
//
// return this.evaluateAux(evaluatedArguments, localEnvironment, globalInfo);
// }
//
// protected tryGetExpectedNumArgs(globalInfo: ISmalltalkGlobalInfo): number | undefined {
// if (['<', '>', '+', '-', '*', '/'].indexOf(this.operatorName.value) >= 0) {
// return 2;
// }
//
// const fnDefRaw = globalInfo.functionDefinitions.get(this.operatorName.value);
// // const macroDef = globalInfo.MacroDefinitions.get(this.operatorName);
//
// switch (this.operatorName.value) {
// case 'print':
// return -1; // Was 1. print now takes any number of arguments.
//
// case '=':
// return 2;
//
// default:
// if (fnDefRaw !== undefined) {
// const fnDef = fnDefRaw as FunctionDefinition<ISmalltalkValue>;
//
// return fnDef.argList.value.length;
// // } else if (globalInfo.MacroDefinitions != null && globalInfo.MacroDefinitions.ContainsKey(this.operatorName)) {
// // return globalInfo.MacroDefinitions[operatorName].ArgumentCount;
// } else {
// return undefined;
// }
// }
// }
//
// // protected virtual bool TryInvokeMacro(
// // List<IExpression<T>> unevaluatedArguments,
// // EnvironmentFrame<T> localEnvironment,
// // IGlobalInfo<T> globalInfo,
// // out T macroResult)
// // {
// // macroResult = default(T);
// // return false;
// // }
//
// protected evaluateAux(
// evaluatedArguments: ISmalltalkValue[],
// localEnvironment: ISmalltalkEnvironmentFrame,
// globalInfo: ISmalltalkGlobalInfo
// ): ISmalltalkValue {
// const firstArgAsInt =
// evaluatedArguments.length > 0 && globalInfo.valueIsInteger(evaluatedArguments[0])
// ? globalInfo.valueAsInteger(evaluatedArguments[0])
// : 0;
// const secondArgAsInt =
// evaluatedArguments.length > 1 && globalInfo.valueIsInteger(evaluatedArguments[1])
// ? globalInfo.valueAsInteger(evaluatedArguments[1])
// : 0;
//
// const twoArgumentIntegerPredicateRaw = this.twoArgumentIntegerPredicates.get(
// this.operatorName.value
// );
// const twoArgumentIntegerOperatorRaw = this.twoArgumentIntegerOperators.get(
// this.operatorName.value
// );
//
// // if (IntegerOperatorKeeper.TwoArgumentOperators.ContainsKey(this.operatorName.Value))
// if (typeof twoArgumentIntegerOperatorRaw !== 'undefined') {
// // return globalInfo.IntegerAsValue(IntegerOperatorKeeper.TwoArgumentOperators[operatorName.Value](firstArgAsInt, secondArgAsInt));
// return globalInfo.integerAsValue(
// (twoArgumentIntegerOperatorRaw as (operand1: number, operand2: number) => number)(
// firstArgAsInt,
// secondArgAsInt
// )
// );
// // } else if (IntegerOperatorKeeper.TwoArgumentPredicates.ContainsKey(this.operatorName.Value))
// } else if (typeof twoArgumentIntegerPredicateRaw !== 'undefined') {
// // return IntegerOperatorKeeper.TwoArgumentPredicates[operatorName.Value](firstArgAsInt, secondArgAsInt) ? globalInfo.TrueValue : globalInfo.FalseValue;
// return (
// twoArgumentIntegerPredicateRaw as (operand1: number, operand2: number) => boolean
// )(firstArgAsInt, secondArgAsInt)
// ? globalInfo.trueValue
// : globalInfo.falseValue;
// }
//
// const fnDefRaw = globalInfo.functionDefinitions.get(this.operatorName.value);
//
// switch (this.operatorName.value) {
// case '=':
// return evaluatedArguments[0] === evaluatedArguments[1]
// ? globalInfo.trueValue
// : globalInfo.falseValue;
//
// // case 'print':
// // globalInfo.print(evaluatedArguments);
// //
// // return evaluatedArguments[0];
//
// default:
// if (typeof fnDefRaw !== 'undefined') {
// // Evaluate a user-defined function.
// const newEnvironment = new EnvironmentFrame<ISmalltalkValue>(
// globalInfo.dynamicScoping ? localEnvironment : globalInfo.globalEnvironment
// );
//
// // if (globalInfo.Debug)
// // {
// // UpdateStackTrace(localEnvironment, newEnvironment, operatorName.Line, operatorName.Column);
// // }
//
// const fnDef = fnDefRaw; // as ISmalltalkFunctionDefinition;
//
// newEnvironment.compose(fnDef.argList, evaluatedArguments);
//
// return fnDef.body.evaluate(newEnvironment, ?, ?, globalInfo);
// }
//
// throw new EvaluationException(
// `EvaluateAux() : Unknown operator name '${this.operatorName.value}'`,
// this.operatorName.line,
// this.operatorName.column
// );
// }
// }
// }
const operatorsThatTakeEitherIntOrFloatArgs = ['=', '<', '>', '+', '-', '*', '/'];
const oneArgumentDoubleOperators = new Map<string, (x: number) => number>();
const twoArgumentDoublePredicates = new Map<string, (x: number, y: number) => boolean>();
const twoArgumentDoubleOperators = new Map<string, (x: number, y: number) => number>();
oneArgumentDoubleOperators.set('exp', (x: number) => Math.exp(x));
oneArgumentDoubleOperators.set('ln', (x: number) => Math.log(x));
oneArgumentDoubleOperators.set('sin', (x: number) => Math.sin(x));
oneArgumentDoubleOperators.set('cos', (x: number) => Math.cos(x));
oneArgumentDoubleOperators.set('tan', (x: number) => Math.tan(x));
twoArgumentDoublePredicates.set('<', (x: number, y: number) => x < y);
twoArgumentDoublePredicates.set('>', (x: number, y: number) => x > y);
twoArgumentDoubleOperators.set('pow', (x: number, y: number) => Math.pow(x, y));
twoArgumentDoubleOperators.set('atan2', (x: number, y: number) => Math.atan2(y, x));
export class SmalltalkOperatorUsage implements ISmalltalkExpression {
// The method reference cache, as described in Exercise 12 on page 348:
// private cachedClassReference: ISmalltalkClass | undefined;
// private cachedMethodReference: ISmalltalkFunctionDefinition | undefined;
private readonly valueOpNames = [
// Arithmetic:
'=',
'<',
'>',
'+',
'-',
'*',
'/',
// Math functions:
'pow',
'exp',
'ln',
'sin',
'cos',
'tan',
'atan2',
'random',
'floor',
// Type predicates
'number?',
'symbol?',
'char?',
'string?',
'object?',
'array?',
'typename',
// 'hash',
// 'ref=',
// String functions
'tostring',
'stringtosymbol',
'string<',
'strlen',
'stridx',
'substr',
'strcat',
// Array functions
'newarray',
'arraylength',
'arrayget',
'arrayset',
// Utility functions
'print',
'throw'
];
constructor(
private readonly operatorName: Name,
public readonly expressionList: ISmalltalkExpression[]
) {
// CachedClassReference = null;
// CachedMethodReference = null;
}
private evaluateNew(globalInfo: ISmalltalkGlobalInfo): ISmalltalkValue {
if (this.expressionList.length === 0) {
throw new EvaluationException(
'EvaluateNew() : There are no arguments.',
this.operatorName.line,
this.operatorName.column
);
}
const expr0 = this.expressionList[0];
if (!isSmalltalkVariable(expr0)) {
throw new EvaluationException(
'EvaluateNew() : The first argument is not in the form of a variable.',
this.operatorName.line,
this.operatorName.column
);
}
// expr0 is an ISmalltalkVariable; its name is the name of the class of which we will create an instance.
const className = expr0.name;
const smalltalkClass = globalInfo.classDict.get(className);
if (typeof smalltalkClass === 'undefined') {
throw new EvaluationException(
`EvaluateNew() : Unknown class name '${className}'.`,
this.operatorName.line,
this.operatorName.column
);
}
const env = new EnvironmentFrame<ISmalltalkValue>();
for (const memberVariable of smalltalkClass.clRep) {
env.dict.set(memberVariable.name, globalInfo.falseValue);
// Or: env.add(memberVariable, globalInfo.zeroValue);
}
const result = new SmalltalkUserValue(smalltalkClass, env);
result.value.dict.set(selfVar.name, result);
// Or: result.value.add(selfVar, result);
return result;
}
private evaluateAuxInt(
evaluatedArguments: ISmalltalkValue[],
// eslint-disable-next-line @typescript-eslint/no-unused-vars
globalInfo: ISmalltalkGlobalInfo
): ISmalltalkValue {
const firstArgAsInt = evaluatedArguments[0].toInteger();
const secondArgAsInt = evaluatedArguments[1].toInteger();
if (typeof firstArgAsInt === 'undefined') {
throw new Error('evaluateAuxInt() : firstArg is not an integer.');
} else if (typeof secondArgAsInt === 'undefined') {
throw new Error('evaluateAuxInt() : secondArg is not an integer.');
}
switch (this.operatorName.value) {
case '+':
return new SmalltalkInteger(firstArgAsInt + secondArgAsInt);
case '-':
return new SmalltalkInteger(firstArgAsInt - secondArgAsInt);
case '*':
return new SmalltalkInteger(firstArgAsInt * secondArgAsInt);
case '/':
return new SmalltalkInteger(Math.floor(firstArgAsInt / secondArgAsInt));
case '=':
// return firstArgAsInt === secondArgAsInt
return evaluatedArguments[0].equals(evaluatedArguments[1])
? globalInfo.trueValue
: globalInfo.falseValue;
case '<':
return firstArgAsInt < secondArgAsInt
? globalInfo.trueValue
: globalInfo.falseValue;
case '>':
return firstArgAsInt > secondArgAsInt
? globalInfo.trueValue
: globalInfo.falseValue;
default:
throw new Error(
`evaluateAuxInt() : Unsupported operator '${this.operatorName.value}'.`
);
}
// if (IntegerOperatorKeeper.TwoArgumentOperators.ContainsKey(OperatorName.Value))
// {
// return new SmalltalkInteger(IntegerOperatorKeeper.TwoArgumentOperators[OperatorName.Value](firstArgAsInt, secondArgAsInt));
// }
// else if (IntegerOperatorKeeper.TwoArgumentPredicates.ContainsKey(OperatorName.Value))
// {
// return IntegerOperatorKeeper.TwoArgumentPredicates[OperatorName.Value](firstArgAsInt, secondArgAsInt) ? globalInfo.TrueValue : globalInfo.FalseValue;
// }
//
// // Internal error:
// throw new Exception(string.Format("SmalltalkOperatorUsage.EvaluateAuxInt() : Invalid operator {0}", OperatorName.Value));
}
// private ISmalltalkValue EvaluateAuxFloat(List<ISmalltalkValue> evaluatedArguments, SmalltalkGlobalInfo globalInfo)
// {
// // Currently, EvaluateAuxFloat() is only called for two-argument functions.
// var firstArgAsDouble = ((ISmalltalkNumber)evaluatedArguments[0]).ToDouble();
// var secondArgAsDouble = ((ISmalltalkNumber)evaluatedArguments[1]).ToDouble();
//
// if (DoubleOperatorKeeper.TwoArgumentOperators.ContainsKey(OperatorName.Value))
// {
// return new SmalltalkFloatValue(DoubleOperatorKeeper.TwoArgumentOperators[OperatorName.Value](firstArgAsDouble, secondArgAsDouble));
// }
// else if (DoubleOperatorKeeper.TwoArgumentPredicates.ContainsKey(OperatorName.Value))
// {
// return DoubleOperatorKeeper.TwoArgumentPredicates[OperatorName.Value](firstArgAsDouble, secondArgAsDouble) ? globalInfo.TrueValue : globalInfo.FalseValue;
// }
//
// // Internal error:
// throw new Exception(string.Format("SmalltalkOperatorUsage.EvaluateAuxFloat() : Invalid operator {0}", OperatorName.Value));
// }
// TODO 2014/02/04: Split EvaluateGlobalFunction() into EvaluateValueOp() and a new, much smaller EvaluateGlobalFunction() (i.e. user-defined functions).
private evaluateValueOp(
evaluatedArguments: ISmalltalkValue[],
globalInfo: ISmalltalkGlobalInfo
): ISmalltalkValue {
evaluatedArguments = evaluatedArguments.map((arg) => unblockValue(arg));
// First, verify the number of arguments.
let expectedNumArgs = -1;
const actualNumArgs = evaluatedArguments.length;
// Note: We only check DoubleOperatorKeeper here (and not IntegerOperatorKeeper) because
// the integer operators are a subset of the double operators.
if (oneArgumentDoubleOperators.has(this.operatorName.value)) {
expectedNumArgs = 1;
} else if (
twoArgumentDoublePredicates.has(this.operatorName.value) ||
twoArgumentDoubleOperators.has(this.operatorName.value)
) {
expectedNumArgs = 2;
} else {
switch (this.operatorName.value) {
case 'strcat':
expectedNumArgs = -1; // Any number of arguments will do.
break;
case 'print':
case 'number?':
case 'symbol?':
case 'char?':
case 'string?':
case 'object?':
case 'array?':
case 'random':
case 'tostring':
case 'stringtosymbol':
case 'floor':
case 'throw':
case 'strlen':
case 'typename':
case 'hash':
case 'newarray':
case 'arraylength':
expectedNumArgs = 1;
break;
case '=':
case 'string<':
case 'ref=':
case 'arrayget':
case 'stridx':
expectedNumArgs = 2;
break;
case 'substr':
case 'arrayset':
expectedNumArgs = 3;
break;
default:
break; // Do not throw an exception; global user-defined functions pass through here.
}
}
if (expectedNumArgs >= 0 && actualNumArgs != expectedNumArgs) {
throw new EvaluationException(
`EvaluateValueOp: Expected ${expectedNumArgs} arguments for operator '${this.operatorName.value}', instead of the actual ${actualNumArgs} arguments.`,
this.operatorName.line,
this.operatorName.column
);
}
// Next, check the types of the arguments.
let exceptionMessage = '';
switch (this.operatorName.value) {
case '+':
case '-':
case '*':
case '/':
//case '<':
case '>':
case 'pow':
case 'atan2':
if (!evaluatedArguments[0].isNumber()) {
exceptionMessage = 'First argument is not a number';
} else if (!evaluatedArguments[1].isNumber()) {
exceptionMessage = 'Second argument is not a number';
}
break;
case '<':
if (
!(evaluatedArguments[0].isNumber() && evaluatedArguments[1].isNumber()) &&
!(evaluatedArguments[0].isSymbol() && evaluatedArguments[1].isSymbol()) &&
!(evaluatedArguments[0].isCharacter() && evaluatedArguments[1].isCharacter()) &&
!(evaluatedArguments[0].isString() && evaluatedArguments[1].isString())
) {
exceptionMessage =
'Arguments must be both numbers or both symbols or both characters or both strings';
}
break;
case 'random': // 2014/02/01: TODO: Should we insist that random's argument be an integer?
if (!evaluatedArguments[0].isInteger) {
exceptionMessage = 'Argument is not an integer';
}
break;
case 'exp':
case 'ln':
case 'sin':
case 'cos':
case 'tan':
case 'floor':
if (!evaluatedArguments[0].isNumber()) {
exceptionMessage = 'Argument is not a number';
}
break;
case 'stringtosymbol':
if (!evaluatedArguments[0].isString()) {
exceptionMessage = 'Argument is not a string';
} else if ((evaluatedArguments[0] as ISmalltalkStringValue).value === '') {
exceptionMessage = 'Argument is the empty string'; // We know that it's empty and not the null .NET reference.
}
break;
case 'throw':
case 'strlen':
if (!evaluatedArguments[0].isString()) {
exceptionMessage = 'Argument is not a string';
}
break;
case 'string<': // Deprecated; see <
if (!evaluatedArguments[0].isString()) {
exceptionMessage = 'First argument is not a string';
} else if (!evaluatedArguments[1].isString()) {
exceptionMessage = 'Second argument is not a string';
}
break;
case 'substr':
if (!evaluatedArguments[0].isString()) {
exceptionMessage = 'First argument is not a string';
} else if (!evaluatedArguments[1].isNumber()) {
exceptionMessage = 'Second argument is not a number';
} else if (!evaluatedArguments[2].isNumber()) {
exceptionMessage = 'Third argument is not a number';
}
break;
case 'strcat':
for (let i = 0; i < evaluatedArguments.length; ++i) {
if (evaluatedArguments[i].isObject()) {
const owner = evaluatedArguments[i].owner;
exceptionMessage = `Argument ${i + 1} is an object of type '${
typeof owner !== 'undefined' ? owner.className : '<No owner>'
}'`;
break;
}
}
break;
case 'newarray':
if (!evaluatedArguments[0].isInteger) {
exceptionMessage = 'Argument is not an integer';
}
break;
case 'arraylength':
if (!evaluatedArguments[0].isArray()) {
exceptionMessage = 'Argument is not an array';
}
break;
case 'arrayget':
case 'arrayset':
if (!evaluatedArguments[0].isArray()) {
exceptionMessage = 'First argument is not an array';
} else if (!evaluatedArguments[1].isInteger) {
exceptionMessage = 'Second argument is not an integer';
}
break;
case 'stridx':
if (!evaluatedArguments[0].isString()) {
exceptionMessage = 'First argument is not a string';
} else if (!evaluatedArguments[1].isNumber()) {
exceptionMessage = 'Second argument is not a number';
}
break;
default:
break;
}
if (exceptionMessage) {
throw new EvaluationException(
`EvaluateValueOp: Operator '${this.operatorName.value}': ${exceptionMessage}`,
this.operatorName.line,
this.operatorName.column
);
}
// Now evaluate.
let a: ISmalltalkValue[] | undefined;
let f: number | undefined;
let n: number | undefined;
let s: string | undefined;
try {
switch (this.operatorName.value) {
case '=':
return evaluatedArguments[0].equals(evaluatedArguments[1])
? globalInfo.trueValue
: globalInfo.falseValue;
case 'print':
console.log(evaluatedArguments[0]);
return evaluatedArguments[0];
case 'number?':
return evaluatedArguments[0].isNumber()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'symbol?':
return evaluatedArguments[0].isSymbol()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'char?':
return evaluatedArguments[0].isCharacter()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'string?':
return evaluatedArguments[0].isString()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'object?':
return evaluatedArguments[0].isObject()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'array?':
return evaluatedArguments[0].isArray()
? globalInfo.trueValue
: globalInfo.falseValue;
case 'random':
n = evaluatedArguments[0].toInteger();
if (typeof n === 'undefined') {
throw new Error('random: Argument is not an integer.');
}
return new SmalltalkInteger(Math.floor(n * Math.random()));
case 'tostring':
return new SmalltalkString(evaluatedArguments[0].toString());
case 'stringtosymbol':
s = evaluatedArguments[0].toStringX();
if (typeof s === 'undefined') {
throw new Error('strlen: Argument is not a string.');
}
return new SmalltalkSymbol(s);
case 'floor':
f = evaluatedArguments[0].toFloat();
if (typeof f === 'undefined') {
throw new Error('floor: Argument is not a number.');
}
return new SmalltalkInteger(f);
// case 'throw':
// throw new SmalltalkException(((SmalltalkStringValue)evaluatedArguments[0]).Value, OperatorName.Line, OperatorName.Column);
// case 'string<': // See page 54. Deprecated; see <
// return ((SmalltalkStringValue)evaluatedArguments[0]).Value.localeCompare(((SmalltalkStringValue)evaluatedArguments[1]).Value) < 0
// ? globalInfo.TrueValue : globalInfo.FalseValue;
case 'strlen':
s = evaluatedArguments[0].toStringX();
if (typeof s === 'undefined') {
throw new Error('strlen: Argument is not a string.');
}
return new SmalltalkInteger(s.length);
// case 'substr':
// var strForSubstr = (SmalltalkStringValue)evaluatedArguments[0];
// var startForSubstr = globalInfo.ValueAsInteger(evaluatedArguments[1]);
// var lengthForSubstr = globalInfo.ValueAsInteger(evaluatedArguments[2]);
//
// return new SmalltalkStringValue(strForSubstr.Value.Substring(startForSubstr, lengthForSubstr));
case 'typename':
return new SmalltalkString(evaluatedArguments[0].getTypename());
// case 'hash':
// // ThAW 2014/01/28 : For now, we will avoid calling GetHashCode() on Smalltalk objects.
// // Of course, Smalltalk classes are free to implement their own hash functions.
// // (Would a hash function stub in the Object class prevent this global "hash" implementation from being called?)
// var hashResult = 0;
//
// if (!evaluatedArguments[0].IsObject())
// {
// hashResult = evaluatedArguments[0].GetHashCode();
// }
//
// return new SmalltalkInteger(hashResult);
// case 'ref=':
// return object.ReferenceEquals(evaluatedArguments[0], evaluatedArguments[1]) ? globalInfo.TrueValue : globalInfo.FalseValue;
// case 'strcat': // TODO 2014/12/09 : Use string.Join() instead of a StringBuilder?
// var sb = new StringBuilder();
//
// foreach (var ea in evaluatedArguments)
// {
// sb.Append(ea.ToString());
// }
//
// return new SmalltalkStringValue(sb.ToString());
case 'newarray':
n = evaluatedArguments[0].toInteger();
if (typeof n === 'undefined') {
throw new Error('newarray: Argument is not an integer.');
}
return new SmalltalkArray(n);
case 'arraylength':
a = evaluatedArguments[0].toArray();
if (typeof a === 'undefined') {
throw new Error('arraylength: Argument is not an array.');
}
return new SmalltalkInteger(a.length);
// case 'arrayget':
// return ((SmalltalkArrayValue)evaluatedArguments[0]).GetElement(((SmalltalkInteger)evaluatedArguments[1]).Value);
// case 'arrayset':
// return ((SmalltalkArrayValue)evaluatedArguments[0]).SetElement(
// ((SmalltalkInteger)evaluatedArguments[1]).Value, evaluatedArguments[2]);
// case 'stridx':
// return ((SmalltalkStringValue)evaluatedArguments[0]).Index(evaluatedArguments[1]);
default:
if (this.operatorName.value === '<') {
const arg0 = evaluatedArguments[0];
const arg1 = evaluatedArguments[1];
if (isSmalltalkCharacter(arg0) && isSmalltalkCharacter(arg1)) {
return arg0.value.localeCompare(arg1.value) < 0
? globalInfo.trueValue
: globalInfo.falseValue;
} else if (isSmalltalkString(arg0) && isSmalltalkString(arg1)) {
return arg0.value.localeCompare(arg1.value) < 0
? globalInfo.trueValue
: globalInfo.falseValue;
} else if (isSmalltalkSymbol(arg0) && isSmalltalkSymbol(arg1)) {
return arg0.value.localeCompare(arg1.value) < 0
? globalInfo.trueValue
: globalInfo.falseValue;
}
}
if (
operatorsThatTakeEitherIntOrFloatArgs.indexOf(this.operatorName.value) >= 0
) {
if (evaluatedArguments.every((arg) => arg.isInteger)) {
return this.evaluateAuxInt(evaluatedArguments, globalInfo);
// } else {
// // return this.evaluateAuxFloat(evaluatedArguments, globalInfo);
// throw new Error('evaluateAuxFloat() not yet implemented.');
}
}
f = evaluatedArguments.length > 0 ? evaluatedArguments[0].toFloat() : undefined;
if (typeof f !== 'undefined') {
const oneArgumentDoubleOperator = oneArgumentDoubleOperators.get(
this.operatorName.value
);
if (typeof oneArgumentDoubleOperator !== 'undefined') {
return new SmalltalkFloat(oneArgumentDoubleOperator(f));
}
const f2 =
evaluatedArguments.length > 1
? evaluatedArguments[1].toFloat()
: undefined;
if (typeof f2 !== 'undefined') {
const twoArgumentDoublePredicate = twoArgumentDoublePredicates.get(
this.operatorName.value
);
const twoArgumentDoubleOperator = twoArgumentDoubleOperators.get(
this.operatorName.value
);
if (typeof twoArgumentDoublePredicate !== 'undefined') {
return twoArgumentDoublePredicate(f, f2)
? globalInfo.trueValue
: globalInfo.falseValue;
} else if (typeof twoArgumentDoubleOperator !== 'undefined') {
return new SmalltalkFloat(twoArgumentDoubleOperator(f, f2));
}
}
}
// #if DEAD_CODE
// // Evaluate a user-defined function.
//
// if (!globalInfo.FunctionDefinitions.ContainsKey(OperatorName.Value))
// {
// throw new EvaluationException(
// string.Format(`EvaluateGlobalFunction: Unknown function name '{0}'`, OperatorName.Value), this.operatorName.line, this.operatorName.column);
// }
//
// var funDef = globalInfo.FunctionDefinitions[OperatorName.Value];
// var newEnvironment = new SmalltalkEnvironmentFrame(null);
//
// newEnvironment.Compose(funDef.ArgList, evaluatedArguments);
// return funDef.Body.Evaluate(newEnvironment, globalInfo.ObjectInstance, null, globalInfo);
// #else
throw new EvaluationException(
`EvaluateValueOp: Unknown value op '${this.operatorName.value}'`,
this.operatorName.line,
this.operatorName.column
);
// #endif
}
} catch (ex) {
// catch (EvaluationException)
// {
// throw;
// }
throw new EvaluationException(
`EvaluateValueOp: Operator '${this.operatorName.value}': ${ex}`,
this.operatorName.line,
this.operatorName.column
);
}
}
private evaluateGlobalFunction(
evaluatedArguments: ISmalltalkValue[],
globalInfo: ISmalltalkGlobalInfo
): ISmalltalkValue {
if (this.valueOpNames.indexOf(this.operatorName.value) >= 0) {
return this.evaluateValueOp(evaluatedArguments, globalInfo);
}
try {
// Evaluate a user-defined function.
const funDef = globalInfo.functionDefinitions.get(this.operatorName.value);
if (typeof funDef === 'undefined') {
throw new EvaluationException(
`EvaluateGlobalFunction: Unknown function name '${this.operatorName.value}'`,
this.operatorName.line,
this.operatorName.column
);
}
// var funDef = globalInfo.FunctionDefinitions[OperatorName.Value];
const newEnvironment = new EnvironmentFrame<ISmalltalkValue>();
newEnvironment.compose(
funDef.argList as IVariable<ISmalltalkValue>[],
evaluatedArguments
);
return funDef.body.evaluate(globalInfo, newEnvironment, {
receiver: globalInfo.objectInstance
});
// } catch (error: EvaluationException) {
// throw error;
} catch (error) {
throw new EvaluationException(
`EvaluateGlobalFunction: Operator '${this.operatorName.value}': ${error}`,
this.operatorName.line,
this.operatorName.column
);
}
}
private evaluateMethod(
method: ISmalltalkFunctionDefinition,
evaluatedArguments: ISmalltalkValue[],
// receiver: ISmalltalkValue, // | undefined,
// c: ISmalltalkClass | undefined,
globalInfo: ISmalltalkGlobalInfo,
options?: unknown
): ISmalltalkValue {
const newEnvironment =
new EnvironmentFrame<ISmalltalkValue>(/* globalInfo.GlobalEnvironment */);
newEnvironment.compose(method.argList as IVariable<ISmalltalkValue>[], evaluatedArguments);
return method.body.evaluate(globalInfo, newEnvironment, options);
}
public evaluate(
globalInfo: ISmalltalkGlobalInfo, // I.e. IGlobalInfo<ISmalltalkValue>
localEnvironment: ISmalltalkEnvironmentFrame | undefined, // I.e. IEnvironmentFrame<ISmalltalkValue> | undefined
options?: unknown
): ISmalltalkValue {
if (this.operatorName.value === 'new') {
return this.evaluateNew(globalInfo);
}
let evaluatedArguments: ISmalltalkValue[];
// let method: ISmalltalkFunctionDefinition;
// SmalltalkClass classInWhichMethodWasFound;
// if (typeof options === 'undefined') {
// throw new Error('SmalltalkOperatorUsage.evaluate() : options is undefined');
// }
const optionsX = options as ISmalltalkEvaluateOptions;
// const c = optionsX.c;
const c = typeof optionsX !== 'undefined' ? optionsX.c : undefined;
// const receiver = optionsX.receiver;
// const receiver = typeof optionsX !== 'undefined' ? optionsX.receiver : undefined;
if (this.expressionList.length > 0) {
// const variable = this.expressionList[0] as ISmalltalkVariable;
const expr0 = this.expressionList[0];
// if (variable != null && variable.Name == "super") {
if (isSmalltalkVariable(expr0) && expr0.name === 'super') {
if (typeof c === 'undefined') {
throw new EvaluationException(
`${this.operatorName.value}: super usage: c is undefined`,
this.operatorName.line,
this.operatorName.column
);
}
if (typeof c.superClass === 'undefined') {
throw new EvaluationException(
`${this.operatorName.value}: super usage: c.superClass is undefined`,
this.operatorName.line,
this.operatorName.column
);
}
const { method, classInWhichMethodWasFound } = c.superClass.findMethod(
this.operatorName.value
);
if (typeof method === 'undefined') {
throw new EvaluationException(
`${this.operatorName.value}: super usage: Method '${this.operatorName.value}' not found`,
this.operatorName.line,
this.operatorName.column
);
}
const selfValue = selfVar.evaluate(globalInfo, localEnvironment, options);
evaluatedArguments = this.expressionList
.slice(1)
.map((expr) => expr.evaluate(globalInfo, localEnvironment, options));
return this.evaluateMethod(
method,
evaluatedArguments,
// selfValue,
// classInWhichMethodWasFound,
globalInfo,
{ c: classInWhichMethodWasFound, receiver: selfValue }
);
}
}
// #if USE_BLOCKS
// if (typeof receiver === 'undefined') {
// throw new Error('SmalltalkOperatorUsage.evaluate() : receiver is undefined');
// }
// Create blocks (suspended computations) from the expressions.
evaluatedArguments = this.expressionList.map(
(expr) =>
new SmalltalkBlock(
expr,
globalInfo,
localEnvironment,
options
// receiver,
// c,
) as ISmalltalkValue
);
// #else
// evaluatedArguments = ExpressionList.Select(expr => expr.Evaluate(localEnvironment, receiver, c, globalInfo)).ToList();
// #endif
let result: ISmalltalkValue;
if (evaluatedArguments.length === 0) {
result = this.evaluateGlobalFunction(evaluatedArguments, globalInfo);
} else {
evaluatedArguments[0] = unblockValue(evaluatedArguments[0]);
let method: ISmalltalkFunctionDefinition | undefined;
// let classInWhichMethodWasFound: ISmalltalkClass | undefined;
let newReceiverClass = evaluatedArguments[0].owner;
if (typeof newReceiverClass !== 'undefined') {
// throw new Error('OperatorUsage.evaluate() : newReceiverClass is undefined');
// if (newReceiverClass === CachedClassReference) {
// method = CachedMethodReference;
// } else {
// { method, classInWhichMethodWasFound }
const foo = newReceiverClass.findMethod(this.operatorName.value);
method = foo.method;
newReceiverClass = foo.classInWhichMethodWasFound;
}
// CachedClassReference = newReceiverClass;
// CachedMethodReference = method;
// }
if (typeof method !== 'undefined') {
result = this.evaluateMethod(
method,
evaluatedArguments.slice(1),
// evaluatedArguments[0],
// newReceiverClass,
globalInfo,
{ c: newReceiverClass, receiver: evaluatedArguments[0] }
);
} else {
result = this.evaluateGlobalFunction(evaluatedArguments, globalInfo);
}
}
return unblockValue(result);
}
}