UNPKG

@gobstones/gobstones-parser

Version:
194 lines (172 loc) 5.93 kB
/* eslint-disable no-underscore-dangle */ /* A SourceReader represents the current position in a source file. * It keeps track of line and column numbers. * Methods are non-destructive. For example: * * let r = new SourceReader('foo.gbs', 'if\n(True)'); * * r.peek(); // ~~> 'i' * r = r.consumeCharacter(); // Note: returns a new file reader. * * r.peek(); // ~~> 'f' * r = r.consumeCharacter(); * * r.peek(); // ~~> '\n' * r = r.consumeCharacter('\n'); * * r.line(); // ~~> 2 */ export class SourceReader { private _filename: string; private _string: string; private _index: number; private _line: number; private _column: number; private _regions: string[]; public constructor(filename: string, string: string) { this._filename = filename; // Filename this._string = string; // Source of the current file this._index = 0; // Index in the current file this._line = 1; // Line in the current file this._column = 1; // Column in the current file this._regions = []; // Lexical (static) stack of regions } public _clone(): SourceReader { const r = new SourceReader(this._filename, this._string); r._index = this._index; r._line = this._line; r._column = this._column; r._regions = this._regions; return r; } public get filename(): string { return this._filename; } public get line(): number { return this._line; } public get column(): number { return this._column; } public get region(): string { if (this._regions.length > 0) { return this._regions[0]; } else { return ''; } } /* Consume one character */ public consumeCharacter(): SourceReader { const r = this._clone(); if (r.peek() === '\n') { r._line++; r._column = 1; } else { r._column++; } r._index++; return r; } /* Consume characters from the input, one per each character in the string * (the contents of the string are ignored). */ public consumeString(string: string): SourceReader { let r = this._clone(); // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of string) { r = r.consumeCharacter(); } return r; } /* Returns the SourceReader after consuming an 'invisible' character. * Invisible characters affect the index but not the line or column. */ public consumeInvisibleCharacter(): SourceReader { const r = this._clone(); r._index++; return r; } /* Consume 'invisible' characters from the input, one per each character * in the string */ public consumeInvisibleString(string: string): SourceReader { // eslint-disable-next-line @typescript-eslint/no-this-alias let r: SourceReader = this; // eslint-disable-next-line @typescript-eslint/no-unused-vars for (const _ of string) { r = r.consumeInvisibleCharacter(); } return r; } /* Return true if the substring occurs at the current point. */ public startsWith(sub: string | any[]): boolean { const i = this._index; const j = this._index + sub.length; return j <= this._string.length && this._string.substring(i, j) === sub; } /* Return true if we have reached the end of the current file */ public eof(): boolean { return this._index >= this._string.length; } /* Return the current character, assuming we have not reached EOF */ public peek(): string { return this._string[this._index]; } /* Push a region to the stack of regions (non-destructively) */ public beginRegion(region: string): SourceReader { const r = this._clone(); r._regions = [region].concat(r._regions); return r; } /* Pop a region from the stack of regions (non-destructively) */ public endRegion(): SourceReader { const r = this._clone(); if (r._regions.length > 0) { r._regions = r._regions.slice(1); } return r; } } /* Return a source reader that represents an unknown position */ export const UnknownPosition = new SourceReader('(?)', ''); export type Input = string | Record<string, string> | string[]; /* An instance of MultifileReader represents a scanner for reading * source code from a list of files. */ export class MultifileReader { private _filenames: string[]; private _input: Input; private _index: number; /* The 'input' parameter should be either: * (1) a string. e.g. 'program {}', or * (2) a map from filenames to strings, e.g. * { * 'foo.gbs': 'program { P() }', * 'bar.gbs': 'procedure P() {}', * } */ public constructor(input: Input) { if (typeof input === 'string') { input = { '(?)': input }; } this._filenames = Object.keys(input); this._filenames.sort(); this._input = input; this._index = 0; } /* Return true if there are more files */ public moreFiles(): boolean { return this._index + 1 < this._filenames.length; } /* Advance to the next file */ public nextFile(): void { this._index++; } /* Return a SourceReader for the current files */ public readCurrentFile(): SourceReader { if (this._index < this._filenames.length) { const filename = this._filenames[this._index]; return new SourceReader(filename, this._input[filename]); } else { return new SourceReader('(?)', ''); } } }