language-rust
language-rust copied to clipboard
Add quasi quotation
Besides the obvious quasi quotation, I want to capture variables with SubstNt tokens in Exp and bind variables with MatchNt in Pat. The following interaction should work:
>>> import Language.Rust.Quote
>>> :set -XQuasiQuotes
>>> let one = [lit| 1i32 |]
>>> [expr| |x: i32| -> $ret:ty $body:expr |] = [expr| |x: i32| -> i32 { x + $one } |]
ret :: Ty Span
body :: Expr Span
>>> import Language.Rust.Pretty
>>> pretty ret
i32
>>> pretty body
{ x + 1 }
Here is my current approach. It is terrible. I can't believe I have been reduced to this, but I've been thinking non-stop about this for 2 weeks now and have come up with nothing better.
- [x] Find an (unsafe) way to get a
Q (String -> Maybe Type)function (instead ofString -> Q Type) and use that to make the swapping function. Alternately, lex twice and extract all of the interesting tokens, look them up, and pass them in as a function in the second lexing pass. - [x] Swap in
NonTerminals containingerror <variable-name>- but of the right type! - [ ] Make view patterns for the parser (instead of pattern matching directly) and make those patterns check for errors using something atrocious like this. Hopefully this function is cheap - if it isn't, we may have a problem (although I can think of more typeclass based hacks with specialization)
- [x] In the quoters below in
dataToPatQanddataToExpQ, whenever something is of the right type, check if it is defined. If so, do nothing, otherwise, use the error message (ewwwwww!) to lookup variable or make an identifier pattern.
Is there nothing better?
Maybe there is. Here are some unorganized thoughts on this topic:
- Parametrize the
Pmonad over an arbitrary monad (in particularQandIdentity, allowing me to swap tokens in this monad) - this is already done in another branch, although I have misgivings about performance. I had to disable the monomorphism restriction to get the generated files to compile, and I have a feeling that polymorphic lexer/parser will take a hit in performance. - Add "variable" cases to the AST. That has the crappy effect of annoying a user who is not using quasi quotes - the extra cases don't make sense in the context of pure Rust. I can always not export those constructors, but then pattern matches will never be exhaustive. :(
The "good"TM solution is probably an extensible AST with type families, but given that the AST is already ~1kloc, adding a type family for every constructor may get... a bit too big. Plus this also looks ugly to the end user.
Also, the solution described initially isn't so bad. It does not change the reliability of regular parsing - it only risks failing in the case of quasi quotes (which will be marked experimental and non-portable, and will only cause compile-time headaches).
Here is a simple example of why we need the ViewPatterns approach:
> let x = [expr| 1 |]
> let y = [stmt| $x; |]
<interactive>:42:9: error:
• Exception when trying to run compile-time code:
Subst x
Code: Language.Haskell.TH.Quote.quoteExp stmt " $x; "
• In the quasi-quotation: [stmt| $x; |]
Even more insidiously (check -ddump-splices and you'll see the error is "leaking")
> let x = [expr| 1 + 2 |]
> let y = [expr| $x * 3 |]
<interactive>:11:15: error:
• Couldn't match expected type ‘Language.Rust.Data.Position.Span’
with actual type ‘Language.Rust.Syntax.AST.Expr
Language.Rust.Data.Position.Span’
• In the fifth argument of ‘Language.Rust.Syntax.AST.Binary’, namely
‘x’
In the expression:
(Language.Rust.Syntax.AST.Binary
[]
Language.Rust.Syntax.AST.MulOp
x
(Language.Rust.Syntax.AST.Lit
[]
(Language.Rust.Syntax.AST.Int
Language.Rust.Syntax.AST.Dec
3
Language.Rust.Syntax.AST.Unsuffixed
(Language.Rust.Data.Position.Span
(Language.Rust.Data.Position.Position 6 11 21)
(Language.Rust.Data.Position.Position 7 11 22)))
(Language.Rust.Data.Position.Span
(Language.Rust.Data.Position.Position 6 11 21)
(Language.Rust.Data.Position.Position 8 11 23)))
x)
In an equation for ‘y’:
y = (Language.Rust.Syntax.AST.Binary
[]
Language.Rust.Syntax.AST.MulOp
x
(Language.Rust.Syntax.AST.Lit
[]
(Language.Rust.Syntax.AST.Int
Language.Rust.Syntax.AST.Dec
3
Language.Rust.Syntax.AST.Unsuffixed
(Language.Rust.Data.Position.Span
(Language.Rust.Data.Position.Position 6 11 21)
(Language.Rust.Data.Position.Position 7 11 22)))
(Language.Rust.Data.Position.Span
(Language.Rust.Data.Position.Position 6 11 21)
(Language.Rust.Data.Position.Position 8 11 23)))
x)
Alright. I've mulled over this for a week and I've come up with a new approach, which isn't a hack:
- [ ] wrap all recursive ASTs in
m(sodata ExprT m a = PlusT (m (ExprT m a)) (m (ExprT m a)) a...) - [ ] update production rules to be parametrized over
Context m => ExprT m SpanwhereContextdefines functions that need to look at the contents of parsed values (like the path case of patterns which checks for one identifier patterns) - [ ] make type synonyms (
type Expr = ExprT Identity) and patterns (pattern Plus x y = PlusT (Identity x) (Identity y)). This also means fixing how Haddock handles pattern synonyms - [ ] re-make
Quote!
This may take some time...
This won't make the 0.1.0.0 release. Instead, Language.Rust.Quote quasiquoters will not support $x or $x:ty splices/extractors.