Add a simple scanner for identifiers
This commit is contained in:
parent
7cbbf788f8
commit
bcec19ecc8
5 changed files with 734 additions and 0 deletions
358
src/ast.ts
Normal file
358
src/ast.ts
Normal file
|
@ -0,0 +1,358 @@
|
||||||
|
|
||||||
|
import "reflect-metadata"
|
||||||
|
|
||||||
|
interface JsonArray extends Array<Json> { };
|
||||||
|
interface JsonObject { [key: string]: Json }
|
||||||
|
type Json = string | boolean | number | JsonArray | JsonObject;
|
||||||
|
|
||||||
|
export enum SyntaxKind {
|
||||||
|
|
||||||
|
// Tokens
|
||||||
|
|
||||||
|
Literal,
|
||||||
|
Identifier,
|
||||||
|
Operator,
|
||||||
|
Punctuated,
|
||||||
|
|
||||||
|
SourceFile,
|
||||||
|
|
||||||
|
QualName,
|
||||||
|
|
||||||
|
// Expressions
|
||||||
|
|
||||||
|
ConstantExpr,
|
||||||
|
ReferenceExpr,
|
||||||
|
|
||||||
|
// Type declarations
|
||||||
|
|
||||||
|
TypeReference,
|
||||||
|
|
||||||
|
// Declaration nodes
|
||||||
|
|
||||||
|
VariableDecl,
|
||||||
|
FunctionDecl,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EdgeType {
|
||||||
|
Primitive = 1,
|
||||||
|
Node = 2,
|
||||||
|
Nullable = 4,
|
||||||
|
List = 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextFile {
|
||||||
|
|
||||||
|
constructor(public path: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextPos {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public offset: number,
|
||||||
|
public line: number,
|
||||||
|
public column: number
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new TextPos(this.offset, this.line, this.column)
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
offset: this.offset,
|
||||||
|
line: this.line,
|
||||||
|
column: this.column
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TextSpan {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public file: TextFile,
|
||||||
|
public start: TextPos,
|
||||||
|
public end: TextPos
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
clone() {
|
||||||
|
return new TextSpan(this.file, this.start.clone(), this.end.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
file: this.file.path,
|
||||||
|
start: this.start.toJSON(),
|
||||||
|
end: this.end.toJSON(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Literal {
|
||||||
|
|
||||||
|
kind = SyntaxKind.Literal;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
value: EdgeType.Primitive,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public value: string | bigint,
|
||||||
|
public span: TextSpan
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
value: typeof this.value === 'bigint' ? Number(this.value) : this.value,
|
||||||
|
span: this.span.toJSON(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PunctType {
|
||||||
|
Paren,
|
||||||
|
Bracket,
|
||||||
|
Brace,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Punctuated {
|
||||||
|
|
||||||
|
kind = SyntaxKind.Punctuated
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
punctuator: EdgeType.Primitive,
|
||||||
|
elements: EdgeType.Node | EdgeType.List
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public punctuator: PunctType,
|
||||||
|
public elements: Token[],
|
||||||
|
public span: TextSpan
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
kind: 'Punctuated',
|
||||||
|
punctuator: this.punctuator,
|
||||||
|
elements: this.elements.map(element => element.toJSON()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Identifier {
|
||||||
|
|
||||||
|
kind = SyntaxKind.Identifier;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
text: EdgeType.Primitive
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public text: string,
|
||||||
|
public span: TextSpan
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
kind: 'Identifier',
|
||||||
|
text: this.text,
|
||||||
|
span: this.span.toJSON(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Operator {
|
||||||
|
|
||||||
|
kind = SyntaxKind.Operator;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
text: EdgeType.Primitive
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public text: string,
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON(): Json {
|
||||||
|
return {
|
||||||
|
kind: 'Operator',
|
||||||
|
text: this.text,
|
||||||
|
span: this.span.toJSON(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Token
|
||||||
|
= Identifier
|
||||||
|
| Operator
|
||||||
|
| Literal
|
||||||
|
| Punctuated
|
||||||
|
|
||||||
|
export class QualName {
|
||||||
|
|
||||||
|
kind = SyntaxKind.QualName;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
name: EdgeType.Node,
|
||||||
|
path: EdgeType.Node | EdgeType.List,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: string,
|
||||||
|
public path: Identifier[],
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConstantExpr {
|
||||||
|
|
||||||
|
kind = SyntaxKind.ConstantExpr;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
value: EdgeType.Primitive,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public value: string | bigint,
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Expr
|
||||||
|
= ConstantExpr
|
||||||
|
|
||||||
|
export class TypeReference {
|
||||||
|
|
||||||
|
kind = SyntaxKind.TypeReference;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
name: EdgeType.Node,
|
||||||
|
args: EdgeType.Node | EdgeType.List,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: QualName,
|
||||||
|
public args: TypeDecl[],
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TypeDecl
|
||||||
|
= TypeReference
|
||||||
|
|
||||||
|
export class Unexpanded {
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
tokens: EdgeType.Node | EdgeType.List
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public tokens: Token[],
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FunctionDecl {
|
||||||
|
|
||||||
|
kind = SyntaxKind.FunctionDecl;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
name: EdgeType.Node,
|
||||||
|
params: EdgeType.Node | EdgeType.List,
|
||||||
|
returnType: EdgeType.Node | EdgeType.Nullable,
|
||||||
|
body: EdgeType.Node | EdgeType.List,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public name: QualName,
|
||||||
|
public params: Param[],
|
||||||
|
public returnType: TypeDecl | null,
|
||||||
|
public body: Statement[] | null,
|
||||||
|
public span: TextSpan,
|
||||||
|
public parentNode: Syntax | null = null
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VariableDecl {
|
||||||
|
|
||||||
|
kind = SyntaxKind.VariableDecl;
|
||||||
|
|
||||||
|
static META = {
|
||||||
|
bindings: EdgeType.Node,
|
||||||
|
typeDecl: EdgeType.Node | EdgeType.Nullable,
|
||||||
|
value: EdgeType.Node | EdgeType.Nullable,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public bindings: Pattern,
|
||||||
|
public typeDecl: TypeDecl | null,
|
||||||
|
public value: Expr | null,
|
||||||
|
public span: TextSpan
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Decl
|
||||||
|
= Unexpanded
|
||||||
|
| FunctionDecl
|
||||||
|
| VariableDecl
|
||||||
|
|
||||||
|
export type Syntax
|
||||||
|
= Decl
|
||||||
|
| Expr
|
||||||
|
| SourceFile
|
||||||
|
| QualName
|
||||||
|
|
||||||
|
export class SourceFile {
|
||||||
|
|
||||||
|
constructor(public elements: Decl[], public span: TextSpan) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
117
src/bin/bolt.ts
Normal file
117
src/bin/bolt.ts
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import * as fs from "fs"
|
||||||
|
|
||||||
|
import yargs from "yargs"
|
||||||
|
|
||||||
|
import { Scanner } from "../scanner"
|
||||||
|
import { Token, TextFile } from "../ast"
|
||||||
|
|
||||||
|
function toArray<T>(value: T): T extends Array<any> ? T : T[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value as T[]
|
||||||
|
}
|
||||||
|
return value === null || value === undefined ? [] : [value]
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushAll<T>(array: T[], elements: T[]) {
|
||||||
|
for (const element of elements) {
|
||||||
|
array.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flatMap<T>(array: T[], proc: (element: T) => T[]) {
|
||||||
|
let out: T[] = []
|
||||||
|
for (const element of array) {
|
||||||
|
pushAll(out, proc(element));
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Hook {
|
||||||
|
timing: 'before' | 'after'
|
||||||
|
name: string
|
||||||
|
effects: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseHook(str: string): Hook {
|
||||||
|
let timing: 'before' | 'after' = 'before';
|
||||||
|
if (str[0] === '+') {
|
||||||
|
str = str.substring(1)
|
||||||
|
timing = 'after';
|
||||||
|
}
|
||||||
|
const [name, rawEffects] = str.split('=');
|
||||||
|
return {
|
||||||
|
timing,
|
||||||
|
name,
|
||||||
|
effects: rawEffects.split(','),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yargs
|
||||||
|
.command(
|
||||||
|
|
||||||
|
'compile [files..]',
|
||||||
|
'Compile a set of source files',
|
||||||
|
|
||||||
|
yargs => yargs
|
||||||
|
.string('hook')
|
||||||
|
.describe('hook', 'Add a hook to a specific compile phase. See the manual for details.'),
|
||||||
|
|
||||||
|
args => {
|
||||||
|
|
||||||
|
const hooks: Hook[] = toArray(args.hook as string[] | string).map(parseHook);
|
||||||
|
|
||||||
|
for (const filepath of toArray(args.files as string[] | string)) {
|
||||||
|
|
||||||
|
const file = new TextFile(filepath);
|
||||||
|
const content = fs.readFileSync(filepath, 'utf8')
|
||||||
|
const scanner = new Scanner(file, content)
|
||||||
|
const tokens: Token[] = [];
|
||||||
|
|
||||||
|
for (const hook of hooks) {
|
||||||
|
if (hook.name === 'scan' && hook.timing === 'before') {
|
||||||
|
for (const effect of hook.effects) {
|
||||||
|
switch (effect) {
|
||||||
|
case 'abort':
|
||||||
|
process.exit(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Could not execute hook effect '${effect}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const token = scanner.scanToken()
|
||||||
|
if (token === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const hook of hooks) {
|
||||||
|
if (hook.name === 'scan' && hook.timing == 'after') {
|
||||||
|
for (const effect of hook.effects) {
|
||||||
|
switch (effect) {
|
||||||
|
case 'dump':
|
||||||
|
console.log(JSON.stringify(tokens.map(t => t.toJSON()), undefined, 2));
|
||||||
|
break;
|
||||||
|
case 'abort':
|
||||||
|
process.exit(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Could not execute hook effect '${effect}'.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.help()
|
||||||
|
.version()
|
||||||
|
.argv
|
||||||
|
|
3
src/index.ts
Normal file
3
src/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
251
src/scanner.ts
Normal file
251
src/scanner.ts
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
|
||||||
|
import XRegExp from "xregexp"
|
||||||
|
|
||||||
|
import {
|
||||||
|
TextFile,
|
||||||
|
TextPos,
|
||||||
|
TextSpan,
|
||||||
|
Identifier,
|
||||||
|
Operator,
|
||||||
|
PunctType,
|
||||||
|
Token,
|
||||||
|
Punctuated,
|
||||||
|
} from "./ast"
|
||||||
|
|
||||||
|
function escapeChar(ch: string) {
|
||||||
|
switch (ch) {
|
||||||
|
case '\a': return '\\a';
|
||||||
|
case '\b': return '\\b';
|
||||||
|
case '\f': return '\\f';
|
||||||
|
case '\n': return '\\n';
|
||||||
|
case '\r': return '\\r';
|
||||||
|
case '\t': return '\\t';
|
||||||
|
case '\v': return '\\v';
|
||||||
|
case '\0': return '\\0';
|
||||||
|
case '\'': return '\\\'';
|
||||||
|
default:
|
||||||
|
const code = ch.charCodeAt(0);
|
||||||
|
if (code >= 0x20 && code <= 0x7E) {
|
||||||
|
return ch
|
||||||
|
} else if (code < 0x7F) {
|
||||||
|
return `\\x${code.toString(16).padStart(2, '0')}`
|
||||||
|
} else {
|
||||||
|
return `\\u${code.toString(16).padStart(4, '0')}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPunctType(ch: string) {
|
||||||
|
switch (ch) {
|
||||||
|
case '(':
|
||||||
|
case ')':
|
||||||
|
return PunctType.Paren;
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
return PunctType.Bracket;
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
return PunctType.Brace;
|
||||||
|
default:
|
||||||
|
throw new Error(`given character is not a valid punctuator`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isClosePunct(ch: string) {
|
||||||
|
switch (ch) {
|
||||||
|
case '}':
|
||||||
|
case ']':
|
||||||
|
case ')':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOpenPunct(ch: string) {
|
||||||
|
switch (ch) {
|
||||||
|
case '{':
|
||||||
|
case '(':
|
||||||
|
case '[':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanError extends Error {
|
||||||
|
constructor(public file: TextFile, public position: TextPos, public char: string) {
|
||||||
|
super(`${file.path}:${position.line}:${position.column}: unexpected char '${escapeChar(char)}'`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Stream<T> {
|
||||||
|
read(): T
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWhiteSpace(ch: string) {
|
||||||
|
return ch == '\n' || XRegExp('\\p{Zs}').test(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isNewLine(ch: string) {
|
||||||
|
return ch == '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIdentStart(ch: string) {
|
||||||
|
return ch == '_' || XRegExp('\\p{L}').test(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIdentPart(ch: string) {
|
||||||
|
return ch == '_' || XRegExp('\\p{L}').test(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOperatorStart(ch: string) {
|
||||||
|
return /[+\-*\/%$!><]/.test(ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOperatorPart(ch: string) {
|
||||||
|
return /[=+\-*\/%$!><]/.test(ch)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const EOF = ''
|
||||||
|
|
||||||
|
export class Scanner {
|
||||||
|
|
||||||
|
protected buffer: string[] = [];
|
||||||
|
protected currPos = new TextPos(0,1,1);
|
||||||
|
protected offset = 0;
|
||||||
|
|
||||||
|
constructor(public file: TextFile, public input: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readChar() {
|
||||||
|
if (this.offset == this.input.length) {
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
return this.input[this.offset++]
|
||||||
|
}
|
||||||
|
|
||||||
|
protected peekChar(count = 1) {
|
||||||
|
while (this.buffer.length < count) {
|
||||||
|
this.buffer.push(this.readChar());
|
||||||
|
}
|
||||||
|
return this.buffer[count - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getChar() {
|
||||||
|
|
||||||
|
const ch = this.buffer.length > 0
|
||||||
|
? this.buffer.shift()!
|
||||||
|
: this.readChar()
|
||||||
|
|
||||||
|
if (ch == EOF) {
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewLine(ch)) {
|
||||||
|
this.currPos.line += 1;
|
||||||
|
this.currPos.column = 1;
|
||||||
|
} else {
|
||||||
|
this.currPos.column += 1;
|
||||||
|
}
|
||||||
|
this.currPos.offset += 1;
|
||||||
|
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
protected takeWhile(pred: (ch: string) => boolean) {
|
||||||
|
let text = this.getChar();
|
||||||
|
while (true) {
|
||||||
|
const c0 = this.peekChar();
|
||||||
|
if (!pred(c0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.getChar()
|
||||||
|
text += c0;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
scanToken() {
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
const c0 = this.peekChar();
|
||||||
|
|
||||||
|
if (isWhiteSpace(c0)) {
|
||||||
|
this.getChar();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c0 == EOF) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPos = this.currPos.clone()
|
||||||
|
|
||||||
|
if (isOpenPunct(c0)) {
|
||||||
|
|
||||||
|
this.getChar();
|
||||||
|
|
||||||
|
const punctType = getPunctType(c0);
|
||||||
|
const elements: Token[] = [];
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
|
||||||
|
const c1 = this.peekChar();
|
||||||
|
|
||||||
|
if (c1 === EOF) {
|
||||||
|
throw new ScanError(this.file, this.currPos.clone(), EOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClosePunct(c1)) {
|
||||||
|
if (punctType == getPunctType(c1)) {
|
||||||
|
this.getChar();
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw new ScanError(this.file, this.currPos, c1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this.scanToken();
|
||||||
|
if (token === null) {
|
||||||
|
throw new ScanError(this.file, this.currPos.clone(), EOF)
|
||||||
|
}
|
||||||
|
elements.push(token!);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const endPos = this.currPos.clone();
|
||||||
|
|
||||||
|
return new Punctuated(punctType, elements, new TextSpan(this.file, startPos, endPos));
|
||||||
|
|
||||||
|
} else if (isIdentStart(c0)) {
|
||||||
|
|
||||||
|
const name = this.takeWhile(isIdentPart);
|
||||||
|
const endPos = this.currPos.clone();
|
||||||
|
return new Identifier(name, new TextSpan(this.file, startPos, endPos))
|
||||||
|
|
||||||
|
} else if (isOperatorStart(c0)) {
|
||||||
|
|
||||||
|
const text = this.takeWhile(isOperatorPart)
|
||||||
|
const endPos = this.currPos.clone()
|
||||||
|
return new Operator(text, new TextSpan(this.file, startPos, endPos));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
throw new ScanError(this.file, this.currPos.clone(), c0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
scanTokenList() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
5
test/000-alpha-identifiers.bolt
Normal file
5
test/000-alpha-identifiers.bolt
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
ThisIsReallyALongIdentifierName
|
||||||
|
x
|
||||||
|
Y
|
Loading…
Reference in a new issue