~xigoi/xidoc

3155e68be743665d27daca0ed04c9e774f122680 — Adam Blažek 1 year, 11 months ago f6974bd string-view
Error messages report locations
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]