parsley.internal.machine.instructions.TokenStringInstrs.scala Maven / Gradle / Ivy
The newest version!
package parsley.internal.machine.instructions
import parsley.token.TokenSet
import parsley.internal.errors.{ErrorItem, Desc}
import parsley.internal.machine.Context
import scala.annotation.tailrec
private [internal] class TokenEscape extends Instr with NumericReader {
private [this] final val expected = Some(Desc("escape code"))
override def apply(ctx: Context): Unit = escape(ctx) match {
case TokenEscape.EscapeChar(escapeChar) =>ctx.pushAndContinue(escapeChar)
case TokenEscape.BadCode => ctx.expectedFail(expected, reason = "invalid escape sequence")
case TokenEscape.NoParse => ctx.expectedTokenFail(expected, 3)
}
private final def consumeAndReturn(ctx: Context, n: Int, c: Char) = {
ctx.fastUncheckedConsumeChars(n)
new TokenEscape.EscapeChar(c)
}
private final def lookAhead(ctx: Context, n: Int): Char = ctx.input.charAt(ctx.offset + n)
private final def lookAhead(ctx: Context, n: Int, c: Char): Boolean = ctx.offset + n < ctx.inputsz && lookAhead(ctx, n) == c
private final def numericEscape(ctx: Context, escapeCode: Int) = {
if (escapeCode <= 0x10FFFF) new TokenEscape.EscapeChar(escapeCode.toChar)
else TokenEscape.BadCode
}
private final def nonDecimalNumericEscape(ctx: Context, lexer: (Context, Int, Boolean) => Option[Int]) = {
ctx.fastUncheckedConsumeChars(1)
lexer(ctx, 0, true) match {
case Some(x) => numericEscape(ctx, x)
case None => TokenEscape.NoParse
}
}
private final def decimalEscape(ctx: Context, d: Int) = {
ctx.fastUncheckedConsumeChars(1)
numericEscape(ctx, decimal(ctx, d, false).get)
}
private final def hexadecimalEscape(ctx: Context) = nonDecimalNumericEscape(ctx, hexadecimal)
private final def octalEscape(ctx: Context) = nonDecimalNumericEscape(ctx, octal)
private final def caretEscape(ctx: Context) = {
ctx.fastUncheckedConsumeChars(1)
if (ctx.moreInput && ctx.nextChar >= 'A' && ctx.nextChar <= 'Z') consumeAndReturn(ctx, 1, (ctx.nextChar - 'A' + 1).toChar)
else TokenEscape.NoParse
}
protected final def escape(ctx: Context): TokenEscape.Escape = {
val threeAvailable = ctx.offset + 2 < ctx.inputsz
if (ctx.moreInput) {
ctx.nextChar match {
case 'a' => consumeAndReturn(ctx, 1, '\u0007')
case 'b' => consumeAndReturn(ctx, 1, '\b')
case 'f' => consumeAndReturn(ctx, 1, '\u000c')
case 'n' => consumeAndReturn(ctx, 1, '\n')
case 'r' => consumeAndReturn(ctx, 1, '\r')
case 't' => consumeAndReturn(ctx, 1, '\t')
case 'v' => consumeAndReturn(ctx, 1, '\u000b')
case '\\' => consumeAndReturn(ctx, 1, '\\')
case '\"' => consumeAndReturn(ctx, 1, '\"')
case '\'' => consumeAndReturn(ctx, 1, '\'')
case d@('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9') => decimalEscape(ctx, d.asDigit)
case 'x' => hexadecimalEscape(ctx)
case 'o' => octalEscape(ctx)
case '^' => caretEscape(ctx)
case 'A' if threeAvailable && lookAhead(ctx, 1) == 'C' && lookAhead(ctx, 2) == 'K' => consumeAndReturn(ctx, 3, '\u0006') //ACK
case 'B' => //BS BEL
if (lookAhead(ctx, 1, 'S')) consumeAndReturn(ctx, 2, '\u0008')
else if (lookAhead(ctx, 2, 'L') && lookAhead(ctx, 1) == 'E') {
consumeAndReturn(ctx, 3, '\u0007')
}
else TokenEscape.NoParse
case 'C' => //CR CAN
if (lookAhead(ctx, 1, 'R')) consumeAndReturn(ctx, 2, '\u000d')
else if (lookAhead(ctx, 2, 'N') && lookAhead(ctx, 1) == 'A') consumeAndReturn(ctx, 3, '\u0018')
else TokenEscape.NoParse
case 'D' if threeAvailable => //DC1 DC2 DC3 DC4 DEL DLE
val c = lookAhead(ctx, 2)
lookAhead(ctx, 1) match {
case 'C' if c == '1' => consumeAndReturn(ctx, 3, '\u0011')
case 'C' if c == '2' => consumeAndReturn(ctx, 3, '\u0012')
case 'C' if c == '3' => consumeAndReturn(ctx, 3, '\u0013')
case 'C' if c == '4' => consumeAndReturn(ctx, 3, '\u0014')
case 'E' if c == 'L' => consumeAndReturn(ctx, 3, '\u001f')
case 'L' if c == 'E' => consumeAndReturn(ctx, 3, '\u0010')
case _ => TokenEscape.NoParse
}
case 'E' => //EM ETX ETB ESC EOT ENQ
if (lookAhead(ctx, 1, 'M')) consumeAndReturn(ctx, 2, '\u0019')
else if (threeAvailable) lookAhead(ctx, 1) match {
case 'N' if lookAhead(ctx, 2) == 'Q' => consumeAndReturn(ctx, 3, '\u0005')
case 'O' if lookAhead(ctx, 2) == 'T' => consumeAndReturn(ctx, 3, '\u0004')
case 'S' if lookAhead(ctx, 2) == 'C' => consumeAndReturn(ctx, 3, '\u001b')
case 'T' if lookAhead(ctx, 2) == 'X' => consumeAndReturn(ctx, 3, '\u0003')
case 'T' if lookAhead(ctx, 2) == 'B' => consumeAndReturn(ctx, 3, '\u0017')
case _ => TokenEscape.NoParse
}
else TokenEscape.NoParse
case 'F' => //FF FS
if (lookAhead(ctx, 1, 'F')) consumeAndReturn(ctx, 2, '\u000c')
else if (lookAhead(ctx, 1, 'S')) consumeAndReturn(ctx, 2, '\u001c')
else TokenEscape.NoParse
case 'G' if lookAhead(ctx, 1, 'S') => consumeAndReturn(ctx, 2, '\u001d') //GS
case 'H' if lookAhead(ctx, 1, 'T') => consumeAndReturn(ctx, 2, '\u0009') //HT
case 'L' if lookAhead(ctx, 1, 'F') => consumeAndReturn(ctx, 2, '\n') //LF
case 'N' => //NUL NAK
if (threeAvailable && lookAhead(ctx, 1) == 'U' && lookAhead(ctx, 2) == 'L') consumeAndReturn(ctx, 3, '\u0000')
else if (threeAvailable && lookAhead(ctx, 1) == 'A' && lookAhead(ctx, 2) == 'K') {
consumeAndReturn(ctx, 3, '\u0015')
}
else TokenEscape.NoParse
case 'R' if lookAhead(ctx, 1, 'S') => consumeAndReturn(ctx, 2, '\u001e') //RS
case 'S' => //SO SI SP SOH STX SYN SUB
if (lookAhead(ctx, 1, 'O')) consumeAndReturn(ctx, 2, '\u000e')
else if (lookAhead(ctx, 1, 'I')) consumeAndReturn(ctx, 2, '\u000f')
else if (lookAhead(ctx, 1, 'P')) consumeAndReturn(ctx, 2, '\u0020')
else if (threeAvailable) lookAhead(ctx, 1) match {
case 'O' if lookAhead(ctx, 2) == 'H' => consumeAndReturn(ctx, 3, '\u0001')
case 'T' if lookAhead(ctx, 2) == 'X' => consumeAndReturn(ctx, 3, '\u0002')
case 'Y' if lookAhead(ctx, 2) == 'N' => consumeAndReturn(ctx, 3, '\u0016')
case 'U' if lookAhead(ctx, 2) == 'B' => consumeAndReturn(ctx, 3, '\u001a')
case _ => TokenEscape.NoParse
}
else TokenEscape.NoParse
case 'U' if lookAhead(ctx, 1, 'S') => consumeAndReturn(ctx, 2, '\u001f') //US
case 'V' if lookAhead(ctx, 1, 'T') => consumeAndReturn(ctx, 2, '\u000b') //VT
case _ => TokenEscape.NoParse
}
}
else TokenEscape.NoParse
}
// $COVERAGE-OFF$
override def toString: String = "TokenEscape"
// $COVERAGE-ON$
}
private [instructions] object TokenEscape {
private [instructions] sealed trait Escape
private [instructions] case class EscapeChar(escapeChar: Char) extends Escape
private [instructions] case object BadCode extends Escape
private [instructions] case object NoParse extends Escape
}
private [instructions] sealed trait TokenStringLike extends Instr {
final protected lazy val expectedString = Some(Desc("string"))
final protected lazy val expectedEos = Some(Desc("end of string"))
final protected lazy val expectedChar = Some(Desc("string character"))
// All failures must be handled by this function
protected def handleEscaped(ctx: Context, builder: StringBuilder): Boolean
@tailrec private final def restOfString(ctx: Context, builder: StringBuilder): Unit = {
if (ctx.moreInput) ctx.nextChar match {
case '"' =>
ctx.fastUncheckedConsumeChars(1)
ctx.pushAndContinue(builder.result())
case '\\' =>
ctx.fastUncheckedConsumeChars(1)
if (handleEscaped(ctx, builder)) restOfString(ctx, builder)
case c if c > '\u0016' =>
builder += c
ctx.fastUncheckedConsumeChars(1)
restOfString(ctx, builder)
case _ => ctx.expectedFail(expectedChar)
}
else ctx.expectedFail(expectedEos)
}
final override def apply(ctx: Context): Unit = {
if (ctx.moreInput && ctx.nextChar == '"') {
ctx.fastUncheckedConsumeChars(1)
restOfString(ctx, new StringBuilder)
}
else ctx.expectedFail(expectedString)
}
}
private [internal] object TokenRawString extends TokenStringLike {
override def handleEscaped(ctx: Context, builder: StringBuilder): Boolean = {
builder += '\\'
if (ctx.moreInput && ctx.nextChar > '\u0016') {
builder += ctx.nextChar
ctx.fastUncheckedConsumeChars(1)
true
}
else {
ctx.expectedFail(expectedChar)
false
}
}
// $COVERAGE-OFF$
override def toString: String = "TokenRawString"
// $COVERAGE-ON$
}
private [internal] final class TokenString(ws: TokenSet) extends TokenEscape with TokenStringLike {
private [this] final val expectedEscape = Some(Desc("escape code"))
private [this] final val expectedGap = Some(Desc("end of string gap"))
private def readGap(ctx: Context): Boolean = {
val completedGap = ctx.moreInput && ctx.nextChar == '\\'
if (completedGap) ctx.fastUncheckedConsumeChars(1)
else ctx.expectedFail(expectedGap)
completedGap
}
override def handleEscaped(ctx: Context, builder: StringBuilder): Boolean = {
if (spaces(ctx) != 0) readGap(ctx)
else if (ctx.moreInput && ctx.nextChar == '&') {
ctx.fastUncheckedConsumeChars(1)
true
}
else escape(ctx) match {
case TokenEscape.EscapeChar(c) =>
builder += c
true
case TokenEscape.BadCode =>
ctx.expectedFail(expectedEscape, reason = "invalid escape sequence")
false
case TokenEscape.NoParse =>
ctx.expectedFail(expectedEscape)
false
}
}
@tailrec private def spaces(ctx: Context, n: Int = 0): Int = {
if (ctx.moreInput && ws(ctx.nextChar)) {
ctx.consumeChar()
spaces(ctx, n + 1)
}
else n
}
// $COVERAGE-OFF$
override def toString: String = "TokenString"
// $COVERAGE-ON$
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy