sweet-js / sweet-core

Sweeten your JavaScript.
https://www.sweetjs.org
BSD 2-Clause "Simplified" License
4.58k stars 208 forks source link

Variable binding (hygiene, gensym) not working in 2-level macros #760

Open HeatherSoron opened 4 years ago

HeatherSoron commented 4 years ago

Variable binding in a 2-layer macro (i.e., a macro that calls another macro) is broken, in several cases I've tested.

Here's a fairly simple one (simpler than what I was using, but still clearly demonstrates the problem):

syntax shallow = function(ctx) {
    let name = ctx.next().value
    let val = ctx.next().value
    return #`let ${name} = ${val}`
}

syntax deep = function(ctx) {
    let name = ctx.next().value
    return #`shallow ${name} 42`
}

shallow foo 42
deep bar

console.log(foo)
console.log(bar)

This should result in the value "42" being printed twice to stdout, but will actually result in a ReferenceError on the second console.log. The reason for this, is that Sweet.js expands it to the following code:

let foo_13 = 42;
let bar_14 = 42;
console.log(foo_13);
console.log(bar);

That deep bar appears to be misbehaving, as the identifier bar is not picked up later on, during the console.log(bar) call.

HeatherSoron commented 4 years ago

If anyone wants a pointer on where this is in the codebase, the function reduceBindingIdentifier in sweet-js/core/token-expander.js is what adds the _14.

However, I suspect there's a parsing error earlier in the compilation process, because after doing some research, I discovered that "hygienic macros" seem to be distinguished by the user not having to explicitly call gensym-like functions (this detail isn't in the Sweet.js documentation itself, but is mentioned by other resources online).

DET171 commented 1 year ago

Any updates on how to solve this?

I've recently came across this problem as well.