Skip to content

Instantly share code, notes, and snippets.

@sasaki-shigeo
Created June 10, 2025 03:33
Show Gist options
  • Save sasaki-shigeo/028415e77cc10c4289aa856a12a33153 to your computer and use it in GitHub Desktop.
Save sasaki-shigeo/028415e77cc10c4289aa856a12a33153 to your computer and use it in GitHub Desktop.
//
// Description: A Scala implementation of a parser for integer literals
//
package string2int
// The states of the parser
enum State { case START, ZERO, DEC, OCT, B, BIN, X, HEX, ERROR }
import State._
// The class of the transition table entries.
case class TransitionEntry(action: Function1[Char, Unit], nextState: State)
def parseInt(str: String): Int = {
val parser = new StateMachine(str)
parser.parseInt()
}
def parseLong(str: String): Long = {
val parser = new StateMachine(str)
parser.parseLong()
}
class StateMachine(input: String) {
var st = State.START
var acc = 0L
def parseLong(): Long = {
st = START
acc = 0L
for (ch <- input) {
val TransitionEntry(action, nextState) = transitionFunction(st, ch)
action(ch)
st = nextState
}
if (st == ERROR || st == X || st == B) {
throw new NumberFormatException(s"Invalid input: $input")
}
acc // result value
}
def parseInt(): Int = {
val result = parseLong()
if (result < Int.MinValue || result > Int.MaxValue) {
throw new NumberFormatException(s"Value out of range for int: $result")
}
result.toInt
}
def doNothing(ch: Char): Unit = { /* do nothing */ }
def accumulateDecimal(ch: Char): Unit = {
acc = 10 * acc + (ch - '0')
}
def accumulateOctal(ch: Char): Unit = {
acc = 8 * acc + (ch - '0')
}
def accumulateBinary(ch: Char): Unit = {
acc = 2 * acc + (ch - '0')
}
def accumulateHexByDecimal(ch: Char): Unit = {
acc = 16 * acc + (ch - '0')
}
def accumulateHexByUpper(ch: Char): Unit = {
acc = 16 * acc + (ch - 'A' + 10)
}
def accumulateHexByLower(ch: Char): Unit = {
acc = 16 * acc + (ch - 'a' + 10)
}
// This is a mock of the action table.
// This function is executed at runtime
// while an action table should computed at compile time.
def transitionFunction(st: State, ch: Char): TransitionEntry = st match {
case START => ch match {
case '0' => TransitionEntry(doNothing(_), ZERO)
case '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
TransitionEntry(accumulateDecimal(_), DEC)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case ZERO => ch match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' =>
TransitionEntry(accumulateOctal(_), OCT)
case 'b' | 'B' => TransitionEntry(doNothing(_), B)
case 'x' | 'X' => TransitionEntry(doNothing(_), X)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case DEC => ch match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
TransitionEntry(accumulateDecimal(_), DEC)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case OCT => ch match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' =>
TransitionEntry(accumulateOctal(_), OCT)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case B => ch match {
case '0' | '1' => TransitionEntry(accumulateBinary(_), BIN)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case BIN => ch match {
case '0' | '1' => TransitionEntry(accumulateBinary(_), BIN)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case X => ch match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
TransitionEntry(accumulateHexByDecimal(_), HEX)
case 'A' | 'B' | 'C' | 'D' | 'E' | 'F' =>
TransitionEntry(accumulateHexByUpper(_), HEX)
case 'a' | 'b' | 'c' | 'd' | 'e' | 'f' =>
TransitionEntry(accumulateHexByLower(_), HEX)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case HEX => ch match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
TransitionEntry(accumulateHexByDecimal(_), HEX)
case 'A' | 'B' | 'C' | 'D' | 'E' | 'F' =>
TransitionEntry(accumulateHexByUpper(_), HEX)
case 'a' | 'b' | 'c' | 'd' | 'e' | 'f' =>
TransitionEntry(accumulateHexByLower(_), HEX)
case _ => TransitionEntry(doNothing(_), ERROR)
}
case ERROR => TransitionEntry(doNothing(_), ERROR)
}
}
// ParseInt test cases
//
//> using test.dep org.scalameta::munit::1.1.1
//> using file string2int.scala
//
import munit.FunSuite
import string2int._
class ParseIntTest extends FunSuite {
test("0") {
assertEquals(parseInt("0"), 0)
}
test("00") {
assertEquals(parseInt("00"), 0 )
}
test("99") {
assertEquals(parseInt("99"), 99)
}
test("077 (octal)") {
assertEquals(parseInt("077"), 63)
}
test("0b101010 (binary)") {
assertEquals(parseInt("0b101010"), 42)
}
test("0x2A (hexadecimal)") {
assertEquals(parseInt("0x2A"), 42)
}
test("'0x' causes an error") {
intercept[NumberFormatException] {
parseInt("0x")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment