Last active
June 16, 2020 22:51
-
-
Save sir-wabbit/504105d76b824163a0be4c85ea10f1c1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait CharReader { | |
def current(): Int | |
def advance(): Unit | |
} | |
object CharReader { | |
final val EOF: Int = -1 | |
final val EOB: Int = -2 | |
@silent final class FromString(val text: String, end: Int) extends CharReader { | |
var index = 0 | |
override def current(): Int = if (index < text.length) text.charAt(index) else end | |
override def advance(): Unit = if (index < text.length) index += 1 | |
} | |
} | |
sealed abstract class Result[+S, +E, +A](final val tag: Int) extends Product with Serializable | |
object Result { | |
final val TagOk: Int = 0 | |
final val TagErr: Int = 1 | |
final val TagEOB: Int = 2 | |
final case class Ok[A](value: A) extends Result[Nothing, Nothing, A](TagOk) | |
final case class Err[E](value: E) extends Result[Nothing, E, Nothing](TagErr) | |
final case class Cont[S](value: S) extends Result[S, Nothing, Nothing](TagEOB) | |
} | |
import Result._ | |
sealed trait ParseIntError extends Product with Serializable | |
object ParseIntError { | |
final case object UnexpectedEOF extends ParseIntError | |
final case object Overflow extends ParseIntError | |
final case object Empty extends ParseIntError | |
} | |
sealed abstract class ParseIntState(final val tag: Int) extends Product with Serializable | |
object ParseIntState { | |
final val TagInit: Int = 0 | |
final val TagMain: Int = 1 | |
final case object Init extends ParseIntState(TagInit) | |
final case class Main(first: Boolean, negative: Boolean, limit: Int, result: Int) extends ParseIntState(TagMain) | |
} | |
def readInt(reader: CharReader, state: ParseIntState = ParseIntState.Init): Result[ParseIntState, ParseIntError, Int] = { | |
@tailrec def main(first: Boolean, negative: Boolean, limit: Int, result: Int): Result[ParseIntState, ParseIntError, Int] = { | |
val ch = reader.current() | |
if (ch == CharReader.EOF) { | |
if (first) Err(ParseIntError.Empty) | |
else Ok(if (negative) result else -result) | |
} | |
else if (ch == CharReader.EOB) Cont(ParseIntState.Main(first, negative, limit, result)) | |
else { | |
if ('0' <= ch && ch <= '9') { | |
val digit = ch - '0' | |
if (digit == 0 && first) { | |
reader.advance() | |
Ok(0) | |
} else { | |
// Note that we are calculating -VALUE since this allows us to avoid | |
// problems around Integer.MAX_VALUE. | |
if (result < limit / 10) Err(ParseIntError.Overflow) | |
else { | |
val a = result * 10 | |
if (a < limit + digit) Err(ParseIntError.Overflow) | |
else { | |
reader.advance() | |
main(first = false, negative = negative, limit, a - digit) | |
} | |
} | |
} | |
} else if (first) Err(ParseIntError.Empty) | |
else Ok(if (negative) result else -result) | |
} | |
} | |
if (state.tag == ParseIntState.TagInit) { | |
// Get first char. | |
val firstChar = reader.current() | |
if (firstChar == CharReader.EOF) Err(ParseIntError.Empty) | |
else if (firstChar == CharReader.EOB) Cont(ParseIntState.Init) | |
else { | |
// Possible leading "+" or "-" | |
if (firstChar == '-') { | |
reader.advance() | |
main(first = true, negative = true, limit = Integer.MIN_VALUE, 0) | |
} else if (firstChar == '+') { | |
reader.advance() | |
main(first = true, negative = false, limit = -Integer.MAX_VALUE, 0) | |
} | |
else main(first = true, negative = false, limit = -Integer.MAX_VALUE, 0) | |
} | |
} else { | |
val s = state.asInstanceOf[ParseIntState.Main] : @silent | |
main(s.first, s.negative, s.limit, s.result) | |
} | |
} | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
///////////////////////////////////////////////////////////////////////////////////////////////////// | |
property("readInt satisfies its spec") { | |
import Parsing.Result._ | |
def r(s: String): (String, Result[ParseIntState, ParseIntError, Int]) = { | |
@tailrec def go(state: Parsing.ParseIntState, rest: String): (String, Result[ParseIntState, ParseIntError, Int]) = { | |
if (rest.length < 3) { | |
val reader = new CharReader.FromString(rest, CharReader.EOF) | |
val r = Parsing.readInt(reader, state) | |
val l = reader.text.substring(reader.index) | |
l -> r | |
} else { | |
val reader = new CharReader.FromString(rest.substring(0, 3), CharReader.EOB) | |
Parsing.readInt(reader, state) match { | |
case r@(Ok(_) | Err(_)) => | |
val l = reader.text.substring(reader.index) ++ rest.substring(3) | |
l -> r | |
case Cont(s) => | |
assert(reader.index == reader.text.length) | |
go(s, rest.substring(3)) | |
} | |
} | |
} | |
go(ParseIntState.Init, s) | |
} | |
assert(r("0") === ("" -> Ok(0))) | |
assert(r("1") === ("" -> Ok(1))) | |
assert(r("123456789") === ("" -> Ok(123456789))) | |
assert(r("123456789 ") === (" " -> Ok(123456789))) | |
assert(r(" 1") === (" 1" -> Err(ParseIntError.Empty))) | |
assert(r(" 1 ") === (" 1 " -> Err(ParseIntError.Empty))) | |
assert(r("- 1") === (" 1" -> Err(ParseIntError.Empty))) | |
assert(r("+ 1") === (" 1" -> Err(ParseIntError.Empty))) | |
assert(r(Int.MinValue.toString) === "" -> Ok(Int.MinValue)) | |
assert(r(Int.MaxValue.toString) === "" -> Ok(Int.MaxValue)) | |
assert(r((Int.MaxValue - 1).toString) === "" -> Ok(Int.MaxValue - 1)) | |
assert(r((Int.MinValue + 1).toString) === "" -> Ok(Int.MinValue + 1)) | |
assert(r("") === "" -> Err(ParseIntError.Empty)) | |
assert(r("-") === "" -> Err(ParseIntError.Empty)) | |
assert(r("+") === "" -> Err(ParseIntError.Empty)) | |
assert(r("+-1") === "-1" -> Err(ParseIntError.Empty)) | |
assert(r("-+1") === "+1" -> Err(ParseIntError.Empty)) | |
assert(r("++1") === "+1" -> Err(ParseIntError.Empty)) | |
assert(r("11 1") === " 1" -> Ok(11)) | |
assert(r("123.1") === ".1" -> Ok(123)) | |
assert(r("12-") === "-" -> Ok(12)) | |
// Not an *Int* | |
assert(r("99999999999999999999999")._2 === Err(ParseIntError.Overflow)) | |
assert(r(Long.MinValue.toString)._2 === Err(ParseIntError.Overflow)) | |
assert(r(Long.MaxValue.toString)._2 === Err(ParseIntError.Overflow)) | |
assert(r((Long.MaxValue - 1).toString)._2 === Err(ParseIntError.Overflow)) | |
assert(r((Long.MinValue + 1).toString)._2 === Err(ParseIntError.Overflow)) | |
assert(r((Int.MaxValue.toLong + 1).toString)._2 === Err(ParseIntError.Overflow)) | |
assert(r((Int.MinValue.toLong - 1).toString)._2 === Err(ParseIntError.Overflow)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment