diff --git a/.eslintignore b/.eslintignore index a16bd3cd..30e8a0ff 100644 --- a/.eslintignore +++ b/.eslintignore @@ -22,4 +22,4 @@ vite.config.js reverbGen.mjs hydra.mjs jsdoc-synonyms.js -packages/tidal/hs2js.mjs \ No newline at end of file +packages/hs2js/src/hs2js.mjs \ No newline at end of file diff --git a/packages/hs2js/src/hs2js.mjs b/packages/hs2js/src/hs2js.mjs index 1f894d14..e7b7cf46 100644 --- a/packages/hs2js/src/hs2js.mjs +++ b/packages/hs2js/src/hs2js.mjs @@ -1,12 +1,10 @@ -import { parse } from "./parser.mjs"; +import { parse } from './parser.mjs'; function runApply(node, scope, ops) { if (node.children.length !== 2) - throw new Error( - `expected 2 children for node type apply, got ${node.children.length}` - ); + throw new Error(`expected 2 children for node type apply, got ${node.children.length}`); const [fn, arg] = node.children.map((child) => run(child, scope, ops)); - if (typeof fn !== "function") { + if (typeof fn !== 'function') { throw new Error(`${node.children[0].text} is not a function`); } // only works if fn is curried! @@ -19,23 +17,23 @@ function runInfix(left, symbol, right, ops) { return customOp(left, right); } switch (symbol) { - case "+": + case '+': return left + right; - case "-": + case '-': return left - right; - case "*": + case '*': return left * right; - case "/": + case '/': return left / right; - case "$": + case '$': return left(right); - case "&": - console.log("right", right); + case '&': + console.log('right', right); return right(left); - case ".": + case '.': return (x) => left(right(x)); default: - throw new Error("unexpected infix operator " + symbol); + throw new Error('unexpected infix operator ' + symbol); } } @@ -55,79 +53,75 @@ export function run(node, scope, ops = {}) { let runInScope = (node, scp = scope) => run(node, scp, ops); //console.log("node", node.type, node.text); switch (node.type) { - case "ERROR": + case 'ERROR': throw new Error(`invalid syntax: "${node.text}"`); - case "declarations": + case 'declarations': let result; node.children.forEach((declaration) => { result = runInScope(declaration); }); return result; - case "integer": + case 'integer': return Number(node.text); - case "float": + case 'float': return Number(node.text); - case "string": + case 'string': const str = node.text.slice(1, -1); return String(str); - case "lambda": + case 'lambda': const [_, lpatterns, __, lbody] = node.children; return curry(lpatterns.children, lbody, scope, ops); - case "function": + case 'function': const [fvariable, fpatterns, fbody] = node.children; scope[fvariable.text] = curry(fpatterns.children, fbody, scope, ops); return scope[fvariable.text]; - case "list": { + case 'list': { return node.children .filter((_, i) => i % 2 === 1) // elements are at odd indices .map((node) => runInScope(node)); } - case "match": - if (node.children[0].text !== "=" || node.children.length !== 2) { - throw new Error("match node so far only support simple assignments"); + case 'match': + if (node.children[0].text !== '=' || node.children.length !== 2) { + throw new Error('match node so far only support simple assignments'); } return runInScope(node.children[1]); - case "bind": - if (node.children.length !== 2) - throw new Error("expected 2 children for node type bind"); - if (node.children[0].type !== "variable") - throw new Error("expected variable as first child of bind node"); - if (node.children[1].type !== "match") - throw new Error("expected match as first child of bind node"); + case 'bind': + if (node.children.length !== 2) throw new Error('expected 2 children for node type bind'); + if (node.children[0].type !== 'variable') throw new Error('expected variable as first child of bind node'); + if (node.children[1].type !== 'match') throw new Error('expected match as first child of bind node'); const [bvariable, bmatch] = node.children; const value = runInScope(bmatch); scope[bvariable.text] = value; return value; - case "variable": + case 'variable': return scope[node.text]; - case "infix": { + case 'infix': { const [a, op, b] = node.children; const symbol = op.text; const [left, right] = [runInScope(a), runInScope(b)]; return runInfix(left, symbol, right, ops); } - case "apply": + case 'apply': return runApply(node, scope, ops); - case "left_section": { + case 'left_section': { const [_, b, op] = node.children; const right = runInScope(b); return (left) => runInfix(left, op.text, right, ops); } - case "right_section": { + case 'right_section': { const [_, op, b] = node.children; const right = runInScope(b); return (left) => runInfix(left, op.text, right, ops); } - case "parens": - if (node.children.length !== 3) - throw new Error("expected 3 children for node type parens"); + case 'parens': + if (node.children.length !== 3) throw new Error('expected 3 children for node type parens'); return runInScope(node.children[1]); default: if (node.children.length === 0) { - throw new Error("unhandled leaf type " + node.type); + throw new Error('unhandled leaf type ' + node.type); } if (node.children.length > 1) { - throw new Error("unhandled branch type " + node.type); + throw new Error('unhandled branch type ' + node.type); } return runInScope(node.children[0]); } diff --git a/packages/hs2js/src/index.mjs b/packages/hs2js/src/index.mjs index 170ef1b6..c3aab230 100644 --- a/packages/hs2js/src/index.mjs +++ b/packages/hs2js/src/index.mjs @@ -1,2 +1,2 @@ -export * from "./hs2js.mjs"; -export * from "./parser.mjs"; +export * from './hs2js.mjs'; +export * from './parser.mjs'; diff --git a/packages/hs2js/src/parser.mjs b/packages/hs2js/src/parser.mjs index 829a5da3..6fbd9ae2 100644 --- a/packages/hs2js/src/parser.mjs +++ b/packages/hs2js/src/parser.mjs @@ -1,6 +1,6 @@ -import Parser from "web-tree-sitter"; +import Parser from 'web-tree-sitter'; -let base = "/"; +let base = '/'; export function setBase(path) { base = path; } @@ -28,10 +28,5 @@ export function loadParser() { export async function parse(code) { const parser = await loadParser(); // for some reason, the parser doesn't like new lines.. - return parser.parse( - code - .replaceAll("\n\n", "~~~~") - .replaceAll("\n", "") - .replaceAll("~~~~", "\n") - ); + return parser.parse(code.replaceAll('\n\n', '~~~~').replaceAll('\n', '').replaceAll('~~~~', '\n')); } diff --git a/packages/hs2js/vite.config.js b/packages/hs2js/vite.config.js new file mode 100644 index 00000000..ce715712 --- /dev/null +++ b/packages/hs2js/vite.config.js @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'path'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [], + build: { + lib: { + entry: resolve(__dirname, 'src', 'index.mjs'), + name: 'hs2js', + formats: ['es', 'iife'], + fileName: (ext) => ({ es: 'index.mjs', iife: 'index.js' })[ext], + }, + rollupOptions: { + // external: [...Object.keys(dependencies)], + plugins: [], + }, + target: 'esnext', + }, +}); diff --git a/packages/hs2js/vite.config.mjs b/packages/hs2js/vite.config.mjs deleted file mode 100644 index 9eb4a07a..00000000 --- a/packages/hs2js/vite.config.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { defineConfig } from "vite"; -import { resolve } from "path"; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [], - build: { - lib: { - entry: resolve(__dirname, "src", "index.mjs"), - name: "hs2js", - formats: ["es", "iife"], - fileName: (ext) => ({ es: "index.mjs", iife: "index.js" }[ext]), - }, - rollupOptions: { - // external: [...Object.keys(dependencies)], - plugins: [], - }, - target: "esnext", - }, -}); diff --git a/packages/tidal/hs2js.mjs b/packages/tidal/hs2js.mjs deleted file mode 100644 index 733b7aeb..00000000 --- a/packages/tidal/hs2js.mjs +++ /dev/null @@ -1,130 +0,0 @@ -function runApply(node, scope, ops) { - if (node.children.length !== 2) - throw new Error(`expected 2 children for node type apply, got ${node.children.length}`); - const [fn, arg] = node.children.map((child) => run(child, scope, ops)); - if (typeof fn !== 'function') { - throw new Error(`${node.children[0].text} is not a function`); - } - // only works if fn is curried! - return fn(arg); -} - -function runInfix(left, symbol, right, ops) { - const customOp = ops[symbol]; - if (customOp) { - return customOp(left, right); - } - switch (symbol) { - case '+': - return left + right; - case '-': - return left - right; - case '*': - return left * right; - case '/': - return left / right; - case '$': - return left(right); - case '&': - return right(left); - case '.': - return (x) => left(right(x)); - default: - throw new Error('unexpected infix operator ' + symbol); - } -} - -function curry(patterns, body, scope, ops) { - const [variable, ...rest] = patterns; - return (arg) => { - let _scope = { ...scope, [variable.text]: arg }; - if (patterns.length === 1) { - const result = run(body, _scope, ops); - return result; - } - return curry(rest, body, _scope, ops); - }; -} - -export function run(node, scope, ops = {}) { - let runInScope = (node, scp = scope) => run(node, scp, ops); - //console.log("node", node.type, node.text); - switch (node.type) { - case 'ERROR': - throw new Error(`invalid syntax: "${node.text}"`); - case 'declarations': - let result; - node.children.forEach((declaration) => { - result = runInScope(declaration); - }); - return result; - case 'integer': - return Number(node.text); - case 'float': - return Number(node.text); - case 'string': - const str = node.text.slice(1, -1); - return String(str); - case 'lambda': - const [_, lpatterns, __, lbody] = node.children; - return curry(lpatterns.children, lbody, scope, ops); - case 'function': - const [fvariable, fpatterns, fbody] = node.children; - scope[fvariable.text] = curry(fpatterns.children, fbody, scope, ops); - return scope[fvariable.text]; - case 'list': { - return node.children - .filter((_, i) => i % 2 === 1) // elements are at odd indices - .map((node) => runInScope(node)); - } - case 'match': - if (node.children[0].text !== '=' || node.children.length !== 2) { - throw new Error('match node so far only support simple assignments'); - } - return runInScope(node.children[1]); - case 'bind': - if (node.children.length !== 2) throw new Error('expected 2 children for node type bind'); - if (node.children[0].type !== 'variable') throw new Error('expected variable as first child of bind node'); - if (node.children[1].type !== 'match') throw new Error('expected match as first child of bind node'); - const [bvariable, bmatch] = node.children; - const value = runInScope(bmatch); - scope[bvariable.text] = value; - return value; - case 'variable': - return scope[node.text]; - case 'infix': { - const [a, op, b] = node.children; - const symbol = op.text; - const [left, right] = [runInScope(a), runInScope(b)]; - return runInfix(left, symbol, right, ops); - } - case 'apply': - return runApply(node, scope, ops); - case 'left_section': { - const [_, b, op] = node.children; - const right = runInScope(b); - return (left) => runInfix(left, op.text, right, ops); - } - case 'right_section': { - const [_, op, b] = node.children; - const right = runInScope(b); - return (left) => runInfix(left, op.text, right, ops); - } - case 'parens': - if (node.children.length !== 3) throw new Error('expected 3 children for node type parens'); - return runInScope(node.children[1]); - default: - if (node.children.length === 0) { - throw new Error('unhandled leaf type ' + node.type); - } - if (node.children.length > 1) { - throw new Error('unhandled branch type ' + node.type); - } - return runInScope(node.children[0]); - } -} - -export async function evaluate(haskellCode, scope = globalThis, ops) { - const ast = await parse(haskellCode); - return run(ast.rootNode, scope, ops); -} diff --git a/packages/tidal/parser.mjs b/packages/tidal/parser.mjs deleted file mode 100644 index 1b1f8a03..00000000 --- a/packages/tidal/parser.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import Parser from 'web-tree-sitter'; - -const base = import.meta.env.BASE_URL; - -async function loadParser() { - await Parser.init({ - locateFile(scriptName, scriptDirectory) { - return `${base}${scriptName}`; - }, - }); - const parser = new Parser(); - const Lang = await Parser.Language.load(`${base}tree-sitter-haskell.wasm`); - parser.setLanguage(Lang); - return parser; -} - -let parserLoaded = loadParser(); -export async function parse(code) { - const parser = await parserLoaded; - // for some reason, the parser doesn't like new lines.. - return parser.parse(code.replaceAll('\n\n', '~~~~').replaceAll('\n', '').replaceAll('~~~~', '\n')); -}