Update frontend and integration with CLI

This commit is contained in:
Sam Vervaeck 2020-05-31 20:48:13 +02:00
parent a140ffac9d
commit 518631f3b8
6 changed files with 430 additions and 315 deletions

View file

@ -2,24 +2,10 @@
import "reflect-metadata"
import "source-map-support/register"
import { sync as globSync } from "glob"
import * as path from "path"
import * as fs from "fs"
import yargs from "yargs"
import yaml from "js-yaml"
import semver from "semver"
import { Program } from "../program"
import { parseSourceFile } from "../parser"
import { Frontend } from "../frontend"
import { Package } from "../common"
import {hasOwnProperty, upsearchSync, expandPath, FastStringMap, assert} from "../util"
import {isString} from "util"
import {DiagnosticPrinter, E_FIELD_NOT_PRESENT, E_FIELD_MUST_BE_BOOLEAN, E_FIELD_MUST_BE_STRING, E_FIELD_HAS_INVALID_VERSION_NUMBER} from "../diagnostics"
//global.print = function (value: any) {
// console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
//}
import { expandPath } from "../util"
const BOLT_HOME = expandPath(process.env['BOLT_HOME'] ?? '~/.bolt-compiler')
@ -30,159 +16,15 @@ function toArray<T>(value: T | T[]): 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
}
const diagnostics = new DiagnosticPrinter();
function loadPackageMetadata(rootDir: string) {
let name = null
let version = null;
let autoImport = false;
let hasVersionErrors = false;
let hasNameErrors = false;
const filepath = path.join(rootDir, 'Boltfile');
if (fs.existsSync(filepath)) {
const data = yaml.safeLoad(fs.readFileSync(filepath, 'utf8'));
if (data !== undefined) {
if (hasOwnProperty(data, 'name')) {
if (!isString(data.name)) {
diagnostics.add({
message: E_FIELD_MUST_BE_STRING,
severity: 'error',
args: { name: 'name' },
});
hasNameErrors = true;
} else {
name = data.name;
}
}
if (hasOwnProperty(data, 'version')) {
if (!isString(data.version)) {
diagnostics.add({
message: E_FIELD_MUST_BE_STRING,
args: { name: 'version' },
severity: 'error',
});
hasVersionErrors = true;
} else {
if (!semver.valid(data.version)) {
diagnostics.add({
message: E_FIELD_HAS_INVALID_VERSION_NUMBER,
args: { name: 'version' },
severity: 'error',
});
hasVersionErrors = true;
} else {
version = data.version;
}
}
}
if (hasOwnProperty(data, 'auto-import')) {
if (typeof(data['auto-import']) !== 'boolean') {
diagnostics.add({
message: E_FIELD_MUST_BE_BOOLEAN,
args: { name: 'auto-import' },
severity: 'error',
})
} else {
autoImport = data['auto-import'];
}
}
}
}
if (name === null && !hasNameErrors) {
diagnostics.add({
message: E_FIELD_NOT_PRESENT,
severity: 'warning',
args: { name: 'name' },
});
}
if (version === null && !hasVersionErrors) {
diagnostics.add({
message: E_FIELD_NOT_PRESENT,
severity: 'warning',
args: { name: 'version' },
});
}
return {
name,
version,
autoImport,
};
}
function loadPackageFromPath(rootDir: string, isDependency: boolean): Package {
rootDir = path.resolve(rootDir);
const data = loadPackageMetadata(rootDir);
const pkg = new Package(rootDir, data.name, data.version, [], data.autoImport, isDependency);
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
pkg.addSourceFile(parseSourceFile(filepath, pkg));
}
return pkg;
}
function error(message: string) {
console.error(`Error: ${message}`);
}
function loadPackagesAndSourceFiles(filenames: string[], pkgResolver: PackageResolver, cwd = '.', useStd: boolean): Package[] {
cwd = path.resolve(cwd);
const anonPkg = new Package(cwd, null, null, [], false, false);
const pkgs = [ anonPkg ];
for (const filename of filenames) {
if (fs.statSync(filename).isDirectory()) {
pkgs.push(loadPackageFromPath(filename, false));
} else {
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg));
}
function parsePackageResolverFlags(frontend: Frontend, flags: string[]) {
for (const flag of flags) {
const [pkgName, pkgPath] = flag.split(':');
frontend.mapPackageNameToPath(pkgName, pkgPath)
}
if (useStd && pkgs.find(pkg => pkg.name === 'stdlib') === undefined) {
const resolvedPath = pkgResolver.findPackagePath('stdlib');
if (resolvedPath === null) {
error(`Package 'stdlib' is required to build the current source set but it was not found. Use --no-std if you know what you are doing.`);
process.exit(1);
}
const stdlibPkg = loadPackageFromPath(resolvedPath, true);
assert(stdlibPkg !== null);
pkgs.push(stdlibPkg);
}
return pkgs;
}
class PackagePathResolver {
private packageNameToPath = new FastStringMap<string, string>();
public findPackagePath(name: string): string | null {
if (this.packageNameToPath.has(name)) {
return this.packageNameToPath.get(name);
}
return null;
}
public mapPackgeNameToPath(name: string, filepath: string): void {
this.packageNameToPath.set(name, filepath);
}
}
yargs
@ -215,24 +57,16 @@ yargs
args => {
const useStd = args['std'] as boolean ?? true;
const cwd = process.cwd();
const pkgResolver = new PackagePathResolver();
for (const pkgMapping of toArray(args.pkg as string[] | string)) {
const [pkgName, pkgPath] = pkgMapping.split(':');
pkgResolver.mapPackgeNameToPath(pkgName, pkgPath)
}
const files = toArray(args.files as string[] | string);
if (files.length === 0) {
const metadataPath = upsearchSync('Boltfile');
if (metadataPath === null) {
error(`No source files specified on the command-line and no Boltfile found in ${cwd} or any of its parent directories.`)
process.exit(1);
}
files.push(metadataPath);
}
const pkgs = loadPackagesAndSourceFiles(files, pkgResolver, cwd, useStd);
const program = new Program(pkgs);
const frontend = new Frontend();
frontend.check(program);
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
if (program !== null) {
frontend.check(program);
}
if (frontend.diagnostics.hasErrors) {
process.exit(1);
}
}
)
@ -254,25 +88,29 @@ yargs
, args => {
const force = args.force as boolean;
const useStd = args['std'] as boolean ?? true;
const cwd = process.cwd();
const files = toArray(args.files as string[] | string);
if (files.length === 0) {
const metadataPath = upsearchSync('Boltfile');
if (metadataPath === null) {
error(`No source files specified on the command-line and no Boltfile found in ${cwd} or any of its parent directories.`)
process.exit(1);
}
files.push(metadataPath);
}
const pkgs = loadPackagesAndSourceFiles(files);
const program = new Program(pkgs);
const frontend = new Frontend();
frontend.check(program);
if (frontend.diagnostics.hasErrors && !args.force) {
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
if (program === null && !force) {
process.exit(1);
}
frontend.compile(program, args.target);
if (program !== null) {
frontend.check(program);
if (frontend.diagnostics.hasErrors && !force) {
process.exit(1);
}
frontend.compile(program, args.target);
}
})
.command(
@ -283,19 +121,42 @@ yargs
yargs => yargs
.string('work-dir')
.describe('work-dir', 'The working directory where files will be resolved against.')
.default('work-dir', '.'),
.default('work-dir', '.')
.boolean('skip-type-checks')
.describe('skip-type-checks', 'Do not check the program for common mistakes before evaluating.')
.default('skip-type-checks', false)
.boolean('force')
.describe('force', 'Ignore as much errors as possible.')
.default('force', false)
args => {
, args => {
const sourceFiles = toArray(args.files as string | string[]).map(parseSourceFile);
const runTypeChecker = !(args["skip-type-checks"] as boolean);
const force = args.force as boolean;
const useStd = args['std'] as boolean ?? true;
const cwd = process.cwd();
const files = toArray(args.files as string[] | string);
if (sourceFiles.length === 0) {
throw new Error(`Executing packages is not yet supported.`)
const frontend = new Frontend();
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
if (program === null && !force) {
process.exit(1);
}
if (program !== null) {
if (runTypeChecker) {
frontend.check(program);
}
if (frontend.diagnostics.hasErrors && !force) {
process.exit(1);
}
frontend.eval(program);
}
const program = new Program(sourceFiles);
const frontend = new Frontend();
frontend.eval(program);
}

View file

@ -12,10 +12,12 @@ import {
BoltSourceFile,
isSourceFile,
BoltSyntax,
BoltModifiers
BoltModifiers,
ReturnStatement,
FunctionBodyElement
} from "./ast";
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
import {FastStringMap, enumOr, escapeChar, assert} from "./util";
import {TextSpan, TextPos, TextFile} from "./text";
import {Scanner} from "./scanner";
import * as path from "path"
@ -35,46 +37,7 @@ export function getSourceFile(node: Syntax) {
export function getPackage(node: Syntax) {
const sourceFile = getSourceFile(node);
assert(sourceFile.kind === SyntaxKind.BoltSourceFile);
return (sourceFile as BoltSourceFile).package;
}
let nextPackageId = 1;
export class Package {
public id = nextPackageId++;
private sourceFilesByPath = new FastStringMap<string, SourceFile>();
constructor(
public rootDir: string,
public name: string | null,
public version: string | null,
sourceFiles: SourceFile[],
public isAutoImported: boolean,
public isDependency: boolean,
) {
for (const sourceFile of sourceFiles) {
this.addSourceFile(sourceFile);
}
}
public getAllSourceFiles(): IterableIterator<SourceFile> {
return this.sourceFilesByPath.values();
}
public getMainLibrarySourceFile(): SourceFile | null {
const fullPath = path.resolve(this.rootDir, 'lib.bolt');
if (!this.sourceFilesByPath.has(fullPath)) {
return null;
}
return this.sourceFilesByPath.get(fullPath)
}
public addSourceFile(sourceFile: SourceFile) {
this.sourceFilesByPath.set(sourceFile.span!.file.fullPath, sourceFile);
}
return (sourceFile as BoltSourceFile).pkg;
}
export function getNodeLanguage(node: Syntax): string {
@ -175,7 +138,7 @@ export function isRightAssoc(kind: OperatorKind) {
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)}`)
super(`${actual.span!.file.origPath}:${actual.span!.start.line}:${actual.span!.start.column}: expected ${enumOr(expected.map(e => describeKind(e)))} but got ${describeKind(actual.kind)}`);
}
}
@ -220,7 +183,7 @@ export class OperatorTable {
}
export function getModulePathToNode(node: BoltSyntax): string[] {
export function getModulePathToNode(node: Syntax): string[] {
let elements = [];
while (true) {
if (node.kind === SyntaxKind.BoltModule) {
@ -420,7 +383,7 @@ export function describeKind(kind: SyntaxKind): string {
}
}
export function *getAllReturnStatementsInFunctionBody(body: FunctionBody): IterableIterator<ReturnStatement> {
export function *getAllReturnStatementsInFunctionBody(body: FunctionBodyElement[]): IterableIterator<ReturnStatement> {
for (const element of body) {
switch (element.kind) {
case SyntaxKind.BoltReturnStatement:

View file

@ -1,9 +1,15 @@
import * as fs from "fs"
import chalk from "chalk"
import {Syntax} from "./ast";
import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint} from "./util";
import { Syntax } from "./ast";
import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint, assert} from "./util";
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants";
import { TextPos, TextFile, TextSpan } from "./text";
export const E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS = 'No Boltfile found in {path} or any of its parent directories.'
export const E_SSCAN_ERROR = "Got an unexpected {char}"
export const E_STDLIB_NOT_FOUND = "Package 'stdlib' is required to build the current source set but it was not found. Use --no-std if you know what you are doing."
export const E_PARSE_ERROR = "Expected {expected:enum} but got {actual}"
export const E_MAY_NOT_RETURN_A_VALUE = "Returning a value inside a function that does not return values."
export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths.";;;;
export const E_FILE_NOT_FOUND = "A file named {filename} was not found.";
@ -41,6 +47,8 @@ export interface Diagnostic {
args?: MapLike<FormatArg>;
node?: Syntax;
nested?: Diagnostic[];
position?: TextPos,
file?: TextFile,
}
function firstIndexOfNonEmpty(str: string) {
@ -54,6 +62,18 @@ function firstIndexOfNonEmpty(str: string) {
return j
}
export class DiagnosticWriter {
constructor(private fd: number) {
}
public add(diagnostic: Diagnostic) {
fs.writeSync(this.fd, JSON.stringify(diagnostic) + '\n');
}
}
export class DiagnosticPrinter {
public hasErrors = false
@ -106,9 +126,16 @@ export class DiagnosticPrinter {
out += diagnostic.message + '\n';
}
let span = null;
if (diagnostic.node !== undefined) {
span = diagnostic.node.span!;
} else if (diagnostic.position !== undefined) {
assert(diagnostic.file !== undefined);
span = new TextSpan(diagnostic.file!, diagnostic.position, diagnostic.position)
}
if (span !== null) {
out += '\n'
const span = diagnostic.node.span!;
const content = span.file.getText();
const startLine = Math.max(0, span.start.line-1-BOLT_DIAG_NUM_EXTRA_LINES)
const lines = content.split('\n')

View file

@ -1,26 +1,30 @@
import * as path from "path"
import * as fs from "fs-extra"
import { now } from "microtime"
import { EventEmitter } from "events"
import { sync as globSync } from "glob"
import { Program } from "./program"
import { emitNode } from "./emitter"
import { Syntax, BoltSourceFile, SourceFile, NodeVisitor, createBoltConditionalCase } from "./ast"
import { getFileStem, MapLike } from "./util"
import { Syntax, BoltSourceFile, SourceFile, NodeVisitor, createBoltConditionalCase, setParents, kindToString } from "./ast"
import { getFileStem, MapLike, assert, FastStringMap, upsearchSync } from "./util"
import { verbose, memoize } from "./util"
import { Container, Newable } from "./ioc"
import ExpandBoltTransform from "./transforms/expand"
import CompileBoltToJSTransform from "./transforms/boltToJS"
import ConstFoldTransform from "./transforms/constFold"
import { TransformManager } from "./transforms/index"
import {DiagnosticPrinter} from "./diagnostics"
import {DiagnosticPrinter, E_PARSE_ERROR, E_STDLIB_NOT_FOUND, E_SSCAN_ERROR as E_SCAN_ERROR, E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS} from "./diagnostics"
import { TypeChecker } from "./types"
import { checkServerIdentity } from "tls"
import { CheckInvalidFilePaths, CheckTypeAssignments, CheckReferences } from "./checks"
import { SymbolResolver, BoltSymbolResolutionStrategy } from "./resolver"
import { Evaluator } from "./evaluator"
import { getNodeLanguage } from "./common"
import { getNodeLanguage, ParseError, ScanError } from "./common"
import { Package, loadPackageMetadata } from "./package"
import { TextFile } from "./text"
import { Scanner } from "./scanner"
import { Parser } from "./parser"
import { now } from "moment"
const targetExtensions: MapLike<string> = {
'JS': '.mjs',
@ -68,11 +72,17 @@ export class Frontend {
public diagnostics: DiagnosticPrinter;
public timing: Timing;
private packagePathOverrides = new FastStringMap<string, string>();
constructor() {
this.diagnostics = new DiagnosticPrinter();
this.timing = new Timing();
}
public mapPackageNameToPath(pkgName: string, pkgPath: string): void {
this.packagePathOverrides.set(pkgName, pkgPath);
}
public check(program: Program) {
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
@ -142,7 +152,7 @@ export class Frontend {
}
private mapToTargetFile(node: SourceFile) {
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultExtension(getNodeLanguage(node)));
return path.join('.bolt-work', getFileStem(node.span!.file.fullPath) + getDefaultFileExtension(getNodeLanguage(node)));
}
public eval(program: Program) {
@ -154,9 +164,125 @@ export class Frontend {
}
}
private parseSourceFile(filepath: string, pkg: Package): BoltSourceFile | null {
const file = new TextFile(filepath);
const contents = fs.readFileSync(file.origPath, 'utf8');
const scanner = new Scanner(file, contents)
const parser = new Parser();
let sourceFile;
try {
sourceFile = parser.parseSourceFile(scanner, pkg);
} catch (e) {
if (e instanceof ScanError) {
this.diagnostics.add({
severity: 'fatal',
message: E_SCAN_ERROR,
args: { char: e.char },
position: e.position,
file: e.file,
});
return null;
} else if (e instanceof ParseError) {
this.diagnostics.add({
message: E_PARSE_ERROR,
args: { actual: kindToString(e.actual.kind), expected: e.expected.map(kindToString) },
node: e.actual,
severity: 'fatal',
});
return null;
} else {
throw e;
}
}
setParents(sourceFile);
return sourceFile;
}
public loadPackageFromPath(filepath: string, isDependency: boolean): Package {
let metadataPath;
let rootDir;
if (path.basename(filepath) === 'Boltfile') {
metadataPath = filepath
rootDir = path.dirname(filepath);
} else {
metadataPath = path.join(filepath, 'Boltfile');
rootDir = filepath;
}
const data = loadPackageMetadata(this.diagnostics, metadataPath);
const pkg = new Package(rootDir, data.name, data.version, [], data.autoImport, isDependency);
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
const sourceFile = this.parseSourceFile(filepath, pkg);
if (sourceFile !== null) {
pkg.addSourceFile(sourceFile);
}
}
return pkg;
}
private findPackagePath(pkgName: string): string | null {
if (this.packagePathOverrides.has(pkgName)) {
return this.packagePathOverrides.get(pkgName);
}
return null;
}
public loadProgramFromFileList(filenames: string[], cwd = '.', useStd = true): Program | null {
cwd = path.resolve(cwd);
if (filenames.length === 0) {
const metadataPath = upsearchSync('Boltfile');
if (metadataPath === null) {
this.diagnostics.add({
severity: 'fatal',
message: E_NO_BOLTFILE_FOUND_IN_PATH_OR_PARENT_DIRS,
});
return null;
}
filenames.push(metadataPath);
}
const anonPkg = new Package(cwd, null, null, [], false, false);
const pkgs = [ anonPkg ];
for (const filename of filenames) {
if (fs.statSync(filename).isDirectory() || path.basename(filename) === 'Boltfile') {
pkgs.push(this.loadPackageFromPath(filename, false));
} else {
const sourceFile = this.parseSourceFile(filename, anonPkg);
if (sourceFile !== null) {
anonPkg.addSourceFile(sourceFile);
}
}
}
if (useStd) {
if (pkgs.find(pkg => pkg.name === 'stdlib') === undefined) {
const resolvedPath = this.findPackagePath('stdlib');
if (resolvedPath === null) {
this.diagnostics.add({
message: E_STDLIB_NOT_FOUND,
severity: 'error',
});
return null;
}
const stdlibPkg = this.loadPackageFromPath(resolvedPath, true);
pkgs.push(stdlibPkg);
}
}
return new Program(pkgs);
}
}
function getDefaultExtension(target: string) {
function getDefaultFileExtension(target: string) {
if (targetExtensions[target] === undefined) {
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
}

134
src/package.ts Normal file
View file

@ -0,0 +1,134 @@
import * as path from "path"
import * as fs from "fs"
import yaml from "js-yaml"
import semver from "semver"
import {DiagnosticPrinter, E_FIELD_MUST_BE_BOOLEAN, E_FIELD_NOT_PRESENT, E_FIELD_MUST_BE_STRING, E_FIELD_HAS_INVALID_VERSION_NUMBER} from "./diagnostics";
import {hasOwnProperty, FastStringMap} from "./util";
import {isString} from "util"
import { SourceFile } from "./ast";
let nextPackageId = 1;
export class Package {
public id = nextPackageId++;
private sourceFilesByPath = new FastStringMap<string, SourceFile>();
constructor(
public rootDir: string,
public name: string | null,
public version: string | null,
sourceFiles: SourceFile[],
public isAutoImported: boolean,
public isDependency: boolean,
) {
for (const sourceFile of sourceFiles) {
this.addSourceFile(sourceFile);
}
}
public getAllSourceFiles(): IterableIterator<SourceFile> {
return this.sourceFilesByPath.values();
}
public getMainLibrarySourceFile(): SourceFile | null {
const fullPath = path.resolve(this.rootDir, 'lib.bolt');
if (!this.sourceFilesByPath.has(fullPath)) {
return null;
}
return this.sourceFilesByPath.get(fullPath)
}
public addSourceFile(sourceFile: SourceFile) {
this.sourceFilesByPath.set(sourceFile.span!.file.fullPath, sourceFile);
}
}
export function loadPackageMetadata(diagnostics: DiagnosticPrinter, rootDir: string) {
let name = null
let version = null;
let autoImport = false;
let hasVersionErrors = false;
let hasNameErrors = false;
const filepath = path.join(rootDir, 'Boltfile');
if (fs.existsSync(filepath)) {
const data = yaml.safeLoad(fs.readFileSync(filepath, 'utf8'));
if (data !== undefined) {
if (hasOwnProperty(data, 'name')) {
if (!isString(data.name)) {
diagnostics.add({
message: E_FIELD_MUST_BE_STRING,
severity: 'error',
args: { name: 'name' },
});
hasNameErrors = true;
} else {
name = data.name;
}
}
if (hasOwnProperty(data, 'version')) {
if (!isString(data.version)) {
diagnostics.add({
message: E_FIELD_MUST_BE_STRING,
args: { name: 'version' },
severity: 'error',
});
hasVersionErrors = true;
} else {
if (!semver.valid(data.version)) {
diagnostics.add({
message: E_FIELD_HAS_INVALID_VERSION_NUMBER,
args: { name: 'version' },
severity: 'error',
});
hasVersionErrors = true;
} else {
version = data.version;
}
}
}
if (hasOwnProperty(data, 'auto-import')) {
if (typeof(data['auto-import']) !== 'boolean') {
diagnostics.add({
message: E_FIELD_MUST_BE_BOOLEAN,
args: { name: 'auto-import' },
severity: 'error',
})
} else {
autoImport = data['auto-import'];
}
}
}
}
if (name === null && !hasNameErrors) {
diagnostics.add({
message: E_FIELD_NOT_PRESENT,
severity: 'warning',
args: { name: 'name' },
});
}
if (version === null && !hasVersionErrors) {
diagnostics.add({
message: E_FIELD_NOT_PRESENT,
severity: 'warning',
args: { name: 'version' },
});
}
return {
name,
version,
autoImport,
};
}

View file

@ -13,7 +13,6 @@ import {
BoltParameter,
BoltSourceElement,
createBoltQualName,
BoltQualName,
BoltPattern,
createBoltBindPattern,
BoltImportDirective,
@ -28,7 +27,6 @@ import {
createBoltImportDirective,
BoltModifiers,
BoltStringLiteral,
BoltImportSymbol,
BoltExpressionStatement,
createBoltExpressionStatement,
BoltVariableDeclaration,
@ -53,7 +51,6 @@ import {
BoltSourceFile,
BoltFunctionBodyElement,
createBoltSourceFile,
setParents,
BoltMatchExpression,
createBoltMatchArm,
BoltMatchArm,
@ -79,6 +76,9 @@ import {
createBoltMemberExpression,
BoltDeclarationLike,
BoltTraitOrImplElement,
BoltQualName,
BoltLoopStatement,
createBoltLoopStatement,
} from "./ast"
import { parseForeignLanguage } from "./foreign"
@ -90,10 +90,14 @@ import {
ParseError,
setOrigNodeRange,
createTokenStream,
Package,
} from "./common"
import { Stream, uniq, assert } from "./util"
import { Scanner } from "./scanner"
import { TextSpan, TextPos } from "./text"
import {JSScanner} from "./foreign/js/scanner";
import { Package } from "./package"
export type BoltTokenStream = Stream<BoltToken>;
export function isModifierKeyword(kind: SyntaxKind) {
@ -201,40 +205,40 @@ export class Parser {
return (this as any)['parse' + kindToString(kind).substring('Bolt'.length)](tokens);
}
public parseModulePath(tokens: BoltTokenStream): BoltModulePath | null {
// private parseModulePath(tokens: BoltTokenStream): BoltModulePath | null {
let firstToken = tokens.peek();;
let lastToken: Token;
let isAbsolute = false;
let elements = [];
// let firstToken = tokens.peek();;
// let lastToken: Token;
// let isAbsolute = false;
// let elements = [];
const t0 = tokens.peek();
if (t0.kind === SyntaxKind.BoltColonColon) {
isAbsolute = true;
tokens.get();
lastToken = t0;
}
// const t0 = tokens.peek();
// if (t0.kind === SyntaxKind.BoltColonColon) {
// isAbsolute = true;
// tokens.get();
// lastToken = t0;
// }
if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) {
while (true) {
const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltIdentifier);
elements.push(t1 as BoltIdentifier)
const t2 = tokens.get();
if (tokens.peek(2).kind !== SyntaxKind.BoltColonColon) {
lastToken = t2;
break;
}
}
}
// if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) {
// while (true) {
// const t1 = tokens.get();
// assertToken(t1, SyntaxKind.BoltIdentifier);
// elements.push(t1 as BoltIdentifier)
// const t2 = tokens.get();
// if (tokens.peek(2).kind !== SyntaxKind.BoltColonColon) {
// lastToken = t2;
// break;
// }
// }
// }
if (!isAbsolute && elements.length === 0) {
return null;
}
const result = createBoltModulePath(isAbsolute, elements);
setOrigNodeRange(result, firstToken, lastToken!);
return result;
}
// if (!isAbsolute && elements.length === 0) {
// return null;
// }
// const result = createBoltModulePath(isAbsolute, elements);
// setOrigNodeRange(result, firstToken, lastToken!);
// return result;
// }
public parseQualName(tokens: BoltTokenStream): BoltQualName {
@ -838,10 +842,22 @@ export class Parser {
return node;
}
public parseLoopStatement(tokens: BoltTokenStream): BoltLoopStatement {
const t0 = tokens.get();
assertToken(t0, SyntaxKind.BoltLoopKeyword);
const t1 = tokens.get();
assertToken(t1, SyntaxKind.BoltBraced);
const innerTokens = createTokenStream(t1);
const elements = this.parseFunctionBodyElements(innerTokens);
const result = createBoltLoopStatement(elements);
setOrigNodeRange(result, t0, t1);
return result;
}
public parseStatement(tokens: BoltTokenStream): BoltStatement {
if (this.lookaheadIsMacroCall(tokens)) {
return this.parseMacroCall(tokens);
}
// if (this.lookaheadIsMacroCall(tokens)) {
// return this.parseMacroCall(tokens);
// }
const t0 = tokens.peek();
if (KIND_EXPRESSION_T0.indexOf(t0.kind) !== -1) {
return this.parseExpressionStatement(tokens);
@ -1196,7 +1212,7 @@ export class Parser {
tokens.get();
switch (target) {
case "Bolt":
body = this.parseStatements(createTokenStream(t3));
body = this.parseFunctionBodyElement(createTokenStream(t3));
break;
default:
body = parseForeignLanguage(target, t3.text, t3.span!.file, t3.span!.start);
@ -1335,10 +1351,10 @@ export class Parser {
// Parse all 'fn ...' and 'type ...' elements
const t5 = tokens.get();
assertToken(t5, SyntaxKind.BoltBraced);
const elements = this.parseSourceElements(createTokenStream(t5));
const elements = this.parseTraitOrImplElements(createTokenStream(t5));
// Create and return the result
const result = createBoltImplDeclaration(modifiers, typeParams, name, traitTypeExpr, elements as BoltDeclaration[]);
const result = createBoltImplDeclaration(modifiers, typeParams, name, traitTypeExpr, elements);
setOrigNodeRange(result, firstToken, t5);
return result;
}
@ -1577,6 +1593,13 @@ export class Parser {
return this.canParseExpression(tokens);
}
private canParseLoopStatement(tokens: BoltTokenStream): boolean {
if (tokens.peek(1).kind !== SyntaxKind.BoltLoopKeyword) {
return false;
}
return true;
}
private canParseStatement(tokens: BoltTokenStream): boolean {
const t0 = tokens.peek();
switch (t0.kind) {
@ -1689,22 +1712,3 @@ export class Parser {
}
}
import { Scanner } from "./scanner"
import { TextFile, TextSpan, TextPos } from "./text"
import * as fs from "fs"
import {JSScanner} from "./foreign/js/scanner";
import {emitNode} from "./emitter";
import { timingSafeEqual } from "crypto";
import { isatty } from "tty";
export function parseSourceFile(filepath: string, pkg: Package): BoltSourceFile {
const file = new TextFile(filepath);
const contents = fs.readFileSync(file.origPath, 'utf8');
const scanner = new Scanner(file, contents)
const parser = new Parser();
const sourceFile = parser.parseSourceFile(scanner, pkg);
setParents(sourceFile);
return sourceFile;
}