Update frontend and integration with CLI
This commit is contained in:
parent
a140ffac9d
commit
518631f3b8
6 changed files with 430 additions and 315 deletions
251
src/bin/bolt.ts
251
src/bin/bolt.ts
|
@ -2,24 +2,10 @@
|
||||||
import "reflect-metadata"
|
import "reflect-metadata"
|
||||||
import "source-map-support/register"
|
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 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 { Frontend } from "../frontend"
|
||||||
import { Package } from "../common"
|
import { expandPath } from "../util"
|
||||||
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 }))
|
|
||||||
//}
|
|
||||||
|
|
||||||
const BOLT_HOME = expandPath(process.env['BOLT_HOME'] ?? '~/.bolt-compiler')
|
const BOLT_HOME = expandPath(process.env['BOLT_HOME'] ?? '~/.bolt-compiler')
|
||||||
|
|
||||||
|
@ -30,160 +16,16 @@ function toArray<T>(value: T | T[]): T[] {
|
||||||
return value === null || value === undefined ? [] : [value]
|
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) {
|
function error(message: string) {
|
||||||
console.error(`Error: ${message}`);
|
console.error(`Error: ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPackagesAndSourceFiles(filenames: string[], pkgResolver: PackageResolver, cwd = '.', useStd: boolean): Package[] {
|
function parsePackageResolverFlags(frontend: Frontend, flags: string[]) {
|
||||||
cwd = path.resolve(cwd);
|
for (const flag of flags) {
|
||||||
const anonPkg = new Package(cwd, null, null, [], false, false);
|
const [pkgName, pkgPath] = flag.split(':');
|
||||||
const pkgs = [ anonPkg ];
|
frontend.mapPackageNameToPath(pkgName, pkgPath)
|
||||||
for (const filename of filenames) {
|
|
||||||
if (fs.statSync(filename).isDirectory()) {
|
|
||||||
pkgs.push(loadPackageFromPath(filename, false));
|
|
||||||
} else {
|
|
||||||
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
yargs
|
||||||
|
|
||||||
|
@ -215,24 +57,16 @@ yargs
|
||||||
args => {
|
args => {
|
||||||
const useStd = args['std'] as boolean ?? true;
|
const useStd = args['std'] as boolean ?? true;
|
||||||
const cwd = process.cwd();
|
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);
|
const files = toArray(args.files as string[] | string);
|
||||||
if (files.length === 0) {
|
const frontend = new Frontend();
|
||||||
const metadataPath = upsearchSync('Boltfile');
|
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
|
||||||
if (metadataPath === null) {
|
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
|
||||||
error(`No source files specified on the command-line and no Boltfile found in ${cwd} or any of its parent directories.`)
|
if (program !== null) {
|
||||||
|
frontend.check(program);
|
||||||
|
}
|
||||||
|
if (frontend.diagnostics.hasErrors) {
|
||||||
process.exit(1);
|
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);
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -254,24 +88,28 @@ yargs
|
||||||
|
|
||||||
, args => {
|
, args => {
|
||||||
|
|
||||||
|
const force = args.force as boolean;
|
||||||
|
const useStd = args['std'] as boolean ?? true;
|
||||||
const cwd = process.cwd();
|
const cwd = process.cwd();
|
||||||
const files = toArray(args.files as string[] | string);
|
const files = toArray(args.files as string[] | string);
|
||||||
if (files.length === 0) {
|
|
||||||
const metadataPath = upsearchSync('Boltfile');
|
const frontend = new Frontend();
|
||||||
if (metadataPath === null) {
|
|
||||||
error(`No source files specified on the command-line and no Boltfile found in ${cwd} or any of its parent directories.`)
|
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
|
||||||
|
|
||||||
|
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
|
||||||
|
|
||||||
|
if (program === null && !force) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
files.push(metadataPath);
|
|
||||||
}
|
if (program !== null) {
|
||||||
const pkgs = loadPackagesAndSourceFiles(files);
|
|
||||||
const program = new Program(pkgs);
|
|
||||||
const frontend = new Frontend();
|
|
||||||
frontend.check(program);
|
frontend.check(program);
|
||||||
if (frontend.diagnostics.hasErrors && !args.force) {
|
if (frontend.diagnostics.hasErrors && !force) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
frontend.compile(program, args.target);
|
frontend.compile(program, args.target);
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -283,19 +121,42 @@ yargs
|
||||||
yargs => yargs
|
yargs => yargs
|
||||||
.string('work-dir')
|
.string('work-dir')
|
||||||
.describe('work-dir', 'The working directory where files will be resolved against.')
|
.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) {
|
const frontend = new Frontend();
|
||||||
throw new Error(`Executing packages is not yet supported.`)
|
|
||||||
|
parsePackageResolverFlags(frontend, toArray(args.pkg as string | string[]));
|
||||||
|
|
||||||
|
const program = frontend.loadProgramFromFileList(files, cwd, useStd);
|
||||||
|
|
||||||
|
if (program === null && !force) {
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const program = new Program(sourceFiles);
|
if (program !== null) {
|
||||||
const frontend = new Frontend();
|
if (runTypeChecker) {
|
||||||
|
frontend.check(program);
|
||||||
|
}
|
||||||
|
if (frontend.diagnostics.hasErrors && !force) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
frontend.eval(program);
|
frontend.eval(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,12 @@ import {
|
||||||
BoltSourceFile,
|
BoltSourceFile,
|
||||||
isSourceFile,
|
isSourceFile,
|
||||||
BoltSyntax,
|
BoltSyntax,
|
||||||
BoltModifiers
|
BoltModifiers,
|
||||||
|
ReturnStatement,
|
||||||
|
FunctionBodyElement
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
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 {TextSpan, TextPos, TextFile} from "./text";
|
||||||
import {Scanner} from "./scanner";
|
import {Scanner} from "./scanner";
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
|
@ -35,46 +37,7 @@ export function getSourceFile(node: Syntax) {
|
||||||
export function getPackage(node: Syntax) {
|
export function getPackage(node: Syntax) {
|
||||||
const sourceFile = getSourceFile(node);
|
const sourceFile = getSourceFile(node);
|
||||||
assert(sourceFile.kind === SyntaxKind.BoltSourceFile);
|
assert(sourceFile.kind === SyntaxKind.BoltSourceFile);
|
||||||
return (sourceFile as BoltSourceFile).package;
|
return (sourceFile as BoltSourceFile).pkg;
|
||||||
}
|
|
||||||
|
|
||||||
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 getNodeLanguage(node: Syntax): string {
|
export function getNodeLanguage(node: Syntax): string {
|
||||||
|
@ -175,7 +138,7 @@ export function isRightAssoc(kind: OperatorKind) {
|
||||||
|
|
||||||
export class ParseError extends Error {
|
export class ParseError extends Error {
|
||||||
constructor(public actual: Syntax, public expected: SyntaxKind[]) {
|
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 = [];
|
let elements = [];
|
||||||
while (true) {
|
while (true) {
|
||||||
if (node.kind === SyntaxKind.BoltModule) {
|
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) {
|
for (const element of body) {
|
||||||
switch (element.kind) {
|
switch (element.kind) {
|
||||||
case SyntaxKind.BoltReturnStatement:
|
case SyntaxKind.BoltReturnStatement:
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
|
|
||||||
|
import * as fs from "fs"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import { Syntax } from "./ast";
|
import { Syntax } from "./ast";
|
||||||
import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint} from "./util";
|
import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint, assert} from "./util";
|
||||||
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants";
|
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_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_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.";
|
export const E_FILE_NOT_FOUND = "A file named {filename} was not found.";
|
||||||
|
@ -41,6 +47,8 @@ export interface Diagnostic {
|
||||||
args?: MapLike<FormatArg>;
|
args?: MapLike<FormatArg>;
|
||||||
node?: Syntax;
|
node?: Syntax;
|
||||||
nested?: Diagnostic[];
|
nested?: Diagnostic[];
|
||||||
|
position?: TextPos,
|
||||||
|
file?: TextFile,
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstIndexOfNonEmpty(str: string) {
|
function firstIndexOfNonEmpty(str: string) {
|
||||||
|
@ -54,6 +62,18 @@ function firstIndexOfNonEmpty(str: string) {
|
||||||
return j
|
return j
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DiagnosticWriter {
|
||||||
|
|
||||||
|
constructor(private fd: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(diagnostic: Diagnostic) {
|
||||||
|
fs.writeSync(this.fd, JSON.stringify(diagnostic) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export class DiagnosticPrinter {
|
export class DiagnosticPrinter {
|
||||||
|
|
||||||
public hasErrors = false
|
public hasErrors = false
|
||||||
|
@ -106,9 +126,16 @@ export class DiagnosticPrinter {
|
||||||
out += diagnostic.message + '\n';
|
out += diagnostic.message + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let span = null;
|
||||||
if (diagnostic.node !== undefined) {
|
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'
|
out += '\n'
|
||||||
const span = diagnostic.node.span!;
|
|
||||||
const content = span.file.getText();
|
const content = span.file.getText();
|
||||||
const startLine = Math.max(0, span.start.line-1-BOLT_DIAG_NUM_EXTRA_LINES)
|
const startLine = Math.max(0, span.start.line-1-BOLT_DIAG_NUM_EXTRA_LINES)
|
||||||
const lines = content.split('\n')
|
const lines = content.split('\n')
|
||||||
|
|
142
src/frontend.ts
142
src/frontend.ts
|
@ -1,26 +1,30 @@
|
||||||
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as fs from "fs-extra"
|
import * as fs from "fs-extra"
|
||||||
import { now } from "microtime"
|
|
||||||
import { EventEmitter } from "events"
|
import { EventEmitter } from "events"
|
||||||
|
import { sync as globSync } from "glob"
|
||||||
|
|
||||||
import { Program } from "./program"
|
import { Program } from "./program"
|
||||||
import { emitNode } from "./emitter"
|
import { emitNode } from "./emitter"
|
||||||
import { Syntax, BoltSourceFile, SourceFile, NodeVisitor, createBoltConditionalCase } from "./ast"
|
import { Syntax, BoltSourceFile, SourceFile, NodeVisitor, createBoltConditionalCase, setParents, kindToString } from "./ast"
|
||||||
import { getFileStem, MapLike } from "./util"
|
import { getFileStem, MapLike, assert, FastStringMap, upsearchSync } from "./util"
|
||||||
import { verbose, memoize } from "./util"
|
import { verbose, memoize } from "./util"
|
||||||
import { Container, Newable } from "./ioc"
|
import { Container, Newable } from "./ioc"
|
||||||
import ExpandBoltTransform from "./transforms/expand"
|
import ExpandBoltTransform from "./transforms/expand"
|
||||||
import CompileBoltToJSTransform from "./transforms/boltToJS"
|
import CompileBoltToJSTransform from "./transforms/boltToJS"
|
||||||
import ConstFoldTransform from "./transforms/constFold"
|
import ConstFoldTransform from "./transforms/constFold"
|
||||||
import { TransformManager } from "./transforms/index"
|
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 { TypeChecker } from "./types"
|
||||||
import { checkServerIdentity } from "tls"
|
|
||||||
import { CheckInvalidFilePaths, CheckTypeAssignments, CheckReferences } from "./checks"
|
import { CheckInvalidFilePaths, CheckTypeAssignments, CheckReferences } from "./checks"
|
||||||
import { SymbolResolver, BoltSymbolResolutionStrategy } from "./resolver"
|
import { SymbolResolver, BoltSymbolResolutionStrategy } from "./resolver"
|
||||||
import { Evaluator } from "./evaluator"
|
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> = {
|
const targetExtensions: MapLike<string> = {
|
||||||
'JS': '.mjs',
|
'JS': '.mjs',
|
||||||
|
@ -68,11 +72,17 @@ export class Frontend {
|
||||||
public diagnostics: DiagnosticPrinter;
|
public diagnostics: DiagnosticPrinter;
|
||||||
public timing: Timing;
|
public timing: Timing;
|
||||||
|
|
||||||
|
private packagePathOverrides = new FastStringMap<string, string>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.diagnostics = new DiagnosticPrinter();
|
this.diagnostics = new DiagnosticPrinter();
|
||||||
this.timing = new Timing();
|
this.timing = new Timing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public mapPackageNameToPath(pkgName: string, pkgPath: string): void {
|
||||||
|
this.packagePathOverrides.set(pkgName, pkgPath);
|
||||||
|
}
|
||||||
|
|
||||||
public check(program: Program) {
|
public check(program: Program) {
|
||||||
|
|
||||||
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
|
const resolver = new SymbolResolver(program, new BoltSymbolResolutionStrategy);
|
||||||
|
@ -142,7 +152,7 @@ export class Frontend {
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapToTargetFile(node: SourceFile) {
|
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) {
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultExtension(target: string) {
|
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 getDefaultFileExtension(target: string) {
|
||||||
if (targetExtensions[target] === undefined) {
|
if (targetExtensions[target] === undefined) {
|
||||||
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
|
throw new Error(`Could not derive an appropriate extension for target "${target}".`)
|
||||||
}
|
}
|
||||||
|
|
134
src/package.ts
Normal file
134
src/package.ts
Normal 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
122
src/parser.ts
122
src/parser.ts
|
@ -13,7 +13,6 @@ import {
|
||||||
BoltParameter,
|
BoltParameter,
|
||||||
BoltSourceElement,
|
BoltSourceElement,
|
||||||
createBoltQualName,
|
createBoltQualName,
|
||||||
BoltQualName,
|
|
||||||
BoltPattern,
|
BoltPattern,
|
||||||
createBoltBindPattern,
|
createBoltBindPattern,
|
||||||
BoltImportDirective,
|
BoltImportDirective,
|
||||||
|
@ -28,7 +27,6 @@ import {
|
||||||
createBoltImportDirective,
|
createBoltImportDirective,
|
||||||
BoltModifiers,
|
BoltModifiers,
|
||||||
BoltStringLiteral,
|
BoltStringLiteral,
|
||||||
BoltImportSymbol,
|
|
||||||
BoltExpressionStatement,
|
BoltExpressionStatement,
|
||||||
createBoltExpressionStatement,
|
createBoltExpressionStatement,
|
||||||
BoltVariableDeclaration,
|
BoltVariableDeclaration,
|
||||||
|
@ -53,7 +51,6 @@ import {
|
||||||
BoltSourceFile,
|
BoltSourceFile,
|
||||||
BoltFunctionBodyElement,
|
BoltFunctionBodyElement,
|
||||||
createBoltSourceFile,
|
createBoltSourceFile,
|
||||||
setParents,
|
|
||||||
BoltMatchExpression,
|
BoltMatchExpression,
|
||||||
createBoltMatchArm,
|
createBoltMatchArm,
|
||||||
BoltMatchArm,
|
BoltMatchArm,
|
||||||
|
@ -79,6 +76,9 @@ import {
|
||||||
createBoltMemberExpression,
|
createBoltMemberExpression,
|
||||||
BoltDeclarationLike,
|
BoltDeclarationLike,
|
||||||
BoltTraitOrImplElement,
|
BoltTraitOrImplElement,
|
||||||
|
BoltQualName,
|
||||||
|
BoltLoopStatement,
|
||||||
|
createBoltLoopStatement,
|
||||||
} from "./ast"
|
} from "./ast"
|
||||||
|
|
||||||
import { parseForeignLanguage } from "./foreign"
|
import { parseForeignLanguage } from "./foreign"
|
||||||
|
@ -90,10 +90,14 @@ import {
|
||||||
ParseError,
|
ParseError,
|
||||||
setOrigNodeRange,
|
setOrigNodeRange,
|
||||||
createTokenStream,
|
createTokenStream,
|
||||||
Package,
|
|
||||||
} from "./common"
|
} from "./common"
|
||||||
import { Stream, uniq, assert } from "./util"
|
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 type BoltTokenStream = Stream<BoltToken>;
|
||||||
|
|
||||||
export function isModifierKeyword(kind: SyntaxKind) {
|
export function isModifierKeyword(kind: SyntaxKind) {
|
||||||
|
@ -201,40 +205,40 @@ export class Parser {
|
||||||
return (this as any)['parse' + kindToString(kind).substring('Bolt'.length)](tokens);
|
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 firstToken = tokens.peek();;
|
||||||
let lastToken: Token;
|
// let lastToken: Token;
|
||||||
let isAbsolute = false;
|
// let isAbsolute = false;
|
||||||
let elements = [];
|
// let elements = [];
|
||||||
|
|
||||||
const t0 = tokens.peek();
|
// const t0 = tokens.peek();
|
||||||
if (t0.kind === SyntaxKind.BoltColonColon) {
|
// if (t0.kind === SyntaxKind.BoltColonColon) {
|
||||||
isAbsolute = true;
|
// isAbsolute = true;
|
||||||
tokens.get();
|
// tokens.get();
|
||||||
lastToken = t0;
|
// lastToken = t0;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) {
|
// if (tokens.peek(2).kind === SyntaxKind.BoltColonColon) {
|
||||||
while (true) {
|
// while (true) {
|
||||||
const t1 = tokens.get();
|
// const t1 = tokens.get();
|
||||||
assertToken(t1, SyntaxKind.BoltIdentifier);
|
// assertToken(t1, SyntaxKind.BoltIdentifier);
|
||||||
elements.push(t1 as BoltIdentifier)
|
// elements.push(t1 as BoltIdentifier)
|
||||||
const t2 = tokens.get();
|
// const t2 = tokens.get();
|
||||||
if (tokens.peek(2).kind !== SyntaxKind.BoltColonColon) {
|
// if (tokens.peek(2).kind !== SyntaxKind.BoltColonColon) {
|
||||||
lastToken = t2;
|
// lastToken = t2;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!isAbsolute && elements.length === 0) {
|
// if (!isAbsolute && elements.length === 0) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
const result = createBoltModulePath(isAbsolute, elements);
|
// const result = createBoltModulePath(isAbsolute, elements);
|
||||||
setOrigNodeRange(result, firstToken, lastToken!);
|
// setOrigNodeRange(result, firstToken, lastToken!);
|
||||||
return result;
|
// return result;
|
||||||
}
|
// }
|
||||||
|
|
||||||
public parseQualName(tokens: BoltTokenStream): BoltQualName {
|
public parseQualName(tokens: BoltTokenStream): BoltQualName {
|
||||||
|
|
||||||
|
@ -838,10 +842,22 @@ export class Parser {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseStatement(tokens: BoltTokenStream): BoltStatement {
|
public parseLoopStatement(tokens: BoltTokenStream): BoltLoopStatement {
|
||||||
if (this.lookaheadIsMacroCall(tokens)) {
|
const t0 = tokens.get();
|
||||||
return this.parseMacroCall(tokens);
|
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);
|
||||||
|
// }
|
||||||
const t0 = tokens.peek();
|
const t0 = tokens.peek();
|
||||||
if (KIND_EXPRESSION_T0.indexOf(t0.kind) !== -1) {
|
if (KIND_EXPRESSION_T0.indexOf(t0.kind) !== -1) {
|
||||||
return this.parseExpressionStatement(tokens);
|
return this.parseExpressionStatement(tokens);
|
||||||
|
@ -1196,7 +1212,7 @@ export class Parser {
|
||||||
tokens.get();
|
tokens.get();
|
||||||
switch (target) {
|
switch (target) {
|
||||||
case "Bolt":
|
case "Bolt":
|
||||||
body = this.parseStatements(createTokenStream(t3));
|
body = this.parseFunctionBodyElement(createTokenStream(t3));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
body = parseForeignLanguage(target, t3.text, t3.span!.file, t3.span!.start);
|
body = parseForeignLanguage(target, t3.text, t3.span!.file, t3.span!.start);
|
||||||
|
@ -1335,10 +1351,10 @@ export class Parser {
|
||||||
// Parse all 'fn ...' and 'type ...' elements
|
// Parse all 'fn ...' and 'type ...' elements
|
||||||
const t5 = tokens.get();
|
const t5 = tokens.get();
|
||||||
assertToken(t5, SyntaxKind.BoltBraced);
|
assertToken(t5, SyntaxKind.BoltBraced);
|
||||||
const elements = this.parseSourceElements(createTokenStream(t5));
|
const elements = this.parseTraitOrImplElements(createTokenStream(t5));
|
||||||
|
|
||||||
// Create and return the result
|
// 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);
|
setOrigNodeRange(result, firstToken, t5);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1577,6 +1593,13 @@ export class Parser {
|
||||||
return this.canParseExpression(tokens);
|
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 {
|
private canParseStatement(tokens: BoltTokenStream): boolean {
|
||||||
const t0 = tokens.peek();
|
const t0 = tokens.peek();
|
||||||
switch (t0.kind) {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue