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