@gobstones/gobstones-lang
Version:
1,221 lines (1,077 loc) • 40.2 kB
text/typescript
/* 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;
}
}