UNPKG

@gobstones/gobstones-lang

Version:
1,166 lines (1,092 loc) 41.6 kB
/* eslint-disable no-underscore-dangle */ import { /* Definitions */ N_DefProgram, N_DefInteractiveProgram, N_DefProcedure, N_DefFunction, /* Statements */ N_StmtBlock, N_StmtReturn, N_StmtIf, N_StmtRepeat, N_StmtForeach, N_StmtWhile, N_StmtSwitch, N_StmtAssignVariable, N_StmtAssignTuple, N_StmtProcedureCall, /* Patterns */ N_PatternWildcard, N_PatternVariable, N_PatternNumber, N_PatternStructure, N_PatternTuple, N_PatternTimeout, /* Expressions */ N_ExprVariable, N_ExprConstantNumber, N_ExprConstantString, N_ExprChoose, N_ExprMatching, N_ExprList, N_ExprRange, N_ExprTuple, N_ExprStructure, N_ExprStructureUpdate, N_ExprFunctionCall, /* Other */ ASTDefProgram, SymbolTable, ASTMain, ASTDefInteractiveProgram, ASTDefProcedure, ASTDefFunction, ASTNode, ASTStmtBlock, ASTStmtReturn, ASTStmtIf, ASTStmtRepeat, ASTStmtForeach, ASTStmtWhile, ASTStmtSwitch, ASTStmtAssignVariable, ASTStmtAssignTuple, ASTStmtProcedureCall, ASTPatternVariable, ASTPatternWildcard, ASTPatternNumber, ASTPatternStructure, ASTPatternTuple, ASTPatternTimeout, ASTNodeWithBranches, ASTExpr, ASTExprVariable, ASTExprConstantNumber, ASTExprConstantString, ASTExprChoose, ASTExprMatching, ASTExprList, ASTMatchingBranch, ASTSwitchBranch, ASTPattern, ASTExprRange, ASTExprTuple, ASTExprStructure, ASTExprStructureUpdate, ASTExprFunctionCall, SourceReader } from '@gobstones/gobstones-parser'; import { IPushInteger, IPushString, IPushVariable, ISetVariable, IUnsetVariable, ILabel, IJump, IJumpIfFalse, IJumpIfStructure, IJumpIfTuple, ICall, IReturn, IMakeTuple, IMakeList, IMakeStructure, IUpdateStructure, IReadTupleComponent, IReadStructureField, IReadStructureFieldPop, IDup, IPop, IPrimitiveCall, ISaveState, IRestoreState, ITypeCheck, Code, Instruction } from './instruction'; import { TypeAny, TypeInteger, TypeTuple, TypeStructure, TypeList } from './value'; import { RuntimePrimitives } from './runtime'; import { i18n } from './i18n'; /* * A compiler receives a symbol table (instance of SymbolTable). * * The method this.compile(ast) receives an abstract syntax tree * (the output of a parser). * * The AST is expected to have been linted against the given symbol table. * * The compiler produces an instance of Code, representing code for the * virtual machine. * * Compiling a program should never throw an exception. * Exceptions thrown in this module correspond to assertions, * i.e. internal errors that should never occur. * - Static conditions should be checked beforehand during the * parsing and linting phases. * - Runtime conditions are to be checked later, during execution. */ export class Compiler { private _symtable: SymbolTable; private _code: Code; private _nextLabel: number; private _nextVariable: number; private _primitives: RuntimePrimitives; public constructor(symtable: SymbolTable) { this._symtable = symtable; this._code = new Code([]); this._nextLabel = 0; this._nextVariable = 0; this._primitives = new RuntimePrimitives(); } public compile(ast: ASTMain): Code { this._compileMain(ast); return this._code; } public _compileMain(ast: ASTMain): void { /* Accept the empty source */ if (ast.definitions.length === 0) { this._produce(ast.startPos, ast.endPos, new IReturn()); return; } /* Compile the program (or interactive program) */ for (const definition of ast.definitions) { if (definition.tag === N_DefProgram) { this._compileDefProgram(definition as ASTDefProgram); } else if (definition.tag === N_DefInteractiveProgram) { this._compileDefInteractiveProgram(definition as ASTDefInteractiveProgram); } } /* Compile procedures and functions */ for (const definition of ast.definitions) { if (definition.tag === N_DefProcedure) { this._compileDefProcedure(definition as ASTDefProcedure); } else if (definition.tag === N_DefFunction) { this._compileDefFunction(definition as ASTDefFunction); } } } public _compileDefProgram(definition: ASTDefProgram): void { this._compileStatement(definition.body); this._produce(definition.startPos, definition.endPos, new IReturn()); } /* An interactive program is compiled as a switch statement * followed by a Return instruction. */ public _compileDefInteractiveProgram(definition: ASTDefInteractiveProgram): void { this._compileMatchBranches(definition, false /* isMatching */); this._produce(definition.startPos, definition.endPos, new IReturn()); } /* A procedure definition: * * procedure P(x1, ..., xN) { * <body> * } * * is compiled as follows: * * P: * SetVariable x1 * ... * SetVariable xN * <body> * Return */ public _compileDefProcedure(definition: ASTDefProcedure): void { this._produce(definition.startPos, definition.endPos, new ILabel(definition.name.value)); for (const parameter of definition.parameters) { const parameterName = parameter.value; this._produce(definition.startPos, definition.endPos, new ISetVariable(parameterName)); } this._compileStatement(definition.body); this._produce(definition.startPos, definition.endPos, new IReturn()); } /* A function definition: * * function f(x1, ..., xN) { * <body> * } * * is compiled as follows: * * f: * SaveState * SetVariable x1 * ... * SetVariable xN * <body> * RestoreState * Return */ public _compileDefFunction(definition: ASTDefFunction): void { this._produceList(definition.startPos, definition.endPos, [ new ILabel(definition.name.value), new ISaveState() ]); for (const parameter of definition.parameters) { const parameterName = parameter.value; this._produce(definition.startPos, definition.endPos, new ISetVariable(parameterName)); } this._compileStatement(definition.body); this._produceList(definition.startPos, definition.endPos, [ new IRestoreState(), new IReturn() ]); } /* Statements are compiled to VM instructions that start and end * with an empty local stack. The stack may grow and shrink during the * execution of a statement, but it should be empty by the end. * * The only exception to this rule is the "return" statement, which * pushes a single value on the stack. */ public _compileStatement(statement: ASTNode): void { switch (statement.tag) { case N_StmtBlock: return this._compileStmtBlock(statement as ASTStmtBlock); case N_StmtReturn: return this._compileStmtReturn(statement as ASTStmtReturn); case N_StmtIf: return this._compileStmtIf(statement as ASTStmtIf); case N_StmtRepeat: return this._compileStmtRepeat(statement as ASTStmtRepeat); case N_StmtForeach: return this._compileStmtForeach(statement as ASTStmtForeach); case N_StmtWhile: return this._compileStmtWhile(statement as ASTStmtWhile); case N_StmtSwitch: return this._compileStmtSwitch(statement as ASTStmtSwitch); case N_StmtAssignVariable: return this._compileStmtAssignVariable(statement as ASTStmtAssignVariable); case N_StmtAssignTuple: return this._compileStmtAssignTuple(statement as ASTStmtAssignTuple); case N_StmtProcedureCall: return this._compileStmtProcedureCall(statement as ASTStmtProcedureCall); default: throw Error('Compiler: Statement not implemented: ' + Symbol.keyFor(statement.tag)); } } public _compileStmtBlock(block: ASTStmtBlock): void { for (const statement of block.statements) { this._compileStatement(statement); } } /* Merely push the return value in the stack. * The "new IReturn()" instruction itself is produced by the * methods: * _compileDefProgram * _compileDefInteractiveProgram * _compileDefProcedure * _compileDefFunction * */ public _compileStmtReturn(statement: ASTStmtReturn): void { return this._compileExpression(statement.result); } /* * If without else: * * <condition> * TypeCheck Bool * JumpIfFalse labelElse * <thenBranch> * labelElse: * * If with else: * * <condition> * TypeCheck Bool * JumpIfFalse labelElse * <thenBranch> * Jump labelEnd * labelElse: * <elseBranch> * labelEnd: */ public _compileStmtIf(statement: ASTStmtIf): void { this._compileExpression(statement.condition); this._produce( statement.condition.startPos, statement.condition.endPos, new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})) ); const labelElse = this._freshLabel(); this._produce(statement.startPos, statement.endPos, new IJumpIfFalse(labelElse)); this._compileStatement(statement.thenBlock); if (statement.elseBlock === undefined) { this._produce(statement.startPos, statement.endPos, new ILabel(labelElse)); } else { const labelEnd = this._freshLabel(); this._produceList(statement.startPos, statement.endPos, [ new IJump(labelEnd), new ILabel(labelElse) ]); this._compileStatement(statement.elseBlock); this._produce(statement.startPos, statement.endPos, new ILabel(labelEnd)); } } /* <times> * TypeCheck Integer * labelStart: * Dup ;\ * PushInteger 0 ;| if not positive, end * PrimitiveCall ">", 2 ;| * JumpIfFalse labelEnd ;/ * <body> * PushInteger 1 ;\ subtract 1 * PrimitiveCall "-", 2 ;/ * Jump labelStart * labelEnd: * Pop ; pop the remaining number */ public _compileStmtRepeat(statement: ASTStmtRepeat): void { this._compileExpression(statement.times); this._produce( statement.times.startPos, statement.times.endPos, new ITypeCheck(new TypeInteger()) ); const labelStart = this._freshLabel(); const labelEnd = this._freshLabel(); this._produceList(statement.startPos, statement.endPos, [ new ILabel(labelStart), new IDup(), new IPushInteger(0), new IPrimitiveCall('>', 2), new IJumpIfFalse(labelEnd) ]); this._compileStatement(statement.body); this._produceList(statement.startPos, statement.endPos, [ new IPushInteger(1), new IPrimitiveCall('-', 2), new IJump(labelStart), new ILabel(labelEnd), new IPop() ]); } /* <range> ;\ _list = temporary variable * TypeCheck List(Any) ;| holding the list we are ranging over * SetVariable _list ;/ * * PushVariable _list ;\ _n = temporary variable * PrimitiveCall "_unsafeListLength", 1 ;| holding the total length * SetVariable _n ;/ of the list * * PushInteger 0 ;\ _pos = temporary variable holding the * SetVariable _pos ;/ current index inside the list * * labelStart: * PushVariable _pos ;\ * PushVariable _n ;| if out of the bounds of the list, end * PrimitiveCall "<", 2 ;| * JumpIfFalse labelEnd ;/ * * PushVariable _list ;\ get the `pos`-th element of the * PushVariable _pos ;| list and match the value * PrimitiveCall "_unsafeListNth", 2 ;| with the pattern of the foreach * [match with the pattern or fail] ;/ * * <body> * * PushVariable _pos ;\ * PushInteger 1 ;| add 1 to the current index * PrimitiveCall "+", 2 ;| * SetVariable _pos ;/ * * Jump labelStart * labelEnd: * UnsetVariable _list * UnsetVariable _n * UnsetVariable _pos * [unset all the variables bound by the pattern] */ public _compileStmtForeach(statement: ASTStmtForeach): void { const labelStart = this._freshLabel(); const labelEnd = this._freshLabel(); const list = this._freshVariable(); const pos = this._freshVariable(); const n = this._freshVariable(); this._compileExpression(statement.range); this._produceList(statement.range.startPos, statement.range.endPos, [ new ITypeCheck(new TypeList(new TypeAny())), new ISetVariable(list), new IPushVariable(list), new IPrimitiveCall('_unsafeListLength', 1), new ISetVariable(n) ]); this._produceList(statement.startPos, statement.endPos, [ new IPushInteger(0), new ISetVariable(pos), new ILabel(labelStart), new IPushVariable(pos), new IPushVariable(n), new IPrimitiveCall('<', 2), new IJumpIfFalse(labelEnd), new IPushVariable(list), new IPushVariable(pos), new IPrimitiveCall('_unsafeListNth', 2) ]); this._compileMatchForeachPatternOrFail(statement.pattern); this._compileStatement(statement.body); this._produceList(statement.startPos, statement.endPos, [ new IPushVariable(pos), new IPushInteger(1), new IPrimitiveCall('+', 2), new ISetVariable(pos), new IJump(labelStart), new ILabel(labelEnd), new IUnsetVariable(list), new IUnsetVariable(n), new IUnsetVariable(pos) ]); this._compilePatternUnbind(statement.pattern as ASTPatternVariable); } /* Attempt to match the pattern against the top of the stack. * If the pattern matches, bind its variables. * Otherwise, issue an error message. * Always pops the top of the stack. */ public _compileMatchForeachPatternOrFail(pattern: ASTPattern): void { switch (pattern.tag) { case N_PatternWildcard: this._produce(pattern.startPos, pattern.endPos, new IPop()); return; case N_PatternVariable: { const patternVariable = pattern as ASTPatternVariable; this._produce( pattern.startPos, pattern.endPos, new ISetVariable(patternVariable.variableName.value) ); return; } default: { /* Attempt to match, issuing an error message if there is no match: * * [if subject matches pattern, jump to L] * [error message: no match] * L: * [bind pattern to subject] * [pop subject] */ const label = this._freshLabel(); this._compilePatternCheck(pattern, label); this._produceList(pattern.startPos, pattern.endPos, [ new IPushString('foreach-pattern-does-not-match'), new IPrimitiveCall('_FAIL', 1), new ILabel(label) ]); this._compilePatternBind(pattern); this._produce(pattern.startPos, pattern.endPos, new IPop()); return; } } } /* labelStart: * <condition> * TypeCheck Bool * JumpIfFalse labelEnd * <body> * Jump labelStart * labelEnd: */ public _compileStmtWhile(statement: ASTStmtWhile): void { const labelStart = this._freshLabel(); const labelEnd = this._freshLabel(); this._produce(statement.startPos, statement.endPos, new ILabel(labelStart)); this._compileExpression(statement.condition); this._produceList(statement.startPos, statement.endPos, [ new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})), new IJumpIfFalse(labelEnd) ]); this._compileStatement(statement.body); this._produceList(statement.startPos, statement.endPos, [ new IJump(labelStart), new ILabel(labelEnd) ]); } /* If the branches of the switch are: * pattern1 -> body1 * ... -> ... * patternN -> bodyN * the switch construction is compiled as follows: * * <subject> * [if matches pattern1, jump to label1] * ... * [if matches patternN, jump to labelN] * [error message: no match] * * label1: * [bind parameters in pattern1] * [pop subject] * <body1> * [unbind parameters in pattern1] * Jump labelEnd * ... * labelN: * [bind parameters in patternN] * [pop subject] * <bodyN> * [unbind parameters in patternN] * Jump labelEnd * labelEnd: */ public _compileStmtSwitch(statement: ASTStmtSwitch): void { /* Compile the subject */ this._compileExpression(statement.subject); this._compileMatchBranches(statement, false /* !isMatching */); } public _compileMatchBranches(statement: ASTNodeWithBranches, isMatching: boolean): void { const branchLabels = []; /* Attempt to match each pattern */ for (const branch of statement.branches) { const label = this._freshLabel(); branchLabels.push(label); this._compilePatternCheck(branch.pattern, label); } /* Issue an error message if there is no match */ this._produceList(statement.startPos, statement.endPos, [ new IPushString('switch-does-not-match'), new IPrimitiveCall('_FAIL', 1) ]); /* Compile each branch */ const labelEnd = this._freshLabel(); for (let i = 0; i < branchLabels.length; i++) { const branch = statement.branches[i]; const label = branchLabels[i]; this._produce(branch.startPos, branch.endPos, new ILabel(label)); this._compilePatternBind(branch.pattern); this._produce(branch.startPos, branch.endPos, new IPop()); if (isMatching) { this._compileExpression((branch as ASTMatchingBranch).body); } else { this._compileStatement((branch as ASTSwitchBranch).body); } this._compilePatternUnbind(branch.pattern as ASTPatternVariable); this._produce(branch.startPos, branch.endPos, new IJump(labelEnd)); } this._produce(statement.startPos, statement.endPos, new ILabel(labelEnd)); } public _compileStmtAssignVariable(statement: ASTStmtAssignVariable): void { this._compileExpression(statement.value); this._produce( statement.startPos, statement.endPos, new ISetVariable(statement.variable.value) ); } public _compileStmtAssignTuple(statement: ASTStmtAssignTuple): void { this._compileExpression(statement.value); /* Check that the value is indeed a tuple of the expected length */ const anys = []; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _variable of statement.variables) { anys.push(new TypeAny()); } const expectedType = new TypeTuple(anys); this._produce(statement.startPos, statement.endPos, new ITypeCheck(expectedType)); /* Assign each variable */ for (let index = 0; index < statement.variables.length; index++) { this._produceList(statement.startPos, statement.endPos, [ new IReadTupleComponent(index), new ISetVariable(statement.variables[index].value) ]); } /* Pop the tuple */ this._produce(statement.startPos, statement.endPos, new IPop()); } /* There are two cases: * (1) The procedure is a built-in primitive. * (2) The procedure is a user-defined procedure. */ public _compileStmtProcedureCall(statement: ASTStmtProcedureCall): void { const procedureName = statement.procedureName.value; for (const argument of statement.args) { this._compileExpression(argument); } if (this._primitives.isProcedure(procedureName)) { this._compileStmtProcedureCallPrimitive(statement); } else if (this._symtable.isProcedure(procedureName)) { this._compileStmtProcedureCallUserDefined(statement); } else { throw Error('Compiler: ' + procedureName + ' is an undefined procedure.'); } } public _compileStmtProcedureCallPrimitive(statement: ASTStmtProcedureCall): void { this._produce( statement.startPos, statement.endPos, new IPrimitiveCall(statement.procedureName.value, statement.args.length) ); } public _compileStmtProcedureCallUserDefined(statement: ASTStmtProcedureCall): void { this._produce( statement.startPos, statement.endPos, new ICall(statement.procedureName.value, statement.args.length) ); } /* Pattern checks are instructions that check whether the * top of the stack has the expected form (matching a given pattern) * and, in that case, branching to the given label. * The top of the stack is never popped. * The arguments of a pattern are not bound by this instruction. */ public _compilePatternCheck(pattern: ASTPattern, targetLabel: string): void { switch (pattern.tag) { case N_PatternWildcard: return this._compilePatternCheckWildcard( pattern as ASTPatternWildcard, targetLabel ); case N_PatternVariable: return this._compilePatternCheckVariable( pattern as ASTPatternVariable, targetLabel ); case N_PatternNumber: return this._compilePatternCheckNumber(pattern as ASTPatternNumber, targetLabel); case N_PatternStructure: return this._compilePatternCheckStructure( pattern as ASTPatternStructure, targetLabel ); case N_PatternTuple: return this._compilePatternCheckTuple(pattern as ASTPatternTuple, targetLabel); case N_PatternTimeout: return this._compilePatternCheckTimeout(pattern as ASTPatternTimeout, targetLabel); default: throw Error( 'Compiler: Pattern check not implemented: ' + Symbol.keyFor(pattern.tag) ); } } public _compilePatternCheckWildcard(pattern: ASTPatternWildcard, targetLabel: string): void { this._produce(pattern.startPos, pattern.endPos, new IJump(targetLabel)); } public _compilePatternCheckVariable(pattern: ASTPatternVariable, targetLabel: string): void { this._produce(pattern.startPos, pattern.endPos, new IJump(targetLabel)); } public _compilePatternCheckNumber(pattern: ASTPatternNumber, targetLabel: string): void { this._produceList(pattern.startPos, pattern.endPos, [ new IDup(), new ITypeCheck(new TypeInteger()), new IPushInteger(parseInt(pattern.number.value, 10)), new IPrimitiveCall('/=', 2), new IJumpIfFalse(targetLabel) ]); } public _compilePatternCheckStructure(pattern: ASTPatternStructure, targetLabel: string): void { /* Check that the type of the value coincides with the type * of the constructor */ const constructorName = pattern.constructorName.value; const typeName = this._symtable.constructorType(constructorName); const expectedType = new TypeStructure(typeName, {}); this._produce(pattern.startPos, pattern.endPos, new ITypeCheck(expectedType)); /* Jump if the value matches */ this._produce( pattern.startPos, pattern.endPos, new IJumpIfStructure(constructorName, targetLabel) ); } public _compilePatternCheckTuple(pattern: ASTPatternTuple, targetLabel: string): void { /* Check that the type of the value coincides with the type * of the tuple */ const anys = []; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _variable of pattern.boundVariables) { anys.push(new TypeAny()); } const expectedType = new TypeTuple(anys); this._produce(pattern.startPos, pattern.endPos, new ITypeCheck(expectedType)); /* Jump if the value matches */ this._produce( pattern.startPos, pattern.endPos, new IJumpIfTuple(pattern.boundVariables.length, targetLabel) ); } public _compilePatternCheckTimeout(pattern: ASTPatternTimeout, targetLabel: string): void { this._produce( pattern.startPos, pattern.endPos, new IJumpIfStructure(i18n('CONS:TIMEOUT'), targetLabel) ); } /* Pattern binding are instructions that bind the parameters * of a pattern to the corresponding parts of the value currently * at the top of the stack. The value at the top of the stack * is never popped (it must be duplicated if necessary). */ public _compilePatternBind(pattern: ASTPattern): void { switch (pattern.tag) { case N_PatternWildcard: return; /* No parameters to bind */ case N_PatternVariable: this._compilePatternBindVariable(pattern as ASTPatternVariable); return; case N_PatternNumber: return; /* No parameters to bind */ case N_PatternStructure: this._compilePatternBindStructure(pattern as ASTPatternStructure); return; case N_PatternTuple: this._compilePatternBindTuple(pattern as ASTPatternTuple); return; case N_PatternTimeout: return; /* No parameters to bind */ default: throw Error( 'Compiler: Pattern binding not implemented: ' + Symbol.keyFor(pattern.tag) ); } } public _compilePatternBindVariable(pattern: ASTPatternVariable): void { this._produceList(pattern.startPos, pattern.endPos, [ new IDup(), new ISetVariable(pattern.variableName.value) ]); } public _compilePatternBindStructure(pattern: ASTPatternStructure): void { /* Allow structure pattern with no parameters, even if the constructor * has parameters */ if (pattern.boundVariables.length === 0) { return; } const constructorName = pattern.constructorName.value; const fieldNames = this._symtable.constructorFields(constructorName); for (let i = 0; i < fieldNames.length; i++) { const variable = pattern.boundVariables[i]; const fieldName = fieldNames[i]; this._produceList(pattern.startPos, pattern.endPos, [ new IReadStructureField(fieldName), new ISetVariable(variable.value) ]); } } public _compilePatternBindTuple(pattern: ASTPatternTuple): void { for (let index = 0; index < pattern.boundVariables.length; index++) { const variable = pattern.boundVariables[index]; this._produceList(pattern.startPos, pattern.endPos, [ new IReadTupleComponent(index), new ISetVariable(variable.value) ]); } } /* Pattern unbinding are instructions that unbind the parameters * of a pattern. */ public _compilePatternUnbind(pattern: ASTPatternVariable): void { for (const variable of pattern.boundVariables) { this._produceList(pattern.startPos, pattern.endPos, [ new IUnsetVariable(variable.value) ]); } } /* Expressions are compiled to instructions that make the size * of the local stack grow in exactly one. * The stack may grow and shrink during the evaluation of an * expression, but an expression should not consume values * that were present on the stack before its evaluation started. * In the end the stack should have exactly one more value than * at the start. */ public _compileExpression(expression: ASTExpr): void { switch (expression.tag) { case N_ExprVariable: return this._compileExprVariable(expression as ASTExprVariable); case N_ExprConstantNumber: return this._compileExprConstantNumber(expression as ASTExprConstantNumber); case N_ExprConstantString: return this._compileExprConstantString(expression as ASTExprConstantString); case N_ExprChoose: return this._compileExprChoose(expression as ASTExprChoose); case N_ExprMatching: return this._compileExprMatching(expression as ASTExprMatching); case N_ExprList: return this._compileExprList(expression as ASTExprList); case N_ExprRange: return this._compileExprRange(expression as ASTExprRange); case N_ExprTuple: return this._compileExprTuple(expression as ASTExprTuple); case N_ExprStructure: return this._compileExprStructure(expression as ASTExprStructure); case N_ExprStructureUpdate: return this._compileExprStructureUpdate(expression as ASTExprStructureUpdate); case N_ExprFunctionCall: return this._compileExprFunctionCall(expression as ASTExprFunctionCall); default: throw Error( 'Compiler: Expression not implemented: ' + Symbol.keyFor(expression.tag) ); } } public _compileExprVariable(expression: ASTExprVariable): void { this._produce( expression.startPos, expression.endPos, new IPushVariable(expression.variableName.value) ); } public _compileExprConstantNumber(expression: ASTExprConstantNumber): void { this._produce( expression.startPos, expression.endPos, new IPushInteger(parseInt(expression.number.value, 10)) ); } public _compileExprConstantString(expression: ASTExprConstantString): void { this._produce( expression.startPos, expression.endPos, new IPushString(expression.string.value) ); } /* * An expression of the form: * * choose a when (cond) b otherwise * * is compiled similarly as a statement of the form: * * if (cond) { a } else { b } * * Recall that a 'choose' with many branches: * * choose a1 when (cond1) * ... * aN when (condN) * b otherwise * * is actually parsed as a sequence of nested binary choose * constructions: * * choose a1 when (cond1) * ( * ... * choose aN when (condN) * b otherwise * ... * ) otherwise * */ public _compileExprChoose(expression: ASTExprChoose): void { this._compileExpression(expression.condition); this._produce( expression.condition.startPos, expression.condition.endPos, new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})) ); const labelOtherwise = this._freshLabel(); this._produce(expression.startPos, expression.endPos, new IJumpIfFalse(labelOtherwise)); this._compileExpression(expression.trueExpr); const labelEnd = this._freshLabel(); this._produceList(expression.startPos, expression.endPos, [ new IJump(labelEnd), new ILabel(labelOtherwise) ]); this._compileExpression(expression.falseExpr); this._produce(expression.startPos, expression.endPos, new ILabel(labelEnd)); } public _compileExprMatching(expression: ASTExprMatching): void { this._compileExpression(expression.subject); this._compileMatchBranches(expression, true /* isMatching */); } public _compileExprList(expression: ASTExprList): void { for (const element of expression.elements) { this._compileExpression(element); } this._produce( expression.startPos, expression.endPos, new IMakeList(expression.elements.length) ); } /* * Range expresions [first..last] and [first,second..last] * are compiled by calling the primitive functions * _makeRange * _makeRangeWithSecond */ public _compileExprRange(expression: ASTExprRange): void { this._compileExpression(expression.first); this._compileExpression(expression.last); if (expression.second === undefined) { this._produce( expression.startPos, expression.endPos, new IPrimitiveCall('_makeRange', 2) ); } else { this._compileExpression(expression.second); this._produce( expression.startPos, expression.endPos, new IPrimitiveCall('_makeRangeWithSecond', 3) ); } } public _compileExprTuple(expression: ASTExprTuple): void { for (const element of expression.elements) { this._compileExpression(element); } this._produce( expression.startPos, expression.endPos, new IMakeTuple(expression.elements.length) ); } public _compileExprStructure(expression: ASTExprStructure): void { const fieldNames = []; for (const fieldBinding of expression.fieldBindings) { this._compileExpression(fieldBinding.value); fieldNames.push(fieldBinding.fieldName.value); } const constructorName = expression.constructorName.value; const typeName = this._symtable.constructorType(constructorName); this._produce( expression.startPos, expression.endPos, new IMakeStructure(typeName, constructorName, fieldNames) ); } public _compileExprStructureUpdate(expression: ASTExprStructureUpdate): void { this._compileExpression(expression.original); const fieldNames = []; for (const fieldBinding of expression.fieldBindings) { this._compileExpression(fieldBinding.value); fieldNames.push(fieldBinding.fieldName.value); } const constructorName = expression.constructorName.value; const typeName = this._symtable.constructorType(constructorName); this._produce( expression.startPos, expression.endPos, new IUpdateStructure(typeName, constructorName, fieldNames) ); } /* There are four cases: * (1) The function is '&&' or '||' which must be considered separately * to account for short-circuting. * (2) The function is a built-in primitive. * (3) The function is a user-defined function. * (4) The function is an observer / field accessor. */ public _compileExprFunctionCall(expression: ASTExprFunctionCall): void { const functionName = expression.functionName.value; if (functionName === '&&') { this._compileExprFunctionCallAnd(expression); } else if (functionName === '||') { this._compileExprFunctionCallOr(expression); } else { for (const argument of expression.args) { this._compileExpression(argument); } if (this._primitives.isFunction(functionName)) { this._compileExprFunctionCallPrimitive(expression); } else if (this._symtable.isFunction(functionName)) { this._compileExprFunctionCallUserDefined(expression); } else if (this._symtable.isField(functionName)) { this._compileExprFunctionCallFieldAccessor(expression); } else { throw Error('Compiler: ' + functionName + ' is an undefined function.'); } } } /* <expr1> * TypeCheck Bool * JumpIfStructure 'False' labelEnd * Pop * <expr2> * TypeCheck Bool * labelEnd: */ public _compileExprFunctionCallAnd(expression: ASTExprFunctionCall): void { const expr1 = expression.args[0]; const expr2 = expression.args[1]; const labelEnd = this._freshLabel(); this._compileExpression(expr1); this._produceList(expression.startPos, expression.endPos, [ new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})), new IJumpIfStructure(i18n('CONS:False'), labelEnd), new IPop() ]); this._compileExpression(expr2); this._produceList(expression.startPos, expression.endPos, [ new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})), new ILabel(labelEnd) ]); } /* <expr1> * TypeCheck Bool * JumpIfStructure 'True' labelEnd * Pop * <expr2> * TypeCheck Bool * labelEnd: */ public _compileExprFunctionCallOr(expression: ASTExprFunctionCall): void { const expr1 = expression.args[0]; const expr2 = expression.args[1]; const labelEnd = this._freshLabel(); this._compileExpression(expr1); this._produceList(expression.startPos, expression.endPos, [ new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})), new IJumpIfStructure(i18n('CONS:True'), labelEnd), new IPop() ]); this._compileExpression(expr2); this._produceList(expression.startPos, expression.endPos, [ new ITypeCheck(new TypeStructure(i18n('TYPE:Bool'), {})), new ILabel(labelEnd) ]); } public _compileExprFunctionCallPrimitive(expression: ASTExprFunctionCall): void { this._produce( expression.startPos, expression.endPos, new IPrimitiveCall(expression.functionName.value, expression.args.length) ); } public _compileExprFunctionCallUserDefined(expression: ASTExprFunctionCall): void { this._produce( expression.startPos, expression.endPos, new ICall(expression.functionName.value, expression.args.length) ); } public _compileExprFunctionCallFieldAccessor(expression: ASTExprFunctionCall): void { this._produceList(expression.startPos, expression.endPos, [ new IReadStructureFieldPop(expression.functionName.value) ]); } /* Helpers */ /* Produce the given instruction, setting its starting and ending * position to startPos and endPos respectively */ public _produce(startPos: SourceReader, endPos: SourceReader, instruction: Instruction): void { instruction.startPos = startPos; instruction.endPos = endPos; this._code.produce(instruction); } public _produceList( startPos: SourceReader, endPos: SourceReader, instructions: Instruction[] ): void { for (const instruction of instructions) { this._produce(startPos, endPos, instruction); } } /* Create a fresh label name */ public _freshLabel(): string { const label = '_l' + this._nextLabel.toString(); this._nextLabel++; return label; } /* Create a fresh local variable name */ public _freshVariable(): string { const v = '_v' + this._nextVariable.toString(); this._nextVariable++; return v; } }