diff --git a/private/lex.nim b/private/lex.nim index edba682..2b67d01 100644 --- a/private/lex.nim +++ b/private/lex.nim @@ -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) diff --git a/test/tserialization.nim b/test/tserialization.nim index 6a117c2..0f4812a 100644 --- a/test/tserialization.nim +++ b/test/tserialization.nim @@ -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": diff --git a/yaml/parser.nim b/yaml/parser.nim index 8debfbf..200cf28 100644 --- a/yaml/parser.nim +++ b/yaml/parser.nim @@ -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) = diff --git a/yaml/serialization.nim b/yaml/serialization.nim index 1bdfee2..9834e30 100644 --- a/yaml/serialization.nim +++ b/yaml/serialization.nim @@ -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..