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
|
all: lib/ast.js
|
||||||
bolt check stdlib
|
bolt check stdlib
|
||||||
|
|
122
src/ast-spec.txt
122
src/ast-spec.txt
|
@ -1,18 +1,14 @@
|
||||||
|
|
||||||
@language Bolt;
|
|
||||||
@language JS;
|
|
||||||
|
|
||||||
node EndOfFile > BoltToken, JSToken;
|
node EndOfFile > BoltToken, JSToken;
|
||||||
node Token;
|
node Token;
|
||||||
node SourceFile;
|
node SourceFile;
|
||||||
|
node FunctionBody;
|
||||||
|
|
||||||
// Bolt language AST definitions
|
// Bolt language AST definitions
|
||||||
|
|
||||||
type BoltValue = Integer | bool | String;
|
node BoltSyntax;
|
||||||
|
|
||||||
node FunctionBody;
|
node BoltToken > Token, BoltSyntax;
|
||||||
|
|
||||||
node BoltToken > Token;
|
|
||||||
|
|
||||||
node BoltStringLiteral > BoltToken {
|
node BoltStringLiteral > BoltToken {
|
||||||
value: String,
|
value: String,
|
||||||
|
@ -85,23 +81,22 @@ node BoltParenthesized > BoltPunctuated;
|
||||||
node BoltBraced > BoltPunctuated;
|
node BoltBraced > BoltPunctuated;
|
||||||
node BoltBracketed > BoltPunctuated;
|
node BoltBracketed > BoltPunctuated;
|
||||||
|
|
||||||
node BoltSourceFile > SourceFile {
|
node BoltSourceFile > BoltSyntax, SourceFile {
|
||||||
elements: Vec<BoltSourceElement>,
|
elements: Vec<BoltSourceElement>,
|
||||||
package: Package,
|
package: Package,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltQualName {
|
node BoltQualName > BoltSyntax {
|
||||||
isAbsolute: bool,
|
isAbsolute: bool,
|
||||||
modulePath: Vec<BoltIdentifier>,
|
modulePath: Vec<BoltIdentifier>,
|
||||||
name: BoltSymbol,
|
name: BoltSymbol,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltModulePath {
|
node BoltTypeExpression > BoltSyntax;
|
||||||
isAbsolute: bool,
|
|
||||||
elements: Vec<BoltIdentifier>,
|
|
||||||
}
|
|
||||||
|
|
||||||
node BoltTypeExpression;
|
node BoltTypeOfExpression > BoltTypeExpression {
|
||||||
|
expression: BoltExpression,
|
||||||
|
}
|
||||||
|
|
||||||
node BoltReferenceTypeExpression > BoltTypeExpression {
|
node BoltReferenceTypeExpression > BoltTypeExpression {
|
||||||
name: BoltQualName,
|
name: BoltQualName,
|
||||||
|
@ -117,14 +112,14 @@ node BoltLiftedTypeExpression > BoltTypeExpression {
|
||||||
expression: BoltExpression,
|
expression: BoltExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltTypeParameter {
|
node BoltTypeParameter > BoltSyntax {
|
||||||
index: usize,
|
index: usize,
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
typeExpr: Option<BoltTypeExpression>,
|
typeExpr: Option<BoltTypeExpression>,
|
||||||
defaultType: Option<BoltTypeExpression>,
|
defaultType: Option<BoltTypeExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltPattern;
|
node BoltPattern > BoltSyntax;
|
||||||
|
|
||||||
node BoltBindPattern > BoltPattern {
|
node BoltBindPattern > BoltPattern {
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
|
@ -139,7 +134,7 @@ node BoltExpressionPattern > BoltPattern {
|
||||||
expression: BoltExpression,
|
expression: BoltExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltTuplePatternElement {
|
node BoltTuplePatternElement > BoltSyntax {
|
||||||
index: usize,
|
index: usize,
|
||||||
pattern: BoltPattern,
|
pattern: BoltPattern,
|
||||||
}
|
}
|
||||||
|
@ -148,7 +143,7 @@ node BoltTuplePattern > BoltPattern {
|
||||||
elements: Vec<BoltTuplePatternElement>,
|
elements: Vec<BoltTuplePatternElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltRecordFieldPattern {
|
node BoltRecordFieldPattern > BoltSyntax {
|
||||||
isRest: bool,
|
isRest: bool,
|
||||||
name: Option<BoltIdentifier>,
|
name: Option<BoltIdentifier>,
|
||||||
pattern: Option<BoltPattern>,
|
pattern: Option<BoltPattern>,
|
||||||
|
@ -159,7 +154,7 @@ node BoltRecordPattern > BoltPattern {
|
||||||
fields: Vec<BoltRecordFieldPattern>,
|
fields: Vec<BoltRecordFieldPattern>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltExpression;
|
node BoltExpression > BoltSyntax;
|
||||||
|
|
||||||
node BoltQuoteExpression > BoltExpression {
|
node BoltQuoteExpression > BoltExpression {
|
||||||
tokens: Vec<Token | BoltExpression>,
|
tokens: Vec<Token | BoltExpression>,
|
||||||
|
@ -193,7 +188,7 @@ node BoltYieldExpression > BoltExpression {
|
||||||
value: BoltExpression,
|
value: BoltExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltMatchArm {
|
node BoltMatchArm > BoltSyntax {
|
||||||
pattern: BoltPattern,
|
pattern: BoltPattern,
|
||||||
body: BoltExpression,
|
body: BoltExpression,
|
||||||
}
|
}
|
||||||
|
@ -203,7 +198,7 @@ node BoltMatchExpression > BoltExpression {
|
||||||
arms: Vec<BoltMatchArm>,
|
arms: Vec<BoltMatchArm>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltCase {
|
node BoltCase > BoltSyntax {
|
||||||
test: BoltExpression,
|
test: BoltExpression,
|
||||||
result: BoltExpression,
|
result: BoltExpression,
|
||||||
}
|
}
|
||||||
|
@ -220,13 +215,13 @@ node BoltConstantExpression > BoltExpression {
|
||||||
value: BoltValue,
|
value: BoltValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltStatement > BoltFunctionBodyElement, BoltSourceElement;
|
node BoltStatement > BoltSyntax, BoltFunctionBodyElement, BoltSourceElement;
|
||||||
|
|
||||||
node BoltReturnStatement > BoltStatement {
|
node BoltReturnStatement > BoltStatement {
|
||||||
value: Option<BoltExpression>,
|
value: Option<BoltExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltConditionalCase {
|
node BoltConditionalCase > BoltSyntax {
|
||||||
test: Option<BoltExpression>,
|
test: Option<BoltExpression>,
|
||||||
body: Vec<BoltFunctionBodyElement>,
|
body: Vec<BoltFunctionBodyElement>,
|
||||||
}
|
}
|
||||||
|
@ -243,31 +238,33 @@ node BoltExpressionStatement > BoltStatement {
|
||||||
expression: BoltExpression,
|
expression: BoltExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltParameter {
|
node BoltParameter > BoltSyntax {
|
||||||
index: usize,
|
index: usize,
|
||||||
bindings: BoltPattern,
|
bindings: BoltPattern,
|
||||||
typeExpr: Option<BoltTypeExpression>,
|
typeExpr: Option<BoltTypeExpression>,
|
||||||
defaultValue: Option<BoltExpression>,
|
defaultValue: Option<BoltExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltDeclaration > BoltSourceElement;
|
node BoltDeclaration > BoltSyntax, BoltSourceElement;
|
||||||
|
|
||||||
node BoltTypeDeclaration > BoltSourceElement;
|
node BoltTypeDeclaration > BoltSyntax, BoltSourceElement;
|
||||||
|
|
||||||
enum BoltModifiers {
|
enum BoltModifiers {
|
||||||
IsMutable = 0x1,
|
IsMutable = 0x1,
|
||||||
IsPublic = 0x2,
|
IsPublic = 0x2,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltModule > BoltSourceElement {
|
node BoltModule > BoltSyntax, BoltSourceElement {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
name: Vec<BoltIdentifier>,
|
name: Vec<BoltIdentifier>,
|
||||||
elements: Vec<BoltSourceElement>,
|
elements: Vec<BoltSourceElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
node BoltDeclarationLike;
|
||||||
|
|
||||||
node BoltFunctionBodyElement;
|
node BoltFunctionBodyElement;
|
||||||
|
|
||||||
node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration {
|
node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration, BoltDeclarationLike {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
target: String,
|
target: String,
|
||||||
name: BoltSymbol,
|
name: BoltSymbol,
|
||||||
|
@ -277,14 +274,14 @@ node BoltFunctionDeclaration > BoltFunctionBodyElement, BoltDeclaration {
|
||||||
body: Vec<BoltFunctionBodyElement>,
|
body: Vec<BoltFunctionBodyElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration {
|
node BoltVariableDeclaration > BoltFunctionBodyElement, BoltDeclaration, BoltDeclarationLike {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
bindings: BoltPattern,
|
bindings: BoltPattern,
|
||||||
typeExpr: Option<BoltTypeExpression>,
|
typeExpr: Option<BoltTypeExpression>,
|
||||||
value: Option<BoltExpression>,
|
value: Option<BoltExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltImportSymbol;
|
node BoltImportSymbol > BoltSyntax;
|
||||||
|
|
||||||
node BoltPlainImportSymbol > BoltImportSymbol {
|
node BoltPlainImportSymbol > BoltImportSymbol {
|
||||||
remote: BoltQualName,
|
remote: BoltQualName,
|
||||||
|
@ -294,10 +291,10 @@ node BoltPlainImportSymbol > BoltImportSymbol {
|
||||||
node BoltImportDirective > BoltSourceElement {
|
node BoltImportDirective > BoltSourceElement {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
file: BoltStringLiteral,
|
file: BoltStringLiteral,
|
||||||
symbols: Vec<BoltImportSymbol>,
|
symbols: Option<Vec<BoltImportSymbol>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltExportSymbol;
|
node BoltExportSymbol > BoltSyntax;
|
||||||
|
|
||||||
node BoltPlainExportSymbol {
|
node BoltPlainExportSymbol {
|
||||||
local: BoltQualName,
|
local: BoltQualName,
|
||||||
|
@ -309,14 +306,14 @@ node BoltExportDirective > BoltSourceElement {
|
||||||
symbols: Option<Vec<BoltExportSymbol>>,
|
symbols: Option<Vec<BoltExportSymbol>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltTraitDeclaration > BoltDeclaration, BoltTypeDeclaration {
|
node BoltTraitDeclaration > BoltDeclarationLike, BoltTypeDeclaration {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
typeParams: Option<Vec<BoltTypeParameter>>,
|
typeParams: Option<Vec<BoltTypeParameter>>,
|
||||||
elements: Vec<BoltDeclaration>,
|
elements: Vec<BoltDeclaration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltImplDeclaration > BoltDeclaration {
|
node BoltImplDeclaration > BoltTypeDeclaration, BoltDeclarationLike {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
trait: BoltTypeExpression,
|
trait: BoltTypeExpression,
|
||||||
|
@ -324,21 +321,21 @@ node BoltImplDeclaration > BoltDeclaration {
|
||||||
elements: Vec<BoltDeclaration>,
|
elements: Vec<BoltDeclaration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltTypeAliasDeclaration > BoltDeclaration, BoltTypeDeclaration {
|
node BoltTypeAliasDeclaration > BoltDeclarationLike, BoltTypeDeclaration {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
typeParams: Option<Vec<BoltTypeParameter>>,
|
typeParams: Option<Vec<BoltTypeParameter>>,
|
||||||
typeExpr: BoltTypeExpression,
|
typeExpr: BoltTypeExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltRecordMember;
|
node BoltRecordMember > BoltSyntax;
|
||||||
|
|
||||||
node BoltRecordField > BoltRecordMember {
|
node BoltRecordField > BoltRecordMember {
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
typeExpr: BoltTypeExpression,
|
typeExpr: BoltTypeExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration {
|
node BoltRecordDeclaration > BoltDeclaration, BoltTypeDeclaration, BoltDeclarationLike {
|
||||||
modifiers: BoltModifiers,
|
modifiers: BoltModifiers,
|
||||||
name: BoltIdentifier,
|
name: BoltIdentifier,
|
||||||
typeParms: Option<Vec<BoltTypeParameter>>,
|
typeParms: Option<Vec<BoltTypeParameter>>,
|
||||||
|
@ -354,9 +351,9 @@ node BoltMacroCall > BoltRecordMember, BoltStatement, BoltDeclaration, BoltExpre
|
||||||
|
|
||||||
// JavaScript AST definitions
|
// JavaScript AST definitions
|
||||||
|
|
||||||
type JSValue = Int | String | Bool | Void;
|
node JSSyntax;
|
||||||
|
|
||||||
node JSToken > Token;
|
node JSToken > JSSyntax, Token;
|
||||||
|
|
||||||
node JSOperator > JSToken {
|
node JSOperator > JSToken {
|
||||||
text: String,
|
text: String,
|
||||||
|
@ -388,6 +385,8 @@ node JSFunctionKeyword > JSToken;
|
||||||
node JSWhileKeyword > JSToken;
|
node JSWhileKeyword > JSToken;
|
||||||
node JSForKeyword > JSToken;
|
node JSForKeyword > JSToken;
|
||||||
|
|
||||||
|
node JSOperator;
|
||||||
|
|
||||||
node JSCloseBrace > JSToken;
|
node JSCloseBrace > JSToken;
|
||||||
node JSCloseBracket > JSToken;
|
node JSCloseBracket > JSToken;
|
||||||
node JSCloseParen > JSToken;
|
node JSCloseParen > JSToken;
|
||||||
|
@ -398,25 +397,25 @@ node JSSemi > JSToken;
|
||||||
node JSComma > JSToken;
|
node JSComma > JSToken;
|
||||||
node JSDot > JSToken;
|
node JSDot > JSToken;
|
||||||
node JSDotDotDot > JSToken;
|
node JSDotDotDot > JSToken;
|
||||||
node JSMulOp > JSToken;
|
node JSMulOp > JSToken, JSOperator;
|
||||||
node JSAddOp > JSToken;
|
node JSAddOp > JSToken, JSOperator;
|
||||||
node JSDivOp > JSToken;
|
node JSDivOp > JSToken, JSOperator;
|
||||||
node JSSubOp > JSToken;
|
node JSSubOp > JSToken, JSOperator;
|
||||||
node JSLtOp > JSToken;
|
node JSLtOp > JSToken, JSOperator;
|
||||||
node JSGtOp > JSToken;
|
node JSGtOp > JSToken, JSOperator;
|
||||||
node JSBOrOp > JSToken;
|
node JSBOrOp > JSToken, JSOperator;
|
||||||
node JSBXorOp > JSToken;
|
node JSBXorOp > JSToken, JSOperator;
|
||||||
node JSBAndOp > JSToken;
|
node JSBAndOp > JSToken, JSOperator;
|
||||||
node JSBNotOp > JSToken;
|
node JSBNotOp > JSToken, JSOperator;
|
||||||
node JSNotOp > JSToken;
|
node JSNotOp > JSToken, JSOperator;
|
||||||
|
|
||||||
node JSPattern;
|
node JSPattern > JSSyntax;
|
||||||
|
|
||||||
node JSBindPattern > JSPattern {
|
node JSBindPattern > JSPattern {
|
||||||
name: JSIdentifier,
|
name: JSIdentifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSExpression;
|
node JSExpression > JSSyntax;
|
||||||
|
|
||||||
node JSConstantExpression > JSExpression {
|
node JSConstantExpression > JSExpression {
|
||||||
value: BoltValue,
|
value: BoltValue,
|
||||||
|
@ -458,8 +457,6 @@ node JSConditionalExpression > JSExpression {
|
||||||
alternate: JSExpression,
|
alternate: JSExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
type JSValue = Int
|
|
||||||
|
|
||||||
node JSLiteralExpression > JSExpression {
|
node JSLiteralExpression > JSExpression {
|
||||||
value: JSValue,
|
value: JSValue,
|
||||||
}
|
}
|
||||||
|
@ -474,12 +471,12 @@ node JSFunctionBodyElement;
|
||||||
|
|
||||||
node JSStatement > JSSourceElement, JSFunctionBodyElement;
|
node JSStatement > JSSourceElement, JSFunctionBodyElement;
|
||||||
|
|
||||||
node JSCatchBlock {
|
node JSCatchBlock > JSSyntax {
|
||||||
bindings: Option<JSPattern>,
|
bindings: Option<JSPattern>,
|
||||||
elements: Vec<JSSourceElement>,
|
elements: Vec<JSSourceElement>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSTryCatchStatement {
|
node JSTryCatchStatement > JSSyntax {
|
||||||
tryBlock: Vec<JSSourceElement>,
|
tryBlock: Vec<JSSourceElement>,
|
||||||
catchBlock: Option<JSCatchBlock>,
|
catchBlock: Option<JSCatchBlock>,
|
||||||
finalBlock: Option<Vec<JSSourceElement>>,
|
finalBlock: Option<Vec<JSSourceElement>>,
|
||||||
|
@ -489,7 +486,7 @@ node JSExpressionStatement > JSStatement {
|
||||||
expression: JSExpression,
|
expression: JSExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSConditionalCase {
|
node JSConditionalCase > JSSyntax {
|
||||||
test: Option<JSExpression>,
|
test: Option<JSExpression>,
|
||||||
body: Vec<JSFunctionBodyElement>,
|
body: Vec<JSFunctionBodyElement>,
|
||||||
}
|
}
|
||||||
|
@ -502,19 +499,19 @@ node JSReturnStatement > JSStatement {
|
||||||
value: Option<JSExpression>,
|
value: Option<JSExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSParameter {
|
node JSParameter > JSSyntax {
|
||||||
index: usize,
|
index: usize,
|
||||||
bindings: JSPattern,
|
bindings: JSPattern,
|
||||||
defaultValue: Option<JSExpression>,
|
defaultValue: Option<JSExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSDeclaration > JSSourceElement;
|
node JSDeclaration > JSSyntax, JSSourceElement;
|
||||||
|
|
||||||
enum JSDeclarationModifiers {
|
enum JSDeclarationModifiers {
|
||||||
IsExported = 0x1,
|
IsExported = 0x1,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSImportBinding;
|
node JSImportBinding > JSSyntax;
|
||||||
|
|
||||||
node JSImportStarBinding > JSImportBinding {
|
node JSImportStarBinding > JSImportBinding {
|
||||||
local: JSIdentifier,
|
local: JSIdentifier,
|
||||||
|
@ -548,7 +545,6 @@ node JSLetDeclaration > JSDeclaration, JSFunctionBodyElement {
|
||||||
value: Option<JSExpression>,
|
value: Option<JSExpression>,
|
||||||
}
|
}
|
||||||
|
|
||||||
node JSSourceFile > SourceFile {
|
node JSSourceFile > JSSyntax, SourceFile {
|
||||||
elements: Vec<JSSourceElement>,
|
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 { parseSourceFile } from "../parser"
|
||||||
import { Frontend } from "../frontend"
|
import { Frontend } from "../frontend"
|
||||||
import { Package } from "../common"
|
import { Package } from "../common"
|
||||||
import {hasOwnProperty} from "../util"
|
import {hasOwnProperty, upsearchSync, expandPath, FastStringMap, assert} from "../util"
|
||||||
import {isString} 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) {
|
//global.print = function (value: any) {
|
||||||
// console.error(require('util').inspect(value, { depth: Infinity, colors: true }))
|
// 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[] {
|
function toArray<T>(value: T | T[]): T[] {
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value as T[]
|
return value as T[]
|
||||||
|
@ -49,6 +51,7 @@ function loadPackageMetadata(rootDir: string) {
|
||||||
|
|
||||||
let name = null
|
let name = null
|
||||||
let version = null;
|
let version = null;
|
||||||
|
let autoImport = false;
|
||||||
|
|
||||||
let hasVersionErrors = false;
|
let hasVersionErrors = false;
|
||||||
let hasNameErrors = 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 {
|
return {
|
||||||
name,
|
name,
|
||||||
version
|
version,
|
||||||
|
autoImport,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPackage(rootDir: string): Package {
|
function loadPackageFromPath(rootDir: string, isDependency: boolean): Package {
|
||||||
rootDir = path.resolve(rootDir);
|
rootDir = path.resolve(rootDir);
|
||||||
const data = loadPackageMetadata(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'))) {
|
for (const filepath of globSync(path.join(rootDir, '**/*.bolt'))) {
|
||||||
pkg.addSourceFile(parseSourceFile(filepath, pkg));
|
pkg.addSourceFile(parseSourceFile(filepath, pkg));
|
||||||
}
|
}
|
||||||
return 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);
|
cwd = path.resolve(cwd);
|
||||||
const anonPkg = new Package(cwd, null, null, []);
|
const anonPkg = new Package(cwd, null, null, [], false, false);
|
||||||
const pkgs = [ anonPkg ];
|
const pkgs = [ anonPkg ];
|
||||||
for (const filename of filenames) {
|
for (const filename of filenames) {
|
||||||
if (fs.statSync(filename).isDirectory()) {
|
if (fs.statSync(filename).isDirectory()) {
|
||||||
pkgs.push(loadPackage(filename));
|
pkgs.push(loadPackageFromPath(filename, false));
|
||||||
} else {
|
} else {
|
||||||
anonPkg.addSourceFile(parseSourceFile(filename, anonPkg));
|
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;
|
return pkgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PackagePathResolver {
|
||||||
|
|
||||||
|
private packageNameToPath = new FastStringMap<string, string>();
|
||||||
|
|
||||||
|
public findPackagePath(name: string): string | null {
|
||||||
|
if (this.packageNameToPath.has(name)) {
|
||||||
|
return this.packageNameToPath.get(name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public mapPackgeNameToPath(name: string, filepath: string): void {
|
||||||
|
this.packageNameToPath.set(name, filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
yargs
|
yargs
|
||||||
|
|
||||||
.command(
|
.command(
|
||||||
|
@ -160,9 +206,31 @@ yargs
|
||||||
.command(
|
.command(
|
||||||
'check [files..]',
|
'check [files..]',
|
||||||
'Check the given files/packages for mistakes.',
|
'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 => {
|
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 program = new Program(pkgs);
|
||||||
const frontend = new Frontend();
|
const frontend = new Frontend();
|
||||||
frontend.check(program);
|
frontend.check(program);
|
||||||
|
@ -187,10 +255,20 @@ yargs
|
||||||
|
|
||||||
, args => {
|
, 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 program = new Program(pkgs);
|
||||||
const frontend = new Frontend();
|
const frontend = new Frontend();
|
||||||
frontend.typeCheck(program);
|
frontend.check(program);
|
||||||
if (frontend.diagnostics.hasErrors && !args.force) {
|
if (frontend.diagnostics.hasErrors && !args.force) {
|
||||||
process.exit(1);
|
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 { 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 { 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"
|
import { getSymbolPathFromNode } from "./resolver"
|
||||||
|
@ -140,16 +140,7 @@ export class CheckTypeAssignments extends NodeVisitor {
|
||||||
|
|
||||||
protected visitSyntax(node: Syntax) {
|
protected visitSyntax(node: Syntax) {
|
||||||
for (const error of node.errors) {
|
for (const error of node.errors) {
|
||||||
switch (error.type) {
|
this.diagnostics.add({ node, ...error });
|
||||||
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]}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { BOLT_SUPPORTED_LANGUAGES } from "./constants"
|
||||||
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
|
import {FastStringMap, enumerate, escapeChar, assert} from "./util";
|
||||||
import {TextSpan, TextPos, TextFile} from "./text";
|
import {TextSpan, TextPos, TextFile} from "./text";
|
||||||
import {Scanner} from "./scanner";
|
import {Scanner} from "./scanner";
|
||||||
|
import * as path from "path"
|
||||||
|
|
||||||
export function getSourceFile(node: Syntax) {
|
export function getSourceFile(node: Syntax) {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -41,17 +42,35 @@ export class Package {
|
||||||
|
|
||||||
public id = nextPackageId++;
|
public id = nextPackageId++;
|
||||||
|
|
||||||
|
private sourceFilesByPath = new FastStringMap<string, SourceFile>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public rootDir: string,
|
public rootDir: string,
|
||||||
public name: string | null,
|
public name: string | null,
|
||||||
public version: 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) {
|
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_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 chalk from "chalk"
|
||||||
import {Syntax} from "./ast";
|
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_MAY_NOT_RETURN_A_VALUE = "Returning a value inside a function that does not return values."
|
||||||
export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths.";;;;
|
export const E_MUST_RETURN_A_VALUE = "The function must return a value on all control paths.";;;;
|
||||||
|
@ -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_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_MUST_BE_STRING = "Field '{name}' must be a string."
|
||||||
export const E_FIELD_NOT_PRESENT = "Field '{name}' is not present."
|
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_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_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_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_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_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 BOLT_HARD_ERRORS = process.env['BOLT_HARD_ERRORS']
|
||||||
|
|
||||||
const DIAG_NUM_EXTRA_LINES = 1;
|
|
||||||
|
|
||||||
export interface Diagnostic {
|
export interface Diagnostic {
|
||||||
message: string;
|
message: string;
|
||||||
severity: string;
|
severity: string;
|
||||||
args?: MapLike<FormatArg>;
|
args?: MapLike<FormatArg>;
|
||||||
node?: Syntax;
|
node?: Syntax;
|
||||||
|
nested?: Diagnostic[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstIndexOfNonEmpty(str: string) {
|
function firstIndexOfNonEmpty(str: string) {
|
||||||
|
@ -78,7 +86,7 @@ export class DiagnosticPrinter {
|
||||||
out += chalk.bold.yellow(`${span.file.origPath}:${span.start.line}:${span.start.column}: `);
|
out += chalk.bold.yellow(`${span.file.origPath}:${span.start.line}:${span.start.column}: `);
|
||||||
}
|
}
|
||||||
if (diagnostic.args !== undefined) {
|
if (diagnostic.args !== undefined) {
|
||||||
out += format(diagnostic.message, diagnostic.args) + '\n';
|
out += format(diagnostic.message, mapValues(diagnostic.args, prettyPrint)) + '\n';
|
||||||
} else {
|
} else {
|
||||||
out += diagnostic.message + '\n';
|
out += diagnostic.message + '\n';
|
||||||
}
|
}
|
||||||
|
@ -87,9 +95,9 @@ export class DiagnosticPrinter {
|
||||||
out += '\n'
|
out += '\n'
|
||||||
const span = diagnostic.node.span!;
|
const span = diagnostic.node.span!;
|
||||||
const content = span.file.getText();
|
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 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))
|
const gutterWidth = Math.max(2, countDigits(endLine+1))
|
||||||
for (let i = startLine; i < endLine; i++) {
|
for (let i = startLine; i < endLine; i++) {
|
||||||
const line = lines[i];
|
const line = lines[i];
|
||||||
|
|
|
@ -9,10 +9,6 @@ export class Emitter {
|
||||||
|
|
||||||
switch (node.kind) {
|
switch (node.kind) {
|
||||||
|
|
||||||
case SyntaxKind.BoltModulePath:
|
|
||||||
out += node.elements.map(el => el.text).join('::');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SyntaxKind.BoltQualName:
|
case SyntaxKind.BoltQualName:
|
||||||
if (node.modulePath !== null) {
|
if (node.modulePath !== null) {
|
||||||
if (node.isAbsolute) {
|
if (node.isAbsolute) {
|
||||||
|
|
|
@ -93,11 +93,17 @@ export class Frontend {
|
||||||
const checkers = checks.map(check => container.createInstance(check));
|
const checkers = checks.map(check => container.createInstance(check));
|
||||||
|
|
||||||
for (const sourceFile of program.getAllSourceFiles()) {
|
for (const sourceFile of program.getAllSourceFiles()) {
|
||||||
checker.registerSourceFile(sourceFile);
|
|
||||||
resolver.registerSourceFile(sourceFile);
|
resolver.registerSourceFile(sourceFile);
|
||||||
}
|
}
|
||||||
for (const sourceFile of program.getAllSourceFiles()) {
|
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,
|
BoltModulePath,
|
||||||
isBoltSymbol,
|
isBoltSymbol,
|
||||||
BoltIdentifierChild,
|
BoltIdentifierChild,
|
||||||
|
BoltDeclarationLike,
|
||||||
} from "./ast"
|
} from "./ast"
|
||||||
|
|
||||||
import { parseForeignLanguage } from "./foreign"
|
import { parseForeignLanguage } from "./foreign"
|
||||||
|
@ -1284,7 +1285,7 @@ export class Parser {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseDeclaration(tokens: BoltTokenStream): BoltDeclaration {
|
public parseDeclarationLike(tokens: BoltTokenStream): BoltDeclarationLike {
|
||||||
let t0 = tokens.peek(1);
|
let t0 = tokens.peek(1);
|
||||||
let i = 1;
|
let i = 1;
|
||||||
if (t0.kind === SyntaxKind.BoltPubKeyword) {
|
if (t0.kind === SyntaxKind.BoltPubKeyword) {
|
||||||
|
@ -1365,7 +1366,7 @@ export class Parser {
|
||||||
} else if (KIND_STATEMENT_T0.indexOf(t1.kind) !== -1) {
|
} else if (KIND_STATEMENT_T0.indexOf(t1.kind) !== -1) {
|
||||||
return this.parseStatement(tokens);
|
return this.parseStatement(tokens);
|
||||||
} else if (KIND_DECLARATION_KEYWORD.indexOf(t1.kind) !== -1) {
|
} else if (KIND_DECLARATION_KEYWORD.indexOf(t1.kind) !== -1) {
|
||||||
return this.parseDeclaration(tokens);
|
return this.parseDeclarationLike(tokens);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseError(t0, KIND_SOURCEELEMENT_T0);
|
throw new ParseError(t0, KIND_SOURCEELEMENT_T0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,13 @@ export class Program {
|
||||||
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
|
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
pkgs: Package[]
|
private pkgs: Package[]
|
||||||
) {
|
) {
|
||||||
for (const pkg of pkgs) {
|
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);
|
this.sourceFilesByFilePath.set(stripExtensions(sourceFile.span!.file.fullPath), sourceFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +35,21 @@ export class Program {
|
||||||
return this.sourceFilesByFilePath.get(filepath);
|
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 {
|
public getPackageNamed(name: string): Package {
|
||||||
return this.packagesByName.get(name);
|
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([], false, name);
|
||||||
}
|
}
|
||||||
return new SymbolPath(node.modulePath.map(id => id.text), false, name);
|
return new SymbolPath(node.modulePath.map(id => id.text), false, name);
|
||||||
case SyntaxKind.BoltModulePath:
|
//case SyntaxKind.BoltModulePath:
|
||||||
return new SymbolPath(
|
// return new SymbolPath(
|
||||||
node.elements.slice(0, -1).map(el => el.text),
|
// node.elements.slice(0, -1).map(el => el.text),
|
||||||
node.isAbsolute,
|
// node.isAbsolute,
|
||||||
node.elements[node.elements.length-1].text
|
// node.elements[node.elements.length-1].text
|
||||||
);
|
// );
|
||||||
default:
|
default:
|
||||||
throw new Error(`Could not extract a symbol path from the given node.`);
|
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));
|
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 {
|
public resolveSymbolPath(path: SymbolPath, scope: Scope): SymbolInfo | null {
|
||||||
|
|
||||||
if (path.hasParents()) {
|
if (path.hasParents()) {
|
||||||
|
|
|
@ -40,11 +40,15 @@ const nodeProto = {
|
||||||
const stack = [this];
|
const stack = [this];
|
||||||
while (stack.length > 0) {
|
while (stack.length > 0) {
|
||||||
const node = stack.pop();
|
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) {
|
for (const visitor of visitors) {
|
||||||
if (visitor[key] !== undefined) {
|
for (const kindName of kindNamesToVisit) {
|
||||||
visitor[key](node);
|
const key = `visit${kindName}`
|
||||||
}
|
if (visitor[key] !== undefined) {
|
||||||
|
visitor[key](node);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (const childNode of node.getChildNodes()) {
|
for (const childNode of node.getChildNodes()) {
|
||||||
stack.push(childNode);
|
stack.push(childNode);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
import { Type } from "./types"
|
import { Type } from "./types"
|
||||||
|
import { Diagnostic } from "./diagnostics"
|
||||||
import { Package } from "./common"
|
import { Package } from "./common"
|
||||||
import { TextSpan } from "./text"
|
import { TextSpan } from "./text"
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ interface SyntaxBase {
|
||||||
id: number;
|
id: number;
|
||||||
kind: SyntaxKind;
|
kind: SyntaxKind;
|
||||||
type?: Type;
|
type?: Type;
|
||||||
errors: CompileError[]
|
errors: Diagnostic[]
|
||||||
parentNode: Syntax | null;
|
parentNode: Syntax | null;
|
||||||
span: TextSpan | null;
|
span: TextSpan | null;
|
||||||
visit(visitors: NodeVisitor[]): void;
|
visit(visitors: NodeVisitor[]): void;
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as path from "path"
|
||||||
|
|
||||||
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
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 { Syntax, Declaration, NodeDeclaration, TypeDeclaration, EnumDeclaration, TypeNode, NodeField } from "./ast"
|
||||||
import { MapLike, assert } from "../util"
|
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 nodeDecls: NodeDeclaration[] = decls.filter(decl => decl.type === 'NodeDeclaration') as NodeDeclaration[];
|
||||||
const typeDecls: TypeDeclaration[] = decls.filter(decl => decl.type === 'TypeDeclaration') as TypeDeclaration[];
|
const typeDecls: TypeDeclaration[] = decls.filter(decl => decl.type === 'TypeDeclaration') as TypeDeclaration[];
|
||||||
const enumDecls: EnumDeclaration[] = decls.filter(decl => decl.type === 'EnumDeclaration') as EnumDeclaration[];
|
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);
|
const declByName: MapLike<Declaration> = Object.create(null);
|
||||||
i = 0;
|
i = 0;
|
||||||
|
@ -57,6 +56,11 @@ export function generateAST(decls: Declaration[]) {
|
||||||
jsFile.write(`'${decl.name}': {\n`);
|
jsFile.write(`'${decl.name}': {\n`);
|
||||||
jsFile.indent();
|
jsFile.indent();
|
||||||
jsFile.write(`index: ${decl.index},\n`);
|
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.write(`fields: new Map([\n`);
|
||||||
jsFile.indent();
|
jsFile.indent();
|
||||||
for (const field of getAllFields(decl)) {
|
for (const field of getAllFields(decl)) {
|
||||||
|
@ -189,20 +193,6 @@ export function generateAST(decls: Declaration[]) {
|
||||||
//dtsFile.write(' never\n\n');
|
//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`);
|
dtsFile.write(`export type Syntax\n`);
|
||||||
let first = true;
|
let first = true;
|
||||||
dtsFile.indent();
|
dtsFile.indent();
|
||||||
|
@ -369,6 +359,17 @@ export function generateAST(decls: Declaration[]) {
|
||||||
return children;
|
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> {
|
function* getFinalNodes(declName: string): IterableIterator<string> {
|
||||||
const stack = [ declName ];
|
const stack = [ declName ];
|
||||||
while (stack.length > 0) {
|
while (stack.length > 0) {
|
||||||
|
|
626
src/types.ts
626
src/types.ts
|
@ -1,8 +1,15 @@
|
||||||
|
|
||||||
import { FastStringMap, assert, isPlainObject } from "./util";
|
import { FastStringMap, assert, isPlainObject, some, prettyPrintTag } from "./util";
|
||||||
import { SyntaxKind, Syntax, isBoltTypeExpression, BoltExpression, BoltFunctionDeclaration, BoltFunctionBodyElement, kindToString, SourceFile, isBoltExpression, isBoltMacroCall, BoltTypeExpression } from "./ast";
|
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 } from "./resolver";
|
import { getSymbolPathFromNode, ScopeType, SymbolResolver, SymbolInfo, SymbolPath } from "./resolver";
|
||||||
import { Value, Record } from "./evaluator";
|
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.
|
// 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.
|
// Sort the return types and find the largest types, eliminating types that fall under other types.
|
||||||
|
@ -29,15 +36,31 @@ export type Type
|
||||||
| VariantType
|
| VariantType
|
||||||
| TupleType
|
| TupleType
|
||||||
| UnionType
|
| UnionType
|
||||||
|
| PlainRecordFieldType
|
||||||
|
|
||||||
abstract class TypeBase {
|
abstract class TypeBase {
|
||||||
|
|
||||||
public abstract kind: TypeKind;
|
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 {
|
export class OpaqueType extends TypeBase {
|
||||||
|
@ -94,17 +117,20 @@ export class VariantType extends TypeBase {
|
||||||
|
|
||||||
export class UnionType extends TypeBase {
|
export class UnionType extends TypeBase {
|
||||||
|
|
||||||
|
private elements: Type[] = [];
|
||||||
|
|
||||||
public kind: TypeKind.UnionType = TypeKind.UnionType;
|
public kind: TypeKind.UnionType = TypeKind.UnionType;
|
||||||
|
|
||||||
constructor(private elements: Type[] = []) {
|
constructor(elements: Iterable<Type> = []) {
|
||||||
super();
|
super();
|
||||||
|
this.elements = [...elements];
|
||||||
}
|
}
|
||||||
|
|
||||||
public addElement(element: Type): void {
|
public addElement(element: Type): void {
|
||||||
this.elements.push(element);
|
this.elements.push(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getElements(): IterableIterator<Type> {
|
public getElementTypes(): IterableIterator<Type> {
|
||||||
return this.elements[Symbol.iterator]();
|
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;
|
public kind: TypeKind.RecordType = TypeKind.RecordType;
|
||||||
|
|
||||||
|
@ -132,17 +158,26 @@ export class RecordType {
|
||||||
constructor(
|
constructor(
|
||||||
iterable?: Iterable<[string, RecordFieldType]>,
|
iterable?: Iterable<[string, RecordFieldType]>,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
if (iterable !== undefined) {
|
if (iterable !== undefined) {
|
||||||
for (const [name, type] of iterable) {
|
for (const [name, type] of iterable) {
|
||||||
this.fieldTypes.set(name, type);
|
this.fieldTypes.set(name, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFieldNames() {
|
||||||
|
return this.fieldTypes.keys();
|
||||||
|
}
|
||||||
|
|
||||||
public addField(name: string, type: RecordFieldType): void {
|
public addField(name: string, type: RecordFieldType): void {
|
||||||
this.fieldTypes.set(name, type);
|
this.fieldTypes.set(name, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getFields() {
|
||||||
|
return this.fieldTypes[Symbol.iterator]();
|
||||||
|
}
|
||||||
|
|
||||||
public hasField(name: string) {
|
public hasField(name: string) {
|
||||||
return name in this.fieldTypes;
|
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 {
|
export enum ErrorType {
|
||||||
AssignmentError,
|
AssignmentError,
|
||||||
|
NotARecord,
|
||||||
|
TypeMismatch,
|
||||||
|
TooFewArguments,
|
||||||
|
TooManyArguments,
|
||||||
|
MayNotReturnValue,
|
||||||
|
MustReturnValue,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NotARecordError {
|
||||||
|
type: ErrorType.NotARecord;
|
||||||
|
node: Syntax;
|
||||||
|
candidate: Syntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AssignmentError {
|
interface AssignmentError {
|
||||||
|
@ -167,58 +224,105 @@ interface AssignmentError {
|
||||||
right: Syntax;
|
right: Syntax;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CompileError
|
interface TypeMismatchError {
|
||||||
= AssignmentError
|
type: ErrorType.TypeMismatch;
|
||||||
|
left: Type;
|
||||||
export class TupleType extends TypeBase {
|
right: Type;
|
||||||
|
|
||||||
kind: TypeKind.TupleType = TypeKind.TupleType;
|
|
||||||
|
|
||||||
constructor(public elementTypes: Type[]) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//export function narrowType(outer: Type, inner: Type): Type {
|
interface TooManyArgumentsError {
|
||||||
// if (isAnyType(outer) || isNeverType(inner)) {
|
type: ErrorType.TooManyArguments;
|
||||||
// return inner;
|
caller: Syntax;
|
||||||
// }
|
callee: Syntax;
|
||||||
// // TODO cover the other cases
|
index: number;
|
||||||
// return outer;
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
//export function intersectTypes(a: Type, b: Type): Type {
|
interface TooFewArgumentsError {
|
||||||
// if (a.kind === TypeKind.NeverType && b.kind === TypeKind.NeverType)
|
type: ErrorType.TooFewArguments;
|
||||||
// return new NeverType();
|
caller: Syntax;
|
||||||
// }
|
callee: Syntax;
|
||||||
// if (a.kind == TypeKind.AnyType) {
|
index: number;
|
||||||
// return a
|
}
|
||||||
// }
|
|
||||||
// if (isAnyType(a)) {
|
interface MustReturnValueError {
|
||||||
// return b;
|
type: ErrorType.MustReturnValue;
|
||||||
// }
|
}
|
||||||
// if (a.kind === TypeKind.FunctionType && b.kind === TypeKind.FunctionType) {
|
|
||||||
// if (a.paramTypes.length !== b.paramTypes.length) {
|
interface MayNotReturnValueError {
|
||||||
// return new NeverType();
|
type: ErrorType.MayNotReturnValue;
|
||||||
// }
|
}
|
||||||
// const returnType = intersectTypes(a.returnType, b.returnType);
|
|
||||||
// const paramTypes = a.paramTypes
|
export type CompileError
|
||||||
// .map((_, i) => intersectTypes(a.paramTypes[i], b.paramTypes[i]));
|
= AssignmentError
|
||||||
// return new FunctionType(paramTypes, returnType)
|
| TypeMismatchError
|
||||||
// }
|
| TooManyArgumentsError
|
||||||
// return new NeverType();
|
| 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 {
|
export class TypeChecker {
|
||||||
|
|
||||||
private opaqueTypes = new FastStringMap<number, OpaqueType>();
|
private opaqueTypes = new FastStringMap<number, OpaqueType>();
|
||||||
|
|
||||||
private anyType = new AnyType();
|
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
|
private syntaxType = new UnionType(); // FIXME
|
||||||
|
|
||||||
|
@ -228,11 +332,17 @@ export class TypeChecker {
|
||||||
|
|
||||||
public getTypeOfValue(value: Value): Type {
|
public getTypeOfValue(value: Value): Type {
|
||||||
if (typeof(value) === 'string') {
|
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') {
|
} 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') {
|
} 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) {
|
} else if (value instanceof Record) {
|
||||||
const recordType = new RecordType()
|
const recordType = new RecordType()
|
||||||
for (const [fieldName, fieldValue] of value.getFields()) {
|
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 {
|
public registerSourceFile(sourceFile: SourceFile): void {
|
||||||
for (const node of sourceFile.preorder()) {
|
for (const node of sourceFile.preorder()) {
|
||||||
if (isBoltMacroCall(node)) {
|
if (isBoltMacroCall(node)) {
|
||||||
|
@ -253,6 +424,12 @@ export class TypeChecker {
|
||||||
node.type = this.createInitialTypeForExpression(node);
|
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 {
|
private createInitialTypeForExpression(node: Syntax): Type {
|
||||||
|
@ -282,7 +459,7 @@ export class TypeChecker {
|
||||||
if (this.opaqueTypes.has(recordSym!.id)) {
|
if (this.opaqueTypes.has(recordSym!.id)) {
|
||||||
resultType = this.opaqueTypes.get(recordSym!.id);
|
resultType = this.opaqueTypes.get(recordSym!.id);
|
||||||
} else {
|
} else {
|
||||||
const opaqueType = new OpaqueType(recordSym!);
|
const opaqueType = new OpaqueType(name, node);
|
||||||
this.opaqueTypes.set(recordSym!.id, opaqueType);
|
this.opaqueTypes.set(recordSym!.id, opaqueType);
|
||||||
resultType = opaqueType;
|
resultType = opaqueType;
|
||||||
}
|
}
|
||||||
|
@ -346,67 +523,328 @@ export class TypeChecker {
|
||||||
}
|
}
|
||||||
|
|
||||||
public isVoidType(type: Type): boolean {
|
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[] = [];
|
private isTypeAlwaysCallable(type: Type) {
|
||||||
visitExpression(node);
|
return type.kind === TypeKind.FunctionType;
|
||||||
return results;
|
}
|
||||||
|
|
||||||
|
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) {
|
switch (node.kind) {
|
||||||
|
|
||||||
case SyntaxKind.BoltMemberExpression:
|
case SyntaxKind.BoltMemberExpression:
|
||||||
{
|
{
|
||||||
visitExpression(node.expression);
|
for (const element of node.path) {
|
||||||
break;
|
for (const memberType of this.getTypesForMember(element, element.text, node.expression.type!)) {
|
||||||
}
|
yield memberType;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function visitFunctionBodyElement(node: BoltFunctionBodyElement) {
|
case SyntaxKind.BoltMatchExpression:
|
||||||
switch (node.kind) {
|
{
|
||||||
case SyntaxKind.BoltFunctionDeclaration:
|
const unionType = new UnionType();
|
||||||
results.push(node);
|
for (const matchArm of node.arms) {
|
||||||
|
unionType.addElement(this.createInitialTypeForExpression(matchArm.body));
|
||||||
|
}
|
||||||
|
yield unionType;
|
||||||
break;
|
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;
|
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:
|
default:
|
||||||
throw new Error(`Unexpected node type ${kindToString(node.kind)}`);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
66
src/util.ts
66
src/util.ts
|
@ -1,14 +1,31 @@
|
||||||
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
|
import * as os from "os"
|
||||||
|
|
||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
|
import { LOG_DATETIME_FORMAT } from "./constants"
|
||||||
|
|
||||||
export function isPowerOf(x: number, n: number):boolean {
|
export function isPowerOf(x: number, n: number):boolean {
|
||||||
const a = Math.log(x) / Math.log(n);
|
const a = Math.log(x) / Math.log(n);
|
||||||
return Math.pow(a, n) == x;
|
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 {
|
export function every<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
|
||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = iterator.next();
|
const { value, done } = iterator.next();
|
||||||
|
@ -97,6 +114,23 @@ export function isPlainObject(value: any): value is object {
|
||||||
return Object.getPrototypeOf(value) === proto
|
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> {
|
export class FastStringMap<K extends PropertyKey, V> {
|
||||||
|
|
||||||
private mapping = Object.create(null);
|
private mapping = Object.create(null);
|
||||||
|
@ -104,7 +138,13 @@ export class FastStringMap<K extends PropertyKey, V> {
|
||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.mapping.clear();
|
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 {
|
public get(key: K): V {
|
||||||
if (!(key in this.mapping)) {
|
if (!(key in this.mapping)) {
|
||||||
throw new Error(`No value found for key '${key}'.`);
|
throw new Error(`No value found for key '${key}'.`);
|
||||||
|
@ -112,6 +152,12 @@ export class FastStringMap<K extends PropertyKey, V> {
|
||||||
return this.mapping[key];
|
return this.mapping[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public *keys(): IterableIterator<K> {
|
||||||
|
for (const key of Object.keys(this.mapping)) {
|
||||||
|
yield key as K;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public *values(): IterableIterator<V> {
|
public *values(): IterableIterator<V> {
|
||||||
for (const key of Object.keys(this.mapping)) {
|
for (const key of Object.keys(this.mapping)) {
|
||||||
yield this.mapping[key];
|
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) {
|
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) {
|
export function warn(message: string) {
|
||||||
console.error(chalk.gray('[') + chalk.red('warn') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
|
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 = '.') {
|
export function upsearchSync(filename: string, startDir = '.') {
|
||||||
let currDir = startDir;
|
let currDir = startDir;
|
||||||
while (true) {
|
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 {
|
pub mod IO {
|
||||||
|
|
||||||
|
import "./numbers"
|
||||||
|
import "./either"
|
||||||
|
import "./vec"
|
||||||
|
import "./string"
|
||||||
|
|
||||||
pub type Result<T> = Either<Error, T>;
|
pub type Result<T> = Either<Error, T>;
|
||||||
|
|
||||||
pub foreign "JS" fn print(message: String) {
|
pub foreign "JS" fn print(message: String) {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
pub import "./option"
|
pub import "./option"
|
||||||
pub import "./either"
|
pub import "./either"
|
||||||
pub import "./string"
|
pub import "./string"
|
||||||
|
pub import "./numbers"
|
||||||
pub import "./math"
|
pub import "./math"
|
||||||
pub import "./vec"
|
pub import "./vec"
|
||||||
pub import "./lang/bolt"
|
pub import "./lang/bolt"
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
import "./numbers.bolt";
|
||||||
|
|
||||||
// precedence a + b < a * b;
|
// precedence a + b < a * b;
|
||||||
|
|
||||||
pub fn fac(n: I) -> I where I: int {
|
pub fn fac(n: I) -> I where I: int {
|
||||||
|
|
Loading…
Reference in a new issue