@gobstones/gobstones-parser
Version:
Gobstones parser
483 lines (424 loc) • 16.2 kB
text/typescript
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 {};
}
}
}