UNPKG

@gobstones/gobstones-parser

Version:
1,095 lines (998 loc) 38.9 kB
/* eslint-disable camelcase */ /* eslint-disable no-underscore-dangle */ import { ASTMain, ASTStmtBlock, N_DefFunction, N_DefInteractiveProgram, N_DefProcedure, N_DefProgram, N_DefType, N_ExprChoose, N_ExprConstantNumber, N_ExprConstantString, N_ExprFunctionCall, N_ExprList, N_ExprMatching, N_ExprRange, N_ExprStructure, N_ExprStructureUpdate, N_ExprTuple, N_ExprVariable, N_PatternNumber, N_PatternStructure, N_PatternTimeout, N_PatternTuple, N_PatternVariable, N_PatternWildcard, N_StmtAssignTuple, N_StmtAssignVariable, N_StmtBlock, N_StmtForeach, N_StmtIf, N_StmtProcedureCall, N_StmtRepeat, N_StmtReturn, N_StmtSwitch, N_StmtWhile } from './ast'; import { LocalIndex, LocalParameter, LocalVariable, SymbolTable } from './symtable'; import { i18n, i18nF } from './i18n'; import { GbsSyntaxError } from './exceptions'; import { RecursionChecker } from './recursion_checker'; import { SourceReader } from './reader'; // Only for typing purposes // eslint-disable-next-line @typescript-eslint/ban-types const toStr = (x: string | Function): string => x as string; const isBlockWithReturn = (stmt: ASTStmtBlock): boolean => stmt.tag === N_StmtBlock && stmt.statements.length > 0 && stmt.statements.slice(-1)[0].tag === N_StmtReturn; function fail(startPos: SourceReader, endPos: SourceReader, reason: string, args: any[]): void { throw new GbsSyntaxError(startPos, endPos, reason, args); } /* A semantic analyzer receives * a symbol table (instance of SymbolTable) * an abstract syntax tree (the output of a parser) * * Then: * * - It performs semantic checks (linting) to ensure that the * program is well-formed. * * - It builds a symbol table with information on global identifiers * such as procedures, functions, types, constructors, and fields. * * - The semantic analysis is structured as a recursive visit over the * AST. * * We assume that the AST is the valid output of a parser. */ export class Linter { private _symtable: SymbolTable; private _enabledLinterChecks: Record<string, boolean>; public constructor(symtable: SymbolTable) { this._symtable = symtable; /* All checks performed by the linter have an entry in this dictionary. * The value of a check indicates whether it is enabled (true) or * disabled (false). * * If a check is disabled, it does not produce a syntax error. */ this._enabledLinterChecks = { // Linter options 'source-should-have-a-program-definition': true, 'procedure-should-not-have-return': true, 'function-should-have-return': true, 'return-statement-not-allowed-here': true, 'wildcard-pattern-should-be-last': true, 'variable-pattern-should-be-last': true, 'structure-pattern-repeats-constructor': true, 'structure-pattern-repeats-tuple-arity': true, 'structure-pattern-repeats-timeout': true, 'pattern-does-not-match-type': true, 'patterns-in-interactive-program-must-be-events': true, 'patterns-in-interactive-program-cannot-be-variables': true, 'patterns-in-switch-must-not-be-events': true, 'patterns-in-foreach-must-not-be-events': true, 'repeated-variable-in-tuple-assignment': true, 'constructor-used-as-procedure': true, 'undefined-procedure': true, 'procedure-arity-mismatch': true, 'numeric-pattern-repeats-number': true, 'structure-pattern-arity-mismatch': true, 'structure-construction-repeated-field': true, 'structure-construction-invalid-field': true, 'structure-construction-missing-field': true, 'structure-construction-cannot-be-an-event': true, 'undefined-function': true, 'function-arity-mismatch': true, 'type-used-as-constructor': true, 'procedure-used-as-constructor': true, 'undeclared-constructor': true, // Extensions 'forbidden-extension-destructuring-foreach': true, 'forbidden-extension-allow-recursion': true }; } public lint(ast: ASTMain): SymbolTable { this._lintMain(ast); return this._symtable; } public _ensureLintCheckExists(linterCheckId: string): void { if (!(linterCheckId in this._enabledLinterChecks)) { throw Error('Linter check "' + linterCheckId + '" does not exist.'); } } public enableCheck(linterCheckId: string, enabled: boolean): void { this._ensureLintCheckExists(linterCheckId); this._enabledLinterChecks[linterCheckId] = enabled; } public _lintCheck( startPos: SourceReader, endPos: SourceReader, reason: string, args: any[] ): void { this._ensureLintCheckExists(reason); if (this._enabledLinterChecks[reason]) { fail(startPos, endPos, reason, args); } } public _lintMain(ast: ASTMain): void { /* Collect all definitions into the symbol table. * This should be done all together, before linting individual * definitions, so all the names of types, constructors, fields, etc. * are already known when checking statements and expressions. */ for (const definition of ast.definitions) { this._addDefinitionToSymbolTable(definition); } /* The source should either be empty or have exactly one program */ if (ast.definitions.length > 0 && this._symtable.program === undefined) { this._lintCheck( ast.startPos, ast.endPos, 'source-should-have-a-program-definition', [] ); } /* Lint individual definitions */ for (const definition of ast.definitions) { this._lintDefinition(definition); } /* Disable recursion */ this._disableRecursion(ast); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public _addDefinitionToSymbolTable(definition: any): void { switch (definition.tag) { case N_DefProgram: return this._symtable.defProgram(definition); case N_DefInteractiveProgram: return this._symtable.defInteractiveProgram(definition); case N_DefProcedure: return this._symtable.defProcedure(definition); case N_DefFunction: return this._symtable.defFunction(definition); case N_DefType: return this._symtable.defType(definition); default: throw Error('Unknown definition: ' + Symbol.keyFor(definition.tag)); } } /** Definitions **/ public _lintDefinition(definition: { tag: symbol }): void { switch (definition.tag) { case N_DefProgram: return this._lintDefProgram(definition); case N_DefInteractiveProgram: return this._lintDefInteractiveProgram(definition); case N_DefProcedure: return this._lintDefProcedure(definition); case N_DefFunction: return this._lintDefFunction(definition); case N_DefType: return this._lintDefType(); default: throw Error('Linter: Definition not implemented: ' + Symbol.keyFor(definition.tag)); } } public _lintDefProgram(definition: { tag?: symbol; body?: any }): void { /* Lint body */ this._lintStmtBlock(definition.body, true /* allowReturn */); /* Remove all local names */ this._symtable.exitScope(); } public _lintDefInteractiveProgram(definition: { tag?: symbol; branches?: any }): void { /* Lint all branches */ this._lintSwitchBranches(definition.branches, true /* isInteractiveProgram */); } public _lintDefProcedure(definition: { tag?: symbol; body?: any; startPos?: any; endPos?: any; name?: any; parameters?: any; }): void { /* Check that it does not have a return statement */ if (isBlockWithReturn(definition.body)) { this._lintCheck( definition.startPos, definition.endPos, 'procedure-should-not-have-return', [definition.name.value] ); } /* Add parameters as local names */ for (const parameter of definition.parameters) { this._symtable.addNewLocalName(parameter, LocalParameter); } /* Lint body */ this._lintStmtBlock(definition.body, false /* !allowReturn */); /* Remove all local names */ this._symtable.exitScope(); } public _lintDefFunction(definition: { tag?: symbol; body?: any; startPos?: any; endPos?: any; name?: any; parameters?: any; }): void { /* Check that it has a return statement */ if (!isBlockWithReturn(definition.body)) { this._lintCheck(definition.startPos, definition.endPos, 'function-should-have-return', [ definition.name.value ]); } /* Add parameters as local names */ for (const parameter of definition.parameters) { this._symtable.addNewLocalName(parameter, LocalParameter); } /* Lint body */ this._lintStmtBlock(definition.body, true /* allowReturn */); /* Remove all local names */ this._symtable.exitScope(); } public _lintDefType(): void { /* No restrictions */ } /** Statements **/ public _lintStatement(statement: { tag: symbol }): void { switch (statement.tag) { case N_StmtBlock: /* Do not allow return in nested blocks */ return this._lintStmtBlock(statement, false /* !allowReturn */); case N_StmtReturn: return this._lintStmtReturn(statement); case N_StmtIf: return this._lintStmtIf(statement); case N_StmtRepeat: return this._lintStmtRepeat(statement); case N_StmtForeach: return this._lintStmtForeach(statement); case N_StmtWhile: return this._lintStmtWhile(statement); case N_StmtSwitch: return this._lintStmtSwitch(statement); case N_StmtAssignVariable: return this._lintStmtAssignVariable(statement); case N_StmtAssignTuple: return this._lintStmtAssignTuple(statement); case N_StmtProcedureCall: return this._lintStmtProcedureCall(statement); default: throw Error('Linter: Statement not implemented: ' + Symbol.keyFor(statement.tag)); } } public _lintStmtBlock(block: { tag?: symbol; statements?: any }, allowReturn: boolean): void { let i = 0; for (const statement of block.statements) { const returnAllowed = allowReturn && i === block.statements.length - 1; if (!returnAllowed && statement.tag === N_StmtReturn) { this._lintCheck( statement.startPos, statement.endPos, 'return-statement-not-allowed-here', [] ); } this._lintStatement(statement); i++; } } public _lintStmtReturn(statement: { tag?: symbol; result?: any }): void { this._lintExpression(statement.result); } public _lintStmtIf(statement: { tag?: symbol; condition?: any; thenBlock?: any; elseBlock?: any; }): void { this._lintExpression(statement.condition); this._lintStatement(statement.thenBlock); if (statement.elseBlock !== undefined) { this._lintStatement(statement.elseBlock); } } public _lintStmtRepeat(statement: { tag?: symbol; times?: any; body?: any }): void { this._lintExpression(statement.times); this._lintStatement(statement.body); } public _lintStmtForeach(statement: { tag?: symbol; pattern?: any; range?: any; body?: any; }): void { this._lintStmtForeachPattern(statement.pattern); this._lintExpression(statement.range); for (const variable of statement.pattern.boundVariables) { this._symtable.addNewLocalName(variable, LocalIndex); } this._lintStatement(statement.body); for (const variable of statement.pattern.boundVariables) { this._symtable.removeLocalName(variable); } } public _lintStmtForeachPattern(pattern: { tag?: any; startPos: any; endPos: any; constructorName: any; boundVariables: any; }): void { /* If "DestructuringForeach" is disabled, forbid complex patterns. * Allow only variable patterns (indices). */ if (pattern.tag !== N_PatternVariable) { this._lintCheck( pattern.startPos, pattern.endPos, 'forbidden-extension-destructuring-foreach', [] ); } /* Check that the pattern itself is well-formed */ this._lintPattern(pattern); /* The pattern in a foreach cannot be an event */ const patternType = this._patternType(pattern); if (patternType === i18n('TYPE:Event')) { this._lintCheck( pattern.startPos, pattern.endPos, 'patterns-in-foreach-must-not-be-events', [] ); } } public _lintStmtWhile(statement: { tag?: symbol; condition?: any; body?: any }): void { this._lintExpression(statement.condition); this._lintStatement(statement.body); } public _lintStmtSwitch(statement: { tag?: symbol; subject?: any; branches?: any }): void { this._lintExpression(statement.subject); this._lintSwitchBranches(statement.branches, false /* !isInteractiveProgram */); } public _lintSwitchBranches(branches: any[], isInteractiveProgram: boolean): void { this._lintBranches(branches, isInteractiveProgram, false /* isMatching */); } public _lintBranches( branches: any[], isInteractiveProgram: boolean, isMatching: boolean ): void { /* Check that each pattern is well-formed */ for (const branch of branches) { this._lintPattern(branch.pattern); } this._branchesCheckWildcardAndVariable(branches); this._branchesCheckNoRepeats(branches); this._branchesCheckCompatible(branches); if (isInteractiveProgram) { this._branchesCheckTypeEvent(branches); } else { this._branchesCheckTypeNotEvent(branches); } /* Lint recursively each branch */ for (const branch of branches) { this._lintBranchBody(branch, isMatching); } } /* Check that there is at most one wildcard/variable pattern at the end */ public _branchesCheckWildcardAndVariable(branches: any[]): void { let i = 0; const n = branches.length; for (const branch of branches) { if (branch.pattern.tag === N_PatternWildcard && i !== n - 1) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'wildcard-pattern-should-be-last', [] ); } if (branch.pattern.tag === N_PatternVariable && i !== n - 1) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'variable-pattern-should-be-last', [branch.pattern.variableName.value] ); } i++; } } /* Check that there are no repeated constructors in a sequence * of branches. */ public _branchesCheckNoRepeats(branches: any[]): void { const coveredNumbers = {}; const coveredConstructors = {}; const coveredTuples = {}; let coveredTimeout = false; for (const branch of branches) { switch (branch.pattern.tag) { case N_PatternWildcard: case N_PatternVariable: /* Already checked in _switchBranchesCheckWildcardAndVariable */ break; case N_PatternNumber: { const number = branch.pattern.number.value; if (number in coveredNumbers) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'numeric-pattern-repeats-number', [number] ); } coveredNumbers[number] = true; break; } case N_PatternStructure: { const constructorName = branch.pattern.constructorName.value; if (constructorName in coveredConstructors) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'structure-pattern-repeats-constructor', [constructorName] ); } coveredConstructors[constructorName] = true; break; } case N_PatternTuple: { const arity = branch.pattern.boundVariables.length; if (arity in coveredTuples) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'structure-pattern-repeats-tuple-arity', [arity] ); } coveredTuples[arity] = true; break; } case N_PatternTimeout: { if (coveredTimeout) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'structure-pattern-repeats-timeout', [] ); } coveredTimeout = true; break; } default: throw Error( 'Linter: pattern "' + Symbol.keyFor(branch.pattern.tag) + '" not implemented.' ); } } } /* Check that constructors are compatible, * i.e. that they belong to the same type */ public _branchesCheckCompatible(branches: any[]): void { let expectedType; for (const branch of branches) { const patternType = this._patternType(branch.pattern); if (expectedType === undefined) { expectedType = patternType; } else if (patternType !== undefined && expectedType !== patternType) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'pattern-does-not-match-type', [i18nF('<pattern-type>')(expectedType), i18nF('<pattern-type>')(patternType)] ); } } } /* Check that there are patterns are of type Event */ public _branchesCheckTypeEvent(branches: any[]): void { for (const branch of branches) { const patternType = this._patternType(branch.pattern); if (patternType !== undefined && patternType !== i18n('TYPE:Event')) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'patterns-in-interactive-program-must-be-events', [] ); } if (branch.pattern.tag === N_PatternVariable) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'patterns-in-interactive-program-cannot-be-variables', [] ); } } } /* Check that there are no patterns of type Event */ public _branchesCheckTypeNotEvent(branches: any[]): void { for (const branch of branches) { const patternType = this._patternType(branch.pattern); if (patternType === i18n('TYPE:Event')) { this._lintCheck( branch.pattern.startPos, branch.pattern.endPos, 'patterns-in-switch-must-not-be-events', [] ); } } } /* Recursively lint the body of each branch. Locally bind variables. */ public _lintBranchBody( branch: { pattern: { boundVariables: any }; body: { tag: symbol } }, isMatching: boolean ): void { for (const variable of branch.pattern.boundVariables) { this._symtable.addNewLocalName(variable, LocalParameter); } if (isMatching) { this._lintExpression(branch.body); } else { this._lintStatement(branch.body); } for (const variable of branch.pattern.boundVariables) { this._symtable.removeLocalName(variable); } } /* Return a description of the type of a pattern */ public _patternType(pattern: { tag?: any; startPos: any; endPos: any; constructorName: any; boundVariables: any; }): string { switch (pattern.tag) { case N_PatternWildcard: case N_PatternVariable: return undefined; case N_PatternNumber: return toStr(i18n('TYPE:Integer')); case N_PatternStructure: return this._symtable.constructorType(pattern.constructorName.value); case N_PatternTuple: return '_TUPLE_' + pattern.boundVariables.length.toString(); case N_PatternTimeout: return toStr(i18n('TYPE:Event')); default: throw Error( 'Linter: pattern "' + Symbol.keyFor(pattern.tag) + '" not implemented.' ); } } public _lintStmtAssignVariable(statement: { tag?: symbol; variable?: any; value?: any }): void { this._symtable.setLocalName(statement.variable, LocalVariable); this._lintExpression(statement.value); } public _lintStmtAssignTuple(statement: { tag?: symbol; variables?: any; value?: any }): void { const variables = {}; for (const variable of statement.variables) { this._symtable.setLocalName(variable, LocalVariable); if (variable.value in variables) { this._lintCheck( variable.startPos, variable.endPos, 'repeated-variable-in-tuple-assignment', [variable.value] ); } variables[variable.value] = true; } this._lintExpression(statement.value); } public _lintStmtProcedureCall(statement: { tag?: symbol; procedureName?: any; startPos?: any; endPos?: any; args?: any; }): void { const name = statement.procedureName.value; /* Check that it is a procedure */ if (!this._symtable.isProcedure(name)) { if (this._symtable.isConstructor(name)) { this._lintCheck( statement.startPos, statement.endPos, 'constructor-used-as-procedure', [name, this._symtable.constructorType(name)] ); } else { this._lintCheck(statement.startPos, statement.endPos, 'undefined-procedure', [ name ]); } } /* Check that the number of argument coincides */ const expected = this._symtable.procedureParameters(name).length; const received = statement.args.length; if (expected !== received) { this._lintCheck(statement.startPos, statement.endPos, 'procedure-arity-mismatch', [ name, expected, received ]); } /* Check all the arguments */ for (const argument of statement.args) { this._lintExpression(argument); } } /** Patterns **/ public _lintPattern(pattern: { tag?: any; startPos: any; endPos: any; constructorName: any; boundVariables: any; }): void { switch (pattern.tag) { case N_PatternWildcard: return this._lintPatternWildcard(); case N_PatternVariable: return this._lintPatternVariable(); case N_PatternNumber: return this._lintPatternNumber(); case N_PatternStructure: return this._lintPatternStructure(pattern); case N_PatternTuple: return this._lintPatternTuple(); case N_PatternTimeout: return this._lintPatternTimeout(); default: throw Error( 'Linter: pattern "' + Symbol.keyFor(pattern.tag) + '" not implemented.' ); } } public _lintPatternWildcard(): void { /* No restrictions */ } public _lintPatternVariable(): void { /* No restrictions */ } public _lintPatternNumber(): void { /* No restrictions */ } public _lintPatternStructure(pattern: { tag?: any; startPos: any; endPos: any; constructorName: any; boundVariables: any; }): void { const name = pattern.constructorName.value; /* Check that the constructor exists */ if (!this._symtable.isConstructor(name)) { this._failExpectedConstructorButGot( // throws pattern.startPos, pattern.endPos, name ); return; } /* Check that the number of parameters match. * Note: constructor patterns with 0 arguments are always allowed. */ const expected = this._symtable.constructorFields(name).length; const received = pattern.boundVariables.length; if (received > 0 && expected !== received) { this._lintCheck(pattern.startPos, pattern.endPos, 'structure-pattern-arity-mismatch', [ name, expected, received ]); } } public _lintPatternTuple(): void { /* No restrictions */ } public _lintPatternTimeout(): void { /* No restrictions */ } /** Expressions **/ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public _lintExpression(expression: any): void { switch (expression.tag) { case N_ExprVariable: return this._lintExprVariable(); case N_ExprConstantNumber: return this._lintExprConstantNumber(); case N_ExprConstantString: return this._lintExprConstantString(); case N_ExprChoose: return this._lintExprChoose(expression); case N_ExprMatching: return this._lintExprMatching(expression); case N_ExprList: return this._lintExprList(expression); case N_ExprRange: return this._lintExprRange(expression); case N_ExprTuple: return this._lintExprTuple(expression); case N_ExprStructure: return this._lintExprStructure(expression); case N_ExprStructureUpdate: return this._lintExprStructureUpdate(expression); case N_ExprFunctionCall: return this._lintExprFunctionCall(expression); default: throw Error('Linter: Expression not implemented: ' + Symbol.keyFor(expression.tag)); } } public _lintExprVariable(): void { /* No restrictions. * Note: the restriction that a variable is defined before it is used * is a dynamic constraint . */ } public _lintExprConstantNumber(): void { /* No restrictions */ } public _lintExprConstantString(): void { /* No restrictions */ } public _lintExprChoose(expression: { condition: any; trueExpr: any; falseExpr: any }): void { this._lintExpression(expression.condition); this._lintExpression(expression.trueExpr); this._lintExpression(expression.falseExpr); } public _lintExprMatching(expression: { subject: any; branches: any }): void { this._lintExpression(expression.subject); this._lintMatchingBranches(expression.branches); } public _lintMatchingBranches(branches: any[]): void { this._lintBranches(branches, false /* !isInteractiveProgram */, true /* isMatching */); } public _lintExprList(expression: { elements: any }): void { for (const element of expression.elements) { this._lintExpression(element); } } public _lintExprRange(expression: { first: any; second: any; last: any }): void { this._lintExpression(expression.first); if (expression.second !== undefined) { this._lintExpression(expression.second); } this._lintExpression(expression.last); } public _lintExprTuple(expression: { elements: any }): void { for (const element of expression.elements) { this._lintExpression(element); } } public _lintExprStructure(expression: { tag?: symbol; constructorName?: any; startPos: any; endPos: any; fieldBindings?: any; original?: any; fieldNames?: (() => any) | (() => any) | (() => any); }): void { this._lintExprStructureOrUpdate(expression, undefined); } public _lintExprStructureUpdate(expression: { tag?: symbol; constructorName?: any; startPos: any; endPos: any; fieldBindings?: any; original?: any; fieldNames?: (() => any) | (() => any) | (() => any); }): void { this._lintExprStructureOrUpdate(expression, expression.original); } /* Check a structure construction: C(x1 <- e1, ..., xN <- eN) * or a structure update: C(original | x1 <- e1, ..., xN <- eN). * * If original is undefined, it is a structure construction. * If original is not undefined, it is the updated expression. * */ // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types public _lintExprStructureOrUpdate(expression: any, original: { tag: symbol }): void { /* Check that constructor exists */ const constructorName: string = expression.constructorName.value; if (!this._symtable.isConstructor(constructorName)) { this._failExpectedConstructorButGot( // throws expression.startPos, expression.endPos, constructorName ); return; } this._checkStructureTypeNotEvent(constructorName, expression); this._checkStructureNoRepeatedFields(constructorName, expression); this._checkStructureBindingsCorrect(constructorName, expression); /* If it is a structure construction, check that the fields are complete */ if (original === undefined) { this._checkStructureBindingsComplete(constructorName, expression); } /* If it is an update, recursively check the original expression */ if (original !== undefined) { this._lintExpression(original); } /* Recursively check expressions in field bindings */ for (const fieldBinding of expression.fieldBindings) { this._lintExpression(fieldBinding.value); } } /* Check that there are no repeated fields in a structure * construction/update */ public _checkStructureNoRepeatedFields( constructorName: string, expression: { fieldNames: () => any; startPos: SourceReader; endPos: SourceReader } ): void { const declaredFields = expression.fieldNames(); const seen = {}; for (const fieldName of declaredFields) { if (fieldName in seen) { this._lintCheck( expression.startPos, expression.endPos, 'structure-construction-repeated-field', [constructorName, fieldName] ); } seen[fieldName] = true; } } /* Check that all bindings in a structure construction/update * correspond to existing fields */ public _checkStructureBindingsCorrect( constructorName: string, expression: { fieldNames: () => any; startPos: SourceReader; endPos: SourceReader } ): void { const declaredFields = expression.fieldNames(); const constructorFields = this._symtable.constructorFields(constructorName); for (const fieldName of declaredFields) { if (constructorFields.indexOf(fieldName) === -1) { this._lintCheck( expression.startPos, expression.endPos, 'structure-construction-invalid-field', [constructorName, fieldName] ); } } } /* Check that bindings in a structure construction/update * cover all existing fields */ public _checkStructureBindingsComplete( constructorName: string, expression: { fieldNames: () => any; startPos: SourceReader; endPos: SourceReader } ): void { const declaredFields = expression.fieldNames(); const constructorFields = this._symtable.constructorFields(constructorName); for (const fieldName of constructorFields) { if (declaredFields.indexOf(fieldName) === -1) { this._lintCheck( expression.startPos, expression.endPos, 'structure-construction-missing-field', [constructorName, fieldName] ); } } } /* Check that a structure construction/update does not involve * constructors of the Event type, which should only be * handled implicitly in an interactive program. */ public _checkStructureTypeNotEvent( constructorName: string, expression: { startPos: SourceReader; endPos: SourceReader } ): void { const constructorType = this._symtable.constructorType(constructorName); if (constructorType === i18n('TYPE:Event')) { this._lintCheck( expression.startPos, expression.endPos, 'structure-construction-cannot-be-an-event', [constructorName] ); } } public _lintExprFunctionCall(expression: { functionName: { value: any }; startPos: SourceReader; endPos: SourceReader; args: string | any[]; }): void { /* Check that it is a function or a field */ const name = expression.functionName.value; if (!this._symtable.isFunction(name) && !this._symtable.isField(name)) { this._lintCheck(expression.startPos, expression.endPos, 'undefined-function', [name]); } /* Check that the number of argument coincides */ let expected: number; if (this._symtable.isFunction(name)) { expected = this._symtable.functionParameters(name).length; } else { /* Fields always have exactly one parameter */ expected = 1; } const received = expression.args.length; if (expected !== received) { this._lintCheck(expression.startPos, expression.endPos, 'function-arity-mismatch', [ name, expected, received ]); } /* Recursively check arguments */ for (const argument of expression.args) { this._lintExpression(argument); } } public _disableRecursion(ast: ASTMain): void { if (this._enabledLinterChecks['forbidden-extension-allow-recursion']) { const cycle = new RecursionChecker().callCycle(ast); if (cycle !== undefined) { this._lintCheck( cycle[0].location.startPos, cycle[0].location.endPos, 'forbidden-extension-allow-recursion', [cycle] ); } } } /* Throw a syntax error indicating that we expected the name of a * constructor, but we got a name which is not a constructor. * * If the name is a type or a procedure, provide a more helpful * error message. (Coinciding constructor and procedure names are * not forbidden, but it is probably a mistake). */ public _failExpectedConstructorButGot( startPos: SourceReader, endPos: SourceReader, name: string ): void { if (this._symtable.isType(name)) { this._lintCheck(startPos, endPos, 'type-used-as-constructor', [ name, this._symtable.typeConstructors(name) ]); } else if (this._symtable.isProcedure(name)) { this._lintCheck(startPos, endPos, 'procedure-used-as-constructor', [name]); } else { this._lintCheck(startPos, endPos, 'undeclared-constructor', [name]); } } }