diff --git a/README.md b/README.md index 59dc4e5a6..e7fe56d68 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,6 @@ print(fac(5)) // 10 ## FAQ -### What made you write Bolt? - -Because I was tired of writing applications in the horror that JavaScript -is. Moreover, I usually write these applications by myself, so I have to be -clever about how they will be implemented. - ### Why should I choose Bolt over JavaScript? Bolt was made to make writing user-interfaces dead-simple, while also making @@ -48,6 +42,18 @@ about _correctness_, _performance_ and _scalability_. - Scalability, because just like Rust, Bolt takes a _functional_ approach to software design using type traits, favouring composition over inheritance. +### What languages inspired Bolt? + +Rust and Haskell are two of my favorite languages that you'll notice Bolt +shares a lot of its syntax and semantics with. + +### What's the difference between Bolt and Rust? + +I really like Rust, but if I just care about writing an application I believe +Rust's memory model with its borrow checker is overkill. Having a garbage +collector certainly results in a performance penalty, but I believe that as +long as the user does not notice it, it does not really matter. + ## License Bolt itself is licensed under the GPL-3.0, because we put a lot of work in it diff --git a/src/ast.ts b/src/ast.ts index 1dac3c142..ddeea54f8 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,6 +1,8 @@ // FIXME SyntaxBase.getSpan() does not work then [n1, n2] is given as origNode +import * as path from "path" + import { Stream, StreamWrapper } from "./util" import { Scanner } from "./scanner" import { RecordType, PrimType, OptionType, VariantType, stringType, intType, boolType } from "./checker" @@ -41,6 +43,7 @@ export enum SyntaxKind { ModKeyword, EnumKeyword, StructKeyword, + NewTypeKeyword, // Special nodes @@ -86,15 +89,20 @@ export enum SyntaxKind { ImportDecl, RecordDecl, VariantDecl, + NewTypeDecl, } export class TextFile { - constructor(public path: string) { + constructor(public path: string, public cwd: string = '.') { } + get fullPath() { + return path.resolve(this.cwd, this.path) + } + } export class TextPos { @@ -588,7 +596,7 @@ export class QualName { for (const chunk of this.path) { out += chunk.text + '.' } - return out + this.name + return out + this.name.text } toJSON(): Json { @@ -982,7 +990,7 @@ export class FuncDecl extends SyntaxBase { public name: QualName, public params: Param[], public returnType: TypeDecl | null, - public body: Stmt[] | null, + public body: Body | null, public span: TextSpan | null = null, public origNode: [Syntax, Syntax] | Syntax | null = null, public parentNode: Syntax | null = null @@ -990,18 +998,18 @@ export class FuncDecl extends SyntaxBase { super(); } - toJSON(): Json { - return { - kind: 'FuncDecl', - isPublic: this.isPublic, - target: this.target, - name: this.name.toJSON(), - params: this.params.map(p => p.toJSON()), - returnType: this.returnType !== null ? this.returnType.toJSON() : null, - body: this.body !== null ? this.body.map(s => s.toJSON()) : null, - span: this.span !== null ? this.span.toJSON() : this.span, - } - } + // toJSON(): Json { + // return { + // kind: 'FuncDecl', + // isPublic: this.isPublic, + // target: this.target, + // name: this.name.toJSON(), + // params: this.params.map(p => p.toJSON()), + // returnType: this.returnType !== null ? this.returnType.toJSON() : null, + // body: this.body !== null ? this.body.map(s => s.toJSON()) : null, + // span: this.span !== null ? this.span.toJSON() : this.span, + // } + // } *getChildren(): IterableIterator { yield this.name @@ -1105,10 +1113,31 @@ export class RecordDecl extends SyntaxBase { } +export class NewTypeDecl extends SyntaxBase { + + kind: SyntaxKind.NewTypeDecl = SyntaxKind.NewTypeDecl; + + constructor( + public isPublic: boolean, + public name: Identifier, + public span: TextSpan | null = null, + public origNode: [Syntax, Syntax] | Syntax | null = null, + public parentNode: Syntax | null = null + ) { + super(); + } + + *getChildren() { + yield this.name; + } + +} + export type Decl = Sentence | FuncDecl | ImportDecl + | NewTypeDecl | VarDecl | RecordDecl diff --git a/src/bin/bolt.ts b/src/bin/bolt.ts index af7f127bb..8a18bb5b8 100644 --- a/src/bin/bolt.ts +++ b/src/bin/bolt.ts @@ -9,14 +9,13 @@ import { spawnSync } from "child_process" import yargs from "yargs" -import { Scanner } from "../scanner" -import { Parser } from "../parser" -import { Expander } from "../expander" -import { TypeChecker } from "../checker" -import { Compiler } from "../compiler" -import { Evaluator } from "../evaluator" -import { Emitter } from "../emitter" -import { TextFile, SourceFile, setParents } from "../ast" +import { Package, loadPackage } from "../package" +import { Program } from "../program" +import { TextFile } from "../ast" + +global.debug = function (value: any) { + console.error(require('util').inspect(value, { depth: Infinity, colors: true })) +} function toArray(value: T | T[]): T[] { if (Array.isArray(value)) { @@ -44,178 +43,68 @@ function flatMap(array: T[], proc: (element: T) => T[]) { return out } -interface Hook { - timing: 'before' | 'after' - name: string - effects: string[] -} - -function parseHook(str: string): Hook { - let timing: 'before' | 'after' = 'before'; - if (str[0] === '+') { - str = str.substring(1) - timing = 'after'; - } - const [name, rawEffects] = str.split('='); - return { - timing, - name, - effects: rawEffects.split(','), - } -} - yargs .command( - 'compile [files..]', - 'Compile a set of source files', - - yargs => yargs - .string('hook') - .describe('hook', 'Add a hook to a specific compile phase. See the manual for details.'), + 'link [name]', + 'Link projects with each other', + + yargs => yargs, args => { - const hooks: Hook[] = toArray(args.hook as string[] | string).map(parseHook); - const sourceFiles: SourceFile[] = []; + console.log(args.name) - for (const filepath of toArray(args.files as string[] | string)) { - - const file = new TextFile(filepath); - const content = fs.readFileSync(filepath, 'utf8') - const scanner = new Scanner(file, content) - - for (const hook of hooks) { - if (hook.name === 'scan' && hook.timing === 'before') { - for (const effect of hook.effects) { - switch (effect) { - case 'abort': - process.exit(0); - break; - default: - throw new Error(`Could not execute hook effect '${effect}.`); - } - } - } - } - - const sourceFile = scanner.scan(); - // while (true) { - // const token = scanner.scanToken() - // if (token === null) { - // break; - // } - // tokens.push(token); - // } - - for (const hook of hooks) { - if (hook.name === 'scan' && hook.timing == 'after') { - for (const effect of hook.effects) { - switch (effect) { - case 'dump': - console.log(JSON.stringify(sourceFile.toJSON(), undefined, 2)); - break; - case 'abort': - process.exit(0); - break; - default: - throw new Error(`Could not execute hook effect '${effect}'.`) - } - } - } - } - - sourceFiles.push(sourceFile); - - } - - const checker = new TypeChecker() - - for (const sourceFile of sourceFiles) { - const parser = new Parser() - const evaluator = new Evaluator(checker) - const expander = new Expander(parser, evaluator, checker) - const expandedSourceFile = expander.getFullyExpanded(sourceFile) - - for (const hook of hooks) { - if (hook.name === 'expand' && hook.timing == 'after') { - for (const effect of hook.effects) { - switch (effect) { - case 'dump': - console.log(JSON.stringify(expandedSourceFile.toJSON(), undefined, 2)); - break; - case 'abort': - process.exit(0); - break; - default: - throw new Error(`Could not execute hook effect '${effect}'.`) - } - } - } - } - - } - - }) - - .command( - - 'exec [files..]', - 'Run the specified Bolt scripts', - - yargs => - yargs, - - args => { - - const parser = new Parser() - const checker = new TypeChecker() - - const sourceFiles = toArray(args.files as string[]).map(filepath => { - const file = new TextFile(filepath) - const contents = fs.readFileSync(filepath, 'utf8') - const scanner = new Scanner(file, contents) - const sourceFile = scanner.scan(); - const evaluator = new Evaluator(checker) - const expander = new Expander(parser, evaluator, checker) - const expanded = expander.getFullyExpanded(sourceFile) as SourceFile; - // console.log(require('util').inspect(expanded.toJSON(), { colors: true, depth: Infinity })) - setParents(expanded) - return expanded; - }) - - const compiler = new Compiler(checker, { target: "JS" }) - const bundle = compiler.compile(sourceFiles) - const emitter = new Emitter() - const outfiles = bundle.map(file => { - const text = emitter.emit(file); - fs.mkdirpSync('.bolt-work') - const filepath = path.join('.bolt-work', path.relative(process.cwd(), stripExtension(path.resolve(file.loc.source)) + '.mjs')) - fs.writeFileSync(filepath, text, 'utf8') - return filepath - }) - - spawnSync('node', [outfiles[0]], { stdio: 'inherit' }) } ) .command( - 'dump [file]', - 'Dump a representation of a given primitive node to disk', + 'bundle [files..]', + 'Compile and optimise a set of Bolt packages/scripts', - yargs => yargs, + yargs => yargs + .string('work-dir') + .describe('work-dir', 'The working directory where files will be resolved against.') + .default('work-dir', '.'), args => { - const file = new TextFile(args.file as string) - const contents = fs.readFileSync(args.file, 'utf8') - const scanner = new Scanner(file, contents) - const parser = new Parser(); - const patt = parser.parsePattern(scanner) - console.log(JSON.stringify(patt.toJSON(), undefined, 2)) + const files = toArray(args.path as string[] | string).map(filepath => new TextFile(filepath, args['work-dir'])); + const program = new Program(files) + program.compile({ target: "JS" }); + + }) + + .command( + + 'exec [files..]', + 'Run the specified Bolt packages/scripts', + + yargs => yargs + .string('work-dir') + .describe('work-dir', 'The working directory where files will be resolved against.') + .default('work-dir', '.'), + + args => { + + const files = toArray(args.files as string | string[]).map(p => new TextFile(p)); + + if (files.length > 0) { + + const program = new Program(files); + + for (const file of files) { + program.eval(file) + } + + } else { + + throw new Error(`Executing packages is not yet supported.`) + + } } diff --git a/src/checker.ts b/src/checker.ts index 9be3700ed..ce0ed5bde 100644 --- a/src/checker.ts +++ b/src/checker.ts @@ -16,6 +16,17 @@ export class PrimType extends Type { } +export class FunctionType extends Type { + + constructor( + public paramTypes: Type[], + public returnType: Type, + ) { + super(); + } + +} + export class VariantType extends Type { constructor(public elementTypes: Type[]) { @@ -28,6 +39,8 @@ export const stringType = new PrimType() export const intType = new PrimType() export const boolType = new PrimType() export const voidType = new PrimType() +export const anyType = new PrimType() +export const noneType = new PrimType(); export class RecordType { @@ -78,6 +91,9 @@ function getFullName(node: Syntax) { let curr: Syntax | null = node; while (true) { switch (curr.kind) { + case SyntaxKind.Identifier: + out.unshift(curr.text) + break; case SyntaxKind.Module: out.unshift(curr.name.fullText); break; @@ -99,15 +115,56 @@ export class TypeChecker { protected types = new Map(); protected scopes = new Map(); - protected createType(node: Syntax): Type { + constructor() { + } - console.log(node) + protected inferTypeFromUsage(bindings: Patt, body: Body) { + return anyType; + } + + protected getTypeOfBody(body: Body) { + return anyType; + } + + protected createType(node: Syntax): Type { switch (node.kind) { case SyntaxKind.ConstExpr: return node.value.type; + case SyntaxKind.NewTypeDecl: + console.log(getFullName(node.name)) + this.symbols[getFullName(node.name)] = new PrimType(); + return noneType; + + case SyntaxKind.FuncDecl: + let returnType = anyType; + if (node.returnType !== null) { + returnType = this.getTypeOfNode(node.returnType) + } + if (node.body !== null) { + returnType = this.intersectTypes(returnType, this.getTypeOfBody(node.body)) + } + let paramTypes = node.params.map(param => { + let paramType = this.getTypeOfNode(param); + if (node.body !== null) { + paramType = this.intersectTypes( + paramType, + this.inferTypeFromUsage(param.bindings, node.body) + ) + } + return paramType + }) + return new FunctionType(paramTypes, returnType); + + case SyntaxKind.TypeRef: + const reffed = this.getTypeNamed(node.name.fullText); + if (reffed === null) { + throw new Error(`Could not find a type named '${node.name.fullText}'`); + } + return reffed; + case SyntaxKind.RecordDecl: const typ = new RecordType(map(node.fields, ([name, typ]) => ([name.text, typ]))); @@ -116,17 +173,11 @@ export class TypeChecker { return typ; - // if (typeof node.value === 'bigint') { - // return intType; - // } else if (typeof node.value === 'string') { - // return stringType; - // } else if (typeof node.value === 'boolean') { - // return boolType; - // } else if (isNode(node.value)) { - // return this.getTypeNamed(`Bolt.AST.${SyntaxKind[node.value.kind]}`)! - // } else { - // throw new Error(`Unrecognised kind of value associated with ConstExpr`) - // } + case SyntaxKind.Param: + if (node.typeDecl !== null) { + return this.getTypeOfNode(node.typeDecl) + } + return anyType; default: throw new Error(`Could not derive type of ${SyntaxKind[node.kind]}`) @@ -136,8 +187,8 @@ export class TypeChecker { } getTypeNamed(name: string) { - return name in this.typeNames - ? this.typeNames[name] + return name in this.symbols + ? this.symbols[name] : null } @@ -152,13 +203,23 @@ export class TypeChecker { check(node: Syntax) { + this.getTypeOfNode(node); + switch (node.kind) { case SyntaxKind.Sentence: + case SyntaxKind.RecordDecl: + case SyntaxKind.NewTypeDecl: break; - case SyntaxKind.RecordDecl: - this.getTypeOfNode(node); + case SyntaxKind.FuncDecl: + if (node.body !== null) { + if (Array.isArray(node.body)) { + for (const element of node.body) { + this.check(element) + } + } + } break; case SyntaxKind.Module: @@ -191,6 +252,27 @@ export class TypeChecker { return scope } + protected intersectTypes(a: Type, b: Type): Type { + if (a === noneType || b == noneType) { + return noneType; + } + if (b === anyType) { + return a + } + if (a === anyType) { + return b; + } + if (a instanceof FunctionType && b instanceof FunctionType) { + if (a.paramTypes.length !== b.paramTypes.length) { + return noneType; + } + const returnType = this.intersectTypes(a.returnType, b.returnType); + const paramTypes = a.paramTypes.map((_, i) => this.intersectTypes(a.paramTypes[i], b.paramTypes[i])); + return new FunctionType(paramTypes, returnType) + } + return noneType; + } + // getMapperForNode(target: string, node: Syntax): Mapper { // return this.getScope(node).getMapper(target) // } diff --git a/src/compiler.ts b/src/compiler.ts index 80ae27f6d..9321fe045 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -95,6 +95,12 @@ export class Compiler { } switch (node.kind) { + + case SyntaxKind.Module: + for (const element of node.elements) { + this.compileDecl(element, preamble); + } + break; case SyntaxKind.ImportDecl: preamble.push({ diff --git a/src/evaluator.ts b/src/evaluator.ts index 7d9c3c47e..e6265dfd5 100644 --- a/src/evaluator.ts +++ b/src/evaluator.ts @@ -103,6 +103,18 @@ export class Evaluator { switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.Module: + for (const element of node.elements) { + this.eval(element); + } + break; + + case SyntaxKind.NewTypeDecl: + case SyntaxKind.RecordDecl: + case SyntaxKind.FuncDecl: + break; + case SyntaxKind.MatchExpr: const value = this.eval(node.value); for (const [pattern, result] of node.arms) { diff --git a/src/expander.ts b/src/expander.ts index e5f9076c9..37754a83f 100644 --- a/src/expander.ts +++ b/src/expander.ts @@ -129,7 +129,8 @@ export class Expander { return node; } - return new Module(node.isPublic, node.name, node.elements, null, node); + return new Module(node.isPublic, node.name, expanded, null, node); + } else if (node.kind === SyntaxKind.Sentence) { diff --git a/src/package.ts b/src/package.ts new file mode 100644 index 000000000..3acfd1359 --- /dev/null +++ b/src/package.ts @@ -0,0 +1,14 @@ + +import { TextFile } from "./ast" + +export class Package { + + constructor( + public name: string | null, + public files: TextFile[], + ) { + + } + +} + diff --git a/src/parser.ts b/src/parser.ts index 780066e54..81977dfe3 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -25,6 +25,7 @@ import { SourceElement, Module, RecordDecl, + NewTypeDecl, } from "./ast" import { stringType, intType } from "./checker" @@ -55,6 +56,8 @@ function describeKind(kind: SyntaxKind): string { return "':'" case SyntaxKind.Dot: return "'.'" + case SyntaxKind.RArrow: + return "'->'" case SyntaxKind.Comma: return "','" case SyntaxKind.ModKeyword: @@ -91,8 +94,52 @@ export class ParseError extends Error { } } +enum OperatorKind { + Prefix, + InfixL, + InfixR, + Suffix, +} + +interface OperatorInfo { + kind: OperatorKind; + arity: number; + name: string; + precedence: number; +} + export class Parser { + operatorTable = [ + [ + [OperatorKind.InfixL, 2, '&&'], + [OperatorKind.InfixL, 2, '||'] + ], + [ + [OperatorKind.InfixL, 2, '<'], + [OperatorKind.InfixL, 2, '>'], + [OperatorKind.InfixL, 2, '<='], + [OperatorKind.InfixL, 2, '>='] + ], + [ + [OperatorKind.InfixL, 2, '>>'], + [OperatorKind.InfixL, 2, '<<'] + ], + [ + [OperatorKind.InfixL, 2, '+'], + [OperatorKind.InfixL, 2, '-'], + ], + [ + [OperatorKind.InfixL, 2, '/'], + [OperatorKind.InfixL, 2, '*'], + [OperatorKind.InfixL, 2, '%'], + ], + [ + [OperatorKind.Prefix, '!'] + ], + ]; + + parseQualName(tokens: TokenStream): QualName { const path: Identifier[] = []; @@ -171,7 +218,23 @@ export class Parser { // Assuming first token is 'syntax' tokens.get(); - throw new Error('not implemented') + const t1 = tokens.get(); + if (t1.kind !== SyntaxKind.Braced) { + throw new ParseError(t1, [SyntaxKind.Braced]) + } + + const innerTokens = t1.toTokenStream(); + + const pattern = this.parsePattern(innerTokens) + + const t2 = innerTokens.get(); + if (t2.kind !== SyntaxKind.RArrow) { + throw new ParseError(t2, [SyntaxKind.RArrow]); + } + + const body = this.parseBody(innerTokens); + + return new Macro(pattern, body) } @@ -255,7 +318,7 @@ export class Parser { } parseStmt(tokens: TokenStream): Stmt { - + this.parseCallExpr(tokens) } parseRecordDecl(tokens: TokenStream): RecordDecl { @@ -329,6 +392,34 @@ export class Parser { } } + parseNewType(tokens: TokenSteam): NewTypeDecl { + + let isPublic = false; + let t0 = tokens.get(); + if (t0.kind !== SyntaxKind.Identifier) { + throw new ParseError(t0, [SyntaxKind.PubKeyword, SyntaxKind.NewTypeKeyword]) + } + if (t0.text === 'pub') { + isPublic = true; + t0 = tokens.get(); + if (t0.kind !== SyntaxKind.Identifier) { + throw new ParseError(t0, [SyntaxKind.NewTypeKeyword]) + } + } + + if (t0.text !== 'newtype') { + throw new ParseError(t0, [SyntaxKind.NewTypeKeyword]) + } + + const name = tokens.get(); + if (name.kind !== SyntaxKind.Identifier) { + throw new ParseError(name, [SyntaxKind.Identifier]) + } + + return new NewTypeDecl(isPublic, name) + + } + parseFuncDecl(tokens: TokenStream, origNode: Syntax | null): FuncDecl { let target = "Bolt"; @@ -489,6 +580,10 @@ export class Parser { } } switch (kw.text) { + case 'newtype': + return this.parseNewType(tokens); + case 'syntax': + return this.parseSyntax(tokens); case 'mod': return this.parseModDecl(tokens); case 'fn': @@ -500,13 +595,62 @@ export class Parser { case 'enum': return this.parseVariantDecl(tokens); default: - throw new ParseError(kw, [SyntaxKind.ModKeyword, SyntaxKind.LetKeyword, SyntaxKind.FnKeyword, SyntaxKind.EnumKeyword, SyntaxKind.StructKeyword]) + try { + return this.parseExpr(tokens) + } catch (e) { + if (e instanceof ParseError) { + throw new ParseError(kw, [...e.expected, SyntaxKind.ModKeyword, SyntaxKind.LetKeyword, SyntaxKind.FnKeyword, SyntaxKind.EnumKeyword, SyntaxKind.StructKeyword]) + } else { + throw e; + } + } } } else { return this.parseStmt(tokens) } } + getOperatorDesc(seekArity: number, seekName: string): OperatorInfo { + for (let i = 0; i < this.operatorTable.length; ++i) { + for (const [kind, arity, name] of this.operatorTable[i]) { + if (artity == seekArity && name === seekName) { + return { + kind, + name, + arity, + precedence: i + } + } + } + } + } + + parseBinOp(tokens: TokenStream, lhs: Expr , minPrecedence: number) { + let lookahead = tokens.peek(1); + while (true) { + if (lookahead.kind !== SyntaxKind.Operator) { + break; + } + const lookaheadDesc = this.getOperatorDesc(2, lookahead.text); + if (lookaheadDesc === null || lookaheadDesc.precedence < minPrecedence) { + break; + } + const op = lookahead; + const opDesc = this.getOperatorDesc(2, op.text); + tokens.get(); + let rhs = this.parsePrimExpr(tokens) + lookahead = tokens.peek() + while (lookaheadDesc.arity === 2 + && ((lookaheadDesc.precedence > opDesc.precedence) + || lookaheadDesc.kind === OperatorKind.InfixR && lookaheadDesc.precedence === opDesc.precedence)) { + rhs = this.parseBinOp(tokens, rhs, lookaheadDesc.precedence) + } + lookahead = tokens.peek(); + lhs = new CallExpr(new RefExpr(new QualName(op, [])), [lhs, rhs]); + } + return lhs + } + parseCallExpr(tokens: TokenStream): CallExpr { const operator = this.parsePrimExpr(tokens) diff --git a/src/program.ts b/src/program.ts new file mode 100644 index 000000000..70e547622 --- /dev/null +++ b/src/program.ts @@ -0,0 +1,56 @@ + +import * as path from "path" +import * as fs from "fs" + +import { FastStringMap } from "./util" +import { Parser } from "./parser" +import { TypeChecker } from "./checker" +import { Evaluator } from "./evaluator" +import { Expander } from "./expander" +import { Scanner } from "./scanner" +import { Compiler } from "./compiler" +import { TextFile, SourceFile } from "./ast" + +export class Program { + + parser: Parser + evaluator: Evaluator; + checker: TypeChecker; + expander: Expander; + + sourceFiles = new Map(); + + constructor(public files: TextFile[]) { + this.checker = new TypeChecker(); + this.parser = new Parser(); + this.evaluator = new Evaluator(this.checker); + this.expander = new Expander(this.parser, this.evaluator, this.checker); + for (const file of files) { + const contents = fs.readFileSync(file.fullPath, 'utf8'); + const scanner = new Scanner(file, contents) + this.sourceFiles.set(file, scanner.scan()); + } + } + + compile(file: TextFile) { + const original = this.sourceFiles.get(file); + if (original === undefined) { + throw new Error(`File ${file.path} does not seem to be part of this Program.`) + } + const expanded = this.expander.getFullyExpanded(original) as SourceFile; + const compiler = new Compiler(this.checker, { target: "JS" }) + const compiled = compiler.compile(expanded) + return compiled + } + + eval(file: TextFile) { + const original = this.sourceFiles.get(file); + if (original === undefined) { + throw new Error(`File ${file.path} does not seem to be part of this Program.`) + } + const expanded = this.expander.getFullyExpanded(original) as SourceFile; + return this.evaluator.eval(expanded) + } + +} + diff --git a/stdlib/Boltfile b/stdlib/Boltfile new file mode 100644 index 000000000..e69de29bb diff --git a/stdlib/lang/bolt.bolt b/stdlib/lang/bolt.bolt new file mode 100644 index 000000000..49a4b7bb8 --- /dev/null +++ b/stdlib/lang/bolt.bolt @@ -0,0 +1,24 @@ + +mod Lang.Bolt.AST { + + pub struct Pos { + offset: Int, + line: Int, + column: Int, + } + + pub struct Span { + file: File, + start: TextPos, + end: TextPos, + } + + pub struct Identifier { + text: String, + span: Option, + orig_node: Option, + parent: Option, + } + +} + diff --git a/stdlib/main.bolt b/stdlib/main.bolt new file mode 100644 index 000000000..e69de29bb diff --git a/stdlib/math.bolt b/stdlib/math.bolt new file mode 100644 index 000000000..4a44ffbe3 --- /dev/null +++ b/stdlib/math.bolt @@ -0,0 +1,17 @@ + +newtype Int; + +pub fn fac(n: Int) -> Int { + if n == 0 { + return 1 + } else { + return fac(n-1) + } +} + +fn (a: Int) + (b: Int) -> Int { + +} + +precedence a + b < a * b; + diff --git a/stdlib/syntax.bolt b/stdlib/syntax.bolt new file mode 100644 index 000000000..b1bef73b3 --- /dev/null +++ b/stdlib/syntax.bolt @@ -0,0 +1,11 @@ + +syntax { + quote { + macro $name: QualName { + + } + } => { + + } +} +