Skip to content

Commit

Permalink
Merge pull request #100 from VirtusLab/aliases
Browse files Browse the repository at this point in the history
Implement alias lookup
  • Loading branch information
kpodsiad authored Nov 16, 2021
2 parents f292c10 + 84e165e commit da4a50c
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
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
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

Expand All @@ -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],
Expand All @@ -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],
Expand All @@ -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 |
Expand All @@ -86,17 +109,19 @@ 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)
case Left(err) => Left(err)
}

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
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,15 @@ 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"),
SequenceStart(),
Alias(Anchor("a")),
Alias(Anchor("b")),
SequenceEnd,
FlowMappingEnd,
MappingEnd,
DocumentEnd(),
StreamEnd
)
Expand Down
Loading

0 comments on commit da4a50c

Please sign in to comment.