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 "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,159 +16,15 @@ 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);
}
} }

View file

@ -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:

View file

@ -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')

View file

@ -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;
}
}
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) { 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
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, 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;
}