diff options
author | Timothee Cour <timothee.cour2@gmail.com> | 2021-08-24 21:50:40 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-08-25 06:50:40 +0200 |
commit | 3aa16c1de00c723d48e48fe3fdf07a276d1b4b6a (patch) | |
tree | d4547a8ab662981357d06334bf46f935f18e65d4 | |
parent | 3d1bba04ab1092630983695734d6984ddff4688c (diff) | |
download | Nim-3aa16c1de00c723d48e48fe3fdf07a276d1b4b6a.tar.gz |
fix RFC #341: dot-like operators are now parsed with same precedence as `.` (#18711)
* fix RFC #341: dot-like operators are now parsed with same precedence as `.` * fixup * [skip ci] address comment in changelog * address comment * update grammmar * add manual entry * fixup * -d:nimPreviewDotLikeOps * address comment to unblock PR: move nimPreviewDotLikeOps out of config/config.nims
-rw-r--r-- | changelog.md | 4 | ||||
-rw-r--r-- | compiler/lineinfos.nim | 2 | ||||
-rw-r--r-- | compiler/parser.nim | 34 | ||||
-rw-r--r-- | doc/grammar.txt | 2 | ||||
-rw-r--r-- | doc/manual.rst | 9 | ||||
-rw-r--r-- | tests/config.nims | 1 | ||||
-rw-r--r-- | tests/misc/trfc_341.nim | 27 |
7 files changed, 74 insertions, 5 deletions
diff --git a/changelog.md b/changelog.md index 447401cd0..3ba008cf3 100644 --- a/changelog.md +++ b/changelog.md @@ -104,6 +104,10 @@ - In `std/dom`, `Interval` is now a `ref object`, same as `Timeout`. Definitions of `setTimeout`, `clearTimeout`, `setInterval`, `clearInterval` were updated. +- With `-d:nimPreviewDotLikeOps` (default in devel), dot-like operators (operators starting with `.`, but not with `..`) + now have the same precedence as `.`, so that `a.?b.c` is now parsed as `(a.?b).c` instead of `a.?(b.c)`. + A warning is generated when a dot-like operator is used without `-d:nimPreviewDotLikeOps`. + - The allocator for Nintendo Switch, which was nonfunctional because of breaking changes in libnx, was removed, in favour of the new `-d:nimAllocPagesViaMalloc` option. diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index c64d7b9dd..fd7ce0c04 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -46,6 +46,7 @@ type warnCannotOpenFile = "CannotOpenFile", warnOctalEscape = "OctalEscape", warnXIsNeverRead = "XIsNeverRead", warnXmightNotBeenInit = "XmightNotBeenInit", warnDeprecated = "Deprecated", warnConfigDeprecated = "ConfigDeprecated", + warnDotLikeOps = "DotLikeOps", warnSmallLshouldNotBeUsed = "SmallLshouldNotBeUsed", warnUnknownMagic = "UnknownMagic", warnRstRedefinitionOfLabel = "RedefinitionOfLabel", warnRstUnknownSubstitutionX = "UnknownSubstitutionX", @@ -116,6 +117,7 @@ const warnXmightNotBeenInit: "'$1' might not have been initialized", warnDeprecated: "$1", warnConfigDeprecated: "config file '$1' is deprecated", + warnDotLikeOps: "$1", warnSmallLshouldNotBeUsed: "'l' should not be used as an identifier; may look like '1' (one)", warnUnknownMagic: "unknown magic '$1' might crash the compiler", warnRstRedefinitionOfLabel: "redefinition of label '$1'", diff --git a/compiler/parser.nim b/compiler/parser.nim index b089614b2..30180e545 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -466,6 +466,17 @@ proc dotExpr(p: var Parser, a: PNode): PNode = exprColonEqExprListAux(p, tkParRi, y) result = y +proc dotLikeExpr(p: var Parser, a: PNode): PNode = + #| dotLikeExpr = expr DOTLIKEOP optInd symbol + var info = p.parLineInfo + result = newNodeI(nkInfix, info) + optInd(p, result) + var opNode = newIdentNodeP(p.tok.ident, p) + getTok(p) + result.add(opNode) + result.add(a) + result.add(parseSymbol(p, smAfterDot)) + proc qualifiedIdent(p: var Parser): PNode = #| qualifiedIdent = symbol ('.' optInd symbol)? result = parseSymbol(p) @@ -788,10 +799,15 @@ proc commandExpr(p: var Parser; r: PNode; mode: PrimaryMode): PNode = p.hasProgress = false result.add commandParam(p, isFirstParam, mode) +proc isDotLike(tok: Token): bool = + result = tok.tokType == tkOpr and tok.ident.s.len > 1 and + tok.ident.s[0] == '.' and tok.ident.s[1] != '.' + proc primarySuffix(p: var Parser, r: PNode, baseIndent: int, mode: PrimaryMode): PNode = #| primarySuffix = '(' (exprColonEqExpr comma?)* ')' #| | '.' optInd symbol generalizedLit? + #| | DOTLIKEOP optInd symbol generalizedLit? #| | '[' optInd exprColonEqExprList optPar ']' #| | '{' optInd exprColonEqExprList optPar '}' #| | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax @@ -840,11 +856,19 @@ proc primarySuffix(p: var Parser, r: PNode, # `foo ref` or `foo ptr`. Unfortunately, these two are also # used as infix operators for the memory regions feature and # the current parsing rules don't play well here. - if p.inPragma == 0 and (isUnary(p.tok) or p.tok.tokType notin {tkOpr, tkDotDot}): - # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet - # solution, but pragmas.nim can't handle that - result = commandExpr(p, result, mode) - break + let isDotLike2 = p.tok.isDotLike + if isDotLike2 and p.lex.config.isDefined("nimPreviewDotLikeOps"): + # synchronize with `tkDot` branch + result = dotLikeExpr(p, result) + result = parseGStrLit(p, result) + else: + if isDotLike2: + parMessage(p, warnDotLikeOps, "dot-like operators will be parsed differently with `-d:nimPreviewDotLikeOps`") + if p.inPragma == 0 and (isUnary(p.tok) or p.tok.tokType notin {tkOpr, tkDotDot}): + # actually parsing {.push hints:off.} as {.push(hints:off).} is a sweet + # solution, but pragmas.nim can't handle that + result = commandExpr(p, result, mode) + break else: break diff --git a/doc/grammar.txt b/doc/grammar.txt index d4f4a0515..f58621b97 100644 --- a/doc/grammar.txt +++ b/doc/grammar.txt @@ -29,6 +29,7 @@ exprList = expr ^+ comma exprColonEqExprList = exprColonEqExpr (comma exprColonEqExpr)* (comma)? dotExpr = expr '.' optInd (symbol | '[:' exprList ']') explicitGenericInstantiation = '[:' exprList ']' ( '(' exprColonEqExpr ')' )? +dotLikeExpr = expr DOTLIKEOP optInd symbol qualifiedIdent = symbol ('.' optInd symbol)? setOrTableConstr = '{' ((exprColonEqExpr comma)* | ':' ) '}' castExpr = 'cast' ('[' optInd typeDesc optPar ']' '(' optInd expr optPar ')') / @@ -56,6 +57,7 @@ tupleConstr = '(' optInd (exprColonEqExpr comma?)* optPar ')' arrayConstr = '[' optInd (exprColonEqExpr comma?)* optPar ']' primarySuffix = '(' (exprColonEqExpr comma?)* ')' | '.' optInd symbol generalizedLit? + | DOTLIKEOP optInd symbol generalizedLit? | '[' optInd exprColonEqExprList optPar ']' | '{' optInd exprColonEqExprList optPar '}' | &( '`'|IDENT|literal|'cast'|'addr'|'type') expr # command syntax diff --git a/doc/manual.rst b/doc/manual.rst index 9c7c219e9..72d50a901 100644 --- a/doc/manual.rst +++ b/doc/manual.rst @@ -748,6 +748,7 @@ has the second-lowest precedence. Otherwise, precedence is determined by the first character. + ================ ======================================================= ================== =============== Precedence level Operators First character Terminal symbol ================ ======================================================= ================== =============== @@ -783,6 +784,14 @@ of a call or whether it is parsed as a tuple constructor: .. code-block:: nim echo (1, 2) # pass the tuple (1, 2) to echo +Dot-like operators +------------------ + +Terminal symbol in the grammar: `DOTLIKEOP`. + +Dot-like operators are operators starting with `.`, but not with `..`, for e.g. `.?`; +they have the same precedence as `.`, so that `a.?b.c` is parsed as `(a.?b).c` instead of `a.?(b.c)`. + Grammar ------- diff --git a/tests/config.nims b/tests/config.nims index 0327f0b76..86787db1d 100644 --- a/tests/config.nims +++ b/tests/config.nims @@ -36,3 +36,4 @@ switch("define", "nimExperimentalAsyncjsThen") switch("define", "nimExperimentalLinenoiseExtra") switch("define", "nimPreviewFloatRoundtrip") +switch("define", "nimPreviewDotLikeOps") diff --git a/tests/misc/trfc_341.nim b/tests/misc/trfc_341.nim new file mode 100644 index 000000000..37cf675c6 --- /dev/null +++ b/tests/misc/trfc_341.nim @@ -0,0 +1,27 @@ +# test for https://github.com/nim-lang/RFCs/issues/341 +import std/json +import std/jsonutils +import std/macros + +macro fn1(a: untyped): string = newLit a.lispRepr + +doAssert fn1(a.?b.c) == """(DotExpr (Infix (Ident ".?") (Ident "a") (Ident "b")) (Ident "c"))""" + +template `.?`(a: JsonNode, b: untyped{ident}): JsonNode = + a[astToStr(b)] + +proc identity[T](a: T): T = a +proc timesTwo[T](a: T): T = a * 2 + +template main = + let a = (a1: 1, a2: "abc", a3: (a4: 2.5)) + let j = a.toJson + doAssert j.?a1.getInt == 1 + doAssert j.?a3.?a4.getFloat == 2.5 + doAssert j.?a3.?a4.getFloat.timesTwo == 5.0 + doAssert j.?a3.identity.?a4.getFloat.timesTwo == 5.0 + doAssert j.identity.?a3.identity.?a4.identity.getFloat.timesTwo == 5.0 + doAssert j.identity.?a3.?a4.identity.getFloat.timesTwo == 5.0 + +static: main() +main() |