@gobstones/gobstones-lang
Version:
481 lines (407 loc) • 13.4 kB
text/typescript
/* 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];
}
}