NimYAML/yaml/data.nim

274 lines
11 KiB
Nim

import hashes
import private/escaping
type
Anchor* = distinct string ## \
## An ``Anchor`` identifies an anchor in the current document.
## It is not necessarily unique and references to an anchor must be
## resolved immediately on occurrence.
##
## Anchor provides the operator `$` for converting to string, `==` for
## comparison, and `hash` for usage in a hashmap.
Tag* = distinct string ## \
## A ``Tag`` contains an URI, like for example ``"tag:yaml.org,2002:str"``.
ScalarStyle* = enum
## Original style of the scalar (for input),
## or desired style of the scalar (for output).
ssAny, ssPlain, ssSingleQuoted, ssDoubleQuoted, ssLiteral, ssFolded
CollectionStyle* = enum
csAny, csBlock, csFlow, csPair
EventKind* = enum
## Kinds of YAML events that may occur in an ``YamlStream``. Event kinds
## are discussed in `YamlStreamEvent <#YamlStreamEvent>`_.
yamlStartStream, yamlEndStream,
yamlStartDoc, yamlEndDoc, yamlStartMap, yamlEndMap,
yamlStartSeq, yamlEndSeq, yamlScalar, yamlAlias
Event* = object
## An element from a `YamlStream <#YamlStream>`_. Events that start an
## object (``yamlStartMap``, ``yamlStartSeq``, ``yamlScalar``) have
## an optional anchor and a tag associated with them. The anchor will be
## set to ``yAnchorNone`` if it doesn't exist.
##
## A missing tag in the YAML character stream generates
## the non-specific tags ``?`` or ``!`` according to the YAML
## specification. These are by convention mapped to the ``TagId`` s
## ``yTagQuestionMark`` and ``yTagExclamationMark`` respectively.
## Mapping is done by a `TagLibrary <#TagLibrary>`_.
##
## ``startPos`` and ``endPos`` are only relevant for events from an input
## stream - they are generally ignored if used with events that generate
## output.
startPos*, endPos*: Mark
case kind*: EventKind
of yamlStartStream, yamlEndStream: discard
of yamlStartMap:
mapProperties*: Properties
mapStyle*: CollectionStyle
of yamlStartSeq:
seqProperties*: Properties
seqStyle*: CollectionStyle
of yamlScalar:
scalarProperties*: Properties
scalarStyle* : ScalarStyle
scalarContent*: string
of yamlStartDoc:
explicitDirectivesEnd*: bool
version*: string
handles*: seq[tuple[handle, uriPrefix: string]]
of yamlEndDoc:
explicitDocumentEnd*: bool
of yamlEndMap, yamlEndSeq: discard
of yamlAlias:
aliasTarget* : Anchor
Mark* = tuple[line, column: Positive]
Properties* = tuple[anchor: Anchor, tag: Tag]
const
yamlTagRepositoryPrefix* = "tag:yaml.org,2002:"
nimyamlTagRepositoryPrefix* = "tag:nimyaml.org,2016:"
proc defineTag*(uri: string): Tag =
## defines a tag. Use this to optimize away copies of globally defined
## Tags.
result = uri.Tag
#shallow(result.string) # doesn't work at compile-time
proc defineCoreTag*(name: string): Tag =
## defines a tag in YAML's core namespace, ``tag:yaml.org,2002:``
result = defineTag(yamlTagRepositoryPrefix & name)
const
yAnchorNone*: Anchor = "".Anchor ## \
## yielded when no anchor was defined for a YAML node
defaultMark: Mark = (1.Positive, 1.Positive) ## \
## used for events that are not generated from input.
yTagExclamationMark*: Tag = defineTag("!")
yTagQuestionMark* : Tag = defineTag("?")
# failsafe schema
yTagString* = defineCoreTag("str")
yTagSequence* = defineCoreTag("seq")
yTagMapping* = defineCoreTag("map")
# json & core schema
yTagNull* = defineCoreTag("null")
yTagBoolean* = defineCoreTag("bool")
yTagInteger* = defineCoreTag("int")
yTagFloat* = defineCoreTag("float")
# other language-independent YAML types (from http://yaml.org/type/ )
yTagOrderedMap* = defineCoreTag("omap")
yTagPairs* = defineCoreTag("pairs")
yTagSet* = defineCoreTag("set")
yTagBinary* = defineCoreTag("binary")
yTagMerge* = defineCoreTag("merge")
yTagTimestamp* = defineCoreTag("timestamp")
yTagValue* = defineCoreTag("value")
yTagYaml* = defineCoreTag("yaml")
# NimYAML specific tags
yTagNimField* = defineTag(nimyamlTagRepositoryPrefix & "field")
proc properties*(event: Event): Properties =
## returns the tag of the given event
case event.kind
of yamlStartMap: result = event.mapProperties
of yamlStartSeq: result = event.seqProperties
of yamlScalar: result = event.scalarProperties
else: raise newException(FieldDefect, "Event " & $event.kind & " has no properties")
proc collectionStyle*(event: Event): CollectionStyle =
## returns the style of the given collection start event
case event.kind
of yamlStartMap: result = event.mapStyle
of yamlStartSeq: result = event.seqStyle
else: raise (ref FieldDefect)(msg: "Event " & $event.kind & " has no collectionStyle")
proc startStreamEvent*(): Event =
return Event(startPos: defaultMark, endPos: defaultMark, kind: yamlStartStream)
proc endStreamEvent*(): Event =
return Event(startPos: defaultMark, endPos: defaultMark, kind: yamlEndStream)
proc startDocEvent*(explicit: bool = false, version: string = "",
handles: seq[tuple[handle, uriPrefix: string]] = @[],
startPos, endPos: Mark = defaultMark): Event
{.inline, raises: [].} =
## creates a new event that marks the start of a YAML document
result = Event(startPos: startPos, endPos: endPos,
kind: yamlStartDoc, version: version, handles: handles,
explicitDirectivesEnd: explicit)
proc endDocEvent*(explicit: bool = false, startPos, endPos: Mark = defaultMark): Event
{.inline, raises: [].} =
## creates a new event that marks the end of a YAML document
result = Event(startPos: startPos, endPos: endPos,
kind: yamlEndDoc, explicitDocumentEnd: explicit)
proc startMapEvent*(style: CollectionStyle, props: Properties,
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that marks the start of a YAML mapping
result = Event(startPos: startPos, endPos: endPos,
kind: yamlStartMap, mapProperties: props,
mapStyle: style)
proc startMapEvent*(style: CollectionStyle = csAny,
tag: Tag = yTagQuestionMark,
anchor: Anchor = yAnchorNone,
startPos, endPos: Mark = defaultMark): Event {.inline.} =
return startMapEvent(style, (anchor, tag), startPos, endPos)
proc endMapEvent*(startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that marks the end of a YAML mapping
result = Event(startPos: startPos, endPos: endPos, kind: yamlEndMap)
proc startSeqEvent*(style: CollectionStyle,
props: Properties,
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that marks the beginning of a YAML sequence
result = Event(startPos: startPos, endPos: endPos,
kind: yamlStartSeq, seqProperties: props,
seqStyle: style)
proc startSeqEvent*(style: CollectionStyle = csAny,
tag: Tag = yTagQuestionMark,
anchor: Anchor = yAnchorNone,
startPos, endPos: Mark = defaultMark): Event {.inline.} =
return startSeqEvent(style, (anchor, tag), startPos, endPos)
proc endSeqEvent*(startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that marks the end of a YAML sequence
result = Event(startPos: startPos, endPos: endPos, kind: yamlEndSeq)
proc scalarEvent*(content: string, props: Properties,
style: ScalarStyle = ssAny,
startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that represents a YAML scalar
result = Event(startPos: startPos, endPos: endPos,
kind: yamlScalar, scalarProperties: props,
scalarContent: content, scalarStyle: style)
proc scalarEvent*(content: string = "", tag: Tag = yTagQuestionMark,
anchor: Anchor = yAnchorNone,
style: ScalarStyle = ssAny,
startPos, endPos: Mark = defaultMark): Event {.inline.} =
return scalarEvent(content, (anchor, tag), style, startPos, endPos)
proc aliasEvent*(target: Anchor, startPos, endPos: Mark = defaultMark): Event {.inline, raises: [].} =
## creates a new event that represents a YAML alias
result = Event(startPos: startPos, endPos: endPos, kind: yamlAlias, aliasTarget: target)
proc `==`*(left, right: Anchor): bool {.borrow.}
proc `$`*(id: Anchor): string {.borrow.}
proc hash*(id: Anchor): Hash {.borrow.}
proc `==`*(left, right: Tag): bool {.borrow.}
proc `$`*(tag: Tag): string {.borrow.}
proc hash*(tag: Tag): Hash {.borrow.}
proc `==`*(left: Event, right: Event): bool {.raises: [].} =
## compares all existing fields of the given items
if left.kind != right.kind: return false
case left.kind
of yamlStartStream, yamlEndStream, yamlStartDoc, yamlEndDoc, yamlEndMap, yamlEndSeq:
result = true
of yamlStartMap:
result = left.mapProperties == right.mapProperties
of yamlStartSeq:
result = left.seqProperties == right.seqProperties
of yamlScalar:
result = left.scalarProperties == right.scalarProperties and
left.scalarContent == right.scalarContent
of yamlAlias: result = left.aliasTarget == right.aliasTarget
proc renderAttrs*(props: Properties, isPlain: bool = true): string =
result = ""
if props.anchor != yAnchorNone: result &= " &" & $props.anchor
case props.tag
of yTagQuestionMark: discard
of yTagExclamationMark:
if isPlain: result &= " <!>"
else:
result &= " <" & $props.tag & ">"
proc `$`*(event: Event): string {.raises: [].} =
## outputs a human-readable string describing the given event.
## This string is compatible to the format used in the yaml test suite.
case event.kind
of yamlStartStream: result = "+STR"
of yamlEndStream: result = "-STR"
of yamlEndMap: result = "-MAP"
of yamlEndSeq: result = "-SEQ"
of yamlStartDoc:
result = "+DOC"
if event.explicitDirectivesEnd: result &= " ---"
of yamlEndDoc:
result = "-DOC"
if event.explicitDocumentEnd: result &= " ..."
of yamlStartMap: result = "+MAP" & renderAttrs(event.mapProperties)
of yamlStartSeq: result = "+SEQ" & renderAttrs(event.seqProperties)
of yamlScalar:
result = "=VAL" & renderAttrs(event.scalarProperties,
event.scalarStyle == ssPlain or
event.scalarStyle == ssAny)
case event.scalarStyle
of ssPlain, ssAny: result &= " :"
of ssSingleQuoted: result &= " \'"
of ssDoubleQuoted: result &= " \""
of ssLiteral: result &= " |"
of ssFolded: result &= " >"
result &= yamlTestSuiteEscape(event.scalarContent)
of yamlAlias: result = "=ALI *" & $event.aliasTarget