Skip to content

Commit

Permalink
Add overloaded macros & infix macros
Browse files Browse the repository at this point in the history
  • Loading branch information
N8 committed Oct 12, 2019
1 parent 385c146 commit f00842d
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 35 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down Expand Up @@ -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
[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
75 changes: 43 additions & 32 deletions src/compiler/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}.`);
Expand Down Expand Up @@ -1763,17 +1767,21 @@ 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;
advance();
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;
Expand Down Expand Up @@ -1813,6 +1821,7 @@ function paramMacro() {
advance();
break;
}
return true;
})
if (err) {
return [err];
Expand Down Expand Up @@ -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}.`));
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down
170 changes: 168 additions & 2 deletions src/compiler/transpile.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const { expect } = require("chai");
const {
expect
} = require("chai");
const tokenize = require("./tokenize");
const parse = require("./parse");
const gen = require("./gen");
Expand Down Expand Up @@ -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 () {
};`
}
}

Expand All @@ -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(""));
})
})
})
Expand Down

0 comments on commit f00842d

Please sign in to comment.