diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e11a5f..72f786e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [0.4.4] - 2019-10-12 +### Added +- Infix macros +- Overloaded macros +- `$Z` macro to access gloabl namespace ## [0.4.0] - 2019-10-01 ### Added - Macro system and `include` @@ -168,4 +173,5 @@ [0.3.14]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.3.14 [0.3.19]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.3.19 [0.3.20]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.3.20 -[0.4.0]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.4.0 \ No newline at end of file +[0.4.0]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.4.0 +[0.4.4]: https://www.npmjs.com/package/@zlanguage/zcomp/v/0.4.4 \ No newline at end of file diff --git a/src/compiler/parse.js b/src/compiler/parse.js index 3e251aa..ab6069b 100644 --- a/src/compiler/parse.js +++ b/src/compiler/parse.js @@ -921,6 +921,10 @@ function expr({ infix = true } = {}) { break; case "$": advance(); + if (tok.id === "Z") { + zeroth = "$Z"; + break; + } // isMacro = true; if (macros[tok.id] === undefined) { return error(`Undefined macro ${tok.id}.`); @@ -1763,7 +1767,7 @@ function paramMacro() { const ids = []; let correctPath; let err; - macroParams.forEach(function handle(param, p = params) { + macroParams.every(function handle(param, p = params) { const { name, captureType } = param; if (p === macros[macroName].divergencePoint) { const ops = macros[macroName].options; @@ -1771,9 +1775,13 @@ function paramMacro() { correctPath = ops.indexOf(tok.id); if (correctPath === -1) { err = error(`Macro "${macroName}" is only overloaded for the following identifiers: ${ops.map(op => `"${op}"`).join(", ")}. There is no overload available for "${tok.id}".`) + return false; } params[name] = name.startsWith("\"") ? name : "\"" + name + "\""; - return; + macros[macroName].paths[correctPath].params.slice(macros[macroName].divergencePoint + 1).forEach((param, p) => { + handle(param, p + macros[macroName].divergencePoint + 1); + }); + return false; } if (typeof p === "number") { p = params; @@ -1813,6 +1821,7 @@ function paramMacro() { advance(); break; } + return true; }) if (err) { return [err]; @@ -1864,7 +1873,7 @@ function statements() { const statements = []; let nextStatement; while (true) { - if (tok && tok.id === "$") { + if (tok && tok.id === "$" && nextTok.id !== "Z") { advance("$"); if (macros[tok.id] === undefined) { statements.push(error(`Undefined macro ${tok.id}.`)); @@ -1987,34 +1996,36 @@ function parseMacro(tokGen, exp = true) { } function resolveOpMacros(ast) { - ast.forEach((statement, index) => { - if (statement.type === "invocation" && Object.keys(macros).includes(statement.zeroth)) { - const macroName = statement.zeroth; - const paramNames = macros[macroName].params.map(param => param.name); - const params = {}; - params[paramNames[0]] = resolveOpMacros([statement.wunth[0]])[0]; - params[paramNames[1]] = resolveOpMacros([statement.wunth[1]])[0];; - ast[index] = pureMacro(macroName, params)[0]; - } - if (statement.zeroth && statement.zeroth.type === "invocation" && Object.keys(macros).includes(statement.zeroth.zeroth)) { - statement.zeroth = resolveOpMacros([statement.zeroth])[0]; - } - if (statement.wunth && statement.wunth.type === "invocation" && Object.keys(macros).includes(statement.wunth.zeroth)) { - statement.wunth = resolveOpMacros([statement.wunth])[0]; - } - if (statement.twoth && statement.twoth.type === "invocation" && Object.keys(macros).includes(statement.twoth.zeroth)) { - statement.twoth = resolveOpMacros([statement.twoth])[0]; - } - if (Array.isArray(statement.zeroth)) { - statement.zeroth = resolveOpMacros(statement.zeroth); - } - if (Array.isArray(statement.wunth)) { - statement.wunth = resolveOpMacros(statement.wunth); - } - if (Array.isArray(statement.twoth)) { - statement.wunth = resolveOpMacros(statemnet.wunth); - } - }); + if (Array.isArray(ast)) { + ast.forEach((statement, index) => { + if (statement.type === "invocation" && Object.keys(macros).includes(statement.zeroth)) { + const macroName = statement.zeroth; + const paramNames = macros[macroName].params.map(param => param.name); + const params = {}; + params[paramNames[0]] = resolveOpMacros([statement.wunth[0]])[0]; + params[paramNames[1]] = resolveOpMacros([statement.wunth[1]])[0];; + ast[index] = pureMacro(macroName, params)[0]; + } + if (statement.zeroth && statement.zeroth.type === "invocation" && Object.keys(macros).includes(statement.zeroth.zeroth)) { + statement.zeroth = resolveOpMacros([statement.zeroth])[0]; + } + if (statement.wunth && statement.wunth.type === "invocation" && Object.keys(macros).includes(statement.wunth.zeroth)) { + statement.wunth = resolveOpMacros([statement.wunth])[0]; + } + if (statement.twoth && statement.twoth.type === "invocation" && Object.keys(macros).includes(statement.twoth.zeroth)) { + statement.twoth = resolveOpMacros([statement.twoth])[0]; + } + if (Array.isArray(statement.zeroth)) { + statement.zeroth = resolveOpMacros(statement.zeroth); + } + if (Array.isArray(statement.wunth)) { + statement.wunth = resolveOpMacros(statement.wunth); + } + if (Array.isArray(statement.twoth)) { + statement.wunth = resolveOpMacros(statement.wunth); + } + }); + } return ast; } function parse(tokGen, debug = true) { @@ -2042,7 +2053,7 @@ function parse(tokGen, debug = true) { console.log(warning); }) } - // console.log(JSON.stringify(statementz, undefined, 4)); + console.log(JSON.stringify(statementz, undefined, 4)); if (!findAndThrow(statementz)) { // Resolve top-level get. if (isGoroutine(statementz)) { diff --git a/src/compiler/transpile.test.js b/src/compiler/transpile.test.js index ec71ed4..43af7e3 100644 --- a/src/compiler/transpile.test.js +++ b/src/compiler/transpile.test.js @@ -1,4 +1,6 @@ -const { expect } = require("chai"); +const { + expect +} = require("chai"); const tokenize = require("./tokenize"); const parse = require("./parse"); const gen = require("./gen"); @@ -221,6 +223,170 @@ const transpileTests = { 'import ramda.src.map': `const map = stone(require("ramda/src/map"));`, "importstd gr": 'const gr = stone(require("@zlanguage/zstdlib/src/js/gr"));', "export fooey": "module.exports = stone(fooey);" + }, + "control flow": { + "if foo { log(bar) }": `if (assertBool(foo)) { + log(bar); + }`, + "if foo { log(bar) } else if fooey { log(baree) } else { log(foobar) }": `if (assertBool(foo)) { + log(bar); + } else { + if (assertBool(fooey)) { + log(baree); + } else { + log(foobar); + } + }`, + 'loop { log("YAY") }': `while (true) { + log("YAY"); + }` + }, + "error handling": { + [`try { + raise "FOOEY" + } on err { + settle err + }`]: `try { + throw new Error("FOOEY"); + } catch (err) { + err["settled"] = true; + if (assertBool($eq(err["settled"], undefined))) { + throw new Error("Error err not settled.") + } + } + ` + }, + "goroutines": { + [`copy(go func () { + get fooey() + })`]: `copy(async function () { + await fooey()._from(); + }); + `, + [`go { + get fooey() + }`]: `(async function () { + await fooey()._from(); + })(); + ` + }, + "enums": { + [`enum Foo { + Bar(x: number!), + Baz(y: _!, z: number!) + } derives (Nada) where { + foobar () {} + } `]: `function Bar(x) { + + if($eq(Object.keys((x == null) ? { [Symbol()]: 0 } : x).sort(), ["x"].sort())) { + ({ x } = x); + } + + + if (typeOf(x) !== "number") { + throw new Error("Foo.Bar.x must be of type number. However, you passed " + x + " to Foo.Bar which is not of type number."); + } + + return Nada({ + type() { return "Foo"; }, + get constructor() { return Bar; }, + get parent() { return Foo; }, + get fields() { return ["x"]; }, + get x(){ return x; }, + "="(other) { + return other.constructor === Bar && $eq(x, other.x); + } + }); + } + + Bar.extract = function (val) { + if (val.constructor === Bar) { + return [val.x]; + } + return undefined; + }; + + function Baz(y, z) { + + if($eq(Object.keys((y == null) ? { [Symbol()]: 0 } : y).sort(), ["y", "z"].sort())) { + ({ y, z } = y); + } + + + if (typeOf(z) !== "number") { + throw new Error("Foo.Baz.z must be of type number. However, you passed " + z + " to Foo.Baz which is not of type number."); + } + + return Nada({ + type() { return "Foo"; }, + get constructor() { return Baz; }, + get parent() { return Foo; }, + get fields() { return ["y", "z"]; }, + get y(){ return y; }, + get z(){ return z; }, + "="(other) { + return other.constructor === Baz && $eq(y, other.y) && $eq(z, other.z); + } + }); + } + + Baz.extract = function (val) { + if (val.constructor === Baz) { + return [val.y, val.z]; + } + return undefined; + }; + + let Foo = { + order: [Bar, Baz], + Bar, + Baz + }; + Foo.foobar = function () { + }; + + `, + [`enum Point(x: number!, y: number!) where { + nada () {} + }`]: `function Point(x, y) { + + if($eq(Object.keys((x == null) ? { [Symbol()]: 0 } : x).sort(), ["x", "y"].sort())) { + ({ x, y } = x); + } + + + if (typeOf(x) !== "number") { + throw new Error("Point.Point.x must be of type number. However, you passed " + x + " to Point.Point which is not of type number."); + } + + if (typeOf(y) !== "number") { + throw new Error("Point.Point.y must be of type number. However, you passed " + y + " to Point.Point which is not of type number."); + } + + return { + type() { return "Point"; }, + get constructor() { return Point; }, + get parent() { return Point; }, + get fields() { return ["x", "y"]; }, + get x(){ return x; }, + get y(){ return y; }, + "="(other) { + return other.constructor === Point && $eq(x, other.x) && $eq(y, other.y); + } + }; + } + + Point.extract = function (val) { + if (val.constructor === Point) { + return [val.x, val.y]; + } + return undefined; + }; + + Point.order = [Point]; + + Point.nada = function () { + };` } } @@ -246,7 +412,7 @@ Object.entries(transpileTests).forEach(([testName, tests]) => { describe(testName, () => { Object.entries(tests).forEach(([expr, res]) => { it(`should transpile ${expr} to ${res == null ? res : res.toString()}`, () => { - expect(transpileZ(expr).replace(/\n$/, "")).to.eql(res.replace(/\n$/, "")); + expect(transpileZ(expr).split("\n").map(x => x.trim()).join("")).to.eql(res.split("\n").map(x => x.trim()).join("")); }) }) })