@tsukiroku/tiny
Version:
Tiny interpreter
922 lines (921 loc) • 44.1 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UNDEFINED = exports.NULL = void 0;
const node_fs_1 = require("node:fs");
const Tiny = __importStar(require("../../index"));
exports.NULL = { kind: 309 /* Tiny.ObjectKind.NULL */ };
exports.UNDEFINED = { kind: 310 /* Tiny.ObjectKind.UNDEFINED */ };
class Evaluator {
program;
enviroment;
option;
messages;
constructor(program, enviroment, option) {
this.program = program;
this.enviroment = enviroment;
this.option = option;
this.messages = Tiny.localization(option);
}
eval() {
if (this.program.errors.length > 0)
return null;
const result = this.evalStatements(this.program.statements, this.enviroment);
if (result?.kind === 308 /* Tiny.ObjectKind.ERROR */) {
Tiny.printError(result, this.option.filename, this.option.stdio.stderr, this.option);
return null;
}
return result;
}
evalStatements(statements, enviroment) {
let results = [];
for (const statement of statements) {
const result = this.evalStatement(statement, enviroment);
results.push(result);
if (result) {
if (result.kind === 307 /* Tiny.ObjectKind.RETURN_VALUE */)
return result.value;
if (result.kind === 308 /* Tiny.ObjectKind.ERROR */)
return result;
}
}
if (results.length === 0)
return exports.UNDEFINED;
return results[results.length - 1];
}
evalBlockStatements(statement, enviroment) {
const { statements, returnFinal } = statement;
let results = [];
for (const statement of statements) {
const result = this.evalStatement(statement, enviroment);
results.push(result);
if (result) {
if (result.kind === 307 /* Tiny.ObjectKind.RETURN_VALUE */)
return result;
if (result.kind === 308 /* Tiny.ObjectKind.ERROR */)
return result;
}
}
if (results.length === 0)
return exports.UNDEFINED;
if (returnFinal)
return results[results.length - 1];
else
return exports.UNDEFINED;
}
evalStatement(statement, enviroment) {
switch (statement.kind) {
case Tiny.NodeKind.ExpressionStatement:
return this.evalExpression(statement.expression, enviroment);
case Tiny.NodeKind.LetStatement: {
const value = this.evalExpression(statement.value, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
const name = statement.ident.value;
if (statement.ident && name !== '_')
enviroment.set(name, value);
return null;
}
case Tiny.NodeKind.ReturnStatement: {
const expression = this.evalExpression(statement.value, enviroment);
if (expression)
return {
value: expression,
kind: 307 /* Tiny.ObjectKind.RETURN_VALUE */,
};
return exports.NULL;
}
case Tiny.NodeKind.WhileStatement: {
let condition = this.evalExpression(statement.condition, enviroment);
if (condition?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return condition;
const resultExpr = [];
while (this.isTruthy(condition)) {
const result = this.evalExpression(statement.body, enviroment);
if (result?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return result;
condition = this.evalExpression(statement.condition, enviroment);
if (condition?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return condition;
resultExpr.push(result);
}
return exports.NULL;
}
case Tiny.NodeKind.DecoratorStatement: {
const decorator = statement;
const value = this.evalExpression(decorator.value, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
const func = this.evalFunction(decorator.function, enviroment, value);
if (func?.kind !== 305 /* Tiny.ObjectKind.FUNCTION */)
return null;
return func;
}
default:
return exports.NULL;
}
}
evalExpression(expression, enviroment) {
if (!expression)
return null;
switch (expression.kind) {
case Tiny.ExpressionKind.Literal:
return this.evalLiteral(expression);
case Tiny.ExpressionKind.Prefix: {
return this.evalPrefix(expression.operator, expression.right, enviroment, {
line: expression.line,
column: expression.column,
});
}
case Tiny.ExpressionKind.Infix:
const infix = expression;
switch (infix.operator) {
case Tiny.TokenType.ASSIGN:
return this.evalIdentInfix(infix.operator, infix.left, infix.right, enviroment, {
line: infix.line,
column: infix.column,
});
case Tiny.TokenType.ELEMENT:
return this.evalElementInfix(infix.left, infix.right, enviroment, {
line: infix.line,
column: infix.column,
});
}
return this.evalInfix(infix.operator, infix.left, infix.right, enviroment, {
line: infix.line,
column: infix.column,
});
case Tiny.ExpressionKind.Block:
return this.evalBlockStatements(expression, enviroment);
case Tiny.ExpressionKind.If: {
return this.evalIfExpression(expression.condition, expression.consequence, expression.alternative, enviroment);
}
case Tiny.ExpressionKind.Ident:
return this.evalIdent(expression.value, enviroment, {
line: expression.line,
column: expression.column,
});
case Tiny.ExpressionKind.Function:
return this.evalFunction(expression, enviroment);
case Tiny.ExpressionKind.Call:
return this.evalCallExpression(expression, enviroment);
case Tiny.ExpressionKind.Array: {
const args = this.evalExpressions(expression.elements, enviroment);
if (args.length == 1 && args[0]?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return args[0];
return {
value: args,
kind: 303 /* Tiny.ObjectKind.ARRAY */,
};
}
case Tiny.ExpressionKind.Index: {
const resultExpr = this.evalExpression(expression.left, enviroment);
if (!resultExpr)
return null;
if (resultExpr.kind === 308 /* Tiny.ObjectKind.ERROR */)
return exports.NULL;
const index = this.evalExpression(expression.index, enviroment);
if (!index)
return null;
if (index.kind === 308 /* Tiny.ObjectKind.ERROR */)
return exports.NULL;
return this.evalIndex(resultExpr, index, {
line: expression.line,
column: expression.column,
});
}
case Tiny.ExpressionKind.Object:
return this.evalObjectParameters(expression.pairs, enviroment);
case Tiny.ExpressionKind.Typeof: {
const value = this.evalExpression(expression.value, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
if (!value)
return exports.NULL;
return {
kind: 301 /* Tiny.ObjectKind.STRING */,
value: Tiny.objectKindStringify(value.kind),
};
}
case Tiny.ExpressionKind.Throw: {
const message = this.evalExpression(expression.message, enviroment);
if (message?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return message;
if (!message)
return exports.NULL;
return Tiny.error(Tiny.objectStringify(message), expression.line, expression.column);
}
case Tiny.ExpressionKind.Delete: {
if (expression.value?.kind !== Tiny.ExpressionKind.Ident)
return Tiny.error(this.messages.runtimeError.deleteRequiresIdentifier, expression.line, expression.column);
enviroment.delete(expression.value.value);
return exports.NULL;
}
case Tiny.ExpressionKind.Use: {
const path = this.evalExpression(expression.path, enviroment);
if (path?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return path;
if (path?.kind !== 301 /* Tiny.ObjectKind.STRING */)
return Tiny.error(this.messages.runtimeError.useRequiresString, expression.line, expression.column);
return this.importEnv(path.value, enviroment, this, {
line: expression.line,
column: expression.column,
});
}
case Tiny.ExpressionKind.Void: {
const value = this.evalExpression(expression.value, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
return exports.UNDEFINED;
}
case Tiny.ExpressionKind.Expr: {
const value = this.evalExpression(expression.value, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return {
kind: 304 /* Tiny.ObjectKind.OBJECT */,
pairs: new Map([
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'message',
},
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: value.message,
},
],
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'line',
},
{
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: value.line,
},
],
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'column',
},
{
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: value.column,
},
],
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'filename',
},
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: this.option.filename,
},
],
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'error',
},
{
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: true,
},
],
]),
};
return value;
}
default:
return null;
}
}
importEnv(path, enviroment, evaluator, position) {
try {
if (!path.endsWith('.tiny'))
path += '.tiny';
const parsed = new Tiny.Parser(new Tiny.Lexer((0, node_fs_1.readFileSync)(`${evaluator.option.root}${path}`, 'utf8'), {
...evaluator.option,
stderr: evaluator.option.stdio.stderr,
}, path), evaluator.option).parseProgram();
parsed.errors.forEach((error) => Tiny.printError(error, path, evaluator.option.stdio.stderr, {
...evaluator.option,
}));
return new Tiny.Evaluator(parsed, enviroment, {
...evaluator.option,
filename: path,
}).eval();
}
catch (e) {
return {
kind: 308 /* Tiny.ObjectKind.ERROR */,
message: `Could not import file: ${evaluator.option.root}${path}`,
...position,
};
}
}
evalFunction(expression, enviroment, decorator) {
const functionObject = {
function: expression.function ?? null,
parameters: expression.parameters,
body: expression.body,
enviroment: enviroment,
option: this.option,
decorator: decorator,
kind: 305 /* Tiny.ObjectKind.FUNCTION */,
};
const name = functionObject.function ? functionObject.function.value ?? null : null;
if (expression.function && name && name !== '_')
enviroment.set(name, functionObject);
return functionObject;
}
evalCallExpression(expression, enviroment) {
const functionObject = this.evalExpression(expression.function, enviroment);
if (functionObject?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return functionObject;
const args = this.evalExpressions(expression.parameters, enviroment);
if (args.length == 1 && args[0]?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return args[0];
return this.applyFunction(functionObject, expression.function.value, args, enviroment, {
line: expression.line,
column: expression.column,
}, {
kind: 304 /* Tiny.ObjectKind.OBJECT */,
pairs: new Map([
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'arguments',
},
{
kind: 303 /* Tiny.ObjectKind.ARRAY */,
value: args,
},
],
[
{
kind: 301 /* Tiny.ObjectKind.STRING */,
value: 'decorator',
},
functionObject.decorator ?? exports.NULL,
],
]),
});
}
evalObjectParameters(parameters, enviroment) {
const object = {
kind: 304 /* Tiny.ObjectKind.OBJECT */,
pairs: new Map(),
};
parameters.forEach((arg) => {
const key = this.evalExpression(arg.key, enviroment);
if (!key)
return;
if (key.kind === 308 /* Tiny.ObjectKind.ERROR */)
return key;
const value = this.evalExpression(arg.value, enviroment);
if (!value)
return;
if (value.kind === 308 /* Tiny.ObjectKind.ERROR */)
return key;
if (key.kind !== 301 /* Tiny.ObjectKind.STRING */ && key.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return;
if (key)
object.pairs.set(key, value);
});
return object;
}
evalExpressions(expression, enviroment) {
return expression.map((expression) => this.evalExpression(expression, enviroment));
}
getDecorator(key, func) {
if (!func.decorator)
return null;
return new Map([...func.decorator.pairs].map(([key, value]) => [key.value, value])).get(key) ?? exports.NULL;
}
applyFunction(functionObject, name, parameters, enviroment, position, thisObject) {
if (functionObject?.kind === 305 /* Tiny.ObjectKind.FUNCTION */) {
if (!this.getDecorator('skipCheckArguments', functionObject) && functionObject.parameters.length !== parameters.length)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.invalidArgument, name ?? '<Anonymous>', functionObject.parameters.length, parameters.length), position.line, position.column);
const result = this.evalExpression(functionObject.body, this.extendFunctionEnv(functionObject, parameters, enviroment, thisObject, this.getDecorator('noCapture', functionObject)?.value ?? false));
if (result?.kind === 307 /* Tiny.ObjectKind.RETURN_VALUE */)
return result.value;
if (result?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return result;
return result;
}
if (functionObject?.kind === 306 /* Tiny.ObjectKind.BUILTIN */)
return functionObject.func(parameters, enviroment, this, position);
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.invalidFunction, name ?? '<unknown>'), position.line, position.column);
}
extendFunctionEnv(functionObject, parameters, enviroment, thisObject, noCapture) {
if (functionObject?.kind === 305 /* Tiny.ObjectKind.FUNCTION */) {
let extendEnviroment = new Tiny.Enviroment(enviroment);
if (!noCapture)
extendEnviroment.outer = functionObject.enviroment;
functionObject.parameters.forEach((param, i) => {
if (param?.kind === Tiny.ExpressionKind.Ident)
extendEnviroment.set(param.value, parameters[i]);
});
extendEnviroment.set('this', thisObject);
return extendEnviroment;
}
return new Tiny.Enviroment();
}
evalIdent(name, enviroment, position) {
if (enviroment.get(name))
return enviroment.get(name);
const builtin = Tiny.builtinFunction(name);
if (!builtin)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.identifierNotDefined_2, name), position.line, position.column);
return builtin;
}
evalLiteral(literal) {
switch (literal.value.kind) {
case Tiny.LiteralKind.Number:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: literal.value.value,
};
case Tiny.LiteralKind.String:
return {
kind: 301 /* Tiny.ObjectKind.STRING */,
value: literal.value.value,
};
case Tiny.LiteralKind.Boolean:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: literal.value.value,
};
default:
return exports.NULL;
}
}
evalPrefix(operator, right, enviroment, position) {
const expression = this.evalExpression(right, enviroment);
if (expression?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return expression;
switch (operator) {
case Tiny.TokenType.MINUS:
return this.evalMinus(expression, position);
case Tiny.TokenType.BANG:
return this.evalBang(expression);
default:
return exports.NULL;
}
}
evalInfix(operator, leftOperand, rightOperand, enviroment, position) {
const left = this.evalExpression(leftOperand, enviroment);
if (left?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return left;
const right = this.evalExpression(rightOperand, enviroment);
if (right?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return right;
if (operator === Tiny.TokenType.NULLISH)
return left?.kind === 309 /* Tiny.ObjectKind.NULL */ ? right : left;
switch (left?.kind) {
case 300 /* Tiny.ObjectKind.NUMBER */:
return this.evalNumberInfix(operator, left, right, position);
case 301 /* Tiny.ObjectKind.STRING */:
return this.evalStringInfix(operator, left, right, position);
case 302 /* Tiny.ObjectKind.BOOLEAN */:
return this.evalBooleanInfix(operator, left, right, position);
case 304 /* Tiny.ObjectKind.OBJECT */:
return this.evalObjectInfix(operator, left, right, position);
case 303 /* Tiny.ObjectKind.ARRAY */:
return this.evalArrayInfix(operator, left, right, position);
default:
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, left?.kind, right?.kind), position.line, position.column);
}
}
typeMissmatch(left, right, position) {
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, Tiny.objectKindStringify(left?.kind ?? 309 /* Tiny.ObjectKind.NULL */), Tiny.objectKindStringify(right?.kind ?? 309 /* Tiny.ObjectKind.NULL */)), position.line, position.column);
}
evalNumberInfix(operator, leftOperand, rightOperand, position) {
if (operator === Tiny.TokenType.IN)
return this.evalInOperator(leftOperand, rightOperand, position);
if (leftOperand?.kind !== 300 /* Tiny.ObjectKind.NUMBER */ || rightOperand?.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return this.typeMissmatch(leftOperand, rightOperand, position);
switch (operator) {
case Tiny.TokenType.PLUS:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: leftOperand.value + rightOperand.value,
};
case Tiny.TokenType.MINUS:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: leftOperand.value - rightOperand.value,
};
case Tiny.TokenType.SLASH:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: leftOperand.value / rightOperand.value,
};
case Tiny.TokenType.ASTERISK:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: leftOperand.value * rightOperand.value,
};
case Tiny.TokenType.PERCENT:
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: leftOperand.value % rightOperand.value,
};
case Tiny.TokenType.EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value === rightOperand.value,
};
case Tiny.TokenType.NOT_EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value !== rightOperand.value,
};
case Tiny.TokenType.GT:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value > rightOperand.value,
};
case Tiny.TokenType.LT:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value < rightOperand.value,
};
case Tiny.TokenType.GTE:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value >= rightOperand.value,
};
case Tiny.TokenType.LTE:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value <= rightOperand.value,
};
default:
return null;
}
}
evalBooleanInfix(operator, leftOperand, rightOperand, position) {
if (operator === Tiny.TokenType.IN)
return this.evalInOperator(leftOperand, rightOperand, position);
if (leftOperand?.kind !== 302 /* Tiny.ObjectKind.BOOLEAN */ || rightOperand?.kind !== 302 /* Tiny.ObjectKind.BOOLEAN */)
return this.typeMissmatch(leftOperand, rightOperand, position);
switch (operator) {
case Tiny.TokenType.EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value === rightOperand.value,
};
case Tiny.TokenType.NOT_EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value !== rightOperand.value,
};
case Tiny.TokenType.AND:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value && rightOperand.value,
};
case Tiny.TokenType.OR:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value || rightOperand.value,
};
default:
return null;
}
}
evalStringInfix(operator, leftOperand, rightOperand, position) {
if (operator === Tiny.TokenType.IN)
return this.evalInOperator(leftOperand, rightOperand, position);
if (leftOperand?.kind !== 301 /* Tiny.ObjectKind.STRING */ || rightOperand?.kind !== 301 /* Tiny.ObjectKind.STRING */)
return this.typeMissmatch(leftOperand, rightOperand, position);
switch (operator) {
case Tiny.TokenType.PLUS:
return {
kind: 301 /* Tiny.ObjectKind.STRING */,
value: `${leftOperand.value}${rightOperand.value}`,
};
case Tiny.TokenType.EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value === rightOperand.value,
};
case Tiny.TokenType.NOT_EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: leftOperand.value !== rightOperand.value,
};
default:
return null;
}
}
evalObjectInfix(operator, leftOperand, rightOperand, position) {
switch (operator) {
case Tiny.TokenType.IN:
return this.evalInOperator(leftOperand, rightOperand, position);
}
if (leftOperand?.kind !== 304 /* Tiny.ObjectKind.OBJECT */ || rightOperand?.kind !== 304 /* Tiny.ObjectKind.OBJECT */)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, Tiny.objectKindStringify(leftOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */), Tiny.objectKindStringify(rightOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */)), position.line, position.column);
switch (operator) {
case Tiny.TokenType.EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: JSON.stringify(leftOperand.pairs) === JSON.stringify(rightOperand.pairs),
};
case Tiny.TokenType.NOT_EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: JSON.stringify(leftOperand.pairs) !== JSON.stringify(rightOperand.pairs),
};
case Tiny.TokenType.PLUS:
return {
kind: 304 /* Tiny.ObjectKind.OBJECT */,
pairs: new Map([
...[...leftOperand.pairs.entries()].filter(([k]) => !new Map([...rightOperand.pairs.entries()].map(([k, v]) => [JSON.stringify(k), v])).has(JSON.stringify(k))),
...rightOperand.pairs,
]),
};
default:
return null;
}
}
evalArrayInfix(operator, leftOperand, rightOperand, position) {
switch (operator) {
case Tiny.TokenType.IN:
return this.evalInOperator(leftOperand, rightOperand, position);
}
if (leftOperand?.kind !== 303 /* Tiny.ObjectKind.ARRAY */ || rightOperand?.kind !== 303 /* Tiny.ObjectKind.ARRAY */)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, Tiny.objectKindStringify(leftOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */), Tiny.objectKindStringify(rightOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */)), position.line, position.column);
switch (operator) {
case Tiny.TokenType.PLUS:
return {
kind: 303 /* Tiny.ObjectKind.ARRAY */,
value: [...leftOperand.value, ...rightOperand.value],
};
case Tiny.TokenType.EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: JSON.stringify(leftOperand.value) === JSON.stringify(rightOperand.value),
};
case Tiny.TokenType.NOT_EQUAL:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: JSON.stringify(leftOperand.value) !== JSON.stringify(rightOperand.value),
};
default:
return null;
}
}
evalIdentInfix(operator, leftOperand, rightOperand, enviroment, position) {
if (operator === Tiny.TokenType.ASSIGN) {
const right = this.evalExpression(rightOperand, enviroment);
if (right?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return right;
if (leftOperand?.kind !== Tiny.ExpressionKind.Ident && leftOperand?.kind !== Tiny.ExpressionKind.Index)
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
switch (leftOperand.kind) {
case Tiny.ExpressionKind.Ident: {
if (!enviroment.get(leftOperand.value))
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.identifierNotDefined_1, leftOperand.value), position.line, position.column);
enviroment.update(leftOperand.value, right);
return right;
}
case Tiny.ExpressionKind.Index: {
const index = leftOperand.index;
const left = this.evalExpression(leftOperand.left, enviroment);
if (left?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return left;
if (left?.kind !== 303 /* Tiny.ObjectKind.ARRAY */ && left?.kind !== 304 /* Tiny.ObjectKind.OBJECT */)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, Tiny.objectKindStringify(left?.kind ?? 309 /* Tiny.ObjectKind.NULL */), Tiny.objectKindStringify(303 /* Tiny.ObjectKind.ARRAY */)), position.line, position.column);
if (left?.kind === 303 /* Tiny.ObjectKind.ARRAY */) {
const resultIdx = this.evalExpression(index, enviroment);
if (resultIdx?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return resultIdx;
if (resultIdx?.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
const value = this.evalExpression(rightOperand, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
if (value?.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return Tiny.error(this.messages.runtimeError.typeMismatch_2, position.line, position.column);
left.value[resultIdx.value] = value;
return value;
}
else {
const resultIdx = this.evalExpression(index, enviroment);
if (resultIdx?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return resultIdx;
if (resultIdx?.kind !== 301 /* Tiny.ObjectKind.STRING */ && resultIdx?.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
const value = this.evalExpression(rightOperand, enviroment);
if (value?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return value;
left.pairs = new Map(Array.from(new Map([...new Map([...left.pairs].map(([k, v]) => [k.value, v])), [resultIdx.value, value]]).entries()).map(([k, v]) => [
typeof k === 'string'
? {
value: k,
kind: 301 /* Tiny.ObjectKind.STRING */,
}
: {
value: k,
kind: 300 /* Tiny.ObjectKind.NUMBER */,
},
v,
]));
return value;
}
}
}
}
return null;
}
evalElementInfix(leftOperand, rightOperand, enviroment, position) {
const left = this.evalExpression(leftOperand, enviroment);
if (left?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return left;
if (left?.kind !== 304 /* Tiny.ObjectKind.OBJECT */ && left?.kind !== 303 /* Tiny.ObjectKind.ARRAY */)
return null;
let right = null;
if (rightOperand?.kind === Tiny.ExpressionKind.Ident)
right = {
kind: 301 /* Tiny.ObjectKind.STRING */,
value: rightOperand.value,
};
else if (rightOperand?.kind === Tiny.ExpressionKind.Call)
right = rightOperand;
else
right = this.evalExpression(rightOperand, enviroment);
if (right?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return right;
if (left.kind === 303 /* Tiny.ObjectKind.ARRAY */)
if (right?.kind === 300 /* Tiny.ObjectKind.NUMBER */)
return this.evalIndex(left, right, position);
else
exports.NULL;
if (right?.kind === 300 /* Tiny.ObjectKind.NUMBER */ || right?.kind === 301 /* Tiny.ObjectKind.STRING */) {
return new Map([...left.pairs].map(([key, value]) => [key.value, value])).get(right.value) ?? exports.UNDEFINED;
}
else if (right?.kind === Tiny.ExpressionKind.Call) {
const expression = new Map([...left.pairs].map(([key, value]) => [key.value, value])).get(right.function.value) ?? exports.UNDEFINED;
if (expression?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return expression;
if (expression?.kind !== 305 /* Tiny.ObjectKind.FUNCTION */)
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.invalidFunction, right.function.value ?? '<unknown>'), position.line, position.column);
const callResult = this.evalCallExpression({
kind: Tiny.ExpressionKind.Call,
function: {
kind: Tiny.ExpressionKind.Function,
function: expression.function,
parameters: expression.parameters,
body: expression.body,
...position,
},
parameters: right.parameters,
...position,
}, enviroment);
if (callResult?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return callResult;
return callResult;
}
else
return exports.NULL;
}
evalInOperator(leftOperand, rightOperand, position) {
switch (rightOperand?.kind) {
case 303 /* Tiny.ObjectKind.ARRAY */:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: rightOperand.value.map((x) => JSON.stringify(x)).includes(JSON.stringify(leftOperand)),
};
case 304 /* Tiny.ObjectKind.OBJECT */: {
if (leftOperand?.kind === 301 /* Tiny.ObjectKind.STRING */ || leftOperand?.kind === 300 /* Tiny.ObjectKind.NUMBER */)
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: [...rightOperand.pairs.keys()].map((x) => JSON.stringify(x)).includes(JSON.stringify(leftOperand)),
};
else
return this.typeMissmatch(leftOperand, rightOperand, position);
}
case 301 /* Tiny.ObjectKind.STRING */:
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: rightOperand.value.includes(leftOperand.value),
};
case 300 /* Tiny.ObjectKind.NUMBER */:
if (leftOperand?.kind === 304 /* Tiny.ObjectKind.OBJECT */)
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: [...leftOperand.pairs.values()].map((x) => JSON.stringify(x)).includes(JSON.stringify(rightOperand)),
};
else
return this.typeMissmatch(leftOperand, rightOperand, position);
default:
return Tiny.error(Tiny.errorFormatter(this.messages.runtimeError.typeMismatch_2, Tiny.objectKindStringify(leftOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */), Tiny.objectKindStringify(rightOperand?.kind ?? 309 /* Tiny.ObjectKind.NULL */)), position.line, position.column);
}
}
evalIfExpression(condition, consequence, alternative, enviroment) {
const conditionExpression = this.evalExpression(condition, enviroment);
if (conditionExpression?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return conditionExpression;
if (this.isTruthy(conditionExpression)) {
const consequenceResult = this.evalExpression(consequence, enviroment);
if (consequenceResult?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return consequenceResult;
return consequenceResult;
}
else if (alternative) {
const alternativeResult = this.evalExpression(alternative, enviroment);
if (alternativeResult?.kind === 308 /* Tiny.ObjectKind.ERROR */)
return alternativeResult;
return alternativeResult;
}
else
return exports.NULL;
}
evalIndex(left, index, position) {
switch (left?.kind) {
case 303 /* Tiny.ObjectKind.ARRAY */: {
if (index?.kind === 300 /* Tiny.ObjectKind.NUMBER */)
return this.evalArrayIndex(left, index, position);
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
}
case 304 /* Tiny.ObjectKind.OBJECT */: {
let key;
switch (index?.kind) {
case 301 /* Tiny.ObjectKind.STRING */:
case 300 /* Tiny.ObjectKind.NUMBER */:
key = index.value;
break;
default:
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
}
return new Map([...left.pairs].map(([key, value]) => [key.value, value])).get(key) ?? exports.UNDEFINED;
}
default:
return exports.NULL;
}
}
evalArrayIndex(left, index, position) {
if (index?.kind !== 300 /* Tiny.ObjectKind.NUMBER */ || left?.kind !== 303 /* Tiny.ObjectKind.ARRAY */)
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
if (index.value < 0 || index.value >= left.value.length)
return Tiny.error(this.messages.runtimeError.indexOutOfRange, position.line, position.column);
return left.value[index.value];
}
isTruthy(object) {
if (!object)
return false;
switch (object.kind) {
case 302 /* Tiny.ObjectKind.BOOLEAN */:
return object.value;
case 300 /* Tiny.ObjectKind.NUMBER */:
return object.value !== 0;
case 309 /* Tiny.ObjectKind.NULL */:
case 310 /* Tiny.ObjectKind.UNDEFINED */:
return false;
default:
return true;
}
}
evalBang(object) {
return {
kind: 302 /* Tiny.ObjectKind.BOOLEAN */,
value: !this.isTruthy(object),
};
}
evalMinus(object, position) {
if (object?.kind !== 300 /* Tiny.ObjectKind.NUMBER */)
return Tiny.error(this.messages.runtimeError.typeMismatch_1, position.line, position.column);
return {
kind: 300 /* Tiny.ObjectKind.NUMBER */,
value: -object.value,
};
}
}
exports.default = Evaluator;