Skip to content

Instantly share code, notes, and snippets.

@lihaoyi
Last active November 6, 2017 05:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lihaoyi/51e4b50e550d32154a62c3e7cd401efd to your computer and use it in GitHub Desktop.
Save lihaoyi/51e4b50e550d32154a62c3e7cd401efd to your computer and use it in GitHub Desktop.
package fasterparser
import scala.collection.mutable
object Parse {
def main(args: Array[String]): Unit = {
def hello[_:Ctx] = P( "hello" )
def world[_:Ctx] = P( "world" )
def helloWorld[_:Ctx] = P( hello.! ~ (" " | ",").rep ~ world.! )
println(hello("hello ")) // Parsed.Success(())
println(helloWorld("hello ")) // Parsed.Failure(6)
println(helloWorld("hello world"))// Parsed.Success((hello,world))
println(helloWorld("hello world"))// Parsed.Success((hello,world))
println(helloWorld("hello, world"))// Parsed.Success((hello,world))
println(Json.jsonExpr("31337"))
println(Json.jsonExpr("31337"))
println(Json.jsonExpr("\"31337\""))
println(Json.jsonExpr("[true, 123]"))
println(Json.jsonExpr("""{"hello": [true, 123], "world": {"foo": {"bar": "baz"}}}"""))
}
type P[+T] = Parsed[T]
def P[T](t: => Parsed[T])(implicit ctx: Ctx[_], name: sourcecode.Name): Parsed[T] = {
ctx.stack.append(name.value)
val res = t
ctx.stack.remove(ctx.stack.length - 1)
res
}
implicit def strToParsed(s: String)(implicit ctx: Ctx[_]): Parsed[Unit] = {
if (ctx.input.startsWith(s, ctx.position)) {
ctx.position = ctx.position + s.length
ctx.isSuccess = true
val res = ctx.success.asInstanceOf[Parsed.Success[Unit]]
res.value = ()
res
}else{
ctx.isSuccess = false
ctx.failure.index = ctx.position
ctx.failure
}
}
implicit class EagerOps[S, T](parse0: S)(implicit conv: S => Parsed[T]){
def ~/[V, R](other: => Parsed[V])(implicit s: Implicits.Sequencer[T, V, R]): Parsed[R] = {
this ~ other
}
def ~/ : Parsed[T] = {
conv(parse0)
}
def ~[V, R](other: => Parsed[V])(implicit s: Implicits.Sequencer[T, V, R]): Parsed[R] = {
conv(parse0) match{
case f: Parsed.Failure => f
case p: Parsed.Success[T] =>
val pValue = p.value
other match{
case f: Parsed.Failure => f
case p: Parsed.Success[V] =>
val r = p.asInstanceOf[Parsed.Success[R]]
val applied = s.apply(pValue, p.value)
r.value = applied
r
}
}
}
}
implicit class ByNameOps[S, T](parse0: => S)(implicit conv: S => Parsed[T], ctx: Ctx[_]){
def rep[V](implicit repeater: Implicits.Repeater[T, V]): Parsed[V] = rep(sep=null)
def rep[V](sep: => Parsed[_] = null)(implicit repeater: Implicits.Repeater[T, V]): Parsed[V] = {
val acc = repeater.initial
def end() = {
val res = ctx.success.asInstanceOf[Parsed.Success[V]]
res.value = repeater.result(acc)
res
}
def rec(): Parsed[V] = {
conv(parse0) match{
case f: Parsed.Failure => end()
case s: Parsed.Success[T] =>
repeater.accumulate(s.value, acc)
val sep1 = sep
if (sep1 == null) rec()
else sep1 match{
case s: Parsed.Success[_] => rec()
case f: Parsed.Failure => end()
}
}
}
rec()
}
def log()(implicit name: sourcecode.Name): Parsed[T] = {
println("Starting " + name.value + " " + ctx.position)
val res = conv(parse0)
println("Ending " + name.value + " " + ctx.position + " " + ctx.isSuccess)
res
}
def ! : Parsed[String] = {
val startPos = ctx.position
conv(parse0) match{
case f: Parsed.Failure => f
case s: Parsed.Success[_] =>
val endPos= ctx.position
val ret = s.asInstanceOf[Parsed.Success[String]]
ret.value = ctx.input.substring(startPos, endPos)
ret
}
}
def ?[V](implicit optioner: Implicits.Optioner[T, V]): Parsed[V] = {
val startPos = ctx.position
val res = ctx.success.asInstanceOf[Parsed.Success[V]]
conv(parse0) match{
case f: Parsed.Failure =>
res.value = optioner.none
ctx.position = startPos
res
case s: Parsed.Success[T] =>
res.value = optioner.some(s.value)
res
}
}
def |[V >: T](other: => Parsed[V]): Parsed[V] = {
val startPos = ctx.position
conv(parse0) match {
case p: Parsed.Success[T] => p
case f: Parsed.Failure =>
other match{
case p: Parsed.Success[V] => p
case f: Parsed.Failure =>
f.index = startPos
f
}
}
}
}
def CharsWhileIn(s: String)(implicit ctx: Ctx[_]) = CharsWhile(s.toSet)
def CharIn(s: CharSequence*)(implicit ctx: Ctx[_]) = {
val set = s.flatMap(_.toString).toSet
def currentCharMatches = ctx.input.lift(ctx.position).exists(set)
if (!currentCharMatches) {
ctx.isSuccess = false
ctx.failure
}else{
ctx.position += 1
val res = ctx.success.asInstanceOf[Parsed.Success[Unit]]
res.value = ()
res
}
}
def CharsWhile(p: Char => Boolean)(implicit ctx: Ctx[_]) = {
def currentCharMatches = ctx.input.lift(ctx.position).exists(p)
if (!currentCharMatches) {
ctx.isSuccess = false
ctx.failure
}else{
while(currentCharMatches) ctx.position += 1
val res = ctx.success.asInstanceOf[Parsed.Success[Unit]]
res.value = ()
res
}
}
implicit def parseInputCtx(s: String): Ctx[_] = Ctx(s)
}
class Ctx[+_](val input: String,
var position: Int,
val stack: mutable.Buffer[String],
val success: Parsed.Success[_],
val failure: Parsed.Failure,
var isSuccess: Boolean)
object Ctx{
def apply(input: String) = {
new Ctx(input, 0, mutable.Buffer.empty, new Parsed.Success, new Parsed.Failure, true)
}
}
abstract class Parsed[+T](val isSuccess: Boolean){
def map[V](f: T => V): Parsed[V]
}
object Parsed{
class Success[T] extends Parsed[T](true){
var value: T = null.asInstanceOf[T]
def map[V](f: T => V) = {
val this2 = this.asInstanceOf[Success[V]]
this2.value = f(value)
this2
}
override def toString() = s"Parsed.Success($value)"
}
class Failure extends Parsed[Nothing](false){
var index = 0
override def toString() = s"Parsed.Failure($index)"
def map[V](f: Nothing => V) = this
}
}
object Js {
sealed trait Val extends Any {
def value: Any
def apply(i: Int): Val = this.asInstanceOf[Arr].value(i)
def apply(s: java.lang.String): Val =
this.asInstanceOf[Obj].value.find(_._1 == s).get._2
}
case class Str(value: java.lang.String) extends AnyVal with Val
case class Obj(value: (java.lang.String, Val)*) extends AnyVal with Val
case class Arr(value: Val*) extends AnyVal with Val
case class Num(value: Double) extends AnyVal with Val
case object False extends Val{
def value = false
}
case object True extends Val{
def value = true
}
case object Null extends Val{
def value = null
}
}
case class NamedFunction[T, V](f: T => V, name: String) extends (T => V){
def apply(t: T) = f(t)
override def toString() = name
}
object Json{
import Parse._
def StringChars[_:Ctx] = NamedFunction(!"\"\\".contains(_: Char), "StringChars")
def space[_:Ctx] = P( CharsWhileIn(" \r\n").? )
def digits[_:Ctx] = P( CharsWhileIn("0123456789"))
def exponent[_:Ctx] = P( CharIn("eE") ~ CharIn("+-").? ~ digits )
def fractional[_:Ctx] = P( "." ~ digits )
def integral[_:Ctx] = P( "0" | CharIn('1' to '9') ~ digits.? )
def number[_:Ctx] = P( CharIn("+-").? ~ integral ~ fractional.? ~ exponent.? ).!.map(
x => Js.Num(x.toDouble)
)
def `null`[_:Ctx] = P( "null" ).map(_ => Js.Null)
def `false`[_:Ctx] = P( "false" ).map(_ => Js.False)
def `true`[_:Ctx] = P( "true" ).map(_ => Js.True)
def hexDigit[_:Ctx] = P( CharIn('0'to'9', 'a'to'f', 'A'to'F') )
def unicodeEscape[_:Ctx] = P( "u" ~ hexDigit ~ hexDigit ~ hexDigit ~ hexDigit )
def escape[_:Ctx] = P( "\\" ~ (CharIn("\"/\\bfnrt") | unicodeEscape) )
def strChars[_:Ctx] = P( CharsWhile(StringChars) )
def string[_:Ctx] =
P( space ~ "\"" ~/ (strChars | escape).rep.! ~ "\"").map(Js.Str)
def array[_:Ctx] =
P( "[" ~/ jsonExpr.rep(sep=",".~/) ~ space ~ "]").map(Js.Arr(_:_*))
def pair[_:Ctx] = P( string.map(_.value) ~/ ":" ~/ jsonExpr )
def obj[_:Ctx] =
P( "{" ~/ pair.rep(sep=",".~/) ~ space ~ "}").map(Js.Obj(_:_*))
def jsonExpr[_:Ctx]: P[Js.Val] = P(
space ~ (obj | array | string | `true` | `false` | `null` | number) ~ space
)
}
@lihaoyi
Copy link
Author

lihaoyi commented Oct 4, 2017

Full repo, including build files and codegen https://github.com/lihaoyi/fasterparser

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment