Improved and fixed issues with error reporting

This commit is contained in:
Felix Krause 2016-09-23 15:42:24 +02:00
parent a8d68d1696
commit 2ad41d349e
4 changed files with 167 additions and 141 deletions

View File

@ -44,7 +44,8 @@ type
folded: bool folded: bool
chomp: ChompType chomp: ChompType
c: char c: char
tokenLineGetter: proc(lex: YamlLexer, marker: bool): string {.raises: [].} tokenLineGetter: proc(lex: YamlLexer, pos: tuple[line, column: int],
marker: bool): string {.raises: [].}
searchColonImpl: proc(lex: YamlLexer): bool searchColonImpl: proc(lex: YamlLexer): bool
YamlLexer* = ref YamlLexerObj YamlLexer* = ref YamlLexerObj
@ -190,26 +191,26 @@ proc afterMark(lex: YamlLexer, t: typedesc[StringSource], m: int):
int {.inline.} = int {.inline.} =
lex.sSource.pos - m lex.sSource.pos - m
proc lineWithMarker(lex: YamlLexer, t: typedesc[BaseLexer], marker: bool): proc lineWithMarker(lex: YamlLexer, pos: tuple[line, column: int],
string = t: typedesc[BaseLexer], marker: bool): string =
if lex.curStartPos.line == lex.blSource.lineNumber: if pos.line == lex.blSource.lineNumber:
result = lex.blSource.getCurrentLine(false) result = lex.blSource.getCurrentLine(false)
if marker: result.add(spaces(lex.curStartPos.column - 1) & "^\n") if marker: result.add(spaces(pos.column - 1) & "^\n")
else: result = "" else: result = ""
proc lineWithMarker(lex: YamlLexer, t: typedesc[StringSource], marker: bool): proc lineWithMarker(lex: YamlLexer, pos: tuple[line, column: int],
string = t: typedesc[StringSource], marker: bool): string =
var var
lineStartIndex = lex.sSource.pos lineStartIndex = lex.sSource.pos
lineEndIndex: int lineEndIndex: int
curLine = lex.sSource.line curLine = lex.sSource.line
if lex.curStartPos.line == curLine: if pos.line == curLine:
lineEndIndex = lex.sSource.pos lineEndIndex = lex.sSource.pos
while lex.sSource.src[lineEndIndex] notin lineEnd: inc(lineEndIndex) while lex.sSource.src[lineEndIndex] notin lineEnd: inc(lineEndIndex)
while true: while true:
while lineStartIndex >= 0 and lex.sSource.src[lineStartIndex] notin lineEnd: while lineStartIndex >= 0 and lex.sSource.src[lineStartIndex] notin lineEnd:
dec(lineStartIndex) dec(lineStartIndex)
if curLine == lex.curStartPos.line: if curLine == pos.line:
inc(lineStartIndex) inc(lineStartIndex)
break break
let wasLF = lex.sSource.src[lineStartIndex] == '\l' let wasLF = lex.sSource.src[lineStartIndex] == '\l'
@ -220,7 +221,7 @@ proc lineWithMarker(lex: YamlLexer, t: typedesc[StringSource], marker: bool):
dec(lineEndIndex) dec(lineEndIndex)
dec(curLine) dec(curLine)
result = lex.sSource.src.substr(lineStartIndex, lineEndIndex - 1) & "\n" result = lex.sSource.src.substr(lineStartIndex, lineEndIndex - 1) & "\n"
if marker: result.add(spaces(lex.curStartPos.column - 1) & "^\n") if marker: result.add(spaces(pos.column - 1) & "^\n")
# lexer states # lexer states
@ -246,7 +247,7 @@ proc docEndAfterBlockScalar[T](lex: YamlLexer): bool
proc tagHandle[T](lex: YamlLexer): bool proc tagHandle[T](lex: YamlLexer): bool
proc anchor[T](lex: YamlLexer): bool proc anchor[T](lex: YamlLexer): bool
proc alias[T](lex: YamlLexer): bool proc alias[T](lex: YamlLexer): bool
proc streamEnd(lex: YamlLexer): bool proc streamEnd[T](lex: YamlLexer): bool
{.pop.} {.pop.}
# implementation # implementation
@ -371,8 +372,7 @@ proc expectLineEnd[T](lex: YamlLexer): bool =
lex.advance(T) lex.advance(T)
while lex.c notin lineEnd: lex.advance(T) while lex.c notin lineEnd: lex.advance(T)
of EndOfFile: of EndOfFile:
startToken[T](lex) lex.nextState = streamEnd[T]
lex.nextState = streamEnd
break break
of '\l': of '\l':
lex.lexLF(T) lex.lexLF(T)
@ -857,7 +857,7 @@ proc blockScalarLineStart[T](lex: YamlLexer, recentWasMoreIndented: var bool):
lex.lexCR(T) lex.lexCR(T)
lex.indentation = 0 lex.indentation = 0
of EndOfFile: of EndOfFile:
lex.nextState = streamEnd lex.nextState = streamEnd[T]
return false return false
of ' ', '\t': of ' ', '\t':
recentWasMoreIndented = true recentWasMoreIndented = true
@ -886,7 +886,7 @@ proc blockScalar[T](lex: YamlLexer): bool =
lex.lexCR(T) lex.lexCR(T)
lex.newlines.inc() lex.newlines.inc()
of EndOfFile: of EndOfFile:
lex.nextState = streamEnd lex.nextState = streamEnd[T]
break outer break outer
else: else:
if lex.blockScalarIndent <= lex.indentation: if lex.blockScalarIndent <= lex.indentation:
@ -1032,13 +1032,15 @@ proc alias[T](lex: YamlLexer): bool =
lex.cur = ltAlias lex.cur = ltAlias
result = true result = true
proc streamEnd(lex: YamlLexer): bool = proc streamEnd[T](lex: YamlLexer): bool =
debug("lex: streamEnd") debug("lex: streamEnd")
startToken[T](lex)
lex.cur = ltStreamEnd lex.cur = ltStreamEnd
result = true result = true
proc tokenLine[T](lex: YamlLexer, marker: bool): string = proc tokenLine[T](lex: YamlLexer, pos: tuple[line, column: int], marker: bool):
result = lex.lineWithMarker(T, marker) string =
result = lex.lineWithMarker(pos, T, marker)
proc searchColon[T](lex: YamlLexer): bool = proc searchColon[T](lex: YamlLexer): bool =
var flowDepth = if lex.cur in [ltBraceOpen, ltBracketOpen]: 1 else: 0 var flowDepth = if lex.cur in [ltBraceOpen, ltBracketOpen]: 1 else: 0
@ -1180,7 +1182,11 @@ proc endBlockScalar*(lex: YamlLexer) =
lex.folded = true lex.folded = true
proc getTokenLine*(lex: YamlLexer, marker: bool = true): string = proc getTokenLine*(lex: YamlLexer, marker: bool = true): string =
result = lex.tokenLineGetter(lex, marker) result = lex.tokenLineGetter(lex, lex.curStartPos, marker)
proc getTokenLine*(lex: YamlLexer, pos: tuple[line, column: int],
marker: bool = true): string =
result = lex.tokenLineGetter(lex, pos, marker)
proc isImplicitKeyStart*(lex: YamlLexer): bool = proc isImplicitKeyStart*(lex: YamlLexer): bool =
result = lex.searchColonImpl(lex) result = lex.searchColonImpl(lex)

View File

@ -69,6 +69,16 @@ template assertStringEqual(expected, actual: string) =
echo "expected:\n", expected, "\nactual:\n", actual echo "expected:\n", expected, "\nactual:\n", actual
assert(false) assert(false)
template expectConstructionError(l, c: int, body: typed) =
try:
body
echo "Expected YamlConstructionError, but none was raised!"
fail()
except YamlConstructionError:
let e = (ref YamlConstructionError)(getCurrentException())
doAssert l == e.line, "Expected error line " & $l & ", was " & $e.line
doAssert c == e.column, "Expected error column " & $c & ", was " & $e.column
proc newNode(v: string): ref Node = proc newNode(v: string): ref Node =
new(result) new(result)
result.value = v result.value = v
@ -305,19 +315,19 @@ suite "Serialization":
test "Load Tuple - unknown field": test "Load Tuple - unknown field":
let input = "str: value\nfoo: bar\ni: 42\nb: true" let input = "str: value\nfoo: bar\ni: 42\nb: true"
var result: MyTuple var result: MyTuple
expect(YamlConstructionError): expectConstructionError(2, 1):
load(input, result) load(input, result)
test "Load Tuple - missing field": test "Load Tuple - missing field":
let input = "str: value\nb: true" let input = "str: value\nb: true"
var result: MyTuple var result: MyTuple
expect(YamlConstructionError): expectConstructionError(2, 8):
load(input, result) load(input, result)
test "Load Tuple - duplicate field": test "Load Tuple - duplicate field":
let input = "str: value\ni: 42\nb: true\nb: true" let input = "str: value\ni: 42\nb: true\nb: true"
var result: MyTuple var result: MyTuple
expect(YamlConstructionError): expectConstructionError(4, 1):
load(input, result) load(input, result)
test "Load Multiple Documents": test "Load Multiple Documents":
@ -350,21 +360,21 @@ suite "Serialization":
"%YAML 1.2\n--- \nfirstnamechar: P\nsurname: Pan\nage: 12", output) "%YAML 1.2\n--- \nfirstnamechar: P\nsurname: Pan\nage: 12", output)
test "Load custom object - unknown field": test "Load custom object - unknown field":
let input = "firstnamechar: P\nsurname: Pan\nage: 12\noccupation: free" let input = " firstnamechar: P\n surname: Pan\n age: 12\n occupation: free"
var result: Person var result: Person
expect(YamlConstructionError): expectConstructionError(4, 3):
load(input, result) load(input, result)
test "Load custom object - missing field": test "Load custom object - missing field":
let input = "surname: Pan\nage: 12" let input = "surname: Pan\nage: 12\n "
var result: Person var result: Person
expect(YamlConstructionError): expectConstructionError(3, 3):
load(input, result) load(input, result)
test "Load custom object - duplicate field": test "Load custom object - duplicate field":
let input = "firstnamechar: P\nsurname: Pan\nage: 12\nsurname: Pan" let input = "firstnamechar: P\nsurname: Pan\nage: 12\nsurname: Pan"
var result: Person var result: Person
expect(YamlConstructionError): expectConstructionError(4, 1):
load(input, result) load(input, result)
test "Load sequence with explicit tags": test "Load sequence with explicit tags":
@ -433,9 +443,9 @@ suite "Serialization":
barkometer: 13""", output barkometer: 13""", output
test "Load custom variant object - missing field": test "Load custom variant object - missing field":
let input = "{name: Bastet, kind: akCat}" let input = "[{name: Bastet}, {kind: akCat}]"
var result: Animal var result: Animal
expect(YamlConstructionError): expectConstructionError(1, 32):
load(input, result) load(input, result)
test "Dump cyclic data structure": test "Dump cyclic data structure":

View File

@ -56,6 +56,7 @@ type
nextAnchorId: AnchorId nextAnchorId: AnchorId
newlines: int newlines: int
explicitFlowKey: bool explicitFlowKey: bool
plainScalarStart: tuple[line, column: int]
LevelEndResult = enum LevelEndResult = enum
lerNothing, lerOne, lerAdditionalMapEnd lerNothing, lerOne, lerAdditionalMapEnd
@ -126,7 +127,6 @@ proc illegalToken(c: ParserContext, expected: string = ""):
if expected.len > 0: msg.add(" (expected " & expected & ")") if expected.len > 0: msg.add(" (expected " & expected & ")")
msg.add(": " & $c.lex.cur) msg.add(": " & $c.lex.cur)
result = c.generateError(msg) result = c.generateError(msg)
echo result.line, ", ", result.column
proc callCallback(c: ParserContext, msg: string) {.raises: [YamlParserError].} = proc callCallback(c: ParserContext, msg: string) {.raises: [YamlParserError].} =
try: try:
@ -332,6 +332,14 @@ proc handleFlowPlainScalar(c: ParserContext) =
c.advance() c.advance()
c.lex.newlines = 0 c.lex.newlines = 0
proc lastTokenContext(s: YamlStream, line, column: var int,
lineContent: var string): bool =
let c = ParserContext(s)
line = c.lex.curStartPos.line
column = c.lex.curStartPos.column
lineContent = c.lex.getTokenLine(true)
result = true
# --- macros for defining parser states --- # --- macros for defining parser states ---
template capitalize(s: string): string = template capitalize(s: string): string =
@ -409,7 +417,7 @@ macro parserState(name: untyped, impl: untyped): typed =
parserStates(initial, blockLineStart, blockObjectStart, blockAfterObject, parserStates(initial, blockLineStart, blockObjectStart, blockAfterObject,
scalarEnd, plainScalarEnd, objectEnd, expectDocEnd, startDoc, scalarEnd, plainScalarEnd, objectEnd, expectDocEnd, startDoc,
afterDocument, closeMoreIndentedLevels, afterDocument, closeMoreIndentedLevels, afterPlainScalarYield,
emitEmptyScalar, tagHandle, anchor, alias, flow, leaveFlowMap, emitEmptyScalar, tagHandle, anchor, alias, flow, leaveFlowMap,
leaveFlowSeq, flowAfterObject, leaveFlowSinglePairMap) leaveFlowSeq, flowAfterObject, leaveFlowSinglePairMap)
@ -592,7 +600,7 @@ parserState blockObjectStart:
state = scalarEnd state = scalarEnd
of ltScalarPart: of ltScalarPart:
result = c.handleBlockItemStart(e) result = c.handleBlockItemStart(e)
let cachedPos = c.lex.curStartPos c.plainScalarStart = c.lex.curStartPos
while true: while true:
c.advance() c.advance()
case c.lex.cur case c.lex.cur
@ -602,7 +610,6 @@ parserState blockObjectStart:
of ltScalarPart: discard of ltScalarPart: discard
of ltEmptyLine: c.lex.newlines.inc() of ltEmptyLine: c.lex.newlines.inc()
else: break else: break
c.lex.curStartPos = cachedPos
c.lex.newlines = 0 c.lex.newlines = 0
state = plainScalarEnd state = plainScalarEnd
stored = blockAfterObject stored = blockAfterObject
@ -640,9 +647,19 @@ parserState scalarEnd:
parserState plainScalarEnd: parserState plainScalarEnd:
c.currentScalar(e) c.currentScalar(e)
result = true result = true
state = objectEnd c.lastTokenContextImpl = proc(s: YamlStream, line, column: var int,
lineContent: var string): bool {.raises: [].} =
let c = ParserContext(s)
(line, column) = c.plainScalarStart
lineContent = c.lex.getTokenLine(c.plainScalarStart, true)
result = true
state = afterPlainScalarYield
stored = blockAfterObject stored = blockAfterObject
parserState afterPlainScalarYield:
c.lastTokenContextImpl = lastTokenContext
state = objectEnd
parserState blockAfterObject: parserState blockAfterObject:
case c.lex.cur case c.lex.cur
of ltIndentation, ltEmptyLine: of ltIndentation, ltEmptyLine:
@ -1002,14 +1019,6 @@ parserState flowAfterObject:
else: else:
raise c.generateError("Unexpected content (expected flow indicator)") raise c.generateError("Unexpected content (expected flow indicator)")
proc lastTokenContext(s: YamlStream, line, column: var int,
lineContent: var string): bool =
let c = ParserContext(s)
line = c.lex.curStartPos.line
column = c.lex.curStartPos.column
lineContent = c.lex.getTokenLine(true)
result = true
# --- parser initialization --- # --- parser initialization ---
proc init(c: ParserContext, p: YamlParser) = proc init(c: ParserContext, p: YamlParser) =

View File

@ -106,6 +106,12 @@ proc safeTagUri(id: TagId): string {.raises: [].} =
else: return uri else: return uri
except KeyError: internalError("Unexpected KeyError for TagId " & $id) except KeyError: internalError("Unexpected KeyError for TagId " & $id)
proc constructionError(s: YamlStream, msg: string): ref YamlConstructionError =
result = newException(YamlConstructionError, msg)
if not s.getLastTokenContext(result.line, result.column, result.lineContent):
(result.line, result.column) = (-1, -1)
result.lineContent = ""
template constructScalarItem*(s: var YamlStream, i: untyped, template constructScalarItem*(s: var YamlStream, i: untyped,
t: typedesc, content: untyped) = t: typedesc, content: untyped) =
## Helper template for implementing ``constructObject`` for types that ## Helper template for implementing ``constructObject`` for types that
@ -113,13 +119,14 @@ template constructScalarItem*(s: var YamlStream, i: untyped,
## the scalar as ``YamlStreamEvent`` in the content. Exceptions raised in ## the scalar as ``YamlStreamEvent`` in the content. Exceptions raised in
## the content will be automatically catched and wrapped in ## the content will be automatically catched and wrapped in
## ``YamlConstructionError``, which will then be raised. ## ``YamlConstructionError``, which will then be raised.
bind constructionError
let i = s.next() let i = s.next()
if i.kind != yamlScalar: if i.kind != yamlScalar:
raise newException(YamlConstructionError, "Expected scalar") raise constructionError(s, "Expected scalar")
try: content try: content
except YamlConstructionError: raise except YamlConstructionError: raise
except Exception: except Exception:
var e = newException(YamlConstructionError, var e = constructionError(s,
"Cannot construct to " & name(t) & ": " & item.scalarContent) "Cannot construct to " & name(t) & ": " & item.scalarContent)
e.parent = getCurrentException() e.parent = getCurrentException()
raise e raise e
@ -139,22 +146,28 @@ proc representObject*(value: string, ts: TagStyle,
## represents a string as YAML scalar ## represents a string as YAML scalar
c.put(scalarEvent(value, tag, yAnchorNone)) c.put(scalarEvent(value, tag, yAnchorNone))
proc parseHex[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64](s: string): T = proc parseHex[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64](
s: YamlStream, val: string): T =
result = 0 result = 0
for i in 2..<s.len: for i in 2..<val.len:
case s[i] case val[i]
of '_': discard of '_': discard
of '0'..'9': result = result shl 4 or T(ord(s[i]) - ord('0')) of '0'..'9': result = result shl 4 or T(ord(val[i]) - ord('0'))
of 'a'..'f': result = result shl 4 or T(ord(s[i]) - ord('a') + 10) of 'a'..'f': result = result shl 4 or T(ord(val[i]) - ord('a') + 10)
of 'A'..'F': result = result shl 4 or T(ord(s[i]) - ord('A') + 10) of 'A'..'F': result = result shl 4 or T(ord(val[i]) - ord('A') + 10)
else: raise newException(ValueError, "Invalid character in hex: " & escape("" & s[i])) else:
raise s.constructionError("Invalid character in hex: " &
escape("" & val[i]))
proc parseOctal[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64](s: string): T = proc parseOctal[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64](
for i in 2..<s.len: s: YamlStream, val: string): T =
case s[i] for i in 2..<val.len:
case val[i]
of '_': discard of '_': discard
of '0'..'7': result = result shl 3 + T((ord(s[i]) - ord('0'))) of '0'..'7': result = result shl 3 + T((ord(val[i]) - ord('0')))
else: raise newException(ValueError, "Invalid character in hex: " & escape("" & s[i])) else:
raise s.constructionError("Invalid character in hex: " &
escape("" & val[i]))
proc constructObject*[T: int8|int16|int32|int64]( proc constructObject*[T: int8|int16|int32|int64](
s: var YamlStream, c: ConstructionContext, result: var T) s: var YamlStream, c: ConstructionContext, result: var T)
@ -162,9 +175,9 @@ proc constructObject*[T: int8|int16|int32|int64](
## constructs an integer value from a YAML scalar ## constructs an integer value from a YAML scalar
constructScalarItem(s, item, T): constructScalarItem(s, item, T):
if item.scalarContent[0] == '0' and item.scalarContent[1] in {'x', 'X' }: if item.scalarContent[0] == '0' and item.scalarContent[1] in {'x', 'X' }:
result = parseHex[T](item.scalarContent) result = parseHex[T](s, item.scalarContent)
elif item.scalarContent[0] == '0' and item.scalarContent[1] in {'o', 'O'}: elif item.scalarContent[0] == '0' and item.scalarContent[1] in {'o', 'O'}:
result = parseOctal[T](item.scalarContent) result = parseOctal[T](s, item.scalarContent)
else: else:
result = T(parseBiggestInt(item.scalarContent)) result = T(parseBiggestInt(item.scalarContent))
@ -201,9 +214,9 @@ proc constructObject*[T: uint8|uint16|uint32|uint64](
## construct an unsigned integer value from a YAML scalar ## construct an unsigned integer value from a YAML scalar
constructScalarItem(s, item, T): constructScalarItem(s, item, T):
if item.scalarContent[0] == '0' and item.scalarContent[1] in {'x', 'X'}: if item.scalarContent[0] == '0' and item.scalarContent[1] in {'x', 'X'}:
result = parseHex[T](item.scalarContent) result = parseHex[T](s, item.scalarContent)
elif item.scalarContent[0] == '0' and item.scalarContent[1] in {'o', 'O'}: elif item.scalarContent[0] == '0' and item.scalarContent[1] in {'o', 'O'}:
result = parseOctal[T](item.scalarContent) result = parseOctal[T](s, item.scalarContent)
else: result = T(parseBiggestUInt(item.scalarContent)) else: result = T(parseBiggestUInt(item.scalarContent))
proc constructObject*(s: var YamlStream, c: ConstructionContext, proc constructObject*(s: var YamlStream, c: ConstructionContext,
@ -251,8 +264,8 @@ proc constructObject*[T: float|float32|float64](
else: result = Inf else: result = Inf
of yTypeFloatNaN: result = NaN of yTypeFloatNaN: result = NaN
else: else:
raise newException(YamlConstructionError, raise s.constructionError("Cannot construct to float: " &
"Cannot construct to float: " & item.scalarContent) escape(item.scalarContent))
proc representObject*[T: float|float32|float64](value: T, ts: TagStyle, proc representObject*[T: float|float32|float64](value: T, ts: TagStyle,
c: SerializationContext, tag: TagId) {.raises: [].} = c: SerializationContext, tag: TagId) {.raises: [].} =
@ -274,8 +287,8 @@ proc constructObject*(s: var YamlStream, c: ConstructionContext,
of yTypeBoolTrue: result = true of yTypeBoolTrue: result = true
of yTypeBoolFalse: result = false of yTypeBoolFalse: result = false
else: else:
raise newException(YamlConstructionError, raise s.constructionError("Cannot construct to bool: " &
"Cannot construct to bool: " & item.scalarContent) escape(item.scalarContent))
proc representObject*(value: bool, ts: TagStyle, c: SerializationContext, proc representObject*(value: bool, ts: TagStyle, c: SerializationContext,
tag: TagId) {.raises: [].} = tag: TagId) {.raises: [].} =
@ -288,8 +301,8 @@ proc constructObject*(s: var YamlStream, c: ConstructionContext,
## constructs a char value from a YAML scalar ## constructs a char value from a YAML scalar
constructScalarItem(s, item, char): constructScalarItem(s, item, char):
if item.scalarContent.len != 1: if item.scalarContent.len != 1:
raise newException(YamlConstructionError, raise s.constructionError("Cannot construct to char (length != 1): " &
"Cannot construct to char (length != 1): " & item.scalarContent) escape(item.scalarContent))
else: result = item.scalarContent[0] else: result = item.scalarContent[0]
proc representObject*(value: char, ts: TagStyle, c: SerializationContext, proc representObject*(value: char, ts: TagStyle, c: SerializationContext,
@ -311,7 +324,7 @@ proc constructObject*[T](s: var YamlStream, c: ConstructionContext,
## constructs a Nim seq from a YAML sequence ## constructs a Nim seq from a YAML sequence
let event = s.next() let event = s.next()
if event.kind != yamlStartSeq: if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start") raise s.constructionError("Expected sequence start")
result = newSeq[T]() result = newSeq[T]()
while s.peek().kind != yamlEndSeq: while s.peek().kind != yamlEndSeq:
var item: T var item: T
@ -325,7 +338,7 @@ proc constructObject*[T](s: var YamlStream, c: ConstructionContext,
## constructs a Nim seq from a YAML sequence ## constructs a Nim seq from a YAML sequence
let event = s.next() let event = s.next()
if event.kind != yamlStartSeq: if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start") raise s.constructionError("Expected sequence start")
result = {} result = {}
while s.peek().kind != yamlEndSeq: while s.peek().kind != yamlEndSeq:
var item: T var item: T
@ -354,15 +367,15 @@ proc constructObject*[I, T](s: var YamlStream, c: ConstructionContext,
## constructs a Nim array from a YAML sequence ## constructs a Nim array from a YAML sequence
var event = s.next() var event = s.next()
if event.kind != yamlStartSeq: if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected sequence start") raise s.constructionError("Expected sequence start")
for index in low(I)..high(I): for index in low(I)..high(I):
event = s.peek() event = s.peek()
if event.kind == yamlEndSeq: if event.kind == yamlEndSeq:
raise newException(YamlConstructionError, "Too few array values") raise s.constructionError("Too few array values")
constructChild(s, c, result[index]) constructChild(s, c, result[index])
event = s.next() event = s.next()
if event.kind != yamlEndSeq: if event.kind != yamlEndSeq:
raise newException(YamlConstructionError, "Too much array values") raise s.constructionError("Too many array values")
proc representObject*[I, T](value: array[I, T], ts: TagStyle, proc representObject*[I, T](value: array[I, T], ts: TagStyle,
c: SerializationContext, tag: TagId) = c: SerializationContext, tag: TagId) =
@ -388,8 +401,7 @@ proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
## constructs a Nim Table from a YAML mapping ## constructs a Nim Table from a YAML mapping
let event = s.next() let event = s.next()
if event.kind != yamlStartMap: if event.kind != yamlStartMap:
raise newException(YamlConstructionError, "Expected map start, got " & raise s.constructionError("Expected map start, got " & $event.kind)
$event.kind)
result = initTable[K, V]() result = initTable[K, V]()
while s.peek.kind != yamlEndMap: while s.peek.kind != yamlEndMap:
var var
@ -398,7 +410,7 @@ proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
constructChild(s, c, key) constructChild(s, c, key)
constructChild(s, c, value) constructChild(s, c, value)
if result.contains(key): if result.contains(key):
raise newException(YamlConstructionError, "Duplicate table key!") raise s.constructionError("Duplicate table key!")
result[key] = value result[key] = value
discard s.next() discard s.next()
@ -426,25 +438,24 @@ proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
result: var OrderedTable[K, V]) result: var OrderedTable[K, V])
{.raises: [YamlConstructionError, YamlStreamError].} = {.raises: [YamlConstructionError, YamlStreamError].} =
## constructs a Nim OrderedTable from a YAML mapping ## constructs a Nim OrderedTable from a YAML mapping
let event = s.next() var event = s.next()
if event.kind != yamlStartSeq: if event.kind != yamlStartSeq:
raise newException(YamlConstructionError, "Expected seq start, got " & raise s.constructionError("Expected seq start, got " & $event.kind)
$event.kind)
result = initOrderedTable[K, V]() result = initOrderedTable[K, V]()
while s.peek.kind != yamlEndSeq: while s.peek.kind != yamlEndSeq:
var var
key: K key: K
value: V value: V
if s.next().kind != yamlStartMap: event = s.next()
raise newException(YamlConstructionError, if event.kind != yamlStartMap:
"Expected map start, got " & $event.kind) raise s.constructionError("Expected map start, got " & $event.kind)
constructChild(s, c, key) constructChild(s, c, key)
constructChild(s, c, value) constructChild(s, c, value)
if s.next().kind != yamlEndMap: event = s.next()
raise newException(YamlConstructionError, if event.kind != yamlEndMap:
"Expected map end, got " & $event.kind) raise s.constructionError("Expected map end, got " & $event.kind)
if result.contains(key): if result.contains(key):
raise newException(YamlConstructionError, "Duplicate table key!") raise s.constructionError("Duplicate table key!")
result.add(key, value) result.add(key, value)
discard s.next() discard s.next()
@ -499,41 +510,42 @@ macro matchMatrix(t: typedesc): untyped =
for i in 0..<numFields: for i in 0..<numFields:
result.add(newLit(false)) result.add(newLit(false))
proc checkDuplicate(t: typedesc, name: string, i: int, matched: NimNode): proc checkDuplicate(s: NimNode, t: typedesc, name: string, i: int,
NimNode {.compileTime.} = matched: NimNode): NimNode {.compileTime.} =
result = newIfStmt((newNimNode(nnkBracketExpr).add(matched, newLit(i)), result = newIfStmt((newNimNode(nnkBracketExpr).add(matched, newLit(i)),
newNimNode(nnkRaiseStmt).add(newCall("newException", newIdentNode( newNimNode(nnkRaiseStmt).add(newCall(bindSym("constructionError"), s,
"YamlConstructionError"), newLit("While constructing " & newLit("While constructing " & typetraits.name(t) &
typetraits.name(t) & ": Duplicate field: " & escape(name)))))) ": Duplicate field: " & escape(name))))))
proc checkMissing(t: typedesc, name: string, i: int, matched: NimNode): proc checkMissing(s: NimNode, t: typedesc, name: string, i: int,
NimNode {.compileTime.} = matched: NimNode): NimNode {.compileTime.} =
result = newIfStmt((newCall("not", newNimNode(nnkBracketExpr).add(matched, result = newIfStmt((newCall("not", newNimNode(nnkBracketExpr).add(matched,
newLit(i))), newNimNode(nnkRaiseStmt).add(newCall("newException", newLit(i))), newNimNode(nnkRaiseStmt).add(newCall(
newIdentNode("YamlConstructionError"), newLit("While constructing " & bindSym("constructionError"), s, newLit("While constructing " &
typetraits.name(t) & ": Missing field: " & escape(name)))))) typetraits.name(t) & ": Missing field: " & escape(name))))))
proc markAsFound(i: int, matched: NimNode): NimNode {.compileTime.} = proc markAsFound(i: int, matched: NimNode): NimNode {.compileTime.} =
newAssignment(newNimNode(nnkBracketExpr).add(matched, newLit(i)), newAssignment(newNimNode(nnkBracketExpr).add(matched, newLit(i)),
newLit(true)) newLit(true))
macro ensureAllFieldsPresent(t: typedesc, o: typed, matched: typed): typed = macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, o: typed,
matched: typed): typed =
result = newStmtList() result = newStmtList()
let let
tDesc = getType(getType(t)[1]) tDesc = getType(getType(t)[1])
var field = 0 var field = 0
for child in tDesc[2].children: for child in tDesc[2].children:
if child.kind == nnkRecCase: if child.kind == nnkRecCase:
result.add(checkMissing(t, $child[0], field, matched)) result.add(checkMissing(s, t, $child[0], field, matched))
for bIndex in 1 .. len(child) - 1: for bIndex in 1 .. len(child) - 1:
let discChecks = newStmtList() let discChecks = newStmtList()
for item in child[bIndex][1].children: for item in child[bIndex][1].children:
inc(field) inc(field)
discChecks.add(checkMissing(t, $item, field, matched)) discChecks.add(checkMissing(s, t, $item, field, matched))
result.add(newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])), result.add(newIfStmt((infix(newDotExpr(o, newIdentNode($child[0])),
"==", child[bIndex][0]), discChecks))) "==", child[bIndex][0]), discChecks)))
else: else:
result.add(checkMissing(t, $child, field, matched)) result.add(checkMissing(s, t, $child, field, matched))
inc(field) inc(field)
macro constructFieldValue(t: typedesc, stream: untyped, context: untyped, macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
@ -548,7 +560,7 @@ macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
discType = newCall("type", discriminant) discType = newCall("type", discriminant)
var disOb = newNimNode(nnkOfBranch).add(newStrLitNode($child[0])) var disOb = newNimNode(nnkOfBranch).add(newStrLitNode($child[0]))
disOb.add(newStmtList( disOb.add(newStmtList(
checkDuplicate(t, $child[0], fieldIndex, matched), checkDuplicate(stream, t, $child[0], fieldIndex, matched),
newNimNode(nnkVarSection).add( newNimNode(nnkVarSection).add(
newNimNode(nnkIdentDefs).add( newNimNode(nnkIdentDefs).add(
newIdentNode("value"), discType, newEmptyNode())), newIdentNode("value"), discType, newEmptyNode())),
@ -566,24 +578,24 @@ macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
var ifStmt = newIfStmt((cond: discTest, body: newStmtList( var ifStmt = newIfStmt((cond: discTest, body: newStmtList(
newCall("constructChild", stream, context, field)))) newCall("constructChild", stream, context, field))))
ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add( ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add(
newCall("newException", newIdentNode("YamlConstructionError"), newCall(bindSym("constructionError"), stream,
infix(newStrLitNode("Field " & $item & " not allowed for " & infix(newStrLitNode("Field " & $item & " not allowed for " &
$child[0] & " == "), "&", prefix(discriminant, "$")))))) $child[0] & " == "), "&", prefix(discriminant, "$"))))))
ob.add(newStmtList(checkDuplicate(t, $item, fieldIndex, matched), ob.add(newStmtList(checkDuplicate(stream, t, $item, fieldIndex,
ifStmt, markAsFound(fieldIndex, matched))) matched), ifStmt, markAsFound(fieldIndex, matched)))
result.add(ob) result.add(ob)
else: else:
yAssert child.kind == nnkSym yAssert child.kind == nnkSym
var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child)) var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child))
let field = newDotExpr(o, newIdentNode($child)) let field = newDotExpr(o, newIdentNode($child))
ob.add(newStmtList( ob.add(newStmtList(
checkDuplicate(t, $child, fieldIndex, matched), checkDuplicate(stream, t, $child, fieldIndex, matched),
newCall("constructChild", stream, context, field), newCall("constructChild", stream, context, field),
markAsFound(fieldIndex, matched))) markAsFound(fieldIndex, matched)))
result.add(ob) result.add(ob)
inc(fieldIndex) inc(fieldIndex)
result.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add( result.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add(
newCall("newException", newIdentNode("YamlConstructionError"), newCall(bindSym("constructionError"), stream,
infix(newLit("While constructing " & typetraits.name(t) & infix(newLit("While constructing " & typetraits.name(t) &
": Unknown field: "), "&", name))))) ": Unknown field: "), "&", name)))))
@ -604,8 +616,8 @@ proc constructObject*[O: object|tuple](
startKind = when isVariantObject(O): yamlStartSeq else: yamlStartMap startKind = when isVariantObject(O): yamlStartSeq else: yamlStartMap
endKind = when isVariantObject(O): yamlEndSeq else: yamlEndMap endKind = when isVariantObject(O): yamlEndSeq else: yamlEndMap
if e.kind != startKind: if e.kind != startKind:
raise newException(YamlConstructionError, "While constructing " & raise s.constructionError("While constructing " &
typetraits.name(O) & ": Expected map start, got " & $e.kind) typetraits.name(O) & ": Expected " & $startKind & ", got " & $e.kind)
when isVariantObject(O): reset(result) # make discriminants writeable when isVariantObject(O): reset(result) # make discriminants writeable
while s.peek.kind != endKind: while s.peek.kind != endKind:
# todo: check for duplicates in input and raise appropriate exception # todo: check for duplicates in input and raise appropriate exception
@ -613,12 +625,10 @@ proc constructObject*[O: object|tuple](
e = s.next() e = s.next()
when isVariantObject(O): when isVariantObject(O):
if e.kind != yamlStartMap: if e.kind != yamlStartMap:
raise newException(YamlConstructionError, raise s.constructionError("Expected single-pair map, got " & $e.kind)
"Expected single-pair map, got " & $e.kind)
e = s.next() e = s.next()
if e.kind != yamlScalar: if e.kind != yamlScalar:
raise newException(YamlConstructionError, raise s.constructionError("Expected field name, got " & $e.kind)
"Expected field name, got " & $e.kind)
let name = e.scalarContent let name = e.scalarContent
when result is tuple: when result is tuple:
var i = 0 var i = 0
@ -626,7 +636,7 @@ proc constructObject*[O: object|tuple](
for fname, value in fieldPairs(result): for fname, value in fieldPairs(result):
if fname == name: if fname == name:
if matched[i]: if matched[i]:
raise newException(YamlConstructionError, "While constructing " & raise s.constructionError("While constructing " &
typetraits.name(O) & ": Duplicate field: " & escape(name)) typetraits.name(O) & ": Duplicate field: " & escape(name))
constructChild(s, c, value) constructChild(s, c, value)
matched[i] = true matched[i] = true
@ -634,24 +644,24 @@ proc constructObject*[O: object|tuple](
break break
inc(i) inc(i)
if not found: if not found:
raise newException(YamlConstructionError, "While constructing " & raise s.constructionError("While constructing " &
typetraits.name(O) & ": Unknown field: " & escape(name)) typetraits.name(O) & ": Unknown field: " & escape(name))
else: else:
constructFieldValue(O, s, c, name, result, matched) constructFieldValue(O, s, c, name, result, matched)
when isVariantObject(O): when isVariantObject(O):
e = s.next() e = s.next()
if e.kind != yamlEndMap: if e.kind != yamlEndMap:
raise newException(YamlConstructionError, raise s.constructionError("Expected end of single-pair map, got " &
"Expected end of single-pair map, got " & $e.kind) $e.kind)
discard s.next() discard s.next()
when result is tuple: when result is tuple:
var i = 0 var i = 0
for fname, value in fieldPairs(result): for fname, value in fieldPairs(result):
if not matched[i]: if not matched[i]:
raise newException(YamlConstructionError, "While constructing " & raise s.constructionError("While constructing " &
typetraits.name(O) & ": Field missing: " & escape(fname)) typetraits.name(O) & ": Field missing: " & escape(fname))
inc(i) inc(i)
else: ensureAllFieldsPresent(O, result, matched) else: ensureAllFieldsPresent(s, O, result, matched)
proc representObject*[O: object|tuple](value: O, ts: TagStyle, proc representObject*[O: object|tuple](value: O, ts: TagStyle,
c: SerializationContext, tag: TagId) = c: SerializationContext, tag: TagId) =
@ -674,12 +684,11 @@ proc constructObject*[O: enum](s: var YamlStream, c: ConstructionContext,
## constructs a Nim enum from a YAML scalar ## constructs a Nim enum from a YAML scalar
let e = s.next() let e = s.next()
if e.kind != yamlScalar: if e.kind != yamlScalar:
raise newException(YamlConstructionError, "Expected scalar, got " & raise s.constructionError("Expected scalar, got " & $e.kind)
$e.kind)
try: result = parseEnum[O](e.scalarContent) try: result = parseEnum[O](e.scalarContent)
except ValueError: except ValueError:
var ex = newException(YamlConstructionError, "Cannot parse '" & var ex = s.constructionError("Cannot parse '" &
e.scalarContent & "' as " & type(O).name) escape(e.scalarContent) & "' as " & type(O).name)
ex.parent = getCurrentException() ex.parent = getCurrentException()
raise ex raise ex
@ -715,7 +724,7 @@ macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
branch.add(branchContent) branch.add(branchContent)
ifStmt.add(branch) ifStmt.add(branch)
let raiseStmt = newNimNode(nnkRaiseStmt).add( let raiseStmt = newNimNode(nnkRaiseStmt).add(
newCall("newException", newIdentNode("YamlConstructionError"), newCall(bindSym("constructionError"), s,
infix(newStrLitNode("This value type does not map to any field in " & infix(newStrLitNode("This value type does not map to any field in " &
typetraits.name(t) & ": "), "&", typetraits.name(t) & ": "), "&",
newCall("uri", newIdentNode("serializationTagLibrary"), newCall("uri", newIdentNode("serializationTagLibrary"),
@ -751,7 +760,7 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
of yTypeBoolTrue, yTypeBoolFalse: of yTypeBoolTrue, yTypeBoolFalse:
possibleTagIds.add(yamlTag(bool)) possibleTagIds.add(yamlTag(bool))
of yTypeNull: of yTypeNull:
raise newException(YamlConstructionError, "not implemented!") raise s.constructionError("not implemented!")
of yTypeUnknown: of yTypeUnknown:
possibleTagIds.add(yamlTag(string)) possibleTagIds.add(yamlTag(string))
of yTagExclamationMark: of yTagExclamationMark:
@ -760,12 +769,12 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
possibleTagIds.add(item.scalarTag) possibleTagIds.add(item.scalarTag)
of yamlStartMap: of yamlStartMap:
if item.mapTag in [yTagQuestionMark, yTagExclamationMark]: if item.mapTag in [yTagQuestionMark, yTagExclamationMark]:
raise newException(YamlConstructionError, raise s.constructionError(
"Complex value of implicit variant object type must have a tag.") "Complex value of implicit variant object type must have a tag.")
possibleTagIds.add(item.mapTag) possibleTagIds.add(item.mapTag)
of yamlStartSeq: of yamlStartSeq:
if item.seqTag in [yTagQuestionMark, yTagExclamationMark]: if item.seqTag in [yTagQuestionMark, yTagExclamationMark]:
raise newException(YamlConstructionError, raise s.constructionError(
"Complex value of implicit variant object type must have a tag.") "Complex value of implicit variant object type must have a tag.")
possibleTagIds.add(item.seqTag) possibleTagIds.add(item.seqTag)
else: internalError("Unexpected item kind: " & $item.kind) else: internalError("Unexpected item kind: " & $item.kind)
@ -775,22 +784,19 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
of yamlScalar: of yamlScalar:
if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark, if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark,
yamlTag(T)]: yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " & raise s.constructionError("Wrong tag for " & typetraits.name(T))
typetraits.name(T))
elif item.scalarAnchor != yAnchorNone: elif item.scalarAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
of yamlStartMap: of yamlStartMap:
if item.mapTag notin [yTagQuestionMark, yamlTag(T)]: if item.mapTag notin [yTagQuestionMark, yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " & raise s.constructionError("Wrong tag for " & typetraits.name(T))
typetraits.name(T))
elif item.mapAnchor != yAnchorNone: elif item.mapAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
of yamlStartSeq: of yamlStartSeq:
if item.seqTag notin [yTagQuestionMark, yamlTag(T)]: if item.seqTag notin [yTagQuestionMark, yamlTag(T)]:
raise newException(YamlConstructionError, "Wrong tag for " & raise s.constructionError("Wrong tag for " & typetraits.name(T))
typetraits.name(T))
elif item.seqAnchor != yAnchorNone: elif item.seqAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
else: internalError("Unexpected item kind: " & $item.kind) else: internalError("Unexpected item kind: " & $item.kind)
constructObject(s, c, result) constructObject(s, c, result)
@ -804,9 +810,9 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext,
return return
elif item.scalarTag notin elif item.scalarTag notin
[yTagQuestionMark, yTagExclamationMark, yamlTag(string)]: [yTagQuestionMark, yTagExclamationMark, yamlTag(string)]:
raise newException(YamlConstructionError, "Wrong tag for string") raise s.constructionError("Wrong tag for string")
elif item.scalarAnchor != yAnchorNone: elif item.scalarAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
constructObject(s, c, result) constructObject(s, c, result)
proc constructChild*[T](s: var YamlStream, c: ConstructionContext, proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
@ -819,10 +825,9 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
return return
elif item.kind == yamlStartSeq: elif item.kind == yamlStartSeq:
if item.seqTag notin [yTagQuestionMark, yamlTag(seq[T])]: if item.seqTag notin [yTagQuestionMark, yamlTag(seq[T])]:
raise newException(YamlConstructionError, "Wrong tag for " & raise s.constructionError("Wrong tag for " & typetraits.name(seq[T]))
typetraits.name(seq[T]))
elif item.seqAnchor != yAnchorNone: elif item.seqAnchor != yAnchorNone:
raise newException(YamlConstructionError, "Anchor on non-ref type") raise s.constructionError("Anchor on non-ref type")
constructObject(s, c, result) constructObject(s, c, result)
proc constructChild*[O](s: var YamlStream, c: ConstructionContext, proc constructChild*[O](s: var YamlStream, c: ConstructionContext,
@ -948,10 +953,6 @@ proc load*[K](input: Stream | string, target: var K)
parser = newYamlParser(serializationTagLibrary) parser = newYamlParser(serializationTagLibrary)
events = parser.parse(input) events = parser.parse(input)
try: construct(events, target) try: construct(events, target)
except YamlConstructionError:
var e = (ref YamlConstructionError)(getCurrentException())
discard events.getLastTokenContext(e.line, e.column, e.lineContent)
raise e
except YamlStreamError: except YamlStreamError:
let e = (ref YamlStreamError)(getCurrentException()) let e = (ref YamlStreamError)(getCurrentException())
if e.parent of IOError: raise (ref IOError)(e.parent) if e.parent of IOError: raise (ref IOError)(e.parent)