diff --git a/charon-runtime.lua b/charon-runtime.lua index fc1bb02..a17e77a 100644 --- a/charon-runtime.lua +++ b/charon-runtime.lua @@ -159,7 +159,7 @@ function charon.list_has(tbl, element, finder) end function charon.list_get(tbl, key) - assert(getmetatable(tbl) == List, "list/get only accepts vectors."); + assert(getmetatable(tbl) == List, "list/get only accepts lists."); assert(type(key) == 'number', "list/get key can only be numeric."); local field = tbl[key]; if field == nil then return charon.Unit; end @@ -167,9 +167,9 @@ function charon.list_get(tbl, key) end function charon.list_merge(left, right) - assert(getmetatable(left) == List, "list/merge only accepts vectors."); - assert(getmetatable(right) == List, "list/merge only accepts vectors."); - local vec = charon.vector{}; + assert(getmetatable(left) == List, "list/merge only accepts lists."); + assert(getmetatable(right) == List, "list/merge only accepts lists."); + local vec = charon.list{}; for _, v in pairs(left) do vec[#vec + 1] = v; end @@ -180,12 +180,12 @@ function charon.list_merge(left, right) end function charon.list_len(left) - assert(getmetatable(left) == List, "list/add only accepts vectors."); + assert(getmetatable(left) == List, "list/add only accepts lists."); return #left; end function charon.list_reduce_indexed(vec, fn, value) - assert(getmetatable(vec) == List, "list/reduce-indexed only accepts vectors."); + assert(getmetatable(vec) == List, "list/reduce-indexed only accepts lists."); local start = 1; if value == nil then start = 2; @@ -198,7 +198,7 @@ function charon.list_reduce_indexed(vec, fn, value) end function charon.list_reduce(vec, fn, value) - assert(getmetatable(vec) == List, "list/reduce only accepts vectors."); + assert(getmetatable(vec) == List, "list/reduce only accepts lists."); local start = 1; if value == nil then start = 2; @@ -211,8 +211,8 @@ function charon.list_reduce(vec, fn, value) end function charon.list_append(left, ...) - assert(getmetatable(left) == List, "list/append only accepts vectors."); - local vec = charon.vector{}; + assert(getmetatable(left) == List, "list/append only accepts lists."); + local vec = charon.list{}; for _, v in pairs(left) do vec[#vec + 1] = v; end @@ -223,8 +223,8 @@ function charon.list_append(left, ...) end function charon.list_prepend(left, ...) - assert(getmetatable(left) == List, "list/prepend only accepts vectors."); - local vec = charon.vector{}; + assert(getmetatable(left) == List, "list/prepend only accepts lists."); + local vec = charon.list{}; for _, v in pairs(left) do vec[#vec + 1] = v; end @@ -235,9 +235,9 @@ function charon.list_prepend(left, ...) end function charon.list_drop(left, n) - assert(getmetatable(left) == List, "list/drop only accepts vectors."); + assert(getmetatable(left) == List, "list/drop only accepts lists."); assert(type(n) == 'number', "list/drop second argument must be a number."); - local vec = charon.vector{}; + local vec = charon.list{}; local min = math.min(#left, n); for i=1, min do vec[i] = left[i]; @@ -246,9 +246,9 @@ function charon.list_drop(left, n) end function charon.list_drop_left(left, n) - assert(getmetatable(left) == List, "list/drop-left only accepts vectors."); + assert(getmetatable(left) == List, "list/drop-left only accepts lists."); assert(type(n) == 'number', "list/drop-left second argument must be a number."); - local vec = charon.vector{}; + local vec = charon.list{}; local min = math.min(#left, n); for i=min, #left do vec[i] = left[i]; @@ -257,8 +257,8 @@ function charon.list_drop_left(left, n) end function charon.list_map(tbl, mapper) - assert(getmetatable(tbl) == List, "list/map only accepts vectors."); - local vec = charon.vector{}; + assert(getmetatable(tbl) == List, "list/map only accepts lists."); + local vec = charon.list{}; for k, v in pairs(tbl) do vec[#vec + 1] = mapper(v, k); end @@ -266,8 +266,8 @@ function charon.list_map(tbl, mapper) end function charon.list_filter(tbl, filter) - assert(getmetatable(tbl) == List, "list/map only accepts vectors."); - local vec = charon.vector{}; + assert(getmetatable(tbl) == List, "list/map only accepts lists."); + local vec = charon.list{}; for k, v in pairs(tbl) do if filter(v, k) then vec[#vec + 1] = v; @@ -277,7 +277,7 @@ function charon.list_filter(tbl, filter) end function charon.list_each(tbl, consumer) - assert(getmetatable(tbl) == List, "list/each only accepts vectors."); + assert(getmetatable(tbl) == List, "list/each only accepts lists."); for k, v in pairs(tbl) do consumer(v, k); end @@ -471,7 +471,7 @@ function charon.range(from, to, inc) assert(inc == nil or type(inc) == 'number' , 'Range\'s third argument can only be a number or not provided. Saw ' .. tostring(inc) .. ' instead.') if inc == nil then inc = 1; end - local p = {}; + local p = charon.list{}; local j = 1; if from > to then for i=from, to, -inc do diff --git a/docs.md b/docs.md index af02cf8..86a1a3e 100644 --- a/docs.md +++ b/docs.md @@ -99,6 +99,47 @@ A list of objects, functions and operators. - `file/write` - `file/read` +## Module system + +Charon lang modules are exported tables. To maximize the interop between pure +Lua modules and Charon modules, Charon exports a table with all public objects, +either functions or variables, with their names untouched. + +The same goes when importing, the `import` macro will bind to a private local +variable the imported module. The macro also has the option to deconstruct the +object and assign it's fields. + +```clj +; Imports the whole table +(import module :from "module") +(println! (+ (object/get module "a") module::b module::c)) + +; Or just the needed fields +(import [a b c] :from "module") +(println! (+ a b c)) +``` + +At the moment there is no metadata field exported from modules, but it will +have in a future. + +Modules will tell if the exported object has a known type, if it is callable or +not and the purity in case of callable objects, even attached metadata and +docstring. + +```clj +(def a-func [a b] + "It sums" + (+ a b)) +;; Other module +(import [a-func] :from "a-module") +(def-value :no-export a-func-meta (object/get-meta a-func)) +(println! "a-func:" a-func-meta::docstring) +(println! "a-func purity:" a-func-meta::pure?) +``` + +That's a practical example of how to make a runtime read of the module's +metadata, **not implemented yet!**. + ## Library objects and collections ### string diff --git a/package.json b/package.json index 812dfc2..b192c9d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "charon", - "version": "0.8.1", + "version": "0.9.0", "preview": true, "description": "Charon language compiler", "main": "dist/index.js", diff --git a/readme.md b/readme.md index d1251cc..f3c30e6 100644 --- a/readme.md +++ b/readme.md @@ -87,6 +87,8 @@ of "assigning" a variable as other languages do. ## Standard library +Module system is described in [docs/Module System](docs.md#module-system). + Charon standard library is small, it relies on Lua standard library for most things. diff --git a/resources/charon.pegjs b/resources/charon.pegjs index 70b7f1f..b3e3518 100644 --- a/resources/charon.pegjs +++ b/resources/charon.pegjs @@ -3,16 +3,17 @@ */ Program - = _ program:(r:Invoke _ { return r })* + = _ program:(r:Invoke _ { return r })* (';' [^\r\n]*)? { return { type: 'Program', program } } Term - = Vector + = List / Table / Invoke / Literal + / AccessExpression / NAME / WILDCARD @@ -52,23 +53,23 @@ AccessSegment } } -Vector - = LSQUARE_BRACE _ list:(e:Term _ { return e })* RSQUARE_BRACE +List + = LSQUARE_BRACE _ values:(e:Term _ { return e })* RSQUARE_BRACE { return { _location: location(), - type: 'Vector', - list + type: 'List', + values } } Table - = LBRACE _ list:(e:Term _ { return e })* RBRACE + = LBRACE _ values:(e:Term _ { return e })* RBRACE { return { _location: location(), type: 'Table', - list + values } } @@ -95,7 +96,7 @@ NUMBER { return { _location: location(), type: 'Token', value, name: 'NUMBER' } } STRING - = value:$('"' [^"]* '"') + = value:$('"' ('\\"' / [^"])* '"') { return { _location: location(), type: 'Token', value, name: 'STRING' } } SYMBOL diff --git a/src/Compiler.ts b/src/Compiler.ts index 3b6f601..37681ba 100644 --- a/src/Compiler.ts +++ b/src/Compiler.ts @@ -125,6 +125,14 @@ Please do not hesitate to provide additional steps for reproduction. private readonly PKG = '__local_package'; private code = ''; + private genEmbedRuntime() { + const rt = readFileSync('charon-runtime.lua') + .toString() + .replace(/(local charon.+?\n)/, '-- $1') + .replace(/(return charon;)/, '-- $1'); + return (`\n${rt}`).replace(/\n/g, '\n '); + } + /** * Compiles the source code to target language. * @param input The source code to be compiled. @@ -135,7 +143,7 @@ Please do not hesitate to provide additional steps for reproduction. this.code = input.toString(); const ast = this.parser.parse(input); const rt = this.options.embedRuntime - ? `local charon = (function()${('\n' + readFileSync('charon-runtime.lua').toString()).replace(/\n/g, '\n ')}\nend)();` + ? `local charon = {};\ndo${this.genEmbedRuntime()}\nend` : `local charon = require 'charon-runtime';` ; const header = `${rt}\nlocal ${this.PKG} = {};\n`; @@ -150,12 +158,14 @@ Please do not hesitate to provide additional steps for reproduction. return program.program.map(this.genInvoke.bind(this)).join(';\n'); } - private genLetLocalBind(args: ast.Vector): String { + private genLetLocalBind(args: ast.List): String { const bind = [] - for (let i = 0; i < args.list.length; i += 2) { - const name = args.list[i]; - if (!ast.isName(name)) throw `Let binding pairs must start with names!`; - const val = args.list[i + 1]; + for (let i = 0; i < args.values.length; i += 2) { + const name = args.values[i]; + if (!ast.isName(name)) { + throw new SyntaxError(`Let binding pairs must start with names!`, name._location); + } + const val = args.values[i + 1]; const bound = this.registerVar(name, DataKind.LOCAL); bind.push(`local ${bound.name} = ${this.genTerm(val)}`); } @@ -164,7 +174,7 @@ Please do not hesitate to provide additional steps for reproduction. private genLet(invoke: ast.Invoke): string { const first = invoke.args[0] - if (!ast.isVector(first)) throw 'First should be a binding array!'; + if (!ast.isList(first)) throw 'First should be a binding array!'; const $ = new Compiler(this, this.pureContext, this.options); const bind = $.genLetLocalBind(first); return `(function(${$.closure}) ${bind}; ${ @@ -208,8 +218,9 @@ Please do not hesitate to provide additional steps for reproduction. * @param invoke */ private processMacro(invoke: ast.Invoke): string { - if (!ast.isName(invoke.target)) + if (!ast.isName(invoke.target)) { throw new BadMacroDefinition(`Attempting to expand macro with an invalid name! Symbols and access expressions are not allowed as macro names.`, invoke._location); + } const { target: name, args } = invoke; const ref = this.getCheckedReference(name); switch (ref.name) { @@ -253,10 +264,10 @@ Please do not hesitate to provide additional steps for reproduction. case '#for': { return new Compiler(this, this.pureContext, this.options).genFor(invoke); } - case '#fn':{ + case '#fn': { const arg0 = args[0]; - if (!ast.isVector(arg0)) - throw new SyntaxError(`Function expression's first argument must be a binding vector!`); + if (!ast.isList(arg0)) + throw new SyntaxError(`Function expression's first argument must be a binding vector!`, invoke._location); const argInput = this.genBindingVector(arg0); return `(function() local __self_ref__; __self_ref__ = (function(${argInput}) ${this.termListLastReturns(args.slice(1))} end); return __self_ref__; end)()`; } @@ -267,9 +278,9 @@ Please do not hesitate to provide additional steps for reproduction. } case '#catch': { const binding = args[0]; - if (!ast.isVector(binding)) + if (!ast.isList(binding)) throw new SyntaxError(`Catch first argument must be a binding vector with one argument!`); - const err = binding.list[0]; + const err = binding.values[0]; if (!ast.isName(err)) throw new SyntaxError(`Catch's first binding element must be a name!`); const scope = new Compiler(this, undefined, this.options); @@ -286,7 +297,9 @@ Please do not hesitate to provide additional steps for reproduction. case '#thread-parallel-last': return this.genTerm(threadParallel(invoke, args, Array.prototype.push)); case '#not': - if (args.length != 1) throw `not operator only accepts one argument.`; + if (args.length != 1) { + throw new SyntaxError(`not operator only accepts one argument.`, invoke._location); + } return `(not ${this.genTerm(args[0])})`; case '#eq': case '#neq': @@ -305,38 +318,16 @@ Please do not hesitate to provide additional steps for reproduction. case '#mul': case '#pow': return this.genBOP(invoke, ref.name, args); - case '#import': { - const targetOrBinder = args[0]; - const from = args[1]; - if (!ast.isSymbol(from)) - throw new SyntaxError(`Import's second argument must be a symbol!`, from._location); - if (from.value !== 'from') - throw new SyntaxError(`Import's second argument is a symbol: expected ':from' but found ':${from.value}'`, from._location); - const toImport = args[2]; - if (ast.isName(targetOrBinder)) { - const local = this.registerVar(targetOrBinder, DataKind.LOCAL); - return `local ${local.name} = require(${this.genTerm(toImport)});`; - } else if (ast.isVector(targetOrBinder)) { - const src = this.genTerm(toImport); - const names = targetOrBinder.list.map(t => { - if (!ast.isName(t)) throw new SyntaxError(`Import binding vector expects names only.`, t._location); - return t; - }); - for (const term of names) { - this.registerVar(term, DataKind.LOCAL, Scope.LOCAL); - } - const str = names.map(this.genTerm.bind(this)); - const bindDeclare = str.map(s => `local ${s};\n`).join(''); - const bind = names.map(s => `${this.genTerm(s)} = __package["${s.value}"];`); - return `${bindDeclare}do local __package = require(${src}); ${bind} end` - } else { - throw new SyntaxError(`Unexpected ${targetOrBinder.type} found as first import argument. Expecting name or binding vector.`, targetOrBinder._location); - } - } + case '#import': + return this.genImport(args); case '#def-value': { - if (this.pureContext) throw `Value definition cannot be done from pure context.`; + if (this.pureContext) { + throw new PurityViolationError(`Value definition cannot be done from pure context.`, invoke._location); + } const name = args[0]; - if (!ast.isName(name)) throw `def-value must start with a name!`; + if (!ast.isName(name)) { + throw new SyntaxError(`def-value must start with a name!`, invoke._location); + } const val = args[1]; const local = this.registerVar(name, DataKind.LOCAL, Scope.GLOBAL); return `${this.genScopedRef(local)} = ${this.genTerm(val)};`; @@ -361,10 +352,44 @@ Please do not hesitate to provide additional steps for reproduction. } } const macro = this.getMacro(ref?.name ?? ''); - if (macro == null) throw `Undefined macro ${name}`; + if (macro == null) { + throw new ReferenceError(name.value, `Undefined macro ${name}`, invoke._location); + } return macro(ref?.name ?? '', args); } + private genImport(args: ast.Term[]): string { + const targetOrBinder = args[0]; + if (ast.isString(targetOrBinder) || (ast.isName(targetOrBinder) && args.length === 1)) { + return `require(${this.genTerm(targetOrBinder)});` + } + const from = args[1]; + if (!ast.isSymbol(from)) + throw new SyntaxError(`Import's second argument must be a symbol!`, from._location); + if (from.value !== 'from') + throw new SyntaxError(`Import's second argument is a symbol: expected ':from' but found ':${from.value}'`, from._location); + const toImport = args[2]; + if (ast.isName(targetOrBinder)) { + const local = this.registerVar(targetOrBinder, DataKind.LOCAL); + return `local ${local.name} = require(${this.genTerm(toImport)});`; + } else if (ast.isList(targetOrBinder)) { + const src = this.genTerm(toImport); + const names = targetOrBinder.values.map(t => { + if (!ast.isName(t)) throw new SyntaxError(`Import binding vector expects names only.`, t._location); + return t; + }); + for (const term of names) { + this.registerVar(term, DataKind.LOCAL, Scope.LOCAL); + } + const str = names.map(this.genTerm.bind(this)); + const bindDeclare = str.map(s => `local ${s};\n`).join(''); + const bind = names.map(s => `${this.genTerm(s)} = __package["${s.value}"];`); + return `${bindDeclare}do local __package = require(${src}); ${bind} end` + } else { + throw new SyntaxError(`Unexpected ${targetOrBinder.type} found as first import argument. Expecting name or binding vector.`, targetOrBinder._location); + } + } + /** * Generates a binary-like operator such as +,-, or /... * @param invoke @@ -442,26 +467,26 @@ Please do not hesitate to provide additional steps for reproduction. private genFor(invoke: ast.Invoke): string { const { args } = invoke; const args0 = args[0]; - if (!ast.isVector(args0)) { + if (!ast.isList(args0)) { throw new SyntaxError(`For loop first argument must be a binding vector!`, args0._location); } - if (args0.list.length < 2 || args0.list.length > 3) { + if (args0.values.length < 2 || args0.values.length > 3) { throw new SyntaxError(`For loop binding vector must have either two or three arguments.`, args0._location); } - const cardinal: 2 | 3 = args0.list.length as any; - const v = args0.list[0]; + const cardinal: 2 | 3 = args0.values.length as any; + const v = args0.values[0]; if (!ast.isName(v)) { throw new SyntaxError(`For loop binding vector's first argument must be a name!`, v._location); } this.registerVar(v, DataKind.LOCAL, Scope.LOCAL); - const k = args0.list[1]; + const k = args0.values[1]; if (cardinal === 3 && !ast.isName(k)) { throw new SyntaxError(`If looping in pairs the second argument (the key) must be also a name!`, v._location); } if (cardinal === 3 && ast.isName(k)) { this.registerVar(k, DataKind.LOCAL, Scope.LOCAL); } - const iterable = cardinal === 3 ? args0.list[2] : k; + const iterable = cardinal === 3 ? args0.values[2] : k; let body: Optional = null; const terms = args.slice(1).map(this.genTerm.bind(this)).join(';'); if (cardinal === 3) { @@ -638,9 +663,9 @@ Please do not hesitate to provide additional steps for reproduction. } } - private genBindingVector(bind: ast.Vector): string { + private genBindingVector(bind: ast.List): string { const args = []; - for (const arg of bind.list) { + for (const arg of bind.values) { if (!ast.isName(arg)) throw 'Binding vector should contain only names!'; const local = this.registerVar(arg, DataKind.LOCAL); args.push(this.genScopedRef(local)); @@ -654,7 +679,7 @@ Please do not hesitate to provide additional steps for reproduction. const name = invoke.args[0]; if (!ast.isName(name)) throw 'First arg should be a name!'; const bind = invoke.args[1]; - if (!ast.isVector(bind)) throw 'Missing binding vector!'; + if (!ast.isList(bind)) throw 'Missing binding vector!'; const local = this.registerVar(name, pure ? DataKind.FUNC : DataKind.IMPURE_FUNC, Scope.GLOBAL); const $ = new Compiler(this, pure, this.options); const args = $.genBindingVector(bind); @@ -678,7 +703,7 @@ Please do not hesitate to provide additional steps for reproduction. out.push(this.genTerm(item)); } } - return out.join(';') + return out.join(';'); } private genDefBody(invoke: ast.Invoke): string { @@ -689,16 +714,16 @@ Please do not hesitate to provide additional steps for reproduction. private genTable(table: ast.Table): string { const pairs: string[] = []; - for (let i = 0; i < table.list.length; i += 2) { - const l = this.genTerm(table.list[i]); - const r = this.genTerm(table.list[i + 1] ?? { type: 'Token', name: 'NAME', value: 'unit' }); + for (let i = 0; i < table.values.length; i += 2) { + const l = this.genTerm(table.values[i]); + const r = this.genTerm(table.values[i + 1] ?? { type: 'Token', name: 'NAME', value: 'unit' }); pairs.push(`[${l}] = ${r}`); } return `charon.table{ ${pairs.join()} }`; } - private genVector(vector: ast.Vector): string { - return `charon.list{ ${vector.list.map(this.genTerm.bind(this)).join()} }` + private genList(list: ast.List): string { + return `charon.list{ ${list.values.map(this.genTerm.bind(this)).join()} }` } private genSymbol(symbol: ast.SYMBOL): string { @@ -716,10 +741,16 @@ Please do not hesitate to provide additional steps for reproduction. private genTerm(term: ast.Term): string { switch (term.type) { - case 'Vector': return this.genVector(term); + case 'List': return this.genList(term); case 'Table': return this.genTable(term); case 'Invoke': return this.genInvoke(term); - case 'AccessExpression': return this.genAccessExpression(term); + case 'AccessExpression': { + const found = term.segments.find(segment => segment.mode === ':' || segment.mode === ':?'); + if (found != null) { + throw new SyntaxError(`Except for function calls, all property access must be unbounded (using the :: operator), expecting "${term.root.value}::${term.segments.map(s => s.name.value).join('::')}" but saw "${term.root.value}${term.segments.map(s => `${s.mode}${s.name.value}`)}"`, term._location); + } + return this.genAccessExpression(term); + } case 'Token': switch (term.name) { case 'WILDCARD': throw new SyntaxError(`Unexpected wildcard symbol.`, term._location); diff --git a/src/CompilerExtras.ts b/src/CompilerExtras.ts index 2b522c7..943a53e 100644 --- a/src/CompilerExtras.ts +++ b/src/CompilerExtras.ts @@ -200,7 +200,7 @@ export function thread(args: ast.Term[], applier: Applier): ast.Term { return receiver; } -export function threadParallel(invoke: ast.Invoke, args: ast.Term[], applier: Applier): ast.Vector { +export function threadParallel(invoke: ast.Invoke, args: ast.Term[], applier: Applier): ast.List { const args0 = args[0]; const list = args.slice(1).map(term => { if (!ast.isInvoke(term)) { @@ -213,9 +213,9 @@ export function threadParallel(invoke: ast.Invoke, args: ast.Term[], applier: Ap return term; }); return { - type: 'Vector', + type: 'List', _location: invoke._location, - list + values: list }; } diff --git a/src/Main.ts b/src/Main.ts index 2f834a2..83e0836 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -29,7 +29,7 @@ import * as yargs from 'yargs'; import { EntryPoint } from './exec'; import { version } from '../package.json'; import { writeFileSync, readFileSync } from 'fs'; -import { CharonError } from './errors'; +import { CharonError, CompileError } from './errors'; /** * Entry point class. @@ -101,7 +101,14 @@ export default class Main { mode: args.type as any }); } catch (err) { - console.error(err); + if (err instanceof CompileError) { + console.error(err.message); + if (err.cause instanceof CharonError && err.cause.cause != null) { + console.error(err.cause.cause.message); + } + } else { + console.error(err); + } return 2; } return 0; diff --git a/src/ast.ts b/src/ast.ts index 9a9c042..0f5ddc9 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -23,20 +23,12 @@ SOFTWARE. */ // Abstract syntax tree definitions -export function isName(term: Term): term is NAME { - return term.type === 'Token' && term.name === 'NAME'; -} - -export function isSymbol(term: Term): term is SYMBOL { - return term.type === 'Token' && term.name === 'SYMBOL'; -} - export function isAccessExpression(term: Term): term is AccessExpression { return term.type === 'AccessExpression'; } -export function isVector(term: Term): term is Vector { - return term.type === 'Vector'; +export function isList(term: Term): term is List { + return term.type === 'List'; } export function isTable(term: Term): term is Table { @@ -51,6 +43,29 @@ export function isWildcard(term: Term): term is WILDCARD { return term.type === 'Token' && term.name === 'WILDCARD'; } +export function isLiteral(term: Term): term is Literal { + return term.type === 'Token' && + ( term.name === 'NUMBER' + || term.name === 'STRING' + || term.name === 'SYMBOL'); +} + +export function isName(term: Term): term is NAME { + return term.type === 'Token' && term.name === 'NAME'; +} + +export function isSymbol(term: Term): term is SYMBOL { + return term.type === 'Token' && term.name === 'SYMBOL'; +} + +export function isNumber(term: Term): term is NUMBER { + return term.type === 'Token' && term.name === 'NUMBER'; +} + +export function isString(term: Term): term is STRING { + return term.type === 'Token' && term.name === 'STRING'; +} + export type CodeLocation = { start: { offset: number, line: number, column: number }; @@ -69,7 +84,7 @@ export type Program = } export type Term - = Vector + = List | Table | Invoke | Literal @@ -99,16 +114,16 @@ export type AccessSegment = mode: '::?' | ':?' | '::' | ':'; } & LocatedCode -export type Vector = +export type List = { - type: 'Vector'; - list: Term[]; + type: 'List'; + values: Term[]; } & LocatedCode export type Table = { type: 'Table'; - list: Term[]; + values: Term[]; } & LocatedCode export type Literal diff --git a/src/errors.ts b/src/errors.ts index 4b6dcf6..d64fe92 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -74,7 +74,7 @@ export class CompileError extends Error { return `${sourceName}:${line}:${column} ${before}${sub}${after} ${r` ${before}`}${r`^${sub}`} -${cause}`; +${cause.name}: ${cause.message}`; } } @@ -88,6 +88,7 @@ export class CharonError extends Error { public readonly cause: Optional = null) { super(message); + this.name = 'CharonError'; } } @@ -95,19 +96,25 @@ export class CharonError extends Error { * This exception is thrown when unhandled edge cases occur. This means that * there is a bug in the compiler. */ -export class UnexpectedParseException extends CharonError {} +export class UnexpectedParseException extends CharonError { + name = 'UnexpectedParseException'; +} /** * Syntax errors occur when parsing the Charon source code, and encounter an * unknown token. */ -export class SyntaxError extends CharonError {} +export class SyntaxError extends CharonError { + name = 'SyntaxError'; +} /** * This error is thrown when the compiler is able to detect purity violations * in function definitions. */ -export class PurityViolationError extends CharonError {} +export class PurityViolationError extends CharonError { + name = 'PurityViolationError'; +} /** * This error is thrown when attempting to reference something unknown. @@ -120,6 +127,7 @@ export class ReferenceError extends CharonError { cause: Optional = null) { super(message, origin, cause); + this.name = 'ReferenceError'; } } @@ -129,4 +137,6 @@ export class ReferenceError extends CharonError { * This might happen if a macro is defined using access expressions for * instance, which points to a bug in the compiler. */ -export class BadMacroDefinition extends CharonError {} +export class BadMacroDefinition extends CharonError { + name = 'BadMacroDefinition'; +}