Refactor scanner to use less state (#281)

The scanner was doing a lot of work per character. While this may have
been a performance problem, the motivation here is maintainability. This
change makes it so that advancing by one character is simply Scanner.position++.
It also does more work lazily: allocating substrings on demand.

Highlights:

* Remove unused tokens
* Rename Kind to Token
* Remove token position map
* Remove character size bookkeeping
* Remove (ch, chNext, chNext) state
* Convert class to closure

Note: Line and column tracking was temporarily removed to facilitate
refactoring and will be re-added in the following change.
This commit is contained in:
Nick Guerrera 2021-02-08 11:38:10 -08:00 коммит произвёл GitHub
Родитель 34185960de
Коммит 3198caebe3
6 изменённых файлов: 455 добавлений и 766 удалений

Просмотреть файл

@ -189,11 +189,6 @@ export function isIdentifierPart(ch: number,): boolean {
ch > CharacterCodes.maxAsciiCharacter && isUnicodeIdentifierPart(ch);
}
/** Characters that are in this range are actually code points that take two characters in utf16 */
export function sizeOf(ch: number): number {
return ch >= 0xD800 && ch <= 0xDBFF ? 2 : 1;
}
function lookupInUnicodeMap(code: number, map: ReadonlyArray<number>): boolean {
// Bail out quickly if it couldn't possibly be in the map.
if (code < map[0]) {

Просмотреть файл

@ -18,7 +18,8 @@ export const messages = {
InvalidEscapeSequence: { code: 1104, category: MessageCategory.Error, text: 'Invalid escape sequence' },
NoNewLineAtStartOfTripleQuotedString: { code: 1105, category: MessageCategory.Error, text: 'String content in triple quotes must begin on a new line' },
NoNewLineAtEndOfTripleQuotedString: { code: 1106, category: MessageCategory.Error, text: 'Closing triple quotes must begin on a new line' },
InconsistentTripleQuoteIndentation: { code: 1107, category: MessageCategory.Error, text: 'All lines in triple-quoted string lines must have the same indentation as closing triple quotes' }
InconsistentTripleQuoteIndentation: { code: 1107, category: MessageCategory.Error, text: 'All lines in triple-quoted string lines must have the same indentation as closing triple quotes' },
UnexpectedToken: { code: 1108, category: MessageCategory.Error, text: 'Unexpected token: \'{0}\'' }
};
export function format(text: string, ...args: Array<string | number>): string {

Просмотреть файл

@ -1,11 +1,10 @@
import { format } from './messages.js';
import { Kind, Scanner } from './scanner.js';
import { createScanner, Token } from './scanner.js';
import * as Types from './types.js';
export function parse(code: string) {
const scanner = new Scanner(code);
scanner.onError = (msg, params) => error(format(msg.text, ...params));
const scanner = createScanner(code, (msg, params) => error(format(msg.text, ...params)));
nextToken();
return parseADLScript();
@ -17,11 +16,11 @@ export function parse(code: string) {
end: 0
};
while (!scanner.eof) {
while (!scanner.eof()) {
script.statements.push(parseStatement());
}
script.end = scanner.offset;
script.end = scanner.position;
return script;
}
@ -32,33 +31,33 @@ export function parse(code: string) {
const tok = token();
switch (tok) {
case Kind.ImportKeyword:
case Token.ImportKeyword:
if (decorators.length > 0) {
error('Cannot decorate an import statement');
}
return parseImportStatement();
case Kind.ModelKeyword:
case Token.ModelKeyword:
return parseModelStatement(decorators);
case Kind.InterfaceKeyword:
case Token.InterfaceKeyword:
return parseInterfaceStatement(decorators);
case Kind.Semicolon:
case Token.Semicolon:
if (decorators.length > 0) {
error('Cannot decorate an empty statement');
}
// no need to put empty statement nodes in the tree for now
// since we aren't trying to emit ADL
parseExpected(Kind.Semicolon);
parseExpected(Token.Semicolon);
continue;
}
throw error(`Expected statement, but found ${Kind[tok]}`);
throw error(`Expected statement, but found ${Token[tok]}`);
}
}
function parseDecoratorList() {
const decorators: Array<Types.DecoratorExpressionNode> = [];
while (token() === Kind.At) {
while (token() === Token.At) {
decorators.push(parseDecoratorExpression());
}
@ -69,15 +68,15 @@ export function parse(code: string) {
decorators: Array<Types.DecoratorExpressionNode>
): Types.InterfaceStatementNode {
const pos = tokenPos();
parseExpected(Kind.InterfaceKeyword);
parseExpected(Token.InterfaceKeyword);
const id = parseIdentifier();
let parameters: Types.ModelExpressionNode | undefined;
if (token() === Kind.OpenParen) {
if (token() === Token.OpenParen) {
const modelPos = tokenPos();
parseExpected(Kind.OpenParen);
parseExpected(Token.OpenParen);
const modelProps = parseModelPropertyList();
parseExpected(Kind.CloseParen);
parseExpected(Token.CloseParen);
parameters = finishNode({
kind: Types.SyntaxKind.ModelExpression,
properties: modelProps,
@ -86,18 +85,18 @@ export function parse(code: string) {
}
parseExpected(Kind.OpenBrace);
parseExpected(Token.OpenBrace);
const properties: Array<Types.InterfacePropertyNode> = [];
do {
if (token() == Kind.CloseBrace) {
if (token() == Token.CloseBrace) {
break;
}
const memberDecorators = parseDecoratorList();
properties.push(parseInterfaceProperty(memberDecorators));
} while (parseOptional(Kind.Comma) || parseOptional(Kind.Semicolon));
} while (parseOptional(Token.Comma) || parseOptional(Token.Semicolon));
parseExpected(Kind.CloseBrace);
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.InterfaceStatement,
@ -111,13 +110,13 @@ export function parse(code: string) {
function parseInterfaceProperty(decorators: Array<Types.DecoratorExpressionNode>): Types.InterfacePropertyNode {
const pos = tokenPos();
const id = parseIdentifier();
parseExpected(Kind.OpenParen);
parseExpected(Token.OpenParen);
const modelPos = tokenPos();
let modelProps: Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode>= [];
if (!parseOptional(Kind.CloseParen)) {
if (!parseOptional(Token.CloseParen)) {
modelProps = parseModelPropertyList();
parseExpected(Kind.CloseParen);
parseExpected(Token.CloseParen);
}
const parameters: Types.ModelExpressionNode = finishNode({
kind: Types.SyntaxKind.ModelExpression,
@ -125,7 +124,7 @@ export function parse(code: string) {
decorators: []
}, modelPos);
parseExpected(Kind.Colon);
parseExpected(Token.Colon);
const returnType = parseExpression();
return finishNode({
@ -142,19 +141,19 @@ export function parse(code: string) {
): Types.ModelStatementNode {
const pos = tokenPos();
parseExpected(Kind.ModelKeyword);
parseExpected(Token.ModelKeyword);
const id = parseIdentifier();
let templateParameters: Array<Types.TemplateParameterDeclarationNode> = [];
if (parseOptional(Kind.LessThan)) {
if (parseOptional(Token.LessThan)) {
templateParameters = parseTemplateParameters();
parseExpected(Kind.GreaterThan);
parseExpected(Token.GreaterThan);
}
if (token() === Kind.OpenBrace) {
parseExpected(Kind.OpenBrace);
if (token() === Token.OpenBrace) {
parseExpected(Token.OpenBrace);
const properties = parseModelPropertyList();
parseExpected(Kind.CloseBrace);
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.ModelStatement,
@ -163,10 +162,10 @@ export function parse(code: string) {
decorators,
properties
}, pos);
} else if (token() === Kind.Equals) {
parseExpected(Kind.Equals);
} else if (token() === Token.Equals) {
parseExpected(Token.Equals);
const assignment = parseExpression();
parseExpected(Kind.Semicolon);
parseExpected(Token.Semicolon);
return finishNode({
kind: Types.SyntaxKind.ModelStatement,
id,
@ -190,7 +189,7 @@ export function parse(code: string) {
} as const, pos);
params.push(param);
} while (parseOptional(Kind.Comma));
} while (parseOptional(Token.Comma));
return params;
}
@ -199,13 +198,13 @@ export function parse(code: string) {
const properties: Array<Types.ModelPropertyNode | Types.ModelSpreadPropertyNode> = [];
do {
if (token() === Kind.CloseBrace || token() === Kind.CloseParen) {
if (token() === Token.CloseBrace || token() === Token.CloseParen) {
break;
}
const memberDecorators = parseDecoratorList();
if (token() === Kind.Elipsis) {
if (token() === Token.Elipsis) {
if (memberDecorators.length > 0) {
error('Cannot decorate a spread property');
}
@ -213,7 +212,7 @@ export function parse(code: string) {
} else {
properties.push(parseModelProperty(memberDecorators));
}
} while (parseOptional(Kind.Comma) || parseOptional(Kind.Semicolon));
} while (parseOptional(Token.Comma) || parseOptional(Token.Semicolon));
return properties;
@ -221,7 +220,7 @@ export function parse(code: string) {
function parseModelSpreadProperty(): Types.ModelSpreadPropertyNode {
const pos = tokenPos();
parseExpected(Kind.Elipsis);
parseExpected(Token.Elipsis);
// This could be broadened to allow any type expression
const target = parseIdentifier();
@ -236,18 +235,18 @@ export function parse(code: string) {
const pos = tokenPos();
let id: Types.IdentifierNode | Types.StringLiteralNode;
switch (token()) {
case Kind.Identifier:
case Token.Identifier:
id = parseIdentifier();
break;
case Kind.StringLiteral:
case Token.StringLiteral:
id = parseStringLiteral();
break;
default:
throw error(`expected a model property, got ${Kind[token()]}`);
throw error(`expected a model property, got ${Token[token()]}`);
}
const optional = parseOptional(Kind.Question);
parseExpected(Kind.Colon);
const optional = parseOptional(Token.Question);
parseExpected(Token.Colon);
const value = parseExpression();
return finishNode({
@ -267,7 +266,7 @@ export function parse(code: string) {
const pos = tokenPos();
let node: Types.Expression = parseIntersectionExpressionOrHigher();
if (token() !== Kind.Bar) {
if (token() !== Token.Bar) {
return node;
}
@ -276,7 +275,7 @@ export function parse(code: string) {
options: [node]
}, pos);
while (parseOptional(Kind.Bar)) {
while (parseOptional(Token.Bar)) {
const expr = parseIntersectionExpressionOrHigher();
node.options.push(expr);
}
@ -290,7 +289,7 @@ export function parse(code: string) {
const pos = tokenPos();
let node: Types.Expression = parseArrayExpressionOrHigher();
if (token() !== Kind.Ampersand) {
if (token() !== Token.Ampersand) {
return node;
}
@ -299,7 +298,7 @@ export function parse(code: string) {
options: [node]
}, pos);
while (parseOptional(Kind.Ampersand)) {
while (parseOptional(Token.Ampersand)) {
const expr = parseArrayExpressionOrHigher();
node.options.push(expr);
}
@ -313,8 +312,8 @@ export function parse(code: string) {
const pos = tokenPos();
let expr = parsePrimaryExpression();
while (parseOptional(Kind.OpenBracket)) {
parseExpected(Kind.CloseBracket);
while (parseOptional(Token.OpenBracket)) {
parseExpected(Token.CloseBracket);
expr = finishNode({
kind: Types.SyntaxKind.ArrayExpression,
@ -329,13 +328,13 @@ export function parse(code: string) {
const pos = tokenPos();
const expr = parseIdentifierOrMemberExpression();
if (token() !== Kind.LessThan) {
if (token() !== Token.LessThan) {
return expr;
}
parseExpected(Kind.LessThan);
parseExpected(Token.LessThan);
const args = parseExpressionList();
parseExpected(Kind.GreaterThan);
parseExpected(Token.GreaterThan);
return finishNode({
kind: Types.SyntaxKind.TemplateApplication,
@ -347,22 +346,22 @@ export function parse(code: string) {
function parseImportStatement(): Types.ImportStatementNode {
const pos = tokenPos();
parseExpected(Kind.ImportKeyword);
parseExpected(Token.ImportKeyword);
const id = parseIdentifier();
let as: Array<Types.NamedImportNode> = [];
if (token() === Kind.Identifier && tokenValue() === 'as') {
parseExpected(Kind.Identifier);
parseExpected(Kind.OpenBrace);
if (token() === Token.Identifier && tokenValue() === 'as') {
parseExpected(Token.Identifier);
parseExpected(Token.OpenBrace);
if (token() !== Kind.CloseBrace) {
if (token() !== Token.CloseBrace) {
as = parseNamedImports();
}
parseExpected(Kind.CloseBrace);
parseExpected(Token.CloseBrace);
}
parseExpected(Kind.Semicolon);
parseExpected(Token.Semicolon);
return finishNode({
kind: Types.SyntaxKind.ImportStatement,
as, id
@ -377,21 +376,21 @@ export function parse(code: string) {
kind: Types.SyntaxKind.NamedImport,
id: parseIdentifier()
}, pos));
} while (parseOptional(Kind.Comma));
} while (parseOptional(Token.Comma));
return names;
}
function parseDecoratorExpression(): Types.DecoratorExpressionNode {
const pos = tokenPos();
parseExpected(Kind.At);
parseExpected(Token.At);
const target = parseIdentifierOrMemberExpression();
let args: Array<Types.Expression> = [];
if (parseOptional(Kind.OpenParen)) {
if (!parseOptional(Kind.CloseParen)) {
if (parseOptional(Token.OpenParen)) {
if (!parseOptional(Token.CloseParen)) {
args = parseExpressionList();
parseExpected(Kind.CloseParen);
parseExpected(Token.CloseParen);
}
} else if (tokenIsLiteral()) {
args = [parsePrimaryExpression()];
@ -409,7 +408,7 @@ export function parse(code: string) {
do {
args.push(parseExpression());
} while (parseOptional(Kind.Comma));
} while (parseOptional(Token.Comma));
return args;
}
@ -418,7 +417,7 @@ export function parse(code: string) {
let base: Types.IdentifierNode | Types.MemberExpressionNode = parseIdentifier();
while (parseOptional(Kind.Dot)) {
while (parseOptional(Token.Dot)) {
const pos = tokenPos();
base = finishNode({
kind: Types.SyntaxKind.MemberExpression,
@ -432,39 +431,39 @@ export function parse(code: string) {
function parsePrimaryExpression(): Types.Expression {
switch (token()) {
case Kind.Identifier:
case Token.Identifier:
return parseReferenceExpression();
case Kind.StringLiteral:
case Token.StringLiteral:
return parseStringLiteral();
case Kind.TrueKeyword:
case Kind.FalseKeyword:
case Token.TrueKeyword:
case Token.FalseKeyword:
return parseBooleanLiteral();
case Kind.NumericLiteral:
case Token.NumericLiteral:
return parseNumericLiteral();
case Kind.OpenBrace:
case Token.OpenBrace:
return parseModelExpression([]);
case Kind.OpenBracket:
case Token.OpenBracket:
return parseTupleExpression();
case Kind.OpenParen:
case Token.OpenParen:
return parseParenthesizedExpression();
}
throw error(`Unexpected token: ${Kind[token()]}`);
throw error(`Unexpected token: ${Token[token()]}`);
}
function parseParenthesizedExpression(): Types.Expression {
const pos = tokenPos();
parseExpected(Kind.OpenParen);
parseExpected(Token.OpenParen);
const expr = parseExpression();
parseExpected(Kind.CloseParen);
parseExpected(Token.CloseParen);
return finishNode(expr, pos);
}
function parseTupleExpression(): Types.TupleExpressionNode {
const pos = tokenPos();
parseExpected(Kind.OpenBracket);
parseExpected(Token.OpenBracket);
const values = parseExpressionList();
parseExpected(Kind.CloseBracket);
parseExpected(Token.CloseBracket);
return finishNode({
kind: Types.SyntaxKind.TupleExpression,
values
@ -473,9 +472,9 @@ export function parse(code: string) {
function parseModelExpression(decorators: Array<Types.DecoratorExpressionNode>): Types.ModelExpressionNode {
const pos = tokenPos();
parseExpected(Kind.OpenBrace);
parseExpected(Token.OpenBrace);
const properties = parseModelPropertyList();
parseExpected(Kind.CloseBrace);
parseExpected(Token.CloseBrace);
return finishNode({
kind: Types.SyntaxKind.ModelExpression,
decorators,
@ -485,12 +484,10 @@ export function parse(code: string) {
function parseStringLiteral(): Types.StringLiteralNode {
const pos = tokenPos();
const text = tokenValue();
const value = tokenStringValue();
parseExpected(Kind.StringLiteral);
const value = tokenValue();
parseExpected(Token.StringLiteral);
return finishNode({
kind: Types.SyntaxKind.StringLiteral,
text,
value
}, pos);
}
@ -500,7 +497,7 @@ export function parse(code: string) {
const text = tokenValue();
const value = Number(text);
parseExpected(Kind.NumericLiteral);
parseExpected(Token.NumericLiteral);
return finishNode({
kind: Types.SyntaxKind.NumericLiteral,
text,
@ -510,12 +507,10 @@ export function parse(code: string) {
function parseBooleanLiteral(): Types.BooleanLiteralNode {
const pos = tokenPos();
const token = parseExpectedOneOf(Kind.TrueKeyword, Kind.FalseKeyword);
const value = token == Kind.TrueKeyword;
const text = value ? 'true' : 'false';
const token = parseExpectedOneOf(Token.TrueKeyword, Token.FalseKeyword);
const value = token == Token.TrueKeyword;
return finishNode({
kind: Types.SyntaxKind.BooleanLiteral,
text,
value
}, pos);
}
@ -524,10 +519,10 @@ export function parse(code: string) {
const id = token();
const pos = tokenPos();
if (id !== Kind.Identifier) {
error(`expected an identifier, got ${Kind[id]}`);
if (id !== Token.Identifier) {
error(`expected an identifier, got ${Token[id]}`);
}
const sv = scanner.value;
const sv = tokenValue();
nextToken();
@ -543,15 +538,11 @@ export function parse(code: string) {
}
function tokenValue() {
return scanner.value;
}
function tokenStringValue() {
return scanner.stringValue;
return scanner.getTokenValue();
}
function tokenPos() {
return scanner.offset;
return scanner.tokenPosition;
}
function nextToken() {
@ -565,10 +556,10 @@ export function parse(code: string) {
function tokenIsTrivia() {
switch (token()) {
case Kind.Whitespace:
case Kind.NewLine:
case Kind.MultiLineComment:
case Kind.SingleLineComment:
case Token.Whitespace:
case Token.NewLine:
case Token.MultiLineComment:
case Token.SingleLineComment:
return true;
default:
return false;
@ -577,10 +568,10 @@ export function parse(code: string) {
function tokenIsLiteral() {
switch (token()) {
case Kind.NumericLiteral:
case Kind.StringLiteral:
case Kind.TrueKeyword:
case Kind.FalseKeyword:
case Token.NumericLiteral:
case Token.StringLiteral:
case Token.TrueKeyword:
case Token.FalseKeyword:
return true;
default:
return false;
@ -596,18 +587,18 @@ export function parse(code: string) {
}
function error(msg: string) {
throw new Error(`[${scanner.position.line + 1}, ${scanner.position.character + 1}] ${msg}`);
throw new Error(msg);
}
function parseExpected(expectedToken: Kind) {
function parseExpected(expectedToken: Token) {
if (token() === expectedToken) {
nextToken();
} else {
throw error(`expected ${Kind[expectedToken]}, got ${Kind[token()]}`);
throw error(`expected ${Token[expectedToken]}, got ${Token[token()]}`);
}
}
function parseExpectedOneOf<A extends Kind, B extends Kind>(
function parseExpectedOneOf<A extends Token, B extends Token>(
expectedTokenA: A,
expectedTokenB: B
): A | B {
@ -618,11 +609,11 @@ export function parse(code: string) {
nextToken();
return expectedTokenB;
} else {
throw error(`expected ${Kind[expectedTokenA]} or ${Kind[expectedTokenA]}, got ${Kind[token()]}`);
throw error(`expected ${Token[expectedTokenA]} or ${Token[expectedTokenA]}, got ${Token[token()]}`);
}
}
function parseOptional(optionalToken: Kind) {
function parseOptional(optionalToken: Token) {
if (token() === optionalToken) {
nextToken();
return true;
@ -723,4 +714,4 @@ export function walk<T>(node: Types.Node, cb: NodeCb<T>, seen = new Set()): T |
}
return walk(childNode, cb, seen);
});
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -244,19 +244,16 @@ export type LiteralNode = StringLiteralNode | NumericLiteralNode | BooleanLitera
export interface StringLiteralNode extends Node {
kind: SyntaxKind.StringLiteral;
value: string;
text: string;
}
export interface NumericLiteralNode extends Node {
kind: SyntaxKind.NumericLiteral;
value: number;
text: string;
}
export interface BooleanLiteralNode extends Node {
kind: SyntaxKind.BooleanLiteral;
value: boolean;
text: string;
}
export interface UnionExpressionNode extends Node {
@ -278,4 +275,4 @@ export interface TemplateApplicationNode extends Node {
export interface TemplateParameterDeclarationNode extends Node {
kind: SyntaxKind.TemplateParameterDeclaration;
sv: string;
}
}

Просмотреть файл

@ -2,22 +2,21 @@ import { strictEqual } from 'assert';
import { readFile } from 'fs/promises';
import { URL } from 'url';
import { format } from '../compiler/messages.js';
import { Kind, Position, Scanner } from '../compiler/scanner.js';
import { createScanner, throwOnError, Token } from '../compiler/scanner.js';
type TokenEntry = [Kind, string?, Position?];
type TokenEntry = [Token, string?];
function tokens(text: string): Array<TokenEntry> {
const scanner = new Scanner(text);
function tokens(text: string, onError = throwOnError): Array<TokenEntry> {
const scanner = createScanner(text, onError);
const result: Array<TokenEntry> = [];
do {
const token = scanner.scan();
strictEqual(token, scanner.token);
result.push([
scanner.token,
scanner.value,
scanner.positionFromOffset(scanner.offset)
scanner.getTokenText(),
]);
} while (!scanner.eof);
} while (!scanner.eof());
// verify that the input matches the output
const out = result.map(each => each[1]).join('');
@ -26,16 +25,10 @@ function tokens(text: string): Array<TokenEntry> {
return result;
}
function dump(tokens: Array<any>) {
//console.log(tokens.map(each => JSON.stringify(each, undefined, 2)).join('\n'));
console.log(tokens.map(each => JSON.stringify(each[1])).join('\n'));
}
function verify(tokens: Array<TokenEntry>, expecting: Array<TokenEntry>) {
for (const [index, [expectedToken, expectedValue]] of expecting.entries()) {
const [token, value] = tokens[index];
strictEqual(Kind[token], Kind[expectedToken], `Token ${index} must match`);
strictEqual(Token[token], Token[expectedToken], `Token ${index} must match`);
if (expectedValue) {
strictEqual(value, expectedValue, `Token ${index} value must match`);
@ -48,14 +41,14 @@ describe('scanner', () => {
it('smoketest', () => {
const all = tokens('\tthis is a test');
verify(all, [
[Kind.Whitespace],
[Kind.Identifier, 'this'],
[Kind.Whitespace],
[Kind.Identifier, 'is'],
[Kind.Whitespace],
[Kind.Identifier, 'a'],
[Kind.Whitespace],
[Kind.Identifier, 'test'],
[Token.Whitespace],
[Token.Identifier, 'this'],
[Token.Whitespace],
[Token.Identifier, 'is'],
[Token.Whitespace],
[Token.Identifier, 'a'],
[Token.Whitespace],
[Token.Identifier, 'test'],
]);
});
@ -63,14 +56,14 @@ describe('scanner', () => {
const all = tokens('model Foo{x:y}');
verify(all, [
[Kind.ModelKeyword],
[Kind.Whitespace],
[Kind.Identifier, 'Foo'],
[Kind.OpenBrace],
[Kind.Identifier, 'x'],
[Kind.Colon],
[Kind.Identifier, 'y'],
[Kind.CloseBrace]
[Token.ModelKeyword],
[Token.Whitespace],
[Token.Identifier, 'Foo'],
[Token.OpenBrace],
[Token.Identifier, 'x'],
[Token.Colon],
[Token.Identifier, 'y'],
[Token.CloseBrace]
]);
});
@ -78,73 +71,51 @@ describe('scanner', () => {
const all = tokens('@foo(1,"hello",foo)');
verify(all, [
[Kind.At],
[Kind.Identifier, 'foo'],
[Kind.OpenParen],
[Kind.NumericLiteral, '1'],
[Kind.Comma],
[Kind.StringLiteral, '"hello"'],
[Kind.Comma],
[Kind.Identifier],
[Kind.CloseParen]
[Token.At],
[Token.Identifier, 'foo'],
[Token.OpenParen],
[Token.NumericLiteral, '1'],
[Token.Comma],
[Token.StringLiteral, '"hello"'],
[Token.Comma],
[Token.Identifier],
[Token.CloseParen]
]);
});
it('does not scan greater-than-equals as one operator', () => {
const all = tokens('x>=y');
verify(all, [
[Kind.Identifier],
[Kind.GreaterThan],
[Kind.Equals],
[Kind.Identifier]
[Token.Identifier],
[Token.GreaterThan],
[Token.Equals],
[Token.Identifier]
]);
});
it('rescans >=', () => {
const scanner = new Scanner('x>=y');
scanner.scan();
strictEqual(scanner.scan(), Kind.GreaterThan);
strictEqual(scanner.rescanGreaterThan(), Kind.GreaterThanEquals);
});
it('rescans >>=', () => {
const scanner = new Scanner('x>>=');
scanner.scan();
strictEqual(scanner.scan(), Kind.GreaterThan);
strictEqual(scanner.rescanGreaterThan(), Kind.GreaterThanGreaterThanEquals);
});
it('rescans >>', () => {
const scanner = new Scanner('x>>y');
scanner.scan();
strictEqual(scanner.scan(), Kind.GreaterThan);
strictEqual(scanner.rescanGreaterThan(), Kind.GreaterThanGreaterThan);
});
it('scans numeric literals', () => {
const all = tokens('42 0xBEEF 0b1010 1.5e4 314.0e-2 1e+1000');
verify(all, [
[Kind.NumericLiteral, '42'],
[Kind.Whitespace],
[Kind.NumericLiteral, '0xBEEF'],
[Kind.Whitespace],
[Kind.NumericLiteral, '0b1010'],
[Kind.Whitespace],
[Kind.NumericLiteral, '1.5e4'],
[Kind.Whitespace],
[Kind.NumericLiteral, '314.0e-2'],
[Kind.Whitespace],
[Kind.NumericLiteral, '1e+1000'],
[Token.NumericLiteral, '42'],
[Token.Whitespace],
[Token.NumericLiteral, '0xBEEF'],
[Token.Whitespace],
[Token.NumericLiteral, '0b1010'],
[Token.Whitespace],
[Token.NumericLiteral, '1.5e4'],
[Token.Whitespace],
[Token.NumericLiteral, '314.0e-2'],
[Token.Whitespace],
[Token.NumericLiteral, '1e+1000'],
]);
});
function scanString(text: string, expectedValue: string) {
const scanner = new Scanner(text);
scanner.onError = (msg, params) => { throw new Error(format(msg.text, ...params)); };
strictEqual(scanner.scan(), Kind.StringLiteral);
strictEqual(scanner.token, Kind.StringLiteral);
strictEqual(scanner.value, text);
strictEqual(scanner.stringValue, expectedValue);
const scanner = createScanner(text, (msg, params) => { throw new Error(format(msg.text, ...params)); });
strictEqual(scanner.scan(), Token.StringLiteral);
strictEqual(scanner.token, Token.StringLiteral);
strictEqual(scanner.getTokenText(), text);
strictEqual(scanner.getTokenValue(), expectedValue);
}
it('scans strings single-line strings with escape sequences', () => {
@ -173,8 +144,8 @@ describe('scanner', () => {
'This is a triple-quoted string\n\n\n\nAnd this is another line');
});
it('parses this file', async () => {
it('scans this file', async () => {
const text = await readFile(new URL(import.meta.url), 'utf-8');
const all = tokens(text);
tokens(text, function(msg, params) { /* ignore errors */});
});
});