summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2021-08-24 21:50:40 -0700
committerGitHub <noreply@github.com>2021-08-25 06:50:40 +0200
commit3aa16c1de00c723d48e48fe3fdf07a276d1b4b6a (patch)
treed4547a8ab662981357d06334bf46f935f18e65d4
parent3d1bba04ab1092630983695734d6984ddff4688c (diff)
downloadNim-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.md4
-rw-r--r--compiler/lineinfos.nim2
-rw-r--r--compiler/parser.nim34
-rw-r--r--doc/grammar.txt2
-rw-r--r--doc/manual.rst9
-rw-r--r--tests/config.nims1
-rw-r--r--tests/misc/trfc_341.nim27
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()