Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimised softOperator and added Trie implementation #159

Merged
merged 6 commits into from
Jan 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ lazy val parsley = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(
name := projectName,

resolvers ++= Opts.resolver.sonatypeOssReleases, // Will speed up MiMA during fast back-to-back releases
libraryDependencies ++= Seq(
"org.scalatest" %%% "scalatest" % "3.2.14" % Test,
"org.scalatest" %%% "scalatest" % "3.2.15" % Test,
"org.scalatestplus" %%% "scalacheck-1-17" % "3.2.15.0" % Test,
),

Test / testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-oI"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/* SPDX-FileCopyrightText: © 2023 Parsley Contributors <https://github.com/j-mie6/Parsley/graphs/contributors>
* SPDX-License-Identifier: BSD-3-Clause
*/
package parsley.internal.collection.immutable

import scala.annotation.tailrec
import scala.collection.immutable.IntMap

private [parsley] class Trie(private val present: Boolean, children: IntMap[Trie]) {
def contains(key: String): Boolean = suffixes(key).present/*contains(key, 0, key.length)
@tailrec private def contains(key: String, idx: Int, sz: Int): Boolean = {
if (idx == sz) present
else childAt(key, idx) match {
case None => false
case Some(t) => t.contains(key, idx + 1, sz)
}
}*/

def isEmpty: Boolean = this eq Trie.empty
def nonEmpty: Boolean = !isEmpty

def suffixes(key: Char): Trie = children.getOrElse(key.toInt, Trie.empty)
def suffixes(key: String): Trie = suffixes(key, 0, key.length)
@tailrec private def suffixes(key: String, idx: Int, sz: Int): Trie = {
if (idx == sz) this
else childAt(key, idx) match {
case None => Trie.empty
case Some(t) => t.suffixes(key, idx + 1, sz)
}
}

def incl(key: String): Trie = incl(key, 0, key.length)
private def incl(key: String, idx: Int, sz: Int): Trie = {
j-mie6 marked this conversation as resolved.
Show resolved Hide resolved
if (idx == sz && present) this
else if (idx == sz) new Trie(present = true, children)
else childAt(key, idx) match {
case None => new Trie(present, children.updated(key.charAt(idx).toInt, Trie.empty.incl(key, idx + 1, sz)))
case Some(t) =>
val newT = t.incl(key, idx + 1, sz)
if (t eq newT) this
else new Trie(present, children.updated(key.charAt(idx).toInt, newT))
}
}

private def childAt(key: String, idx: Int) = children.get(key.charAt(idx).toInt)
}
private [parsley] object Trie {
val empty = new Trie(present = false, IntMap.empty)

def apply(strs: Iterable[String]): Trie = strs.foldLeft(empty)(_.incl(_))
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import parsley.XAssert._
import parsley.internal.collection.mutable.SinglyLinkedList, SinglyLinkedList.LinkedListIterator
import parsley.internal.deepembedding.ContOps, ContOps.{result, suspend, ContAdapter}
import parsley.internal.deepembedding.singletons._
import parsley.internal.errors.{ExpectDesc, ExpectItem}
import parsley.internal.errors.ExpectItem
import parsley.internal.machine.instructions

// scalastyle:off underscore.import
Expand Down Expand Up @@ -233,7 +233,8 @@ private [backend] object Choice {
//case op@MaxOp(o) => Some((o.head, Some(Desc(o)), o.size, backtracks))
//case _: StringLiteral | RawStringLiteral => Some(('"', Some(Desc("string")), 1, backtracks))
// TODO: This can be done for case insensitive things too, but with duplicated branching
case [email protected](s) if t.caseSensitive => Some((s.head, Some(ExpectDesc(s)), s.codePointCount(0, s.length), backtracks))
case [email protected](s) if t.caseSensitive => Some((s.head, t.expected.asExpectDesc(s), s.codePointCount(0, s.length), backtracks))
case [email protected](s) => Some((s.head, t.expected.asExpectDesc(s), s.codePointCount(0, s.length), backtracks))
case Attempt(t) => tablable(t, backtracks = true)
case (_: Pure[_]) <*> t => tablable(t, backtracks)
case Lift2(_, t, _) => tablable(t, backtracks)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,3 @@ private [parsley] class NonSpecific(name: String, unexpectedIllegal: String => S
// $COVERAGE-ON$
override def instr: instructions.Instr = new instructions.TokenNonSpecific(name, unexpectedIllegal)(start, letter, illegal)
}

/*
private [parsley] final class MaxOp(private [MaxOp] val operator: String, ops: Set[String]) extends Singleton[Unit] {
// $COVERAGE-OFF$
override def pretty: String = s"maxOp($operator)"
// $COVERAGE-ON$
override def instr: instructions.Instr = new instructions.TokenMaxOp(operator, ops)
}
*/
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,31 @@ package parsley.internal.deepembedding.singletons.token
import parsley.token.errors.LabelConfig
import parsley.token.predicate.CharPredicate

import parsley.internal.collection.immutable.Trie
import parsley.internal.deepembedding.singletons.Singleton
import parsley.internal.machine.instructions

private [parsley] final class SoftKeyword(private [SoftKeyword] val specific: String, letter: CharPredicate, val caseSensitive: Boolean,
expected: LabelConfig, expectedEnd: String) extends Singleton[Unit] {
val expected: LabelConfig, expectedEnd: String) extends Singleton[Unit] {
// $COVERAGE-OFF$
override def pretty: String = s"softKeyword($specific)"
// $COVERAGE-ON$
override def instr: instructions.Instr = new instructions.token.SoftKeyword(specific, letter, caseSensitive, expected, expectedEnd)
}

/*
private [parsley] final class MaxOp(private [MaxOp] val operator: String, ops: Set[String]) extends Singleton[Unit] {
private [parsley] final class SoftOperator(private [SoftOperator] val specific: String, letter: CharPredicate, ops: Trie,
val expected: LabelConfig, expectedEnd: String) extends Singleton[Unit] {
// $COVERAGE-OFF$
override def pretty: String = s"maxOp($operator)"
override def pretty: String = s"softOperator($specific)"
// $COVERAGE-ON$
override def instr: instructions.Instr = new instructions.TokenMaxOp(operator, ops)
override def instr: instructions.Instr = new instructions.token.SoftOperator(specific, letter, ops, expected, expectedEnd)
}
*/

// $COVERAGE-OFF$
private [deepembedding] object SoftKeyword {
def unapply(self: SoftKeyword): Some[String] = Some(self.specific)
}
private [deepembedding] object SoftOperator {
def unapply(self: SoftOperator): Some[String] = Some(self.specific)
}
// $COVERAGE-ON$
Original file line number Diff line number Diff line change
Expand Up @@ -241,61 +241,3 @@ private [internal] final class TokenNonSpecific(name: String, unexpectedIllegal:
override def toString: String = s"TokenNonSpecific($name)"
// $COVERAGE-ON$
}

/*
private [instructions] abstract class TokenSpecificAllowTrailing(
specific: String, expected: Option[ExpectDesc], protected final val expectedEnd: Option[ExpectDesc], caseSensitive: Boolean) extends Instr {
def this(specific: String, expected: LabelConfig, expectedEnd: String, caseSensitive: Boolean) = {
this(if (caseSensitive) specific else specific.toLowerCase, expected.asExpectDesc, Some(new ExpectDesc(expectedEnd)), caseSensitive)
}
private [this] final val strsz = specific.length
private [this] final val numCodePoints = specific.codePointCount(0, strsz)
protected def postprocess(ctx: Context, i: Int): Unit

val readCharCaseHandled = {
if (caseSensitive) (ctx: Context, i: Int) => ctx.input.charAt(i)
else (ctx: Context, i: Int) => ctx.input.charAt(i).toLower
}

@tailrec final private def readSpecific(ctx: Context, i: Int, j: Int): Unit = {
if (j < strsz && readCharCaseHandled(ctx, i) == specific.charAt(j)) readSpecific(ctx, i + 1, j + 1)
else if (j < strsz) ctx.expectedFail(expected, numCodePoints)
else {
ctx.saveState()
ctx.fastUncheckedConsumeChars(strsz)
postprocess(ctx, i)
}
}

final override def apply(ctx: Context): Unit = {
if (ctx.inputsz >= ctx.offset + strsz) readSpecific(ctx, ctx.offset, 0)
else ctx.expectedFail(expected, numCodePoints)
}
}

private [internal] final class TokenMaxOp(operator: String, _ops: Set[String]) extends TokenSpecificAllowTrailing(operator, true) {
private val ops = Radix.makeSet(_ops.collect {
case op if op.length > operator.length && op.startsWith(operator) => op.substring(operator.length)
})

@tailrec private def go(ctx: Context, i: Int, ops: RadixSet): Unit = {
lazy val ops_ = ops.suffixes(ctx.input.charAt(i))
val possibleOpsRemain = i < ctx.inputsz && ops.nonEmpty
if (possibleOpsRemain && ops_.contains("")) {
ctx.expectedFail(expectedEnd) //This should only report a single token
ctx.restoreState()
}
else if (possibleOpsRemain) go(ctx, i + 1, ops_)
else {
ctx.states = ctx.states.tail
ctx.pushAndContinue(())
}
}

override def postprocess(ctx: Context, i: Int): Unit = go(ctx, i, ops)

// $COVERAGE-OFF$
override def toString: String = s"TokenMaxOp(${operator})"
// $COVERAGE-ON$
}
*/
Loading