From 9fe1b5bb48b9f90e09cd70790cd54c503ed6b8f6 Mon Sep 17 00:00:00 2001 From: Fawwaz Abdullah Date: Thu, 29 Jun 2023 14:21:42 +0100 Subject: [PATCH] Add indexed Visitor classes for LazyParsley (#206) * feat: add visitor for LazyParsley used for matching and processing while carrying context without having to pattern match or type match must be extended every time a new internal parser class is added * refactor: marked visitors as visible only to parsley.internal * test: add tests for laziness preservation and for testing if the visitors are running properly. also fixed Lift3's missing laziness in the visitor class. * style: shortened lines for visit implementations within LazyParsley classes * fix: strengthened GenericLazyParsleyIVisitor's private bound to frontend, also explicitly declared Any type parameter for *> and <*'s default implementations in that generic visitor * fix: explicitly declared ManyUntil and SkipManyUntil's default overrides' type arguments to visitUnary due to Any inference * fix: LazyParsleyIVisitor's private bound weakened further to parsley to match LazyParsley's private upper bound * fix: subbed functions for partial case analyses where appropriate --------- Co-authored-by: Jamie Willis --- .../frontend/AlternativeEmbedding.scala | 2 + .../frontend/ErrorEmbedding.scala | 14 + .../frontend/IntrinsicEmbedding.scala | 6 + .../frontend/IterativeEmbedding.scala | 18 ++ .../deepembedding/frontend/LazyParsley.scala | 4 + .../frontend/PrimitiveEmbedding.scala | 14 + .../frontend/SelectiveEmbedding.scala | 14 + .../frontend/SequenceEmbedding.scala | 8 + .../deepembedding/frontend/Visitors.scala | 289 ++++++++++++++++++ .../singletons/ErrorEmbedding.scala | 7 + .../singletons/IntrinsicEmbedding.scala | 14 +- .../singletons/PrimitiveEmbedding.scala | 11 + .../singletons/SequenceEmbedding.scala | 5 + .../deepembedding/singletons/Singletons.scala | 2 +- .../singletons/TokenEmbedding.scala | 13 + .../singletons/token/SymbolEmbedding.scala | 9 + .../singletons/token/TextEmbedding.scala | 5 + .../deepembedding/frontend/VisitorTests.scala | 187 ++++++++++++ 18 files changed, 620 insertions(+), 2 deletions(-) create mode 100644 parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/Visitors.scala create mode 100644 parsley/shared/src/test/scala/parsley/internal/deepembedding/frontend/VisitorTests.scala diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/AlternativeEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/AlternativeEmbedding.scala index 5eab3b424..5df86aa4e 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/AlternativeEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/AlternativeEmbedding.scala @@ -15,4 +15,6 @@ private [parsley] final class <|>[A](p: LazyParsley[A], q: LazyParsley[A]) exten p <- suspend(p.optimised[M, R, A]) q <- suspend(q.optimised[M, R, A]) } yield backend.<|>(p, q) + + final override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this)(context, p, q) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala index a290e444e..d6c8bfa2b 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/ErrorEmbedding.scala @@ -7,25 +7,39 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class ErrorLabel[A](p: LazyParsley[A], private [ErrorLabel] val labels: Seq[String]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorLabel(p, labels) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, labels) } private [parsley] final class ErrorExplain[A](p: LazyParsley[A], reason: String) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorExplain(p, reason) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, reason) } private [parsley] final class ErrorAmend[A](p: LazyParsley[A], partial: Boolean) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorAmend(p, partial) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, partial) } private [parsley] final class ErrorEntrench[A](p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorEntrench(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p) } private [parsley] final class ErrorDislodge[A](n: Int, p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorDislodge(n, p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(n, p) } private [parsley] final class ErrorLexical[A](p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.ErrorLexical(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p) } private [parsley] final class VerifiedError[A](p: LazyParsley[A], msggen: Either[A => Seq[String], Option[A => String]]) extends Unary[A, Nothing](p) { override def make(p: StrictParsley[A]): StrictParsley[Nothing] = new backend.VerifiedError(p, msggen) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Nothing] = visitor.visit(this, context)(p, msggen) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IntrinsicEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IntrinsicEmbedding.scala index dc86a47a6..af54120bc 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IntrinsicEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IntrinsicEmbedding.scala @@ -9,11 +9,17 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class Lift2[A, B, C](private [Lift2] val f: (A, B) => C, p: LazyParsley[A], q: =>LazyParsley[B]) extends Binary[A, B, C](p, q) { override def make(p: StrictParsley[A], q: StrictParsley[B]): StrictParsley[C] = new backend.Lift2(f, p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[C] = visitor.visit(this, context)(f, p, q) } private [parsley] final class Lift3[A, B, C, D](private [Lift3] val f: (A, B, C) => D, p: LazyParsley[A], q: =>LazyParsley[B], r: =>LazyParsley[C]) extends Ternary[A, B, C, D](p, q, r) { override def make(p: StrictParsley[A], q: StrictParsley[B], r: StrictParsley[C]): StrictParsley[D] = new backend.Lift3(f, p, q, r) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[D] = visitor.visit(this, context)(f, p, q, r) } private [parsley] final class Local[S, A](val reg: Reg[S], p: LazyParsley[S], q: =>LazyParsley[A]) extends Binary[S, A, A](p, q) with UsesRegister { override def make(p: StrictParsley[S], q: StrictParsley[A]): StrictParsley[A] = new backend.Local(reg, p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(reg, p, q) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IterativeEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IterativeEmbedding.scala index 07e981aac..5546e0318 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IterativeEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/IterativeEmbedding.scala @@ -8,12 +8,18 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class Many[A](p: LazyParsley[A]) extends Unary[A, List[A]](p) { override def make(p: StrictParsley[A]): StrictParsley[List[A]] = new backend.Many(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[List[A]] = visitor.visit(this, context)(p) } private [parsley] final class SkipMany[A](p: LazyParsley[A]) extends Unary[A, Unit](p) { override def make(p: StrictParsley[A]): StrictParsley[Unit] = new backend.SkipMany(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(p) } private [parsley] final class ChainPost[A](p: LazyParsley[A], _op: =>LazyParsley[A => A]) extends Binary[A, A => A, A](p, _op) { override def make(p: StrictParsley[A], op: StrictParsley[A => A]): StrictParsley[A] = new backend.ChainPost(p, op) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, _op) } private [parsley] final class ChainPre[A](p: LazyParsley[A], op: LazyParsley[A => A]) extends LazyParsley[A] { final override def findLetsAux[M[_, _]: ContOps, R](seen: Set[LazyParsley[_]])(implicit state: LetFinderState): M[R, Unit] = { @@ -24,21 +30,33 @@ private [parsley] final class ChainPre[A](p: LazyParsley[A], op: LazyParsley[A = p <- suspend(p.optimised[M, R, A]) op <- suspend(op.optimised[M, R, A => A]) } yield new backend.ChainPre(p, op) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, op) } private [parsley] final class Chainl[A, B](init: LazyParsley[B], p: =>LazyParsley[A], op: =>LazyParsley[(B, A) => B]) extends Ternary[B, A, (B, A) => B, B](init, p, op) { override def make(init: StrictParsley[B], p: StrictParsley[A], op: StrictParsley[(B, A) => B]): StrictParsley[B] = new backend.Chainl(init, p, op) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[B] = visitor.visit(this, context)(init, p, op) } private [parsley] final class Chainr[A, B](p: LazyParsley[A], op: =>LazyParsley[(A, B) => B], private [Chainr] val wrap: A => B) extends Binary[A, (A, B) => B, B](p, op) { override def make(p: StrictParsley[A], op: StrictParsley[(A, B) => B]): StrictParsley[B] = new backend.Chainr(p, op, wrap) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[B] = visitor.visit(this, context)(p, op, wrap) } private [parsley] final class SepEndBy1[A, B](p: LazyParsley[A], sep: =>LazyParsley[B]) extends Binary[A, B, List[A]](p, sep) { override def make(p: StrictParsley[A], sep: StrictParsley[B]): StrictParsley[List[A]] = new backend.SepEndBy1(p, sep) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[List[A]] = visitor.visit(this, context)(p, sep) } private [parsley] final class ManyUntil[A](body: LazyParsley[Any]) extends Unary[Any, List[A]](body) { override def make(p: StrictParsley[Any]): StrictParsley[List[A]] = new backend.ManyUntil(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[List[A]] = visitor.visit(this, context)(body) } private [parsley] final class SkipManyUntil(body: LazyParsley[Any]) extends Unary[Any, Unit](body) { override def make(p: StrictParsley[Any]): StrictParsley[Unit] = new backend.SkipManyUntil(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(body) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/LazyParsley.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/LazyParsley.scala index 3adde04e4..14b6b132d 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/LazyParsley.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/LazyParsley.scala @@ -188,6 +188,10 @@ private [parsley] abstract class LazyParsley[+A] private [deepembedding] { } } + // Processing with visitors. + /** Use a visitor implementation to process this internal lazy parser. */ + def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] + // $COVERAGE-OFF$ /** Pretty-prints a combinator tree, for internal debugging purposes only. */ final private [internal] def prettyAST: String = { diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/PrimitiveEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/PrimitiveEmbedding.scala index 3f27048f8..c99279977 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/PrimitiveEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/PrimitiveEmbedding.scala @@ -11,25 +11,39 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class Attempt[A](p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.Attempt(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p) } private [parsley] final class Look[A](p: LazyParsley[A]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.Look(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p) } private [parsley] final class NotFollowedBy[A](p: LazyParsley[A]) extends Unary[A, Unit](p) { override def make(p: StrictParsley[A]): StrictParsley[Unit] = new backend.NotFollowedBy(p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(p) } private [parsley] final class Put[S](val reg: Reg[S], _p: LazyParsley[S]) extends Unary[S, Unit](_p) with UsesRegister { override def make(p: StrictParsley[S]): StrictParsley[Unit] = new backend.Put(reg, p) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(reg, _p) } private [parsley] final class NewReg[S, A](val reg: Reg[S], init: LazyParsley[S], body: =>LazyParsley[A]) extends Binary[S, A, A](init, body) with UsesRegister { override def make(init: StrictParsley[S], body: StrictParsley[A]): StrictParsley[A] = new backend.NewReg(reg, init, body) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(reg, init, body) } // $COVERAGE-OFF$ private [parsley] final class Debug[A](p: LazyParsley[A], name: String, ascii: Boolean, break: Breakpoint) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.Debug(p, name, ascii, break) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, name, ascii, break) } private [parsley] final class DebugError[A](p: LazyParsley[A], name: String, ascii: Boolean, errBuilder: ErrorBuilder[_]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.DebugError(p, name, ascii, errBuilder) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, name, ascii, errBuilder) } // $COVERAGE-ON$ diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SelectiveEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SelectiveEmbedding.scala index eb14da6b4..aed1de41a 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SelectiveEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SelectiveEmbedding.scala @@ -8,24 +8,38 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class Branch[A, B, C](b: LazyParsley[Either[A, B]], p: =>LazyParsley[A => C], q: =>LazyParsley[B => C]) extends Ternary[Either[A, B], A => C, B => C, C](b, p, q) { override def make(b: StrictParsley[Either[A, B]], p: StrictParsley[A => C], q: StrictParsley[B => C]): StrictParsley[C] = new backend.Branch(b, p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[C] = visitor.visit(this, context)(b, p, q) } private [parsley] final class If[A](b: LazyParsley[Boolean], p: =>LazyParsley[A], q: =>LazyParsley[A]) extends Ternary[Boolean, A, A, A](b, p, q) { override def make(b: StrictParsley[Boolean], p: StrictParsley[A], q: StrictParsley[A]): StrictParsley[A] = new backend.If(b, p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(b, p, q) } private [parsley] final class Filter[A](p: LazyParsley[A], pred: A => Boolean) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.Filter(p, pred) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, pred) } private [parsley] final class MapFilter[A, B](p: LazyParsley[A], f: A => Option[B]) extends Unary[A, B](p) { override def make(p: StrictParsley[A]): StrictParsley[B] = new backend.MapFilter(p, f) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[B] = visitor.visit(this, context)(p, f) } private [parsley] final class FilterOut[A](p: LazyParsley[A], pred: PartialFunction[A, String]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.FilterOut(p, pred) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, pred) } private [parsley] final class GuardAgainst[A](p: LazyParsley[A], pred: PartialFunction[A, Seq[String]]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.GuardAgainst(p, pred) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, pred) } private [parsley] final class UnexpectedWhen[A](p: LazyParsley[A], pred: PartialFunction[A, (String, Option[String])]) extends Unary[A, A](p) { override def make(p: StrictParsley[A]): StrictParsley[A] = new backend.UnexpectedWhen(p, pred) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(p, pred) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SequenceEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SequenceEmbedding.scala index 5ae1fdd01..a2272b35b 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SequenceEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/SequenceEmbedding.scala @@ -7,15 +7,23 @@ import parsley.internal.deepembedding.backend, backend.StrictParsley private [parsley] final class <*>[A, B](pf: LazyParsley[A => B], px: =>LazyParsley[A]) extends Binary[A => B, A, B](pf, px) { override def make(pf: StrictParsley[A => B], px: StrictParsley[A]): StrictParsley[B] = new backend.<*>(pf, px) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[B] = visitor.visit(this, context)(pf, px) } private [parsley] final class >>=[A, B](p: LazyParsley[A], private [>>=] val f: A => LazyParsley[B]) extends Unary[A, B](p) { override def make(p: StrictParsley[A]): StrictParsley[B] = new backend.>>=(p, f) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[B] = visitor.visit(this, context)(p, f) } private [parsley] final class *>[A](_p: LazyParsley[_], _q: =>LazyParsley[A]) extends Binary[Any, A, A](_p, _q) { override def make(p: StrictParsley[Any], q: StrictParsley[A]): StrictParsley[A] = backend.*>(p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(_p, _q) } private [parsley] final class <*[A](_p: LazyParsley[A], _q: =>LazyParsley[_]) extends Binary[A, Any, A](_p, _q) { override def make(p: StrictParsley[A], q: StrictParsley[Any]): StrictParsley[A] = backend.<*(p, q) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(_p, _q) } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/Visitors.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/Visitors.scala new file mode 100644 index 000000000..1e28154cf --- /dev/null +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/frontend/Visitors.scala @@ -0,0 +1,289 @@ +/* SPDX-FileCopyrightText: © 2023 Parsley Contributors + * SPDX-License-Identifier: BSD-3-Clause + */ +package parsley.internal.deepembedding.frontend + +import parsley.debug.Breakpoint +import parsley.errors.ErrorBuilder +import parsley.registers.Reg +import parsley.token.descriptions.SpaceDesc +import parsley.token.descriptions.numeric.PlusSignPresence +import parsley.token.errors.{ErrorConfig, LabelConfig, SpecialisedFilterConfig} +import parsley.token.predicate.CharPredicate + +import parsley.internal.collection.immutable.Trie +import parsley.internal.deepembedding.Sign.SignType +import parsley.internal.deepembedding.singletons._ +import parsley.internal.deepembedding.singletons.token._ +import parsley.internal.errors.CaretWidth + +/** Visitor class template for the processing of parsers without fully explicit exhaustive pattern + * matching. + * + * This particular visitor is indexed on its return type to allow the preservation of relevant type + * information, such as setting it to [[LazyParsley]] to tell the type system that you want to + * produce new parsers using the visitor. + * + * @tparam T Context type for holding processing information as the visitor visits parsers. + * @tparam U Return value wrapper for the results of visiting the parsers. + */ +private [parsley] abstract class LazyParsleyIVisitor[-T, +U[+_]] { // scalastyle:ignore number.of.methods + // Singleton parser visitors. + def visit[A](self: Pure[A], context: T)(x: A): U[A] + def visit[A](self: Fresh[A], context: T)(x: => A): U[A] + def visit(self: Satisfy, context: T)(f: Char => Boolean, expected: LabelConfig): U[Char] + def visit(self: Line.type, context: T): U[Int] + def visit(self: Col.type, context: T): U[Int] + def visit(self: Offset.type, context: T): U[Int] + def visit[S](self: Get[S], context: T)(reg: Reg[S]): U[S] + def visit(self: WhiteSpace, context: T)(ws: Char => Boolean, desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] + def visit(self: SkipComments, context: T)(desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] + def visit(self: Comment, context: T)(desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] + def visit[A](self: Sign[A], context: T)(ty: SignType, signPresence: PlusSignPresence): U[A => A] + def visit(self: NonSpecific, context: T)(name: String, + ue: String => String, + start: Char => Boolean, + letter: Char => Boolean, + illegal: String => Boolean): U[String] + def visit(self: CharTok, context: T)(c: Char, exp: LabelConfig): U[Char] + def visit(self: SupplementaryCharTok, context: T)(codepoint: Int, exp: LabelConfig): U[Int] + def visit(self: StringTok, context: T)(s: String, exp: LabelConfig): U[String] + def visit(self: Eof.type, context: T): U[Unit] + def visit(self: UniSatisfy, context: T)(f: Int => Boolean, exp: LabelConfig): U[Int] + def visit[S](self: Modify[S], context: T)(reg: Reg[S], f: S => S): U[Unit] + def visit(self: Empty, context: T)(width: Int): U[Nothing] + def visit(self: Fail, context: T)(width: CaretWidth, msgs: Seq[String]): U[Nothing] + def visit(self: Unexpected, context: T)(msg: String, width: CaretWidth): U[Nothing] + def visit(self: EscapeMapped, context: T)(escTrie: Trie[Int], escs: Set[String]): U[Int] + def visit(self: EscapeAtMost, context: T)(n: Int, radix: Int): U[BigInt] + def visit(self: EscapeOneOfExactly, context: T)(radix: Int, ns: List[Int], ie: SpecialisedFilterConfig[Int]): U[BigInt] + def visit(self: SoftKeyword, context: T)(specific: String, + letter: CharPredicate, + caseSensitive: Boolean, + expected: LabelConfig, + expectedEnd: String): U[Unit] + def visit(self: SoftOperator, context: T)(specific: String, letter: CharPredicate, ops: Trie[Unit], expected: LabelConfig, expectedEnd: String): U[Unit] + + // Primitive parser visitors. + def visit[A](self: Attempt[A], context: T)(p: LazyParsley[A]): U[A] + def visit[A](self: Look[A], context: T)(p: LazyParsley[A]): U[A] + def visit[A](self: NotFollowedBy[A], context: T)(p: LazyParsley[A]): U[Unit] + def visit[S](self: Put[S], context: T)(reg: Reg[S], p: LazyParsley[S]): U[Unit] + def visit[S, A](self: NewReg[S, A], context: T)(reg: Reg[S], init: LazyParsley[S], body: => LazyParsley[A]): U[A] + def visit[A](self: Debug[A], context: T)(p: LazyParsley[A], name: String, ascii: Boolean, break: Breakpoint): U[A] + def visit[A](self: DebugError[A], context: T)(p: LazyParsley[A], name: String, ascii: Boolean, errBuilder: ErrorBuilder[_]): U[A] + + // Selective parser visitors. + def visit[A, B, C](self: Branch[A, B, C], context: T)(b: LazyParsley[Either[A, B]], p: => LazyParsley[A => C], q: => LazyParsley[B => C]): U[C] + def visit[A](self: If[A], context: T)(b: LazyParsley[Boolean], p: => LazyParsley[A], q: => LazyParsley[A]): U[A] + def visit[A](self: Filter[A], context: T)(p: LazyParsley[A], pred: A => Boolean): U[A] + def visit[A, B](self: MapFilter[A, B], context: T)(p: LazyParsley[A], f: A => Option[B]): U[B] + def visit[A](self: FilterOut[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, String]): U[A] + def visit[A](self: GuardAgainst[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, Seq[String]]): U[A] + def visit[A](self: UnexpectedWhen[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, (String, Option[String])]): U[A] + + // Alternative parser visitors. + def visit[A](self: <|>[A])(context: T, p: LazyParsley[A], q: LazyParsley[A]): U[A] + + // Intrinsic parser visitors. + def visit[A, B, C](self: Lift2[A, B, C], context: T)(f: (A, B) => C, p: LazyParsley[A], q: => LazyParsley[B]): U[C] + def visit[A, B, C, D](self: Lift3[A, B, C, D], context: T)(f: (A, B, C) => D, p: LazyParsley[A], q: => LazyParsley[B], r: => LazyParsley[C]): U[D] + def visit[S, A](self: Local[S, A], context: T)(reg: Reg[S], p: LazyParsley[S], q: => LazyParsley[A]): U[A] + + // Sequence parser visitors. + def visit[A, B](self: A <*> B, context: T)(pf: LazyParsley[A => B], px: => LazyParsley[A]): U[B] + def visit[A, B](self: A >>= B, context: T)(p: LazyParsley[A], f: A => LazyParsley[B]): U[B] + def visit[A](self: *>[A], context: T)(p: LazyParsley[_], _q: => LazyParsley[A]): U[A] + def visit[A](self: <*[A], context: T)(p: LazyParsley[A], _q: => LazyParsley[_]): U[A] + + // Iterative parser visitors. + def visit[A](self: Many[A], context: T)(p: LazyParsley[A]): U[List[A]] + def visit[A](self: SkipMany[A], context: T)(p: LazyParsley[A]): U[Unit] + def visit[A](self: ChainPost[A], context: T)(p: LazyParsley[A], _op: => LazyParsley[A => A]): U[A] + def visit[A](self: ChainPre[A], context: T)(p: LazyParsley[A], op: => LazyParsley[A => A]): U[A] + def visit[A, B](self: Chainl[A, B], context: T)(init: LazyParsley[B], p: => LazyParsley[A], op: => LazyParsley[(B, A) => B]): U[B] + def visit[A, B](self: Chainr[A, B], context: T)(p: LazyParsley[A], op: => LazyParsley[(A, B) => B], wrap: A => B): U[B] + def visit[A, B](self: SepEndBy1[A, B], context: T)(p: LazyParsley[A], sep: => LazyParsley[B]): U[List[A]] + def visit[A](self: ManyUntil[A], context: T)(body: LazyParsley[Any]): U[List[A]] + def visit(self: SkipManyUntil, context: T)(body: LazyParsley[Any]): U[Unit] + + // Error parser visitors. + def visit[A](self: ErrorLabel[A], context: T)(p: LazyParsley[A], labels: Seq[String]): U[A] + def visit[A](self: ErrorExplain[A], context: T)(p: LazyParsley[A], reason: String): U[A] + def visit[A](self: ErrorAmend[A], context: T)(p: LazyParsley[A], partial: Boolean): U[A] + def visit[A](self: ErrorEntrench[A], context: T)(p: LazyParsley[A]): U[A] + def visit[A](self: ErrorDislodge[A], context: T)(n: Int, p: LazyParsley[A]): U[A] + def visit[A](self: ErrorLexical[A], context: T)(p: LazyParsley[A]): U[A] + def visit[A](self: VerifiedError[A], context: T)(p: LazyParsley[A], msggen: Either[A => Seq[String], Option[A => String]]): U[Nothing] +} + +/** Generalised version of [[LazyParsleyIVisitor]] that allows you to define default implementations + * for parser classes that live under a base trait (e.g. [[Unary]]). + * + * This visitor should only require implementations for [[Singleton]], [[Unary]], [[Binary]], + * [[Ternary]], [[<|>]], and [[ChainPre]]. + * + * Unless a specific override is needed, all other visitor methods are implemented relative to + * these six default implementations. + */ +private [frontend] abstract class GenericLazyParsleyIVisitor[-T, +U[+_]] extends LazyParsleyIVisitor[T, U] { // scalastyle:ignore number.of.methods + // Default methods for the four base parser types. + // XXX: These names are different as otherwise some visit methods recurse in an unwanted manner. + def visitSingleton[A](self: Singleton[A], context: T): U[A] + def visitUnary[A, B](self: Unary[A, B], context: T)(p: LazyParsley[A]): U[B] + def visitBinary[A, B, C](self: Binary[A, B, C], context: T)(l: LazyParsley[A], r: => LazyParsley[B]): U[C] + def visitTernary[A, B, C, D](self: Ternary[A, B, C, D], context: T)(f: LazyParsley[A], s: => LazyParsley[B], t: => LazyParsley[C]): U[D] + + // Singleton overrides. + override def visit[A](self: Pure[A], context: T)(x: A): U[A] = + visitSingleton(self, context) + override def visit[A](self: Fresh[A], context: T)(x: => A): U[A] = + visitSingleton(self, context) + override def visit(self: Satisfy, context: T)(f: Char => Boolean, expected: LabelConfig): U[Char] = + visitSingleton(self, context) + override def visit(self: Line.type, context: T): U[Int] = + visitSingleton(self, context) + override def visit(self: Col.type, context: T): U[Int] = + visitSingleton(self, context) + override def visit(self: Offset.type, context: T): U[Int] = + visitSingleton(self, context) + override def visit[S](self: Get[S], context: T)(reg: Reg[S]): U[S] = + visitSingleton(self, context) + override def visit(self: WhiteSpace, context: T)(ws: Char => Boolean, desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] = + visitSingleton(self, context) + override def visit(self: SkipComments, context: T)(desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] = + visitSingleton(self, context) + override def visit(self: Comment, context: T)(desc: SpaceDesc, errorConfig: ErrorConfig): U[Unit] = + visitSingleton(self, context) + override def visit[A](self: Sign[A], context: T)(ty: SignType, signPresence: PlusSignPresence): U[A => A] = + visitSingleton(self, context) + override def visit(self: NonSpecific, context: T)(name: String, + ue: String => String, + start: Char => Boolean, + letter: Char => Boolean, + illegal: String => Boolean): U[String] = + visitSingleton(self, context) + override def visit(self: CharTok, context: T)(c: Char, exp: LabelConfig): U[Char] = + visitSingleton(self, context) + override def visit(self: SupplementaryCharTok, context: T)(codepoint: Int, exp: LabelConfig): U[Int] = + visitSingleton(self, context) + override def visit(self: StringTok, context: T)(s: String, exp: LabelConfig): U[String] = + visitSingleton(self, context) + override def visit(self: Eof.type, context: T): U[Unit] = + visitSingleton(self, context) + override def visit(self: UniSatisfy, context: T)(f: Int => Boolean, exp: LabelConfig): U[Int] = + visitSingleton(self, context) + override def visit[S](self: Modify[S], context: T)(reg: Reg[S], f: S => S): U[Unit] = + visitSingleton(self, context) + override def visit(self: Empty, context: T)(width: Int): U[Nothing] = + visitSingleton(self, context) + override def visit(self: Fail, context: T)(width: CaretWidth, msgs: Seq[String]): U[Nothing] = + visitSingleton(self, context) + override def visit(self: Unexpected, context: T)(msg: String, width: CaretWidth): U[Nothing] = + visitSingleton(self, context) + override def visit(self: EscapeMapped, context: T)(escTrie: Trie[Int], escs: Set[String]): U[Int] = + visitSingleton(self, context) + override def visit(self: EscapeAtMost, context: T)(n: Int, radix: Int): U[BigInt] = + visitSingleton(self, context) + override def visit(self: EscapeOneOfExactly, context: T)(radix: Int, ns: List[Int], ie: SpecialisedFilterConfig[Int]): U[BigInt] = + visitSingleton(self, context) + override def visit(self: SoftKeyword, context: T)(specific: String, + letter: CharPredicate, + caseSensitive: Boolean, + expected: LabelConfig, + expectedEnd: String): U[Unit] = + visitSingleton(self, context) + override def visit(self: SoftOperator, context: T)(specific: String, + letter: CharPredicate, + ops: Trie[Unit], + expected: LabelConfig, + expectedEnd: String): U[Unit] = + visitSingleton(self, context) + + // Primitive overrides. + override def visit[A](self: Attempt[A], context: T)(p: LazyParsley[A]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: Look[A], context: T)(p: LazyParsley[A]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: NotFollowedBy[A], context: T)(p: LazyParsley[A]): U[Unit] = + visitUnary(self, context)(p) + override def visit[S](self: Put[S], context: T)(reg: Reg[S], p: LazyParsley[S]): U[Unit] = + visitUnary(self, context)(p) + override def visit[S, A](self: NewReg[S, A], context: T)(reg: Reg[S], init: LazyParsley[S], body: => LazyParsley[A]): U[A] = + visitBinary(self, context)(init, body) + override def visit[A](self: Debug[A], context: T)(p: LazyParsley[A], name: String, ascii: Boolean, break: Breakpoint): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: DebugError[A], context: T)(p: LazyParsley[A], name: String, ascii: Boolean, errBuilder: ErrorBuilder[_]): U[A] = + visitUnary(self, context)(p) + + // Selective overrides. + override def visit[A, B, C](self: Branch[A, B, C], context: T)(b: LazyParsley[Either[A, B]], p: => LazyParsley[A => C], q: => LazyParsley[B => C]): U[C] = + visitTernary(self, context)(b, p, q) + override def visit[A](self: If[A], context: T)(b: LazyParsley[Boolean], p: => LazyParsley[A], q: => LazyParsley[A]): U[A] = + visitTernary(self, context)(b, p, q) + override def visit[A](self: Filter[A], context: T)(p: LazyParsley[A], pred: A => Boolean): U[A] = + visitUnary(self, context)(p) + override def visit[A, B](self: MapFilter[A, B], context: T)(p: LazyParsley[A], f: A => Option[B]): U[B] = + visitUnary(self, context)(p) + override def visit[A](self: FilterOut[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, String]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: GuardAgainst[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, Seq[String]]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: UnexpectedWhen[A], context: T)(p: LazyParsley[A], pred: PartialFunction[A, (String, Option[String])]): U[A] = + visitUnary(self, context)(p) + + // Intrinsic overrides. + override def visit[A, B, C](self: Lift2[A, B, C], context: T)(f: (A, B) => C, p: LazyParsley[A], q: => LazyParsley[B]): U[C] = + visitBinary(self, context)(p, q) + override def visit[A, B, C, D](self: Lift3[A, B, C, D], context: T)(f: (A, B, C) => D, + p: LazyParsley[A], + q: => LazyParsley[B], + r: => LazyParsley[C]): U[D] = + visitTernary(self, context)(p, q, r) + override def visit[S, A](self: Local[S, A], context: T)(reg: Reg[S], p: LazyParsley[S], q: => LazyParsley[A]): U[A] = + visitBinary(self, context)(p, q) + + // Sequence overrides. + override def visit[A, B](self: A <*> B, context: T)(pf: LazyParsley[A => B], px: => LazyParsley[A]): U[B] = + visitBinary(self, context)(pf, px) + override def visit[A, B](self: A >>= B, context: T)(p: LazyParsley[A], f: A => LazyParsley[B]): U[B] = + visitUnary(self, context)(p) + override def visit[A](self: *>[A], context: T)(p: LazyParsley[_], _q: => LazyParsley[A]): U[A] = + visitBinary[Any, A, A](self, context)(p, _q) + override def visit[A](self: <*[A], context: T)(p: LazyParsley[A], _q: => LazyParsley[_]): U[A] = + visitBinary[A, Any, A](self, context)(p, _q) + + // Iterative overrides. + override def visit[A](self: Many[A], context: T)(p: LazyParsley[A]): U[List[A]] = + visitUnary(self, context)(p) + override def visit[A](self: SkipMany[A], context: T)(p: LazyParsley[A]): U[Unit] = + visitUnary(self, context)(p) + override def visit[A](self: ChainPost[A], context: T)(p: LazyParsley[A], _op: => LazyParsley[A => A]): U[A] = + visitBinary(self, context)(p, _op) + override def visit[A, B](self: Chainl[A, B], context: T)(init: LazyParsley[B], p: => LazyParsley[A], op: => LazyParsley[(B, A) => B]): U[B] = + visitTernary(self, context)(init, p, op) + override def visit[A, B](self: Chainr[A, B], context: T)(p: LazyParsley[A], op: => LazyParsley[(A, B) => B], wrap: A => B): U[B] = + visitBinary(self, context)(p, op) + override def visit[A, B](self: SepEndBy1[A, B], context: T)(p: LazyParsley[A], sep: => LazyParsley[B]): U[List[A]] = + visitBinary(self, context)(p, sep) + override def visit[A](self: ManyUntil[A], context: T)(body: LazyParsley[Any]): U[List[A]] = + visitUnary[Any, List[A]](self, context)(body) + override def visit(self: SkipManyUntil, context: T)(body: LazyParsley[Any]): U[Unit] = + visitUnary[Any, Unit](self, context)(body) + + // Error overrides. + override def visit[A](self: ErrorLabel[A], context: T)(p: LazyParsley[A], labels: Seq[String]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: ErrorExplain[A], context: T)(p: LazyParsley[A], reason: String): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: ErrorAmend[A], context: T)(p: LazyParsley[A], partial: Boolean): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: ErrorEntrench[A], context: T)(p: LazyParsley[A]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: ErrorDislodge[A], context: T)(n: Int, p: LazyParsley[A]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: ErrorLexical[A], context: T)(p: LazyParsley[A]): U[A] = + visitUnary(self, context)(p) + override def visit[A](self: VerifiedError[A], context: T)(p: LazyParsley[A], msggen: Either[A => Seq[String], Option[A => String]]): U[Nothing] = + visitUnary(self, context)(p) +} diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala index 7d271b0bb..317d05c27 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/ErrorEmbedding.scala @@ -4,6 +4,7 @@ package parsley.internal.deepembedding.singletons import parsley.internal.deepembedding.backend.MZero +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.errors.CaretWidth import parsley.internal.machine.instructions @@ -13,6 +14,8 @@ private [parsley] final class Empty private (val width: Int) extends Singleton[N override val pretty: String = "empty" // $COVERAGE-ON$ override val instr: instructions.Instr = new instructions.Empty(width) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Nothing] = visitor.visit(this, context)(width) } private [parsley] final class Fail(width: CaretWidth, msgs: String*) extends Singleton[Nothing] with MZero { @@ -20,6 +23,8 @@ private [parsley] final class Fail(width: CaretWidth, msgs: String*) extends Sin override def pretty: String = s"fail(${msgs.mkString(", ")})" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Fail(width, msgs: _*) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Nothing] = visitor.visit(this, context)(width, msgs) } private [parsley] final class Unexpected(msg: String, width: CaretWidth) extends Singleton[Nothing] with MZero { @@ -27,6 +32,8 @@ private [parsley] final class Unexpected(msg: String, width: CaretWidth) extends override def pretty: String = s"unexpected($msg)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Unexpected(msg, width) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Nothing] = visitor.visit(this, context)(msg, width) } private [parsley] object Empty { diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/IntrinsicEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/IntrinsicEmbedding.scala index 4e846f7c4..e44d7c13a 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/IntrinsicEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/IntrinsicEmbedding.scala @@ -6,7 +6,7 @@ package parsley.internal.deepembedding.singletons import parsley.registers.Reg import parsley.token.errors.LabelConfig -import parsley.internal.deepembedding.frontend.UsesRegister +import parsley.internal.deepembedding.frontend.{LazyParsleyIVisitor, UsesRegister} import parsley.internal.machine.instructions private [parsley] final class CharTok(private [CharTok] val c: Char, val expected: LabelConfig) extends Singleton[Char] { @@ -14,6 +14,8 @@ private [parsley] final class CharTok(private [CharTok] val c: Char, val expecte override def pretty: String = s"char($c)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.CharTok(c, expected) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Char] = visitor.visit(this, context)(c, expected) } private [parsley] final class SupplementaryCharTok(private [SupplementaryCharTok] val codepoint: Int, val expected: LabelConfig) extends Singleton[Int] { @@ -21,6 +23,8 @@ private [parsley] final class SupplementaryCharTok(private [SupplementaryCharTok override def pretty: String = s"char(${Character.toChars(codepoint).mkString})" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.SupplementaryCharTok(codepoint, expected) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context)(codepoint, expected) } private [parsley] final class StringTok(private [StringTok] val s: String, val expected: LabelConfig) extends Singleton[String] { @@ -28,6 +32,8 @@ private [parsley] final class StringTok(private [StringTok] val s: String, val e override def pretty: String = s"string($s)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.StringTok(s, expected) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[String] = visitor.visit(this, context)(s, expected) } private [parsley] object Eof extends Singleton[Unit] { @@ -35,6 +41,8 @@ private [parsley] object Eof extends Singleton[Unit] { override val pretty: String = "eof" // $COVERAGE-ON$ override val instr: instructions.Instr = instructions.Eof + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context) } private [parsley] final class UniSatisfy(private [UniSatisfy] val f: Int => Boolean, val expected: LabelConfig) extends Singleton[Int] { @@ -42,6 +50,8 @@ private [parsley] final class UniSatisfy(private [UniSatisfy] val f: Int => Bool override def pretty: String = "satisfyUnicode(?)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.UniSat(f, expected) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context)(f, expected) } private [parsley] final class Modify[S](val reg: Reg[S], f: S => S) extends Singleton[Unit] with UsesRegister { @@ -49,6 +59,8 @@ private [parsley] final class Modify[S](val reg: Reg[S], f: S => S) extends Sing override def pretty: String = s"modify($reg, ?)" // $COVERAGE-ON$ override def instr: instructions.Instr = instructions.Modify(reg.addr, f) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(reg, f) } private [deepembedding] object CharTok { diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/PrimitiveEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/PrimitiveEmbedding.scala index 15ce6f5f8..466e28059 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/PrimitiveEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/PrimitiveEmbedding.scala @@ -6,6 +6,7 @@ package parsley.internal.deepembedding.singletons import parsley.registers.Reg import parsley.token.errors.LabelConfig +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.machine.instructions private [parsley] final class Satisfy(private [Satisfy] val f: Char => Boolean, val expected: LabelConfig) extends Singleton[Char] { @@ -13,6 +14,8 @@ private [parsley] final class Satisfy(private [Satisfy] val f: Char => Boolean, override val pretty: String = "satisfy(f)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Satisfies(f, expected) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Char] = visitor.visit(this, context)(f, expected) } private [parsley] object Line extends Singleton[Int] { @@ -20,18 +23,24 @@ private [parsley] object Line extends Singleton[Int] { override val pretty: String = "line" // $COVERAGE-ON$ override val instr: instructions.Instr = instructions.Line + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context) } private [parsley] object Col extends Singleton[Int] { // $COVERAGE-OFF$ override val pretty: String = "col" // $COVERAGE-ON$ override val instr: instructions.Instr = instructions.Col + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context) } private [parsley] object Offset extends Singleton[Int] { // $COVERAGE-OFF$ override val pretty: String = "offset" // $COVERAGE-ON$ override val instr: instructions.Instr = instructions.Offset + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context) } // This should really have UsesRegister, however, if it doesn't, this has the nice effect of catching @@ -41,6 +50,8 @@ private [parsley] final class Get[S](reg: Reg[S]) extends Singleton[S] { override def pretty: String = s"get($reg)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Get(reg.addr) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[S] = visitor.visit(this, context)(reg) } private [deepembedding] object Satisfy { diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/SequenceEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/SequenceEmbedding.scala index 239b3b783..0b980ccc2 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/SequenceEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/SequenceEmbedding.scala @@ -3,6 +3,7 @@ */ package parsley.internal.deepembedding.singletons +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.machine.instructions // Core Embedding @@ -11,6 +12,8 @@ private [parsley] final class Pure[A](private [Pure] val x: A) extends Singleton override def pretty: String = s"pure($x)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Push(x) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(x) } private [parsley] final class Fresh[A](x: =>A) extends Singleton[A] { @@ -18,6 +21,8 @@ private [parsley] final class Fresh[A](x: =>A) extends Singleton[A] { override def pretty: String = s"fresh($x)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.Fresh(x) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A] = visitor.visit(this, context)(x) } private [deepembedding] object Pure { diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/Singletons.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/Singletons.scala index d765b49a7..729755f55 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/Singletons.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/Singletons.scala @@ -19,7 +19,7 @@ import parsley.internal.machine.instructions * @note due to the fact these appear in the frontend, they must not be mutable, for the same * reasons as detailed in `LazyParsley` */ -private [singletons] abstract class Singleton[A] extends LazyParsley[A] with StrictParsley[A] { +private [deepembedding] abstract class Singleton[A] extends LazyParsley[A] with StrictParsley[A] { /** The instruction that should be generated during the code generation for this combinator */ def instr: instructions.Instr diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/TokenEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/TokenEmbedding.scala index 64f2c246d..4464ab383 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/TokenEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/TokenEmbedding.scala @@ -8,6 +8,7 @@ import parsley.token.descriptions.numeric.PlusSignPresence import parsley.token.errors.ErrorConfig import parsley.internal.deepembedding.Sign.SignType +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.machine.instructions private [parsley] final class WhiteSpace(ws: Char => Boolean, desc: SpaceDesc, errConfig: ErrorConfig) @@ -15,24 +16,32 @@ private [parsley] final class WhiteSpace(ws: Char => Boolean, desc: SpaceDesc, e // $COVERAGE-OFF$ override val pretty: String = "whiteSpace" override def instr: instructions.Instr = new instructions.TokenWhiteSpace(ws, desc, errConfig) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(ws, desc, errConfig) } private [parsley] final class SkipComments(desc: SpaceDesc, errConfig: ErrorConfig) extends Singleton[Unit] { // $COVERAGE-OFF$ override val pretty: String = "skipComments" override def instr: instructions.Instr = new instructions.TokenSkipComments(desc, errConfig) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(desc, errConfig) } private [parsley] final class Comment(desc: SpaceDesc, errConfig: ErrorConfig) extends Singleton[Unit] { // $COVERAGE-OFF$ override val pretty: String = "comment" override def instr: instructions.Instr = new instructions.TokenComment(desc, errConfig) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = visitor.visit(this, context)(desc, errConfig) } private [parsley] final class Sign[A](ty: SignType, signPresence: PlusSignPresence) extends Singleton[A => A] { // $COVERAGE-OFF$ override val pretty: String = "sign" override def instr: instructions.Instr = new instructions.TokenSign(ty, signPresence) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[A => A] = visitor.visit(this, context)(ty, signPresence) } private [parsley] class NonSpecific(name: String, unexpectedIllegal: String => String, @@ -41,4 +50,8 @@ private [parsley] class NonSpecific(name: String, unexpectedIllegal: String => S override def pretty: String = "nonspecificName" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.TokenNonSpecific(name, unexpectedIllegal)(start, letter, illegal) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[String] = { + visitor.visit(this, context)(name, unexpectedIllegal, start, letter, illegal) + } } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/SymbolEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/SymbolEmbedding.scala index 77ea555db..19f8a76ec 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/SymbolEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/SymbolEmbedding.scala @@ -7,6 +7,7 @@ import parsley.token.errors.LabelConfig import parsley.token.predicate.CharPredicate import parsley.internal.collection.immutable.Trie +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.deepembedding.singletons.Singleton import parsley.internal.machine.instructions @@ -16,6 +17,10 @@ private [parsley] final class SoftKeyword(private [SoftKeyword] val specific: St override def pretty: String = s"softKeyword($specific)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.token.SoftKeyword(specific, letter, caseSensitive, expected, expectedEnd) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = { + visitor.visit(this, context)(specific, letter, caseSensitive, expected, expectedEnd) + } } private [parsley] final class SoftOperator(private [SoftOperator] val specific: String, letter: CharPredicate, ops: Trie[Unit], @@ -24,6 +29,10 @@ private [parsley] final class SoftOperator(private [SoftOperator] val specific: override def pretty: String = s"softOperator($specific)" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.token.SoftOperator(specific, letter, ops, expected, expectedEnd) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Unit] = { + visitor.visit(this, context)(specific, letter, ops, expected, expectedEnd) + } } // $COVERAGE-OFF$ diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/TextEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/TextEmbedding.scala index 01dfd89a4..f616805a2 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/TextEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/singletons/token/TextEmbedding.scala @@ -6,6 +6,7 @@ package parsley.internal.deepembedding.singletons.token import parsley.token.errors.SpecialisedFilterConfig import parsley.internal.collection.immutable.Trie +import parsley.internal.deepembedding.frontend.LazyParsleyIVisitor import parsley.internal.deepembedding.singletons.Singleton import parsley.internal.machine.instructions @@ -14,6 +15,8 @@ private [parsley] final class EscapeMapped(escTrie: Trie[Int], escs: Set[String] override def pretty: String = "escapeMapped" // $COVERAGE-ON$ override def instr: instructions.Instr = new instructions.token.EscapeMapped(escTrie, escs) + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Int] = visitor.visit(this, context)(escTrie, escs) } private [parsley] final class EscapeAtMost(n: Int, radix: Int) extends Singleton[BigInt] { @@ -21,6 +24,7 @@ private [parsley] final class EscapeAtMost(n: Int, radix: Int) extends Singleton // $COVERAGE-OFF$ override def pretty: String = "escapeAtMost" // $COVERAGE-ON$ + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[BigInt] = visitor.visit(this, context)(n, radix) } private [parsley] final class EscapeOneOfExactly(radix: Int, ns: List[Int], inexactErr: SpecialisedFilterConfig[Int]) extends Singleton[BigInt] { @@ -28,4 +32,5 @@ private [parsley] final class EscapeOneOfExactly(radix: Int, ns: List[Int], inex // $COVERAGE-OFF$ override def pretty: String = "escapeOneOfExactly" // $COVERAGE-ON$ + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[BigInt] = visitor.visit(this, context)(radix, ns, inexactErr) } diff --git a/parsley/shared/src/test/scala/parsley/internal/deepembedding/frontend/VisitorTests.scala b/parsley/shared/src/test/scala/parsley/internal/deepembedding/frontend/VisitorTests.scala new file mode 100644 index 000000000..cdab0f8dc --- /dev/null +++ b/parsley/shared/src/test/scala/parsley/internal/deepembedding/frontend/VisitorTests.scala @@ -0,0 +1,187 @@ +package parsley.internal.deepembedding.frontend + +import org.scalatest.Assertion +import org.typelevel.scalaccompat.annotation.unused +import parsley.{Parsley, ParsleyTest} +import parsley.debug.FullBreak +import parsley.errors.{DefaultErrorBuilder, ErrorBuilder, Token} +import parsley.internal.collection.immutable.Trie +import parsley.internal.deepembedding.ContOps +import parsley.internal.deepembedding.Sign +import parsley.internal.deepembedding.backend.StrictParsley +import parsley.internal.deepembedding.singletons.* +import parsley.internal.deepembedding.singletons.token.* +import parsley.internal.errors.{CaretWidth, ExpectDesc, ExpectItem, FlexibleCaret} +import parsley.internal.machine.errors.DefuncError +import parsley.registers.Reg +import parsley.token.descriptions.SpaceDesc +import parsley.token.descriptions.numeric.PlusSignPresence +import parsley.token.errors.{ErrorConfig, FilterConfig, LabelConfig, LabelWithExplainConfig, SpecialisedFilterConfig} +import parsley.token.predicate.Basic + +class VisitorTests extends ParsleyTest { + sealed trait ConstUnit[+A] + object CUnit extends ConstUnit[Nothing] + + private val testVisitor: LazyParsleyIVisitor[Unit, ConstUnit] = + new GenericLazyParsleyIVisitor[Unit, ConstUnit] { + override def visitSingleton[A](self: Singleton[A], context: Unit): ConstUnit[A] = CUnit + + override def visitUnary[A, B](self: Unary[A, B], context: Unit)(p: LazyParsley[A]): ConstUnit[B] = CUnit + + override def visitBinary[A, B, C](self: Binary[A, B, C], context: Unit)(l: LazyParsley[A], r: => LazyParsley[B]): ConstUnit[C] = CUnit + + override def visitTernary[A, B, C, D](self: Ternary[A, B, C, D], context: Unit)(f: LazyParsley[A], + s: => LazyParsley[B], + t: => LazyParsley[C]): ConstUnit[D] = CUnit + + override def visit[A](self: <|>[A])(context: Unit, p: LazyParsley[A], q: LazyParsley[A]): ConstUnit[A] = CUnit + + override def visit[A](self: ChainPre[A], context: Unit)(p: LazyParsley[A], op: => LazyParsley[A => A]): ConstUnit[A] = CUnit + } + + private def dontExecute(): Nothing = + fail("Should not execute.") + + private val dummyParser: LazyParsley[Nothing] = + new LazyParsley[Nothing] { + override protected def findLetsAux[M[_, _] : ContOps, R](seen: Set[LazyParsley[_]])(implicit state: LetFinderState): M[R, Unit] = + dontExecute() + + override protected def preprocess[M[_, _] : ContOps, R, A_ >: Nothing](implicit lets: LetMap, recs: RecMap): M[R, StrictParsley[A_]] = + dontExecute() + + override def visit[T, U[+_]](visitor: LazyParsleyIVisitor[T, U], context: T): U[Nothing] = + dontExecute() + } + + + private val dummyLabelConfig: LabelConfig = new LabelConfig { + override private[parsley] def orElse(other: LabelConfig): LabelConfig = + dontExecute() + + override private[parsley] def orElse(other: LabelWithExplainConfig): LabelWithExplainConfig = + dontExecute() + + override private[parsley] def asExpectDescs: Iterable[ExpectDesc] = + dontExecute() + + override private[parsley] def asExpectDescs(otherwise: String): Iterable[ExpectDesc] = + dontExecute() + + override private[parsley] def asExpectItems(raw: String): Iterable[ExpectItem] = + dontExecute() + + override private[parsley] def apply[A](p: Parsley[A]): Parsley[A] = + dontExecute() + } + + private val dummyCaretWidth: CaretWidth = new FlexibleCaret(0) + + private val dummyErrorBuilder: ErrorBuilder[String] = new DefaultErrorBuilder { + override def unexpectedToken(cs: Iterable[Char], amountOfInputParserWanted: Int, lexicalError: Boolean): Token = + dontExecute() + } + + private def dummySFConfig[A](): SpecialisedFilterConfig[A] = new SpecialisedFilterConfig[A] { + override private[parsley] def filter(p: Parsley[A])(f: A => Boolean): Parsley[A] = + dontExecute() + + override private[parsley] def mkError(offset: Int, line: Int, col: Int, caretWidth: Int, x: A): DefuncError = + dontExecute() + + override private[parsley] def injectLeft[B]: FilterConfig[Either[A, B]] = + dontExecute() + + override private[parsley] def injectRight[B]: FilterConfig[Either[B, A]] = + dontExecute() + + override private[parsley] def injectSnd[B]: FilterConfig[(B, A)] = + dontExecute() + } + + implicit private class TestVisitorOps[A](p: LazyParsley[A]) { + def testV: Assertion = p.visit(testVisitor, ()) shouldBe CUnit + } + + private def dummyRegister(): Reg[Unit] = + Reg.make[Unit] + + def dontEval: Nothing = fail("Laziness was not maintained.") + val crash = new PartialFunction[Any, Nothing] { + def apply(@unused x: Any) = dontEval + def isDefinedAt(x: Any): Boolean = false + } + def crash(@unused x: Any, @unused y: Any): Nothing = dontEval + def crash(@unused x: Any, @unused y: Any, @unused z: Any): Nothing = dontEval + + they should "maintain laziness of the parsers visited" in { + new NewReg(dummyRegister(), dummyParser, dontEval).testV + new Branch(dummyParser, dontEval, dontEval).testV + new If(dummyParser, dontEval, dontEval).testV + new Lift2[Nothing, Nothing, Nothing](crash, dummyParser, dontEval).testV + new Lift3[Nothing, Nothing, Nothing, Nothing](crash, dummyParser, dontEval, dontEval).testV + new Local(dummyRegister(), dummyParser, dontEval).testV + new <*>(dummyParser, dontEval).testV + new *>(dummyParser, dontEval).testV + new <*(dummyParser, dontEval).testV + new ChainPost(dummyParser, dontEval).testV + new Chainl(dummyParser, dontEval, dontEval).testV + new Chainr[Nothing, Nothing](dummyParser, dontEval, crash).testV + new SepEndBy1(dummyParser, dontEval).testV + } + + they should "all return the constant unit object from the test visitor" in { + // The lazy parsers have been tested for this in the laziness preservation test. + new Pure(()).testV + new Fresh(()).testV + new Satisfy(_ => true, dummyLabelConfig).testV + Line.testV + Col.testV + Offset.testV + new Get(dummyRegister()).testV + new WhiteSpace(_ => true, SpaceDesc.plain, new ErrorConfig).testV + new SkipComments(SpaceDesc.plain, new ErrorConfig).testV + new Comment(SpaceDesc.plain, new ErrorConfig).testV + new Sign(Sign.CombinedType, PlusSignPresence.Optional).testV + new NonSpecific("foo", identity[String], _ => true, _ => true, _ => false).testV + new CharTok(' ', dummyLabelConfig).testV + new SupplementaryCharTok(0, dummyLabelConfig).testV + new StringTok("bar", dummyLabelConfig).testV + Eof.testV + new UniSatisfy(_ => true, dummyLabelConfig).testV + new Modify(dummyRegister(), identity[Unit]).testV + Parsley.empty.internal.testV + new Fail(dummyCaretWidth).testV + new Unexpected("qux", dummyCaretWidth).testV + new EscapeMapped(Trie.empty[Int], Set("quux")).testV + new EscapeAtMost(0, 0).testV + new EscapeOneOfExactly(0, Nil, dummySFConfig[Int]()).testV + new SoftKeyword("corge", Basic(_ => true), false, dummyLabelConfig, "grault").testV + new SoftOperator("garply", Basic(_ => true), Trie.empty[Unit], dummyLabelConfig, "waldo").testV + new Attempt(dummyParser).testV + new Look(dummyParser).testV + new NotFollowedBy(dummyParser).testV + new Put(dummyRegister(), dummyParser).testV + new Debug(dummyParser, "fred", false, FullBreak).testV + new DebugError(dummyParser, "plugh", false, dummyErrorBuilder).testV + new Filter[Nothing](dummyParser, crash).testV + new MapFilter[Nothing, Nothing](dummyParser, crash).testV + new FilterOut[Nothing](dummyParser, crash).testV + new GuardAgainst[Nothing](dummyParser, crash).testV + new UnexpectedWhen[Nothing](dummyParser, crash) + new <|>(dummyParser, dummyParser).testV + new >>=[Nothing, Nothing](dummyParser, crash).testV + new Many(dummyParser).testV + new SkipMany(dummyParser).testV + new ManyUntil(dummyParser).testV + new SkipManyUntil(dummyParser).testV + new ErrorLabel(dummyParser, Seq("bazola")).testV + new ErrorExplain(dummyParser, "ztesch").testV + new ErrorAmend(dummyParser, false).testV + new ErrorEntrench(dummyParser).testV + new ErrorDislodge(0, dummyParser).testV + new ErrorLexical(dummyParser).testV + new VerifiedError[Nothing](dummyParser, Left(crash)) + } +}