Update frontend and integration with CLI
This commit is contained in:
parent
a140ffac9d
commit
518631f3b8
6 changed files with 430 additions and 315 deletions
261
src/bin/bolt.ts
261
src/bin/bolt.ts
|
@ -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,24 +88,28 @@ 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);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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')
|
||||
|
|
142
src/frontend.ts
142
src/frontend.ts
|
@ -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
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,
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue