UNPKG

@gobstones/gobstones-lang

Version:
481 lines (407 loc) 13.4 kB
/* eslint-disable no-underscore-dangle */ import { UnknownPosition, SourceReader } from '@gobstones/gobstones-parser'; import { Type } from './value'; /* Opcodes are constant symbols */ export const I_PushInteger = Symbol.for('I_PushInteger'); export const I_PushString = Symbol.for('I_PushString'); export const I_PushVariable = Symbol.for('I_PushVariable'); export const I_SetVariable = Symbol.for('I_SetVariable'); export const I_UnsetVariable = Symbol.for('I_UnsetVariable'); export const I_Label = Symbol.for('I_Label'); export const I_Jump = Symbol.for('I_Jump'); export const I_JumpIfFalse = Symbol.for('I_JumpIfFalse'); export const I_JumpIfStructure = Symbol.for('I_JumpIfStructure'); export const I_JumpIfTuple = Symbol.for('I_JumpIfTuple'); export const I_Call = Symbol.for('I_Call'); export const I_Return = Symbol.for('I_Return'); export const I_MakeTuple = Symbol.for('I_MakeTuple'); export const I_MakeList = Symbol.for('I_MakeList'); export const I_MakeStructure = Symbol.for('I_MakeStructure'); export const I_UpdateStructure = Symbol.for('I_UpdateStructure'); export const I_ReadTupleComponent = Symbol.for('I_ReadTupleComponent'); export const I_ReadStructureField = Symbol.for('I_ReadStructureField'); export const I_ReadStructureFieldPop = Symbol.for('I_ReadStructureFieldPop'); export const I_Add = Symbol.for('I_Add'); export const I_Dup = Symbol.for('I_Dup'); export const I_Pop = Symbol.for('I_Pop'); export const I_PrimitiveCall = Symbol.for('I_PrimitiveCall'); export const I_SaveState = Symbol.for('I_SaveState'); export const I_RestoreState = Symbol.for('I_RestoreState'); export const I_TypeCheck = Symbol.for('I_TypeCheck'); export class Code { private _instructions: Instruction[]; public constructor(instructions: Instruction[]) { this._instructions = instructions; } public toString(): string { const res = []; for (const instruction of this._instructions) { res.push(instruction.toString()); } return res.join('\n'); } public produce(instruction: Instruction): void { this._instructions.push(instruction); } /* Return the instruction at the given location */ public at(ip: number): Instruction { if (ip >= 0 && ip < this._instructions.length) { return this._instructions[ip]; } else { throw Error('Code: instruction pointer out of range.'); } } /* Return a dictionary mapping label names to their corresponding * instruction pointers. */ public labelTargets(): Record<string, number> { const labelTargets = {}; for (let i = 0; i < this._instructions.length; i++) { if (this._instructions[i].opcode === I_Label) { const label = (this._instructions[i] as ILabel).label; if (label in labelTargets) { throw Error('Code: label "' + label + '" is repeated.'); } labelTargets[label] = i; } } return labelTargets; } } function argToString(arg: any): string { if (arg instanceof Array) { const res = []; for (const elem of arg) { res.push(argToString(elem)); } return '[' + res.join(', ') + ']'; } else { return arg.toString(); } } export class Instruction { public _opcode: symbol; public _args: any[]; public _startPos: SourceReader; public _endPos: SourceReader; public constructor(opcode: symbol, args: any[]) { this._opcode = opcode; this._args = args; this._startPos = UnknownPosition; this._endPos = UnknownPosition; } public toString(): string { const opcode = Symbol.keyFor(this._opcode).substring(2); const sargs = []; for (const arg of this._args) { sargs.push(argToString(arg)); } return ' ' + opcode + ' ' + sargs.join(', '); } public get opcode(): symbol { return this._opcode; } public get args(): string[] { return this._args; } public set startPos(position: SourceReader) { this._startPos = position; } public get startPos(): SourceReader { return this._startPos; } public set endPos(position: SourceReader) { this._endPos = position; } public get endPos(): SourceReader { return this._endPos; } } /* Push a constant on the stack. */ export class IPushInteger extends Instruction { public constructor(number: number) { super(I_PushInteger, [number]); } public get number(): number { return this._args[0]; } } export class IPushString extends Instruction { public constructor(string: string) { super(I_PushString, [string]); } public get string(): string { return this._args[0]; } } /* Push a local index/variable/parameter on the stack. */ export class IPushVariable extends Instruction { public constructor(variableName: string) { super(I_PushVariable, [variableName]); } public get variableName(): string { return this._args[0]; } } /* Set a local index/variable/parameter to the value on the top of the stack. */ export class ISetVariable extends Instruction { public constructor(variableName: string) { super(I_SetVariable, [variableName]); } public get variableName(): string { return this._args[0]; } } /* Unset a local index/variable/parameter. * This should be used to avoid the variable being used after the end * of its scope. * * E.g. "i" should have no value after the end of the foreach: * * foreach i in [1,2,3] { * } * x := i */ export class IUnsetVariable extends Instruction { public constructor(variableName: string) { super(I_UnsetVariable, [variableName]); } public get variableName(): string { return this._args[0]; } } /* Pseudo-instruction to mark the target of a jump. */ export class ILabel extends Instruction { public constructor(label: string) { super(I_Label, [label]); } public toString(): string { return this.label + ':'; } public get label(): string { return this._args[0]; } } /* Unconditional jump. */ export class IJump extends Instruction { public constructor(targetLabel: string) { super(I_Jump, [targetLabel]); } public get targetLabel(): string { return this._args[0]; } } /* Jump if the top of the stack is False. * Pops the top of the stack. */ export class IJumpIfFalse extends Instruction { public constructor(targetLabel: string) { super(I_JumpIfFalse, [targetLabel]); } public get targetLabel(): string { return this._args[0]; } } /* Jump if the top of the stack is a structure built using the given * constructor. Does NOT pop the top of the stack. */ export class IJumpIfStructure extends Instruction { public constructor(constructorName: string, targetLabel: string) { super(I_JumpIfStructure, [constructorName, targetLabel]); } public get constructorName(): string { return this._args[0]; } public get targetLabel(): string { return this._args[1]; } } /* Jump if the top of the stack is an n-tuple of the given size. * Does NOT pop the top of the stack. */ export class IJumpIfTuple extends Instruction { public constructor(size: number, targetLabel: string) { super(I_JumpIfTuple, [size, targetLabel]); } public get size(): number { return this._args[0]; } public get targetLabel(): string { return this._args[1]; } } /* Call a subroutine (procedure or function). * The arguments are expected to be located in the stack * with the last one at the top. * * The arguments are popped from the current frame and pushed * onto the new frame. */ export class ICall extends Instruction { public constructor(targetLabel: string, nargs: number) { super(I_Call, [targetLabel, nargs]); } public get targetLabel(): string { return this._args[0]; } public get nargs(): number { return this._args[1]; } } /* Return from a routine to the caller. * If returning a value (from a function or program), * it must be on the top of the stack. */ export class IReturn extends Instruction { public constructor() { super(I_Return, []); } } /* Make a tuple of the given size. * The components are expected to be located in the stack * with the last one at the top. */ export class IMakeTuple extends Instruction { public constructor(size: number) { super(I_MakeTuple, [size]); } public get size(): number { return this._args[0]; } } /* Make a list of the given size. * The elements are expected to be located in the stack * with the last one at the top. */ export class IMakeList extends Instruction { public constructor(size: number) { super(I_MakeList, [size]); } public get size(): number { return this._args[0]; } } /* Make a structure using the given constructor and the given fields. * The values of the fields are expected to be located in the stack * with the last one at the top. */ export class IMakeStructure extends Instruction { public constructor(typeName: string, constructorName: string, fieldNames: string[]) { super(I_MakeStructure, [typeName, constructorName, fieldNames]); } public get typeName(): string { return this._args[0]; } public get constructorName(): string { return this._args[1]; } public get fieldNames(): string[] { return this._args[2]; } } /* Update a structure built using the given constructor with the given * fields. * The stack should have a structure built using the given constructor, * followed by the values of the fields that are expected. * The last field should be at the top. */ export class IUpdateStructure extends Instruction { public constructor(typeName: string, constructorName: string, fieldNames: string[]) { super(I_UpdateStructure, [typeName, constructorName, fieldNames]); } public get typeName(): string { return this._args[0]; } public get constructorName(): string { return this._args[1]; } public get fieldNames(): string[] { return this._args[2]; } } /* Read the n-th component from the tuple at the top of the stack. * Does not pop the tuple. */ export class IReadTupleComponent extends Instruction { public constructor(index: number) { super(I_ReadTupleComponent, [index]); } public get index(): number { return this._args[0]; } } /* Read the given field from the structure at the top of the stack. * Does not pop the structure. */ export class IReadStructureField extends Instruction { public constructor(fieldName: string) { super(I_ReadStructureField, [fieldName]); } public get fieldName(): string { return this._args[0]; } } /* Read the given field from the structure at the top of the stack. * Pop the structure. */ export class IReadStructureFieldPop extends Instruction { public constructor(fieldName: string) { super(I_ReadStructureFieldPop, [fieldName]); } public get fieldName(): string { return this._args[0]; } } /* Add the topmost elements of the stack (used mostly for testing/debugging) */ export class IAdd extends Instruction { public constructor() { super(I_Add, []); } } /* Duplicate the top of the stack (there should be at least one element) */ export class IDup extends Instruction { public constructor() { super(I_Dup, []); } } /* Pop the top of the stack (there should be at least one element) */ export class IPop extends Instruction { public constructor() { super(I_Pop, []); } } /* Call a primitive function. * * The arguments are expected to be located in the stack * with the last one at the top. * * Note: the compiler relies on various primitive functions. * For example, the operation to make a range is a primitive * function: * * function _makeRange(start, end) * * So is the function that checks whether the top of the stack is a list, * etc. (required to compile a "foreach"), and so on. */ export class IPrimitiveCall extends Instruction { public constructor(primitiveName: string, nargs: number) { super(I_PrimitiveCall, [primitiveName, nargs]); } public get primitiveName(): string { return this._args[0]; } public get nargs(): number { return this._args[1]; } } /* Save the global state (when entering a function) */ export class ISaveState extends Instruction { public constructor() { super(I_SaveState, []); } } /* Restore the global state (when leaving a function) */ export class IRestoreState extends Instruction { public constructor() { super(I_RestoreState, []); } } /* Check that the top of the stack has the given type. * Does not pop the top of the stack. */ export class ITypeCheck extends Instruction { public constructor(type: Type) { super(I_TypeCheck, [type]); } public get type(): Type { return this._args[0]; } }