Small fixes and major enhancements to TypeChecker
This commit is contained in:
parent
ce8c0aa7a1
commit
884be8f9ec
23 changed files with 1614 additions and 635 deletions
2
Makefile
2
Makefile
|
@ -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
|
||||
|
|
120
src/ast-spec.txt
120
src/ast-spec.txt
|
@ -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
1132
src/ast.d.ts
vendored
File diff suppressed because it is too large
Load diff
102
src/bin/bolt.ts
102
src/bin/bolt.ts
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
|
||||
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
626
src/types.ts
626
src/types.ts
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
64
src/util.ts
64
src/util.ts
|
@ -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) {
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
|
||||
name: stdlib
|
||||
version: 0.0.1
|
||||
auto-import: true
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
|
||||
import "./numbers.bolt";
|
||||
|
||||
// precedence a + b < a * b;
|
||||
|
||||
pub fn fac(n: I) -> I where I: int {
|
||||
|
|
Loading…
Reference in a new issue