Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[superseded] macros.parseStmt,parseExpr: show erroneous code on error #9853

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 18 additions & 4 deletions lib/core/macros.nim
Expand Up @@ -497,19 +497,33 @@ proc internalErrorFlag*(): string {.magic: "NError", noSideEffect.}
## Some builtins set an error flag. This is then turned into a proper
## exception. **Note**: Ordinary application code should not call this.

proc indent(s: string, prefix: string): string =
# Avoids `strutils.indent` dependency
result.add prefix
for ai in s:
result.add ai
if ai == '\n': result.add prefix

template handleParseError(s: string) =
let x = internalErrorFlag()
if x.len > 0:
var msg = "parse error: "
msg.addQuoted x
msg.add " with:\n"
msg.add s.indent ">"
raise newException(ValueError, msg)

proc parseExpr*(s: string): NimNode {.noSideEffect, compileTime.} =
## Compiles the passed string to its AST representation.
## Expects a single expression. Raises ``ValueError`` for parsing errors.
result = internalParseExpr(s)
let x = internalErrorFlag()
if x.len > 0: raise newException(ValueError, x)
handleParseError(s)

proc parseStmt*(s: string): NimNode {.noSideEffect, compileTime.} =
## Compiles the passed string to its AST representation.
## Expects one or more statements. Raises ``ValueError`` for parsing errors.
result = internalParseStmt(s)
let x = internalErrorFlag()
if x.len > 0: raise newException(ValueError, x)
handleParseError(s)

proc getAst*(macroOrTemplate: untyped): NimNode {.magic: "ExpandToAst", noSideEffect.}
## Obtains the AST nodes returned from a macro or template invocation.
Expand Down
15 changes: 15 additions & 0 deletions tests/assert/testhelper.nim
@@ -1,6 +1,9 @@
from strutils import endsWith, split
from os import isAbsolute

from macros import quote, newIdentNode
include "system/helpers.nim" # for $instantiationInfo

proc checkMsg*(msg, expectedEnd, name: string)=
let filePrefix = msg.split(' ', maxSplit = 1)[0]
if not filePrefix.isAbsolute:
Expand All @@ -10,3 +13,15 @@ proc checkMsg*(msg, expectedEnd, name: string)=
else:
echo name, ":ok"

macro assertEquals*(lhs, rhs): untyped =
# We can't yet depend on `unittests` in testament, so we define this
# simple macro that shows helpful error on failure.
result = quote do:
let lhs2 = `lhs`
let rhs2 = `rhs`
if lhs2 != rhs2:
const loc = instantiationInfo(-1, true)
var msg = "`assertEquals` failed at " & $loc
# brackets are needed to spot differences with invisible chars (eg space).
msg.add "\nlhs:{" & $lhs2 & "}\nrhs:{" & $rhs2 & "}"
doAssert false, msg
68 changes: 53 additions & 15 deletions tests/macros/ttryparseexpr.nim
@@ -1,20 +1,58 @@
discard """
outputsub: '''Error: invalid indentation 45'''
"""

# feature request #1473
import macros
import strutils
import "tests/assert/testhelper.nim"

block: # parseExpr
macro test(text: string): untyped =
try:
result = parseExpr(text.strVal)
except ValueError:
result = newLit getCurrentExceptionMsg()

const
valid = 45

static:
assertEquals test("valid"), 45 # valid test

block: # invalid test
const a = test("foo&&")
doAssert a.endsWith """
Error: invalid indentation" with:
>foo&&""", a

block: # bug #2504
const a = test("\"")
doAssert a.endsWith """
Error: closing \" expected" with:
>""""

block: # parseStmt
macro test(text: string): untyped =
try:
result = parseStmt(text.strVal)
except ValueError:
result = newLit getCurrentExceptionMsg()

const
valid = 45

macro test(text: string): untyped =
try:
result = parseExpr(text.strVal)
except ValueError:
result = newLit getCurrentExceptionMsg()
static:
assertEquals test("(let a = valid; valid)"), 45 # valid test

const
valid = 45
a = test("foo&&")
b = test("valid")
c = test("\"") # bug #2504
block:
const a = test("""
let a = 1
let a2 = 1""")
doAssert a.endsWith """Error: invalid indentation" with:
>let a = 1
> let a2 = 1"""

echo a, " ", b
when false: # BUG: this shouldn't even compile, see https://github.com/nim-lang/Nim/issues/9918
block:
macro fun(): untyped =
parseStmt("""
let a1 = 2 # wrong indentation
a1""")
doAssert fun() == 2