nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.5k stars 1.46k forks source link

`static` expressions in `if`/`block`/`when`/etc don't parse #19802

Open tersec opened 2 years ago

tersec commented 2 years ago

static expressions in if/block/when/etc don't parse.

Example

discard if true:
  static(0)
else:
  static(0)

Current Output

block_staticexpr.nim(2, 10) Error: expected: ':', but got: '('

Expected Output

Successful compilation.

Some variations which do compile successfully:

discard if true:
  int(0)
else:
  int(0)
discard if true:
  (0)
else:
  (0)
discard if true:
  0
else:
  0
echo static(0)

Additional Information

Tested with

Nim Compiler Version 1.2.16 [Linux: amd64]
Compiled at 2022-05-16
Copyright (c) 2006-2020 by Andreas Rumpf

git hash: c6a9f27b3e36bae9aacec1bd6c37893fb98fd33f
active boot switches: -d:release
$ nim --version
Nim Compiler Version 1.6.6 [Linux: amd64]
Compiled at 2022-05-14
Copyright (c) 2006-2021 by Andreas Rumpf

active boot switches: -d:release
$ Nim/bin/nim --version
Nim Compiler Version 1.7.1 [Linux: amd64]
Compiled at 2022-05-18
Copyright (c) 2006-2022 by Andreas Rumpf

git hash: 06f02bb7716066241b04b2a92a3a61f539eadff2
active boot switches: -d:release
DavideGalilei commented 2 years ago

Interestingly enough, this example works instead:

discard if true: static(0)
else: static(1)

Those don't, instead:

discard if true:
  static(0)
else: static(1)
discard if true: static(0)
else:
  static(1)
discard (if true:
    static(0)
  else:
    static(1))

I also found out about a weird static behavior:

echo static: static(0)

This code should output 0, however its output is static0

(all examples were compiled using nim r test.nim)


$ nim --version
Nim Compiler Version 1.6.6 [Linux: amd64]
Compiled at 2022-05-05
Copyright (c) 2006-2021 by Andreas Rumpf

git hash: 0565a70eab02122ce278b98181c7d1170870865c
active boot switches: -d:release
tersec commented 2 years ago

In general, anything which forces the static into being parsed as part of an expression rather than part of a a statement will suffice (along the lines of how (;foo) or similar works the other way around, creating an unambiguous statement-context).

So, e.g.,

discard if true:
  (static(0))
else:
  (static(0))

works fine, as does

discard if true:
  (static(0))
else: (static(1))

as does

discard if true: (static(0))
else:
  (static(1))

and

discard (if true:
    (static(0))
  else:
    (static(1)))

work.

Wrapping the static expression in parens suffices. It's just a kludgy workaround to a parser which doesn't properly parse the documented language grammar.

https://nim-lang.org/docs/manual.html#syntax-grammar shows the static keyword in both

operator =  OP0 | OP1 | OP2 | OP3 | OP4 | OP5 | OP6 | OP7 | OP8 | OP9
         | 'or' | 'xor' | 'and'
         | 'is' | 'isnot' | 'in' | 'notin' | 'of' | 'as' | 'from'
         | 'div' | 'mod' | 'shl' | 'shr' | 'not' | 'static' | '..'
prefixOperator

and

staticStmt = 'static' colcom stmt

Nim just seems to prefer trying to parse the staticStmt and doesn't seem to have a decent recovery mechanism to detect whether it should be parsing static as a prefixOperator.

tersec commented 2 years ago

One can scan for other keywords which appear in a prefix position in both statements and expressions, and, e.g., try seems to have the same kind of problem: echo(try: 0 finally: discard) (i.e. using parens to force Nim to parse a tryExpr rather than a tryStmt) is fine, because

tryExpr = 'try' colcom stmt &(optInd 'except'|'finally')
           (optInd 'except' exprList colcom stmt)*
           (optInd 'finally' colcom stmt)?

allows optInd rather than

tryStmt = 'try' colcom stmt &(IND{=}? 'except'|'finally')
           (IND{=}? 'except' exprList colcom stmt)*
           (IND{=}? 'finally' colcom stmt)?

which require the IND{=}?.

But if one does echo try: 0 finally: discard, then Nim tries parsing it as a tryStmt, and can't recover.

As a final edit to this comment, https://github.com/nim-lang/Nim/issues/18714 is basically this issue for proc/func (or, vice versa) and has the same workarounds.

metagn commented 1 year ago

There were 2 mistakes in the grammar: static is not an operator, and ifExpr actually parses stmts.

With this in mind, staticStmt is parsed first (and yes this changes with indentation). Using a conservative definition of ordered choice (/ in the grammar) that only checks the first token, this would error, but we would need to mention this definition as it's not standard (and use it consistently). Same for #18714.

The problem is that it's hard to implement the required backtracking in the current parser, which sucks because it's only 1 token ahead. Maybe we could get rid of staticStmt and treat it like a regular call but I'm not sure if the compiler could handle it and it would be an AST change