diff --git a/package-lock.json b/package-lock.json index 7e1ba1de6..8f2100366 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,16 @@ "@types/node": "*" } }, + "@types/microtime": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/microtime/-/microtime-2.1.0.tgz", + "integrity": "sha1-rb2Z9QGoXIhpXrHv01FYEPJWOTI=" + }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=" + }, "@types/mocha": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", @@ -176,24 +186,35 @@ } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "requires": { - "has-flag": "^3.0.0" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" } } }, @@ -600,6 +621,37 @@ "dev": true, "requires": { "chalk": "^2.4.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "microtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/microtime/-/microtime-3.0.0.tgz", + "integrity": "sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ==", + "requires": { + "node-addon-api": "^1.2.0", + "node-gyp-build": "^3.8.0" } }, "minimatch": { @@ -613,8 +665,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mkdirp": { "version": "0.5.5", @@ -754,12 +805,22 @@ } } }, + "moment": { + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz", + "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==" + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true }, + "node-addon-api": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.1.tgz", + "integrity": "sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ==" + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -770,6 +831,11 @@ "semver": "^5.7.0" } }, + "node-gyp-build": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.9.0.tgz", + "integrity": "sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==" + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1001,6 +1067,21 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "requires": { + "has-flag": "^4.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + } + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index 0723f9717..f15348084 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,18 @@ "repository": "https://github.com/samvv/Bolt", "dependencies": { "@types/fs-extra": "^8.1.0", + "@types/microtime": "^2.1.0", + "@types/minimist": "^1.2.0", "@types/node": "^13.13.5", "@types/xregexp": "^4.3.0", "@types/yargs": "^15.0.4", "acorn": "^7.2.0", + "chalk": "^4.0.0", "fs-extra": "^9.0.0", "glob": "^7.1.6", + "microtime": "^3.0.0", + "minimist": "^1.2.5", + "moment": "^2.25.3", "pegjs": "^0.11.0-master.b7b87ea", "reflect-metadata": "^0.1.13", "source-map-support": "^0.5.19", diff --git a/spec/ast.txt b/spec/ast.txt index 05b485b46..51df29f90 100644 --- a/spec/ast.txt +++ b/spec/ast.txt @@ -30,6 +30,10 @@ node BoltOperator > BoltSymbol { text: String, } +node BoltAssignment > BoltToken { + operator: Option, +} + node BoltEOS > BoltToken; node BoltComma > BoltToken; diff --git a/src/ast.d.ts b/src/ast.d.ts index 6b37a6ff3..4ccdd445e 100644 --- a/src/ast.d.ts +++ b/src/ast.d.ts @@ -5,92 +5,95 @@ export const enum SyntaxKind { BoltIntegerLiteral = 6, BoltIdentifier = 8, BoltOperator = 9, - BoltEOS = 10, - BoltComma = 11, - BoltSemi = 12, - BoltColon = 13, - BoltDot = 14, - BoltDotDot = 15, - BoltRArrow = 16, - BoltLArrow = 17, - BoltEqSign = 18, - BoltGtSign = 19, - BoltLtSign = 20, - BoltFnKeyword = 22, - BoltForeignKeyword = 23, - BoltLetKeyword = 24, - BoltReturnKeyword = 25, - BoltLoopKeyword = 26, - BoltYieldKeyword = 27, - BoltMatchKeyword = 28, - BoltImportKeyword = 29, - BoltPubKeyword = 30, - BoltModKeyword = 31, - BoltMutKeyword = 32, - BoltEnumKeyword = 33, - BoltStructKeyword = 34, - BoltNewTypeKeyword = 35, - BoltParenthesized = 37, - BoltBraced = 38, - BoltBracketed = 39, - BoltSourceFile = 40, - BoltQualName = 41, - BoltSentence = 42, - BoltReferenceTypeNode = 44, - BoltBindPattern = 46, - BoltTypePattern = 47, - BoltExpressionPattern = 48, - BoltTuplePatternElement = 49, - BoltTuplePattern = 50, - BoltRecordPatternField = 51, - BoltRecordPattern = 52, - BoltReferenceExpression = 54, - BoltCallExpression = 55, - BoltYieldExpression = 56, - BoltMatchArm = 57, - BoltMatchExpression = 58, - BoltCase = 59, - BoltCaseExpression = 60, - BoltBlockExpression = 61, - BoltConstantExpression = 62, - BoltReturnStatement = 64, - BoltResumeStatement = 65, - BoltExpressionStatement = 66, - BoltParameter = 67, - BoltNewTypeDeclaration = 70, - BoltModule = 71, - BoltFunctionDeclaration = 72, - BoltVariableDeclaration = 73, - BoltPlainImportSymbol = 75, - BoltImportDeclaration = 76, - BoltRecordDeclarationField = 77, - BoltRecordDeclaration = 79, - JSOperator = 82, - JSIdentifier = 83, - JSBindPattern = 85, - JSConstantExpression = 87, - JSMemberExpression = 89, - JSCallExpression = 90, - JSBinaryExpression = 91, - JSUnaryExpression = 92, - JSNewExpression = 93, - JSSequenceExpression = 94, - JSConditionalExpression = 95, - JSReferenceExpression = 96, - JSExpressionStatement = 98, - JSConditionalStatement = 99, - JSParameter = 100, - JSFunctionDeclaration = 103, - JSArrowFunctionDeclaration = 104, - JSLetDeclaration = 105, - JSSourceFile = 106, - JSSourceElement = 107, + BoltAssignment = 10, + BoltEOS = 11, + BoltComma = 12, + BoltSemi = 13, + BoltColon = 14, + BoltDot = 15, + BoltDotDot = 16, + BoltRArrow = 17, + BoltLArrow = 18, + BoltEqSign = 19, + BoltGtSign = 20, + BoltLtSign = 21, + BoltFnKeyword = 23, + BoltForeignKeyword = 24, + BoltLetKeyword = 25, + BoltReturnKeyword = 26, + BoltLoopKeyword = 27, + BoltYieldKeyword = 28, + BoltMatchKeyword = 29, + BoltImportKeyword = 30, + BoltPubKeyword = 31, + BoltModKeyword = 32, + BoltMutKeyword = 33, + BoltEnumKeyword = 34, + BoltStructKeyword = 35, + BoltNewTypeKeyword = 36, + BoltParenthesized = 38, + BoltBraced = 39, + BoltBracketed = 40, + BoltSourceFile = 41, + BoltQualName = 42, + BoltSentence = 43, + BoltReferenceTypeNode = 45, + BoltBindPattern = 47, + BoltTypePattern = 48, + BoltExpressionPattern = 49, + BoltTuplePatternElement = 50, + BoltTuplePattern = 51, + BoltRecordPatternField = 52, + BoltRecordPattern = 53, + BoltReferenceExpression = 55, + BoltCallExpression = 56, + BoltYieldExpression = 57, + BoltMatchArm = 58, + BoltMatchExpression = 59, + BoltCase = 60, + BoltCaseExpression = 61, + BoltBlockExpression = 62, + BoltConstantExpression = 63, + BoltReturnStatement = 65, + BoltResumeStatement = 66, + BoltExpressionStatement = 67, + BoltParameter = 68, + BoltNewTypeDeclaration = 71, + BoltModule = 72, + BoltFunctionDeclaration = 73, + BoltVariableDeclaration = 74, + BoltPlainImportSymbol = 76, + BoltImportDeclaration = 77, + BoltRecordDeclarationField = 78, + BoltRecordDeclaration = 80, + JSOperator = 83, + JSIdentifier = 84, + JSBindPattern = 86, + JSConstantExpression = 88, + JSMemberExpression = 90, + JSCallExpression = 91, + JSBinaryExpression = 92, + JSUnaryExpression = 93, + JSNewExpression = 94, + JSSequenceExpression = 95, + JSConditionalExpression = 96, + JSReferenceExpression = 97, + JSExpressionStatement = 99, + JSConditionalStatement = 100, + JSParameter = 101, + JSFunctionDeclaration = 104, + JSArrowFunctionDeclaration = 105, + JSLetDeclaration = 106, + JSSourceFile = 107, + JSSourceElement = 108, } import { TextSpan } from "./text" +export function setParents(node: Syntax): void; + export type SyntaxRange = [Syntax, Syntax]; interface SyntaxBase { @@ -107,6 +110,7 @@ export type BoltToken | BoltIntegerLiteral | BoltIdentifier | BoltOperator + | BoltAssignment | BoltEOS | BoltComma | BoltSemi @@ -162,6 +166,11 @@ export interface BoltOperator extends SyntaxBase { text: string; } +export interface BoltAssignment extends SyntaxBase { + kind: SyntaxKind.BoltAssignment; + operator: string | null; +} + export interface BoltEOS extends SyntaxBase { kind: SyntaxKind.BoltEOS; } @@ -708,6 +717,7 @@ export type BoltSyntax | BoltIntegerLiteral | BoltIdentifier | BoltOperator + | BoltAssignment | BoltEOS | BoltComma | BoltSemi @@ -799,6 +809,7 @@ export type Syntax | BoltIntegerLiteral | BoltIdentifier | BoltOperator + | BoltAssignment | BoltEOS | BoltComma | BoltSemi @@ -888,6 +899,7 @@ export function createBoltStringLiteral(value: string, span?: TextSpan | null): export function createBoltIntegerLiteral(value: bigint, span?: TextSpan | null): BoltIntegerLiteral; export function createBoltIdentifier(text: string, span?: TextSpan | null): BoltIdentifier; export function createBoltOperator(text: string, span?: TextSpan | null): BoltOperator; +export function createBoltAssignment(operator: string | null, span?: TextSpan | null): BoltAssignment; export function createBoltEOS(span?: TextSpan | null): BoltEOS; export function createBoltComma(span?: TextSpan | null): BoltComma; export function createBoltSemi(span?: TextSpan | null): BoltSemi; @@ -976,6 +988,7 @@ export function isBoltIntegerLiteral(value: any): value is BoltIntegerLiteral; export function isBoltSymbol(value: any): value is BoltSymbol; export function isBoltIdentifier(value: any): value is BoltIdentifier; export function isBoltOperator(value: any): value is BoltOperator; +export function isBoltAssignment(value: any): value is BoltAssignment; export function isBoltEOS(value: any): value is BoltEOS; export function isBoltComma(value: any): value is BoltComma; export function isBoltSemi(value: any): value is BoltSemi; diff --git a/src/bin/bolt-treegen.ts b/src/bin/bolt-treegen.ts index 6d1ac854a..31b7b9a01 100755 --- a/src/bin/bolt-treegen.ts +++ b/src/bin/bolt-treegen.ts @@ -6,7 +6,7 @@ import * as fs from "fs" import { parse, SyntaxError } from "../treegen/parser" import { Declaration } from "../treegen/ast" import { generateAST } from "../treegen/index" -import { getFileStem } from "../util" +import { getFileStem } from "../treegen/util" import minimist from "minimist" const PACKAGE_ROOT = path.join(__dirname, '..', '..'); diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index 0272a8a03..1ba5de822 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -3,9 +3,6 @@ import "reflect-metadata" import "source-map-support/register" -import * as path from "path" -import * as fs from "fs-extra" - import yargs from "yargs" import { Program } from "../program" @@ -47,7 +44,7 @@ yargs 'link [name]', 'Link projects with each other', - + yargs => yargs, args => { @@ -66,12 +63,17 @@ yargs yargs => yargs .string('work-dir') .describe('work-dir', 'The working directory where files will be resolved against.') - .default('work-dir', '.'), + .default('work-dir', '.') + .string('inspect-server'), args => { - const files = toArray(args.files as string[] | string).map(filepath => new TextFile(filepath, args['work-dir'])); - const program = new Program(files) + const files = toArray(args.files as string[] | string); + const opts = {}; + if (args['inspect-server'] !== undefined) { + opts.inspector = connectToInspector(args['inspect-server'] as string); + } + const program = new Program(files, opts); program.compile("JS"); }) diff --git a/src/checker.ts b/src/checker.ts index ae94354c5..2c68f6743 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -101,7 +101,7 @@ function getFullName(node: Syntax) { case SyntaxKind.BoltRecordDeclaration: out.unshift(getFullTextOfQualName(curr.name)) break; - } + } curr = curr.parentNode; if (curr === null) { break; @@ -129,6 +129,8 @@ export class TypeChecker { protected createType(node: Syntax): Type { + console.error(`creating type for ${kindToString(node.kind)}`); + switch (node.kind) { case SyntaxKind.BoltReferenceExpression: @@ -145,6 +147,10 @@ export class TypeChecker { case SyntaxKind.BoltExpressionStatement: return voidType; + case SyntaxKind.BoltCallExpression: + // TODO + return anyType; + case SyntaxKind.BoltFunctionDeclaration: let returnType = anyType; if (node.returnType !== null) { @@ -182,8 +188,8 @@ export class TypeChecker { return typ; case SyntaxKind.BoltParameter: - if (node.typeNode !== null) { - return this.getTypeOfNode(node.typeNode) + if (node.type !== null) { + return this.getTypeOfNode(node.type) } return anyType; @@ -218,12 +224,21 @@ export class TypeChecker { case SyntaxKind.BoltSentence: case SyntaxKind.BoltRecordDeclaration: case SyntaxKind.BoltNewTypeDeclaration: + case SyntaxKind.BoltConstantExpression: break; case SyntaxKind.BoltExpressionStatement: this.check(node.expression); break; + case SyntaxKind.BoltCallExpression: + this.check(node.operator); + for (const operand of node.operands) { + this.check(operand); + } + // TODO check whether the overload matches the referenced operator + break; + case SyntaxKind.BoltFunctionDeclaration: if (node.body !== null) { if (Array.isArray(node.body)) { diff --git a/src/compiler.ts b/src/compiler.ts index 64468b403..5f8ac197c 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -30,6 +30,8 @@ import { createJSBindPattern, JSDeclarationModifiers, JSParameter, + BoltSyntax, + BoltPattern, } from "./ast" import { getFullTextOfQualName, hasPublicModifier } from "./util" @@ -95,9 +97,11 @@ export class Compiler { } - protected compileDecl(node: Syntax, preamble: Syntax[]) { + private compilePattern(node: BoltPattern) { - console.log(`compiling ${kindToString(node.kind)}`) + } + + protected compileDecl(node: BoltSyntax, preamble: Syntax[]) { //if (isBoltExpression(node)) { // const compiled = this.compileExpr(node, preamble); @@ -121,6 +125,9 @@ export class Compiler { // TODO break; + case SyntaxKind.BoltNewTypeDeclaration: + break; + case SyntaxKind.BoltVariableDeclaration: const compiledValue = node.value !== null ? this.compileExpr(node.value, preamble) : null; preamble.push( @@ -132,8 +139,11 @@ export class Compiler { ); break; - case SyntaxKind.BoltForeignFunctionDeclaration: - if (node.target === this.target && node.body !== null) { + case SyntaxKind.BoltFunctionDeclaration: + if (node.target === this.target) { + if (node.body === null) { + break; + } const params: JSParameter[] = []; let body: JSStatement[] = []; for (const param of node.params) { @@ -150,6 +160,9 @@ export class Compiler { result.modifiers |= JSDeclarationModifiers.IsExported;; } preamble.push(result) + } else { + // TODO + throw new Error(`Compiling native functions is not yet implemented.`); } break; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 000000000..0074255d4 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,7 @@ + +export const TYPES = { + Program: Symbol('a Bolt program'), + TypeChecker: Symbol('the Bolt type checking system'), + FileManager: Symbol('the file manager'), +} + diff --git a/src/emitter.ts b/src/emitter.ts index 77abbbed2..b0ec8e967 100644 --- a/src/emitter.ts +++ b/src/emitter.ts @@ -5,25 +5,50 @@ export class Emitter { emit(node: Syntax) { - debug(node); + let out = ''; switch (node.kind) { case SyntaxKind.JSReferenceExpression: - return node.name; + out += node.name; + break; + + case SyntaxKind.JSConstantExpression: + if (typeof node.value === 'string') { + out += '"' + node.value + '"'; + } else if (typeof node.value === 'bigint') { + out += node.value.toString(); + } else { + throw new Error(`Could not emit the value of a specific JSConstantExpression.`); + } + break; + + case SyntaxKind.JSFunctionDeclaration: + out += 'function ' + node.name.text + '('; + //out += node.params.map(p => this.emit(p)).join(', '); + out += ') {\n'; + out += '}\n\n' + break; + + case SyntaxKind.JSCallExpression: + out += this.emit(node.operator) + '('; + out += node.operands.map(op => this.emit(op)).join(', '); + out += ')' + break; case SyntaxKind.JSSourceFile: - let out = '' for (const element of node.elements) { out += this.emit(element); } - return out; + break; default: throw new Error(`Could not emit source code for ${kindToString(node.kind)}`) } + return out; + } } diff --git a/src/expander.ts b/src/expander.ts index e566ee5cf..4e1b08fb2 100644 --- a/src/expander.ts +++ b/src/expander.ts @@ -4,6 +4,7 @@ import { SyntaxKind, + setParents, kindToString, BoltSyntax, BoltSentence, @@ -35,20 +36,13 @@ import { TextSpan } from "./text" import { TypeChecker } from "./checker" import { Parser, ParseError } from "./parser" import { Evaluator, TRUE, FALSE } from "./evaluator" -import { StreamWrapper, setOrigNodeRange, BoltTokenStream } from "./util" +import { StreamWrapper, setOrigNodeRange, BoltTokenStream, createTokenStream } from "./util" interface Transformer { pattern: BoltPattern; transform: (node: BoltTokenStream) => BoltSyntax; } -function createTokenStream(node: BoltSentence) { - return new StreamWrapper( - node.tokens, - () => createBoltEOS(new TextSpan(node.span!.file, node.span!.end.clone(), node.span!.end.clone())) - ); -} - function createSimpleBoltReferenceTypeNode(text: string): BoltReferenceTypeNode { const ids = text.split('.').map(name => createBoltIdentifier(name)) return createBoltReferenceTypeNode(createBoltQualName(ids.slice(0, -1), ids[ids.length-1]), []) @@ -145,6 +139,7 @@ export class Expander { const newSourceFile = createBoltSourceFile(expanded); setOrigNodeRange(newSourceFile, node, node); + setParents(newSourceFile); return newSourceFile; } else if (node.kind == SyntaxKind.BoltModule) { @@ -170,6 +165,7 @@ export class Expander { const newModule = createBoltModule(0, node.name, expanded); setOrigNodeRange(newModule, node, node); + setParents(newModule); return newModule; } else if (node.kind === SyntaxKind.BoltSentence) { diff --git a/src/parser.ts b/src/parser.ts index 5bad71fcf..e59228308 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,4 +1,6 @@ +import * as acorn from "acorn" + import { SyntaxKind, kindToString, @@ -45,9 +47,12 @@ import { BoltFunctionDeclaration, createBoltFunctionDeclaration, createBoltCallExpression, + BoltSymbol, } from "./ast" -import { BoltTokenStream, setOrigNodeRange } from "./util" +import { Stream, setOrigNodeRange, createTokenStream, uniq } from "./util" + +export type BoltTokenStream = Stream; function describeKind(kind: SyntaxKind): string { switch (kind) { @@ -148,10 +153,10 @@ const KIND_EXPRESSION_T0 = [ SyntaxKind.BoltYieldKeyword, ] -const KIND_STATEMENT_T0 = [ +const KIND_STATEMENT_T0 = uniq([ SyntaxKind.BoltReturnKeyword, ...KIND_EXPRESSION_T0, -] +]) const KIND_DECLARATION_KEYWORD = [ SyntaxKind.BoltFnKeyword, @@ -162,18 +167,18 @@ const KIND_DECLARATION_KEYWORD = [ SyntaxKind.BoltStructKeyword, ] -const KIND_DECLARATION_T0 = [ +const KIND_DECLARATION_T0 = uniq([ SyntaxKind.BoltPubKeyword, SyntaxKind.BoltForeignKeyword, ...KIND_DECLARATION_KEYWORD, -] +]) -const KIND_SOURCEELEMENT_T0 = [ +const KIND_SOURCEELEMENT_T0 = uniq([ SyntaxKind.BoltModKeyword, ...KIND_EXPRESSION_T0, ...KIND_STATEMENT_T0, ...KIND_DECLARATION_T0, -] +]) export class Parser { @@ -213,6 +218,10 @@ export class Parser { } } + public parse(kind: SyntaxKind, tokens: BoltTokenStream): BoltSyntax { + return (this as any)['parse' + kindToString(kind).substring('Bolt'.length)](tokens); + } + public parseQualName(tokens: BoltTokenStream): BoltQualName { const path: BoltIdentifier[] = []; @@ -373,7 +382,7 @@ export class Parser { //} public parseExpression(tokens: BoltTokenStream): BoltExpression { - return this.parsePrimitiveExpression(tokens) + return this.parseCallOrPrimitiveExpression(tokens) } public parseParameter(tokens: BoltTokenStream): BoltParameter { @@ -627,7 +636,7 @@ export class Parser { let lastToken: BoltSyntax; const firstToken = k0; - if (k0.kind !== SyntaxKind.BoltPubKeyword) { + if (k0.kind === SyntaxKind.BoltPubKeyword) { tokens.get(); modifiers |= BoltDeclarationModifiers.Public; k0 = tokens.peek(); @@ -650,9 +659,9 @@ export class Parser { tokens.get(); - let name: BoltQualName; + let name: BoltSymbol; let returnType = null; - let body = null; + let body: any = null; // FIXME type-checking should not be disabled let params: BoltParameter[] = []; // Parse parameters @@ -687,16 +696,14 @@ export class Parser { if (t0.kind === SyntaxKind.BoltOperator) { - name = createBoltQualName([], t0); - setOrigNodeRange(name, t0, t0); + name = t0; tokens.get(); params.push(parseParamLike(tokens)) } else if (isParamLike(t0) && t1.kind == SyntaxKind.BoltOperator) { params.push(parseParamLike(tokens)); - name = createBoltQualName([], t1); - setOrigNodeRange(name, t1, t1); + name = t1; while (true) { const t2 = tokens.peek(); if (t2.kind !== SyntaxKind.BoltOperator) { @@ -711,7 +718,8 @@ export class Parser { } else if (t0.kind === SyntaxKind.BoltIdentifier) { - name = this.parseQualName(tokens) + name = t0; + tokens.get(); const t2 = tokens.get(); if (t2.kind === SyntaxKind.BoltParenthesized) { const innerTokens = createTokenStream(t2); @@ -762,8 +770,7 @@ export class Parser { body = this.parseStatements(tokens); break; case "JS": - // TODO - //body = acorn.parse(t3.text).body; + body = acorn.parse(t3.text); break; default: throw new Error(`Unrecognised language: ${target}`); @@ -787,7 +794,8 @@ export class Parser { let t0 = tokens.peek(1); let i = 1; if (t0.kind === SyntaxKind.BoltPubKeyword) { - t0 = tokens.peek(i++); + i += 1; + t0 = tokens.peek(i); if (t0.kind !== SyntaxKind.BoltForeignKeyword) { if (KIND_DECLARATION_KEYWORD.indexOf(t0.kind) === -1) { throw new ParseError(t0, KIND_DECLARATION_KEYWORD); @@ -818,22 +826,22 @@ export class Parser { throw new ParseError(t0, KIND_DECLARATION_T0); } } - + public parseSourceElement(tokens: BoltTokenStream): BoltSourceElement { const t0 = tokens.peek(); try { return this.parseDeclaration(tokens) - } catch (e) { - if (!(e instanceof ParseError)) { - throw e; + } catch (e1) { + if (!(e1 instanceof ParseError)) { + throw e1; } try { return this.parseStatement(tokens); - } catch (e) { - if (!(e instanceof ParseError)) { - throw e; + } catch (e2) { + if (!(e2 instanceof ParseError)) { + throw e2; } - throw new ParseError(t0, KIND_SOURCEELEMENT_T0) + throw e1; } } } @@ -879,14 +887,17 @@ export class Parser { // return lhs //} - public parseCallExpression(tokens: BoltTokenStream): BoltCallExpression { + public parseCallOrPrimitiveExpression(tokens: BoltTokenStream): BoltExpression { const operator = this.parsePrimitiveExpression(tokens) - const args: BoltExpression[] = [] const t2 = tokens.get(); + if (t2.kind === SyntaxKind.BoltEOS) { + return operator; + } assertToken(t2, SyntaxKind.BoltParenthesized); + const args: BoltExpression[] = [] const innerTokens = createTokenStream(t2); while (true) { @@ -903,7 +914,9 @@ export class Parser { } } - return createBoltCallExpression(operator, args, null) + const node = createBoltCallExpression(operator, args, null) + setOrigNodeRange(node, operator, t2); + return node; } diff --git a/src/program.ts b/src/program.ts index 2c2bf3fa5..f82435eb2 100644 --- a/src/program.ts +++ b/src/program.ts @@ -1,6 +1,8 @@ import * as path from "path" import * as fs from "fs-extra" +import { now } from "microtime" +import { EventEmitter } from "events" import { Parser } from "./parser" import { TypeChecker } from "./checker" @@ -10,9 +12,10 @@ import { Scanner } from "./scanner" import { Compiler } from "./compiler" import { emit } from "./emitter" import { TextFile } from "./text" -import { BoltSourceFile, Syntax } from "./ast" +import { BoltSourceFile, Syntax, JSSourceFile } from "./ast" import { upsearchSync, FastStringMap, getFileStem, getLanguage } from "./util" import { Package } from "./package" +import { verbose, memoize } from "./util" const targetExtensions: FastStringMap = { 'JS': '.mjs', @@ -20,55 +23,106 @@ const targetExtensions: FastStringMap = { 'C': '.c', }; +export interface TransformationContext { + +} + +interface TimingInfo { + timestamp: number; + refCount: number; +} + +class Timing extends EventEmitter { + + private runningTasks: FastStringMap = Object.create(null); + + public start(name: string) { + if (this.runningTasks[name] !== undefined) { + this.runningTasks[name].refCount++; + return; + } + this.runningTasks[name] = { timestamp: now(), refCount: 1 }; + this.emit(`start ${name}`); + } + + public end(name: string) { + if (this.runningTasks[name] === undefined) { + throw new Error(`Task '${name}' was never started.`); + } + const info = this.runningTasks[name]; + info.refCount--; + if (info.refCount === 0) { + const usecs = now() - info.timestamp; + verbose(`Task '${name}' completed after ${usecs} microseconds.`); + this.emit(`end ${name}`); + } + } + +} + export class Program { public parser: Parser public evaluator: Evaluator; public checker: TypeChecker; public expander: Expander; + public timing: Timing; - private sourceFiles = new Map(); - private packages: FastStringMap = Object.create(null); - - constructor(files: TextFile[]) { + constructor(public files: string[]) { this.checker = new TypeChecker(); this.parser = new Parser(); this.evaluator = new Evaluator(this.checker); this.expander = new Expander(this.parser, this.evaluator, this.checker); - for (const file of files) { - console.log(`Loading ${file.origPath} ...`); - const contents = fs.readFileSync(file.fullPath, 'utf8'); - const scanner = new Scanner(file, contents) - this.sourceFiles.set(file.fullPath, scanner.scan()); - } + this.timing = new Timing(); } + @memoize + public getTextFile(filename: string): TextFile { + return new TextFile(filename); + } + + @memoize + public getSourceFile(file: TextFile): BoltSourceFile { + this.timing.start('read'); + const contents = fs.readFileSync(file.origPath, 'utf8'); + this.timing.end('read'); + const scanner = new Scanner(file, contents) + this.timing.start('scan'); + const sourceFile = scanner.scan(); + this.timing.end('scan'); + return sourceFile; + } + + @memoize + public getFullyExpandedSourceFile(file: TextFile): BoltSourceFile { + const sourceFile = this.getSourceFile(file); + this.timing.start('expand'); + const expanded = this.expander.getFullyExpanded(sourceFile) as BoltSourceFile; + this.timing.end('expand'); + return expanded; + } + + @memoize public getPackage(filepath: string) { - filepath = path.resolve(filepath); - const projectFile = upsearchSync('Boltfile', path.dirname(filepath)); + const file = this.getTextFile(filepath) + const projectFile = upsearchSync('Boltfile', path.dirname(file.fullPath)); if (projectFile === null) { return null; } const projectDir = path.resolve(path.dirname(projectFile)); - if (this.packages[projectDir] !== undefined) { - return this.packages[projectDir]; - } - return this.packages[projectDir] = new Package(projectDir); + return new Package(projectDir); } public compile(target: string) { const compiler = new Compiler(this, this.checker, { target }) - const expanded: SourceFile[] = []; - for (const [filepath, sourceFile] of this.sourceFiles) { - expanded.push(this.expander.getFullyExpanded(sourceFile) as SourceFile); - } - const compiled = compiler.compile(expanded) as AnySourceFile[]; + const expanded = this.files.map(filename => this.getFullyExpandedSourceFile(this.getTextFile(filename))); + const compiled = compiler.compile(expanded) as JSSourceFile[]; for (const rootNode of compiled) { - const filepath = rootNode.span!.file.fullPath; - const pkg = this.getPackage(filepath); - if (pkg !== null) { - - } + //const filepath = rootNode.span!.file.fullPath; + //const pkg = this.getPackage(filepath); + //if (pkg !== null) { + // + //} fs.mkdirp('.bolt-work'); fs.writeFileSync(this.mapToTargetFile(rootNode), emit(rootNode), 'utf8'); } @@ -78,13 +132,12 @@ export class Program { return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getLanguage(node))); } - eval(filename: string) { - const original = this.sourceFiles.get(filename); - if (original === undefined) { - throw new Error(`File ${filename} does not seem to be part of this Program.`) + public eval() { + for (const filename of this.files) { + const file = this.getTextFile(filename); + const expanded = this.getFullyExpandedSourceFile(file); + this.evaluator.eval(expanded) } - const expanded = this.expander.getFullyExpanded(original) as BoltSourceFile; - return this.evaluator.eval(expanded) } } diff --git a/src/renamer.ts b/src/renamer.ts new file mode 100644 index 000000000..b28b04f64 --- /dev/null +++ b/src/renamer.ts @@ -0,0 +1,3 @@ + + + diff --git a/src/scanner.ts b/src/scanner.ts index 2b9ecc0fa..da589c379 100644 --- a/src/scanner.ts +++ b/src/scanner.ts @@ -8,6 +8,7 @@ import { } from "./text" import { + setParents, SyntaxKind, BoltToken, BoltSentence, @@ -27,6 +28,18 @@ import { createBoltEOS, createBoltDot, createBoltEqSign, + createBoltPubKeyword, + createBoltMutKeyword, + createBoltStructKeyword, + createBoltEnumKeyword, + createBoltForeignKeyword, + createBoltAssignment, + createBoltYieldKeyword, + createBoltReturnKeyword, + createBoltFnKeyword, + createBoltLArrow, + createBoltDotDot, + createBoltNewTypeKeyword, } from "./ast" export enum PunctType { @@ -122,14 +135,13 @@ function isIdentPart(ch: string) { return ch == '_' || XRegExp('\\p{L}').test(ch) } -function isOperatorStart(ch: string) { - return /[+\-*\/%$!><]/.test(ch) +function isSymbol(ch: string) { + return /[=+\/-*%$!><&^|]/.test(ch) } -function isOperatorPart(ch: string) { - return /[=+\-*\/%$!><]/.test(ch) - -} +//function isOperatorPart(ch: string) { + //return /[=+\-*\/%$!><]/.test(ch) +//} const EOF = '' @@ -210,12 +222,6 @@ export class Scanner { } switch (c0) { - case '.': - this.getChar(); - return createBoltDot(new TextSpan(this.file, startPos, this.currPos.clone())); - case '=': - this.getChar(); - return createBoltEqSign(new TextSpan(this.file, startPos, this.currPos.clone())); case ';': this.getChar(); return createBoltSemi(new TextSpan(this.file, startPos, this.currPos.clone())); @@ -304,18 +310,42 @@ export class Scanner { const name = this.takeWhile(isIdentPart); const endPos = this.currPos.clone(); - return createBoltIdentifier(name, new TextSpan(this.file, startPos, endPos)) + const span = new TextSpan(this.file, startPos, endPos); + switch (name) { + case 'pub': return createBoltPubKeyword(span); + case 'fn': return createBoltFnKeyword(span); + case 'return': return createBoltReturnKeyword(span); + case 'yield': return createBoltYieldKeyword(span); + case 'foreign': return createBoltForeignKeyword(span); + case 'let': return createBoltPubKeyword(span); + case 'mut': return createBoltMutKeyword(span); + case 'struct': return createBoltStructKeyword(span); + case 'enum': return createBoltEnumKeyword(span); + case 'newtype': return createBoltNewTypeKeyword(span); + default: return createBoltIdentifier(name, span); + } - } else if (isOperatorStart(c0)) { + } else if (isSymbol(c0)) { - const text = this.takeWhile(isOperatorPart) + const text = this.takeWhile(isSymbol) const endPos = this.currPos.clone() const span = new TextSpan(this.file, startPos, endPos); - if (text === '->') { - return createBoltRArrow(span); - } else { - return createBoltOperator(text, span); + if (text.endsWith('=')) { + const operator = text.substring(0, text.length-1); + if (text === '==') { + return createBoltOperator(text, span); + } + return createBoltAssignment(operator.length === 0 ? null : operator, span); + } + + switch (text) { + case '->': return createBoltRArrow(span); + case '<-': return createBoltLArrow(span); + case '.': return createBoltDot(span); + case '..': return createBoltDotDot(span); + case '=': return createBoltEqSign(span); + default: return createBoltOperator(text, span); } } else { @@ -385,7 +415,9 @@ export class Scanner { const startPos = this.currPos.clone(); const elements = this.scanTokens(); const endPos = this.currPos.clone(); - return createBoltSourceFile(elements, new TextSpan(this.file, startPos, endPos)); + const sourceFile = createBoltSourceFile(elements, new TextSpan(this.file, startPos, endPos)); + setParents(sourceFile); + return sourceFile; } } diff --git a/src/treegen/ast-template.js b/src/treegen/ast-template.js index 3c4bfaedf..53e08f805 100644 --- a/src/treegen/ast-template.js +++ b/src/treegen/ast-template.js @@ -13,6 +13,26 @@ function isSyntax(value) { && value.__NODE_TYPE !== undefined; } +function* getChildNodes(node) { + for (const key of Object.keys(node)) { + if (key === 'span' || key === 'parentNode') { + continue + } + const value = node[key]; + if (Array.isArray(value)) { + for (const element of value) { + if (isSyntax(element)) { + yield element; + } + } + } else if (isSyntax(value)) { + if (isSyntax(value)) { + yield value; + } + } + } +} + function createNode(nodeType) { const obj = Object.create(nodeProto); Object.defineProperty(obj, '__NODE_TYPE', { @@ -66,6 +86,13 @@ for (const nodeName of Object.keys(NODE_TYPES)) { } +exported.setParents = function setParents(node, parentNode = null) { + node.parentNode = parentNode; + for (const child of getChildNodes(node)) { + setParents(child, node) + } +} + if (typeof module !== 'undefined') { module.exports = exports; } diff --git a/src/treegen/index.ts b/src/treegen/index.ts index f30f8ae60..799547805 100644 --- a/src/treegen/index.ts +++ b/src/treegen/index.ts @@ -106,6 +106,8 @@ export function generateAST(decls: Declaration[]) { import { TextSpan } from "./text" +export function setParents(node: Syntax): void; + export type SyntaxRange = [Syntax, Syntax]; interface SyntaxBase { diff --git a/src/treegen/util.ts b/src/treegen/util.ts index 55ac4e2e2..fef9b9ea3 100644 --- a/src/treegen/util.ts +++ b/src/treegen/util.ts @@ -1,4 +1,10 @@ +import * as path from "path" + +export function getFileStem(filepath: string): string { + return path.basename(filepath).split('.')[0]; +} + function isWhiteSpace(ch: string) { return /[\r\t ]/.test(ch); } diff --git a/src/util.ts b/src/util.ts index 17abc0b8c..a129eba23 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,20 +1,107 @@ import * as path from "path" import * as fs from "fs" +import moment from "moment" +import chalk from "chalk" -import { TextSpan } from "./text" -import { kindToString, Syntax, BoltToken, BoltQualName, BoltDeclaration, BoltDeclarationModifiers } from "./ast" +import { TextSpan, TextPos } from "./text" +import { Scanner } from "./scanner" +import { kindToString, Syntax, BoltQualName, BoltDeclaration, BoltDeclarationModifiers, createBoltEOS, SyntaxKind, isBoltPunctuated } from "./ast" -export type BoltTokenStream = Stream; +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, + () => createBoltEOS(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; +export function uniq(elements: T[]): T[] { + const out: T[] = []; + const visited = new Set(); + for (const element of elements) { + if (visited.has(element)) { + continue; + } + visited.add(element); + out.push(element); + } + return out; +} + export interface FastStringMap { [key: string]: T } +class DeepMap { + + private rootMap = new Map(); + + public has(key: any[]) { + let curr = this.rootMap; + for (const element of key) { + if (!curr.has(element)) { + return false; + } + curr = curr.get(element)!; + } + return true; + } + + public get(key: any[]) { + let curr = this.rootMap; + for (const element of key) { + if (!curr.has(element)) { + return; + } + curr = curr.get(element)!; + } + return curr; + } + + public set(key: any[], value: any) { + let curr = this.rootMap; + for (const element of key.slice(0, -1)) { + if (!curr.has(element)) { + curr.set(element, new Map()); + } + curr = curr.get(element)!; + } + curr.set(key[key.length-1], value); + } + +} + +export function memoize(target: any, key: PropertyKey) { + const origMethod = target[key]; + target[key] = function wrapper(...args: any[]) { + if (this.__MEMOIZE_CACHE === undefined) { + this.__MEMOIZE_CACHE = Object.create(null); + } + if (this.__MEMOIZE_CACHE[key] === undefined) { + this.__MEMOIZE_CACHE[key] = new DeepMap(); + } + const cache = this.__MEMOIZE_CACHE[key]; + if (cache.has(args)) { + return cache.get(args); + } + const result = origMethod.apply(this, args); + cache.set(args, result); + return result; + } +} + const supportedLanguages = ['Bolt', 'JS']; export function getLanguage(node: Syntax): string { @@ -39,7 +126,7 @@ export function setOrigNodeRange(node: Syntax, startNode: Syntax, endNode: Synta } export function hasPublicModifier(node: BoltDeclaration) { - return (node.modifiers & BoltDeclarationModifiers.IsPublic) > 0; + return (node.modifiers & BoltDeclarationModifiers.Public) > 0; } export function getFullTextOfQualName(node: BoltQualName) { @@ -80,6 +167,12 @@ export class StreamWrapper { } +const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' + +export function verbose(message: string) { + console.error(chalk.gray('[') + chalk.magenta('verb') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message); +} + export function upsearchSync(filename: string, startDir = '.') { let currDir = startDir; while (true) {