author | Alan Dipert
<alan@dipert.org> 2019-10-09 05:23:14 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-10-09 05:23:14 UTC |
parent | 038f3a1dbcf8a7ddf0a55d590e89d5dde3336992 |
jacl.js | +124 | -2 |
diff --git a/jacl.js b/jacl.js index ae969cc..0691e55 100644 --- a/jacl.js +++ b/jacl.js @@ -492,6 +492,43 @@ const isConstituent = ch => { || /[0-9]/.test(ch); } +const symCharNames = new Map([ + ['!', '_BANG_'], + ['$', '_DOLLAR_'], + ['%', '_PERCENT_'], + ['&', '_AMP_'], + ['*', '_STAR_'], + ['+', '_PLUS_'], + ['-', '_DASH_'], + ['.', '_DOT_'], + ['/', '_SLASH_'], + [':', '_COLON_'], + ['<', '_LT_'], + ['=', '_EQUAL_'], + ['>', '_GT_'], + ['?', '_QMARK_'], + ['@', '_AT_'], + ['[', '_LBRACE_'], + [']', '_RBRACE_'], + ['^', '_CARET_'], + ['_', '_UNDERSCORE_'], + ['{', '_LBRACK_'], + ['}', '_RBRACK_'], + ['~', '_TILDE_'] +]); + +const munge = s => { + let munged = ''; + for (const ch of s) { + if (symCharNames.has(ch)) { + munged += symCharNames.get(ch); + } else { + munged += ch; + } + } + return munged; +}; + class Token { constructor(initial = '') { this.str = initial; @@ -589,8 +626,8 @@ class Reader { } } -// Special forms related to interop -for (const s of ['%DOT', '%CALL']) { +// Special forms +for (const s of ['%DOT', '%CALL', '%LAMBDA']) { JACLPKG.intern(s); } @@ -653,6 +690,89 @@ const isLambdaForm = form => { && form.car.name === 'LAMBDA'; } +const assoc = (...objs) => Object.assign({}, ...objs); + +//(def specials '#{if def fn* do let* loop recur new set! ns}) + +const specials = new Map([ + [JACLPKG.intern("%CALL"), (env, form) => { + }] +]); + +const parseCall = (env, [func, ...args]) => { + env = assoc(env, {context: "expr"}); + const fenv = assoc(env, {context: "fexpr"}) + return { + env: env, + op: "call", + f: analyze(fenv, func), + args: args.map(analyze.bind(null, env)) + }; +}; + +const analyzeList = (env, form) => { + if (isMacroForm(form)) { + while (isMacroForm(form)) { + const [sym, ...args] = form; + form = sym.func()(...args); + } + return analyze(env, form); + } else if (specials.has(form.car)) { + return specials.get(form.car)(env, form); + } else if (isLambdaForm(form) || form.car instanceof LispSymbol) { + return parseCall(env, form); + } else { + throw new Error(`Invalid call: ${form}`) + } +}; + +const analyzeSymbol = (env, form) => { + const ret = (x) => assoc({ env: env, form: form }, x); + if (env.context === "fexpr") { + if (env.flocals.has(form)) { + return ret({ op: "local", name: env.flocals.get(form) }); + } else { + return ret({ op: "symfunc" }); + } + } else if (env.context === "expr") { + if (env.vlocals.has(form)) { + return ret({ op: "local", name: env.get('vlocals').get(form) }); + } else { + return ret({ op: "symval" }); + } + } else { + throw new Error(`Unknown context: ${env.context}`); + } +}; + +const emptyEnv = { + vlocals: new Map(), + flocals: new Map(), + context: "expr" +}; + +const analyze = (env, form) => { + if (form instanceof LispSymbol) { + return analyzeSymbol(env, form); + } else if (form instanceof Cons) { + return analyzeList(env, form); + } else { + return mapOf("op", "constant", "env", env, "form", form); + } +}; + +const compileLambda = ([, args, ...body], env) => { + env = new Set(...args, env); + let bodyStr = ''; + if (body.length == 1) { + bodyStr = `return ${compile(body[0], env)};`; + } else if (body.length > 1) { + bodyStr += body.slice(0, -1).map(x => compile(x, env)).join(';'); + bodyStr += `return ${compile(body[body.length-1], env)};`; + } + return `(function(${args.map(munge).join(',')}){${bodyStr}})` +}; + const compile = (form, env) => { if (form instanceof Number || typeof form === 'number') { return form.toString(); @@ -698,6 +818,8 @@ const compile = (form, env) => { return compile(arg1, env); } return compile(arg1, env); + } else if (JACLPKG.intern('%LAMBDA') === op) { + return compileLambda(form, env); } else if (op instanceof LispSymbol) { return `LispSymbol.intern('${op.packageName}', '${op.name}').func()(` + args.map(x => compile(x, env)).join(',') +