Skip to content

Commit

Permalink
Review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamil Podsiadlo committed Nov 13, 2021
1 parent 4092559 commit d37b0f1
Show file tree
Hide file tree
Showing 13 changed files with 78 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package org.virtuslab.yaml

case class LoadSettings(constructors: Map[Tag, YamlDecoder[Any]])
case class LoadSettings(constructors: Map[Tag, YamlDecoder[?]])
object LoadSettings:
val empty = LoadSettings(Map.empty)
3 changes: 2 additions & 1 deletion yaml/shared/src/main/scala/org/virtuslab/yaml/Node.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ sealed trait Node:
def as[T](using
c: YamlDecoder[T],
settings: LoadSettings = LoadSettings.empty
): Either[YamlError, T] = c.construct(this)
): Either[YamlError, T] =
c.construct(this)

object Node:
final case class ScalarNode private[yaml] (value: String, tag: Tag, pos: Option[Range] = None)
Expand Down
29 changes: 18 additions & 11 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/Tag.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ package org.virtuslab.yaml

import scala.reflect.ClassTag

final case class Tag(value: String)
sealed trait Tag:
def value: String

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

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

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

val primitives = Set(nullTag, boolean, int, float, str)
val nullTag: Tag = PrimitiveTag(s"${default}null")
val boolean: Tag = PrimitiveTag(s"${default}bool")
val int: Tag = PrimitiveTag(s"${default}int")
val float: Tag = PrimitiveTag(s"${default}float")
val str: Tag = PrimitiveTag(s"${default}str")
val seq: Tag = PrimitiveTag(s"${default}seq")
val map: Tag = PrimitiveTag(s"${default}map")

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

private val nullPattern = "null|Null|NULL|~".r
private val booleanPattern = "true|True|TRUE|false|False|FALSE".r
Expand Down
26 changes: 19 additions & 7 deletions yaml/shared/src/main/scala/org/virtuslab/yaml/YamlDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ object YamlDecoder:
node: Node
)(using settings: LoadSettings = LoadSettings.empty): Either[ConstructError, T] =
if pf.isDefinedAt(node) then pf(node)
else Left(ConstructError(s"Could't construct ${classTag.runtimeClass.getName} from $node"))
else
Left(
ConstructError(s"""|Could't construct ${classTag.runtimeClass.getName} from ${node.tag}
|${node.pos.map(_.errorMsg).getOrElse("")}
|""".stripMargin)
)
}

private inline def cannotParse(value: Any, tpe: String, node: Node) = ConstructError.from(
Expand Down Expand Up @@ -65,19 +70,17 @@ object YamlDecoder:
given YamlDecoder[Any] = new YamlDecoder {
def construct(node: Node)(using settings: LoadSettings = LoadSettings.empty) = {
node match {
case ScalarNode(value, tag) if Tag.primitives.contains(tag) =>
case ScalarNode(value, tag: PrimitiveTag) if Tag.plainTags.contains(tag) =>
tag match {
case Tag.nullTag => Right(None)
case Tag.boolean => value.toBooleanOption.toRight(cannotParse(value, "Boolean", node))
case Tag.int =>
if value.startsWith("0b") then
Try(Integer.parseInt(value.drop(2), 8)).toEither.swap
Try(Integer.parseInt(value.drop(2), 8)).toEither.left
.map(t => ConstructError.from(t, "Int", node))
.swap
else if value.startsWith("0x") then
Try(Integer.parseInt(value.drop(2), 8)).toEither.swap
Try(Integer.parseInt(value.drop(2), 8)).toEither.left
.map(t => ConstructError.from(t, "Int", node))
.swap
else value.toIntOption.toRight(cannotParse(value, "Int", node))
case Tag.float =>
value.toDoubleOption.toRight(cannotParse(value, "Double", node))
Expand All @@ -92,7 +95,16 @@ object YamlDecoder:
case _ =>
settings.constructors.get(node.tag) match {
case Some(decoder) => decoder.construct(node)
case None => Left(ConstructError(s"Could't create construct instance for ${node.tag}"))
case None =>
Left(
ConstructError(
s"""|Could't construct runtime instance of ${node.tag}
|${node.pos.map(_.errorMsg).getOrElse("")}
|If you're using custom datatype consider using yaml.as[MyType] instead of Any
|Or define LoadSettings where you'll specify how to construct ${node.tag}
|""".stripMargin
)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ object ComposerImpl extends Composer:
case Left(err) => Left(err)

parseChildren(events, Nil).map { case (Result(nodes, rest), pos) =>
Result(new Node.SequenceNode(nodes, Tag.seq, pos), rest)
Result(Node.SequenceNode(nodes, Tag.seq, pos), rest)
}
}

Expand Down Expand Up @@ -96,7 +96,7 @@ object ComposerImpl extends Composer:

parseMappings(events, Nil).map { case (Result(nodes, rest), pos) =>
Result(
new Node.MappingNode(nodes.toMap, Tag.map, pos),
Node.MappingNode(nodes.toMap, Tag.map, pos),
rest
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ end NodeEventMetadata
object NodeEventMetadata:
final val empty = NodeEventMetadata()
def apply(anchor: Anchor): NodeEventMetadata = NodeEventMetadata(anchor = Some(anchor))
@scala.annotation.targetName("applyForTag")
def apply(anchor: Anchor, tag: Tag): NodeEventMetadata =
NodeEventMetadata(anchor = Some(anchor), tag = Some(tag))
def apply(tag: Tag): NodeEventMetadata = NodeEventMetadata(tag = Some(tag))

opaque type Anchor = String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import scala.util.Failure
import scala.util.Success
import scala.util.Try

import org.virtuslab.yaml.CustomTag
import org.virtuslab.yaml.ParseError
import org.virtuslab.yaml.PrimitiveTag
import org.virtuslab.yaml.Range
import org.virtuslab.yaml.Tag
import org.virtuslab.yaml.YamlError
Expand Down Expand Up @@ -388,7 +390,7 @@ final class ParserImpl private (in: Tokenizer) extends Parser:
case _ =>
Right(
Event(
EventKind.Scalar("", ScalarStyle.Plain, metadata),
EventKind.Scalar("", ScalarStyle.Plain, metadata.withTag(Tag.nullTag)),
nextToken.range
)
)
Expand All @@ -407,14 +409,18 @@ final class ParserImpl private (in: Tokenizer) extends Parser:
in.popToken()
value match
case TagValue.NonSpecific =>
parseNodeAttributes(in.peekToken(), metadata.withTag(Tag("!")))
parseNodeAttributes(in.peekToken(), metadata)
case TagValue.Verbatim(value) =>
parseNodeAttributes(in.peekToken(), metadata.withTag(Tag(value)))
parseNodeAttributes(in.peekToken(), metadata.withTag(CustomTag(value)))
case TagValue.Shorthand(handle, suffix) =>
val handleKey = handle.value
directives.get(handleKey) match
case Some(prefix) =>
parseNodeAttributes(in.peekToken(), metadata.withTag(Tag(s"$prefix$suffix")))
val tagValue = s"$prefix$suffix"
val tag =
if Tag.primitivesValues.contains(tagValue) then PrimitiveTag(tagValue)
else CustomTag(tagValue)
parseNodeAttributes(in.peekToken(), metadata.withTag(tag))
case None =>
Left(ParseError(s"There is no registered tag directive for handle $handleKey"))
case _ => Right(metadata, token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class ComposerSuite extends munit.FunSuite:
assertEquals(ComposerImpl.fromEvents(events), expected)
}

test("mapping of scalars".only) {
test("mapping of scalars") {
val events = List(
StreamStart,
DocumentStart(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class ConstructSuite extends munit.FunSuite:
assertEquals(node.as[DummyClass], expectedConstructError)
}

test("decode as Any".only) {
test("decode as Any") {

val node = MappingNode(
SequenceNode(1, 2) -> ScalarNode("seq")
Expand All @@ -56,8 +56,5 @@ class ConstructSuite extends munit.FunSuite:
Seq(1, 2) -> "seq"
)

println(SequenceNode(1, 2))
println(node.as[Any])

assertEquals(node.as[Any], Right(expected))
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class DecoderSuite extends munit.FunSuite:
assertEquals(yaml.as[Map[Any, Any]], Right(expected))
}

test("decode using custom tag".only) {
test("decode using custom tag") {
case class Custom(x: Int, doubledX: Int)

val yaml =
Expand All @@ -214,19 +214,13 @@ class DecoderSuite extends munit.FunSuite:

val expected = Custom(5, 10)

val decoder = new YamlDecoder[Custom] {
override def construct(node: Node)(using
settings: LoadSettings = LoadSettings.empty
): Either[ConstructError, Custom] = node match {
case ScalarNode(value, _) =>
val int = value.toInt
Right(Custom(int, int * 2))
case _ => ???
}
}.asInstanceOf[YamlDecoder[Any]]
val decoder = YamlDecoder[Custom] { case ScalarNode(value, _) =>
val int = value.toInt
Right(Custom(int, int * 2))
}

given settings: LoadSettings = LoadSettings(
Map(Tag("!Custom") -> decoder)
Map(CustomTag("!Custom") -> decoder)
)

assertEquals(yaml.as[Any], Right(expected))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class AnchorSpec extends BaseYamlSuite:
DocumentStart(explicit = true),
MappingStart(),
Scalar("a"),
Scalar("", metadata = NodeEventMetadata(Anchor("anchor"))),
Scalar("", metadata = NodeEventMetadata(Anchor("anchor"), Tag.nullTag)),
Scalar("b"),
Alias(Anchor("anchor")),
MappingEnd,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.virtuslab.yaml
package parser

import org.virtuslab.yaml.internal.load.parse.EventKind.*
import org.virtuslab.yaml.internal.load.parse.NodeEventMetadata
import org.virtuslab.yaml.internal.load.reader.token.ScalarStyle

class MappingSuite extends BaseYamlSuite:
Expand Down Expand Up @@ -151,7 +152,7 @@ class MappingSuite extends BaseYamlSuite:
DocumentStart(),
MappingStart(),
Scalar("key"),
Scalar(""),
Scalar("", metadata = NodeEventMetadata(Tag.nullTag)),
Scalar("key2"),
Scalar("value"),
MappingEnd,
Expand All @@ -172,7 +173,7 @@ class MappingSuite extends BaseYamlSuite:
DocumentStart(),
MappingStart(),
Scalar("key"),
Scalar(""),
Scalar("", metadata = NodeEventMetadata(Tag.nullTag)),
Scalar("period"),
Scalar("10"),
MappingEnd,
Expand Down Expand Up @@ -437,13 +438,13 @@ class MappingSuite extends BaseYamlSuite:
SequenceStart(),
FlowMappingStart(),
Scalar("single line", ScalarStyle.DoubleQuoted),
Scalar(""),
Scalar("", metadata = NodeEventMetadata(Tag.nullTag)),
Scalar("a"),
Scalar("b"),
FlowMappingEnd,
FlowMappingStart(),
Scalar("multi line", ScalarStyle.DoubleQuoted),
Scalar(""),
Scalar("", metadata = NodeEventMetadata(Tag.nullTag)),
Scalar("a"),
Scalar("b"),
FlowMappingEnd,
Expand Down
21 changes: 10 additions & 11 deletions yaml/shared/src/test/scala/org/virtuslab/yaml/parser/TagSuite.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.virtuslab.yaml
package parser

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.*
Expand All @@ -22,13 +21,13 @@ class TagSuite extends BaseYamlSuite:
val expectedEvents = List(
StreamStart,
DocumentStart(),
Scalar("bar", ScalarStyle.DoubleQuoted, NodeEventMetadata(Tag("!foo"))),
Scalar("bar", ScalarStyle.DoubleQuoted, NodeEventMetadata(CustomTag("!foo"))),
DocumentEnd(explicit = true),
DocumentStart(explicit = true),
Scalar(
"bar",
ScalarStyle.DoubleQuoted,
NodeEventMetadata(Tag("tag:example.com,2000:app/foo"))
NodeEventMetadata(CustomTag("tag:example.com,2000:app/foo"))
),
DocumentEnd(),
StreamEnd
Expand All @@ -46,13 +45,13 @@ class TagSuite extends BaseYamlSuite:
val expectedEvents = List(
StreamStart,
DocumentStart(),
Scalar("bar", ScalarStyle.DoubleQuoted, NodeEventMetadata(Tag("!foo"))),
Scalar("bar", ScalarStyle.DoubleQuoted, NodeEventMetadata(CustomTag("!foo"))),
DocumentEnd(),
DocumentStart(explicit = true),
Scalar(
"bar",
ScalarStyle.DoubleQuoted,
NodeEventMetadata(Tag("tag:example.com,2000:app/foo"))
NodeEventMetadata(CustomTag("tag:example.com,2000:app/foo"))
),
DocumentEnd(),
StreamEnd
Expand All @@ -72,9 +71,9 @@ class TagSuite extends BaseYamlSuite:
StreamStart,
DocumentStart(explicit = true),
SequenceStart(),
Scalar("foo", metadata = NodeEventMetadata(Tag("!local"))),
Scalar("bar", metadata = NodeEventMetadata(Tag("tag:yaml.org,2002:str"))),
Scalar("baz", metadata = NodeEventMetadata(Tag("tag:example.com,2000:app/tag!"))),
Scalar("foo", metadata = NodeEventMetadata(CustomTag("!local"))),
Scalar("bar", metadata = NodeEventMetadata(PrimitiveTag("tag:yaml.org,2002:str"))),
Scalar("baz", metadata = NodeEventMetadata(CustomTag("tag:example.com,2000:app/tag!"))),
SequenceEnd,
DocumentEnd(),
StreamEnd
Expand All @@ -89,7 +88,7 @@ class TagSuite extends BaseYamlSuite:
val expectedEvents = List(
StreamStart,
DocumentStart(),
Scalar("a", metadata = NodeEventMetadata(Tag("!"))),
Scalar("a"),
DocumentEnd(),
StreamEnd
)
Expand All @@ -106,7 +105,7 @@ class TagSuite extends BaseYamlSuite:
val expectedEvents = List(
StreamStart,
DocumentStart(explicit = true),
SequenceStart(NodeEventMetadata(Tag("tag:yaml.org,2002:omap"))),
SequenceStart(NodeEventMetadata(CustomTag("tag:yaml.org,2002:omap"))),
MappingStart(),
Scalar("Mark McGwire"),
Scalar("65"),
Expand Down Expand Up @@ -139,7 +138,7 @@ class TagSuite extends BaseYamlSuite:
Scalar(
"bar",
ScalarStyle.DoubleQuoted,
NodeEventMetadata(Tag("tag:example.com,2000:app/foo"))
NodeEventMetadata(CustomTag("tag:example.com,2000:app/foo"))
),
SequenceEnd,
DocumentEnd(),
Expand Down

0 comments on commit d37b0f1

Please sign in to comment.