Skip to content

Commit

Permalink
Math tests work
Browse files Browse the repository at this point in the history
  • Loading branch information
Li Haoyi committed Apr 29, 2015
1 parent da96498 commit 00e742f
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 18 deletions.
38 changes: 29 additions & 9 deletions parsing/src/main/scala/parsing/Parsing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ sealed trait Res[+T]
object Res{
case class Success[T](t: T, index: Int, cut: Boolean = false) extends Res[T]{
override def toString = {
s"Success($index)"
s"Success($index, $cut)"
}
}

Expand Down Expand Up @@ -50,17 +50,17 @@ object Res{
def verboseTrace = {
val body =
for((index, p) <- stack)
yield s"$index\t...${literalize(input.substring(index, index+5))}\t$p"
yield s"$index\t...${literalize(input.slice(index, index+5))}\t$p"
body.mkString("\n")
}
def trace = {
val body =
for((index, p) <- stack)
yield s"$p:$index"
val lastIndex = fullStack.last._1
body.mkString(" / ") + " ..." + literalize(input.substring(lastIndex, lastIndex+10))
body.mkString(" / ") + " ..." + literalize(input.slice(lastIndex, lastIndex+10))
}
override def toString = s"Failure($trace)"
override def toString = s"Failure($trace, $cut)"
}
}
import Res._
Expand Down Expand Up @@ -95,7 +95,7 @@ sealed trait Parser[+T]{
/**
* Parses using this or the parser `p`
*/
def |[T1 >: T, V <: T1](p: Parser[V]) = Parser.Either(this, p)
def |[V >: T](p: Parser[V]): Parser[V] = Parser.Either[V](this, p)

/**
* Parses using this followed by the parser `p`
Expand All @@ -115,14 +115,26 @@ sealed trait Parser[+T]{

def ! = Parser.Capturing(this)

def map[V](f: T => V): Parser[V] = Parser.Mapper(this, f)

protected def fail(input: String, index: Int) =
Failure(input, (index -> this) :: Nil, cut=false)
protected def failMore(f: Failure, index: Int, cut: Boolean = false) =
Failure(f.input, (index -> this) :: f.fullStack, cut=f.cut || cut)
}

object Parser{

case class Mapper[T, V](p: Parser[T], f: T => V) extends Parser[V]{
def parse(input: String, index: Int) = {
p.parse(input, index) match{
case s: Success[T] =>
println("!!! " + s.t)
println("??? " + f(s.t))
Success(f(s.t), s.index, s.cut)
case f: Failure => failMore(f, index)
}
}
}
/**
* A parser that always succeeds, consuming no input
*/
Expand Down Expand Up @@ -287,7 +299,8 @@ object Parser{
def parse(input: String, index: Int) = {
p1.parse(input, index) match{
case f: Failure => failMore(f, index, cut = f.cut)
case s1: Success[_] => p2.parse(input, s1.index) match{
case s1: Success[_] =>
p2.parse(input, s1.index) match{
case f: Failure => failMore(f, index, cut = cut || f.cut || s1.cut)
case s2: Success[_] => Success(ev(s1.t, s2.t), s2.index, s2.cut || s1.cut | cut)
}
Expand Down Expand Up @@ -347,20 +360,21 @@ object Parser{
* Parses using one parser or the other, if the first one fails. Returns
* the first one that succeeds and fails if both fail
*/
case class Either[+T, +V1 <: T, +V2 <: T](p1: Parser[V1], p2: Parser[V2]) extends Parser[T]{
case class Either[V](p1: Parser[V], p2: Parser[V]) extends Parser[V]{
def parse(input: String, index: Int) = {
p1.parse(input, index) match{
case s: Success[_] => s
case f: Failure if f.cut => failMore(f, index)
case _ => p2.parse(input, index) match{
case s: Success[_] => s
case f: Failure if f.cut => failMore(f, index)
case f: Failure => fail(input, index)
}
}
}
override def toString = {
def rec(p: Parser[_]): String = p match {
case p: Either[_, _, _] => rec(p.p1) + " | " + rec(p.p2)
case p: Either[_] => rec(p.p1) + " | " + rec(p.p2)
case p => p.toString
}
"(" + rec(this) + ")"
Expand All @@ -387,6 +401,9 @@ object Parser{
else if (uberSet(input(index))) Res.Success(input(index), index + 1)
else fail(input, index)
}
override def toString = {
s"CharSets(${literalize(strings.flatten.mkString)})"
}
}

/**
Expand Down Expand Up @@ -427,6 +444,9 @@ object Parser{
}
rec(0, bitSet, fail(input, index))
}
override def toString = {
s"CharTrie(${strings.map(literalize(_)).mkString(",")})"
}
}

}
Expand Down
2 changes: 2 additions & 0 deletions parsing/src/main/scala/parsing/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ package object parsing {

val Pass = Parser.Pass
val Fail = Parser.Fail
val Start = Parser.Start
val End = Parser.End
val CharPred = Parser.CharPred
val CharSets = Parser.CharSets
val Dispatcher = Parser.CharTrie
Expand Down
102 changes: 93 additions & 9 deletions parsing/src/test/scala/parsing/JsonTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object JsonTests extends TestSuite{
val unicodeEscape = R( "u" ~ hexDigit ~ hexDigit ~ hexDigit ~ hexDigit )
val escape = R( "\\" ~ (CharSets("\"/\\bfnrt") | unicodeEscape) )
val string = R( space.? ~ "\"" ~ (!"\"" ~ AnyChar | escape).rep ~ "\"")
val array = R( "[" ~! jsonExpr ~ ("," ~! jsonExpr).rep ~ space.? ~ "]")
val array = R( ("[" ~! jsonExpr) ~ ("," ~! jsonExpr).rep ~ space.? ~ "]")
val pair = R( string ~! ":" ~! jsonExpr )
val obj = R( "{" ~! pair ~ ("," ~! pair).rep ~ space.? ~ "}" )
val jsonExpr: Parser[_] = R(space.? ~ (obj | array | string | `true` | `false` | `null` | number) ~ space.?)
Expand Down Expand Up @@ -68,7 +68,7 @@ object JsonTests extends TestSuite{
assert(error == expected)
}
}
check(
* - check(
"""
}
"firstName": "John",
Expand Down Expand Up @@ -96,7 +96,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / (obj | array | string | true | false | null | number):9 ..."}\n "
"""
)
check(
* - check(
"""
{
firstName": "John",
Expand Down Expand Up @@ -124,7 +124,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / obj:9 / pair:10 / string:10 / "\"":23 ..."firstName\""
"""
)
check(
* - check(
"""
{
"firstName" "John",
Expand Down Expand Up @@ -152,7 +152,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / obj:9 / pair:10 / ":":34 ..." \"John\",\n "
"""
)
check(
* - check(
"""
{
"firstName": "John,
Expand Down Expand Up @@ -180,7 +180,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / obj:9 / "}":56 ..."lastName\":"
"""
)
check(
* - check(
"""
{
"firstName": "John",
Expand Down Expand Up @@ -208,7 +208,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / obj:9 / "}":154 ...": \"21 2nd "
"""
)
check(
* - check(
"""
{
"firstName": "John",
Expand Down Expand Up @@ -236,7 +236,7 @@ object JsonTests extends TestSuite{
jsonExpr:0 / obj:9 / pair:438 / string:438 / "\"":455 ..."{\n "
"""
)
check(
* - check(
"""
{
"firstName": "John",
Expand All @@ -248,7 +248,7 @@ object JsonTests extends TestSuite{
"state": "NY",
"postalCode": 10021
},
"phoneNumbers": [
"phoneNumbers":
{
"type" "home",
"number": "212 555-1234"
Expand All @@ -261,7 +261,91 @@ object JsonTests extends TestSuite{
}
""",
"""
jsonExpr:0 / obj:9 / pair:292 / jsonExpr:320 / obj:337 / pair:338 / ":":365 ..." \"home\",\n "
"""
)
* - check(
"""
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": 10021
},
"phoneNumbers": [
{
"type": "home",
"number": 212 555-1234
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}
""",
"""
jsonExpr:0 / obj:9 / pair:292 / jsonExpr:320 / array:321 / jsonExpr:322 / obj:339 / "}":411 ..."555-1234\n "
"""
)
* - check(
"""
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": 10021
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": 646 555-4567
}
]
}
""",
"""
jsonExpr:0 / obj:9 / pair:292 / jsonExpr:320 / array:321 / jsonExpr:440 / obj:457 / "}":528 ..."555-4567\n "
"""
)
* - check(
"""
{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": 10021
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
}
""",
"""
jsonExpr:0 / obj:9 / pair:292 / jsonExpr:320 / array:321 / "]":566 ..."}\n "
"""
)
}
Expand Down
38 changes: 38 additions & 0 deletions parsing/src/test/scala/parsing/MathTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package parsing

import utest._

object MathTests extends TestSuite{
val number = R( CharSets('0'to'9').rep1.!.map(_.toInt) )
val parens = R( "(" ~ addSub ~ ")" )
val factor = R( number | parens )

val divMul: Parser[Int] = R( factor ~ (CharSets("*/").! ~ factor).rep ).map{
case (a, s) => s.foldLeft(a){
case (left, ("*", right)) => left * right
case (left, ("/", right)) => left / right
}
}
val addSub: Parser[Int] = R( divMul ~ (CharSets("+-").! ~ divMul).rep ).map{
case (a, s) => s.foldLeft(a){
case (left, ("+", right)) => left + right
case (left, ("-", right)) => left - right
}
}
val expr = R( addSub ~ End )
val tests = TestSuite{
'pass {
def check(str: String, num: Int) = {
val res = expr.parse(str, 0)
assert(res == Res.Success(num, str.length))
}
check("1+1", 2)
check("1+1*2", 3)
check("(1+1*2)+(3*4*5)", 63)
check("15/3", 5)
check("63/3", 21)
check("(1+1*2)+(3*4*5)/20", 6)
check("((1+1*2)+(3*4*5))/3", 21)
}
}
}

0 comments on commit 00e742f

Please sign in to comment.