Small fixes and major enhancements to TypeChecker

This commit is contained in:
Sam Vervaeck 2020-05-28 14:08:49 +02:00
parent ce8c0aa7a1
commit 884be8f9ec
23 changed files with 1614 additions and 635 deletions

View file

@ -1,5 +1,5 @@
TREEGEN_FILES = src/ast-spec.txt lib/bin/bolt-treegen.js lib/treegen/parser.js lib/treegen/index.js lib/treegen/util.js src/treegen/ast-template.js
TREEGEN_FILES = src/treegen/ast.dts.template src/ast-spec.txt lib/bin/bolt-treegen.js lib/treegen/parser.js lib/treegen/index.js lib/treegen/util.js src/treegen/ast-template.js
all: lib/ast.js
bolt check stdlib

View file

@ -1,18 +1,14 @@
@language Bolt;
@language JS;
node EndOfFile > BoltToken, JSToken;
node Token;
node SourceFile;
node FunctionBody;
// Bolt language AST definitions
type BoltValue = Integer | bool | String;
node BoltSyntax;
node FunctionBody;
node BoltToken > Token;
node BoltToken > Token, BoltSyntax;
node BoltStringLiteral > BoltToken {
value: String,
@ -85,23 +81,22 @@ node BoltParenthesized > BoltPunctuated;
node BoltBraced > BoltPunctuated;
node BoltBracketed > BoltPunctuated;
node BoltSourceFile > SourceFile {
node BoltSourceFile > BoltSyntax, SourceFile {
elements: Vec<BoltSourceElement>,
package: Package,
}
node BoltQualName {
node BoltQualName > BoltSyntax {
isAbsolute: bool,
modulePath: Vec<BoltIdentifier>,
name: BoltSymbol,
}
node BoltModulePath {
isAbsolute: bool,
elements: Vec<BoltIdentifier>,
}
node BoltTypeExpression > BoltSyntax;
node BoltTypeExpression;
node BoltTypeOfExpression > BoltTypeExpression {
expression: BoltExpression,
}
node BoltReferenceTypeExpression > BoltTypeExpression {
name: BoltQualName,
@ -117,14 +112,14 @@ node BoltLiftedTypeExpression > BoltTypeExpression {
expression: BoltExpression,
}
node BoltTypeParameter {
node BoltTypeParameter > BoltSyntax {
index: usize,
name: BoltIdentifier,
typeExpr: Option<BoltTypeExpression>,
defaultType: Option<BoltTypeExpression>,
}
node BoltPattern;
node BoltPattern > BoltSyntax;
node BoltBindPattern > BoltPattern {
name: BoltIdentifier,
@ -139,7 +134,7 @@ node BoltExpressionPattern > BoltPattern {
expression: BoltExpression,
}
node BoltTuplePatternElement {
node BoltTuplePatternElement > BoltSyntax {
index: usize,
pattern: BoltPattern,
}
@ -148,7 +143,7 @@ node BoltTuplePattern > BoltPattern {
elements: Vec<BoltTuplePatternElement>,
}
node BoltRecordFieldPattern {
node BoltRecordFieldPattern > BoltSyntax {
isRest: bool,
name: Option<BoltIdentifier>,
pattern: Option<BoltPattern>,
@ -159,7 +154,7 @@ node BoltRecordPattern > BoltPattern {
fields: Vec<BoltRecordFieldPattern>,
}
node BoltExpression;
node BoltExpression > BoltSyntax;
node BoltQuoteExpression > BoltExpression {
tokens: Vec<Token | BoltExpression>,
@ -193,7 +188,7 @@ node BoltYieldExpression > BoltExpression {
value: BoltExpression,
}
node BoltMatchArm {
node BoltMatchArm > BoltSyntax {
pattern: BoltPattern,
body: BoltExpression,
}
@ -203,7 +198,7 @@ node BoltMatchExpression > BoltExpression {
arms: Vec<BoltMatchArm>,
}
node BoltCase {
node BoltCase > BoltSyntax {
test: BoltExpression,
result: BoltExpression,
}
@ -220,13 +215,13 @@ node BoltConstantExpression > BoltExpression {
value: BoltValue,
}
node BoltStatement > BoltFunctionBodyElement, BoltSourceElement;
node BoltStatement > BoltSyntax, BoltFunctionBodyElement, BoltSourceElement;
node BoltReturnStatement > BoltStatement {
value: Option<BoltExpression>,
}
node BoltConditionalCase {
node BoltConditionalCase > BoltSyntax {
test: Option<BoltExpression>,
body: Vec<BoltFunctionBodyElement>,
}
@ -243,31 +238,33 @@ node BoltExpressionStatement > BoltStatement {
expression: BoltExpression,
}
node BoltParameter {
node BoltParameter > BoltSyntax {
index: usize,
bindings: BoltPattern,
typeExpr: Option<BoltTypeExpression>,
defaultValue: Option<BoltExpression>,
}
node BoltDeclaration > BoltSourceElement;
node BoltDeclaration > BoltSyntax, BoltSourceElement;
node BoltTypeDeclaration > BoltSourceElement;
node BoltTypeDeclaration > BoltSyntax, BoltSourceElement;
enum BoltModifiers {
IsMutable = 0x1,
IsPublic = 0x2,
}
node BoltModule > BoltSourceElement {
node BoltModule > BoltSyntax, BoltSourceElement {
modifiers: BoltModifiers,
name: Vec<BoltIdentifier>,
elements: Vec<BoltSourceElement>,
}
node BoltDeclarationLike;
node BoltFunctionBodyElement;
node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration {
node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration, BoltDeclarationLike {
modifiers: BoltModifiers,
target: String,
name: BoltSymbol,
@ -277,14 +274,14 @@ node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration {
body: Vec<BoltFunctionBodyElement>,
}
node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration {
node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration, BoltDeclarationLike {
modifiers: BoltModifiers,
bindings: BoltPattern,
typeExpr: Option<BoltTypeExpression>,
value: Option<BoltExpression>,
}
node BoltImportSymbol;
node BoltImportSymbol > BoltSyntax;
node BoltPlainImportSymbol > BoltImportSymbol {
remote: BoltQualName,
@ -294,10 +291,10 @@ node BoltPlainImportSymbol > BoltImportSymbol {
node BoltImportDirective > BoltSourceElement {
modifiers: BoltModifiers,
file: BoltStringLiteral,
symbols: Vec<BoltImportSymbol>,
symbols: Option<Vec<BoltImportSymbol>>,
}
node BoltExportSymbol;
node BoltExportSymbol > BoltSyntax;
node BoltPlainExportSymbol {
local: BoltQualName,
@ -309,14 +306,14 @@ node BoltExportDirective > BoltSourceElement {
symbols: Option<Vec<BoltExportSymbol>>,
}
node BoltTraitDeclaration > BoltDeclaration, BoltTypeDeclaration {
node BoltTraitDeclaration > BoltDeclarationLike, BoltTypeDeclaration {
modifiers: BoltModifiers,
name: BoltIdentifier,
typeParams: Option<Vec<BoltTypeParameter>>,
elements: Vec<BoltDeclaration>,
}
node BoltImplDeclaration > BoltDeclaration {
node BoltImplDeclaration > BoltTypeDeclaration, BoltDeclarationLike {
modifiers: BoltModifiers,
name: BoltIdentifier,
trait: BoltTypeExpression,
@ -324,21 +321,21 @@ node BoltImplDeclaration > BoltDeclaration {
elements: Vec<BoltDeclaration>,
}
node BoltTypeAliasDeclaration > BoltDeclaration, BoltTypeDeclaration {
node BoltTypeAliasDeclaration > BoltDeclarationLike, BoltTypeDeclaration {
modifiers: BoltModifiers,
name: BoltIdentifier,
typeParams: Option<Vec<BoltTypeParameter>>,
typeExpr: BoltTypeExpression,
}
node BoltRecordMember;
node BoltRecordMember > BoltSyntax;
node BoltRecordField > BoltRecordMember {
name: BoltIdentifier,
typeExpr: BoltTypeExpression,
}
node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration {
node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration, BoltDeclarationLike {
modifiers: BoltModifiers,
name: BoltIdentifier,
typeParms: Option<Vec<BoltTypeParameter>>,
@ -354,9 +351,9 @@ node BoltMacroCall > BoltRecordMember, BoltStatement, BoltDeclaration, BoltExpre
// JavaScript AST definitions
type JSValue = Int | String | Bool | Void;
node JSSyntax;
node JSToken > Token;
node JSToken > JSSyntax, Token;
node JSOperator > JSToken {
text: String,
@ -388,6 +385,8 @@ node JSFunctionKeyword > JSToken;
node JSWhileKeyword > JSToken;
node JSForKeyword > JSToken;
node JSOperator;
node JSCloseBrace > JSToken;
node JSCloseBracket > JSToken;
node JSCloseParen > JSToken;
@ -398,25 +397,25 @@ node JSSemi > JSToken;
node JSComma > JSToken;
node JSDot > JSToken;
node JSDotDotDot > JSToken;
node JSMulOp > JSToken;
node JSAddOp > JSToken;
node JSDivOp > JSToken;
node JSSubOp > JSToken;
node JSLtOp > JSToken;
node JSGtOp > JSToken;
node JSBOrOp > JSToken;
node JSBXorOp > JSToken;
node JSBAndOp > JSToken;
node JSBNotOp > JSToken;
node JSNotOp > JSToken;
node JSMulOp > JSToken, JSOperator;
node JSAddOp > JSToken, JSOperator;
node JSDivOp > JSToken, JSOperator;
node JSSubOp > JSToken, JSOperator;
node JSLtOp > JSToken, JSOperator;
node JSGtOp > JSToken, JSOperator;
node JSBOrOp > JSToken, JSOperator;
node JSBXorOp > JSToken, JSOperator;
node JSBAndOp > JSToken, JSOperator;
node JSBNotOp > JSToken, JSOperator;
node JSNotOp > JSToken, JSOperator;
node JSPattern;
node JSPattern > JSSyntax;
node JSBindPattern > JSPattern {
name: JSIdentifier,
}
node JSExpression;
node JSExpression > JSSyntax;
node JSConstantExpression > JSExpression {
value: BoltValue,
@ -458,8 +457,6 @@ node JSConditionalExpression > JSExpression {
alternate: JSExpression,
}
type JSValue = Int
node JSLiteralExpression > JSExpression {
value: JSValue,
}
@ -474,12 +471,12 @@ node JSFunctionBodyElement;
node JSStatement > JSSourceElement, JSFunctionBodyElement;
node JSCatchBlock {
node JSCatchBlock > JSSyntax {
bindings: Option<JSPattern>,
elements: Vec<JSSourceElement>,
}
node JSTryCatchStatement {
node JSTryCatchStatement > JSSyntax {
tryBlock: Vec<JSSourceElement>,
catchBlock: Option<JSCatchBlock>,
finalBlock: Option<Vec<JSSourceElement>>,
@ -489,7 +486,7 @@ node JSExpressionStatement > JSStatement {
expression: JSExpression,
}
node JSConditionalCase {
node JSConditionalCase > JSSyntax {
test: Option<JSExpression>,
body: Vec<JSFunctionBodyElement>,
}
@ -502,19 +499,19 @@ node JSReturnStatement > JSStatement {
value: Option<JSExpression>,
}
node JSParameter {
node JSParameter > JSSyntax {
index: usize,
bindings: JSPattern,
defaultValue: Option<JSExpression>,
}
node JSDeclaration > JSSourceElement;
node JSDeclaration > JSSyntax, JSSourceElement;
enum JSDeclarationModifiers {
IsExported = 0x1,
}
node JSImportBinding;
node JSImportBinding > JSSyntax;
node JSImportStarBinding > JSImportBinding {
local: JSIdentifier,
@ -548,7 +545,6 @@ node JSLetDeclaration > JSDeclaration, JSFunctionBodyElement {
value: Option<JSExpression>,
}
node JSSourceFile > SourceFile {
node JSSourceFile > JSSyntax, SourceFile {
elements: Vec<JSSourceElement>,
}

1132
src/ast.d.ts vendored

File diff suppressed because it is too large Load diff

View file

@ -14,14 +14,16 @@ import { Program } from "../program"
import { parseSourceFile } from "../parser"
import { Frontend } from "../frontend"
import { Package } from "../common"
import {hasOwnProperty} from "../util"
import {hasOwnProperty, upsearchSync, expandPath, FastStringMap, assert} from "../util"
import {isString} from "util"
import {DiagnosticPrinter, E_FIELD_NOT_PRESENT, E_FIELD_MUST_BE_STRING, E_FIELD_HAS_INVALID_VERSION_NUMBER} from "../diagnostics"
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')
function toArray<T>(value: T | T[]): T[] {
if (Array.isArray(value)) {
return value as T[]
@ -49,6 +51,7 @@ function loadPackageMetadata(rootDir: string) {
let name = null
let version = null;
let autoImport = false;
let hasVersionErrors = false;
let hasNameErrors = false;
@ -90,6 +93,17 @@ function loadPackageMetadata(rootDir: string) {
}
}
}
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'];
}
}
}
}
@ -111,35 +125,67 @@ function loadPackageMetadata(rootDir: string) {
return {
name,
version
version,
autoImport,
};
}
function loadPackage(rootDir: string): Package {
function loadPackageFromPath(rootDir: string, isDependency: boolean): Package {
rootDir = path.resolve(rootDir);
const data = loadPackageMetadata(rootDir);
const pkg = new Package(rootDir, data.name, data.version, []);
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 loadPackagesAndSourceFiles(filenames: string[], cwd = '.'): Package[] {
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, []);
const anonPkg = new Package(cwd, null, null, [], false, false);
const pkgs = [ anonPkg ];
for (const filename of filenames) {
if (fs.statSync(filename).isDirectory()) {
pkgs.push(loadPackage(filename));
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
.command(
@ -160,9 +206,31 @@ yargs
.command(
'check [files..]',
'Check the given files/packages for mistakes.',
yargs => yargs,
yargs => yargs
.string('work-dir')
.describe('work-dir', 'The working directory where files will be resolved against.')
.default('work-dir', '.')
.boolean('no-std')
.describe('no-std', 'Do not build using the standard library.')
.string('pkg'),
args => {
const pkgs = loadPackagesAndSourceFiles(toArray(args.files as string[] | string));
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);
@ -187,10 +255,20 @@ yargs
, args => {
const pkgs = loadPackagesAndSourceFiles(toArray(args.files as string[] | string));
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.typeCheck(program);
frontend.check(program);
if (frontend.diagnostics.hasErrors && !args.force) {
process.exit(1);
}

View file

@ -1,4 +1,4 @@
import { BoltImportDirective, Syntax, BoltParameter, BoltModulePath, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor, BoltSyntax, BoltIdentifier } from "./ast";
import { BoltImportDirective, Syntax, BoltParameter, BoltReferenceExpression, BoltReferenceTypeExpression, BoltSourceFile, BoltCallExpression, BoltReturnKeyword, BoltReturnStatement, SyntaxKind, NodeVisitor, BoltSyntax, BoltIdentifier } from "./ast";
import { Program } from "./program";
import { DiagnosticPrinter, E_FILE_NOT_FOUND, E_TYPES_NOT_ASSIGNABLE, E_DECLARATION_NOT_FOUND, E_TYPE_DECLARATION_NOT_FOUND, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_A_VALUE } from "./diagnostics";
import { getSymbolPathFromNode } from "./resolver"
@ -140,16 +140,7 @@ export class CheckTypeAssignments extends NodeVisitor {
protected visitSyntax(node: Syntax) {
for (const error of node.errors) {
switch (error.type) {
case ErrorType.AssignmentError:
this.diagnostics.add({
message: E_TYPES_NOT_ASSIGNABLE,
severity: 'error',
node: error.left,
});
default:
throw new Error(`Could not add a diagnostic message for the error ${ErrorType[error.type]}`)
}
this.diagnostics.add({ node, ...error });
}
}

View file

@ -18,6 +18,7 @@ import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
import {TextSpan, TextPos, TextFile} from "./text";
import {Scanner} from "./scanner";
import * as path from "path"
export function getSourceFile(node: Syntax) {
while (true) {
@ -41,17 +42,35 @@ export class Package {
public id = nextPackageId++;
private sourceFilesByPath = new FastStringMap<string, SourceFile>();
constructor(
public rootDir: string,
public name: string | null,
public version: string | null,
public sourceFiles: SourceFile[],
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.sourceFiles.push(sourceFile);
this.sourceFilesByPath.set(sourceFile.span!.file.fullPath, sourceFile);
}
}

View file

@ -1,3 +1,8 @@
export const BOLT_SUPPORTED_LANGUAGES = ['Bolt', 'JS'];
export const BOLT_DIAG_NUM_EXTRA_LINES = 1;
export const BOLT_MAX_FIELDS_TO_PRINT = 3;
export const LOG_DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'

View file

@ -1,7 +1,8 @@
import chalk from "chalk"
import {Syntax} from "./ast";
import {format, MapLike, FormatArg, countDigits} from "./util";
import {format, MapLike, FormatArg, countDigits, mapValues, prettyPrint} from "./util";
import { BOLT_DIAG_NUM_EXTRA_LINES } from "./constants";
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.";;;;
@ -9,22 +10,29 @@ export const E_FILE_NOT_FOUND = "A file named {filename} was not found.";
export const E_FIELD_HAS_INVALID_VERSION_NUMBER = "Field '{name}' contains an invalid version nunmber."
export const E_FIELD_MUST_BE_STRING = "Field '{name}' must be a string."
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
export const E_FIELD_MUST_BE_BOOLEAN = "Field '{name}' must be a either 'true' or 'false'."
export const E_TYPE_DECLARATION_NOT_FOUND = "A type declaration named '{name}' was not found."
export const E_DECLARATION_NOT_FOUND = "Reference to an undefined declaration '{name}'.";
export const E_TYPES_NOT_ASSIGNABLE = "Types {left} and {right} are not assignable.";
export const E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL = "Too few arguments for function call. Expected {expected} but got {actual}.";
export const E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL = "Too many arguments for function call. Expected {expected} but got {actual}.";
export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'."
export const E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER = "Candidate function requires this parameter."
export const E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER = "Argument has no corresponding parameter."
export const E_INVALID_ARGUMENTS = "Invalid arguments passed to function '{name}'"
export const E_RECORD_MISSING_MEMBER = "Record {name} does not have a member declaration named {memberName}"
export const E_TYPES_MISSING_MEMBER = "Not all types resolve to a record with the a member named '{name}'."
export const E_NODE_DOES_NOT_CONTAIN_MEMBER = "This node does not contain the the member '{name}'."
export const E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID = "May not return a value because the function's return type resolves to '()'"
export const E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID = "Must return a value because the function's return type does not resolve to '()'"
const BOLT_HARD_ERRORS = process.env['BOLT_HARD_ERRORS']
const DIAG_NUM_EXTRA_LINES = 1;
export interface Diagnostic {
message: string;
severity: string;
args?: MapLike<FormatArg>;
node?: Syntax;
nested?: Diagnostic[];
}
function firstIndexOfNonEmpty(str: string) {
@ -78,7 +86,7 @@ export class DiagnosticPrinter {
out += chalk.bold.yellow(`${span.file.origPath}:${span.start.line}:${span.start.column}: `);
}
if (diagnostic.args !== undefined) {
out += format(diagnostic.message, diagnostic.args) + '\n';
out += format(diagnostic.message, mapValues(diagnostic.args, prettyPrint)) + '\n';
} else {
out += diagnostic.message + '\n';
}
@ -87,9 +95,9 @@ export class DiagnosticPrinter {
out += '\n'
const span = diagnostic.node.span!;
const content = span.file.getText();
const startLine = Math.max(0, span.start.line-1-DIAG_NUM_EXTRA_LINES)
const startLine = Math.max(0, span.start.line-1-BOLT_DIAG_NUM_EXTRA_LINES)
const lines = content.split('\n')
const endLine = Math.min(lines.length-1, (span.end !== undefined ? span.end.line : startLine)+DIAG_NUM_EXTRA_LINES)
const endLine = Math.min(lines.length-1, (span.end !== undefined ? span.end.line : startLine)+BOLT_DIAG_NUM_EXTRA_LINES)
const gutterWidth = Math.max(2, countDigits(endLine+1))
for (let i = startLine; i < endLine; i++) {
const line = lines[i];

View file

@ -9,10 +9,6 @@ export class Emitter {
switch (node.kind) {
case SyntaxKind.BoltModulePath:
out += node.elements.map(el => el.text).join('::');
break;
case SyntaxKind.BoltQualName:
if (node.modulePath !== null) {
if (node.isAbsolute) {

View file

@ -93,11 +93,17 @@ export class Frontend {
const checkers = checks.map(check => container.createInstance(check));
for (const sourceFile of program.getAllSourceFiles()) {
checker.registerSourceFile(sourceFile);
resolver.registerSourceFile(sourceFile);
}
for (const sourceFile of program.getAllSourceFiles()) {
sourceFile.visit(checkers)
checker.registerSourceFile(sourceFile);
}
for (const pkg of program.getAllPackages()) {
if (!pkg.isDependency) {
for (const sourceFile of pkg.getAllSourceFiles()) {
sourceFile.visit(checkers)
}
}
}
}

View file

@ -81,6 +81,7 @@ import {
BoltModulePath,
isBoltSymbol,
BoltIdentifierChild,
BoltDeclarationLike,
} from "./ast"
import { parseForeignLanguage } from "./foreign"
@ -1284,7 +1285,7 @@ export class Parser {
return result;
}
public parseDeclaration(tokens: BoltTokenStream): BoltDeclaration {
public parseDeclarationLike(tokens: BoltTokenStream): BoltDeclarationLike {
let t0 = tokens.peek(1);
let i = 1;
if (t0.kind === SyntaxKind.BoltPubKeyword) {
@ -1365,7 +1366,7 @@ export class Parser {
} else if (KIND_STATEMENT_T0.indexOf(t1.kind) !== -1) {
return this.parseStatement(tokens);
} else if (KIND_DECLARATION_KEYWORD.indexOf(t1.kind) !== -1) {
return this.parseDeclaration(tokens);
return this.parseDeclarationLike(tokens);
} else {
throw new ParseError(t0, KIND_SOURCEELEMENT_T0);
}

View file

@ -11,10 +11,13 @@ export class Program {
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
constructor(
pkgs: Package[]
private pkgs: Package[]
) {
for (const pkg of pkgs) {
for (const sourceFile of pkg.sourceFiles) {
if (pkg.name !== null) {
this.packagesByName.set(pkg.name, pkg);
}
for (const sourceFile of pkg.getAllSourceFiles()) {
this.sourceFilesByFilePath.set(stripExtensions(sourceFile.span!.file.fullPath), sourceFile);
}
}
@ -32,6 +35,21 @@ export class Program {
return this.sourceFilesByFilePath.get(filepath);
}
public getAllPackages(): IterableIterator<Package> {
return this.pkgs[Symbol.iterator]();
}
public *getAllGloballyDeclaredSourceFiles(): IterableIterator<SourceFile> {
for (const pkg of this.getAllPackages()) {
if (pkg.isAutoImported) {
const mainLibrarySourceFile = pkg.getMainLibrarySourceFile();
if (mainLibrarySourceFile !== null) {
yield mainLibrarySourceFile;
}
}
}
}
public getPackageNamed(name: string): Package {
return this.packagesByName.get(name);
}

View file

@ -1,3 +0,0 @@

View file

@ -42,12 +42,12 @@ export function getSymbolPathFromNode(node: BoltSyntax): SymbolPath {
return new SymbolPath([], false, name);
}
return new SymbolPath(node.modulePath.map(id => id.text), false, name);
case SyntaxKind.BoltModulePath:
return new SymbolPath(
node.elements.slice(0, -1).map(el => el.text),
node.isAbsolute,
node.elements[node.elements.length-1].text
);
//case SyntaxKind.BoltModulePath:
// return new SymbolPath(
// node.elements.slice(0, -1).map(el => el.text),
// node.isAbsolute,
// node.elements[node.elements.length-1].text
// );
default:
throw new Error(`Could not extract a symbol path from the given node.`);
}
@ -491,6 +491,21 @@ export class SymbolResolver {
return scope.getSymbol(this.strategy.getSymbolName(node));
}
public resolveGlobalSymbol(name: string, kind: ScopeType) {
const symbolPath = new SymbolPath([], true, name);
for (const sourceFile of this.program.getAllGloballyDeclaredSourceFiles()) {
const scope = this.getScopeForNode(sourceFile, kind);
if (scope === null) {
continue;
}
const sym = scope.getLocalSymbol(name);
if (sym !== null) {
return sym
}
}
return null;
}
public resolveSymbolPath(path: SymbolPath, scope: Scope): SymbolInfo | null {
if (path.hasParents()) {

View file

@ -40,11 +40,15 @@ const nodeProto = {
const stack = [this];
while (stack.length > 0) {
const node = stack.pop();
const key = `visit${kindToString(node.kind)}`
const kindName = kindToString(node.kind);
const kindNamesToVisit = [kindName, ...NODE_TYPES[kindName].parents];
for (const visitor of visitors) {
if (visitor[key] !== undefined) {
visitor[key](node);
}
for (const kindName of kindNamesToVisit) {
const key = `visit${kindName}`
if (visitor[key] !== undefined) {
visitor[key](node);
}
}
}
for (const childNode of node.getChildNodes()) {
stack.push(childNode);

View file

@ -1,5 +1,6 @@
import { Type } from "./types"
import { Diagnostic } from "./diagnostics"
import { Package } from "./common"
import { TextSpan } from "./text"
@ -13,7 +14,7 @@ interface SyntaxBase {
id: number;
kind: SyntaxKind;
type?: Type;
errors: CompileError[]
errors: Diagnostic[]
parentNode: Syntax | null;
span: TextSpan | null;
visit(visitors: NodeVisitor[]): void;

View file

@ -4,7 +4,7 @@ import * as path from "path"
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
const CUSTOM_TYPES = ['Package'];
const CUSTOM_TYPES = ['Package', 'BoltValue', 'JSValue'];
import { Syntax, Declaration, NodeDeclaration, TypeDeclaration, EnumDeclaration, TypeNode, NodeField } from "./ast"
import { MapLike, assert } from "../util"
@ -21,7 +21,6 @@ export function generateAST(decls: Declaration[]) {
const nodeDecls: NodeDeclaration[] = decls.filter(decl => decl.type === 'NodeDeclaration') as NodeDeclaration[];
const typeDecls: TypeDeclaration[] = decls.filter(decl => decl.type === 'TypeDeclaration') as TypeDeclaration[];
const enumDecls: EnumDeclaration[] = decls.filter(decl => decl.type === 'EnumDeclaration') as EnumDeclaration[];
const langNames: string[] = decls.filter(decl => decl.type === 'LanguageDeclaration').map(decl => decl.name);
const declByName: MapLike<Declaration> = Object.create(null);
i = 0;
@ -57,6 +56,11 @@ export function generateAST(decls: Declaration[]) {
jsFile.write(`'${decl.name}': {\n`);
jsFile.indent();
jsFile.write(`index: ${decl.index},\n`);
jsFile.write(`parents: [`);
for (const parentName of getParentChain(decl.name)) {
jsFile.write(`'${decl.name}', `)
}
jsFile.write(`'Syntax'],\n`);
jsFile.write(`fields: new Map([\n`);
jsFile.indent();
for (const field of getAllFields(decl)) {
@ -189,20 +193,6 @@ export function generateAST(decls: Declaration[]) {
//dtsFile.write(' never\n\n');
//}
for (const langName of langNames) {
dtsFile.write(`export type ${langName}Syntax\n`);
let first = true;
dtsFile.indent();
for (const decl of finalNodes) {
if (decl.name.startsWith(langName)) {
dtsFile.write((first ? '=' : '|') + ' ' + decl.name + '\n');
first = false;
}
}
dtsFile.dedent();
dtsFile.write('\n\n');
}
dtsFile.write(`export type Syntax\n`);
let first = true;
dtsFile.indent();
@ -369,6 +359,17 @@ export function generateAST(decls: Declaration[]) {
return children;
}
function *getParentChain(nodeName: string) {
const stack = [ nodeName ];
while (stack.length > 0) {
const nodeDecl = getDeclarationNamed(stack.pop()!) as NodeDeclaration;
for (const parentName of nodeDecl.parents) {
yield parentName;
stack.push(parentName);
}
}
}
function* getFinalNodes(declName: string): IterableIterator<string> {
const stack = [ declName ];
while (stack.length > 0) {

View file

@ -1,8 +1,15 @@
import { FastStringMap, assert, isPlainObject } from "./util";
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression } from "./ast";
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo } from "./resolver";
import { FastStringMap, assert, isPlainObject, some, prettyPrintTag } from "./util";
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression, BoltCallExpression, BoltSyntax, BoltMemberExpression, BoltDeclaration, isBoltDeclaration, isBoltTypeDeclaration, BoltTypeDeclaration, BoltReturnStatement, BoltIdentifier, BoltRecordDeclaration, isBoltRecordDeclaration, isBoltDeclarationLike } from "./ast";
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
import { Value, Record } from "./evaluator";
import { SourceMap } from "module";
import { timingSafeEqual } from "crypto";
import { isRightAssoc, getReturnStatementsInFunctionBody, BoltFunctionBody, getModulePathToNode } from "./common";
import { relativeTimeThreshold } from "moment";
import { E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL, E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL, E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER, E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER, E_TYPES_NOT_ASSIGNABLE, E_TYPES_MISSING_MEMBER, E_NODE_DOES_NOT_CONTAIN_MEMBER, E_RECORD_MISSING_MEMBER, E_MUST_RETURN_A_VALUE, E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID, E_MAY_NOT_RETURN_A_VALUE, E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID } from "./diagnostics";
import { emitNode } from "./emitter";
import { BOLT_MAX_FIELDS_TO_PRINT } from "./constants";
// TODO For function bodies, we can do something special.
// Sort the return types and find the largest types, eliminating types that fall under other types.
@ -29,15 +36,31 @@ export type Type
| VariantType
| TupleType
| UnionType
| PlainRecordFieldType
abstract class TypeBase {
public abstract kind: TypeKind;
constructor(public symbol?: SymbolInfo) {
/**
* Holds the node that created this type, if any.
*/
public node?: Syntax
constructor(public sym?: SymbolInfo) {
}
public [prettyPrintTag](): string {
return prettyPrintType(this as Type);
}
}
export function isType(value: any) {
return typeof(value) === 'object'
&& value !== null
&& value.__IS_TYPE !== null;
}
export class OpaqueType extends TypeBase {
@ -94,17 +117,20 @@ export class VariantType extends TypeBase {
export class UnionType extends TypeBase {
private elements: Type[] = [];
public kind: TypeKind.UnionType = TypeKind.UnionType;
constructor(private elements: Type[] = []) {
constructor(elements: Iterable<Type> = []) {
super();
this.elements = [...elements];
}
public addElement(element: Type): void {
this.elements.push(element);
}
public getElements(): IterableIterator<Type> {
public getElementTypes(): IterableIterator<Type> {
return this.elements[Symbol.iterator]();
}
@ -123,7 +149,7 @@ class PlainRecordFieldType extends TypeBase {
}
export class RecordType {
export class RecordType extends TypeBase {
public kind: TypeKind.RecordType = TypeKind.RecordType;
@ -132,6 +158,7 @@ export class RecordType {
constructor(
iterable?: Iterable<[string, RecordFieldType]>,
) {
super();
if (iterable !== undefined) {
for (const [name, type] of iterable) {
this.fieldTypes.set(name, type);
@ -139,10 +166,18 @@ export class RecordType {
}
}
public getFieldNames() {
return this.fieldTypes.keys();
}
public addField(name: string, type: RecordFieldType): void {
this.fieldTypes.set(name, type);
}
public getFields() {
return this.fieldTypes[Symbol.iterator]();
}
public hasField(name: string) {
return name in this.fieldTypes;
}
@ -157,8 +192,30 @@ export class RecordType {
}
export class TupleType extends TypeBase {
kind: TypeKind.TupleType = TypeKind.TupleType;
constructor(public elementTypes: Type[] = []) {
super();
}
}
export enum ErrorType {
AssignmentError,
NotARecord,
TypeMismatch,
TooFewArguments,
TooManyArguments,
MayNotReturnValue,
MustReturnValue,
}
interface NotARecordError {
type: ErrorType.NotARecord;
node: Syntax;
candidate: Syntax;
}
interface AssignmentError {
@ -167,58 +224,105 @@ interface AssignmentError {
right: Syntax;
}
export type CompileError
= AssignmentError
export class TupleType extends TypeBase {
kind: TypeKind.TupleType = TypeKind.TupleType;
constructor(public elementTypes: Type[]) {
super();
}
interface TypeMismatchError {
type: ErrorType.TypeMismatch;
left: Type;
right: Type;
}
//export function narrowType(outer: Type, inner: Type): Type {
// if (isAnyType(outer) || isNeverType(inner)) {
// return inner;
// }
// // TODO cover the other cases
// return outer;
//}
interface TooManyArgumentsError {
type: ErrorType.TooManyArguments;
caller: Syntax;
callee: Syntax;
index: number;
}
//export function intersectTypes(a: Type, b: Type): Type {
// if (a.kind === TypeKind.NeverType && b.kind === TypeKind.NeverType)
// return new NeverType();
// }
// if (a.kind == TypeKind.AnyType) {
// return a
// }
// if (isAnyType(a)) {
// return b;
// }
// if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) {
// if (a.paramTypes.length !== b.paramTypes.length) {
// return new NeverType();
// }
// const returnType = intersectTypes(a.returnType, b.returnType);
// const paramTypes = a.paramTypes
// .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i]));
// return new FunctionType(paramTypes, returnType)
// }
// return new NeverType();
//}
interface TooFewArgumentsError {
type: ErrorType.TooFewArguments;
caller: Syntax;
callee: Syntax;
index: number;
}
interface MustReturnValueError {
type: ErrorType.MustReturnValue;
}
interface MayNotReturnValueError {
type: ErrorType.MayNotReturnValue;
}
export type CompileError
= AssignmentError
| TypeMismatchError
| TooManyArgumentsError
| TooFewArgumentsError
| NotARecordError
| MustReturnValueError
| MayNotReturnValueError
export interface FunctionSignature {
paramTypes: Type[];
}
function* getAllPossibleElementTypes(type: Type): IterableIterator<Type> {
switch (type.kind) {
case TypeKind.UnionType:
{
for (const elementType of type.getElementTypes()) {
yield* getAllPossibleElementTypes(elementType);
}
break;
}
default:
yield type;
}
}
export function prettyPrintType(type: Type): string {
let out = ''
let hasElementType = false;
for (const elementType of getAllPossibleElementTypes(type)) {
hasElementType = true;
if (elementType.sym !== undefined) {
out += elementType.sym.name;
} else {
switch (elementType.kind) {
case TypeKind.AnyType:
{
out += 'any';
break;
}
case TypeKind.RecordType:
{
out += '{'
let i = 0;
for (const [fieldName, fieldType] of elementType.getFields()) {
out += fieldName + ': ' + prettyPrintType(fieldType);
i++;
if (i >= BOLT_MAX_FIELDS_TO_PRINT) {
break
}
}
out += '}'
break;
}
default:
throw new Error(`Could not pretty-print type ${TypeKind[elementType.kind]}`)
}
}
}
if (!hasElementType) {
out += '()'
}
return out;
}
export class TypeChecker {
private opaqueTypes = new FastStringMap<number, OpaqueType>();
private anyType = new AnyType();
private stringType = new OpaqueType();
private intType = new OpaqueType();
private floatType = new OpaqueType();
private voidType = new OpaqueType();
private syntaxType = new UnionType(); // FIXME
@ -228,11 +332,17 @@ export class TypeChecker {
public getTypeOfValue(value: Value): Type {
if (typeof(value) === 'string') {
return this.stringType;
const sym = this.resolver.resolveGlobalSymbol('String', ScopeType.Type);
assert(sym !== null);
return new OpaqueType(sym!);
} else if (typeof(value) === 'bigint') {
return this.intType;
const sym = this.resolver.resolveGlobalSymbol('int', ScopeType.Type);
assert(sym !== null);
return new OpaqueType(sym!);
} else if (typeof(value) === 'number') {
return this.floatType;
const sym = this.resolver.resolveGlobalSymbol('f64', ScopeType.Type);
assert(sym !== null);
return new OpaqueType(sym!);
} else if (value instanceof Record) {
const recordType = new RecordType()
for (const [fieldName, fieldValue] of value.getFields()) {
@ -244,6 +354,67 @@ export class TypeChecker {
}
}
private checkTypeMatches(a: Type, b: Type) {
switch (b.kind) {
case TypeKind.FunctionType:
if (a.kind === TypeKind.AnyType) {
return true;
}
if (a.kind === TypeKind.FunctionType) {
if (b.getParameterCount() > a.getParameterCount()) {
a.node?.errors.push({
message: E_TOO_MANY_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
args: {
expected: a.getParameterCount(),
actual: a.getParameterCount(),
},
nested: [{
message: E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER,
severity: 'error',
node: b.getTypeAtParameterIndex(a.getParameterCount()).node!
}]
})
}
if (b.getParameterCount() < a.getParameterCount()) {
let nested = [];
for (let i = b.getParameterCount(); i < a.getParameterCount(); i++) {
nested.push({
message: E_ARGUMENT_HAS_NO_CORRESPONDING_PARAMETER,
severity: 'error',
node: (a.node as BoltCallExpression).operands[i]
});
}
a.node?.errors.push({
message: E_TOO_FEW_ARGUMENTS_FOR_FUNCTION_CALL,
severity: 'error',
args: {
expected: a.getParameterCount(),
actual: b.getParameterCount(),
},
nested,
});
}
const paramCount = a.getParameterCount();
for (let i = 0; i < paramCount; i++) {
const paramA = a.getTypeAtParameterIndex(i);
const paramB = b.getTypeAtParameterIndex(i);
if (this.isTypeAssignableTo(paramA, paramB)) {
a.node?.errors.push({
message: E_TYPES_NOT_ASSIGNABLE,
severity: 'error',
args: {
left: a,
right: b,
},
node: a.node,
})
}
}
}
}
}
public registerSourceFile(sourceFile: SourceFile): void {
for (const node of sourceFile.preorder()) {
if (isBoltMacroCall(node)) {
@ -253,6 +424,12 @@ export class TypeChecker {
node.type = this.createInitialTypeForExpression(node);
}
}
for (const callExpr of sourceFile.findAllChildrenOfKind(SyntaxKind.BoltCallExpression)) {
const callTypeSig = new FunctionType(callExpr.operands.map(op => op.type!), this.anyType);
for (const callableType of this.findTypesInExpression(callExpr.operator)) {
this.checkTypeMatches(callableType, callTypeSig);
}
}
}
private createInitialTypeForExpression(node: Syntax): Type {
@ -282,7 +459,7 @@ export class TypeChecker {
if (this.opaqueTypes.has(recordSym!.id)) {
resultType = this.opaqueTypes.get(recordSym!.id);
} else {
const opaqueType = new OpaqueType(recordSym!);
const opaqueType = new OpaqueType(name, node);
this.opaqueTypes.set(recordSym!.id, opaqueType);
resultType = opaqueType;
}
@ -346,67 +523,328 @@ export class TypeChecker {
}
public isVoidType(type: Type): boolean {
return type === this.voidType;
return this.isTypeAssignableTo(new TupleType, type);
}
public getCallableFunctions(node: BoltExpression): BoltFunctionDeclaration[] {
private *getTypesForMember(origNode: Syntax, fieldName: string, type: Type): IterableIterator<Type> {
switch (type.kind) {
case TypeKind.UnionType:
{
const typesMissingMember = [];
for (const elementType of getAllPossibleElementTypes(type)) {
let foundType = false;
for (const recordType of this.getTypesForMemberNoUnionType(origNode, fieldName, elementType, false)) {
yield recordType;
foundType = true;
}
if (!foundType) {
origNode.errors.push({
message: E_TYPES_MISSING_MEMBER,
severity: 'error',
})
}
}
}
default:
return this.getTypesForMemberNoUnionType(origNode, fieldName, type, true);
}
}
const resolver = this.resolver;
private *getTypesForMemberNoUnionType(origNode: Syntax, fieldName: string, type: Type, hardError: boolean): IterableIterator<Type> {
switch (type.kind) {
case TypeKind.AnyType:
break;
case TypeKind.FunctionType:
if (hardError) {
origNode.errors.push({
message: E_TYPES_MISSING_MEMBER,
severity: 'error',
args: {
name: fieldName,
},
nested: [{
message: E_NODE_DOES_NOT_CONTAIN_MEMBER,
severity: 'error',
node: type.node!,
}]
});
}
break;
case TypeKind.RecordType:
{
if (type.hasField(fieldName)) {
const fieldType = type.getFieldType(fieldName);
assert(fieldType.kind === TypeKind.PlainRecordFieldType);
yield (fieldType as PlainRecordFieldType).type;
} else {
if (hardError) {
origNode.errors.push({
message: E_TYPES_MISSING_MEMBER,
severity: 'error',
args: {
name: fieldName
}
})
}
}
break;
}
default:
throw new Error(`I do not know how to find record member types for ${TypeKind[type.kind]}`)
}
}
const results: BoltFunctionDeclaration[] = [];
visitExpression(node);
return results;
private isTypeAlwaysCallable(type: Type) {
return type.kind === TypeKind.FunctionType;
}
private *getAllNodesForType(type: Type): IterableIterator<Syntax> {
if (type.node !== undefined) {
yield type.node;
}
switch (type.kind) {
case TypeKind.UnionType:
for (const elementType of type.getElementTypes()) {
yield* this.getAllNodesForType(type);
}
break;
default:
}
}
private *findTypesInTypeExpression(node: BoltTypeExpression): IterableIterator<Type> {
switch (node.kind) {
case SyntaxKind.BoltTypeOfExpression:
{
yield* this.findTypesInExpression(node.expression);
break;
}
case SyntaxKind.BoltReferenceTypeExpression:
{
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
assert(scope !== null);
const symbolPath = getSymbolPathFromNode(node.name);
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
if (resolvedSym !== null) {
for (const decl of resolvedSym.declarations) {
assert(isBoltTypeDeclaration(decl));
this.findTypesInTypeDeclaration(decl as BoltTypeDeclaration);
}
}
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
private *findTypesInExpression(node: BoltExpression): IterableIterator<Type> {
function visitExpression(node: BoltExpression) {
switch (node.kind) {
case SyntaxKind.BoltMemberExpression:
{
visitExpression(node.expression);
break;
}
case SyntaxKind.BoltQuoteExpression:
{
// TODO visit all unquote expressions
//visitExpression(node.tokens);
break;
}
case SyntaxKind.BoltCallExpression:
{
// TODO
break;
}
case SyntaxKind.BoltReferenceExpression:
{
const scope = resolver.getScopeForNode(node, ScopeType.Variable);
assert(scope !== null);
const resolvedSym = resolver.resolveSymbolPath(getSymbolPathFromNode(node), scope!);
if (resolvedSym !== null) {
for (const decl of resolvedSym.declarations) {
visitFunctionBodyElement(decl as BoltFunctionBodyElement);
for (const element of node.path) {
for (const memberType of this.getTypesForMember(element, element.text, node.expression.type!)) {
yield memberType;
}
}
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
function visitFunctionBodyElement(node: BoltFunctionBodyElement) {
switch (node.kind) {
case SyntaxKind.BoltFunctionDeclaration:
results.push(node);
case SyntaxKind.BoltMatchExpression:
{
const unionType = new UnionType();
for (const matchArm of node.arms) {
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
}
yield unionType;
break;
case SyntaxKind.BoltVariableDeclaration:
if (node.value !== null) {
visitExpression(node.value);
}
case SyntaxKind.BoltQuoteExpression:
{
break;
}
case SyntaxKind.BoltCallExpression:
{
const nodeSignature = new FunctionType(node.operands.map(op => op.type!), this.anyType);
for (const callableType of this.findTypesInExpression(node.operator)) {
yield callableType;
}
break;
}
case SyntaxKind.BoltReferenceExpression:
{
const scope = this.resolver.getScopeSurroundingNode(node, ScopeType.Variable);
assert(scope !== null);
const symbolPath = getSymbolPathFromNode(node.name);
const resolvedSym = this.resolver.resolveSymbolPath(symbolPath, scope!);
if (resolvedSym !== null) {
for (const decl of resolvedSym.declarations) {
assert(isBoltDeclaration(decl));
yield* this.findTypesInDeclaration(decl as BoltDeclaration);
}
}
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
private *findTypesInTypeDeclaration(node: BoltTypeDeclaration): IterableIterator<Type> {
switch (node.kind) {
case SyntaxKind.BoltTypeAliasDeclaration:
{
yield* this.findTypesInTypeExpression(node.typeExpr);
break;
}
default:
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
}
}
private *findTypesInDeclaration(node: BoltDeclaration) {
switch (node.kind) {
case SyntaxKind.BoltVariableDeclaration:
if (node.typeExpr !== null) {
yield* this.findTypesInTypeExpression(node.typeExpr);
}
if (node.value !== null) {
yield* this.findTypesInExpression(node.value);
}
break;
case SyntaxKind.BoltFunctionDeclaration:
{
yield this.getFunctionReturnType(node);
break;
}
default:
throw new Error(`Could not find callable expressions in declaration ${kindToString(node.kind)}`)
}
}
private getTypeOfExpression(node: BoltExpression): Type {
return new UnionType(this.findTypesInExpression(node));
}
private getFunctionReturnType(node: BoltFunctionDeclaration): Type {
let returnType: Type = this.anyType;
if (node.returnType !== null) {
returnType = new UnionType(this.findTypesInTypeExpression(node.returnType));
}
for (const returnStmt of this.getAllReturnStatementsInFunctionBody(node.body)) {
if (returnStmt.value === null) {
if (!this.isVoidType(returnType)) {
returnStmt.errors.push({
message: E_MAY_NOT_RETURN_A_VALUE,
severity: 'error',
nested: [{
message: E_MAY_NOT_RETURN_BECAUSE_TYPE_RESOLVES_TO_VOID,
severity: 'error',
node: node.returnType !== null ? node.returnType : node,
}]
});
}
} else {
const stmtReturnType = this.getTypeOfExpression(returnStmt.value);
if (!this.isTypeAssignableTo(returnType, stmtReturnType)) {
if (this.isVoidType(stmtReturnType)) {
returnStmt.value.errors.push({
message: E_MUST_RETURN_A_VALUE,
severity: 'error',
nested: [{
message: E_MUST_RETURN_BECAUSE_TYPE_DOES_NOT_RESOLVE_TO_VOID,
severity: 'error',
node: node.returnType !== null ? node.returnType : node,
}]
})
} else {
returnStmt.value.errors.push({
message: E_TYPES_NOT_ASSIGNABLE,
severity: 'error',
args: {
left: returnType,
right: stmtReturnType,
}
})
}
}
}
}
return returnType;
}
private *getAllReturnStatementsInFunctionBody(body: BoltFunctionBody): IterableIterator<BoltReturnStatement> {
for (const element of body) {
switch (element.kind) {
case SyntaxKind.BoltReturnStatement:
{
yield element;
break;
}
case SyntaxKind.BoltConditionalStatement:
{
for (const caseNode of element.cases) {
yield* this.getAllReturnStatementsInFunctionBody(caseNode.body);
}
break;
}
case SyntaxKind.BoltExpressionStatement:
break;
default:
throw new Error(`I did not know how to find return statements in ${kindToString(node.kind)}`);
}
}
}
private isTypeAssignableTo(left: Type, right: Type): boolean {
if (left.kind === TypeKind.NeverType || right.kind === TypeKind.NeverType) {
return false;
}
if (left.kind === TypeKind.AnyType || right.kind === TypeKind.AnyType) {
return true;
}
if (left.kind === TypeKind.OpaqueType && right.kind === TypeKind.OpaqueType) {
return left === right;
}
if (left.kind === TypeKind.RecordType && right.kind === TypeKind.RecordType) {
for (const fieldName of left.getFieldNames()) {
if (!right.hasField(fieldName)) {
return false;
}
}
for (const fieldName of right.getFieldNames()) {
if (!left.hasField(fieldName)) {
return false;
}
if (!this.isTypeAssignableTo(left.getFieldType(fieldName), right.getFieldType(fieldName))) {
return false;
}
}
return true;
}
if (left.kind === TypeKind.FunctionType && right.kind === TypeKind.FunctionType) {
if (left.getParameterCount() !== right.getParameterCount()) {
return false;
}
for (let i = 0; i < left.getParameterCount(); i++) {
if (!this.isTypeAssignableTo(left.getTypeAtParameterIndex(i), right.getTypeAtParameterIndex(i))) {
return false;
}
}
if (!this.isTypeAssignableTo(left.returnType, right.returnType)) {
return false;
}
return true;
}
return false;
}
}

View file

@ -1,14 +1,31 @@
import * as path from "path"
import * as fs from "fs"
import * as os from "os"
import moment from "moment"
import chalk from "chalk"
import { LOG_DATETIME_FORMAT } from "./constants"
export function isPowerOf(x: number, n: number):boolean {
const a = Math.log(x) / Math.log(n);
return Math.pow(a, n) == x;
}
export function some<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
while (true) {
const { value, done } = iterator.next();
if (done) {
break;
}
if (pred(value)) {
return true;
}
}
return false;
}
export function every<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
while (true) {
const { value, done } = iterator.next();
@ -97,6 +114,23 @@ export function isPlainObject(value: any): value is object {
return Object.getPrototypeOf(value) === proto
}
export function mapValues<T extends object, R extends PropertyKey>(obj: T, func: (value: keyof T) => R): { [K in keyof T]: R } {
const newObj: any = {}
for (const key of Object.keys(obj)) {
newObj[key] = func((obj as any)[key]);
}
return newObj;
}
export const prettyPrintTag = Symbol('pretty printer');
export function prettyPrint(value: any): string {
if (isObjectLike(value) && value[prettyPrintTag] !== undefined) {
return value[prettyPrintTag]();
}
return value.toString();
}
export class FastStringMap<K extends PropertyKey, V> {
private mapping = Object.create(null);
@ -105,6 +139,12 @@ export class FastStringMap<K extends PropertyKey, V> {
this.mapping.clear();
}
public *[Symbol.iterator](): IterableIterator<[K, V]> {
for (const key of Object.keys(this.mapping)) {
yield [key as K, this.mapping[key]];
}
}
public get(key: K): V {
if (!(key in this.mapping)) {
throw new Error(`No value found for key '${key}'.`);
@ -112,6 +152,12 @@ export class FastStringMap<K extends PropertyKey, V> {
return this.mapping[key];
}
public *keys(): IterableIterator<K> {
for (const key of Object.keys(this.mapping)) {
yield key as K;
}
}
public *values(): IterableIterator<V> {
for (const key of Object.keys(this.mapping)) {
yield this.mapping[key];
@ -230,16 +276,30 @@ export class StreamWrapper<T> {
}
const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
export function expandPath(filepath: string) {
let out = ''
for (const ch of filepath) {
if (ch === '~') {
out += os.homedir();
} else {
out += ch;
}
}
return out;
}
export function verbose(message: string) {
console.error(chalk.gray('[') + chalk.magenta('verb') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
console.error(chalk.gray('[') + chalk.magenta('verb') + ' ' + chalk.gray(moment().format(LOG_DATETIME_FORMAT) + ']') + ' ' + message);
}
export function warn(message: string) {
console.error(chalk.gray('[') + chalk.red('warn') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
}
export function error(message: string) {
console.error(chalk.gray('[') + chalk.red('erro') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
}
export function upsearchSync(filename: string, startDir = '.') {
let currDir = startDir;
while (true) {

View file

@ -1 +1,3 @@
name: stdlib
version: 0.0.1
auto-import: true

View file

@ -1,11 +1,11 @@
import "./numbers"
import "./either"
import "./vec"
import "./string"
pub mod IO {
import "./numbers"
import "./either"
import "./vec"
import "./string"
pub type Result<T> = Either<Error, T>;
pub foreign "JS" fn print(message: String) {

View file

@ -2,6 +2,7 @@
pub import "./option"
pub import "./either"
pub import "./string"
pub import "./numbers"
pub import "./math"
pub import "./vec"
pub import "./lang/bolt"

View file

@ -1,4 +1,6 @@
import "./numbers.bolt";
// precedence a + b < a * b;
pub fn fac(n: I) -> I where I: int {