data.nim
1 # NimYAML - YAML implementation in Nim 2 # (c) Copyright 2015-2023 Felix Krause 3 # 4 # See the file "copying.txt", included in this 5 # distribution, for details about the copyright. 6 7 ## ================ 8 ## Module yaml/data 9 ## ================ 10 ## 11 ## This module defines the event data structure which serves as internal 12 ## representation of YAML structures. Every YAML document can be described 13 ## as stream of events. 14 15 import hashes 16 import style, private/escaping 17 export style 18 19 type 20 Anchor* = distinct string ## \ 21 ## An ``Anchor`` identifies an anchor in the current document. 22 ## It is not necessarily unique and references to an anchor must be 23 ## resolved immediately on occurrence. 24 ## 25 ## Anchor provides the operator `$` for converting to string, `==` for 26 ## comparison, and `hash` for usage in a hashmap. 27 28 Tag* = distinct string ## \ 29 ## A ``Tag`` contains an URI, like for example ``"tag:yaml.org,2002:str"``. 30 31 EventKind* = enum 32 ## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds 33 ## are discussed in `YamlStreamEvent <#YamlStreamEvent>`_. 34 yamlStartStream, yamlEndStream, 35 yamlStartDoc, yamlEndDoc, yamlStartMap, yamlEndMap, 36 yamlStartSeq, yamlEndSeq, yamlScalar, yamlAlias 37 38 Event* = object 39 ## An element from a `YamlStream <#YamlStream>`_. Events that start an 40 ## object (``yamlStartMap``, ``yamlStartSeq``, ``yamlScalar``) have 41 ## an optional anchor and a tag associated with them. The anchor will be 42 ## set to ``yAnchorNone`` if it doesn't exist. 43 ## 44 ## A missing tag in the YAML character stream generates 45 ## the non-specific tags ``?`` or ``!`` according to the YAML 46 ## specification. These are by convention mapped to the ``TagId`` s 47 ## ``yTagQuestionMark`` and ``yTagExclamationMark`` respectively. 48 ## Mapping is done by a `TagLibrary <#TagLibrary>`_. 49 ## 50 ## ``startPos`` and ``endPos`` are only relevant for events from an input 51 ## stream - they are generally ignored if used with events that generate 52 ## output. 53 startPos*, endPos*: Mark 54 case kind*: EventKind 55 of yamlStartStream, yamlEndStream: discard 56 of yamlStartMap: 57 mapProperties*: Properties 58 mapStyle*: CollectionStyle 59 of yamlStartSeq: 60 seqProperties*: Properties 61 seqStyle*: CollectionStyle 62 of yamlScalar: 63 scalarProperties*: Properties 64 scalarStyle* : ScalarStyle 65 scalarContent*: string 66 of yamlStartDoc: 67 explicitDirectivesEnd*: bool 68 version*: string 69 handles*: seq[tuple[handle, uriPrefix: string]] 70 of yamlEndDoc: 71 explicitDocumentEnd*: bool 72 of yamlEndMap, yamlEndSeq: discard 73 of yamlAlias: 74 aliasTarget* : Anchor 75 76 Mark* = object 77 line* : Positive = 1 78 column*: Positive = 1 79 80 Properties* = tuple[anchor: Anchor, tag: Tag] 81 82 YamlLoadingError* = object of ValueError 83 ## Base class for all exceptions that may be raised during the process 84 ## of loading a YAML character stream. 85 mark*: Mark ## position at which the error has occurred. 86 lineContent*: string ## \ 87 ## content of the line where the error was encountered. Includes a 88 ## second line with a marker ``^`` at the position where the error 89 ## was encountered. 90 91 const 92 yamlTagRepositoryPrefix* = "tag:yaml.org,2002:" 93 nimyamlTagRepositoryPrefix* = "tag:nimyaml.org,2016:" 94 95 proc defineTag*(uri: string): Tag = 96 ## defines a tag. Use this to optimize away copies of globally defined 97 ## Tags. 98 result = uri.Tag 99 #shallow(result.string) # doesn't work at compile-time 100 101 proc defineCoreTag*(name: string): Tag = 102 ## defines a tag in YAML's core namespace, ``tag:yaml.org,2002:`` 103 result = defineTag(yamlTagRepositoryPrefix & name) 104 105 const 106 yAnchorNone*: Anchor = "".Anchor ## \ 107 ## yielded when no anchor was defined for a YAML node 108 109 yTagExclamationMark*: Tag = defineTag("!") 110 yTagQuestionMark* : Tag = defineTag("?") 111 112 # failsafe schema 113 114 yTagString* = defineCoreTag("str") 115 yTagSequence* = defineCoreTag("seq") 116 yTagMapping* = defineCoreTag("map") 117 118 # json & core schema 119 120 yTagNull* = defineCoreTag("null") 121 yTagBoolean* = defineCoreTag("bool") 122 yTagInteger* = defineCoreTag("int") 123 yTagFloat* = defineCoreTag("float") 124 125 # other language-independent YAML types (from http://yaml.org/type/ ) 126 127 yTagOrderedMap* = defineCoreTag("omap") 128 yTagPairs* = defineCoreTag("pairs") 129 yTagSet* = defineCoreTag("set") 130 yTagBinary* = defineCoreTag("binary") 131 yTagMerge* = defineCoreTag("merge") 132 yTagTimestamp* = defineCoreTag("timestamp") 133 yTagValue* = defineCoreTag("value") 134 yTagYaml* = defineCoreTag("yaml") 135 136 # NimYAML specific tags 137 138 yTagNimField* = defineTag(nimyamlTagRepositoryPrefix & "field") 139 140 proc collectionStyle*(event: Event): CollectionStyle = 141 ## returns the style of the given collection start event 142 case event.kind 143 of yamlStartMap: result = event.mapStyle 144 of yamlStartSeq: result = event.seqStyle 145 else: raise (ref FieldDefect)(msg: "Event " & $event.kind & " has no collectionStyle") 146 147 proc startStreamEvent*(): Event = 148 return Event(kind: yamlStartStream) 149 150 proc endStreamEvent*(): Event = 151 return Event(kind: yamlEndStream) 152 153 proc startDocEvent*( 154 explicit = false, 155 version = "", 156 handles : seq[tuple[handle, uriPrefix: string]] = @[], 157 startPos : Mark = Mark(), 158 endPos : Mark = Mark(), 159 ): Event 160 {.inline, raises: [].} = 161 ## creates a new event that marks the start of a YAML document 162 result = Event(startPos: startPos, endPos: endPos, 163 kind: yamlStartDoc, version: version, handles: handles, 164 explicitDirectivesEnd: explicit) 165 166 proc endDocEvent*( 167 explicit = false, 168 startPos : Mark = Mark(), 169 endPos : Mark = Mark(), 170 ): Event 171 {.inline, raises: [].} = 172 ## creates a new event that marks the end of a YAML document 173 result = Event(startPos: startPos, endPos: endPos, 174 kind: yamlEndDoc, explicitDocumentEnd: explicit) 175 176 proc startMapEvent*( 177 style : CollectionStyle, 178 props : Properties, 179 startPos: Mark = Mark(), 180 endPos : Mark = Mark(), 181 ): Event {.inline, raises: [].} = 182 ## creates a new event that marks the start of a YAML mapping 183 result = Event(startPos: startPos, endPos: endPos, 184 kind: yamlStartMap, mapProperties: props, 185 mapStyle: style) 186 187 proc startMapEvent*( 188 style : CollectionStyle = csAny, 189 tag : Tag = yTagQuestionMark, 190 anchor : Anchor = yAnchorNone, 191 startPos: Mark = Mark(), 192 endPos : Mark = Mark(), 193 ): Event {.inline.} = 194 return startMapEvent(style, (anchor, tag), startPos, endPos) 195 196 proc endMapEvent*( 197 startPos : Mark = Mark(), 198 endPos : Mark = Mark(), 199 ): Event {.inline, raises: [].} = 200 ## creates a new event that marks the end of a YAML mapping 201 result = Event(startPos: startPos, endPos: endPos, kind: yamlEndMap) 202 203 proc startSeqEvent*( 204 style : CollectionStyle, 205 props : Properties, 206 startPos: Mark = Mark(), 207 endPos : Mark = Mark(), 208 ): Event {.inline, raises: [].} = 209 ## creates a new event that marks the beginning of a YAML sequence 210 result = Event(startPos: startPos, endPos: endPos, 211 kind: yamlStartSeq, seqProperties: props, 212 seqStyle: style) 213 214 proc startSeqEvent*( 215 style : CollectionStyle = csAny, 216 tag : Tag = yTagQuestionMark, 217 anchor : Anchor = yAnchorNone, 218 startPos: Mark = Mark(), 219 endPos : Mark = Mark(), 220 ): Event {.inline.} = 221 return startSeqEvent(style, (anchor, tag), startPos, endPos) 222 223 proc endSeqEvent*( 224 startPos: Mark = Mark(), 225 endPos : Mark = Mark(), 226 ): Event {.inline, raises: [].} = 227 ## creates a new event that marks the end of a YAML sequence 228 result = Event(startPos: startPos, endPos: endPos, kind: yamlEndSeq) 229 230 proc scalarEvent*( 231 content : string, 232 props : Properties, 233 style : ScalarStyle = ssAny, 234 startPos: Mark = Mark(), 235 endPos : Mark = Mark(), 236 ): Event {.inline, raises: [].} = 237 ## creates a new event that represents a YAML scalar 238 result = Event(startPos: startPos, endPos: endPos, 239 kind: yamlScalar, scalarProperties: props, 240 scalarContent: content, scalarStyle: style) 241 242 proc scalarEvent*( 243 content : string = "", 244 tag : Tag = yTagQuestionMark, 245 anchor : Anchor = yAnchorNone, 246 style : ScalarStyle = ssAny, 247 startPos: Mark = Mark(), 248 endPos : Mark = Mark(), 249 ): Event {.inline.} = 250 return scalarEvent(content, (anchor, tag), style, startPos, endPos) 251 252 proc aliasEvent*( 253 target : Anchor, 254 startPos: Mark = Mark(), 255 endPos : Mark = Mark(), 256 ): Event {.inline, raises: [].} = 257 ## creates a new event that represents a YAML alias 258 result = Event(startPos: startPos, endPos: endPos, kind: yamlAlias, aliasTarget: target) 259 260 proc `==`*(left, right: Anchor): bool {.borrow.} 261 proc `$`*(id: Anchor): string {.borrow.} 262 proc hash*(id: Anchor): Hash {.borrow.} 263 264 proc `==`*(left, right: Tag): bool {.borrow.} 265 proc `$`*(tag: Tag): string {.borrow.} 266 proc hash*(tag: Tag): Hash {.borrow.} 267 268 proc `==`*(left: Event, right: Event): bool {.raises: [].} = 269 ## compares all existing fields of the given items 270 if left.kind != right.kind: return false 271 case left.kind 272 of yamlStartStream, yamlEndStream, yamlStartDoc, yamlEndDoc, yamlEndMap, yamlEndSeq: 273 result = true 274 of yamlStartMap: 275 result = left.mapProperties == right.mapProperties 276 of yamlStartSeq: 277 result = left.seqProperties == right.seqProperties 278 of yamlScalar: 279 result = left.scalarProperties == right.scalarProperties and 280 left.scalarContent == right.scalarContent 281 of yamlAlias: result = left.aliasTarget == right.aliasTarget 282 283 proc renderAttrs*(props: Properties, isPlain: bool = true): string = 284 result = "" 285 if props.anchor != yAnchorNone: result &= " &" & $props.anchor 286 case props.tag 287 of yTagQuestionMark: discard 288 of yTagExclamationMark: 289 if isPlain: result &= " <!>" 290 else: 291 result &= " <" & $props.tag & ">" 292 293 proc `$`*(event: Event): string {.raises: [].} = 294 ## outputs a human-readable string describing the given event. 295 ## This string is compatible to the format used in the yaml test suite. 296 case event.kind 297 of yamlStartStream: result = "+STR" 298 of yamlEndStream: result = "-STR" 299 of yamlEndMap: result = "-MAP" 300 of yamlEndSeq: result = "-SEQ" 301 of yamlStartDoc: 302 result = "+DOC" 303 if event.explicitDirectivesEnd: result &= " ---" 304 of yamlEndDoc: 305 result = "-DOC" 306 if event.explicitDocumentEnd: result &= " ..." 307 of yamlStartMap: 308 result = "+MAP" & (if event.mapStyle == csFlow: " {}" else: "") & 309 renderAttrs(event.mapProperties) 310 of yamlStartSeq: 311 result = "+SEQ" & (if event.seqStyle == csFlow: " []" else: "") & 312 renderAttrs(event.seqProperties) 313 of yamlScalar: 314 result = "=VAL" & renderAttrs(event.scalarProperties, 315 event.scalarStyle == ssPlain or 316 event.scalarStyle == ssAny) 317 case event.scalarStyle 318 of ssPlain, ssAny: result &= " :" 319 of ssSingleQuoted: result &= " \'" 320 of ssDoubleQuoted: result &= " \"" 321 of ssLiteral: result &= " |" 322 of ssFolded: result &= " >" 323 result &= yamlTestSuiteEscape(event.scalarContent) 324 of yamlAlias: result = "=ALI *" & $event.aliasTarget 325 326 proc properties*(event: Event): Properties = 327 ## returns the tag of the given event 328 case event.kind 329 of yamlStartMap: result = event.mapProperties 330 of yamlStartSeq: result = event.seqProperties 331 of yamlScalar: result = event.scalarProperties 332 else: raise newException(FieldDefect, "Event " & $event.kind & " has no properties") 333 334 proc emptyProperties*(event: Event): bool = 335 ## returns true if the given event does not need to render 336 ## any properties if presented into a YAML character stream. 337 let props = event.properties() 338 case props.tag 339 of yTagExclamationMark: 340 result = event.kind == yamlScalar and props.anchor == yAnchorNone 341 of yTagQuestionMark: 342 result = props.anchor == yAnchorNone 343 else: result = false 344 345 proc hasSpecificTag*(event: Event): bool = 346 var props: Properties 347 case event.kind 348 of yamlStartMap: props = event.mapProperties 349 of yamlStartSeq: props = event.seqProperties 350 of yamlScalar: props = event.scalarProperties 351 else: return false 352 case props.tag 353 of yTagExclamationMark, yTagQuestionMark: return false 354 else: return true