From 0713728e0f98a4001aef9877a3b04c4762d64854 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Sun, 5 Jan 2025 18:54:51 +0000 Subject: [PATCH 1/4] chore: README bumps --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4076a728..2a0689e8b 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,18 @@ Parsley is distributed on Maven Central, and can be added to your project via: ```scala // SBT -libraryDependencies += "com.github.j-mie6" %% "parsley" % "4.5.2" +libraryDependencies += "com.github.j-mie6" %% "parsley" % "4.5.3" // scala-cli ---dependency com.github.j-mie6::parsley:4.5.2 +--dependency com.github.j-mie6::parsley:4.5.3 // or in file -//> using dep com.github.j-mie6::parsley:4.5.2 +//> using dep com.github.j-mie6::parsley:4.5.3 // mill -ivy"com.github.j-mie6::parsley:4.5.2" +ivy"com.github.j-mie6::parsley:4.5.3" ``` -Documentation can be found [**here**](https://javadoc.io/doc/com.github.j-mie6/parsley_2.13/latest/index.html) +Documentation can be found [**here**](https://javadoc.io/doc/com.github.j-mie6/parsley-docs_2.13/latest/index.html) If you're a `cats` user, you may also be interested in using [`parsley-cats`](https://github.com/j-mie6/parsley-cats)Cats friendly to augment `parsley` with instances for various `cats` typeclasses: From ada2b39201aa75353f897aef5c37e5db379cd2f9 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Sun, 5 Jan 2025 18:59:04 +0000 Subject: [PATCH 2/4] chore: updated sbt-tl --- .github/workflows/ci.yml | 49 ++++++++++++++++++++-------------------- build.sbt | 1 + project/build.properties | 2 +- project/plugins.sbt | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 155fe7fe9..99c9f049d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,10 +24,10 @@ concurrency: jobs: build: - name: Build and Test + name: Test strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] scala: [2.13, 2.12, 3] java: [temurin@8, temurin@11, temurin@17] project: [rootJS, rootJVM, rootNative] @@ -51,15 +51,14 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: - - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt - - name: Checkout current branch (full) uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Setup sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@8) id: setup-java-temurin-8 if: matrix.java == 'temurin@8' @@ -103,7 +102,7 @@ jobs: run: sbt githubWorkflowCheck - name: Check headers - if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' headerCheckAll - name: scalaJSLink @@ -118,11 +117,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-latest' + if: matrix.java == 'temurin@8' && matrix.os == 'ubuntu-22.04' run: sbt 'project ${{ matrix.project }}' '++ ${{ matrix.scala }}' doc - name: Make target directories @@ -146,19 +145,18 @@ jobs: if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/master') strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt - - name: Checkout current branch (full) uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Setup sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@8) id: setup-java-temurin-8 if: matrix.java == 'temurin@8' @@ -317,19 +315,18 @@ jobs: if: github.event.repository.fork == false && github.event_name != 'pull_request' strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] java: [temurin@8] runs-on: ${{ matrix.os }} steps: - - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt - - name: Checkout current branch (full) uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Setup sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@8) id: setup-java-temurin-8 if: matrix.java == 'temurin@8' @@ -380,7 +377,7 @@ jobs: if: github.ref == 'refs/heads/master' || (github.event_name == 'pull_request' && github.base_ref == 'master') strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] scala: [2.13] java: [temurin@11] runs-on: ${{ matrix.os }} @@ -388,6 +385,9 @@ jobs: - name: Checkout current branch (fast) uses: actions/checkout@v4 + - name: Setup sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@11) id: setup-java-temurin-11 if: matrix.java == 'temurin@11' @@ -417,19 +417,18 @@ jobs: name: Generate Site strategy: matrix: - os: [ubuntu-latest] + os: [ubuntu-22.04] java: [temurin@11] runs-on: ${{ matrix.os }} steps: - - name: Install sbt - if: contains(runner.os, 'macos') - run: brew install sbt - - name: Checkout current branch (full) uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Setup sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@8) id: setup-java-temurin-8 if: matrix.java == 'temurin@8' diff --git a/build.sbt b/build.sbt index 47daa78be..701fcc4a9 100644 --- a/build.sbt +++ b/build.sbt @@ -126,6 +126,7 @@ def testCoverageJob(cacheSteps: List[WorkflowStep]) = WorkflowJob( cond = Some(s"github.ref == 'refs/heads/$mainBranch' || (github.event_name == 'pull_request' && github.base_ref == '$mainBranch')"), steps = WorkflowStep.Checkout :: + WorkflowStep.SetupSbt :: WorkflowStep.SetupJava(List(JavaLTS)) ::: cacheSteps ::: List( WorkflowStep.Sbt(name = Some("Generate coverage report"), commands = List("coverage", "parsley / test", "parsleyDebug / test", "coverageReport")), diff --git a/project/build.properties b/project/build.properties index abbbce5da..04267b14a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.9.8 +sbt.version=1.9.9 diff --git a/project/plugins.sbt b/project/plugins.sbt index 3e0bd53e8..0934b53c7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -val sbtTypelevelVersion = "0.7.3" +val sbtTypelevelVersion = "0.7.5" val circeVersion = "0.14.10" resolvers ++= Opts.resolver.sonatypeOssSnapshots From 2c2410d0bde1a2c23db7a9c781a72f6e501c19d3 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Sun, 5 Jan 2025 19:37:40 +0000 Subject: [PATCH 3/4] feat: backport implementation --- .../backend/SequenceEmbedding.scala | 5 +- .../deepembedding/backend/StrictParsley.scala | 82 ++++--------------- .../deepembedding/frontend/LazyParsley.scala | 11 +-- .../machine/instructions/CoreInstrs.scala | 6 +- .../instructions/PrimitiveInstrs.scala | 67 +-------------- .../src/main/scala/parsley/registers.scala | 9 +- .../src/test/scala/parsley/CoreTests.scala | 6 +- 7 files changed, 34 insertions(+), 152 deletions(-) diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SequenceEmbedding.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SequenceEmbedding.scala index 4cda3f864..f7466d696 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SequenceEmbedding.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/SequenceEmbedding.scala @@ -93,10 +93,9 @@ private [deepembedding] final class >>=[A, B](val p: StrictParsley[A], private [ } override def codeGen[M[_, +_]: ContOps, R](producesResults: Boolean)(implicit instrs: InstrBuffer, state: CodeGenState): M[R, Unit] = { suspend(p.codeGen[M, R](producesResults = true)) |> { - instrs += instructions.DynCall[A] { x => + instrs += instructions.DynCall[A] { (x, refsSz) => val p = f(x) - // FIXME: suppress results within p, then can remove pop - p.demandCalleeSave(state.numRegs) + p.setMinReferenceAllocation(refsSz) if (implicitly[ContOps[M]].isStackSafe) p.overflows() p.instrs } diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala index 3ed341c8d..ab2c83d15 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala @@ -16,7 +16,7 @@ import parsley.internal.deepembedding.ContOps, ContOps.{perform, ContAdapter} import parsley.internal.machine.instructions, instructions.{Instr, Label} import StrictParsley.* -import org.typelevel.scalaccompat.annotation.{nowarn, nowarn3} +import org.typelevel.scalaccompat.annotation.nowarn3 /** This is the root type of the parsley "backend": it represents a combinator tree * where the join-points in the tree (recursive or otherwise) have been factored into @@ -44,14 +44,15 @@ private [deepembedding] trait StrictParsley[+A] { * @param state the code generator state * @return the final array of instructions for this parser */ - final private [deepembedding] def generateInstructions[M[_, +_]: ContOps](numRegsUsedByParent: Int, usedRefs: Set[Ref[_]], + final private [deepembedding] def generateInstructions[M[_, +_]: ContOps](minRef: Int, usedRefs: Set[Ref[_]], bodyMap: Map[Let[_], StrictParsley[_]]) (implicit state: CodeGenState): Array[Instr] = { implicit val instrs: InstrBuffer = newInstrBuffer perform { - generateCalleeSave[M, Array[Instr]](numRegsUsedByParent, this.codeGen(producesResults = true), usedRefs) |> { - // When `numRegsUsedByParent` is -1 this is top level, otherwise it is a flatMap - instrs += (if (numRegsUsedByParent >= 0) instructions.Return else instructions.Halt) + allocateAndExpandRefs(minRef, usedRefs) + this.codeGen[M, Array[Instr]](producesResults = true) |> { + // When `minRef` is -1 this is top level, otherwise it is a flatMap + instrs += (if (minRef >= 0) instructions.Return else instructions.Halt) val letRets = finaliseLets(bodyMap) generateHandlers(state.handlers) finaliseInstrs(instrs, state.nlabels, letRets) @@ -98,51 +99,6 @@ private [deepembedding] object StrictParsley { /** Make a fresh instruction buffer */ private def newInstrBuffer: InstrBuffer = new ResizableArray() - /** Given a set of in-use registers, this function will allocate those that are currented - * unallocated, giving them addresses not currently in use by the allocated registers - * - * @param unallocatedRegs the set of registers that need allocating - * @param regs the set of all registers used by a specific parser - * @return the list of slots that have been freshly allocated to - */ - private def allocateRegisters(unallocatedRegs: Set[Ref[_]], regs: Set[Ref[_]]): List[Int] = { - // Global registers cannot occupy the same slot as another global register - // In a flatMap, that means a newly discovered global register must be allocated to a new slot: this may resize the register pool - assert(unallocatedRegs == regs.filterNot(_.allocated)) - if (unallocatedRegs.nonEmpty) { - val usedSlots = regs.collect { - case reg if reg.allocated => reg.addr - }: @nowarn - val freeSlots = (0 until regs.size).filterNot(usedSlots) - applyAllocation(unallocatedRegs, freeSlots) - } - else Nil - } - - /** Given a set of unallocated registers and a supply of unoccupied slots, allocates each - * register to one of the slots. - * - * @param regs the set of registers that require allocation - * @param freeSlots the supply of slots that are currently not in-use - * @return the slots that were used for allocation - */ - private def applyAllocation(refs: Set[Ref[_]] @nowarn3, freeSlots: Iterable[Int]): List[Int] = { - val allocatedSlots = mutable.ListBuffer.empty[Int] - // TODO: For scala 2.12, use lazyZip and foreach! - /*for ((ref, addr) <- refs.zip(freeSlots)) { - ref.allocate(addr) - allocatedSlots += addr - }*/ // FIXME: until 5.0.0 we need to suppress warnings, and Scala 3 is being annoying (refreshing change) - type Ref_ = Ref[_] - refs.zip(freeSlots).foreach { (refAndAddr: (Ref[_], Int) @nowarn3) => - val ref: Ref_ @nowarn3 = refAndAddr._1 - val addr = refAndAddr._2 - ref.allocate(addr) - allocatedSlots += addr - } - allocatedSlots.toList - } - /** If required, generates callee-save around a main body of instructions. * * This is needed when using `flatMap`, as it is unaware of the register @@ -160,23 +116,17 @@ private [deepembedding] object StrictParsley { * @param instrs the instruction buffer * @param state the code generation state, for label generation */ - private def generateCalleeSave[M[_, +_]: ContOps, R](numRegsUsedByParent: Int, bodyGen: =>M[R, Unit], usedRefs: Set[Ref[_]]) - (implicit instrs: InstrBuffer, state: CodeGenState): M[R, Unit] = { - val reqRegs = usedRefs.size - val localRegs: Set[Ref[_]] @nowarn3 = usedRefs.filterNot(_.allocated): @nowarn3 - val allocatedRegs = allocateRegisters(localRegs, usedRefs) - val calleeSaveRequired = numRegsUsedByParent >= 0 // if this is -1, then we are the top level and have no parent, otherwise it needs to be done - if (calleeSaveRequired && localRegs.nonEmpty) { - val end = state.freshLabel() - val calleeSave = state.freshLabel() - instrs += new instructions.Label(calleeSave) - instrs += new instructions.CalleeSave(end, localRegs, reqRegs, allocatedRegs, numRegsUsedByParent) - bodyGen |> { - instrs += new instructions.Jump(calleeSave) - instrs += new instructions.Label(end) - } + private def allocateAndExpandRefs(minRef: Int, usedRefs: Set[Ref[_]])(implicit instrs: InstrBuffer): Unit = { + var nextSlot = math.max(minRef, 0) + for (r <- usedRefs if !r.allocated) { + r.allocate(nextSlot) + nextSlot += 1 + } + val totalSlotsRequired = nextSlot + // if this is -1, then we are the top level and have no parent, otherwise it needs to be done + if (minRef >= 0 && (minRef < totalSlotsRequired)) { + instrs += new instructions.ExpandRefs(totalSlotsRequired) } - else bodyGen } /** Generates each of the shared, non-recursive, parsers that have been ''used'' by 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 eafd61166..300df525d 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 @@ -44,12 +44,7 @@ private [parsley] abstract class LazyParsley[+A] private [deepembedding] { // The instructions used to execute this parser along with the number of registers it uses final private [parsley] lazy val (instrs: Array[Instr], numRegs: Int) = computeInstrs - /** This parser is the result of a `flatMap` operation, and as such must perform - * callee-save on `numRegs` registers (which belong to its parent) - * - * @param numRegs the number of registers the parent uses (these must be saved) - */ - private [deepembedding] def demandCalleeSave(numRegs: Int): Unit = numRegsUsedByParent = numRegs + private [deepembedding] def setMinReferenceAllocation(minRef: Int): Unit = this.minRef = minRef // Internals // To ensure that stack-overflow cannot occur during the processing of particularly @@ -87,7 +82,7 @@ private [parsley] abstract class LazyParsley[+A] private [deepembedding] { final private var cps = false final private [deepembedding] def isCps: Boolean = cps /** how many registers are used by the ''parent'' of this combinator (this combinator is part of a `flatMap` when this is not -1) */ - final private var numRegsUsedByParent = -1 + final private var minRef = -1 /** Computes the instructions associated with this parser as well as the number of * registers it requires in a (possibly) stack-safe way. @@ -120,7 +115,7 @@ private [parsley] abstract class LazyParsley[+A] private [deepembedding] { implicit val letMap: LetMap = LetMap(letFinderState.lets, letFinderState.recs) for { sp <- this.optimised } yield { implicit val state: backend.CodeGenState = new backend.CodeGenState(letFinderState.numRegs) - sp.generateInstructions(numRegsUsedByParent, usedRefs, letMap.bodies) + sp.generateInstructions(minRef, usedRefs, letMap.bodies) } } }, letFinderState.numRegs) diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/CoreInstrs.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/CoreInstrs.scala index 028f2f3f8..c98d12aa8 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/CoreInstrs.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/CoreInstrs.scala @@ -72,17 +72,17 @@ private [internal] object Apply extends Instr { } // Monadic -private [internal] final class DynCall(f: Any => Array[Instr]) extends Instr { +private [internal] final class DynCall(f: (Any, Int) => Array[Instr]) extends Instr { override def apply(ctx: Context): Unit = { ensureRegularInstruction(ctx) - ctx.call(f(ctx.stack.upop())) + ctx.call(f(ctx.stack.upop(), ctx.regs.size)) } // $COVERAGE-OFF$ override def toString: String = "DynCall(?)" // $COVERAGE-ON$ } private [internal] object DynCall { - def apply[A](f: A => Array[Instr]): DynCall = new DynCall(f.asInstanceOf[Any => Array[Instr]]) + def apply[A](f: (A, Int) => Array[Instr]): DynCall = new DynCall(f.asInstanceOf[(Any, Int) => Array[Instr]]) } // Control Flow diff --git a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/PrimitiveInstrs.scala b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/PrimitiveInstrs.scala index 47e514b01..eaa11efe9 100644 --- a/parsley/shared/src/main/scala/parsley/internal/machine/instructions/PrimitiveInstrs.scala +++ b/parsley/shared/src/main/scala/parsley/internal/machine/instructions/PrimitiveInstrs.scala @@ -5,15 +5,12 @@ */ package parsley.internal.machine.instructions -import parsley.state.Ref import parsley.token.errors.LabelConfig import parsley.internal.errors.ExpectDesc import parsley.internal.machine.Context import parsley.internal.machine.XAssert._ -import org.typelevel.scalaccompat.annotation.nowarn3 - private [internal] final class Satisfies(f: Char => Boolean, expected: Iterable[ExpectDesc]) extends Instr { def this(f: Char => Boolean, expected: LabelConfig) = this(f, expected.asExpectDescs) override def apply(ctx: Context): Unit = { @@ -156,67 +153,11 @@ private [internal] object Span extends Instr { // $COVERAGE-ON$ } -// This instruction holds mutate state, but it is safe to do so, because it's always the first instruction of a DynCall. -private [parsley] final class CalleeSave(var label: Int, localRegs: Set[Ref[_]] @nowarn3, reqSize: Int, slots: List[(Int, Int)], saveArray: Array[AnyRef]) - extends InstrWithLabel { - private def this(label: Int, localRegs: Set[Ref[_]], reqSize: Int, slots: List[Int]) = - this(label, localRegs, reqSize, slots.zipWithIndex, new Array[AnyRef](slots.length)) - // this filters out the slots to ensure we only do callee-save on registers that might exist in the parent - def this(label: Int, localRefs: Set[Ref[_]], reqSize: Int, slots: List[Int], numRegsInContext: Int) = - this(label, localRefs, reqSize, slots.takeWhile(_ < numRegsInContext)) - private var inUse = false - private var oldRegs: Array[AnyRef] = null - - private def save(ctx: Context): Unit = { - for ((slot, idx) <- slots) { - saveArray(idx) = ctx.regs(slot) - ctx.regs(slot) = null - } - // If this is known to increase the size of the register pool, then we need to keep the old array to the side - if (reqSize > ctx.regs.size) { - oldRegs = ctx.regs - ctx.regs = java.util.Arrays.copyOf(oldRegs, reqSize) - } - } - - private def restore(ctx: Context): Unit = { - if (oldRegs != null) { - java.lang.System.arraycopy(ctx.regs, 0, oldRegs, 0, oldRegs.size) - ctx.regs = oldRegs - oldRegs = null - } - for ((slot, idx) <- slots) { - ctx.regs(slot) = saveArray(idx) - saveArray(idx) = null - } - // This is the only way to get them reallocated on the next invocation - localRegs.foreach(_.deallocate()): @nowarn3 - } - - private def continue(ctx: Context): Unit = { - ctx.handlers = ctx.handlers.tail - if (ctx.good) ctx.pc = label - else ctx.fail() - } - +private [parsley] final class ExpandRefs(newSz: Int) extends Instr { override def apply(ctx: Context): Unit = { - // Second-entry, callee-restore and either jump or fail - if (inUse) { - restore(ctx) - inUse = false - continue(ctx) - } - // Entry for the first time, register as a handle, callee-save and inc - else { - ensureRegularInstruction(ctx) - save(ctx) - inUse = true - ctx.pushHandler(ctx.pc) - ctx.inc() + if (newSz > ctx.regs.size) { + ctx.regs = java.util.Arrays.copyOf(ctx.regs, newSz) } + ctx.inc() } - - // $COVERAGE-OFF$ - override def toString: String = s"CalleeSave($label, newSz = $reqSize, slotsToSave = $slots)" - // $COVERAGE-ON$ } diff --git a/parsley/shared/src/main/scala/parsley/registers.scala b/parsley/shared/src/main/scala/parsley/registers.scala index 3c7c51b69..8f6485cce 100644 --- a/parsley/shared/src/main/scala/parsley/registers.scala +++ b/parsley/shared/src/main/scala/parsley/registers.scala @@ -429,8 +429,6 @@ object registers { assert(!allocated) this._v = v } - // This must ONLY be used by CalleeSave in flatMap - private [parsley] def deallocate(): Unit = _v = -1 //override def toString: String = s"Reg(${if (allocated) addr else "unallocated"})" } // $COVERAGE-OFF$ @@ -442,13 +440,12 @@ object registers { /** This function creates a new (global) register of a given type. * * The register created by this function is not allocated to any specific parser until it has been - * used by a parser. It should not be used with multiple different parsers. + * used by a parser. It should not be used with multiple different parsers: while this ''may'' work, + * there is a chance that two such registers collide in allocation, which is undefined behaviour. * * @tparam A the type to be contained in this register during runtime * @return a new register which can contain the given type. - * @note registers created in this manner ''must'' be initialised in the top-level parser and not - * inside a `flatMap`, as this may make them corrupt other registers. They should be used with - * caution. It is recommended to use `makeReg` and `fillReg` where possible. + * * @note They should be used with caution. It is recommended to use `makeReg` and `fillReg` where possible. * @since 2.2.0 */ def make[A]: Reg[A] = new Reg diff --git a/parsley/shared/src/test/scala/parsley/CoreTests.scala b/parsley/shared/src/test/scala/parsley/CoreTests.scala index 316c61d26..9c4d008f8 100644 --- a/parsley/shared/src/test/scala/parsley/CoreTests.scala +++ b/parsley/shared/src/test/scala/parsley/CoreTests.scala @@ -396,10 +396,10 @@ class CoreTests extends ParsleyTest { "flatMap" should "consistently generate a callee-save instruction if needed" in { import parsley.state._ val r = Ref.make[Int] - val p = pure(7).flatMap { _ => - r.set(4) *> r.get + val p = unit.flatMap { _ => + r.update(_ + 1) *> r.get } - (p *> p).parse("") shouldBe Success(4) + (unit.flatMap(_ => r.set(0)) *> p *> p).parse("") shouldBe Success(2) } "span" should "return all the input parsed by a parser, exactly as it was" in { From 3a372830c7593a75f88aa0972ecdbe0244e911c9 Mon Sep 17 00:00:00 2001 From: Jamie Willis Date: Sun, 5 Jan 2025 19:48:51 +0000 Subject: [PATCH 4/4] fix: removed warning in Scala 3 --- .../internal/deepembedding/backend/StrictParsley.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala index ab2c83d15..212e4940d 100644 --- a/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala +++ b/parsley/shared/src/main/scala/parsley/internal/deepembedding/backend/StrictParsley.scala @@ -118,9 +118,11 @@ private [deepembedding] object StrictParsley { */ private def allocateAndExpandRefs(minRef: Int, usedRefs: Set[Ref[_]])(implicit instrs: InstrBuffer): Unit = { var nextSlot = math.max(minRef, 0) - for (r <- usedRefs if !r.allocated) { - r.allocate(nextSlot) - nextSlot += 1 + usedRefs.foreach { (r: Ref[_]) => + if (!r.allocated) { + r.allocate(nextSlot) + nextSlot += 1 + } } val totalSlotsRequired = nextSlot // if this is -1, then we are the top level and have no parent, otherwise it needs to be done