unify tuple expressions (#13793)

* unify tuple expressions

* fix test

* fix test

* apply feedback

* Handle empty tuples

* Fix rendering named unary tuple

* Protect static NimNode against stripping

* Slightly less hacky

* Revert "Slightly less hacky"

This reverts commit 170c5aec0addc029f637afbc948700ca006b7942.

* Slightly less hacky

* Cleanup

* Fix test

* Fix another test

* Add condsym

* Rebase fallout

* changelog: Move from compiler changes to language changes

* Add stricter tests

* Add empty tuple example to doc/astspec

* Fix test

Co-authored-by: Clyybber <darkmine956@gmail.com>
This commit is contained in:
Arne Döring 2021-03-30 02:06:51 +02:00 • committed by GitHub
commit 159c06e045
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 136 additions and 32 deletions

View file

@ -274,6 +274,9 @@
- Custom numeric literals (e.g. `-128'bignum`) are now supported. - Custom numeric literals (e.g. `-128'bignum`) are now supported.
- Tuple expressions are now parsed consistently as
`nnkTupleConstr` node. Will affect macros expecting nodes to be of `nnkPar`.
## Compiler changes ## Compiler changes

View file

@ -131,3 +131,4 @@ proc initDefines*(symbols: StringTableRef) =
defineSymbol("nimHasHintAsError") defineSymbol("nimHasHintAsError")
defineSymbol("nimHasSpellSuggest") defineSymbol("nimHasSpellSuggest")
defineSymbol("nimHasCustomLiterals") defineSymbol("nimHasCustomLiterals")
defineSymbol("nimHasUnifiedTuple")

View file

@ -421,10 +421,9 @@ proc exprColonEqExprListAux(p: var Parser, endTok: TokType, result: PNode) =
var a = exprColonEqExpr(p) var a = exprColonEqExpr(p)
result.add(a) result.add(a)
if p.tok.tokType != tkComma: break if p.tok.tokType != tkComma: break
getTok(p) elif result.kind == nkPar:
# (1,) produces a tuple expression
if endTok == tkParRi and p.tok.tokType == tkParRi and result.kind == nkPar:
result.transitionSonsKind(nkTupleConstr) result.transitionSonsKind(nkTupleConstr)
getTok(p)
skipComment(p, a) skipComment(p, a)
optPar(p) optPar(p)
eat(p, endTok) eat(p, endTok)
@ -584,7 +583,10 @@ proc parsePar(p: var Parser): PNode =
semiStmtList(p, result) semiStmtList(p, result)
elif p.tok.tokType == tkCurlyDotLe: elif p.tok.tokType == tkCurlyDotLe:
result.add(parseStmtPragma(p)) result.add(parseStmtPragma(p))
elif p.tok.tokType != tkParRi: elif p.tok.tokType == tkParRi:
# Empty tuple '()'
result.transitionSonsKind(nkTupleConstr)
else:
var a = simpleExpr(p) var a = simpleExpr(p)
if p.tok.tokType == tkDo: if p.tok.tokType == tkDo:
result = postExprBlocks(p, a) result = postExprBlocks(p, a)
@ -605,13 +607,14 @@ proc parsePar(p: var Parser): PNode =
semiStmtList(p, result) semiStmtList(p, result)
else: else:
a = colonOrEquals(p, a) a = colonOrEquals(p, a)
if a.kind == nkExprColonExpr:
result.transitionSonsKind(nkTupleConstr)
result.add(a) result.add(a)
if p.tok.tokType == tkComma: if p.tok.tokType == tkComma:
getTok(p) getTok(p)
skipComment(p, a) skipComment(p, a)
# (1,) produces a tuple expression: # (1,) produces a tuple expression:
if p.tok.tokType == tkParRi: result.transitionSonsKind(nkTupleConstr)
result.transitionSonsKind(nkTupleConstr)
# progress guaranteed # progress guaranteed
while p.tok.tokType != tkParRi and p.tok.tokType != tkEof: while p.tok.tokType != tkParRi and p.tok.tokType != tkEof:
var a = exprColonEqExpr(p) var a = exprColonEqExpr(p)

View file

@ -1182,7 +1182,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
of nkTupleConstr: of nkTupleConstr:
put(g, tkParLe, "(") put(g, tkParLe, "(")
gcomma(g, n, c) gcomma(g, n, c)
if n.len == 1: put(g, tkComma, ",") if n.len == 1 and n[0].kind != nkExprColonExpr: put(g, tkComma, ",")
put(g, tkParRi, ")") put(g, tkParRi, ")")
of nkCurly: of nkCurly:
put(g, tkCurlyLe, "{") put(g, tkCurlyLe, "{")

View file

@ -2228,6 +2228,12 @@ proc prepareVMValue(arg: PNode): PNode =
if arg.kind in nkLiterals: if arg.kind in nkLiterals:
return arg return arg
if arg.kind == nkExprColonExpr and arg[0].typ != nil and
arg[0].typ.sym != nil and arg[0].typ.sym.magic == mPNimrodNode:
# Poor mans way of protecting static NimNodes
# XXX: Maybe we need a nkNimNode?
return arg
result = copyNode(arg) result = copyNode(arg)
if arg.kind == nkTupleConstr: if arg.kind == nkTupleConstr:
for child in arg: for child in arg:

View file

@ -329,19 +329,56 @@ AST:
Parentheses Parentheses
----------- -----------
Parentheses for affecting operator precedence or tuple construction Parentheses for affecting operator precedence use the ``nnkPar`` node.
are built with the ``nnkPar`` node.
Concrete syntax: Concrete syntax:
.. code-block:: nim .. code-block:: nim
(1, 2, (3)) (a + b) * c
AST: AST:
.. code-block:: nim .. code-block:: nim
nnkPar(nnkIntLit(1), nnkIntLit(2), nnkPar(nnkIntLit(3))) nnkInfix(nnkIdent("*"),
nnkPar(
nnkInfix(nnkIdent("+"), nnkIdent("a"), nnkIdent("b"))),
nnkIdent("c"))
Tuple Constructors
------------------
Nodes for tuple construction are built with the ``nnkTupleConstr`` node.
Concrete syntax:
.. code-block:: nim
(1, 2, 3)
(a: 1, b: 2, c: 3)
()
AST:
.. code-block:: nim
nnkTupleConstr(nnkIntLit(1), nnkIntLit(2), nnkIntLit(3))
nnkTupleConstr(
nnkExprColonExpr(nnkIdent("a"), nnkIntLit(1)),
nnkExprColonExpr(nnkIdent("b"), nnkIntLit(2)),
nnkExprColonExpr(nnkIdent("c"), nnkIntLit(3)))
Since the one tuple would be syntactically identical to parentheses
with an expression in them, the parser expects a trailing comma for
them. For tuple constructors with field names, this is not necessary.
.. code-block:: nim
(1,)
(a: 1)
AST:
.. code-block:: nim
nnkTupleConstr(nnkIntLit(1))
nnkTupleConstr(
nnkExprColonExpr(nnkIdent("a"), nnkIntLit(1)))
Curly braces Curly braces
------------ ------------

View file

@ -133,8 +133,8 @@ proc matchLengthKind*(arg: NimNode; kind: NimNodeKind; length: int): MatchingErr
matchLengthKind(arg, {kind}, length) matchLengthKind(arg, {kind}, length)
proc matchValue(arg: NimNode; kind: set[NimNodeKind]; value: SomeInteger): MatchingError {.compileTime.} = proc matchValue(arg: NimNode; kind: set[NimNodeKind]; value: SomeInteger): MatchingError {.compileTime.} =
let kindFail = not(kind.card == 0 or arg.kind in kind) template kindFail: bool = not(kind.card == 0 or arg.kind in kind)
let valueFail = arg.intVal != int(value) template valueFail: bool = arg.intVal != int(value)
if kindFail or valueFail: if kindFail or valueFail:
result.node = arg result.node = arg
result.kind = WrongKindValue result.kind = WrongKindValue

View file

@ -327,19 +327,43 @@ static:
testArrayAccessOperator(x[y]) testArrayAccessOperator(x[y])
## Parentheses ## Parentheses
scope: scope:
let ast = myquote: let ast = myquote:
(1, 2, (3)) (a + b) * c
ast.matchAst: ast.matchAst:
of nnkPar(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkPar(nnkIntLit(intVal = 3))): of nnkInfix(ident"*", nnkPar(nnkInfix(ident"+", ident"a", ident"b")), ident"c"):
echo "ok" echo "parentheses ok"
## Tuple Constructors
scope:
let ast = myquote:
(1, 2, 3)
(a: 1, b: 2, c: 3)
(1,)
(a: 1)
()
for it in ast:
echo it.lispRepr
it.matchAst:
of nnkTupleConstr(nnkIntLit(intVal = 1), nnkIntLit(intVal = 2), nnkIntLit(intVal = 3)):
echo "simple tuple ok"
of nnkTupleConstr(
nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1)),
nnkExprColonExpr(ident"b", nnkIntLit(intVal = 2)),
nnkExprColonExpr(ident"c", nnkIntLit(intVal = 3))
):
echo "named tuple ok"
of nnkTupleConstr(nnkIntLit(intVal = 1)):
echo "one tuple ok"
of nnkTupleConstr(nnkExprColonExpr(ident"a", nnkIntLit(intVal = 1))):
echo "named one tuple ok"
of nnkTupleConstr():
echo "empty tuple ok"
## Curly braces ## Curly braces

View file

@ -5,7 +5,7 @@ macro check(val, body: untyped): untyped =
result = newStmtList() result = newStmtList()
expectKind body, nnkStmtList expectKind body, nnkStmtList
for b in body: for b in body:
expectKind b, nnkPar expectKind b, nnkTupleConstr
expectLen b, 2 expectLen b, 2
let p = b[0] let p = b[0]
let s = b[1] let s = b[1]

View file

@ -45,7 +45,7 @@ template main =
doAssert lispReprStr([-1]) == """(Bracket (IntLit -1))""" doAssert lispReprStr([-1]) == """(Bracket (IntLit -1))"""
doAssert (-1, 2)[0] == minusOne: doAssert (-1, 2)[0] == minusOne:
"unable to handle negatives after parenthesis" "unable to handle negatives after parenthesis"
doAssert lispReprStr((-1, 2)) == """(Par (IntLit -1) (IntLit 2))""" doAssert lispReprStr((-1, 2)) == """(TupleConstr (IntLit -1) (IntLit 2))"""
proc x(): int = proc x(): int =
var a = 1;-1 # the -1 should act as the return value var a = 1;-1 # the -1 should act as the return value
doAssert x() == minusOne: doAssert x() == minusOne:

View file

@ -48,3 +48,34 @@ macro fun3(): untyped =
int | float | array | seq | object | ptr | pointer | float32 int | float | array | seq | object | ptr | pointer | float32
doAssert n.repr == "int | float | array | seq | object | ptr | pointer | float32", n.repr doAssert n.repr == "int | float | array | seq | object | ptr | pointer | float32", n.repr
fun3() fun3()
macro fun4() =
let n = quote do:
(a: 1)
doAssert n.repr == "(a: 1)", n.repr
fun4()
# nkTupleConstr vs nkPar tests:
block: # lispRepr
macro lispRepr2(a: untyped): string = newLit a.lispRepr
doAssert lispRepr2(()) == """(TupleConstr)"""
doAssert lispRepr2((a: 1)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)))"""
doAssert lispRepr2((a: 1, b: 2)) == """(TupleConstr (ExprColonExpr (Ident "a") (IntLit 1)) (ExprColonExpr (Ident "b") (IntLit 2)))"""
doAssert lispRepr2((1,)) == """(TupleConstr (IntLit 1))"""
doAssert lispRepr2((1, 2)) == """(TupleConstr (IntLit 1) (IntLit 2))"""
doAssert lispRepr2((1, 2, 3.0)) == """(TupleConstr (IntLit 1) (IntLit 2) (FloatLit 3.0))"""
doAssert lispRepr2((1)) == """(Par (IntLit 1))"""
doAssert lispRepr2((1+2)) == """(Par (Infix (Ident "+") (IntLit 1) (IntLit 2)))"""
block: # repr
macro repr2(a: untyped): string = newLit a.repr
doAssert repr2(()) == "()"
doAssert repr2((a: 1)) == "(a: 1)"
doAssert repr2((a: 1, b: 2)) == "(a: 1, b: 2)"
doAssert repr2((1,)) == "(1,)"
doAssert repr2((1, 2)) == "(1, 2)"
doAssert repr2((1, 2, 3.0)) == "(1, 2, 3.0)"
doAssert repr2((1)) == "(1)"
doAssert repr2((1+2)) == "(1 + 2)"

View file

@ -464,7 +464,7 @@ StmtList
DiscardStmt DiscardStmt
Empty Empty
OfBranch OfBranch
Par TupleConstr
Ident "a" Ident "a"
Ident "b" Ident "b"
StmtList StmtList
@ -476,7 +476,7 @@ StmtList
DiscardStmt DiscardStmt
Empty Empty
ElifBranch ElifBranch
Par TupleConstr
Ident "a" Ident "a"
Ident "b" Ident "b"
StmtList StmtList
@ -488,7 +488,7 @@ StmtList
DiscardStmt DiscardStmt
Empty Empty
ExceptBranch ExceptBranch
Par TupleConstr
Ident "a" Ident "a"
Ident "b" Ident "b"
StmtList StmtList

View file

@ -21,7 +21,7 @@ StmtList
Ident "PtrTuple" Ident "PtrTuple"
Empty Empty
PtrTy PtrTy
Par TupleConstr
Ident "int" Ident "int"
Ident "string" Ident "string"
TypeDef TypeDef
@ -43,14 +43,14 @@ StmtList
Ident "RefTupleType" Ident "RefTupleType"
Empty Empty
RefTy RefTy
Par TupleConstr
Ident "int" Ident "int"
Ident "string" Ident "string"
TypeDef TypeDef
Ident "RefTupleVars" Ident "RefTupleVars"
Empty Empty
RefTy RefTy
Par TupleConstr
Ident "a" Ident "a"
Ident "b" Ident "b"
TypeDef TypeDef
@ -80,7 +80,7 @@ StmtList
Empty Empty
Command Command
Ident "static" Ident "static"
Par TupleConstr
Ident "int" Ident "int"
Ident "string" Ident "string"
TypeDef TypeDef
@ -155,7 +155,7 @@ StmtList
Empty Empty
Command Command
Ident "type" Ident "type"
Par TupleConstr
Ident "a" Ident "a"
Ident "b" Ident "b"
TypeDef TypeDef
@ -163,7 +163,7 @@ StmtList
Empty Empty
Command Command
Ident "type" Ident "type"
Par TupleConstr
Ident "int" Ident "int"
Ident "string" Ident "string"
TypeDef TypeDef
@ -287,7 +287,7 @@ StmtList
IdentDefs IdentDefs
Ident "refTuple2" Ident "refTuple2"
RefTy RefTy
Par TupleConstr
Ident "int" Ident "int"
Ident "string" Ident "string"
Empty Empty
@ -524,4 +524,3 @@ dumpTree:
static: static:
staticStmtList1 staticStmtList1
staticStmtList2 staticStmtList2