New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Infix progress #162
Infix progress #162
Conversation
This is awesome! I'll see if I can grasp the code quick enough to help... |
It works well enough to at least play with. The main hitch is currently var enforestation. Trying to do lookbehind matching on the rhs of a var assignment does not currently work. Tests fail on building @disnet I'm currently explicitly passing |
@disnet Here's a problem I'm having with var enforestation. Let's take a normal macro: macro str {
rule {} => { .toString() }
}
var foo = true str; This gives you the expected result of var foo = true str;
// -->
<VariableStatement foo = true> str;
// --->
<VariableStatement foo = true> <Punc .> <Call toString()> <Punc ;> Again, this isn't really a problem for normal macros, as it all works out in the end to correct syntax. The problem is with lookbehind matching, we only want to match on complete term trees to avoid senseless code. So say I turn this into a postfix macro: macro str {
rule infix { $lhs:expr | } => { myToString($lhs) }
}
var foo = true str;
// -->
<VariableStatement foo = true> str;
// --> Match error It will result in a match error, because we'd be splitting a term tree if we took the var foo = true
str; is a valid invocation of this macro if we just slurp until we get to a So I guess the question is, what do you think we should do in the case of a macro after the initial rhs term which could potentially "extend" the expression? Without lookbehind, it wan't a problem, but with lookbehind you could potentially match on the extension, when it should be part of the previous expression. |
This is a macro expansion that seems to make sense to me: macro plus {
rule infix { $lhs:expr | rhs:expr } {
$lhs + $rhs
}
}
// Now
var foo = 12 plus 4;
<VariableStatement: var foo = 12> plus 4 ;
Error: No match (term split)
// Ideal?
var foo = 12 plus 4;
<Keyword: var> foo = 12 plus 4 ;
<Keyword: var> <VariableAssignment: foo => 12 plus 4 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> plus 4 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> plus <Lit: 4> ;
<Keyword: var> <VariableAssignment: foo => 12 + 4 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> + 4 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> + <Lit: 4> ;
<Keyword: var> <VariableAssignment: foo => <Binop: 12 + 4> ;
<VariableStatement: var foo = 12 + 4> <Punc: ;> In this, we keep enforesting the lhs assignment until there isn't a reason to anymore (until it stops forming expressions), instead of just running enforest once. I think this is actually what you want for my trivial macro str {
rule infix { } => { .toString() }
}
var foo = true str;
<Keyword: var> foo = true str ;
<Keyword: var> <VariableAssignment: foo => true str ;
<Keyword: var> <VariableAssignment: foo => <Lit: true> str ;
<Keyword: var> <VariableAssignment: foo => <Lit: true> . toString () ;
<Keyword: var> <VariableAssignment: foo => <ObjDotGet: true . toString> () ;
<Keyword: var> <VariableAssignment: foo => <Call: true . toString ()> ;
<VariableStatement: foo = true.toString()> <Punc: ;> Does this seem like it makes sense? Edit: Here's a more complete example with commas var foo = 12 plus 4,
bar = 42 plus 1;
<Keyword: var> foo = 12 plus 4 , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => 12 plus 4 , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> plus 4 , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> plus <Lit: 4> , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => 12 + 4 , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> + 4 , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => <Lit: 12> + <Lit: 4> , bar = 42 plus 1 ;
<Keyword: var> <VariableAssignment: foo => <Binop: 12 + 4> , bar = 42 plus 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> bar = 42 plus 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => 42 plus 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => <Lit: 42> plus 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => <Lit: 42> plus <Lit: 1> ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => 42 + 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => <Lit: 42> + 1 ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => <Lit: 42> + <Lit: 1> ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableAssignment: bar => <BinOp: 42 + 1> ;
<Keyword: var> <VariableDeclaration: foo = 12 + 4> <Punc: ,> <VariableDeclaration: bar = 42 + 1> ;
<VariableStatement: var foo = 12 + 4 , bar = 42 + 1> <Punc: ;> This would all happen in |
I've been traveling for the holidays so haven't had time to completely grok this yet but I think your plan of enforesting the assignment until you reach a fixedpoint and having |
In: <Keyword: var> <VariableAssignment: foo => 12 plus 4 ; why do we want to allow the macro to match past the |
The reason I wanted to match past it is because you could potentially do some cool transformations that require more context of the surrounding scope without having to override common keywords like Here is an obviously deficient macro, but you get the idea: macro async {
rule infix { var $name:ident = | $call:expr $rest ... } => {
$call.then(function($name) {
$rest ...
})
}
} But maybe its too much? Maybe it doesn't give you enough to really implement ideas like that correctly? I just wanted a potential solution for having more expressive macros that don't require tedious macro composition if you use more than one. |
`enforestVarStatement` is now greedy when it comes to enforesting expressions on the rhs of an assignment. This allows us to juxtapose an expression and a macro that might extend the expression. Additionally, it fixes a longstanding bug wherein the lhs identifier was being enforested, potentially causing macro expansion.
So I decided to not allow lookbehind matching on the
I went ahead and fixed the bug in #120 wherein the rhs identifier was being enforested. I also changed it so the There are just a couple more things left to cleanup, so hopefully I'll get it finished up this weekend, and we can start testing like crazy, as I'm sure there are many bugs. If you want to check out this branch, you can run a build and play around with it. |
Should this work yet? macro => {
rule infix { $args | $body } => {
function $args $body
}
}
(x) => { x * x }; Doesn't seem to match:
The |
@jlongster it should work now. Here's a complete macro for arrow functions: macro => {
rule infix { $arg:ident | { $body ... } } => {
(function($arg) { $body ... }).bind(this)
}
rule infix { ($args:ident (,) ...) | { $body ... } } => {
(function($args (,) ...) { $body ... }).bind(this)
}
rule infix { $arg:ident | $body:expr } => {
(function($arg) { return $body }).bind(this)
}
rule infix { ($args:ident (,) ...) | $body:expr } => {
(function($args (,) ...) { return $body }).bind(this)
}
} |
@disnet It turns out function( ... ) {
return;
(function( ... ) { ... }(...);
} The odd thing was, if I added a 'var asdf = it' declaration at the top of the test file (test_macro_case.js), it would work fine. Is there some weird hygiene related thing going on here that affects how code potentially gets generated? It seems totally bizarre to me that adding that var declaration would mean the difference between I ended up fixing it by removing the outer parens around the returned function expression in |
@disnet Currently the |
I don't think so. |
But wouldn't that mean it should have been broken for everything? It only did that for the |
The first Maybe adding the var alias just pushed the line numbers to line up correctly? |
Oh! Actually I think the real reason it works now is that by using Which is actually wrong: http://sweetjs.org/browser/editor.html#function%20foo%20%28%29%20{%0Areturn%0Afoo;%0A} Ugh! ASI is the worst. |
@disnet I was able to get around the It's looking pretty good. I just need to write up a bunch of test cases. |
So should I revert my change in stxcase, and just change the context on the parens to |
No need. It's actually simpler to just have a function expression without the parens. |
I think there's a problem with trying to match a literal macro n {
rule infix { $x $[|] | $y } => { $x + $y }
}
// fails to match
10 | n 100 |
Oh! That's probably because it's splitting an expression right? Is there a way to make that obvious in the error? |
In this case, it's actually because you're in the middle of building a |
@disnet I think I've taken this as far as I can without getting more widespread testing. One thing to note, is I moved the greedy expression expansion to macro test {
rule { $arg:expr } => {
foo($arg)
}
}
test a => a + 1
// -->
foo(a) => a + 1 It would take only the macro test {
rule { $arg:expr ... } => {
foo($arg (,) ...);
}
}
test a => a + 1 b => b + 2 c => c + 3
// -->
foo(function(a) {
return a + 1;
}.bind(this), function(b) {
return b + 2;
}.bind(this), function(c) {
return c + 3;
}.bind(this)); This means that any macro with macro $ {
rule infix { $lhs:expr | $rhs:expr } => {
$lhs($rhs)
}
}
foo $ bar $ baz $ 42
// -->
foo(bar(baz(42))) Let me know if you think I should revert this. |
@natefaubion I really like the idea of making it more consistent like that. I've been meaning to look into when "inside out" expansion is triggered, but haven't grasped it yet because I thought it happened whenever That's something that is very hard to do in most lisps/schemes btw, so it will be really neat to have that. Granted Lisp hits these problems much more rarely because the actual code shape is a lot more consistent. This lets us have our cake and eat it too. I'm happy to test this more though I probably won't do too much because I'm not working the next few days. The only thing I really want is the fat arrow operator for my es6-macros. I'll play around with it in more depth though. |
This is looking great! I'll merge it in and assuming we don't run into any blockers do a npm release next week. |
👏 Nice work, @natefaubion. |
Woo! 👍 |
👍 |
This is so awesome 👍 |
This is an ongoing PR to track the progress of infix macros. Please chime in at any point. I've broken it down into goals that I'll periodically update so you can see how far along it is:
macro
macro to translate infixrule
s into infixcase
s.syntaxCase
to understand theinfix
form and matching semantics.enforest
andexpandTermTree
to keep track of previous syntax/terms.enforestVarStatement
so we can lookbehind match on var assignmentwithSyntax
which is broken for some reasonFor the time being, I'm using the following form for infix macros because it's the easiest to parse (anything before the bar is lookbehind, anything after is business as usual):
Another proposed forms for infix
case
s:And @disnet liked the haskelly: