# NimYAML - YAML implementation in Nim # (c) Copyright 2016 Felix Krause # # See the file "copying.txt", included in this # distribution, for details about the copyright. import ../yaml, ../yaml/data import lexbase, streams, tables, strutils type LexerToken = enum plusStr, minusStr, plusDoc, minusDoc, plusMap, minusMap, plusSeq, minusSeq, mapBraces, seqBrackets, eqVal, eqAli, chevTag, andAnchor, starAnchor, colonContent, sqContent, dqContent, litContent, foContent, explDirEnd, explDocEnd, noToken StreamPos = enum beforeStream, inStream, afterStream EventLexer = object of BaseLexer content: string EventStreamError = object of ValueError proc nextToken(lex: var EventLexer): LexerToken = while true: case lex.buf[lex.bufpos] of ' ', '\t': lex.bufpos.inc() of '\r': lex.bufpos = lex.handleCR(lex.bufpos) of '\l': lex.bufpos = lex.handleLF(lex.bufpos) else: break if lex.buf[lex.bufpos] == EndOfFile: return noToken case lex.buf[lex.bufpos] of ':', '"', '\'', '|', '>': let t = case lex.buf[lex.bufpos] of ':': colonContent of '"': dqContent of '\'': sqContent of '|': litContent of '>': foContent else: colonContent lex.content = "" lex.bufpos.inc() while true: case lex.buf[lex.bufpos] of EndOfFile: break of '\c': lex.bufpos = lex.handleCR(lex.bufpos) break of '\l': lex.bufpos = lex.handleLF(lex.bufpos) break of '\\': lex.bufpos.inc() case lex.buf[lex.bufpos] of 'n': lex.content.add('\l') of 'r': lex.content.add('\r') of '0': lex.content.add('\0') of 'b': lex.content.add('\b') of 't': lex.content.add('\t') of '\\': lex.content.add('\\') else: raise newException(EventStreamError, "Unknown escape character: " & lex.buf[lex.bufpos]) else: lex.content.add(lex.buf[lex.bufpos]) lex.bufpos.inc() result = t of '<': lex.content = "" lex.bufpos.inc() while lex.buf[lex.bufpos] != '>': lex.content.add(lex.buf[lex.bufpos]) lex.bufpos.inc() if lex.buf[lex.bufpos] == EndOfFile: raise newException(EventStreamError, "Unclosed tag URI!") result = chevTag lex.bufpos.inc() of '&': lex.content = "" lex.bufpos.inc() while lex.buf[lex.bufpos] notin {' ', '\t', '\r', '\l', EndOfFile}: lex.content.add(lex.buf[lex.bufpos]) lex.bufpos.inc() result = andAnchor of '*': lex.content = "" lex.bufpos.inc() while lex.buf[lex.bufpos] notin {' ', '\t', '\r', '\l', EndOfFile}: lex.content.add(lex.buf[lex.bufpos]) lex.bufpos.inc() result = starAnchor of '{': lex.bufpos.inc() if lex.buf[lex.bufpos] == '}': result = mapBraces else: raise newException(EventStreamError, "Invalid token: {" & lex.buf[lex.bufpos]) lex.bufpos.inc() of '[': lex.bufpos.inc() if lex.buf[lex.bufpos] == ']': result = seqBrackets else: raise newException(EventStreamError, "Invalid token: [" & lex.buf[lex.bufpos]) lex.bufpos.inc() else: lex.content = "" while lex.buf[lex.bufpos] notin {' ', '\t', '\r', '\l', EndOfFile}: lex.content.add(lex.buf[lex.bufpos]) lex.bufpos.inc() case lex.content of "+STR": result = plusStr of "-STR": result = minusStr of "+DOC": result = plusDoc of "-DOC": result = minusDoc of "+MAP": result = plusMap of "-MAP": result = minusMap of "+SEQ": result = plusSeq of "-SEQ": result = minusSeq of "=VAL": result = eqVal of "=ALI": result = eqAli of "---": result = explDirEnd of "...": result = explDocEnd else: raise newException(EventStreamError, "Invalid token: " & lex.content) template assertInEvent(name: string) {.dirty.} = if not inEvent: raise newException(EventStreamError, "Illegal token: " & name) template yieldEvent() {.dirty.} = if inEvent: yield curEvent inEvent = false template setTag(t: Tag) {.dirty.} = case curEvent.kind of yamlStartSeq: curEvent.seqProperties.tag = t of yamlStartMap: curEvent.mapProperties.tag = t of yamlScalar: curEvent.scalarProperties.tag = t else: discard template setAnchor(a: Anchor) {.dirty.} = case curEvent.kind of yamlStartSeq: curEvent.seqProperties.anchor = a of yamlStartMap: curEvent.mapProperties.anchor = a of yamlScalar: curEvent.scalarProperties.anchor = a of yamlAlias: curEvent.aliasTarget = a else: discard template curTag(): Tag = var foo: Tag case curEvent.kind of yamlStartSeq: foo = curEvent.seqProperties.tag of yamlStartMap: foo = curEvent.mapProperties.tag of yamlScalar: foo = curEvent.scalarProperties.tag else: raise newException(EventStreamError, $curEvent.kind & " may not have a tag") foo template setCurTag(val: Tag) = case curEvent.kind of yamlStartSeq: curEvent.seqProperties.tag = val of yamlStartMap: curEvent.mapProperties.tag = val of yamlScalar: curEvent.scalarProperties.tag = val else: raise newException(EventStreamError, $curEvent.kind & " may not have a tag") template curAnchor(): Anchor = var foo: Anchor case curEvent.kind of yamlStartSeq: foo = curEvent.seqProperties.anchor of yamlStartMap: foo = curEvent.mapProperties.anchor of yamlScalar: foo = curEvent.scalarProperties.anchor of yamlAlias: foo = curEvent.aliasTarget else: raise newException(EventStreamError, $curEvent.kind & "may not have an anchor") foo template setCurAnchor(val: Anchor) = case curEvent.kind of yamlStartSeq: curEvent.seqProperties.anchor = val of yamlStartMap: curEvent.mapProperties.anchor = val of yamlScalar: curEvent.scalarProperties.anchor = val of yamlAlias: curEvent.aliasTarget = val else: raise newException(EventStreamError, $curEvent.kind & " may not have an anchor") template eventStart(k: EventKind) {.dirty.} = if streamPos == beforeStream: yield Event(kind: yamlStartStream) streamPos = inStream else: yieldEvent() curEvent = Event(kind: k) setTag(yTagQuestionMark) setAnchor(yAnchorNone) inEvent = true proc parseEventStream*(input: Stream): YamlStream = var backend = iterator(): Event = var lex: EventLexer lex.open(input) var inEvent = false curEvent: Event streamPos: StreamPos = beforeStream while lex.buf[lex.bufpos] != EndOfFile: let token = lex.nextToken() case token of plusStr: if streamPos != beforeStream: raise newException(EventStreamError, "Illegal +STR") streamPos = inStream eventStart(yamlStartStream) of minusStr: if streamPos != inStream: raise newException(EventStreamError, "Illegal -STR") streamPos = afterStream eventStart(yamlEndStream) of plusDoc: eventStart(yamlStartDoc) of minusDoc: eventStart(yamlEndDoc) of plusMap: eventStart(yamlStartMap) of minusMap: eventStart(yamlEndMap) of plusSeq: eventStart(yamlStartSeq) of minusSeq: eventStart(yamlEndSeq) of eqVal: eventStart(yamlScalar) of eqAli: eventStart(yamlAlias) of mapBraces: assertInEvent("braces") curEvent.mapStyle = csFlow of seqBrackets: assertInEvent("brackets") curEvent.seqStyle = csFlow of chevTag: assertInEvent("tag") if curTag() != yTagQuestionMark: raise newException(EventStreamError, "Duplicate tag in " & $curEvent.kind) setCurTag(Tag(lex.content)) of andAnchor: assertInEvent("anchor") if curAnchor() != yAnchorNone: raise newException(EventStreamError, "Duplicate anchor in " & $curEvent.kind) setCurAnchor(lex.content.Anchor) of starAnchor: assertInEvent("alias") if curEvent.kind != yamlAlias: raise newException(EventStreamError, "Unexpected alias: " & escape(lex.content)) elif curEvent.aliasTarget != yAnchorNone: raise newException(EventStreamError, "Duplicate alias target: " & escape(lex.content)) else: curEvent.aliasTarget = lex.content.Anchor of colonContent: assertInEvent("scalar content") curEvent.scalarContent = lex.content if curEvent.kind != yamlScalar: raise newException(EventStreamError, "scalar content in non-scalar tag") of sqContent: assertInEvent("scalar content") curEvent.scalarContent = lex.content if curTag() == yTagQuestionMark: setCurTag(yTagExclamationMark) curEvent.scalarStyle = ssSingleQuoted of dqContent: assertInEvent("scalar content") curEvent.scalarContent = lex.content if curTag() == yTagQuestionMark: setCurTag(yTagExclamationMark) curEvent.scalarStyle = ssDoubleQuoted of litContent: assertInEvent("scalar content") curEvent.scalarContent = lex.content curEvent.scalarStyle = ssLiteral of foContent: assertInEvent("scalar content") curEvent.scalarContent = lex.content curEvent.scalarStyle = ssFolded of explDirEnd: assertInEvent("explicit directives end") if curEvent.kind != yamlStartDoc: raise newException(EventStreamError, "Unexpected explicit directives end") of explDocEnd: if curEvent.kind != yamlEndDoc: raise newException(EventStreamError, "Unexpected explicit document end") of noToken: discard yieldEvent() if streamPos == inStream: yield Event(kind: yamlEndStream) result = initYamlStream(backend)