ginger icon indicating copy to clipboard operation
ginger copied to clipboard

Support syntactic sugar for unary operators `not`, `+` and `-`

Open wchresta opened this issue 7 years ago • 8 comments

The not unary is missing. This is easily testable with the following test case:

, testCase "if not true then \"yes\" else \"no\"" $ do
              mkTestHtml [] [] "{% if not true %}yes{% else %}no{% endif %}" "no"

which is failing at the moment. Jinja2 does support this.

wchresta avatar Jun 09 '18 21:06 wchresta

So without looking at the source code, I suspect that this is a simple matter of adding not-constructs to the parser as a special case, and emitting something like CallE (VarE "not") rhs (not exactly this, because the AST is a little bit more involved, but you get the idea).

tdammers avatar Jun 10 '18 09:06 tdammers

While we're at it, {{ True }} gives null instead of True. Jinja2 accepts both, true and True as truthy values.

Also, for the implementation of not, consider the following:

>>> Template('{{ not True }}').render()
'False'
>>> Template('{{ not 1 }}').render()
'False'
>>> Template('{{ not False }}').render()
'True'
>>> Template('{{ not 0 }}').render()
'True'
>>> Template('{{ not "" }}').render()
'True'
>>> Template('{{ not " " }}').render()
'False'

wchresta avatar Jun 10 '18 13:06 wchresta

While we're at it, {{ True }} gives null instead of True. Jinja2 accepts both, true and True as truthy values.

OK, this should be doable and sounds semi-reasonable. Simple matter of adding cases to the parser. Filed as #29.

tdammers avatar Jun 10 '18 13:06 tdammers

Also, for the implementation of not, consider the following:

Template('{{ not True }}').render() 'False' Template('{{ not 1 }}').render() 'False' Template('{{ not False }}').render() 'True' Template('{{ not 0 }}').render() 'True' Template('{{ not "" }}').render() 'True' Template('{{ not " " }}').render() 'False'

Ah, but that's actually one of the things I'd group under "blatant Pythonisms". I think rendering boolean values as "True" and "False" is fundamentally wrong, because...

  • True and False are syntax, not data, just like all the other keywords; and I want to keep these two worlds separated as much as possible.
  • True and False are English; but there is absolutely no reason to assume that the output is also going to be in the English language. So it doesn't really make sense to just inject English words into the output.
  • It leads to more weird inconsistencies: False would stringify to "False", but booleanizing "False" yields True. Ugh.

Realistically, rendering booleans directly into the output is generally going to be more or less an accident, and I don't think anyone is expecting anything utterly useful from it. So I'm perfectly comfortable with something like this:

>>> Template('{{ not True }}').render()
''
>>> Template('{{ not 1 }}').render()
''
>>> Template('{{ not False }}').render()
''
>>> Template('{{ not 0 }}').render()
'True'
>>> Template('{{ not "" }}').render()
'True'
>>> Template('{{ not " " }}').render()
'False'

tdammers avatar Jun 10 '18 14:06 tdammers

Ah yes. With the above comment I wanted to make sure not behaves with non-boolens consistently; it wasn't about the output.

Also, take care of operation ordering: functions (filters,tests) have precedence over not, logical operators bind stronger than not

>>> Template('{{ not " "|int }}').render()
'True'
>>> Template('{{ (not " ")|int }}').render()
'0'
>>> Template('{{ not (" "|int) }}').render()
'True'
>>> Template('{{ not False and False }}').render()
'False'
>>> Template('{{ not (False and False) }}').render()
'True'

wchresta avatar Jun 10 '18 14:06 wchresta

A similar problem as with not also exists for - and +:

Jinja2:

>>> Template('{{ - (22) }}').render()
'-22'

Ginger:

echo "{{ -(22) }}" | stack exec ginger
<<unknown>>:1:5

{{ -(22) }}
    ^

unexpected "("
expecting "0"

wchresta avatar Jun 10 '18 14:06 wchresta

Ah, right, yes... off the top of my head, - is currently parsed as part of the number literal, not as a proper unary operator. We should probably make a generalized unary-operator parser, and then use that for both not and -, similar to the generalized binary-operator parser.

tdammers avatar Jun 10 '18 14:06 tdammers

I created test cases, in case anyone wants to work on it. I'll look into it a bit but I'm not sure how far I'll get today.

https://github.com/wchresta/ginger/tree/fix/%2324/unaryops

wchresta avatar Jun 10 '18 14:06 wchresta