diff --git a/src/checker.ts b/src/checker.ts index 9d5b01fcf..ce8a60ba4 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -202,6 +202,13 @@ export class TypeChecker { switch (node.kind) { + case SyntaxKind.BoltModule: + { + for (const element of node.elements) { + visitSourceElement(element); + } + } + case SyntaxKind.BoltRecordDeclaration: { if (node.members !== null) { diff --git a/src/common.ts b/src/common.ts index 780a4bb70..9c1f8281d 100644 --- a/src/common.ts +++ b/src/common.ts @@ -3,13 +3,56 @@ import { BoltReturnStatement, SyntaxKind, BoltExpression, - BoltSourceFile, - JSSourceFile + BoltQualName, + kindToString, + Syntax, + Token, + isBoltPunctuated } from "./ast"; +import { BOLT_SUPPORTED_LANGUAGES } from "./constants" +import {emit} from "./emitter"; +import {FastStringMap, enumerate, escapeChar} from "./util"; +import {TextSpan, TextPos, TextFile} from "./text"; +import {Scanner} from "./scanner"; -export type SourceFile - = BoltSourceFile - | JSSourceFile +export function getLanguage(node: Syntax): string { + const kindStr = kindToString(node.kind); + for (const prefix of BOLT_SUPPORTED_LANGUAGES) { + if (kindStr.startsWith(prefix)) { + return prefix; + } + } + throw new Error(`Could not determine the language of ${kindStr}`); +} + +export function createTokenStream(node: Syntax) { + if (isBoltPunctuated(node)) { + const origPos = node.span!.start; + const startPos = new TextPos(origPos.offset+1, origPos.line, origPos.column+1); + return new Scanner(node.span!.file, node.text, startPos); + } else { + throw new Error(`Could not convert ${kindToString(node.kind)} to a token stream.`); + } +} + +export const EOF = '' + +export class ScanError extends Error { + constructor(public file: TextFile, public position: TextPos, public char: string) { + super(`${file.origPath}:${position.line}:${position.column}: unexpected char '${escapeChar(char)}'`) + } +} + +export function cloneSpan(span: TextSpan | null) { + if (span === null) { + return null; + } + return span.clone(); +} + +export function setOrigNodeRange(node: Syntax, startNode: Syntax, endNode: Syntax): void { + node.span = new TextSpan(startNode.span!.file, startNode.span!.start.clone(), endNode.span!.end.clone()); +} export type BoltFunctionBody = BoltFunctionBodyElement[]; @@ -37,7 +80,7 @@ export function getReturnStatementsInFunctionBody(body: BoltFunctionBody): BoltR function visitExpression(node: BoltExpression) { switch (node.kind) { case SyntaxKind.BoltBlockExpression: - for (const element of node.statements) { + for (const element of node.elements) { visit(element); } break; @@ -57,3 +100,206 @@ export function getReturnStatementsInFunctionBody(body: BoltFunctionBody): BoltR } +export function hasPublicModifier(node: BoltDeclaration) { + return (node.modifiers & BoltDeclarationModifiers.Public) > 0; +} + +export enum OperatorKind { + Prefix, + InfixL, + InfixR, + Suffix, +} + +export function isRightAssoc(kind: OperatorKind) { + return kind === OperatorKind.InfixR; +} + +export class ParseError extends Error { + constructor(public actual: Syntax, public expected: SyntaxKind[]) { + super(`${actual.span!.file.origPath}:${actual.span!.start.line}:${actual.span!.start.column}: expected ${enumerate(expected.map(e => describeKind(e)))} but got ${describeKind(actual.kind)}`) + } +} + +export interface OperatorInfo { + kind: OperatorKind; + arity: number; + name: string; + precedence: number; +} + +export function assertToken(node: Token, kind: SyntaxKind) { + if (node.kind !== kind) { + throw new ParseError(node, [kind]); + } +} + +type OperatorTableList = [OperatorKind, number, string][][]; + +export class OperatorTable { + + private operatorsByName = new FastStringMap(); + //private operatorsByPrecedence = FastStringMap(); + + constructor(definitions: OperatorTableList) { + let i = 0; + for (const group of definitions) { + for (const [kind, arity, name] of group) { + const info = { kind, arity, name, precedence: i } + this.operatorsByName.set(name, info); + //this.operatorsByPrecedence[i] = info; + } + i++; + } + } + + public lookup(name: string): OperatorInfo | null { + if (!this.operatorsByName.has(name)) { + return null; + } + return this.operatorsByName.get(name); + } + +} + +export function describeKind(kind: SyntaxKind): string { + switch (kind) { + case SyntaxKind.BoltExMark: + return "'!'"; + case SyntaxKind.JSIdentifier: + return "a JavaScript identifier" + case SyntaxKind.BoltIdentifier: + return "an identifier" + case SyntaxKind.BoltOperator: + return "an operator" + case SyntaxKind.BoltStringLiteral: + return "a string" + case SyntaxKind.BoltIntegerLiteral: + return "an integer" + case SyntaxKind.BoltFnKeyword: + return "'fn'" + case SyntaxKind.BoltQuoteKeyword: + return "'quote'"; + case SyntaxKind.BoltModKeyword: + return "'mod'"; + case SyntaxKind.BoltForeignKeyword: + return "'foreign'" + case SyntaxKind.BoltMatchKeyword: + return "'match'"; + case SyntaxKind.BoltYieldKeyword: + return "'yield'"; + case SyntaxKind.BoltReturnKeyword: + return "'return'"; + case SyntaxKind.BoltPubKeyword: + return "'pub'" + case SyntaxKind.BoltLetKeyword: + return "'let'" + case SyntaxKind.BoltSemi: + return "';'" + case SyntaxKind.BoltColon: + return "':'" + case SyntaxKind.BoltColonColon: + return "'::'"; + case SyntaxKind.BoltDot: + return "'.'" + case SyntaxKind.JSDot: + return "'.'" + case SyntaxKind.JSDotDotDot: + return "'...'" + case SyntaxKind.BoltRArrow: + return "'->'" + case SyntaxKind.BoltVBar: + return "'|'"; + case SyntaxKind.BoltComma: + return "','" + case SyntaxKind.BoltModKeyword: + return "'mod'" + case SyntaxKind.BoltStructKeyword: + return "'struct'" + case SyntaxKind.BoltEnumKeyword: + return "'enum'" + case SyntaxKind.BoltTypeKeyword: + return "'type'"; + case SyntaxKind.BoltBraced: + return "'{' .. '}'" + case SyntaxKind.BoltBracketed: + return "'[' .. ']'" + case SyntaxKind.BoltParenthesized: + return "'(' .. ')'" + case SyntaxKind.EndOfFile: + return "'}', ')', ']' or end-of-file" + case SyntaxKind.BoltLtSign: + return "'<'"; + case SyntaxKind.BoltGtSign: + return "'<'"; + case SyntaxKind.BoltEqSign: + return "'='"; + case SyntaxKind.JSOpenBrace: + return "'{'"; + case SyntaxKind.JSCloseBrace: + return "'}'"; + case SyntaxKind.JSOpenBracket: + return "'['"; + case SyntaxKind.JSCloseBracket: + return "']'"; + case SyntaxKind.JSOpenParen: + return "'('"; + case SyntaxKind.JSCloseParen: + return "')'"; + case SyntaxKind.JSSemi: + return "';'"; + case SyntaxKind.JSComma: + return "','"; + case SyntaxKind.BoltTraitKeyword: + return "'trait'"; + case SyntaxKind.BoltTraitKeyword: + return "'impl'"; + case SyntaxKind.BoltImplKeyword: + return "'trait'"; + case SyntaxKind.BoltForKeyword: + return "'for'"; + case SyntaxKind.JSMulOp: + return "'*'"; + case SyntaxKind.JSAddOp: + return "'+'"; + case SyntaxKind.JSDivOp: + return "'/'"; + case SyntaxKind.JSSubOp: + return "'-'"; + case SyntaxKind.JSLtOp: + return "'<'"; + case SyntaxKind.JSGtOp: + return "'>'"; + case SyntaxKind.JSBOrOp: + return "'|'"; + case SyntaxKind.JSBXorOp: + return "'^'"; + case SyntaxKind.JSBAndOp: + return "'&'"; + case SyntaxKind.JSBNotOp: + return "'~'"; + case SyntaxKind.JSNotOp: + return "'~'"; + case SyntaxKind.JSString: + return "a JavaScript string" + case SyntaxKind.JSReturnKeyword: + return "'return'"; + case SyntaxKind.JSForKeyword: + return "'for'"; + case SyntaxKind.JSTryKeyword: + return "'try'"; + case SyntaxKind.BoltRArrowAlt: + return "'=>'"; + default: + throw new Error(`failed to describe ${kindToString(kind)}`) + } +} + +export function toDeclarationPath(node: BoltQualName): string[] { + const lastElement = emit(node.name); + if (node.modulePath === null) { + return [ lastElement ]; + } + return [...node.modulePath.map(id => id.text), lastElement]; +} + diff --git a/src/constants.ts b/src/constants.ts index 8b1378917..c8aac4e41 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1 +1,3 @@ +export const BOLT_SUPPORTED_LANGUAGES = ['Bolt', 'JS']; + diff --git a/src/parser.ts b/src/parser.ts index cdfbd0da5..a4cd78d48 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -29,7 +29,6 @@ import { BoltDeclarationModifiers, BoltStringLiteral, BoltImportSymbol, - BoltCallExpression, BoltExpressionStatement, createBoltExpressionStatement, BoltVariableDeclaration, @@ -37,7 +36,6 @@ import { createBoltVariableDeclaration, BoltReturnStatement, createBoltReturnStatement, - BoltRecordMember, BoltModule, createBoltModule, BoltTypeAliasDeclaration, @@ -47,17 +45,14 @@ import { createBoltCallExpression, BoltSymbol, BoltTypeParameter, - createBoltTypePattern, createBoltTypeParameter, BoltTraitDeclaration, - createBoltTraitKeyword, createBoltTraitDeclaration, createBoltImplDeclaration, BoltImplDeclaration, BoltSourceFile, BoltFunctionBodyElement, createBoltSourceFile, - BoltRecordField, setParents, BoltMatchExpression, createBoltMatchArm, @@ -70,7 +65,6 @@ import { BoltRecordPattern, createBoltRecordPattern, createBoltRecordFieldPattern, - BoltQuoteKeyword, isBoltPunctuated, Token, createBoltQuoteExpression, @@ -82,23 +76,20 @@ import { createBoltFunctionExpression, BoltMacroCall, createBoltMacroCall, - BoltMemberExpression, createBoltMemberExpression, } from "./ast" import { parseForeignLanguage } from "./foreign" import { - Stream, OperatorKind, OperatorTable, assertToken, ParseError, setOrigNodeRange, createTokenStream, - uniq, - assert, -} from "./util" +} from "./common" +import { Stream, uniq } from "./util" export type BoltTokenStream = Stream; diff --git a/src/program.ts b/src/program.ts index ce0ca2ed4..9d2779fdf 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,6 +1,5 @@ -import { SourceFile } from "./common" -import { BoltSourceFile } from "./ast" +import { SourceFile } from "./ast" import { FastStringMap } from "./util"; export class Program { @@ -8,7 +7,7 @@ export class Program { private transformed = new FastStringMap(); constructor( - sourceFiles: BoltSourceFile[] + sourceFiles: SourceFile[] ) { for (const sourceFile of sourceFiles) { this.transformed.set(sourceFile.span!.file.fullPath, sourceFile); diff --git a/src/scanner.ts b/src/scanner.ts index c5d9b5f3a..c7eeaa99a 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -1,5 +1,5 @@ -import { EOF, ScanError } from "./util" +import { EOF, ScanError } from "./common" import { TextFile, @@ -8,8 +8,6 @@ import { } from "./text" import { - setParents, - SyntaxKind, BoltToken, createBoltRArrowAlt, createEndOfFile, @@ -19,7 +17,6 @@ import { createBoltParenthesized, createBoltBraced, createBoltBracketed, - createBoltSourceFile, createBoltSemi, createBoltComma, createBoltStringLiteral, diff --git a/src/transforms/expand.ts b/src/transforms/expand.ts index ef03e3570..54452920a 100644 --- a/src/transforms/expand.ts +++ b/src/transforms/expand.ts @@ -10,12 +10,12 @@ import { BoltMacroCall, } from "../ast" -import { TypeChecker, Scope } from "../checker" +import { TypeChecker } from "../checker" import { BoltTokenStream, Parser, isModifierKeyword } from "../parser" import { Evaluator, TRUE, FALSE } from "../evaluator" import { Transformer, TransformManager } from "./index" import { inject } from "../di" -import {SourceFile} from "../program" +import { SourceFile } from "../ast" interface SyntaxTransformer { pattern: BoltPattern; diff --git a/src/transforms/index.ts b/src/transforms/index.ts index 9a1580b49..dd61b6363 100644 --- a/src/transforms/index.ts +++ b/src/transforms/index.ts @@ -1,8 +1,7 @@ -import { SourceFile, Program } from "../program" +import { Program } from "../program" import { Container, Newable } from "../di" -import {Evaluator} from "../evaluator"; -import {TypeChecker} from "../checker"; +import {SourceFile} from "../ast"; export interface Transformer { isApplicable(node: SourceFile): boolean; diff --git a/src/treegen/index.ts b/src/treegen/index.ts index da1cf7fb1..c58121bd4 100644 --- a/src/treegen/index.ts +++ b/src/treegen/index.ts @@ -5,7 +5,7 @@ import * as path from "path" const PACKAGE_ROOT = path.resolve(__dirname, '..', '..'); import { Syntax, Declaration, NodeDeclaration, TypeDeclaration, EnumDeclaration, TypeNode, NodeField } from "./ast" -import { FastStringMap } from "../util" +import { MapLike } from "../util" import { FileWriter } from "./util" export function generateAST(decls: Declaration[]) { @@ -21,7 +21,7 @@ export function generateAST(decls: Declaration[]) { const enumDecls: EnumDeclaration[] = decls.filter(decl => decl.type === 'EnumDeclaration') as EnumDeclaration[]; const langNames: string[] = decls.filter(decl => decl.type === 'LanguageDeclaration').map(decl => decl.name); - const declByName: FastStringMap = Object.create(null); + const declByName: MapLike = Object.create(null); i = 0; for (const decl of decls) { decl.index = i++; @@ -31,7 +31,7 @@ export function generateAST(decls: Declaration[]) { // Generate a mapping from parent node to child node // This makes it easy to generate union types for the intermediate nodes. - const childrenOf: FastStringMap = Object.create(null); + const childrenOf: MapLike = Object.create(null); for (const nodeDecl of nodeDecls) { for (const parentName of nodeDecl.parents) { if (childrenOf[parentName] === undefined) { diff --git a/src/util.ts b/src/util.ts index 13fc27e6e..6ebdd23c2 100644 --- a/src/util.ts +++ b/src/util.ts @@ -4,31 +4,12 @@ import * as fs from "fs" import moment from "moment" import chalk from "chalk" -import { TextFile, TextSpan, TextPos } from "./text" -import { Scanner } from "./scanner" -import { kindToString, Syntax, BoltQualName, BoltDeclaration, BoltDeclarationModifiers, createEndOfFile, SyntaxKind, isBoltPunctuated } from "./ast" - export function assert(test: boolean): void { if (!test) { throw new Error(`Invariant violation: an internal sanity check failed.`); } } -export function createTokenStream(node: Syntax) { - if (isBoltPunctuated(node)) { - const origPos = node.span!.start; - const startPos = new TextPos(origPos.offset+1, origPos.line, origPos.column+1); - return new Scanner(node.span!.file, node.text, startPos); - } else if (node.kind === SyntaxKind.BoltSentence) { - return new StreamWrapper( - node.tokens, - () => createEndOfFile(new TextSpan(node.span!.file, node.span!.end.clone(), node.span!.end.clone())) - ); - } else { - throw new Error(`Could not convert ${kindToString(node.kind)} to a token stream.`); - } -} - export interface JsonArray extends Array { }; export interface JsonObject { [key: string]: Json } export type Json = null | string | boolean | number | JsonArray | JsonObject; @@ -146,40 +127,6 @@ export function memoize(hasher: (...args: any[]) => string) { } } -const supportedLanguages = ['Bolt', 'JS']; - -export function getLanguage(node: Syntax): string { - const kindStr = kindToString(node.kind); - for (const prefix of supportedLanguages) { - if (kindStr.startsWith(prefix)) { - return prefix; - } - } - throw new Error(`Could not determine the language of ${kindStr}`); -} - -export function cloneSpan(span: TextSpan | null) { - if (span === null) { - return null; - } - return span.clone(); -} - -export function setOrigNodeRange(node: Syntax, startNode: Syntax, endNode: Syntax): void { - node.span = new TextSpan(startNode.span!.file, startNode.span!.start.clone(), endNode.span!.end.clone()); -} - -export function hasPublicModifier(node: BoltDeclaration) { - return (node.modifiers & BoltDeclarationModifiers.Public) > 0; -} - -export function toDeclarationPath(node: BoltQualName): string[] { - if (node.modulePath === null) { - return [ node.name.text ]; - } - return [...node.modulePath.map(id => id.text), node.name.text]; -} - export interface Stream { get(): T; peek(count?: number): T; @@ -235,140 +182,7 @@ export function getFileStem(filepath: string): string { return path.basename(filepath).split('.')[0]; } -export function describeKind(kind: SyntaxKind): string { - switch (kind) { - case SyntaxKind.BoltExMark: - return "'!'"; - case SyntaxKind.JSIdentifier: - return "a JavaScript identifier" - case SyntaxKind.BoltIdentifier: - return "an identifier" - case SyntaxKind.BoltOperator: - return "an operator" - case SyntaxKind.BoltStringLiteral: - return "a string" - case SyntaxKind.BoltIntegerLiteral: - return "an integer" - case SyntaxKind.BoltFnKeyword: - return "'fn'" - case SyntaxKind.BoltQuoteKeyword: - return "'quote'"; - case SyntaxKind.BoltModKeyword: - return "'mod'"; - case SyntaxKind.BoltForeignKeyword: - return "'foreign'" - case SyntaxKind.BoltMatchKeyword: - return "'match'"; - case SyntaxKind.BoltYieldKeyword: - return "'yield'"; - case SyntaxKind.BoltReturnKeyword: - return "'return'"; - case SyntaxKind.BoltPubKeyword: - return "'pub'" - case SyntaxKind.BoltLetKeyword: - return "'let'" - case SyntaxKind.BoltSemi: - return "';'" - case SyntaxKind.BoltColon: - return "':'" - case SyntaxKind.BoltColonColon: - return "'::'"; - case SyntaxKind.BoltDot: - return "'.'" - case SyntaxKind.JSDot: - return "'.'" - case SyntaxKind.JSDotDotDot: - return "'...'" - case SyntaxKind.BoltRArrow: - return "'->'" - case SyntaxKind.BoltVBar: - return "'|'"; - case SyntaxKind.BoltComma: - return "','" - case SyntaxKind.BoltModKeyword: - return "'mod'" - case SyntaxKind.BoltStructKeyword: - return "'struct'" - case SyntaxKind.BoltEnumKeyword: - return "'enum'" - case SyntaxKind.BoltTypeKeyword: - return "'type'"; - case SyntaxKind.BoltBraced: - return "'{' .. '}'" - case SyntaxKind.BoltBracketed: - return "'[' .. ']'" - case SyntaxKind.BoltParenthesized: - return "'(' .. ')'" - case SyntaxKind.EndOfFile: - return "'}', ')', ']' or end-of-file" - case SyntaxKind.BoltLtSign: - return "'<'"; - case SyntaxKind.BoltGtSign: - return "'<'"; - case SyntaxKind.BoltEqSign: - return "'='"; - case SyntaxKind.JSOpenBrace: - return "'{'"; - case SyntaxKind.JSCloseBrace: - return "'}'"; - case SyntaxKind.JSOpenBracket: - return "'['"; - case SyntaxKind.JSCloseBracket: - return "']'"; - case SyntaxKind.JSOpenParen: - return "'('"; - case SyntaxKind.JSCloseParen: - return "')'"; - case SyntaxKind.JSSemi: - return "';'"; - case SyntaxKind.JSComma: - return "','"; - case SyntaxKind.BoltTraitKeyword: - return "'trait'"; - case SyntaxKind.BoltTraitKeyword: - return "'impl'"; - case SyntaxKind.BoltImplKeyword: - return "'trait'"; - case SyntaxKind.BoltForKeyword: - return "'for'"; - case SyntaxKind.JSMulOp: - return "'*'"; - case SyntaxKind.JSAddOp: - return "'+'"; - case SyntaxKind.JSDivOp: - return "'/'"; - case SyntaxKind.JSSubOp: - return "'-'"; - case SyntaxKind.JSLtOp: - return "'<'"; - case SyntaxKind.JSGtOp: - return "'>'"; - case SyntaxKind.JSBOrOp: - return "'|'"; - case SyntaxKind.JSBXorOp: - return "'^'"; - case SyntaxKind.JSBAndOp: - return "'&'"; - case SyntaxKind.JSBNotOp: - return "'~'"; - case SyntaxKind.JSNotOp: - return "'~'"; - case SyntaxKind.JSString: - return "a JavaScript string" - case SyntaxKind.JSReturnKeyword: - return "'return'"; - case SyntaxKind.JSForKeyword: - return "'for'"; - case SyntaxKind.JSTryKeyword: - return "'try'"; - case SyntaxKind.BoltRArrowAlt: - return "'=>'"; - default: - throw new Error(`failed to describe ${kindToString(kind)}`) - } -} - -function enumerate(elements: string[]) { +export function enumerate(elements: string[]) { if (elements.length === 1) { return elements[0] } else { @@ -376,68 +190,7 @@ function enumerate(elements: string[]) { } } -export class ParseError extends Error { - constructor(public actual: Syntax, public expected: SyntaxKind[]) { - super(`${actual.span!.file.origPath}:${actual.span!.start.line}:${actual.span!.start.column}: expected ${enumerate(expected.map(e => describeKind(e)))} but got ${describeKind(actual.kind)}`) - } -} - -export enum OperatorKind { - Prefix, - InfixL, - InfixR, - Suffix, -} - -export function isRightAssoc(kind: OperatorKind) { - return kind === OperatorKind.InfixR; -} - -export interface OperatorInfo { - kind: OperatorKind; - arity: number; - name: string; - precedence: number; -} - -export function assertToken(node: Syntax, kind: SyntaxKind) { - if (node.kind !== kind) { - throw new ParseError(node, [kind]); - } -} - - -type OperatorTableList = [OperatorKind, number, string][][]; - -export class OperatorTable { - - private operatorsByName = new FastStringMap(); - //private operatorsByPrecedence = FastStringMap(); - - constructor(definitions: OperatorTableList) { - let i = 0; - for (const group of definitions) { - for (const [kind, arity, name] of group) { - const info = { kind, arity, name, precedence: i } - this.operatorsByName.set(name, info); - //this.operatorsByPrecedence[i] = info; - } - i++; - } - } - - public lookup(name: string): OperatorInfo | null { - if (!this.operatorsByName.has(name)) { - return null; - } - return this.operatorsByName.get(name); - } - -} - -export const EOF = '' - -function escapeChar(ch: string) { +export function escapeChar(ch: string) { switch (ch) { case '\a': return '\\a'; case '\b': return '\\b'; @@ -460,12 +213,6 @@ function escapeChar(ch: string) { } } -export class ScanError extends Error { - constructor(public file: TextFile, public position: TextPos, public char: string) { - super(`${file.origPath}:${position.line}:${position.column}: unexpected char '${escapeChar(char)}'`) - } -} - export interface MapLike { [key: string]: T; }