mirror of https://github.com/status-im/NimYAML.git
Improved and fixed issues with error reporting
This commit is contained in:
parent
a8d68d1696
commit
2ad41d349e
|
@ -44,7 +44,8 @@ type
|
|||
folded: bool
|
||||
chomp: ChompType
|
||||
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
|
||||
|
||||
YamlLexer* = ref YamlLexerObj
|
||||
|
@ -190,26 +191,26 @@ proc afterMark(lex: YamlLexer, t: typedesc[StringSource], m: int):
|
|||
int {.inline.} =
|
||||
lex.sSource.pos - m
|
||||
|
||||
proc lineWithMarker(lex: YamlLexer, t: typedesc[BaseLexer], marker: bool):
|
||||
string =
|
||||
if lex.curStartPos.line == lex.blSource.lineNumber:
|
||||
proc lineWithMarker(lex: YamlLexer, pos: tuple[line, column: int],
|
||||
t: typedesc[BaseLexer], marker: bool): string =
|
||||
if pos.line == lex.blSource.lineNumber:
|
||||
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 = ""
|
||||
|
||||
proc lineWithMarker(lex: YamlLexer, t: typedesc[StringSource], marker: bool):
|
||||
string =
|
||||
proc lineWithMarker(lex: YamlLexer, pos: tuple[line, column: int],
|
||||
t: typedesc[StringSource], marker: bool): string =
|
||||
var
|
||||
lineStartIndex = lex.sSource.pos
|
||||
lineEndIndex: int
|
||||
curLine = lex.sSource.line
|
||||
if lex.curStartPos.line == curLine:
|
||||
if pos.line == curLine:
|
||||
lineEndIndex = lex.sSource.pos
|
||||
while lex.sSource.src[lineEndIndex] notin lineEnd: inc(lineEndIndex)
|
||||
while true:
|
||||
while lineStartIndex >= 0 and lex.sSource.src[lineStartIndex] notin lineEnd:
|
||||
dec(lineStartIndex)
|
||||
if curLine == lex.curStartPos.line:
|
||||
if curLine == pos.line:
|
||||
inc(lineStartIndex)
|
||||
break
|
||||
let wasLF = lex.sSource.src[lineStartIndex] == '\l'
|
||||
|
@ -220,7 +221,7 @@ proc lineWithMarker(lex: YamlLexer, t: typedesc[StringSource], marker: bool):
|
|||
dec(lineEndIndex)
|
||||
dec(curLine)
|
||||
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
|
||||
|
||||
|
@ -246,7 +247,7 @@ proc docEndAfterBlockScalar[T](lex: YamlLexer): bool
|
|||
proc tagHandle[T](lex: YamlLexer): bool
|
||||
proc anchor[T](lex: YamlLexer): bool
|
||||
proc alias[T](lex: YamlLexer): bool
|
||||
proc streamEnd(lex: YamlLexer): bool
|
||||
proc streamEnd[T](lex: YamlLexer): bool
|
||||
{.pop.}
|
||||
|
||||
# implementation
|
||||
|
@ -371,8 +372,7 @@ proc expectLineEnd[T](lex: YamlLexer): bool =
|
|||
lex.advance(T)
|
||||
while lex.c notin lineEnd: lex.advance(T)
|
||||
of EndOfFile:
|
||||
startToken[T](lex)
|
||||
lex.nextState = streamEnd
|
||||
lex.nextState = streamEnd[T]
|
||||
break
|
||||
of '\l':
|
||||
lex.lexLF(T)
|
||||
|
@ -857,7 +857,7 @@ proc blockScalarLineStart[T](lex: YamlLexer, recentWasMoreIndented: var bool):
|
|||
lex.lexCR(T)
|
||||
lex.indentation = 0
|
||||
of EndOfFile:
|
||||
lex.nextState = streamEnd
|
||||
lex.nextState = streamEnd[T]
|
||||
return false
|
||||
of ' ', '\t':
|
||||
recentWasMoreIndented = true
|
||||
|
@ -886,7 +886,7 @@ proc blockScalar[T](lex: YamlLexer): bool =
|
|||
lex.lexCR(T)
|
||||
lex.newlines.inc()
|
||||
of EndOfFile:
|
||||
lex.nextState = streamEnd
|
||||
lex.nextState = streamEnd[T]
|
||||
break outer
|
||||
else:
|
||||
if lex.blockScalarIndent <= lex.indentation:
|
||||
|
@ -1032,13 +1032,15 @@ proc alias[T](lex: YamlLexer): bool =
|
|||
lex.cur = ltAlias
|
||||
result = true
|
||||
|
||||
proc streamEnd(lex: YamlLexer): bool =
|
||||
proc streamEnd[T](lex: YamlLexer): bool =
|
||||
debug("lex: streamEnd")
|
||||
startToken[T](lex)
|
||||
lex.cur = ltStreamEnd
|
||||
result = true
|
||||
|
||||
proc tokenLine[T](lex: YamlLexer, marker: bool): string =
|
||||
result = lex.lineWithMarker(T, marker)
|
||||
proc tokenLine[T](lex: YamlLexer, pos: tuple[line, column: int], marker: bool):
|
||||
string =
|
||||
result = lex.lineWithMarker(pos, T, marker)
|
||||
|
||||
proc searchColon[T](lex: YamlLexer): bool =
|
||||
var flowDepth = if lex.cur in [ltBraceOpen, ltBracketOpen]: 1 else: 0
|
||||
|
@ -1180,7 +1182,11 @@ proc endBlockScalar*(lex: YamlLexer) =
|
|||
lex.folded = true
|
||||
|
||||
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 =
|
||||
result = lex.searchColonImpl(lex)
|
||||
|
|
|
@ -69,6 +69,16 @@ template assertStringEqual(expected, actual: string) =
|
|||
echo "expected:\n", expected, "\nactual:\n", actual
|
||||
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 =
|
||||
new(result)
|
||||
result.value = v
|
||||
|
@ -305,19 +315,19 @@ suite "Serialization":
|
|||
test "Load Tuple - unknown field":
|
||||
let input = "str: value\nfoo: bar\ni: 42\nb: true"
|
||||
var result: MyTuple
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(2, 1):
|
||||
load(input, result)
|
||||
|
||||
test "Load Tuple - missing field":
|
||||
let input = "str: value\nb: true"
|
||||
var result: MyTuple
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(2, 8):
|
||||
load(input, result)
|
||||
|
||||
test "Load Tuple - duplicate field":
|
||||
let input = "str: value\ni: 42\nb: true\nb: true"
|
||||
var result: MyTuple
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(4, 1):
|
||||
load(input, result)
|
||||
|
||||
test "Load Multiple Documents":
|
||||
|
@ -350,21 +360,21 @@ suite "Serialization":
|
|||
"%YAML 1.2\n--- \nfirstnamechar: P\nsurname: Pan\nage: 12", output)
|
||||
|
||||
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
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(4, 3):
|
||||
load(input, result)
|
||||
|
||||
test "Load custom object - missing field":
|
||||
let input = "surname: Pan\nage: 12"
|
||||
let input = "surname: Pan\nage: 12\n "
|
||||
var result: Person
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(3, 3):
|
||||
load(input, result)
|
||||
|
||||
test "Load custom object - duplicate field":
|
||||
let input = "firstnamechar: P\nsurname: Pan\nage: 12\nsurname: Pan"
|
||||
var result: Person
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(4, 1):
|
||||
load(input, result)
|
||||
|
||||
test "Load sequence with explicit tags":
|
||||
|
@ -433,9 +443,9 @@ suite "Serialization":
|
|||
barkometer: 13""", output
|
||||
|
||||
test "Load custom variant object - missing field":
|
||||
let input = "{name: Bastet, kind: akCat}"
|
||||
let input = "[{name: Bastet}, {kind: akCat}]"
|
||||
var result: Animal
|
||||
expect(YamlConstructionError):
|
||||
expectConstructionError(1, 32):
|
||||
load(input, result)
|
||||
|
||||
test "Dump cyclic data structure":
|
||||
|
|
|
@ -56,6 +56,7 @@ type
|
|||
nextAnchorId: AnchorId
|
||||
newlines: int
|
||||
explicitFlowKey: bool
|
||||
plainScalarStart: tuple[line, column: int]
|
||||
|
||||
LevelEndResult = enum
|
||||
lerNothing, lerOne, lerAdditionalMapEnd
|
||||
|
@ -126,7 +127,6 @@ proc illegalToken(c: ParserContext, expected: string = ""):
|
|||
if expected.len > 0: msg.add(" (expected " & expected & ")")
|
||||
msg.add(": " & $c.lex.cur)
|
||||
result = c.generateError(msg)
|
||||
echo result.line, ", ", result.column
|
||||
|
||||
proc callCallback(c: ParserContext, msg: string) {.raises: [YamlParserError].} =
|
||||
try:
|
||||
|
@ -332,6 +332,14 @@ proc handleFlowPlainScalar(c: ParserContext) =
|
|||
c.advance()
|
||||
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 ---
|
||||
|
||||
template capitalize(s: string): string =
|
||||
|
@ -409,7 +417,7 @@ macro parserState(name: untyped, impl: untyped): typed =
|
|||
|
||||
parserStates(initial, blockLineStart, blockObjectStart, blockAfterObject,
|
||||
scalarEnd, plainScalarEnd, objectEnd, expectDocEnd, startDoc,
|
||||
afterDocument, closeMoreIndentedLevels,
|
||||
afterDocument, closeMoreIndentedLevels, afterPlainScalarYield,
|
||||
emitEmptyScalar, tagHandle, anchor, alias, flow, leaveFlowMap,
|
||||
leaveFlowSeq, flowAfterObject, leaveFlowSinglePairMap)
|
||||
|
||||
|
@ -592,7 +600,7 @@ parserState blockObjectStart:
|
|||
state = scalarEnd
|
||||
of ltScalarPart:
|
||||
result = c.handleBlockItemStart(e)
|
||||
let cachedPos = c.lex.curStartPos
|
||||
c.plainScalarStart = c.lex.curStartPos
|
||||
while true:
|
||||
c.advance()
|
||||
case c.lex.cur
|
||||
|
@ -602,7 +610,6 @@ parserState blockObjectStart:
|
|||
of ltScalarPart: discard
|
||||
of ltEmptyLine: c.lex.newlines.inc()
|
||||
else: break
|
||||
c.lex.curStartPos = cachedPos
|
||||
c.lex.newlines = 0
|
||||
state = plainScalarEnd
|
||||
stored = blockAfterObject
|
||||
|
@ -640,9 +647,19 @@ parserState scalarEnd:
|
|||
parserState plainScalarEnd:
|
||||
c.currentScalar(e)
|
||||
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
|
||||
|
||||
parserState afterPlainScalarYield:
|
||||
c.lastTokenContextImpl = lastTokenContext
|
||||
state = objectEnd
|
||||
|
||||
parserState blockAfterObject:
|
||||
case c.lex.cur
|
||||
of ltIndentation, ltEmptyLine:
|
||||
|
@ -1002,14 +1019,6 @@ parserState flowAfterObject:
|
|||
else:
|
||||
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 ---
|
||||
|
||||
proc init(c: ParserContext, p: YamlParser) =
|
||||
|
|
|
@ -106,6 +106,12 @@ proc safeTagUri(id: TagId): string {.raises: [].} =
|
|||
else: return uri
|
||||
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,
|
||||
t: typedesc, content: untyped) =
|
||||
## 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 content will be automatically catched and wrapped in
|
||||
## ``YamlConstructionError``, which will then be raised.
|
||||
bind constructionError
|
||||
let i = s.next()
|
||||
if i.kind != yamlScalar:
|
||||
raise newException(YamlConstructionError, "Expected scalar")
|
||||
raise constructionError(s, "Expected scalar")
|
||||
try: content
|
||||
except YamlConstructionError: raise
|
||||
except Exception:
|
||||
var e = newException(YamlConstructionError,
|
||||
var e = constructionError(s,
|
||||
"Cannot construct to " & name(t) & ": " & item.scalarContent)
|
||||
e.parent = getCurrentException()
|
||||
raise e
|
||||
|
@ -139,22 +146,28 @@ proc representObject*(value: string, ts: TagStyle,
|
|||
## represents a string as YAML scalar
|
||||
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
|
||||
for i in 2..<s.len:
|
||||
case s[i]
|
||||
for i in 2..<val.len:
|
||||
case val[i]
|
||||
of '_': discard
|
||||
of '0'..'9': result = result shl 4 or T(ord(s[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(s[i]) - ord('A') + 10)
|
||||
else: raise newException(ValueError, "Invalid character in hex: " & escape("" & s[i]))
|
||||
of '0'..'9': result = result shl 4 or T(ord(val[i]) - ord('0'))
|
||||
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(val[i]) - ord('A') + 10)
|
||||
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 =
|
||||
for i in 2..<s.len:
|
||||
case s[i]
|
||||
proc parseOctal[T: int8|int16|int32|int64|uint8|uint16|uint32|uint64](
|
||||
s: YamlStream, val: string): T =
|
||||
for i in 2..<val.len:
|
||||
case val[i]
|
||||
of '_': discard
|
||||
of '0'..'7': result = result shl 3 + T((ord(s[i]) - ord('0')))
|
||||
else: raise newException(ValueError, "Invalid character in hex: " & escape("" & s[i]))
|
||||
of '0'..'7': result = result shl 3 + T((ord(val[i]) - ord('0')))
|
||||
else:
|
||||
raise s.constructionError("Invalid character in hex: " &
|
||||
escape("" & val[i]))
|
||||
|
||||
proc constructObject*[T: int8|int16|int32|int64](
|
||||
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
|
||||
constructScalarItem(s, item, T):
|
||||
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'}:
|
||||
result = parseOctal[T](item.scalarContent)
|
||||
result = parseOctal[T](s, item.scalarContent)
|
||||
else:
|
||||
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
|
||||
constructScalarItem(s, item, T):
|
||||
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'}:
|
||||
result = parseOctal[T](item.scalarContent)
|
||||
result = parseOctal[T](s, item.scalarContent)
|
||||
else: result = T(parseBiggestUInt(item.scalarContent))
|
||||
|
||||
proc constructObject*(s: var YamlStream, c: ConstructionContext,
|
||||
|
@ -251,8 +264,8 @@ proc constructObject*[T: float|float32|float64](
|
|||
else: result = Inf
|
||||
of yTypeFloatNaN: result = NaN
|
||||
else:
|
||||
raise newException(YamlConstructionError,
|
||||
"Cannot construct to float: " & item.scalarContent)
|
||||
raise s.constructionError("Cannot construct to float: " &
|
||||
escape(item.scalarContent))
|
||||
|
||||
proc representObject*[T: float|float32|float64](value: T, ts: TagStyle,
|
||||
c: SerializationContext, tag: TagId) {.raises: [].} =
|
||||
|
@ -274,8 +287,8 @@ proc constructObject*(s: var YamlStream, c: ConstructionContext,
|
|||
of yTypeBoolTrue: result = true
|
||||
of yTypeBoolFalse: result = false
|
||||
else:
|
||||
raise newException(YamlConstructionError,
|
||||
"Cannot construct to bool: " & item.scalarContent)
|
||||
raise s.constructionError("Cannot construct to bool: " &
|
||||
escape(item.scalarContent))
|
||||
|
||||
proc representObject*(value: bool, ts: TagStyle, c: SerializationContext,
|
||||
tag: TagId) {.raises: [].} =
|
||||
|
@ -288,8 +301,8 @@ proc constructObject*(s: var YamlStream, c: ConstructionContext,
|
|||
## constructs a char value from a YAML scalar
|
||||
constructScalarItem(s, item, char):
|
||||
if item.scalarContent.len != 1:
|
||||
raise newException(YamlConstructionError,
|
||||
"Cannot construct to char (length != 1): " & item.scalarContent)
|
||||
raise s.constructionError("Cannot construct to char (length != 1): " &
|
||||
escape(item.scalarContent))
|
||||
else: result = item.scalarContent[0]
|
||||
|
||||
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
|
||||
let event = s.next()
|
||||
if event.kind != yamlStartSeq:
|
||||
raise newException(YamlConstructionError, "Expected sequence start")
|
||||
raise s.constructionError("Expected sequence start")
|
||||
result = newSeq[T]()
|
||||
while s.peek().kind != yamlEndSeq:
|
||||
var item: T
|
||||
|
@ -325,7 +338,7 @@ proc constructObject*[T](s: var YamlStream, c: ConstructionContext,
|
|||
## constructs a Nim seq from a YAML sequence
|
||||
let event = s.next()
|
||||
if event.kind != yamlStartSeq:
|
||||
raise newException(YamlConstructionError, "Expected sequence start")
|
||||
raise s.constructionError("Expected sequence start")
|
||||
result = {}
|
||||
while s.peek().kind != yamlEndSeq:
|
||||
var item: T
|
||||
|
@ -354,15 +367,15 @@ proc constructObject*[I, T](s: var YamlStream, c: ConstructionContext,
|
|||
## constructs a Nim array from a YAML sequence
|
||||
var event = s.next()
|
||||
if event.kind != yamlStartSeq:
|
||||
raise newException(YamlConstructionError, "Expected sequence start")
|
||||
raise s.constructionError("Expected sequence start")
|
||||
for index in low(I)..high(I):
|
||||
event = s.peek()
|
||||
if event.kind == yamlEndSeq:
|
||||
raise newException(YamlConstructionError, "Too few array values")
|
||||
raise s.constructionError("Too few array values")
|
||||
constructChild(s, c, result[index])
|
||||
event = s.next()
|
||||
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,
|
||||
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
|
||||
let event = s.next()
|
||||
if event.kind != yamlStartMap:
|
||||
raise newException(YamlConstructionError, "Expected map start, got " &
|
||||
$event.kind)
|
||||
raise s.constructionError("Expected map start, got " & $event.kind)
|
||||
result = initTable[K, V]()
|
||||
while s.peek.kind != yamlEndMap:
|
||||
var
|
||||
|
@ -398,7 +410,7 @@ proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
|
|||
constructChild(s, c, key)
|
||||
constructChild(s, c, value)
|
||||
if result.contains(key):
|
||||
raise newException(YamlConstructionError, "Duplicate table key!")
|
||||
raise s.constructionError("Duplicate table key!")
|
||||
result[key] = value
|
||||
discard s.next()
|
||||
|
||||
|
@ -426,25 +438,24 @@ proc constructObject*[K, V](s: var YamlStream, c: ConstructionContext,
|
|||
result: var OrderedTable[K, V])
|
||||
{.raises: [YamlConstructionError, YamlStreamError].} =
|
||||
## constructs a Nim OrderedTable from a YAML mapping
|
||||
let event = s.next()
|
||||
var event = s.next()
|
||||
if event.kind != yamlStartSeq:
|
||||
raise newException(YamlConstructionError, "Expected seq start, got " &
|
||||
$event.kind)
|
||||
raise s.constructionError("Expected seq start, got " & $event.kind)
|
||||
result = initOrderedTable[K, V]()
|
||||
while s.peek.kind != yamlEndSeq:
|
||||
var
|
||||
key: K
|
||||
value: V
|
||||
if s.next().kind != yamlStartMap:
|
||||
raise newException(YamlConstructionError,
|
||||
"Expected map start, got " & $event.kind)
|
||||
event = s.next()
|
||||
if event.kind != yamlStartMap:
|
||||
raise s.constructionError("Expected map start, got " & $event.kind)
|
||||
constructChild(s, c, key)
|
||||
constructChild(s, c, value)
|
||||
if s.next().kind != yamlEndMap:
|
||||
raise newException(YamlConstructionError,
|
||||
"Expected map end, got " & $event.kind)
|
||||
event = s.next()
|
||||
if event.kind != yamlEndMap:
|
||||
raise s.constructionError("Expected map end, got " & $event.kind)
|
||||
if result.contains(key):
|
||||
raise newException(YamlConstructionError, "Duplicate table key!")
|
||||
raise s.constructionError("Duplicate table key!")
|
||||
result.add(key, value)
|
||||
discard s.next()
|
||||
|
||||
|
@ -499,41 +510,42 @@ macro matchMatrix(t: typedesc): untyped =
|
|||
for i in 0..<numFields:
|
||||
result.add(newLit(false))
|
||||
|
||||
proc checkDuplicate(t: typedesc, name: string, i: int, matched: NimNode):
|
||||
NimNode {.compileTime.} =
|
||||
proc checkDuplicate(s: NimNode, t: typedesc, name: string, i: int,
|
||||
matched: NimNode): NimNode {.compileTime.} =
|
||||
result = newIfStmt((newNimNode(nnkBracketExpr).add(matched, newLit(i)),
|
||||
newNimNode(nnkRaiseStmt).add(newCall("newException", newIdentNode(
|
||||
"YamlConstructionError"), newLit("While constructing " &
|
||||
typetraits.name(t) & ": Duplicate field: " & escape(name))))))
|
||||
newNimNode(nnkRaiseStmt).add(newCall(bindSym("constructionError"), s,
|
||||
newLit("While constructing " & typetraits.name(t) &
|
||||
": Duplicate field: " & escape(name))))))
|
||||
|
||||
proc checkMissing(t: typedesc, name: string, i: int, matched: NimNode):
|
||||
NimNode {.compileTime.} =
|
||||
proc checkMissing(s: NimNode, t: typedesc, name: string, i: int,
|
||||
matched: NimNode): NimNode {.compileTime.} =
|
||||
result = newIfStmt((newCall("not", newNimNode(nnkBracketExpr).add(matched,
|
||||
newLit(i))), newNimNode(nnkRaiseStmt).add(newCall("newException",
|
||||
newIdentNode("YamlConstructionError"), newLit("While constructing " &
|
||||
newLit(i))), newNimNode(nnkRaiseStmt).add(newCall(
|
||||
bindSym("constructionError"), s, newLit("While constructing " &
|
||||
typetraits.name(t) & ": Missing field: " & escape(name))))))
|
||||
|
||||
proc markAsFound(i: int, matched: NimNode): NimNode {.compileTime.} =
|
||||
newAssignment(newNimNode(nnkBracketExpr).add(matched, newLit(i)),
|
||||
newLit(true))
|
||||
|
||||
macro ensureAllFieldsPresent(t: typedesc, o: typed, matched: typed): typed =
|
||||
macro ensureAllFieldsPresent(s: YamlStream, t: typedesc, o: typed,
|
||||
matched: typed): typed =
|
||||
result = newStmtList()
|
||||
let
|
||||
tDesc = getType(getType(t)[1])
|
||||
var field = 0
|
||||
for child in tDesc[2].children:
|
||||
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:
|
||||
let discChecks = newStmtList()
|
||||
for item in child[bIndex][1].children:
|
||||
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])),
|
||||
"==", child[bIndex][0]), discChecks)))
|
||||
else:
|
||||
result.add(checkMissing(t, $child, field, matched))
|
||||
result.add(checkMissing(s, t, $child, field, matched))
|
||||
inc(field)
|
||||
|
||||
macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
|
||||
|
@ -548,7 +560,7 @@ macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
|
|||
discType = newCall("type", discriminant)
|
||||
var disOb = newNimNode(nnkOfBranch).add(newStrLitNode($child[0]))
|
||||
disOb.add(newStmtList(
|
||||
checkDuplicate(t, $child[0], fieldIndex, matched),
|
||||
checkDuplicate(stream, t, $child[0], fieldIndex, matched),
|
||||
newNimNode(nnkVarSection).add(
|
||||
newNimNode(nnkIdentDefs).add(
|
||||
newIdentNode("value"), discType, newEmptyNode())),
|
||||
|
@ -566,24 +578,24 @@ macro constructFieldValue(t: typedesc, stream: untyped, context: untyped,
|
|||
var ifStmt = newIfStmt((cond: discTest, body: newStmtList(
|
||||
newCall("constructChild", stream, context, field))))
|
||||
ifStmt.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add(
|
||||
newCall("newException", newIdentNode("YamlConstructionError"),
|
||||
newCall(bindSym("constructionError"), stream,
|
||||
infix(newStrLitNode("Field " & $item & " not allowed for " &
|
||||
$child[0] & " == "), "&", prefix(discriminant, "$"))))))
|
||||
ob.add(newStmtList(checkDuplicate(t, $item, fieldIndex, matched),
|
||||
ifStmt, markAsFound(fieldIndex, matched)))
|
||||
ob.add(newStmtList(checkDuplicate(stream, t, $item, fieldIndex,
|
||||
matched), ifStmt, markAsFound(fieldIndex, matched)))
|
||||
result.add(ob)
|
||||
else:
|
||||
yAssert child.kind == nnkSym
|
||||
var ob = newNimNode(nnkOfBranch).add(newStrLitNode($child))
|
||||
let field = newDotExpr(o, newIdentNode($child))
|
||||
ob.add(newStmtList(
|
||||
checkDuplicate(t, $child, fieldIndex, matched),
|
||||
checkDuplicate(stream, t, $child, fieldIndex, matched),
|
||||
newCall("constructChild", stream, context, field),
|
||||
markAsFound(fieldIndex, matched)))
|
||||
result.add(ob)
|
||||
inc(fieldIndex)
|
||||
result.add(newNimNode(nnkElse).add(newNimNode(nnkRaiseStmt).add(
|
||||
newCall("newException", newIdentNode("YamlConstructionError"),
|
||||
newCall(bindSym("constructionError"), stream,
|
||||
infix(newLit("While constructing " & typetraits.name(t) &
|
||||
": Unknown field: "), "&", name)))))
|
||||
|
||||
|
@ -604,8 +616,8 @@ proc constructObject*[O: object|tuple](
|
|||
startKind = when isVariantObject(O): yamlStartSeq else: yamlStartMap
|
||||
endKind = when isVariantObject(O): yamlEndSeq else: yamlEndMap
|
||||
if e.kind != startKind:
|
||||
raise newException(YamlConstructionError, "While constructing " &
|
||||
typetraits.name(O) & ": Expected map start, got " & $e.kind)
|
||||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Expected " & $startKind & ", got " & $e.kind)
|
||||
when isVariantObject(O): reset(result) # make discriminants writeable
|
||||
while s.peek.kind != endKind:
|
||||
# todo: check for duplicates in input and raise appropriate exception
|
||||
|
@ -613,12 +625,10 @@ proc constructObject*[O: object|tuple](
|
|||
e = s.next()
|
||||
when isVariantObject(O):
|
||||
if e.kind != yamlStartMap:
|
||||
raise newException(YamlConstructionError,
|
||||
"Expected single-pair map, got " & $e.kind)
|
||||
raise s.constructionError("Expected single-pair map, got " & $e.kind)
|
||||
e = s.next()
|
||||
if e.kind != yamlScalar:
|
||||
raise newException(YamlConstructionError,
|
||||
"Expected field name, got " & $e.kind)
|
||||
raise s.constructionError("Expected field name, got " & $e.kind)
|
||||
let name = e.scalarContent
|
||||
when result is tuple:
|
||||
var i = 0
|
||||
|
@ -626,7 +636,7 @@ proc constructObject*[O: object|tuple](
|
|||
for fname, value in fieldPairs(result):
|
||||
if fname == name:
|
||||
if matched[i]:
|
||||
raise newException(YamlConstructionError, "While constructing " &
|
||||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Duplicate field: " & escape(name))
|
||||
constructChild(s, c, value)
|
||||
matched[i] = true
|
||||
|
@ -634,24 +644,24 @@ proc constructObject*[O: object|tuple](
|
|||
break
|
||||
inc(i)
|
||||
if not found:
|
||||
raise newException(YamlConstructionError, "While constructing " &
|
||||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Unknown field: " & escape(name))
|
||||
else:
|
||||
constructFieldValue(O, s, c, name, result, matched)
|
||||
when isVariantObject(O):
|
||||
e = s.next()
|
||||
if e.kind != yamlEndMap:
|
||||
raise newException(YamlConstructionError,
|
||||
"Expected end of single-pair map, got " & $e.kind)
|
||||
raise s.constructionError("Expected end of single-pair map, got " &
|
||||
$e.kind)
|
||||
discard s.next()
|
||||
when result is tuple:
|
||||
var i = 0
|
||||
for fname, value in fieldPairs(result):
|
||||
if not matched[i]:
|
||||
raise newException(YamlConstructionError, "While constructing " &
|
||||
raise s.constructionError("While constructing " &
|
||||
typetraits.name(O) & ": Field missing: " & escape(fname))
|
||||
inc(i)
|
||||
else: ensureAllFieldsPresent(O, result, matched)
|
||||
else: ensureAllFieldsPresent(s, O, result, matched)
|
||||
|
||||
proc representObject*[O: object|tuple](value: O, ts: TagStyle,
|
||||
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
|
||||
let e = s.next()
|
||||
if e.kind != yamlScalar:
|
||||
raise newException(YamlConstructionError, "Expected scalar, got " &
|
||||
$e.kind)
|
||||
raise s.constructionError("Expected scalar, got " & $e.kind)
|
||||
try: result = parseEnum[O](e.scalarContent)
|
||||
except ValueError:
|
||||
var ex = newException(YamlConstructionError, "Cannot parse '" &
|
||||
e.scalarContent & "' as " & type(O).name)
|
||||
var ex = s.constructionError("Cannot parse '" &
|
||||
escape(e.scalarContent) & "' as " & type(O).name)
|
||||
ex.parent = getCurrentException()
|
||||
raise ex
|
||||
|
||||
|
@ -715,7 +724,7 @@ macro constructImplicitVariantObject(s, c, r, possibleTagIds: untyped,
|
|||
branch.add(branchContent)
|
||||
ifStmt.add(branch)
|
||||
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 " &
|
||||
typetraits.name(t) & ": "), "&",
|
||||
newCall("uri", newIdentNode("serializationTagLibrary"),
|
||||
|
@ -751,7 +760,7 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
|||
of yTypeBoolTrue, yTypeBoolFalse:
|
||||
possibleTagIds.add(yamlTag(bool))
|
||||
of yTypeNull:
|
||||
raise newException(YamlConstructionError, "not implemented!")
|
||||
raise s.constructionError("not implemented!")
|
||||
of yTypeUnknown:
|
||||
possibleTagIds.add(yamlTag(string))
|
||||
of yTagExclamationMark:
|
||||
|
@ -760,12 +769,12 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
|||
possibleTagIds.add(item.scalarTag)
|
||||
of yamlStartMap:
|
||||
if item.mapTag in [yTagQuestionMark, yTagExclamationMark]:
|
||||
raise newException(YamlConstructionError,
|
||||
raise s.constructionError(
|
||||
"Complex value of implicit variant object type must have a tag.")
|
||||
possibleTagIds.add(item.mapTag)
|
||||
of yamlStartSeq:
|
||||
if item.seqTag in [yTagQuestionMark, yTagExclamationMark]:
|
||||
raise newException(YamlConstructionError,
|
||||
raise s.constructionError(
|
||||
"Complex value of implicit variant object type must have a tag.")
|
||||
possibleTagIds.add(item.seqTag)
|
||||
else: internalError("Unexpected item kind: " & $item.kind)
|
||||
|
@ -775,22 +784,19 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
|||
of yamlScalar:
|
||||
if item.scalarTag notin [yTagQuestionMark, yTagExclamationMark,
|
||||
yamlTag(T)]:
|
||||
raise newException(YamlConstructionError, "Wrong tag for " &
|
||||
typetraits.name(T))
|
||||
raise s.constructionError("Wrong tag for " & typetraits.name(T))
|
||||
elif item.scalarAnchor != yAnchorNone:
|
||||
raise newException(YamlConstructionError, "Anchor on non-ref type")
|
||||
raise s.constructionError("Anchor on non-ref type")
|
||||
of yamlStartMap:
|
||||
if item.mapTag notin [yTagQuestionMark, yamlTag(T)]:
|
||||
raise newException(YamlConstructionError, "Wrong tag for " &
|
||||
typetraits.name(T))
|
||||
raise s.constructionError("Wrong tag for " & typetraits.name(T))
|
||||
elif item.mapAnchor != yAnchorNone:
|
||||
raise newException(YamlConstructionError, "Anchor on non-ref type")
|
||||
raise s.constructionError("Anchor on non-ref type")
|
||||
of yamlStartSeq:
|
||||
if item.seqTag notin [yTagQuestionMark, yamlTag(T)]:
|
||||
raise newException(YamlConstructionError, "Wrong tag for " &
|
||||
typetraits.name(T))
|
||||
raise s.constructionError("Wrong tag for " & typetraits.name(T))
|
||||
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)
|
||||
constructObject(s, c, result)
|
||||
|
||||
|
@ -804,9 +810,9 @@ proc constructChild*(s: var YamlStream, c: ConstructionContext,
|
|||
return
|
||||
elif item.scalarTag notin
|
||||
[yTagQuestionMark, yTagExclamationMark, yamlTag(string)]:
|
||||
raise newException(YamlConstructionError, "Wrong tag for string")
|
||||
raise s.constructionError("Wrong tag for string")
|
||||
elif item.scalarAnchor != yAnchorNone:
|
||||
raise newException(YamlConstructionError, "Anchor on non-ref type")
|
||||
raise s.constructionError("Anchor on non-ref type")
|
||||
constructObject(s, c, result)
|
||||
|
||||
proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
||||
|
@ -819,10 +825,9 @@ proc constructChild*[T](s: var YamlStream, c: ConstructionContext,
|
|||
return
|
||||
elif item.kind == yamlStartSeq:
|
||||
if item.seqTag notin [yTagQuestionMark, yamlTag(seq[T])]:
|
||||
raise newException(YamlConstructionError, "Wrong tag for " &
|
||||
typetraits.name(seq[T]))
|
||||
raise s.constructionError("Wrong tag for " & typetraits.name(seq[T]))
|
||||
elif item.seqAnchor != yAnchorNone:
|
||||
raise newException(YamlConstructionError, "Anchor on non-ref type")
|
||||
raise s.constructionError("Anchor on non-ref type")
|
||||
constructObject(s, c, result)
|
||||
|
||||
proc constructChild*[O](s: var YamlStream, c: ConstructionContext,
|
||||
|
@ -948,10 +953,6 @@ proc load*[K](input: Stream | string, target: var K)
|
|||
parser = newYamlParser(serializationTagLibrary)
|
||||
events = parser.parse(input)
|
||||
try: construct(events, target)
|
||||
except YamlConstructionError:
|
||||
var e = (ref YamlConstructionError)(getCurrentException())
|
||||
discard events.getLastTokenContext(e.line, e.column, e.lineContent)
|
||||
raise e
|
||||
except YamlStreamError:
|
||||
let e = (ref YamlStreamError)(getCurrentException())
|
||||
if e.parent of IOError: raise (ref IOError)(e.parent)
|
||||
|
|
Loading…
Reference in New Issue