Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tags to nodes #98

Merged
merged 3 commits into from
Nov 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import scala.util.Failure
import scala.util.Success
import scala.util.Try

import org.virtuslab.yaml
import org.virtuslab.yaml.Tag
import org.virtuslab.yaml.internal.load.parse.Anchor
import org.virtuslab.yaml.internal.load.parse.EventKind
import org.virtuslab.yaml.internal.load.parse.EventKind.*
import org.virtuslab.yaml.internal.load.parse.NodeEventMetadata
import org.virtuslab.yaml.internal.load.parse.ParserImpl
import org.virtuslab.yaml.internal.load.parse.Tag
import org.virtuslab.yaml.internal.load.reader.Scanner
import org.virtuslab.yaml.internal.load.reader.token.ScalarStyle

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.virtuslab.yaml

case class LoadSettings(constructors: Map[Tag, YamlDecoder[?]])
object LoadSettings:
val empty = LoadSettings(Map.empty)
52 changes: 32 additions & 20 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/Node.scala
Original file line number Diff line number Diff line change
@@ -1,46 +1,58 @@
package org.virtuslab.yaml

import org.virtuslab.yaml.Range
import org.virtuslab.yaml.Tag
import org.virtuslab.yaml.syntax.YamlPrimitive

/**
* ADT that corresponds to the YAML representation graph nodes https://yaml.org/spec/1.2/spec.html#id2764044
*/
sealed trait Node:
def pos: Option[Range]
private[yaml] def pos: Option[Range]
def tag: Tag
def as[T](using
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, T] =
c.construct(this)

object Node:
final case class ScalarNode(value: String, pos: Option[Range] = None) extends Node
final case class ScalarNode private[yaml] (value: String, tag: Tag, pos: Option[Range] = None)
extends Node

object ScalarNode:
def apply(value: String): ScalarNode = new ScalarNode(value)
def apply(value: String): ScalarNode = new ScalarNode(value, Tag.resolveTag(value))
def unapply(node: ScalarNode): Option[(String, Tag)] = Some((node.value, node.tag))
end ScalarNode

final case class SequenceNode(nodes: Seq[Node], pos: Option[Range] = None) extends Node
final case class SequenceNode private[yaml] (
nodes: Seq[Node],
tag: Tag,
pos: Option[Range] = None
) extends Node
object SequenceNode:
def apply(nodes: Node*): SequenceNode = new SequenceNode(nodes, None)
def apply(nodes: Node*): SequenceNode = new SequenceNode(nodes, Tag.seq, None)
def apply(first: YamlPrimitive, rest: YamlPrimitive*): SequenceNode =
val nodes: List[YamlPrimitive] = (first :: rest.toList)
new SequenceNode(nodes.map(_.node), None)
new SequenceNode(nodes.map(_.node), Tag.seq, None)
def unapply(node: SequenceNode): Option[(Seq[Node], Tag)] = Some((node.nodes, node.tag))
end SequenceNode

final case class MappingNode(
mappings: Seq[KeyValueNode],
final case class MappingNode private[yaml] (
mappings: Map[Node, Node],
tag: Tag,
pos: Option[Range] = None
) extends Node

object MappingNode:
def apply(nodes: KeyValueNode*): MappingNode = MappingNode(nodes, None)
def apply(mappings: Map[Node, Node]): MappingNode = MappingNode(mappings, Tag.map, None)
def apply(mappings: (Node, Node)*): MappingNode = MappingNode(mappings.toMap, Tag.map, None)
def apply(
first: (YamlPrimitive, YamlPrimitive),
rest: (YamlPrimitive, YamlPrimitive)*
): MappingNode =
val nodes = (first :: rest.toList)
val kvn = nodes.map((k, v) => KeyValueNode(k.node, v.node))
new MappingNode(kvn, None)

final case class KeyValueNode(
key: Node,
value: Node,
pos: Option[Range] = None
) extends Node

val primitives = (first :: rest.toList)
val mappings = primitives.map((k, v) => (k.node -> v.node)).toMap
new MappingNode(mappings, Tag.map, None)
def unapply(node: MappingNode): Option[(Map[Node, Node], Tag)] = Some((node.mappings, node.tag))
end MappingNode
end Node
51 changes: 51 additions & 0 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/Tag.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.virtuslab.yaml

import scala.reflect.ClassTag

sealed trait Tag:
def value: String

final case class CoreSchemaTag(value: String) extends Tag
final case class CustomTag(value: String) extends Tag

object Tag:
def apply[T](implicit classTag: ClassTag[T]): Tag = CustomTag(
s"!${classTag.runtimeClass.getName}"
)

private val default = "tag:yaml.org,2002:"
val nullTag: Tag = CoreSchemaTag(s"${default}null")
val boolean: Tag = CoreSchemaTag(s"${default}bool")
val int: Tag = CoreSchemaTag(s"${default}int")
val float: Tag = CoreSchemaTag(s"${default}float")
val str: Tag = CoreSchemaTag(s"${default}str")
val seq: Tag = CoreSchemaTag(s"${default}seq")
val map: Tag = CoreSchemaTag(s"${default}map")

val corePrimitives = Set(nullTag, boolean, int, float, str)
val coreSchemaValues = (corePrimitives ++ Set(seq, map)).map(_.value)

private val nullPattern = "null|Null|NULL|~".r
private val booleanPattern = "true|True|TRUE|false|False|FALSE".r
private val int10Pattern = "[-+]?[0-9]+".r
private val int8Pattern = "0o[0-7]+".r
private val int16Pattern = "0x[0-9a-fA-F]+".r
private val floatPattern = "[-+]?(\\.[0-9]+|[0-9]+(\\.[0-9]*)?)([eE][-+]?[0-9]+)?".r
private val minusInfinity = "-(\\.inf|\\.Inf|\\.INF)".r
private val plusInfinity = "\\+?(\\.inf|\\.Inf|\\.INF)".r

def resolveTag(value: String): Tag = {
value match {
case null => nullTag
case nullPattern(_*) => nullTag
case booleanPattern(_*) => boolean
case int10Pattern(_*) => int
case int8Pattern(_*) => int
case int16Pattern(_*) => int
case floatPattern(_*) => float
case minusInfinity(_*) => float
case plusInfinity(_*) => float
case _ => str
}
}
end Tag
7 changes: 4 additions & 3 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/Yaml.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ inline def deriveYamlEncoder[T](using m: Mirror.Of[T]): YamlEncoder[T] = YamlEnc
inline def deriveYamlDecoder[T](using m: Mirror.Of[T]): YamlDecoder[T] = YamlDecoder.derived[T]
inline def deriveYamlCodec[T](using m: Mirror.Of[T]): YamlCodec[T] = YamlCodec.derived[T]

extension (node: Node) def as[T](using c: YamlDecoder[T]): Either[YamlError, T] = c.construct(node)

extension (str: String)
/**
* Parse YAML from the given [[String]], returning either [[YamlError]] or [[T]].
Expand All @@ -24,7 +22,10 @@ extension (str: String)
* - then [[Composer]] produces a representation graph from events
* - finally [[YamlDecoder]] (construct phase from the YAML spec) constructs data type [[T]] from the YAML representation.
*/
def as[T](using c: YamlDecoder[T]): Either[YamlError, T] =
def as[T](using
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, T] =
for
events <- {
val parser = ParserImpl(Scanner(str))
Expand Down
6 changes: 4 additions & 2 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/YamlCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ object YamlCodec:
val decoder = YamlDecoder.derived[T]
val encoder = YamlEncoder.derived[T]

def construct(node: Node): Either[ConstructError, T] = decoder.construct(node)
def asNode(obj: T): Node = encoder.asNode(obj)
def construct(node: Node)(using
settings: LoadSettings = LoadSettings.empty
): Either[ConstructError, T] = decoder.construct(node)
def asNode(obj: T): Node = encoder.asNode(obj)

end YamlCodec