Skip to content

Commit

Permalink
Correctly shadow parent parameters in closures
Browse files Browse the repository at this point in the history
  • Loading branch information
phunanon committed Nov 16, 2023
1 parent 0f62342 commit f6aa86e
Show file tree
Hide file tree
Showing 5 changed files with 40 additions and 10 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1299,6 +1299,20 @@ captured.
→ [0 1 3 6 10]
```

One pit-fall is that, as Insitux only captures data just as a closure is being
created, nested closures can be tricky due to the change in scope.
This example will cause a reference error as `x` technically goes out-of-scope
before `(fn x)` is materialised:

```clj
(let x 1 f (fn (fn x)))
(f) ;Reference Error: "x" did not exist

(let x 1 f (fn (fn x)))
(var x 1)
(f) ;1 - this is fine as calling f captures x, which exists even within f itself
```

**Destructuring**

Destructuring is a syntax available as part of named function signatures,
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "insitux",
"version": "23.11.3",
"version": "23.11.16",
"description": "Extensible scripting language written in portable TypeScript.",
"main": "dist/invoker.js",
"types": "dist/invoker.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const insituxVersion = 231103;
export const insituxVersion = 231116;
import { asBoo } from "./checks";
import { arityCheck, keyOpErr, numOpErr, typeCheck, typeErr } from "./checks";
import { isLetter, isDigit, isSpace, isPunc } from "./checks";
Expand Down
27 changes: 19 additions & 8 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,22 +522,33 @@ function parseForm(
} else if (op === "#" || op === "@" || op === "fn") {
const pins: ParserIns[] = [];
const name = node2str([firstNode, ...nodes]);
/** Used to exclude from capturing in makeClosure. */
const cloParams: string[] = [];
/** Used within the closure, with shadowing of formParams as needed. */
const cloFormParams: ParamsShape = [];
/** Informs capturing of child-closure references to parent params. */
const outerParams = formParams.map(p => p.name);
/** E.g. `(fn x)` */
let monoFnBody = false;
if (op === "fn") {
const parsedParams = parseParams(nodes, false);
push(
cloParams,
parsedParams.shape.map(p => p.name),
);
push(formParams, parsedParams.shape);
push(pins, parsedParams.errors);
const { shape, errors } = parseParams(nodes, false);
const paramNames = shape.map(p => p.name);
push(cloParams, paramNames);
push(pins, errors);
//Closures are allowed to see both parent parameters and their own as
// they then pick from them to capture appropriately. However, we need
// to shadow parent parameters with the same name as child parameters.
const shadowed = outerParams.filter(p => has(paramNames, p));
push(cloFormParams, formParams.filter(p => !has(shadowed, p.name)));
push(cloFormParams, shape);

if (!len(nodes)) {
return err("provide a body");
}
monoFnBody = len(nodes) === 1;
nodes.unshift({ typ: "sym", text: "do", errCtx });
} else {
push(cloFormParams, formParams);
}
//Rewrite partial closure to #(... [body] args)
if (op === "@") {
Expand All @@ -555,7 +566,7 @@ function parseForm(
{ typ: "sym", text: "args", errCtx },
];
}
push(pins, parseForm(nodes, formParams, op !== "@"));
push(pins, parseForm(nodes, cloFormParams, op !== "@"));
const cins = <Ins[]>pins.filter(i => i.typ !== "err");
const errors = pins.filter(i => i.typ === "err");
if (len(errors)) {
Expand Down
5 changes: 5 additions & 0 deletions src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,11 @@ const tests: {
code: `(function f x (fn y (fn z [x y z]))) (((f :a) :b) :c)`,
out: `[:a :b :c]`,
},
{
name: "Clojure w/ shadow param",
code: `(function f y ((fn x y [x y]) 1 y)) (f 5)`,
out: `[1 5]`,
},
{
name: "Destructure var",
code: `(var [x [y]] [1 [2]]) [y x]`,
Expand Down

0 comments on commit f6aa86e

Please sign in to comment.