language-rust icon indicating copy to clipboard operation
language-rust copied to clipboard

Add quasi quotation

Open harpocrates opened this issue 8 years ago • 3 comments

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 of String -> 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 containing error <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 dataToPatQ and dataToExpQ, 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 P monad over an arbitrary monad (in particular Q and Identity, 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).

harpocrates avatar Mar 23 '17 04:03 harpocrates

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)

harpocrates avatar Apr 04 '17 22:04 harpocrates

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 (so data ExprT m a = PlusT (m (ExprT m a)) (m (ExprT m a)) a...)
  • [ ] update production rules to be parametrized over Context m => ExprT m Span where Context defines 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...

harpocrates avatar Apr 26 '17 03:04 harpocrates

This won't make the 0.1.0.0 release. Instead, Language.Rust.Quote quasiquoters will not support $x or $x:ty splices/extractors.

harpocrates avatar May 12 '17 21:05 harpocrates