All Downloads are FREE. Search and download functionalities are using the official Maven repository.

parsley.internal.machine.Context.scala Maven / Gradle / Ivy

The newest version!
package parsley.internal.machine

import instructions.Instr
import stacks.{ArrayStack, Stack, CallStack, CheckStack, HandlerStack, StateStack, HintStack, ErrorStack}, Stack.StackExt
import parsley.{Failure, Result, Success}
import parsley.internal.errors.{ErrorItem, LineBuilder}
import parsley.internal.machine.errors.{
    ErrorItemBuilder,
    DefuncError, ClassicExpectedError, ClassicExpectedErrorWithReason, ClassicFancyError, ClassicUnexpectedError, WithHints, TokenError,
    DefuncHints, EmptyHints, MergeHints, ReplaceHint, PopHints, AddError
}

import scala.annotation.tailrec
import scala.collection.mutable
import parsley.errors.ErrorBuilder

private [parsley] object Context {
    private [Context] val NumRegs = 4
    private [Context] val EmptyPreserve = Array.empty[Int]
    private [Context] val EmptyExchange = Array.empty[Instr]
    private [parsley] def empty: Context = new Context(null, "")
}

private [parsley] final class Context(private [machine] var instrs: Array[Instr],
                                      private [machine] var input: String,
                                      private val sourceFile: Option[String] = None) {
    /** This is the operand stack, where results go to live  */
    private [machine] val stack: ArrayStack[Any] = new ArrayStack()
    /** Current offset into the input */
    private [machine] var offset: Int = 0
    /** The length of the input, stored for whatever reason */
    private [machine] var inputsz: Int = input.length
    /** Call stack consisting of Frames that track the return position and the old instructions */
    //private var calls: FastStack[Frame] = FastStack.empty
    private var calls: CallStack = Stack.empty
    /** State stack consisting of offsets and positions that can be rolled back */
    private [machine] var states: StateStack = Stack.empty
    /** Stack consisting of offsets at previous checkpoints, which may query to test for consumed input */
    private [machine] var checkStack: CheckStack = Stack.empty
    /** Current operational status of the machine */
    private [machine] var status: Status = Good
    /** Stack of handlers, which track the call depth, program counter and stack size of error handlers */
    private [machine] var handlers: HandlerStack = Stack.empty
    /** Current size of the call stack */
    private var depth: Int = 0
    /** Current offset into program instruction buffer */
    private [machine] var pc: Int = 0
    /** Current line number */
    private [machine] var line: Int = 1
    /** Current column number */
    private [machine] var col: Int = 1
    /** State held by the registers, AnyRef to allow for `null` */
    private [machine] val regs: Array[AnyRef] = new Array[AnyRef](Context.NumRegs)
    /** Amount of indentation to apply to debug combinators output */
    private [machine] var debuglvl: Int = 0

    // NEW ERROR MECHANISMS
    private var hints: DefuncHints = EmptyHints
    private var hintsValidOffset = 0
    private var hintStack = Stack.empty[HintStack]
    private [machine] var errs: ErrorStack = Stack.empty

    private [machine] def saveHints(shadow: Boolean): Unit = {
        hintStack = new HintStack(hints, hintsValidOffset, hintStack)
        if (!shadow) hints = EmptyHints
    }
    private [machine] def restoreHints(): Unit = {
        val hintFrame = this.hintStack
        this.hintsValidOffset = hintFrame.validOffset
        this.hints = hintFrame.hints
        this.commitHints()
    }
    private [machine] def commitHints(): Unit = {
        this.hintStack = this.hintStack.tail
    }

    /* ERROR RELABELLING BEGIN */
    private [machine] def mergeHints(): Unit = {
        val hintFrame = this.hintStack
        if (hintFrame.validOffset == offset) this.hints = MergeHints(hintFrame.hints, this.hints)
        commitHints()
    }
    private [machine] def replaceHint(label: String): Unit = hints = ReplaceHint(label, hints)
    private [machine] def popHints: Unit = hints = PopHints(hints)
    /* ERROR RELABELLING END */

    private def addErrorToHints(): Unit = {
        val err = errs.error
        if (err.isTrivialError && err.offset == offset && !err.isExpectedEmpty) {
            // If our new hints have taken place further in the input stream, then they must invalidate the old ones
            if (hintsValidOffset < offset) {
                hints = EmptyHints
                hintsValidOffset = offset
            }
            hints = new AddError(hints, err)
        }
    }
    private [machine] def addErrorToHintsAndPop(): Unit = {
        this.addErrorToHints()
        this.errs = this.errs.tail
    }

    private [machine] def updateCheckOffsetAndHints() = {
        this.checkStack.offset = this.offset
        this.hintsValidOffset = this.offset
    }

    // $COVERAGE-OFF$
    private [machine] def pretty: String = {
        s"""[
           |  stack     = [${stack.mkString(", ")}]
           |  instrs    = ${instrs.mkString("; ")}
           |  input     = ${input.drop(offset).mkString}
           |  pos       = ($line, $col)
           |  status    = $status
           |  pc        = $pc
           |  depth     = $depth
           |  rets      = ${calls.mkString(", ")}
           |  handlers  = ${handlers.mkString(", ")}
           |  recstates = ${states.mkString(", ")}
           |  checks    = ${checkStack.mkString(", ")}
           |  registers = ${regs.zipWithIndex.map{case (r, i) => s"r$i = $r"}.mkString("\n              ")}
           |  errors    = ${errs.mkString(", ")}
           |  hints     = ($hintsValidOffset, ${hints.toSet}):${hintStack.mkString(", ")}
           |]""".stripMargin
    }
    // $COVERAGE-ON$

    @tailrec @inline private [parsley] def runParser[Err: ErrorBuilder, A](): Result[Err, A] = {
        //println(pretty)
        if (status eq Failed) Failure(errs.error.asParseError.format(sourceFile))
        else if (status ne Finished) {
            instrs(pc)(this)
            runParser[Err, A]()
        }
        else if (calls.isEmpty) Success(stack.peek[A])
        else {
            status = Good
            ret()
            runParser[Err, A]()
        }
    }

    @tailrec @inline private def preserveInstrs(preserve: Array[Int], exchange: Array[Instr], i: Int): Unit = if (i >= 0) {
        val idx = preserve(i)
        val instr = instrs(idx)
        exchange(i) = instr
        instrs(idx) = instr.copy
        preserveInstrs(preserve, exchange, i - 1)
    }

    private [machine] def call(at: Int, preserve: Array[Int]): Unit = {
        val exchange = new Array[Instr](preserve.size)
        preserveInstrs(preserve, exchange, preserve.size - 1)
        calls = new CallStack(pc + 1, instrs, preserve, exchange, at, calls)
        for (idx <- preserve) instrs(idx) = instrs(idx).copy
        pc = at
        depth += 1
    }

    private [machine] def call(newInstrs: Array[Instr]): Unit = {
        call(0)
        instrs = newInstrs
    }

    private [machine] def call(at: Int): Unit = {
        calls = new CallStack(pc + 1, instrs, Context.EmptyPreserve, Context.EmptyExchange, at, calls)
        pc = at
        depth += 1
    }

    @tailrec @inline private def restoreInstrs(preserve: Array[Int], exchange: Array[Instr], i: Int): Unit = if (i >= 0) {
        instrs(preserve(i)) = exchange(i)
        restoreInstrs(preserve, exchange, i-1)
    }

    private [machine] def ret(): Unit = {
        instrs = calls.instrs
        restoreInstrs(calls.indices, calls.exchange, calls.indices.size-1)
        pc = calls.ret
        calls = calls.tail
        depth -= 1
    }

    @tailrec private def multiRet(n: Int): Unit = if (n > 0) {
        if (n == 1) ret()
        else {
            val callId = calls.callId
            var m = n - 1
            while (calls.tail != null && calls.tail.callId == callId && m > 0) {
                calls = calls.tail
                m -= 1
            }
            ret()
            multiRet(m)
        }
    }

    private [machine] def catchNoConsumed(handler: =>Unit): Unit = {
        if (offset != checkStack.offset) fail()
        else {
            status = Good
            handler
        }
        checkStack = checkStack.tail
    }

    private [machine] def pushError(err: DefuncError): Unit = this.errs = new ErrorStack(this.useHints(err), this.errs)
    private [machine] def useHints(err: DefuncError): DefuncError = {
        if (hintsValidOffset == offset) WithHints(err, hints)
        else {
            hintsValidOffset = offset
            hints = EmptyHints
            err
        }
    }

    private [machine] def failWithMessage(msgs: String*): Unit = this.fail(new ClassicFancyError(offset, line, col, msgs: _*))
    private [machine] def unexpectedFail(expected: Option[ErrorItem], unexpected: ErrorItem): Unit = {
        this.fail(new ClassicUnexpectedError(offset, line, col, expected, unexpected))
    }
    private [machine] def expectedFail(expected: Option[ErrorItem]): Unit = this.fail(new ClassicExpectedError(offset, line, col, expected))
    private [machine] def expectedFail(expected: Option[ErrorItem], reason: String): Unit = {
        this.fail(new ClassicExpectedErrorWithReason(offset, line, col, expected, reason))
    }
    private [machine] def expectedTokenFail(expected: Option[ErrorItem], size: Int): Unit = this.fail(new TokenError(offset, line, col, expected, size))

    private [machine] def fail(error: DefuncError): Unit = {
        this.pushError(error)
        this.fail()
    }
    private [machine] def fail(): Unit = {
        if (handlers.isEmpty) status = Failed
        else {
            status = Recover
            val handler = handlers
            handlers = handlers.tail
            multiRet(depth - handler.depth)
            pc = handler.pc
            val diffstack = stack.usize - handler.stacksz
            if (diffstack > 0) stack.drop(diffstack)
        }
    }

    private [machine] def pushAndContinue(x: Any) = {
        stack.push(x)
        inc()
    }
    private [machine] def exchangeAndContinue(x: Any) = {
        stack.exchange(x)
        inc()
    }
    private [machine] def inc(): Unit = pc += 1
    private [machine] def nextChar: Char = input.charAt(offset)
    private [machine] def moreInput: Boolean = offset < inputsz
    private [machine] def updatePos(c: Char) = c match {
        case '\n' => line += 1; col = 1
        case '\t' => col += 4 - ((col - 1) & 3)
        case _ => col += 1
    }
    private [machine] def consumeChar(): Char = {
        val c = nextChar
        updatePos(c)
        offset += 1
        c
    }
    private [machine] def fastUncheckedConsumeChars(n: Int) = {
        offset += n
        col += n
    }
    private [machine] def pushHandler(label: Int): Unit = handlers = new HandlerStack(depth, label, stack.usize, handlers)
    private [machine] def pushCheck(): Unit = checkStack = new CheckStack(offset, checkStack)
    private [machine] def saveState(): Unit = states = new StateStack(offset, line, col, states)
    private [machine] def restoreState(): Unit = {
        val state = states
        states = states.tail
        offset = state.offset
        line = state.line
        col = state.col
    }
    private [machine] def writeReg(reg: Int, x: Any): Unit = {
        regs(reg) = x.asInstanceOf[AnyRef]
    }

    // Allows us to reuse a context, helpful for benchmarking and potentially user applications
    private [parsley] def apply(_instrs: Array[Instr], _input: String): Context = {
        instrs = _instrs
        input = _input
        stack.clear()
        offset = 0
        inputsz = input.length
        calls = Stack.empty
        states = Stack.empty
        checkStack = Stack.empty
        status = Good
        handlers = Stack.empty
        depth = 0
        pc = 0
        line = 1
        col = 1
        debuglvl = 0
        hintsValidOffset = 0
        hints = EmptyHints
        hintStack = Stack.empty
        this
    }

    private implicit val lineBuilder: LineBuilder = new LineBuilder {
        def nearestNewlineBefore(off: Int): Option[Int] = {
            if (off < 0) None
            else Some {
                val idx = Context.this.input.lastIndexOf('\n', off-1)
                if (idx == -1) 0 else idx + 1
            }
        }
        def nearestNewlineAfter(off: Int): Option[Int] = {
            if (off > Context.this.inputsz) None
            else Some {
                val idx = Context.this.input.indexOf('\n', off)
                if (idx == -1) Context.this.inputsz else idx
            }
        }
        def segmentBetween(start: Int, end: Int): String = {
            Context.this.input.substring(start, end)
        }
    }

    private implicit val errorItemBuilder: ErrorItemBuilder = new ErrorItemBuilder {
        def inRange(offset: Int): Boolean = offset < Context.this.inputsz
        def charAt(offset: Int): Char = Context.this.input.charAt(offset)
        def substring(offset: Int, size: Int): String = Context.this.input.substring(offset, Math.min(offset + size, Context.this.inputsz))
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy