Last active
August 29, 2015 14:01
-
-
Save jasonjckn/5c7d537042343d49fff6 to your computer and use it in GitHub Desktop.
combinator parser in scala
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
// 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