Skip to content

Commit

Permalink
partial backport for #227
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mie6 committed Apr 8, 2024
1 parent d302775 commit 4540959
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import parsley.token.errors.LabelConfig
import parsley.token.predicate

import parsley.internal.collection.immutable.Trie
import parsley.internal.errors.ExpectDesc
import parsley.internal.errors.{ExpectDesc, ExpectItem}
import parsley.internal.machine.Context
import parsley.internal.machine.XAssert._
import parsley.internal.machine.instructions.Instr

private [token] abstract class Specific extends Instr {
protected val specific: String
protected val caseSensitive: Boolean
protected val expected: Iterable[ExpectDesc]
protected val expected: Iterable[ExpectItem]
private [this] final val strsz = specific.length
private [this] final val numCodePoints = specific.codePointCount(0, strsz)

Expand Down Expand Up @@ -65,12 +65,13 @@ private [token] abstract class Specific extends Instr {
}

private [internal] final class SoftKeyword(protected val specific: String, letter: CharPredicate, protected val caseSensitive: Boolean,
protected val expected: Iterable[ExpectDesc], expectedEnd: Iterable[ExpectDesc]) extends Specific {
protected val expected: Iterable[ExpectItem], protected val reason: Option[String],
expectedEnd: Iterable[ExpectDesc]) extends Specific {
def this(specific: String, letter: predicate.CharPredicate, caseSensitive: Boolean, expected: LabelConfig, expectedEnd: String) = {
this(if (caseSensitive) specific else specific.toLowerCase,
letter.asInternalPredicate,
caseSensitive,
expected.asExpectDescs, Some(new ExpectDesc(expectedEnd)))
expected.asExpectItems(specific), expected.asReason, Some(new ExpectDesc(expectedEnd)))
}

protected def postprocess(ctx: Context): Unit = {
Expand All @@ -90,9 +91,10 @@ private [internal] final class SoftKeyword(protected val specific: String, lette
}

private [internal] final class SoftOperator(protected val specific: String, letter: CharPredicate, ops: Trie[Unit],
protected val expected: Iterable[ExpectDesc], expectedEnd: Iterable[ExpectDesc]) extends Specific {
protected val expected: Iterable[ExpectItem], protected val reason: Option[String],
expectedEnd: Iterable[ExpectDesc]) extends Specific {
def this(specific: String, letter: predicate.CharPredicate, ops: Trie[Unit], expected: LabelConfig, expectedEnd: String) = {
this(specific, letter.asInternalPredicate, ops, expected.asExpectDescs, Some(new ExpectDesc(expectedEnd)))
this(specific, letter.asInternalPredicate, ops, expected.asExpectItems(specific), expected.asReason, Some(new ExpectDesc(expectedEnd)))
}
protected val caseSensitive = true
private val ends = ops.suffixes(specific)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ private [parsley] trait LabelOps {
private [parsley] final def asExpectItems(raw: Char): Iterable[ExpectItem] = asExpectItems(s"$raw")
}

// TODO: reason extraction, maybe tie into errors?
private [parsley] trait ExplainOps

private [parsley] sealed trait ExplainOps {
private [parsley] def asReason: Option[String]
}

// Constraining Types
/** This type can be used to configure ''both'' errors that make labels and those that make reasons.
Expand All @@ -52,6 +52,10 @@ trait LabelConfig extends LabelWithExplainConfig {
*/
trait ExplainConfig extends LabelWithExplainConfig

private [parsley] sealed trait Labeller {
private [parsley] def config(name: String): LabelConfig
}

private [errors] final class Label private[errors] (val labels: Seq[String]) extends LabelConfig {
require(labels.forall(_.nonEmpty), "labels cannot be empty strings")
private [parsley] final override def apply[A](p: Parsley[A]) = p.labels(labels: _*)
Expand All @@ -64,16 +68,18 @@ private [errors] final class Label private[errors] (val labels: Seq[String]) ext
case _ => this
}
private [parsley] final override def orElse(config: LabelConfig) = this
private [parsley] final override def asReason: Option[String] = None
}
/** This object has a factory for configurations producing labels: labels may not be empty.
* @since 4.1.0
* @group labels
*/
object Label {
object Label extends Labeller {
def apply(label: String): LabelConfig = if (label.isEmpty) Hidden else new Label(Seq(label))
def apply(label1: String, label2: String, labels: String*): LabelConfig = new Label(label1 +: label2 +: labels)
// this is required internally, will go in parsley 5
private [parsley] def apply(labels: String*) = new Label(labels)
private [parsley] final def config(name: String) = Label(name)
}

/** This object configures labels by stating that it must be hidden.
Expand All @@ -87,6 +93,7 @@ object Hidden extends LabelConfig {
private [parsley] final override def asExpectItems(@unused raw: String) = asExpectDescs
private [parsley] final override def orElse(config: LabelWithExplainConfig) = this
private [parsley] final override def orElse(config: LabelConfig) = this
private [parsley] final override def asReason: Option[String] = None
}

private [errors] final class Reason private[errors] (val reason: String) extends ExplainConfig {
Expand All @@ -100,6 +107,7 @@ private [errors] final class Reason private[errors] (val reason: String) extend
case lr: LabelAndReason => new LabelAndReason(lr.labels, reason)
case _ => this
}
private [parsley] final override def asReason: Option[String] = Some(reason)
}
/** This object has a factory for configurations producing reasons: if the empty string is provided, this equivalent to [[NotConfigured `NotConfigured`]].
* @since 4.1.0
Expand All @@ -117,6 +125,7 @@ private [errors] final class LabelAndReason private[errors] (val labels: Seq[Str
private [parsley] final override def asExpectDescs(@unused otherwise: String) = asExpectDescs
private [parsley] final override def asExpectItems(@unused raw: String) = asExpectDescs
private [parsley] final override def orElse(config: LabelWithExplainConfig) = this
private [parsley] final override def asReason: Option[String] = Some(reason)
}
/** This object has a factory for configurations producing labels and reasons: if the empty label is provided, this equivalent to [[Hidden `Hidden`]] with no
* reason; if the empty reason is provided this is equivalent to [[Label$ `Label`]].
Expand All @@ -135,11 +144,13 @@ object LabelAndReason {
* @since 4.1.0
* @group labels
*/
object NotConfigured extends LabelConfig with ExplainConfig with LabelWithExplainConfig {
object NotConfigured extends LabelConfig with ExplainConfig with LabelWithExplainConfig with Labeller {
private [parsley] final override def apply[A](p: Parsley[A]) = p
private [parsley] final override def asExpectDescs = None
private [parsley] final override def asExpectDescs(otherwise: String) = Some(new ExpectDesc(otherwise))
private [parsley] final override def asExpectItems(raw: String) = Some(new ExpectRaw(raw))
private [parsley] final override def orElse(config: LabelWithExplainConfig) = config
private [parsley] final override def orElse(config: LabelConfig) = config
private [parsley] final override def asReason: Option[String] = None
private [parsley] final def config(name: String) = this
}
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,11 @@ class ErrorConfig {
* @group symbol
*/
def labelSymbolOperator(symbol: String): LabelConfig = Label(symbol)
// To unify, or not to unify
private [parsley] def defaultSymbolKeyword: Labeller = Label
private [parsley] def defaultSymbolOperator: Labeller = Label
// Other?
private [parsley] def defaultSymbolPunctuation: Labeller = NotConfigured
/** How the required end of a given keyword should be specified in an error.
* @since 4.1.0
* @note defaults to "end of symbol"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package parsley.token.symbol
import parsley.Parsley, Parsley.atomic
import parsley.character.{char, string}
import parsley.token.descriptions.{NameDesc, SymbolDesc}
import parsley.token.errors.ErrorConfig
import parsley.token.errors.{ErrorConfig, NotConfigured}

import parsley.internal.deepembedding.singletons.token

Expand All @@ -22,22 +22,26 @@ private [token] class ConcreteSymbol(nameDesc: NameDesc, symbolDesc: SymbolDesc,

override def apply(name: String): Parsley[Unit] = {
require(name.nonEmpty, "Symbols may not be empty strings")
lazy val punctuationLabel = err.labelSymbolPunctuation.get(name).map {
case None => parsley.token.errors.Hidden
case Some(l) => parsley.token.errors.Label(l)
}.getOrElse(NotConfigured).orElse(err.defaultSymbolPunctuation.config(name))
if (symbolDesc.hardKeywords(name)) softKeyword(name)
else if (symbolDesc.hardOperators(name)) softOperator(name)
else atomic(string(name)).void
else punctuationLabel(atomic(string(name)).void)
}

override def apply(name: Char): Parsley[Unit] = char(name).void

override def softKeyword(name: String): Parsley[Unit] = {
require(name.nonEmpty, "Keywords may not be empty strings")
new Parsley(new token.SoftKeyword(name, nameDesc.identifierLetter, symbolDesc.caseSensitive,
err.labelSymbolKeyword(name), err.labelSymbolEndOfKeyword(name)))
err.labelSymbolKeyword(name).orElse(err.defaultSymbolKeyword.config(name)), err.labelSymbolEndOfKeyword(name)))
}

override def softOperator(name: String): Parsley[Unit] = {
require(name.nonEmpty, "Operators may not be empty strings")
new Parsley(new token.SoftOperator(name, nameDesc.operatorLetter, symbolDesc.hardOperatorsTrie,
err.labelSymbolOperator(name), err.labelSymbolEndOfOperator(name)))
err.labelSymbolOperator(name).orElse(err.defaultSymbolOperator.config(name)), err.labelSymbolEndOfOperator(name)))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ class VisitorTests extends ParsleyTest {

override private[parsley] def apply[A](p: Parsley[A]): Parsley[A] =
dontExecute()

override private[parsley] def asReason: Option[String] = None
}

private val dummyCaretWidth: CaretWidth = new FlexibleCaret(0)
Expand Down

0 comments on commit 4540959

Please sign in to comment.