UNPKG

@gobstones/gobstones-parser

Version:
483 lines (424 loc) 16.2 kB
import { ASTConstructorDeclaration, ASTDef, ASTDefFunction, ASTDefInteractiveProgram, ASTDefProcedure, ASTDefProgram, ASTDefType } from './ast'; /* eslint-disable no-underscore-dangle */ import { i18n, i18nPosition } from './i18n'; import { GbsSyntaxError } from './exceptions'; import { SourceReader } from './reader'; import { Token } from './token'; /* Description of a field */ class FieldDescriptor { private _typeName: string; private _constructorName: string; private _index: number; public constructor(typeName: string, constructorName: string, index: number) { this._typeName = typeName; this._constructorName = constructorName; this._index = index; } public get typeName(): string { return this._typeName; } public get constructorName(): string { return this._constructorName; } public get index(): number { return this._index; } } /* Local name categories */ export const LocalVariable = Symbol.for('LocalVariable'); export const LocalParameter = Symbol.for('LocalParameter'); export const LocalIndex = Symbol.for('LocalIndex'); /* Description of a local name */ class LocalNameDescriptor { private _category: any; private _position: any; public constructor(category, position) { this._category = category; this._position = position; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type public get category() { return this._category; } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type public get position() { return this._position; } } function fail(startPos: SourceReader, endPos: SourceReader, reason: string, args: any[]): void { throw new GbsSyntaxError(startPos, endPos, reason, args); } /* A symbol table keeps track of definitions, associating: * - procedure and function names to their code * - type definitions, constructors, and fields */ export class SymbolTable { private _program: ASTDefProgram | ASTDefInteractiveProgram; private _isInteractiveProgram: boolean; private _procedures: Record<string, ASTDefProcedure>; private _procedureParameters: Record<string, string[]>; private _functions: Record<string, ASTDefFunction>; private _functionParameters: Record<string, string[]>; private _types: Record<string, ASTDefType>; private _typeConstructors: Record<string, string[]>; private _constructors: Record<string, ASTConstructorDeclaration>; private _constructorType: Record<string, string>; private _constructorFields: Record<string, string[]>; private _localNames: Record<string, LocalNameDescriptor>; private _fields: Record<string, FieldDescriptor[]>; public constructor() { this._program = undefined; /* true iff the program is interactive */ this._isInteractiveProgram = false; /* Each procedure name is mapped to its definition */ this._procedures = {}; /* Each procedure name is mapped to its parameters */ this._procedureParameters = {}; /* Each function name is mapped to its definition */ this._functions = {}; /* Each function name is mapped to its parameters */ this._functionParameters = {}; /* Each type name is mapped to its definition */ this._types = {}; /* Each type name is mapped to a list of constructor names */ this._typeConstructors = {}; /* Each constructor name is mapped to its declaration */ this._constructors = {}; /* Each constructor name is mapped to its type name */ this._constructorType = {}; /* Each constructor name is mapped to a list of field names */ this._constructorFields = {}; /* Each field name is mapped to a list of field descriptors. * Each field descriptor is of the form * new FieldDescriptor(typeName, constructorName, index) * where * - 'typeName' is the name of a type * - 'constructorName' is the name of a constructor of the given type * - 'index' is the index of the given field with respect to the * given constructor (starting from 0) */ this._fields = {}; /* Local names include parameters, indices and variables, * which are only meaningful within a routine. * * Local names may be bound/referenced in the following places: * - formal parameters, * - indices of a "foreach", * - pattern matching (formal parameters of a "switch"), * - assignments and tuple assignments, * - reading local variables. * * _localNames maps a name to a descriptor of the form: * new LocalNameDescriptor(category, position) */ this._localNames = {}; } public get program(): any { return this._program; } public isInteractiveProgram(): boolean { return this._isInteractiveProgram; } public isProcedure(name: string): boolean { return name in this._procedures; } public allProcedureNames(): string[] { const names = []; for (const name in this._procedures) { names.push(name); } return names.sort(); } public procedureDefinition(name: string): ASTDefProcedure { if (this.isProcedure(name)) { return this._procedures[name]; } else { throw Error('Undefined procedure.'); } } public procedureParameters(name: string): string[] { if (this.isProcedure(name)) { return this._procedureParameters[name]; } else { throw Error('Undefined procedure.'); } } public isFunction(name: string): boolean { return name in this._functions; } public allFunctionNames(): string[] { const names = []; for (const name in this._functions) { names.push(name); } return names.sort(); } public functionDefinition(name: string): ASTDefFunction { if (this.isFunction(name)) { return this._functions[name]; } else { throw Error('Undefined function.'); } } public functionParameters(name: string): string[] { if (this.isFunction(name)) { return this._functionParameters[name]; } else { throw Error('Undefined function.'); } } public isType(name: string): boolean { return name in this._types; } public typeDefinition(name: string): ASTDefType { if (this.isType(name)) { return this._types[name]; } else { throw Error('Undefined type.'); } } public typeConstructors(name: string): string[] { if (this.isType(name)) { return this._typeConstructors[name]; } else { throw Error('Undefined type.'); } } public isConstructor(name: string): boolean { return name in this._constructors; } public constructorDeclaration(name: string): ASTConstructorDeclaration { if (this.isConstructor(name)) { return this._constructors[name]; } else { throw Error('Undefined constructor.'); } } public constructorType(name: string): string { if (this.isConstructor(name)) { return this._constructorType[name]; } else { throw Error('Undefined constructor.'); } } public constructorFields(name: string): string[] { if (this.isConstructor(name)) { return this._constructorFields[name]; } else { throw Error('Undefined constructor.'); } } public isField(name: string): boolean { return name in this._fields; } public fieldDescriptor(name: string): FieldDescriptor[] { if (this.isField(name)) { return this._fields[name]; } else { throw Error('Undefined field.'); } } public defProgram(definition: ASTDefProgram | ASTDefInteractiveProgram): void { if (this._program !== undefined) { fail(definition.startPos, definition.endPos, 'program-already-defined', [ i18nPosition(this._program.startPos), i18nPosition(definition.startPos) ]); } this._program = definition; } public defInteractiveProgram(definition: ASTDefInteractiveProgram): void { this.defProgram(definition); this._isInteractiveProgram = true; } public defProcedure(definition: ASTDefProcedure): void { const name = definition.name.value; if (name in this._procedures) { fail(definition.name.startPos, definition.name.endPos, 'procedure-already-defined', [ name, i18nPosition(this._procedures[name].startPos), i18nPosition(definition.startPos) ]); } const parameters = []; for (const parameter of definition.parameters) { parameters.push(parameter.value); } this._procedures[name] = definition; this._procedureParameters[name] = parameters; } public defFunction(definition: ASTDefFunction): void { const name = definition.name.value; if (name in this._functions) { fail(definition.name.startPos, definition.name.endPos, 'function-already-defined', [ name, i18nPosition(this._functions[name].startPos), i18nPosition(definition.startPos) ]); } else if (name in this._fields) { const fieldPos = this._constructors[this._fields[name][0].constructorName].startPos; fail( definition.name.startPos, definition.name.endPos, 'function-and-field-cannot-have-the-same-name', [name, i18nPosition(definition.startPos), i18nPosition(fieldPos)] ); } const parameters = []; for (const parameter of definition.parameters) { parameters.push(parameter.value); } this._functions[name] = definition; this._functionParameters[name] = parameters; } public defType(definition: ASTDefType): void { const typeName = definition.typeName.value; if (typeName in this._types) { fail(definition.typeName.startPos, definition.typeName.endPos, 'type-already-defined', [ typeName, i18nPosition(this._types[typeName].startPos), i18nPosition(definition.startPos) ]); } this._types[typeName] = definition; const constructorNames = []; for (const constructorDeclaration of definition.constructorDeclarations) { this._declareConstructor(typeName, constructorDeclaration); constructorNames.push(constructorDeclaration.constructorName.value); } this._typeConstructors[typeName] = constructorNames; } public _declareConstructor( typeName: string, constructorDeclaration: ASTConstructorDeclaration ): void { const constructorName = constructorDeclaration.constructorName.value; if (constructorName in this._constructors) { fail( constructorDeclaration.constructorName.startPos, constructorDeclaration.constructorName.endPos, 'constructor-already-defined', [ constructorName, i18nPosition(this._constructors[constructorName].startPos), i18nPosition(constructorDeclaration.startPos) ] ); } this._constructors[constructorName] = constructorDeclaration; this._constructorType[constructorName] = typeName; const constructorFields = {}; const fieldNames: string[] = []; let index = 0; for (const fieldName of constructorDeclaration.fieldNames) { if (fieldName.value in constructorFields) { fail(fieldName.startPos, fieldName.endPos, 'repeated-field-name', [ constructorName, fieldName.value ]); } constructorFields[fieldName.value] = true; fieldNames.push(fieldName.value); this._declareField( fieldName.startPos, fieldName.endPos, typeName, constructorName, fieldName.value, index ); index++; } this._constructorFields[constructorName] = fieldNames; } public _declareField( startPos: SourceReader, endPos: SourceReader, typeName: string, constructorName: string, fieldName: string, index: number ): void { if (fieldName in this._functions) { fail(startPos, endPos, 'function-and-field-cannot-have-the-same-name', [ fieldName, i18nPosition(this._functions[fieldName].startPos), i18nPosition(startPos) ]); } if (!(fieldName in this._fields)) { this._fields[fieldName] = []; } this._fields[fieldName].push(new FieldDescriptor(typeName, constructorName, index)); } /* Adds a new local name, failing if it already exists. */ public addNewLocalName(localName: Token, category: symbol): void { if (localName.value in this._localNames) { fail(localName.startPos, localName.endPos, 'local-name-conflict', [ localName.value, i18n(Symbol.keyFor(this._localNames[localName.value].category)), i18nPosition(this._localNames[localName.value].position), i18n(Symbol.keyFor(category)), i18nPosition(localName.startPos) ]); } this.setLocalName(localName, category); } /* Sets a local name. * It fails if it already exists with a different category. */ public setLocalName(localName: Token, category: symbol): void { if ( localName.value in this._localNames && this._localNames[localName.value].category !== category ) { fail(localName.startPos, localName.endPos, 'local-name-conflict', [ localName.value, i18n(Symbol.keyFor(this._localNames[localName.value].category)), i18nPosition(this._localNames[localName.value].position), i18n(Symbol.keyFor(category)), i18nPosition(localName.startPos) ]); } this._localNames[localName.value] = new LocalNameDescriptor(category, localName.startPos); } /* Removes a local name. */ public removeLocalName(localName: Token): void { delete this._localNames[localName.value]; } /* Removes all local names. */ public exitScope(): void { this._localNames = {}; } /* Get the attribute dictionary for a global name. * * A global name is the names of a global definition: * - the string 'program' * - any procedure name (e.g. 'P') * - any function name (e.g. 'f') * - any type name (e.g. 'A') * * The result is a dictionary of attributes. * */ public getAttributes(globalName: string): Record<string, ASTDef> { if (globalName === 'program' && this._program !== undefined) { return this._program.attributes; } else if (globalName in this._procedures) { return this._procedures[globalName].attributes; } else if (globalName in this._functions) { return this._functions[globalName].attributes; } else if (globalName in this._types) { return this._types[globalName].attributes; } else { return {}; } } }