git » jacl.git » commit ab1c5b1

analyzing jacl::%lambda with variadic support

author Alan Dipert
2019-10-11 05:04:10 UTC
committer Alan Dipert
2019-10-11 05:04:10 UTC
parent 6e57b36d3429e21d7192a33bc5b165998edd1345

analyzing jacl::%lambda with variadic support

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;
   }
 }