UNPKG

@gobstones/gobstones-lang

Version:
1,221 lines (1,077 loc) 40.2 kB
/* eslint-disable no-underscore-dangle */ import { i18n } from './i18n'; import { ValueInteger, ValueList, ValueStructure, TypeAny, TypeInteger, TypeString, TypeStructure, TypeList, joinTypes, Type, Value } from './value'; import { GbsRuntimeError, SourceReader } from '@gobstones/gobstones-parser'; import { JBoard } from './board_formats'; /* * This module provides the runtime support for the execution of a program. * * The runtime support includes: * * - A definition of a class RuntimeState, representing the global state * of a program. * * - A definition of a class RuntimePrimitives, representing the available * primitive functions. * * This file is a particular implementation, in which RuntimeState * represents a Gobstones board, and RuntimePrimitives are the primitives * functions and procedures available in Gobstones. * * Potential variants of the language might have a different notion of * global state, and different available primitives. */ function fail(startPos: SourceReader, endPos: SourceReader, reason: string, args: any[]): void { throw new GbsRuntimeError(startPos, endPos, reason, args); } const boolEnum = (): string[] => [i18n('CONS:False'), i18n('CONS:True')]; const colorEnum = (): string[] => [ i18n('CONS:Color0'), i18n('CONS:Color1'), i18n('CONS:Color2'), i18n('CONS:Color3') ]; const dirEnum = (): string[] => [ i18n('CONS:Dir0'), i18n('CONS:Dir1'), i18n('CONS:Dir2'), i18n('CONS:Dir3') ]; /* Enumeration of all the constructors of the Event type, including * INIT and TIMEOUT. */ function keyEventEnum(): string[] { const modifiers = [ '', 'CTRL_', 'ALT_', 'SHIFT_', 'CTRL_ALT_', 'CTRL_SHIFT_', 'ALT_SHIFT_', 'CTRL_ALT_SHIFT_' ]; const charKeys = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ]; const specialKeys = [ 'SPACE', 'RETURN', 'TAB', 'BACKSPACE', 'ESCAPE', 'INSERT', 'DELETE', 'HOME', 'END', 'PAGEUP', 'PAGEDOWN', 'F1', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8', 'F9', 'F10', 'F11', 'F12' ]; const symbolKeys = [ 'AMPERSAND', 'ASTERISK', 'AT', 'BACKSLASH', 'CARET', 'COLON', 'DOLLAR', 'EQUALS', 'EXCLAIM', 'GREATER', 'HASH', 'LESS', 'PERCENT', 'PLUS', 'SEMICOLON', 'SLASH', 'QUESTION', 'QUOTE', 'QUOTEDBL', 'UNDERSCORE', 'LEFTPAREN', 'RIGHTPAREN', 'LEFTBRACKET', 'RIGHTBRACKET', 'LEFTBRACE', 'RIGHTBRACE' ]; const arrowKeys = ['LEFT', 'RIGHT', 'UP', 'DOWN']; const keys = charKeys.concat(specialKeys).concat(symbolKeys).concat(arrowKeys); const eventNames = []; for (const modifier of modifiers) { for (const key of keys) { eventNames.push('K_' + modifier + key); } } return eventNames; } const KEY_EVENT_ENUM = keyEventEnum(); const eventEnum = (): string[] => [i18n('CONS:INIT'), i18n('CONS:TIMEOUT')].concat(KEY_EVENT_ENUM); const toEnum = (enumeration: string[], name: string): number => enumeration.indexOf(name); const fromEnum = (enumeration: string[], index: number): string => enumeration[index]; const dirOpposite = (dirName: string): string => fromEnum(dirEnum(), (toEnum(dirEnum(), dirName) + 2) % 4); const dirNext = (dirName: string): string => fromEnum(dirEnum(), (toEnum(dirEnum(), dirName) + 1) % 4); const dirPrev = (dirName: string): string => fromEnum(dirEnum(), (toEnum(dirEnum(), dirName) + 3) % 4); const colorNext = (colorName: string): string => fromEnum(colorEnum(), (toEnum(colorEnum(), colorName) + 1) % 4); const colorPrev = (colorName: string): string => fromEnum(colorEnum(), (toEnum(colorEnum(), colorName) + 3) % 4); /* * An instance of RuntimeState represents the current global state of * a program. In the case of Gobstones, it is a Gobstones board. * * It MUST implement the following methods: * * this.clone() ~~> returns a copy of the state * */ export class RuntimeState { private _width: number; private _height: number; private _board: Record<string, ValueInteger>[][]; private _head: { x: number; y: number }; public constructor() { /* * The board is represented as a list of columns, so that board[x] is the * x-th column and board[x][y] is the cell at (x, y). * * By default, create an empty 9x9 board. */ this._width = 11; this._height = 7; this._board = []; for (let x = 0; x < this._width; x++) { const column = []; for (let y = 0; y < this._height; y++) { column.push(this._emptyCell()); } this._board.push(column); } this._head = { x: 0, y: 0 }; } public clone(): RuntimeState { const newState = new RuntimeState(); newState._width = this._width; newState._height = this._height; newState._board = []; for (let x = 0; x < this._width; x++) { const column = []; for (let y = 0; y < this._height; y++) { const cell = {}; for (const colorName of colorEnum()) { cell[colorName] = this._board[x][y][colorName]; } column.push(cell); } newState._board.push(column); } newState._head = { x: this._head.x, y: this._head.y }; return newState; } /* Dump the state to a Jboard data structure */ public dump(): JBoard { const jboard: JBoard = { head: [], width: 0, height: 0, board: [] }; jboard.width = this._width; jboard.height = this._height; jboard.head = [this._head.x, this._head.y]; jboard.board = []; for (let x = 0; x < this._width; x++) { const column = []; for (let y = 0; y < this._height; y++) { const cell = {}; cell['a'] = this._board[x][y][i18n('CONS:Color0')].asNumber(); cell['n'] = this._board[x][y][i18n('CONS:Color1')].asNumber(); cell['r'] = this._board[x][y][i18n('CONS:Color2')].asNumber(); cell['v'] = this._board[x][y][i18n('CONS:Color3')].asNumber(); column.push(cell); } jboard.board.push(column); } return jboard; } /* Load the state from a Jboard data structure */ public load(jboard: JBoard): void { this._width = jboard.width; this._height = jboard.height; this._head.x = jboard.head[0]; this._head.y = jboard.head[1]; this._board = []; for (let x = 0; x < this._width; x++) { const row = []; for (let y = 0; y < this._height; y++) { const cell = jboard.board[x][y]; const newCell = {}; newCell[i18n('CONS:Color0')] = new ValueInteger(cell['a']); newCell[i18n('CONS:Color1')] = new ValueInteger(cell['n']); newCell[i18n('CONS:Color2')] = new ValueInteger(cell['r']); newCell[i18n('CONS:Color3')] = new ValueInteger(cell['v']); row.push(newCell); } this._board.push(row); } } /* Gobstones specific methods */ public putStone(colorName: string): void { let n = this._board[this._head.x][this._head.y][colorName]; n = n.add(new ValueInteger(1)); this._board[this._head.x][this._head.y][colorName] = n; } public removeStone(colorName: string): void { let n = this._board[this._head.x][this._head.y][colorName]; if (n.le(new ValueInteger(0))) { throw Error('Cannot remove stone.'); } n = n.sub(new ValueInteger(1)); this._board[this._head.x][this._head.y][colorName] = n; } public numStones(colorName: string): ValueInteger { return this._board[this._head.x][this._head.y][colorName]; } public move(dirName: string): void { if (!this.canMove(dirName)) { throw Error('Cannot move.'); } const delta = this._deltaForDirection(dirName); this._head.x += delta[0]; this._head.y += delta[1]; } public goToEdge(dirName: string): void { if (dirName === i18n('CONS:Dir0')) { this._head.y = this._height - 1; } else if (dirName === i18n('CONS:Dir1')) { this._head.x = this._width - 1; } else if (dirName === i18n('CONS:Dir2')) { this._head.y = 0; } else if (dirName === i18n('CONS:Dir3')) { this._head.x = 0; } else { throw Error('Invalid direction: ' + dirName); } } public emptyBoardContents(): void { for (let x = 0; x < this._width; x++) { for (let y = 0; y < this._height; y++) { this._board[x][y] = this._emptyCell(); } } } public canMove(dirName: string): boolean { const delta = this._deltaForDirection(dirName); const x: number = this._head.x + delta[0]; const y: number = this._head.y + delta[1]; return x >= 0 && x < this._width && y >= 0 && y < this._height; } public _deltaForDirection(dirName: string): number[] { let delta; if (dirName === i18n('CONS:Dir0')) { delta = [0, 1]; } else if (dirName === i18n('CONS:Dir1')) { delta = [1, 0]; } else if (dirName === i18n('CONS:Dir2')) { delta = [0, -1]; } else if (dirName === i18n('CONS:Dir3')) { delta = [-1, 0]; } else { throw Error('Invalid direction: ' + dirName); } return delta; } public _emptyCell(): Record<string, ValueInteger> { const cell = {}; for (const colorName of colorEnum()) { cell[colorName] = new ValueInteger(0); } return cell; } } class PrimitiveOperation { private _argumentTypes: Type[]; // eslint-disable-next-line @typescript-eslint/ban-types private _argumentValidator: Function; // eslint-disable-next-line @typescript-eslint/ban-types private _implementation: Function; public constructor(argumentTypes: Type[], argumentValidator, implementation) { this._argumentTypes = argumentTypes; this._argumentValidator = argumentValidator; this._implementation = implementation; } public get argumentTypes(): Type[] { return this._argumentTypes; } public nargs(): number { return this._argumentTypes.length; } public call(globalState: RuntimeState, args: any[]): any { return this._implementation.apply(undefined, [globalState].concat(args)); } /* Check that the arguments are valid according to the validator. * The validator should be a function receiving a start and end * positions, and a list of arguments. * It should throw a GbsRuntimeError if the arguments are invalid. */ public validateArguments( startPos: SourceReader, endPos: SourceReader, globalState: RuntimeState, args: any[] ): void { this._argumentValidator(startPos, endPos, globalState, args); } } /* Casting Gobstones values to JavaScript values and vice-versa */ const typeAny = new TypeAny(); const typeInteger = new TypeInteger(); const typeString = new TypeString(); const typeBool = (): TypeStructure => new TypeStructure(i18n('TYPE:Bool'), {}); const typeListAny = new TypeList(new TypeAny()); function valueFromBool(bool: boolean): ValueStructure { if (bool) { return new ValueStructure(i18n('TYPE:Bool'), i18n('CONS:True'), {}); } else { return new ValueStructure(i18n('TYPE:Bool'), i18n('CONS:False'), {}); } } export const boolFromValue = (value: ValueStructure): boolean => value.constructorName === i18n('CONS:True'); const typeColor = (): TypeStructure => new TypeStructure(i18n('TYPE:Color'), {}); const valueFromColor = (colorName: string): ValueStructure => new ValueStructure(i18n('TYPE:Color'), colorName, {}); const colorFromValue = (value: ValueStructure): string => value.constructorName; const typeDir = (): TypeStructure => new TypeStructure(i18n('TYPE:Dir'), {}); const valueFromDir = (dirName: string): ValueStructure => new ValueStructure(i18n('TYPE:Dir'), dirName, {}); const dirFromValue = (value: ValueStructure): string => value.constructorName; /* Argument validators */ function noValidation(): void { /* No validation */ } const isInteger = (x: Value): boolean => joinTypes(x.type(), typeInteger) !== undefined; const isBool = (x: Value): boolean => joinTypes(x.type(), typeBool()) !== undefined; const isColor = (x: Value): boolean => joinTypes(x.type(), typeColor()) !== undefined; const isDir = (x: Value): boolean => joinTypes(x.type(), typeDir()) !== undefined; export const typesWithOpposite = (): Type[] => [typeInteger, typeBool(), typeDir()]; export const typesWithOrder = (): Type[] => [typeInteger, typeBool(), typeColor(), typeDir()]; /* Generic operations */ function enumIndex(value: ValueStructure): number { if (isBool(value)) { if (boolFromValue(value)) { return 1; } else { return 0; } } else if (isColor(value)) { return toEnum(colorEnum(), colorFromValue(value)); } else if (isDir(value)) { return toEnum(dirEnum(), dirFromValue(value)); } else { throw Error('Value should be Bool, Color or Dir.'); } } function genericLE(a: Value, b: Value): ValueStructure { if (isInteger(a)) { return valueFromBool((a as ValueInteger).le(b as ValueInteger)); } else { const indexA = enumIndex(a as ValueStructure); const indexB = enumIndex(b as ValueStructure); return valueFromBool(indexA <= indexB); } } function genericGE(a: Value, b: Value): ValueStructure { if (isInteger(a)) { return valueFromBool((a as ValueInteger).ge(b as ValueInteger)); } else { const indexA = enumIndex(a as ValueStructure); const indexB = enumIndex(b as ValueStructure); return valueFromBool(indexA >= indexB); } } function genericLT(a: Value, b: Value): ValueStructure { if (isInteger(a)) { return valueFromBool((a as ValueInteger).lt(b as ValueInteger)); } else { const indexA = enumIndex(a as ValueStructure); const indexB = enumIndex(b as ValueStructure); return valueFromBool(indexA < indexB); } } function genericGT(a: Value, b: Value): ValueStructure { if (isInteger(a)) { return valueFromBool((a as ValueInteger).gt(b as ValueInteger)); } else { const indexA = enumIndex(a as ValueStructure); const indexB = enumIndex(b as ValueStructure); return valueFromBool(indexA > indexB); } } function genericNext(a: Value): Value { if (isInteger(a)) { return (a as ValueInteger).add(new ValueInteger(1)); } else if (isBool(a)) { if (boolFromValue(a as ValueStructure)) { return valueFromBool(false); } else { return valueFromBool(true); } } else if (isColor(a)) { return valueFromColor(colorNext(colorFromValue(a as ValueStructure))); } else if (isDir(a)) { return valueFromDir(dirNext(dirFromValue(a as ValueStructure))); } else { throw Error('genericNext: value has no next.'); } } function genericPrev(a: Value): Value { if (isInteger(a)) { return (a as ValueInteger).sub(new ValueInteger(1)); } else if (isBool(a)) { if (boolFromValue(a as ValueStructure)) { return valueFromBool(false); } else { return valueFromBool(true); } } else if (isColor(a)) { return valueFromColor(colorPrev(colorFromValue(a as ValueStructure))); } else if (isDir(a)) { return valueFromDir(dirPrev(dirFromValue(a as ValueStructure))); } else { throw Error('genericPrev: value has no prev.'); } } function genericOpposite(a: Value): Value { if (isInteger(a)) { return (a as ValueInteger).negate(); } else if (isBool(a)) { return valueFromBool(!boolFromValue(a as ValueStructure)); } else if (isDir(a)) { return valueFromDir(dirOpposite(dirFromValue(a as ValueStructure))); } else { throw Error('genericOpposite: value has no opposite.'); } } /* Validate that the type of 'x' is among the given list of types */ function validateTypeAmong( startPos: SourceReader, endPos: SourceReader, x: Value, types: Type[] ): void { /* Succeed if the type of x is in the list 'types' */ for (const type of types) { if (joinTypes(x.type(), type) !== undefined) { return; } } /* Report error */ fail(startPos, endPos, 'expected-value-of-some-type-but-got', [types, x.type()]); } /* Validate that the types of 'x' and 'y' are compatible */ function validateCompatibleTypes( startPos: SourceReader, endPos: SourceReader, x: Value, y: Value ): void { if (joinTypes(x.type(), y.type()) === undefined) { fail(startPos, endPos, 'expected-values-to-have-compatible-types', [x.type(), y.type()]); } } /* Runtime primitives */ export class RuntimePrimitives { private _primitiveTypes: Record<string, Record<string, string[]>>; private _primitiveProcedures: Record<string, PrimitiveOperation>; private _primitiveFunctions: Record<string, PrimitiveOperation>; public constructor() { /* this._primitiveTypes is a dictionary indexed by type names. * * this._primitiveTypes[typeName] is a dictionary indexed by * the constructor names of the given type. * * this._primitiveTypes[typeName][constructorName] * is a list of field names. */ this._primitiveTypes = {}; /* this._primitiveProcedures and this._primitiveFunctions * are dictionaries indexed by the name of the primitive operation * (procedure or function). Their value is an instance of * PrimitiveOperation. */ this._primitiveProcedures = {}; this._primitiveFunctions = {}; /* --Primitive types-- */ /* Booleans */ this._primitiveTypes[i18n('TYPE:Bool')] = {}; for (const boolName of boolEnum()) { this._primitiveTypes[i18n('TYPE:Bool')][boolName] = []; } /* Colors */ this._primitiveTypes[i18n('TYPE:Color')] = {}; for (const colorName of colorEnum()) { this._primitiveTypes[i18n('TYPE:Color')][colorName] = []; } /* Directions */ this._primitiveTypes[i18n('TYPE:Dir')] = {}; for (const dirName of dirEnum()) { this._primitiveTypes[i18n('TYPE:Dir')][dirName] = []; } /* Events */ this._primitiveTypes[i18n('TYPE:Event')] = {}; for (const eventName of eventEnum()) { this._primitiveTypes[i18n('TYPE:Event')][eventName] = []; } /* --Primitive procedures-- */ this._primitiveProcedures[i18n('PRIM:TypeCheck')] = new PrimitiveOperation( [typeAny, typeAny, typeString], (startPos: SourceReader, endPos: SourceReader, globalState, args) => { const v1 = args[0]; const v2 = args[1]; const errorMessage = args[2]; if (joinTypes(v1.type(), v2.type()) === undefined) { fail(startPos, endPos, 'typecheck-failed', [ errorMessage.string, v1.type(), v2.type() ]); } }, (globalState, color) => undefined ); this._primitiveProcedures[i18n('PRIM:PutStone')] = new PrimitiveOperation( [typeColor()], noValidation, (globalState, color) => { globalState.putStone(colorFromValue(color)); return undefined; } ); this._primitiveProcedures[i18n('PRIM:RemoveStone')] = new PrimitiveOperation( [typeColor()], (startPos, endPos, globalState, args) => { const colorName = colorFromValue(args[0]); if (globalState.numStones(colorName).le(new ValueInteger(0))) { fail(startPos, endPos, 'cannot-remove-stone', [colorName]); } }, (globalState, color) => { globalState.removeStone(colorFromValue(color)); return undefined; } ); this._primitiveProcedures[i18n('PRIM:Move')] = new PrimitiveOperation( [typeDir()], (startPos, endPos, globalState, args) => { const dirName = dirFromValue(args[0]); if (!globalState.canMove(dirName)) { fail(startPos, endPos, 'cannot-move-to', [dirName]); } }, (globalState, dir) => { globalState.move(dirFromValue(dir)); return undefined; } ); this._primitiveProcedures[i18n('PRIM:GoToEdge')] = new PrimitiveOperation( [typeDir()], noValidation, (globalState, dir) => { globalState.goToEdge(dirFromValue(dir)); return undefined; } ); this._primitiveProcedures[i18n('PRIM:EmptyBoardContents')] = new PrimitiveOperation( [], noValidation, (globalState, dir) => { globalState.emptyBoardContents(); return undefined; } ); this._primitiveProcedures['_FAIL'] = /* Procedure that always fails */ new PrimitiveOperation( [typeString], (startPos, endPos, globalState, args) => { fail(startPos, endPos, args[0].string, []); }, (globalState, errMsg /* Unreachable */) => undefined ); /* --Primitive functions-- */ this._primitiveFunctions['_makeRange'] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const first = args[0]; const last = args[1]; validateCompatibleTypes(startPos, endPos, first, last); validateTypeAmong(startPos, endPos, first, typesWithOrder()); validateTypeAmong(startPos, endPos, last, typesWithOrder()); }, (globalState, first, last) => { let current = first; if (boolFromValue(genericGT(current, last))) { return new ValueList([]); } const result = []; while (boolFromValue(genericLT(current, last))) { result.push(current); current = genericNext(current); } result.push(current); return new ValueList(result); } ); this._primitiveFunctions['not'] = new PrimitiveOperation( [typeBool()], noValidation, (globalState, x) => valueFromBool(!boolFromValue(x)) ); this._primitiveFunctions['&&'] = new PrimitiveOperation( [typeAny, typeAny], noValidation, /* * This function is a stub so the linter recognizes '&&' * as a defined primitive function of arity 2. * * The implementation of '&&' is treated specially by the * compiler to account for short-circuiting. */ (globalState, x, y) => { throw Error('The function "&&" should never be called'); } ); this._primitiveFunctions['||'] = new PrimitiveOperation( [typeAny, typeAny], noValidation, /* * This function is a stub so the linter recognizes '||' * as a defined primitive function of arity 2. * * The implementation of '||' is treated specially by the * compiler to account for short-circuiting. */ (globalState, x, y) => { throw Error('The function "||" should never be called'); } ); this._primitiveFunctions['_makeRangeWithSecond'] = new PrimitiveOperation( [typeAny, typeAny, typeAny], (startPos, endPos, globalState, args) => { const first = args[0]; const last = args[1]; const second = args[2]; validateTypeAmong(startPos, endPos, first, [typeInteger]); validateTypeAmong(startPos, endPos, last, [typeInteger]); validateTypeAmong(startPos, endPos, second, [typeInteger]); }, (globalState, first, last, second) => { const delta = second.sub(first); if (delta.lt(new ValueInteger(1))) { return new ValueList([]); } let current = first; const result = []; while (current.le(last)) { result.push(current); current = current.add(delta); } return new ValueList(result); } ); this._primitiveFunctions['_unsafeListLength'] = new PrimitiveOperation( [typeAny], noValidation, (globalState, list) => new ValueInteger(list.length()) ); this._primitiveFunctions['_unsafeListNth'] = new PrimitiveOperation( [typeAny, typeAny], noValidation, (globalState, list, index) => list.elements[index.asNumber()] ); this._primitiveFunctions[i18n('PRIM:numStones')] = new PrimitiveOperation( [typeColor()], noValidation, (globalState, color) => globalState.numStones(colorFromValue(color)) ); this._primitiveFunctions[i18n('PRIM:anyStones')] = new PrimitiveOperation( [typeColor()], noValidation, (globalState, color) => { const num = globalState.numStones(colorFromValue(color)); return valueFromBool(num.gt(new ValueInteger(0))); } ); this._primitiveFunctions[i18n('PRIM:canMove')] = new PrimitiveOperation( [typeDir()], noValidation, (globalState, dir) => valueFromBool(globalState.canMove(dirFromValue(dir))) ); this._primitiveFunctions[i18n('PRIM:next')] = new PrimitiveOperation( [typeAny], (startPos, endPos, globalState, args) => { const value = args[0]; validateTypeAmong(startPos, endPos, value, typesWithOrder()); }, (globalState, value) => genericNext(value) ); this._primitiveFunctions[i18n('PRIM:prev')] = new PrimitiveOperation( [typeAny], (startPos, endPos, globalState, args) => { const value = args[0]; validateTypeAmong(startPos, endPos, value, typesWithOrder()); }, (globalState, value) => genericPrev(value) ); this._primitiveFunctions[i18n('PRIM:opposite')] = new PrimitiveOperation( [typeAny], (startPos, endPos, globalState, args) => { const value = args[0]; validateTypeAmong(startPos, endPos, value, typesWithOpposite()); }, (globalState, value) => genericOpposite(value) ); this._primitiveFunctions[i18n('PRIM:minBool')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromBool(false) ); this._primitiveFunctions[i18n('PRIM:maxBool')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromBool(true) ); this._primitiveFunctions[i18n('PRIM:minColor')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromColor(colorEnum()[0]) ); this._primitiveFunctions[i18n('PRIM:maxColor')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromColor(colorEnum()[colorEnum().length - 1]) ); this._primitiveFunctions[i18n('PRIM:minDir')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromDir(dirEnum()[0]) ); this._primitiveFunctions[i18n('PRIM:maxDir')] = new PrimitiveOperation( [], noValidation, (globalState) => valueFromDir(dirEnum()[dirEnum().length - 1]) ); /* Arithmetic operators */ this._primitiveFunctions['+'] = new PrimitiveOperation( [typeInteger, typeInteger], noValidation, (globalState, a, b) => a.add(b) ); this._primitiveFunctions['-'] = new PrimitiveOperation( [typeInteger, typeInteger], noValidation, (globalState, a, b) => a.sub(b) ); this._primitiveFunctions['*'] = new PrimitiveOperation( [typeInteger, typeInteger], noValidation, (globalState, a, b) => a.mul(b) ); this._primitiveFunctions['div'] = new PrimitiveOperation( [typeInteger, typeInteger], (startPos, endPos, globalState, args) => { const b = args[1]; if (b.eq(new ValueInteger(0))) { fail(startPos, endPos, 'cannot-divide-by-zero', []); } }, (globalState, a, b) => a.div(b) ); this._primitiveFunctions['mod'] = new PrimitiveOperation( [typeInteger, typeInteger], (startPos, endPos, globalState, args) => { const b = args[1]; if (b.eq(new ValueInteger(0))) { fail(startPos, endPos, 'cannot-divide-by-zero', []); } }, (globalState, a, b) => a.mod(b) ); this._primitiveFunctions['^'] = new PrimitiveOperation( [typeInteger, typeInteger], (startPos, endPos, globalState, args) => { const b = args[1]; if (b.lt(new ValueInteger(0))) { fail(startPos, endPos, 'negative-exponent', []); } }, (globalState, a, b) => a.pow(b) ); this._primitiveFunctions['-(unary)'] = new PrimitiveOperation( [typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; validateTypeAmong(startPos, endPos, a, typesWithOpposite()); }, (globalState, a) => genericOpposite(a) ); /* Relational operators */ this._primitiveFunctions['=='] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); }, (globalState, a, b) => valueFromBool(a.equal(b)) ); this._primitiveFunctions['/='] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); }, (globalState, a, b) => valueFromBool(!a.equal(b)) ); this._primitiveFunctions['<='] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); validateTypeAmong(startPos, endPos, a, typesWithOrder()); validateTypeAmong(startPos, endPos, b, typesWithOrder()); }, (globalState, a, b) => genericLE(a, b) ); this._primitiveFunctions['>='] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); validateTypeAmong(startPos, endPos, a, typesWithOrder()); validateTypeAmong(startPos, endPos, b, typesWithOrder()); }, (globalState, a, b) => genericGE(a, b) ); this._primitiveFunctions['<'] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); validateTypeAmong(startPos, endPos, a, typesWithOrder()); validateTypeAmong(startPos, endPos, b, typesWithOrder()); }, (globalState, a, b) => genericLT(a, b) ); this._primitiveFunctions['>'] = new PrimitiveOperation( [typeAny, typeAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); validateTypeAmong(startPos, endPos, a, typesWithOrder()); validateTypeAmong(startPos, endPos, b, typesWithOrder()); }, (globalState, a, b) => genericGT(a, b) ); /* User-triggered failure */ this._primitiveProcedures[i18n('PRIM:BOOM')] = new PrimitiveOperation( [typeString], (startPos, endPos, globalState, args) => { fail(startPos, endPos, 'boom-called', [args[0].string]); }, (globalState, msg) => { throw Error('Should not be reachable.'); } ); this._primitiveFunctions[i18n('PRIM:boom')] = this._primitiveProcedures[i18n('PRIM:BOOM')]; /* List operators */ this._primitiveFunctions['++'] = new PrimitiveOperation( [typeListAny, typeListAny], (startPos, endPos, globalState, args) => { const a = args[0]; const b = args[1]; validateCompatibleTypes(startPos, endPos, a, b); }, (globalState, a, b) => a.append(b) ); this._primitiveFunctions[i18n('PRIM:isEmpty')] = new PrimitiveOperation( [typeListAny], noValidation, (globalState, a) => valueFromBool(a.length() === 0) ); this._primitiveFunctions[i18n('PRIM:head')] = new PrimitiveOperation( [typeListAny], (startPos, endPos, globalState, args) => { const a = args[0]; if (a.length() === 0) { fail(startPos, endPos, 'list-cannot-be-empty', []); } }, (globalState, a) => a.head() ); this._primitiveFunctions[i18n('PRIM:tail')] = new PrimitiveOperation( [typeListAny], (startPos, endPos, globalState, args) => { const a = args[0]; if (a.length() === 0) { fail(startPos, endPos, 'list-cannot-be-empty', []); } }, (globalState, a) => a.tail() ); this._primitiveFunctions[i18n('PRIM:oldTail')] = this._primitiveFunctions[ i18n('PRIM:tail') ]; this._primitiveFunctions[i18n('PRIM:init')] = new PrimitiveOperation( [typeListAny], (startPos, endPos, globalState, args) => { const a = args[0]; if (a.length() === 0) { fail(startPos, endPos, 'list-cannot-be-empty', []); } }, (globalState, a) => a.init() ); this._primitiveFunctions[i18n('PRIM:last')] = new PrimitiveOperation( [typeListAny], (startPos, endPos, globalState, args) => { const a = args[0]; if (a.length() === 0) { fail(startPos, endPos, 'list-cannot-be-empty', []); } }, (globalState, a) => a.last() ); } /* Types */ public types(): string[] { const typeNames = []; for (const typeName in this._primitiveTypes) { typeNames.push(typeName); } return typeNames; } public typeConstructors(typeName: string): string[] { if (!(typeName in this._primitiveTypes)) { throw Error('Not a primitive type: ' + typeName); } const constructorNames = []; for (const constructorName in this._primitiveTypes[typeName]) { constructorNames.push(constructorName); } return constructorNames; } public constructorFields(typeName: string, constructorName: string): string[] { if (!(typeName in this._primitiveTypes)) { throw Error('Not a primitive type: ' + typeName); } if (!(constructorName in this._primitiveTypes[typeName])) { throw Error('Not a primitive constructor: ' + constructorName); } return this._primitiveTypes[typeName][constructorName]; } /* Operations */ public isOperation(primitiveName: string): boolean { return ( primitiveName in this._primitiveProcedures || primitiveName in this._primitiveFunctions ); } public getOperation(primitiveName: string): PrimitiveOperation { if (primitiveName in this._primitiveProcedures) { return this._primitiveProcedures[primitiveName]; } else if (primitiveName in this._primitiveFunctions) { return this._primitiveFunctions[primitiveName]; } else { throw Error(primitiveName + ' is not a primitive.'); } } /* Procedures */ public procedures(): string[] { const procedureNames = []; for (const procedureName in this._primitiveProcedures) { procedureNames.push(procedureName); } return procedureNames; } public isProcedure(primitiveName: string): boolean { return primitiveName in this._primitiveProcedures; } /* Functions */ public functions(): string[] { const functionNames = []; for (const functionName in this._primitiveFunctions) { functionNames.push(functionName); } return functionNames; } public isFunction(primitiveName: string): boolean { return primitiveName in this._primitiveFunctions; } }