Skip to content

Instantly share code, notes, and snippets.

@jasonjckn
Last active August 29, 2015 14:01
Show Gist options
  • Save jasonjckn/5c7d537042343d49fff6 to your computer and use it in GitHub Desktop.
Save jasonjckn/5c7d537042343d49fff6 to your computer and use it in GitHub Desktop.
combinator parser in scala
// Parser T is a monad around a function that accepts input and returns Result (i.e. Consumed input, or Failed to parse input)
// the a.flatMap(v=>b) function (Monad bind) will produce a new monad with new function that composes the the parser functions in a and b.
abstract class Result[T]
case class Failed[T](desc: String) extends Result[T]
case class Consumed[T](value: T, rest: String) extends Result[T]
trait Parser[T] extends ((String) => Result[T]) {
def flatMap[A](v2m: ((T) => Parser[A])): Parser[A] = {
new Parser[A] {
def apply(input: String) = {
Parser.this(input) match {
case Consumed(v, rest) => v2m(v)(rest)
case Failed(desc) => Failed[A](desc)
}
}
}
}
def map[A](v2v: (T => A)): Parser[A] = flatMap(v2v andThen value)
def value[A](v: A): Parser[A] = new Parser[A] {
def apply(input: String) = Consumed(v, input)
}
def rescue(p: Parser[T]) : Parser[T] = {
new Parser[T] {
def apply(input: String) : Result[T] = {
Parser.this(input) match {
case x@Consumed(_, _) => x
case Failed(desc) => p(input)
}
}
}
}
}
object Parsers {
def stringP(s: String) = new Parser[String] {
def apply(input: String) = {
if(input.startsWith(s)) Consumed(s, input.substring(s.length))
else Failed[String]("Could not match on " + s)
}
}
def digit() = new Parser[String] {
def apply(input: String) = {
if(input.length > 0 && input.charAt(0) >= '0' && input.charAt(0) <= '9')
Consumed(input.substring(0,1), input.substring(1))
else
Failed[String]("Could not match on digit")
}
}
def many1[A](p : Parser[A]) : Parser[List[A]] = p flatMap ((a) => (many1(p) rescue p.value(List.empty)) map ((lst) => List(a) ::: lst))
def sepBy[A, B](sep: Parser[A], p: Parser[B]) = many1(p flatMap (v => sep map (_ => v))) flatMap (lst => p map ((a) => List(a) ::: lst))
def number = many1(digit()) map (_.reduce(_ ++ _)) map Integer.parseInt
def parens[A](p: Parser[A]) = stringP("(") flatMap (x => p) flatMap (v => stringP(")") map (_ => v))
def comma = stringP(",")
def spaces = many1(stringP(" "))
}
object Main {
def main(args : Array[String]) {
import Parsers._
// parse numbers seperated by comma and then sum it.
val parser1 = parens(sepBy(comma, number) map (_.sum))
println(parser1("(1000,100,10,1)")) // this prints "1111"
val parser2 = for {
a <- number
_ <- spaces
b <- number
} yield a * b
println(parser2("9 11")) // prints "99"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment