diff --git a/tests/test-suite/jvm/src/main/scala/org/virtuslab/yaml/TestRunner.scala b/tests/test-suite/jvm/src/main/scala/org/virtuslab/yaml/TestRunner.scala index 2e3c33032..1b6b21de5 100644 --- a/tests/test-suite/jvm/src/main/scala/org/virtuslab/yaml/TestRunner.scala +++ b/tests/test-suite/jvm/src/main/scala/org/virtuslab/yaml/TestRunner.scala @@ -54,16 +54,15 @@ object TestRunnerUtils: def convertEventToYamlTestSuiteFormat(events: Seq[EventKind]): String = events .map { - case StreamStart => "+STR" - case StreamEnd => "-STR" - case DocumentStart(explicit) => if (explicit) "+DOC ---" else "+DOC" - case DocumentEnd(explicit) => if (explicit) "-DOC ..." else "-DOC" - case SequenceStart(data) => s"+SEQ${data.asString}" - case SequenceEnd => "-SEQ" - case MappingStart(data) => s"+MAP${data.asString}" - case FlowMappingStart(data) => s"+MAP${data.asString}" - case MappingEnd | FlowMappingEnd => "-MAP" - case Alias(alias) => s"=ALI *$alias" + case StreamStart => "+STR" + case StreamEnd => "-STR" + case DocumentStart(explicit) => if (explicit) "+DOC ---" else "+DOC" + case DocumentEnd(explicit) => if (explicit) "-DOC ..." else "-DOC" + case SequenceStart(data) => s"+SEQ${data.asString}" + case SequenceEnd => "-SEQ" + case MappingStart(data) => s"+MAP${data.asString}" + case MappingEnd => "-MAP" + case Alias(alias) => s"=ALI *$alias" case Scalar(value, style, data) => style match { case ScalarStyle.Plain => s"=VAL${data.asString} :$value" diff --git a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/compose/Composer.scala b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/compose/Composer.scala index c47455938..b036eadb3 100644 --- a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/compose/Composer.scala +++ b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/compose/Composer.scala @@ -1,6 +1,7 @@ package org.virtuslab.yaml.internal.load.compose import scala.annotation.tailrec +import scala.collection.mutable import org.virtuslab.yaml.ComposerError import org.virtuslab.yaml.Node @@ -8,8 +9,10 @@ import org.virtuslab.yaml.Position import org.virtuslab.yaml.Range import org.virtuslab.yaml.Tag import org.virtuslab.yaml.YamlError +import org.virtuslab.yaml.internal.load.parse.Anchor import org.virtuslab.yaml.internal.load.parse.Event 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.reader.Scanner @@ -29,24 +32,38 @@ object ComposerImpl extends Composer: override def fromEvents(events: List[Event]): Either[YamlError, Node] = events match case Nil => Left(ComposerError("No events available")) - case _ => composeNode(events).map(_.node) + case _ => composeNode(events, mutable.Map.empty).map(_.node) - private def composeNode(events: List[Event]): ComposeResult[Node] = events match + private def composeNode( + events: List[Event], + aliases: mutable.Map[Anchor, Node] + ): ComposeResult[Node] = events match case head :: tail => head.kind match - case EventKind.StreamStart | _: EventKind.DocumentStart => composeNode(tail) - case _: EventKind.SequenceStart => composeSequenceNode(tail) - case _: EventKind.MappingStart | _: EventKind.FlowMappingStart => composeMappingNode(tail) + case EventKind.StreamStart | _: EventKind.DocumentStart => composeNode(tail, aliases) + case EventKind.SequenceStart(NodeEventMetadata(anchor, _)) => + composeSequenceNode(tail, anchor, aliases) + case EventKind.MappingStart(NodeEventMetadata(anchor, _)) => + composeMappingNode(tail, anchor, aliases) case s: EventKind.Scalar => val tag: Tag = s.metadata.tag.getOrElse(Tag.resolveTag(s.value)) - Right(Result(Node.ScalarNode(s.value, tag, head.pos), tail)) + val node = Node.ScalarNode(s.value, tag, head.pos) + s.metadata.anchor.foreach(anchor => aliases.put(anchor, node)) + Right(Result(node, tail)) // todo #88 - case _: EventKind.Alias => Left(ComposerError(s"Aliases aren't currently supported")) - case event => Left(ComposerError(s"Expected YAML node, but found: $event")) + case EventKind.Alias(alias) => + aliases.get(alias) match + case Some(node) => Right(Result(node, tail)) + case None => Left(ComposerError(s"There is no anchor for $alias alias")) + case event => Left(ComposerError(s"Expected YAML node, but found: $event")) case Nil => Left(ComposerError("No events available")) - private def composeSequenceNode(events: List[Event]): ComposeResult[Node.SequenceNode] = { + private def composeSequenceNode( + events: List[Event], + anchorOpt: Option[Anchor], + aliases: mutable.Map[Anchor, Node] + ): ComposeResult[Node.SequenceNode] = { @tailrec def parseChildren( events: List[Event], @@ -57,16 +74,22 @@ object ComposerImpl extends Composer: case (Event(EventKind.SequenceEnd, _)) :: tail => Right((Result(children, tail), firstChildPos)) case _ => - composeNode(events) match + composeNode(events, aliases) match case Right(node, rest) => parseChildren(rest, children :+ node, node.pos) case Left(err) => Left(err) parseChildren(events, Nil).map { case (Result(nodes, rest), pos) => - Result(Node.SequenceNode(nodes, Tag.seq, pos), rest) + val sequence = Node.SequenceNode(nodes, Tag.seq, pos) + anchorOpt.foreach(anchor => aliases.put(anchor, sequence)) + Result(sequence, rest) } } - private def composeMappingNode(events: List[Event]): ComposeResult[Node.MappingNode] = { + private def composeMappingNode( + events: List[Event], + anchorOpt: Option[Anchor], + aliases: mutable.Map[Anchor, Node] + ): ComposeResult[Node.MappingNode] = { @tailrec def parseMappings( events: List[Event], @@ -75,7 +98,7 @@ object ComposerImpl extends Composer: ): ComposeResultWithPos[List[(Node, Node)]] = { events match case Nil => Left(ComposerError("Not found MappingEnd event for mapping")) - case Event(EventKind.MappingEnd | EventKind.FlowMappingEnd, _) :: tail => + case Event(EventKind.MappingEnd, _) :: tail => Right((Result(mappings, tail), firstChildPos)) case (e @ Event( EventKind.StreamStart | EventKind.StreamEnd | _: EventKind.DocumentStart | @@ -86,8 +109,8 @@ object ComposerImpl extends Composer: case _ => val mapping = for - key <- composeNode(events) - v <- composeNode(key.remaining) + key <- composeNode(events, aliases) + v <- composeNode(key.remaining, aliases) yield Result((key.node, v.node), v.remaining) mapping match case Right(node @ (key, value), rest) => parseMappings(rest, mappings :+ node, key.pos) @@ -95,8 +118,10 @@ object ComposerImpl extends Composer: } parseMappings(events, Nil).map { case (Result(nodes, rest), pos) => + val mapping = Node.MappingNode(nodes.toMap, Tag.map, pos) + anchorOpt.foreach(anchor => aliases.put(anchor, mapping)) Result( - Node.MappingNode(nodes.toMap, Tag.map, pos), + mapping, rest ) } diff --git a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/Event.scala b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/Event.scala index e2964d125..bd9c688e1 100644 --- a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/Event.scala +++ b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/Event.scala @@ -42,9 +42,6 @@ enum EventKind: case MappingStart(metadata: NodeEventMetadata = NodeEventMetadata.empty) case MappingEnd - case FlowMappingStart(metadata: NodeEventMetadata = NodeEventMetadata.empty) - case FlowMappingEnd - /** * Carries additional information about event which represents YAML node (scalar, start of mapping or sequence). * This could be: diff --git a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/ParserImpl.scala b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/ParserImpl.scala index dfb835d60..5a46f994d 100644 --- a/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/ParserImpl.scala +++ b/yaml/shared/src/main/scala/org/virtuslab/yaml/internal/load/parse/ParserImpl.scala @@ -232,14 +232,14 @@ final class ParserImpl private (in: Tokenizer) extends Parser: case TokenKind.FlowMappingStart => in.popToken() productions.prependAll(ParseFlowMappingEntryOpt :: ParseFlowMappingEnd :: Nil) - Right(Event(EventKind.FlowMappingStart(), token.range)) + Right(Event(EventKind.MappingStart(), token.range)) case _ => Left(ParseError.from(TokenKind.FlowMappingStart, token)) private def parseFlowMappingEnd(token: Token) = token.kind match case TokenKind.FlowMappingEnd => in.popToken() - Right(Event(EventKind.FlowMappingEnd, token.range)) + Right(Event(EventKind.MappingEnd, token.range)) case _ => Left(ParseError.from(TokenKind.FlowMappingEnd, token)) @@ -379,7 +379,7 @@ final class ParserImpl private (in: Tokenizer) extends Parser: case TokenKind.FlowMappingStart => in.popToken() productions.prependAll(ParseFlowMappingEntryOpt :: ParseFlowMappingEnd :: Nil) - Right(Event(EventKind.FlowMappingStart(metadata), nextToken.range)) + Right(Event(EventKind.MappingStart(metadata), nextToken.range)) case TokenKind.FlowSequenceStart => in.popToken() productions.prependAll(ParseFlowSeqEntryOpt :: ParseFlowSeqEnd :: Nil) diff --git a/yaml/shared/src/test/scala/org/virtuslab/yaml/decoder/DecoderSuite.scala b/yaml/shared/src/test/scala/org/virtuslab/yaml/decoder/DecoderSuite.scala index 8a0139290..2e85cccd3 100644 --- a/yaml/shared/src/test/scala/org/virtuslab/yaml/decoder/DecoderSuite.scala +++ b/yaml/shared/src/test/scala/org/virtuslab/yaml/decoder/DecoderSuite.scala @@ -186,6 +186,109 @@ class DecoderSuite extends munit.FunSuite: assertEquals(yaml.as[Spec], Right(expectedSpec)) } + test("alias for scalar node") { + val yaml = + s"""|- &a 5 + |- *a + |""".stripMargin + + assertEquals(yaml.as[Any], Right(List(5, 5))) + } + + test("alias for sequence node") { + val yaml = + s"""|seq1: &a + | - 1 + | - 2 + |seq2: *a + |""".stripMargin + + assertEquals( + yaml.as[Any], + Right( + Map( + "seq1" -> List(1, 2), + "seq2" -> List(1, 2) + ) + ) + ) + } + + test("alias for value in sequence") { + val yaml = + s"""|- &b + | name: Mark McGwire + | hr: 65 + |- *b + |""".stripMargin + + assertEquals( + yaml.as[Any], + Right( + List( + Map("name" -> "Mark McGwire", "hr" -> 65), + Map("name" -> "Mark McGwire", "hr" -> 65) + ) + ) + ) + } + + test("alias for flow sequence node") { + val yaml = + s"""|seq1: &a [1, 2] + |seq2: *a + |""".stripMargin + + assertEquals( + yaml.as[Any], + Right( + Map( + "seq1" -> List(1, 2), + "seq2" -> List(1, 2) + ) + ) + ) + } + + test("alias for mapping node") { + val yaml = + s"""|map1: &a + | 1: 2 + | k1: v1 + |map2: *a + |""".stripMargin + + assertEquals( + yaml.as[Any], + Right( + Map( + "map1" -> Map(1 -> 2, "k1" -> "v1"), + "map2" -> Map(1 -> 2, "k1" -> "v1") + ) + ) + ) + } + + test("alias for flow mapping node") { + val yaml = + s"""|map1: &a { + | 1: 2, + | k1: v1 + |} + |map2: *a + |""".stripMargin + + assertEquals( + yaml.as[Any], + Right( + Map( + "map1" -> Map(1 -> 2, "k1" -> "v1"), + "map2" -> Map(1 -> 2, "k1" -> "v1") + ) + ) + ) + } + test("decode into Map[Any, Any]") { val yaml = diff --git a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/AnchorSpec.scala b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/AnchorSpec.scala index 51f596727..1846eec2b 100644 --- a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/AnchorSpec.scala +++ b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/AnchorSpec.scala @@ -109,7 +109,7 @@ class AnchorSpec extends BaseYamlSuite: val expectedEvents = List( StreamStart, DocumentStart(), - FlowMappingStart(), + MappingStart(), Scalar("a", metadata = NodeEventMetadata(Anchor("a"))), Scalar("b", metadata = NodeEventMetadata(Anchor("b"))), Scalar("seq"), @@ -117,7 +117,7 @@ class AnchorSpec extends BaseYamlSuite: Alias(Anchor("a")), Alias(Anchor("b")), SequenceEnd, - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) diff --git a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/MappingSuite.scala b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/MappingSuite.scala index 7a0037bbd..a734348ca 100644 --- a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/MappingSuite.scala +++ b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/MappingSuite.scala @@ -209,12 +209,12 @@ class MappingSuite extends BaseYamlSuite: DocumentStart(), MappingStart(), Scalar("doubles"), - FlowMappingStart(), + MappingStart(), Scalar("double1"), Scalar("1.0"), Scalar("double2"), Scalar("2.0"), - FlowMappingEnd, + MappingEnd, MappingEnd, DocumentEnd(), StreamEnd @@ -247,13 +247,13 @@ class MappingSuite extends BaseYamlSuite: DocumentStart(), MappingStart(), Scalar("replicas"), - FlowMappingStart(), - FlowMappingStart(), + MappingStart(), + MappingStart(), Scalar("replicas"), Scalar(""), - FlowMappingEnd, + MappingEnd, Scalar(""), - FlowMappingEnd, + MappingEnd, MappingEnd, DocumentEnd(), StreamEnd @@ -267,8 +267,8 @@ class MappingSuite extends BaseYamlSuite: val expectedEvents = List( StreamStart, DocumentStart(), - FlowMappingStart(), - FlowMappingEnd, + MappingStart(), + MappingEnd, DocumentEnd(), StreamEnd ) @@ -281,10 +281,10 @@ class MappingSuite extends BaseYamlSuite: val expectedEvents = List( StreamStart, DocumentStart(), - FlowMappingStart(), - FlowMappingStart(), - FlowMappingEnd, - FlowMappingEnd, + MappingStart(), + MappingStart(), + MappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -297,10 +297,10 @@ class MappingSuite extends BaseYamlSuite: val expectedEvents = List( StreamStart, DocumentStart(), - FlowMappingStart(), + MappingStart(), SequenceStart(), SequenceEnd, - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -314,10 +314,10 @@ class MappingSuite extends BaseYamlSuite: val events = List( StreamStart, DocumentStart(), - FlowMappingStart(), + MappingStart(), Scalar("key"), Scalar("value"), - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -334,12 +334,12 @@ class MappingSuite extends BaseYamlSuite: val events = List( StreamStart, DocumentStart(), - FlowMappingStart(), - FlowMappingStart(), + MappingStart(), + MappingStart(), Scalar("double"), Scalar("1.0"), - FlowMappingEnd, - FlowMappingEnd, + MappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -356,13 +356,13 @@ class MappingSuite extends BaseYamlSuite: val events = List( StreamStart, DocumentStart(), - FlowMappingStart(), + MappingStart(), Scalar("doubles"), SequenceStart(), Scalar("v1"), Scalar("v2"), SequenceEnd, - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -380,12 +380,12 @@ class MappingSuite extends BaseYamlSuite: val events = List( StreamStart, DocumentStart(), - FlowMappingStart(), + MappingStart(), Scalar("k1"), Scalar("v1"), Scalar("k2"), Scalar("v2"), - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) @@ -454,18 +454,18 @@ class MappingSuite extends BaseYamlSuite: StreamStart, DocumentStart(explicit = true), SequenceStart(), - FlowMappingStart(), + MappingStart(), Scalar("single line", ScalarStyle.DoubleQuoted), Scalar("", metadata = NodeEventMetadata(Tag.nullTag)), Scalar("a"), Scalar("b"), - FlowMappingEnd, - FlowMappingStart(), + MappingEnd, + MappingStart(), Scalar("multi line", ScalarStyle.DoubleQuoted), Scalar("", metadata = NodeEventMetadata(Tag.nullTag)), Scalar("a"), Scalar("b"), - FlowMappingEnd, + MappingEnd, SequenceEnd, DocumentEnd(), StreamEnd @@ -483,10 +483,10 @@ class MappingSuite extends BaseYamlSuite: val expectedEvents = List( StreamStart, DocumentStart(explicit = true), - FlowMappingStart(), + MappingStart(), Scalar("foo", ScalarStyle.DoubleQuoted), Scalar("bar"), - FlowMappingEnd, + MappingEnd, DocumentEnd(), StreamEnd ) diff --git a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/SequenceSuite.scala b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/SequenceSuite.scala index 72f44156c..a333f7ade 100644 --- a/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/SequenceSuite.scala +++ b/yaml/shared/src/test/scala/org/virtuslab/yaml/parser/SequenceSuite.scala @@ -176,8 +176,8 @@ class SequenceSuite extends BaseYamlSuite: StreamStart, DocumentStart(), SequenceStart(), - FlowMappingStart(), - FlowMappingEnd, + MappingStart(), + MappingEnd, SequenceEnd, DocumentEnd(), StreamEnd @@ -303,10 +303,10 @@ class SequenceSuite extends BaseYamlSuite: StreamStart, DocumentStart(), SequenceStart(), - FlowMappingStart(), + MappingStart(), Scalar("key", style = ScalarStyle.DoubleQuoted), Scalar(":value"), - FlowMappingEnd, + MappingEnd, SequenceEnd, DocumentEnd(), StreamEnd