Implement `is` (tests)
Jinja2 uses the is keyword to express "test expressions". Here's the relevant Jinja2 documentation.
In Ginger, since we want tests, functions, and filter, to all be just different syntax for function calls, this is simply a matter of adding syntax sugar that turns expression is test(arg0,arg1,...) into test(expression, arg0, arg1, ...), similar to the syntax sugar for filters (expression|filter(args) → filter(expression, args)). Thus, it can (and should) be implemented entirely in the parser.
Considering #28, we need to introduce one bit of extra sugaring here: when generating code for is constructs, the is_ prefix needs to be added to the test name to resolve to the desired test function. This also implies that only literal test names are allowed after the is (plus optional arguments). So the following are valid:
-
foo is even -
foo is divisibleby 2 -
foo is divisibleby(2) -
foo is divisibleby(party.guests|count) -
(party.guests|count) is divisibleby 3
But these are not:
-
foo is predicates[1] -
foo is (bar or baz) -
foo is (party.type == 'informal' ? divisibleby 3 : even)
Further, the auto-prepending of if_- means that the following:
foo is divisibleby 3
...will desugar into:
is_divisibleby(foo, 3)
I made a PR now for this issue. I don't think it's complete work, builtins are missing for sure. I'd like to have feedback though, as I am very new to this codebase and also Haskell. Thanks!
A question: how do you debug parsers? I used
templateBody $ fromRight undefined $ fromJust $ parseGinger (Prelude.const Nothing) Nothing "{% if 1 is lt(2) %}true{% else %}false{% endif %}"
to run stuff then looked at that output. Maybe there could be some debug utility which does this and also prettyprints? I would be really grateful for some clues about debugging Parsec in general too.
Hey there, and thanks for your work so far. I've added a few remarks, nothing grand though.
A question: how do you debug parsers?
My go-to strategy here is just writing more tests; but otherwise, what you have there is more or less what I'd do (although I prefer to pattern-match directly instead of going through fromRight / fromJust). Also look at the helper functions in the SimulationTests module for inspiration. On top of that, you can also just run individual parser directly through runParser, which gives you more control over what exactly you feed to your parser, and which exact parsers you execute (i.e., you won't have to parse a complete template and then drill down to extract what you want, but you can for example run the expression parser directly). Like so:
*Text.Ginger.Parse Control.Monad.Reader> runReader (runParserT expressionP defParseState "" "(5 + 6)") (mkParserOptions (const . return $ Nothing))
Right (CallE (line 1, column 2) (VarE (line 1, column 4) "sum") [(Nothing,NumberLiteralE (line 1, column 2) 5.0),(Nothing,NumberLiteralE (line 1, column 6) 6.0)])
Some pretty-printing of the AST would of course be useful for this kind of thing.
As far as the tests go, what we currently have is simulation tests that work the entire chain (parser and interpreter) in concert, plus some property tests for specific parts of the chain (the GVal type, and the optimizer, off the top of my head). We could certainly use more unit tests that focus on individual parts, e.g. testing individual parsers and just checking the parsed AST, as well as fabricating AST and running that through the interpreter only. But because my time is limited, I prioritized end-to-end tests, because these help improve correctness, while the unit tests would be more about understanding the building blocks and finding the source of already known bugs.
Thanks, I get your point about the testing stuff.
Pushed changes to the PR, please review. :)
Should I work on the builtins in this one or in a separate one later when this is merged?
Should I work on the builtins in this one or in a separate one later when this is merged?
Probably nicer to keep the discussion in one place, so I guess we should keep this open.
Thanks for your work so far, btw.