author | Alan Dipert
<alan@dipert.org> 2019-10-11 05:04:10 UTC |
committer | Alan Dipert
<alan@dipert.org> 2019-10-11 05:04:10 UTC |
parent | 6e57b36d3429e21d7192a33bc5b165998edd1345 |
jacl.js | +76 | -23 |
diff --git a/jacl.js b/jacl.js index 9f4c13a..dff0ae0 100644 --- a/jacl.js +++ b/jacl.js @@ -514,7 +514,8 @@ const symCharNames = new Map([ ['_', '_UNDERSCORE_'], ['{', '_LBRACK_'], ['}', '_RBRACK_'], - ['~', '_TILDE_'] + ['~', '_TILDE_'], + [' ', '_SPACE_'] ]); const munge = s => { @@ -686,14 +687,31 @@ const isLambdaForm = form => { return form instanceof Cons && form.car && form.car instanceof LispSymbol - && form.car.packageName === 'COMMON-LISP' - && form.car.name === 'LAMBDA'; + && ( + (form.car.packageName === 'COMMON-LISP' && form.car.name === 'LAMBDA') + || (form.car.packageName === 'JACL' && form.car.name === '%LAMBDA') + ); } const merge = (...objs) => Object.assign({}, ...objs); //(def specials '#{if def fn* do let* loop recur new set! ns}) +const analyzeBlock = (env, forms) => { + let stmts = forms.slice(0, forms.length-1) + .map(x => analyze(env.withContext("statement"), x)), + ret; + if (forms.length <= 1) { + ret = analyze(env, forms[0]); + } else { + ret = analyze( + env.withContext(env.context === "statement" ? "statement" : "return"), + forms.slice(forms.length-1)[0] + ) + } + return { statements: stmts, ret: ret }; +}; + const stringy = x => x instanceof String || typeof x === 'string' || x instanceof LispString; const analyzeSpecials = new Map([ [JACLPKG.intern("%DOT"), (env, form) => { @@ -704,12 +722,12 @@ const analyzeSpecials = new Map([ op: "js-field", env: env, form: form, - target: analyze(merge(env, { context: "val" }), target), + target: analyze(env.withContext("expr"), target), field: field instanceof LispSymbol ? field.name : field.toString() }; }], [JACLPKG.intern("%CALL"), (env, form) => { - env = merge(env, { context: "val" }); + env = env.withContext("expr"); const [, func, ...args] = form; return { op: "call", @@ -718,17 +736,49 @@ const analyzeSpecials = new Map([ f: analyze(env, func), args: args.map(analyze.bind(null, env)) } + }], + [JACLPKG.intern("%LAMBDA"), (env, form) => { + const [, arglist, ...exprs] = form, + args = (arglist === null ? [] : Array.from(arglist)) + .map(x => x.name), + restIdx = args.findIndex(x => x === '&REST'), + isVariadic = restIdx >= 0, + argNames = args.filter(x => x !== '&REST'); + + if (isVariadic && restIdx != args.length-2) { + throw new Error(`&REST must be followed by argument symbol`); + } + + const bodyEnv = env.withContext("return"); + argNames.forEach(s => bodyEnv.locals.val.add(s)); + + return merge({ + op: "lambda", + env: env, + form: form, + isVariadic: isVariadic, + minArgs: argNames.length, + argNames: argNames, + restArgName: isVariadic ? args[restIdx+1] : null + }, analyzeBlock(bodyEnv, exprs)); }] ]); -const parseCall = (env, [func, ...args]) => { - env = merge(env, { context: "val" }); - const fenv = merge(env, { context: "fun" }) +const parseCall = (env, form) => { + const [func, ...args] = form; + let f; + if (isLambdaForm(func)) { + f = analyze(env, func); + } else if (func instanceof LispSymbol && env.locals.fun.has(func.name)) { + f = { env: env, form: func, op: "local", name: munge(func.name) }; + } else if (func instanceof LispSymbol) { + f = { env: env, form: func, op: "global", slot: "function" }; + } return { env: env, op: "call", - f: analyze(fenv, func), - args: args.map(analyze.bind(null, env)) + f: f, + args: args.map(analyze.bind(null, env.withContext("expr"))) }; }; @@ -741,10 +791,10 @@ const analyzeList = (env, form) => { return analyze(env, form); } else if (analyzeSpecials.has(form.car)) { return analyzeSpecials.get(form.car)(env, form); - } else if (isLambdaForm(form) || form.car instanceof LispSymbol) { + } else if (isLambdaForm(form.car) || form.car instanceof LispSymbol) { return parseCall(env, form); } else { - throw new Error(`Invalid call: ${form}`) + throw new Error(`Invalid call`) } }; @@ -754,27 +804,30 @@ const analyzeSymbol = (env, form) => { return ret({ op: "constant" }); } else if (form.packageName === 'JS' && !form.getPackage().isExported(form.name)) { return ret({ op: "js-var", name: form.name }); - } else if (env.context === "fun" && env.locals.fun.has(form)) { - return ret({ op: "local", name: env.locals.fun.get(form) }); - } else if (env.locals.val.has(form)) { - return ret({ op: "local", name: env.locals.val.get(form) }); + } else if (env.locals.val.has(form.name)) { + return ret({ op: "local", name: munge(form.name) }); } else { - return ret({ op: "global" }); + return ret({ op: "global", slot: "value" }); } }; class Env { constructor() { this.locals = { - fun: new Map(), - val: new Map() + fun: new Set(), + val: new Set() }; - this.context = "val"; + this.context = "expr"; } - copy() { + clone() { const newEnv = new Env(); - newEnv.locals.fun = new Map(this.locals.fun); - newEnv.locals.val = new Map(this.locals.val); + newEnv.locals.fun = new Set(this.locals.fun); + newEnv.locals.val = new Set(this.locals.val); + return newEnv; + } + withContext(ctx) { + const newEnv = this.clone(); + newEnv.context = ctx; return newEnv; } }