Skip to content

Commit

Permalink
Optimised softOperator and added Trie implementation (#159)
Browse files Browse the repository at this point in the history
* Broke out soft operator and shared code: we need a Trie to continue

* Added Trie to complete SoftOperator implementation

* Fixed issues

* Removed Radix

* Removed old instructions in comment

* Improved completeness of Trie testing, added sonatype release resolver
  • Loading branch information
j-mie6 authored Jan 30, 2023
1 parent b55d4be commit 8d9d48d
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 226 deletions.
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 = {
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 t@token.SoftKeyword(s) if t.caseSensitive => Some((s.head, Some(ExpectDesc(s)), s.codePointCount(0, s.length), backtracks))
case t@token.SoftKeyword(s) if t.caseSensitive => Some((s.head, t.expected.asExpectDesc(s), s.codePointCount(0, s.length), backtracks))
case t@token.SoftOperator(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

0 comments on commit 8d9d48d

Please sign in to comment.