M examples/error.xd => examples/error.xd +1 -0
@@ 1,4 1,5 @@
[section Error;
+ [p This document contains an error.]
[p
The command [XIDOC-IS-BAD] doesn't exist.
]
M src/xidoc.nim => src/xidoc.nim +2 -1
@@ 9,6 9,7 @@ import xidocpkg/commands/default
import xidocpkg/commands/utils
import xidocpkg/error
import xidocpkg/expand
+import xidocpkg/string_view
import xidocpkg/translations
import xidocpkg/types
@@ 30,7 31,7 @@ proc renderXidoc*(body: string, path = "", target = tHtml, snippet = false, safe
safeMode: safeMode,
verbose: verbose,
stack: @[Frame(
- cmdName: "[top]",
+ cmdName: "[top]".toStringView,
path: some(path),
)]
)
M src/xidocpkg/commands/default.nim => src/xidocpkg/commands/default.nim +1 -1
@@ 370,7 370,7 @@ commands defaultCommands:
target: doc.target,
snippet: true,
stack: @[Frame(
- cmdName: "[top]",
+ cmdName: "[top]".toStringView,
lang: some doc.lookup(lang),
path: some(path),
)]
M src/xidocpkg/error.nim => src/xidocpkg/error.nim +14 -13
@@ 1,3 1,4 @@
+import ./string_view
import ./types
import std/options
import std/pegs
@@ 16,17 17,19 @@ proc xidocWarning*(msge: string) =
stderr.writeLine("Warning: " & msge)
proc format*(err: XidocError, doc: Document, termColors: bool): FormattedXidocError =
- const
- red = "\e[91m"
- yellow = "\e[93m"
- reset = "\e[0m"
+ let
+ red = if termColors: "\e[91m" else: ""
+ yellow = if termColors: "\e[93m" else: ""
+ cyan = if termColors: "\e[96m" else: ""
+ gray = if termColors: "\e[90m" else: ""
+ reset = if termColors: "\e[0m" else: ""
var msg: string
- if termColors:
- msg &= red
- msg &= &"Error while rendering file {doc.stack[0].path.get}\n"
- if termColors:
- msg &= yellow
+ msg &= &"{red}Error while rendering file {doc.stack[0].path.get}\n"
for frame in doc.stack[1..^1]:
+ msg &= yellow
+ if frame.cmd.body == doc.body:
+ let ctx = lineContext(frame.cmd)
+ msg &= &"at ({ctx.lnNumA}, {ctx.colNumA})-({ctx.lnNumB}, {ctx.colNumB}), "
const maxDisplayedArgLength = 48
var truncatedArg = frame.cmdArg.replace(peg"\s+", " ")
if truncatedArg.len > maxDisplayedArgLength:
@@ 35,8 38,6 @@ proc format*(err: XidocError, doc: Document, termColors: bool): FormattedXidocEr
let numOpeningBrackets = truncatedArg.count('[')
let numClosingBrackets = truncatedArg.count(']')
truncatedArg.add "]…".repeat(numOpeningBrackets - numClosingBrackets)
- msg &= &"in [{frame.cmdName}{truncatedArg}]\n"
- if termColors:
- msg &= reset
- msg &= err.msg
+ msg &= &"in {gray}[{cyan}{frame.cmdName}{reset}{truncatedArg}{gray}]\n"
+ msg &= reset & err.msg
return FormattedXidocError(msg: msg)
M src/xidocpkg/expand.nim => src/xidocpkg/expand.nim +2 -2
@@ 59,8 59,7 @@ proc expand*(doc: Document, view: StringView, typ: XidocType): XidocValue =
let name = node.name
let command = doc.lookup(commands, name)
ifSome command:
- var frame = Frame(cmdName: name, cmdArg: node.arg)
- doc.stack.add frame
+ doc.stack.add Frame(cmd: node.whole, cmdName: name, cmdArg: node.arg)
let val = command(node.arg)
discard doc.stack.pop
case typ
@@ 93,6 92,7 @@ proc expand*(doc: Document, view: StringView, typ: XidocType): XidocValue =
of Optional:
discard # TODO
do:
+ doc.stack.add Frame(cmd: node.whole, cmdName: name, cmdArg: node.arg)
xidocError &"Command not found: {name}"
proc expand*(doc: Document, str: string, typ: XidocType): XidocValue =
M src/xidocpkg/parser.nim => src/xidocpkg/parser.nim +3 -14
@@ 15,25 15,13 @@ type
of xnkWhitespace:
newline*: bool
of xnkCommand:
+ whole*: StringView
name*: StringView
arg*: StringView
XidocNodes* = seq[XidocNode]
const nonTextChars = Whitespace + {'[', ']'}
-func lineContext(body: ref string, i: int): tuple[lnNum, colNum: int, msg: string] =
- let lns = body[].splitLines
- var lnIndex = 0
- var lenSum = 0
- while i >= lenSum + lns[lnIndex].len:
- lenSum += lns[lnIndex].len + 1
- lnIndex.inc
- let colIndex = i - lenSum
- result.lnNum = lnIndex + 1
- result.colNum = colIndex + 1
- let caret = &"{' '.repeat(($result.lnNum).len)} │ {' '.repeat(colIndex)}^"
- result.msg = &"{result.lnNum} │ {lns[lnIndex]}\n{caret}"
-
proc skipBalancedText(body: ref string, i: var int, stop: int) =
## Scans `body` from position `i`, incrementing the index
## until it reaches an unbalanced bracket or the end of the string.
@@ 81,6 69,7 @@ proc parseXidocCommand(body: ref string, i: var int, stop: int): XidocNode =
## `body[i]` must be '[' at the start.
## Returns a `XidocNode` with kind `xnkCommand`.
assert body[i] == '['
+ let start = i
i.inc
let name = parseXidocStringHelper(body, i, stop)
if i > stop:
@@ 92,7 81,7 @@ proc parseXidocCommand(body: ref string, i: var int, stop: int): XidocNode =
skipBalancedText(body, i, stop)
if i > stop:
xidocError "Parse error: Unexpected end of file (did you forget to close a bracket?)"
- result = XidocNode(kind: xnkCommand, name: name, arg: body.view(argStart..<i))
+ result = XidocNode(kind: xnkCommand, whole: body.view(start..i), name: name, arg: body.view(argStart..<i))
i.inc
proc parseXidoc*(view: StringView, verbose = false): XidocNodes =
M src/xidocpkg/string_view.nim => src/xidocpkg/string_view.nim +31 -0
@@ 1,3 1,4 @@
+import std/strformat
import std/strutils
type
@@ 19,6 20,36 @@ func toStringView*(body: string): StringView =
converter `$`*(view: StringView): string =
view.body[][view.slice]
+func lineContext*(view: StringView): tuple[lnNumA, colNumA, lnNumB, colNumB: int] =
+ let lns = view.body[].splitLines
+ var lnIndex = 0
+ var lenSum = 0
+ let a = view.slice.a
+ while a >= lenSum + lns[lnIndex].len:
+ lenSum += lns[lnIndex].len + 1
+ lnIndex.inc
+ result.lnNumA = lnIndex + 1
+ result.colNumA = a - lenSum + 1
+ let b = view.slice.b
+ while b >= lenSum + lns[lnIndex].len:
+ lenSum += lns[lnIndex].len + 1
+ lnIndex.inc
+ result.lnNumB = lnIndex + 1
+ result.colNumB = b - lenSum + 1
+
+func lineContext*(body: ref string, i: int): tuple[lnNum, colNum: int, msg: string] =
+ let lns = body[].splitLines
+ var lnIndex = 0
+ var lenSum = 0
+ while i >= lenSum + lns[lnIndex].len:
+ lenSum += lns[lnIndex].len + 1
+ lnIndex.inc
+ let colIndex = i - lenSum
+ result.lnNum = lnIndex + 1
+ result.colNum = colIndex + 1
+ let caret = &"{' '.repeat(($result.lnNum).len)} │ {' '.repeat(colIndex)}^"
+ result.msg = &"{result.lnNum} │ {lns[lnIndex]}\n{caret}"
+
func strip*(view: StringView): StringView =
result = view
while result.slice.a <= result.slice.b and result.body[result.slice.a] in Whitespace:
M src/xidocpkg/types.nim => src/xidocpkg/types.nim +3 -2
@@ 34,8 34,9 @@ type
Command* = proc(arg: StringView): XidocValue
Frame* = object
args*: Table[string, StringView]
- cmdArg*: string
- cmdName*: string
+ cmd*: StringView
+ cmdArg*: StringView
+ cmdName*: StringView
commands*: Table[string, Command]
lang*: Option[Language]
path*: Option[string]