From 9550fedc3ec047e4e55e986ae8e29bff330b6a92 Mon Sep 17 00:00:00 2001 From: Lucas Satabin Date: Sun, 15 Oct 2023 12:26:37 +0200 Subject: [PATCH] Fix default value Fixes the problem that the default is emitted in objects if the first field is not matching. --- .../src/main/scala/fs2/data/esp/ESP.scala | 35 ++++++++++-------- .../src/main/scala/fs2/data/esp/Expr.scala | 34 ++++++++++-------- .../src/main/scala/fs2/data/esp/Rhs.scala | 4 +++ .../src/main/scala/fs2/data/mft/MFT.scala | 3 ++ .../src/main/scala/fs2/data/mft/package.scala | 3 ++ .../fs2/data/mft/query/QueryCompiler.scala | 35 ++++++++++-------- .../data/json/jq/internal/ESPCompiledJq.scala | 2 +- .../fs2/data/json/tagged/JsonTagger.scala | 3 +- .../test/scala/fs2/data/json/jq/JqSpec.scala | 36 +++++++++++++++++++ 9 files changed, 110 insertions(+), 45 deletions(-) diff --git a/finite-state/shared/src/main/scala/fs2/data/esp/ESP.scala b/finite-state/shared/src/main/scala/fs2/data/esp/ESP.scala index b6d038a2f..7c008567a 100644 --- a/finite-state/shared/src/main/scala/fs2/data/esp/ESP.scala +++ b/finite-state/shared/src/main/scala/fs2/data/esp/ESP.scala @@ -76,8 +76,8 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, TT: Tag2Tag[InTag, OutTag], G: Evaluator[Guard, Tag[InTag]]): Pull[F, Nothing, Expr[Out]] = e match { - case Expr.Call(q, d, args) => - call(env, q, d, args, in, false) + case Expr.Call(q, d, args, next) => + (call(env, q, d, args, in, false), step(env, next, in)).mapN(Expr.concat(_, _)) case Expr.Epsilon => Pull.pure(Expr.Epsilon) case Expr.Open(o, next) => @@ -86,8 +86,13 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, step(env, next, in).map(Expr.Close(c, _)) case Expr.Leaf(v, next) => step(env, next, in).map(Expr.Leaf(v, _)) - case Expr.Concat(e1, e2) => - (step(env, e1, in), step(env, e2, in)).mapN(Expr.concat(_, _)) + case Expr.Default(v, next) => + step(env, next, in).map { + case Expr.Epsilon => Expr.Leaf(v, Expr.Epsilon) + case e @ Expr.Close(_, _) => Expr.Leaf(v, e) + case q @ Expr.Call(_, _, _, _) => Expr.Default(v, q) + case e => e + } } def eval[In, Out](env: Vector[Expr[Out]], depth: Int, in: Option[In], rhs: Rhs[OutTag])(implicit @@ -99,7 +104,7 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, case Rhs.Call(q, d, params) => params .traverse(eval(env, depth, in, _)) - .map(Expr.Call(q, d(depth), _)) + .map(Expr.Call(q, d(depth), _, Expr.Epsilon)) case Rhs.SelfCall(q, params) => params .traverse(eval(env, depth, in, _)) @@ -110,6 +115,8 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, .liftTo[Pull[F, Nothing, *]](new ESPException(s"unknown parameter $i")) case Rhs.Epsilon => Pull.pure(Expr.Epsilon) + case Rhs.Default(v) => + Pull.pure(Expr.Default(Out.makeLeaf(v), Expr.Epsilon)) case Rhs.Tree(tag, inner) => eval(env, depth, in, inner) .map(inner => Expr.Open(Out.makeOpen(tag), Expr.concat(inner, Expr.Close(Out.makeClose(tag), Expr.Epsilon)))) @@ -142,8 +149,12 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, private def squeeze[Out](e: Expr[Out]): (Expr[Out], List[Out]) = e match { - case Expr.Call(_, _, _) => (e, Nil) - case Expr.Epsilon => (Expr.Epsilon, Nil) + case Expr.Call(_, _, _, _) => (e, Nil) + case Expr.Epsilon => (Expr.Epsilon, Nil) + case Expr.Default(v, Expr.Epsilon) => (Expr.Leaf(v, Expr.Epsilon), Nil) + case Expr.Default(v, e @ Expr.Close(_, _)) => squeeze(Expr.Leaf(v, e)) + case Expr.Default(_, e @ (Expr.Open(_, _) | Expr.Leaf(_, _))) => squeeze(e) + case Expr.Default(_, _) => (e, Nil) case Expr.Open(o, e) => val (e1, s1) = squeeze(e) (e1, o :: s1) @@ -153,19 +164,13 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, case Expr.Leaf(v, e) => val (e1, s1) = squeeze(e) (e1, v :: s1) - case Expr.Concat(Expr.Epsilon, e2) => - val (e21, s2) = squeeze(e2) - (e21, s2) - case Expr.Concat(e1, e2) => - val (e11, s1) = squeeze(e1) - (Expr.concat(e11, e2), s1) } def squeezeAll[Out](e: Expr[Out]): (Expr[Out], List[Out]) = { @tailrec def loop(e: Expr[Out], acc: ListBuffer[Out]): (Expr[Out], List[Out]) = e match { - case Expr.Epsilon | Expr.Call(_, _, _) => (e, acc.result()) + case Expr.Epsilon | Expr.Call(_, _, _, _) => (e, acc.result()) case _ => val (e1, s) = squeeze(e) loop(e1, acc ++= s) @@ -204,7 +209,7 @@ private[data] class ESP[F[_], Guard, InTag, OutTag](val init: Int, Out: Conversion[OutTag, Out], TT: Tag2Tag[InTag, OutTag], G: Evaluator[Guard, Tag[InTag]]): Pipe[F, In, Out] = - transform[In, Out](Chunk.empty, 0, _, Expr.Call(init, 0, Nil), new ListBuffer).stream + transform[In, Out](Chunk.empty, 0, _, Expr.Call(init, 0, Nil, Expr.Epsilon), new ListBuffer).stream } diff --git a/finite-state/shared/src/main/scala/fs2/data/esp/Expr.scala b/finite-state/shared/src/main/scala/fs2/data/esp/Expr.scala index 0dbe9c763..ad909a479 100644 --- a/finite-state/shared/src/main/scala/fs2/data/esp/Expr.scala +++ b/finite-state/shared/src/main/scala/fs2/data/esp/Expr.scala @@ -22,29 +22,35 @@ import cats.syntax.show._ sealed trait Expr[+Out] object Expr { - case class Call[Out](q: Int, depth: Int, params: List[Expr[Out]]) extends Expr[Out] + case class Call[Out](q: Int, depth: Int, params: List[Expr[Out]], next: Expr[Out]) extends Expr[Out] case object Epsilon extends Expr[Nothing] case class Open[Out](open: Out, next: Expr[Out]) extends Expr[Out] case class Close[Out](close: Out, next: Expr[Out]) extends Expr[Out] case class Leaf[Out](value: Out, next: Expr[Out]) extends Expr[Out] - case class Concat[Out](fst: Expr[Out], snd: Expr[Out]) extends Expr[Out] + case class Default[Out](v: Out, next: Expr[Out]) extends Expr[Out] def concat[Out](e1: Expr[Out], e2: Expr[Out]): Expr[Out] = (e1, e2) match { - case (Epsilon, _) => e2 - case (_, Epsilon) => e1 - case (Open(o, Epsilon), _) => Open(o, e2) - case (Close(c, Epsilon), _) => Close(c, e2) - case (Leaf(v, Epsilon), _) => Leaf(v, e2) - case (_, _) => Concat(e1, e2) + case (Epsilon, _) => e2 + case (_, Epsilon) => e1 + case (Call(q, d, p, Epsilon), _) => Call(q, d, p, e2) + case (Call(q, d, p, e1), _) => Call(q, d, p, concat(e1, e2)) + case (Default(v, Epsilon), _) => Default(v, e2) + case (Default(v, e1), _) => Default(v, concat(e1, e2)) + case (Open(o, Epsilon), _) => Open(o, e2) + case (Open(o, e1), _) => Open(o, concat(e1, e2)) + case (Close(c, Epsilon), _) => Close(c, e2) + case (Close(c, e1), _) => Close(c, concat(e1, e2)) + case (Leaf(v, Epsilon), _) => Leaf(v, e2) + case (Leaf(v, e1), _) => Leaf(v, concat(e1, e2)) } implicit def show[Out: Show]: Show[Expr[Out]] = Show.show { - case Call(q, d, ps) => show"q${q}_$d(${(ps: List[Expr[Out]]).mkString_(", ")})" - case Epsilon => "" - case Open(o, next) => show"$o $next" - case Close(c, next) => show"$c $next" - case Leaf(l, next) => show"$l $next" - case Concat(e1, e2) => show"$e1 $e2" + case Call(q, d, ps, next) => show"q${q}_$d(${(ps: List[Expr[Out]]).mkString_(", ")}) $next" + case Epsilon => "" + case Open(o, next) => show"$o $next" + case Close(c, next) => show"$c $next" + case Leaf(l, next) => show"$l $next" + case Default(v, next) => show"($v)? $next" } } diff --git a/finite-state/shared/src/main/scala/fs2/data/esp/Rhs.scala b/finite-state/shared/src/main/scala/fs2/data/esp/Rhs.scala index b7cfd5f42..31e05b651 100644 --- a/finite-state/shared/src/main/scala/fs2/data/esp/Rhs.scala +++ b/finite-state/shared/src/main/scala/fs2/data/esp/Rhs.scala @@ -40,6 +40,9 @@ object Rhs { /** Empty RHS. */ case object Epsilon extends Rhs[Nothing] + /** Default RHS. Value is used only if the result would be epsilon */ + case class Default[T](v: T) extends Rhs[T] + /** Builds a tree. */ case class Tree[OutTag](tag: OutTag, inner: Rhs[OutTag]) extends Rhs[OutTag] @@ -73,6 +76,7 @@ object Rhs { case ApplyToLeaf(_) => "$f(%)" case Concat(fst, snd) => show"$fst $snd" case Epsilon => "" + case Default(v) => show"($v)?" } } diff --git a/finite-state/shared/src/main/scala/fs2/data/mft/MFT.scala b/finite-state/shared/src/main/scala/fs2/data/mft/MFT.scala index aa3720250..6b40abb5d 100644 --- a/finite-state/shared/src/main/scala/fs2/data/mft/MFT.scala +++ b/finite-state/shared/src/main/scala/fs2/data/mft/MFT.scala @@ -58,6 +58,7 @@ sealed trait Rhs[+OutTag] { object Rhs { case class Call[OutTag](q: Int, x: Forest, parameters: List[Rhs[OutTag]]) extends Rhs[OutTag] case object Epsilon extends Rhs[Nothing] + case class Default[OutTag](v: OutTag) extends Rhs[OutTag] case class Param(n: Int) extends Rhs[Nothing] case class Node[OutTag](tag: OutTag, children: Rhs[OutTag]) extends Rhs[OutTag] case class CopyNode[OutTag](children: Rhs[OutTag]) extends Rhs[OutTag] @@ -71,6 +72,7 @@ object Rhs { case Call(q, x, Nil) => show"q$q($x)" case Call(q, x, ps) => show"q$q($x${(ps: List[Rhs[O]]).mkString_(", ", ", ", "")})" case Epsilon => "" + case Default(v) => show"($v)?" case Param(i) => show"y$i" case Node(tag, children) => show"<$tag>($children)" case CopyNode(children) => show"%t($children)" @@ -262,6 +264,7 @@ private[data] class MFT[Guard, InTag, OutTag](init: Int, val rules: Map[Int, Rul case Rhs.Call(q, Forest.Second, params) => ERhs.Call(q, Depth.Value(1), params.map(translateRhs(_))) case Rhs.Param(i) => ERhs.Param(i) case Rhs.Epsilon => ERhs.Epsilon + case Rhs.Default(v) => ERhs.Default(v) case Rhs.Node(tag, inner) => ERhs.Tree(tag, translateRhs(inner)) case Rhs.CopyNode(inner) => ERhs.CapturedTree(translateRhs(inner)) case Rhs.Leaf(v) => ERhs.Leaf(v) diff --git a/finite-state/shared/src/main/scala/fs2/data/mft/package.scala b/finite-state/shared/src/main/scala/fs2/data/mft/package.scala index 64dc85eae..f876974b0 100644 --- a/finite-state/shared/src/main/scala/fs2/data/mft/package.scala +++ b/finite-state/shared/src/main/scala/fs2/data/mft/package.scala @@ -76,6 +76,9 @@ package object mft { def leaf[OutTag](out: OutTag): Rhs[OutTag] = Rhs.Leaf(out) + def default[OutTag](out: OutTag): Rhs[OutTag] = + Rhs.Default(out) + def copy: Rhs[Nothing] = Rhs.CopyLeaf diff --git a/finite-state/shared/src/main/scala/fs2/data/mft/query/QueryCompiler.scala b/finite-state/shared/src/main/scala/fs2/data/mft/query/QueryCompiler.scala index 6248c3e5c..559083400 100644 --- a/finite-state/shared/src/main/scala/fs2/data/mft/query/QueryCompiler.scala +++ b/finite-state/shared/src/main/scala/fs2/data/mft/query/QueryCompiler.scala @@ -83,10 +83,7 @@ private[fs2] abstract class QueryCompiler[InTag, OutTag, Path] { // input is copied in the first argument q0(any) -> qinit(x0, qcopy(x0)) - def translatePath(path: Path, - default: Rhs[OutTag], - start: builder.StateBuilder, - end: builder.StateBuilder): Unit = { + def translatePath(path: Path, start: builder.StateBuilder, end: builder.StateBuilder): Unit = { val regular = path2regular(path) val dfa = regular.deriveDFA // resolve transitions into patterns and guards @@ -118,7 +115,6 @@ private[fs2] abstract class QueryCompiler[InTag, OutTag, Path] { val states2 = transitions.foldLeft(states1) { case (states, (pattern, guard, tgt)) => val finalTgt = dfa.finals.contains(tgt) - val trapTgt = dfa.trap.contains(tgt) val (q2, states1) = states.get(tgt) match { case Some(q2) => (q2, states) @@ -127,16 +123,14 @@ private[fs2] abstract class QueryCompiler[InTag, OutTag, Path] { (q2, states.updated(tgt, q2)) } val pat: builder.Guardable = tagOf(pattern).fold(anyNode)(aNode(_)) - if (trapTgt) { - q1(pat.when(guard)) -> (if (default == eps) q2(x1, copyArgs: _*) ~ q1(x2, copyArgs: _*) else default) - } else if (!finalTgt) { - q1(pat.when(guard)) -> q2(x1, copyArgs: _*) ~ (if (default == eps) q1(x2, copyArgs: _*) else eps) + if (!finalTgt) { + q1(pat.when(guard)) -> q2(x1, copyArgs: _*) ~ q1(x2, copyArgs: _*) } else if (emitSelected) { q1(pat.when(guard)) -> end(x1, (copyArgs :+ copy(qcopy(x1))): _*) ~ q2(x1, copyArgs: _*) ~ - (if (default == eps) q1(x2, copyArgs: _*) else eps) + q1(x2, copyArgs: _*) } else { q1(pat.when(guard)) -> end(x1, (copyArgs :+ qcopy(x1)): _*) ~ q2(x1, copyArgs: _*) ~ - (if (default == eps) q1(x2, copyArgs: _*) else eps) + q1(x2, copyArgs: _*) } states1 } @@ -155,7 +149,7 @@ private[fs2] abstract class QueryCompiler[InTag, OutTag, Path] { val q1 = state(args = q.nargs + 1) // compile the variable binding path - translatePath(source, eps, q, q1) + translatePath(source, q, q1) // then the body with the bound variable translate(result, variable :: vars, q1) @@ -173,14 +167,27 @@ private[fs2] abstract class QueryCompiler[InTag, OutTag, Path] { val copyArgs = List.tabulate(q.nargs)(y(_)) q(any) -> q1(x0, (copyArgs :+ qv(x0, copyArgs: _*)): _*) - case Query.Ordpath(path, default) => + case Query.Ordpath(path, None) => val q1 = state(args = q.nargs + 1) // compile the path - translatePath(path, default.map(leaf(_)).getOrElse(eps), q, q1) + translatePath(path, q, q1) // emit the result q1(any) -> y(q.nargs) + + case Query.Ordpath(path, Some(dflt)) => + val q0 = state(args = q.nargs) + val q1 = state(args = q.nargs + 1) + + // compile the path + translatePath(path, q0, q1) + + // emit the result + val copyArgs = List.tabulate(q.nargs)(y(_)) + q(any) -> default(dflt) ~ q0(Forest.Self, copyArgs: _*) + q1(any) -> y(q.nargs) + case Query.Node(tag, child) => val q1 = state(args = q.nargs) diff --git a/json/src/main/scala/fs2/data/json/jq/internal/ESPCompiledJq.scala b/json/src/main/scala/fs2/data/json/jq/internal/ESPCompiledJq.scala index 4737e0066..86fdea5c9 100644 --- a/json/src/main/scala/fs2/data/json/jq/internal/ESPCompiledJq.scala +++ b/json/src/main/scala/fs2/data/json/jq/internal/ESPCompiledJq.scala @@ -28,7 +28,7 @@ private[jq] class ESPCompiledJq[F[_]: RaiseThrowable](val esp: JqESP[F]) extends def apply(in: Stream[F, Token]): Stream[F, Token] = in.through(JsonTagger.pipe) - .through(esp.pipe) + .through(esp.pipe[TaggedJson, TaggedJson]) .map(untag(_)) .unNone diff --git a/json/src/main/scala/fs2/data/json/tagged/JsonTagger.scala b/json/src/main/scala/fs2/data/json/tagged/JsonTagger.scala index 099f9edcc..b366ade04 100644 --- a/json/src/main/scala/fs2/data/json/tagged/JsonTagger.scala +++ b/json/src/main/scala/fs2/data/json/tagged/JsonTagger.scala @@ -19,10 +19,11 @@ package data package json package tagged -import scala.collection.mutable.ListBuffer import cats.Show import cats.syntax.all._ +import scala.collection.mutable.ListBuffer + private[json] sealed trait TaggedJson private[json] object TaggedJson { case object StartJson extends TaggedJson diff --git a/json/src/test/scala/fs2/data/json/jq/JqSpec.scala b/json/src/test/scala/fs2/data/json/jq/JqSpec.scala index 4c61dd547..36bfbdf25 100644 --- a/json/src/test/scala/fs2/data/json/jq/JqSpec.scala +++ b/json/src/test/scala/fs2/data/json/jq/JqSpec.scala @@ -421,4 +421,40 @@ object JqSpec extends SimpleIOSuite { ) } + test("documentation") { + val source = json"""{ + "field1": 0, + "field2": "test", + "field3": [1, 2, 3] + }""".lift[IO] + for { + compiled <- compiler.compile(jq"""[ { "field2": .field2, "field3": .field3[] } ]""") + result <- source.through(compiled).compile.toList + } yield expect.same( + List( + Token.StartArray, + Token.StartObject, + Token.Key("field2"), + Token.StringValue("test"), + Token.Key("field3"), + Token.NumberValue("1"), + Token.EndObject, + Token.StartObject, + Token.Key("field2"), + Token.StringValue("test"), + Token.Key("field3"), + Token.NumberValue("2"), + Token.EndObject, + Token.StartObject, + Token.Key("field2"), + Token.StringValue("test"), + Token.Key("field3"), + Token.NumberValue("3"), + Token.EndObject, + Token.EndArray + ), + result + ) + } + }