Skip to content

Commit

Permalink
curried chains, closing #172
Browse files Browse the repository at this point in the history
  • Loading branch information
j-mie6 committed Jan 10, 2024
1 parent 3ba4964 commit 342f582
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 91 deletions.
10 changes: 5 additions & 5 deletions docs/api-guide/expr/chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ operators. As an example:

> <h3>`p op p op p op p op p`</h3>
The above can be parsed using `chain.left1(p, op)` to have the
The above can be parsed using `chain.left1(p)(op)` to have the
effect of parsing like:

> <h3>`(((p op p) op p) op p) op p`</h3>
It can also be parded using `chain.right1(p, op)` to have the
It can also be parded using `chain.right1(p)(op)` to have the
effect of parsing like:

> <h3>`p op (p op (p op (p op p)))`</h3>
Expand All @@ -56,7 +56,7 @@ Given input of shape:

> <h3>`p op op op op`</h3>
The combinator `postfix p op` will parse the input and apply
The combinator `postfix(p)(op)` will parse the input and apply
results such that it would look like:

> <h3>`(((p op) op) op) op`</h3>
Expand All @@ -65,7 +65,7 @@ Similarly, given input of shape:

> <h3>`op op op op p`</h3>
The combinator `prefix op p` will parse the input and apply
The combinator `prefix(p)(op)` will parse the input and apply
results such that it would look like:

> <h3>`op (op (op (op p)))`</h3>
Expand All @@ -84,7 +84,7 @@ or not. This is enforced by the type signature of the operations
themselves:

```scala
def prefix1[A, B <: A](op: Parsley[A => B], p: Parsley[A]): Parsley[B]
def prefix1[A, B <: A](p: Parsley[A])(op: Parsley[A => B]): Parsley[B]
```

Given that `B` is a subtype of `A`, it is not possible for the
Expand Down
4 changes: 2 additions & 2 deletions docs/api-guide/expr/infix.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ sealed trait Expr
case class Add(x: Expr, y: Num) extends Expr
case class Num(n: Int) extends Expr

lazy val expr = infix.left1(num, "+".as(Add(_, _)))
lazy val expr = infix.left1(num)("+".as(Add(_, _)))
lazy val num = digit.foldLeft1(0)((n, d) => n * 10 + d.asDigit).map(Num(_))
expr.parse("56+43+123")
```
Expand All @@ -62,7 +62,7 @@ left, but `Num` must appear on the right. As `Num` and `Add`
share a common supertype `Expr`, this is what the chains will return. To illustrate what happens if `right1` was used instead:

```scala mdoc:fail
lazy val badExpr = infix.right1(num, "+".as(Add(_, _)))
lazy val badExpr = infix.right1(num)("+".as(Add(_, _)))
```

Notice that the error message here refers to the type `(A, C) => B`. In practice, to help reduce type ascriptions, the explicit `C >: B` is used as well -- in the working example, `C` is `Expr`, `A` is `Num`, and `B` is `Add`. The wrap is actually `A => C`, which in this case is provided by `Num <:< Expr`.
2 changes: 1 addition & 1 deletion docs/api-guide/generic.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ like in [chain](expr/chain.md) or [precedence](expr/precedence.md) combinators:
import parsley.expr.chain
import parsley.syntax.character.stringLift

val term = chain.left1(px, Add.from("+")) // or `Add <# "+"`
val term = chain.left1(px)(Add.from("+")) // or `Add <# "+"`
```

They are analogous to the `as` and `#>` combinators respectively.
Expand Down
16 changes: 7 additions & 9 deletions docs/tutorial/building-expression-parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,16 +125,14 @@ val number = digit.foldLeft1[Int](0)((n, d) => n * 10 + d.asDigit)
val add = (x: Int, y: Int) => x + y
val sub = (x: Int, y: Int) => x - y

// chain.left1[A](p: Parsley[A], op: Parsley[(A, A) => A]): Parsley[A]
lazy val expr: Parsley[Int] = chain.left1(term, '+'.as(add) | '-'.as(sub))
lazy val term = chain.left1[Int](atom, '*' as (_ * _))
// chain.left1[A](p: Parsley[A])(op: Parsley[(A, A) => A]): Parsley[A]
lazy val expr: Parsley[Int] = chain.left1(term)('+'.as(add) | '-'.as(sub))
lazy val term = chain.left1(atom)('*' as (_ * _))
lazy val atom = '(' ~> expr <~ ')' | number
```

The structure of the parser is roughly the same, however now you'll notice that `expr` and `term`
are no longer self-recursive, and neither `term` nor `atom` need to be lazy (or have explicit types).
Just to illustrate, if we provide the type argument to `chain.left1` we can continue to use
`_ * _`, but without it, we need explicit type signatures: see `add` and `sub`.

@:todo(The first type parameter represents the type of the `atom`s, and the second is the type of the `term`
itself: unlike mainstream parser combinator libraries in Haskell, Parsley allows these types to vary \(see
Expand All @@ -144,10 +142,10 @@ To make the relationship very clear between what we had before and what we have
the transformation from recursive to `chains` follows these shape:

```scala
self <**> (op <*> next) | next == chain.left1(next, op) // flipped op
self <**> op <*> next | next == chain.left1(next, op) // normal op
next <**> (op <*> self </> identity) == chain.right1(next, op) // no backtracking, flipped
atomic(next <**> op <*> self) | next == chain.right1(next, op) // backtracking, normal op
self <**> (op <*> next) | next == chain.left1(next)(op) // flipped op
self <**> op <*> next | next == chain.left1(next)(op) // normal op
next <**> (op <*> self </> identity) == chain.right1(next)(op) // no backtracking, flipped
atomic(next <**> op <*> self) | next == chain.right1(next)(op) // backtracking, normal op
```

In this parser, the nesting of the chains dictates the precedence order (again, terms are found _inside_
Expand Down
4 changes: 2 additions & 2 deletions docs/tutorial/interlude-1-haskell.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ lazy val `<pat-naked>`: Parsley[PatNaked] =
| PatTuple("(" ~> sepBy1(`<pat>`, ",") <~ ")")
| PatList("[" ~> sepBy(`<pat>`, ",") <~ "]")
)
lazy val `<pat>` = infix.right1(`<pat-paren>`, PatCons from ":")
lazy val `<pat>` = infix.right1(`<pat-paren>`)(PatCons from ":")
lazy val `<pat-paren>` = atomic(`<pat-app>`) | `<pat-naked>`
lazy val `<pat-app>` = PatApp(`<pat-con>`, some(`<pat-naked>`))
lazy val `<pat-con>` = ( atomic("(" ~> (ConsCon from ":") <~ ")")
Expand Down Expand Up @@ -503,7 +503,7 @@ cases: there isn't much more to say until we try and deal with much more complex
features.

```scala mdoc
lazy val `<type>`: Parsley[Type] = infix.right1(`<type-app>`, FunTy from "->")
lazy val `<type>`: Parsley[Type] = infix.right1(`<type-app>`)(FunTy from "->")
lazy val `<type-app>` = `<type-atom>`.reduceLeft(TyApp)
lazy val `<type-atom>` = ( `<type-con>` | `<var-id>` | (UnitTy from "()")
| ListTy("[" ~> `<type>` <~ "]")
Expand Down
4 changes: 2 additions & 2 deletions parsley/shared/src/main/scala/parsley/Parsley.scala
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ final class Parsley[+A] private [parsley] (private [parsley] val internal: front
* @return a parser which parses this parser many times and folds the results together with `f` and `k` right-associatively.
* @group fold
*/
def foldRight[B](k: B)(f: (A, B) => B): Parsley[B] = chain.prefix(this.map(f.curried), pure(k))
def foldRight[B](k: B)(f: (A, B) => B): Parsley[B] = chain.prefix(pure(k))(this.map(f.curried))
/** This combinator will parse this parser '''zero''' or more times combining the results with the function `f` and base value `k` from the left.
*
* This parser will continue to be parsed until it fails having '''not consumed''' input.
Expand Down Expand Up @@ -781,7 +781,7 @@ final class Parsley[+A] private [parsley] (private [parsley] val internal: front
* @since 2.3.0
* @group fold
*/
def reduceLeft[B >: A](op: (B, A) => B): Parsley[B] = infix.left1(this, pure(op))
def reduceLeft[B >: A](op: (B, A) => B): Parsley[B] = infix.left1(this)(pure(op))
/** This combinator will parse this parser '''zero''' or more times combining the results left-associatively with the function `op`.
*
* This parser will continue to be parsed until it fails having '''not consumed''' input.
Expand Down
10 changes: 5 additions & 5 deletions parsley/shared/src/main/scala/parsley/expr/Fixity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ sealed trait Fixity {
*/
case object InfixL extends Fixity {
override type Op[-A, B] = (B, A) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.left1(p, op)
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.left1(p)(op)
}

/**
Expand All @@ -35,7 +35,7 @@ case object InfixL extends Fixity {
*/
case object InfixR extends Fixity {
override type Op[-A, B] = (A, B) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.right1(p, op)
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.right1(p)(op)
}

/**
Expand All @@ -45,7 +45,7 @@ case object InfixR extends Fixity {
*/
case object Prefix extends Fixity {
override type Op[A, B] = B => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.prefix(op, p)
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.prefix(p)(op)
}

/**
Expand All @@ -55,7 +55,7 @@ case object Prefix extends Fixity {
*/
case object Postfix extends Fixity {
override type Op[A, B] = B => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.postfix(p, op)
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.postfix(p)(op)
}

/**
Expand All @@ -65,5 +65,5 @@ case object Postfix extends Fixity {
*/
case object InfixN extends Fixity {
override type Op[-A, +B] = (A, A) => B
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.nonassoc(p, op)
private [expr] def chain[A, B](p: Parsley[A], op: Parsley[Op[A, B]])(implicit wrap: A => B): Parsley[B] = infix.nonassoc(p)(op)
}
32 changes: 16 additions & 16 deletions parsley/shared/src/main/scala/parsley/expr/chain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ object chain {
* scala> sealed trait Expr
* scala> case class Add(x: Expr, y: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.right1(digit.map(d => Num(d.asDigit)), char('+').as(Add))
* scala> val expr = chain.right1(digit.map(d => Num(d.asDigit)))(char('+').as(Add))
* scala> expr.parse("1+2+3+4")
* val res0 = Success(Add(Num(1), Add(Num(2), Add(Num(3), Num(4)))))
* scala> expr.parse("")
Expand All @@ -52,7 +52,7 @@ object chain {
* @since 4.0.0
* @group binary
*/
def right1[A](p: Parsley[A], op: =>Parsley[(A, A) => A]): Parsley[A] = infix.right1(p, op)
def right1[A](p: Parsley[A])(op: =>Parsley[(A, A) => A]): Parsley[A] = infix.right1(p)(op)

/** This combinator handles left-associative parsing, and application of, '''zero''' or more binary operators between '''one''' or more values.
*
Expand All @@ -66,7 +66,7 @@ object chain {
* scala> sealed trait Expr
* scala> case class Add(x: Expr, y: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.left1(digit.map(d => Num(d.asDigit)), char('+').as(Add))
* scala> val expr = chain.left1(digit.map(d => Num(d.asDigit)))(char('+').as(Add))
* scala> expr.parse("1+2+3+4")
* val res0 = Success(Add(Add(Add(Num(1), Num(2)), Num(3)), Num(4)))
* scala> expr.parse("")
Expand All @@ -80,7 +80,7 @@ object chain {
* @since 4.0.0
* @group binary
*/
def left1[A](p: Parsley[A], op: =>Parsley[(A, A) => A]): Parsley[A] = infix.left1(p, op)
def left1[A](p: Parsley[A])(op: =>Parsley[(A, A) => A]): Parsley[A] = infix.left1(p)(op)

/** This combinator handles right-associative parsing, and application of, '''zero''' or more binary operators between '''zero''' or more values.
*
Expand All @@ -95,7 +95,7 @@ object chain {
* scala> sealed trait Expr
* scala> case class Add(x: Expr, y: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.right(digit.map(d => Num(d.asDigit)), char('+').as(Add), Num(0))
* scala> val expr = chain.right(digit.map(d => Num(d.asDigit)))(char('+').as(Add), Num(0))
* scala> expr.parse("1+2+3+4")
* val res0 = Success(Add(Num(1), Add(Num(2), Add(Num(3), Num(4)))))
* scala> expr.parse("")
Expand All @@ -111,7 +111,7 @@ object chain {
* @since 4.0.0
* @group binary
*/
def right[A](p: Parsley[A], op: =>Parsley[(A, A) => A], x: A): Parsley[A] = infix.right(p, op, x) // TODO: right(x)(p, op)?
def right[A](p: Parsley[A])(op: =>Parsley[(A, A) => A], x: A): Parsley[A] = infix.right(p)(op, x)

/** This combinator handles left-associative parsing, and application of, '''zero''' or more binary operators between '''zero''' or more values.
*
Expand All @@ -126,7 +126,7 @@ object chain {
* scala> sealed trait Expr
* scala> case class Add(x: Expr, y: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.left(digit.map(d => Num(d.asDigit)), char('+').as(Add), Num(0))
* scala> val expr = chain.left(digit.map(d => Num(d.asDigit)))(char('+').as(Add), Num(0))
* scala> expr.parse("1+2+3+4")
* val res0 = Success(Add(Add(Add(Num(1), Num(2)), Num(3)), Num(4)))
* scala> expr.parse("")
Expand All @@ -142,7 +142,7 @@ object chain {
* @since 4.0.0
* @group binary
*/
def left[A](p: Parsley[A], op: =>Parsley[(A, A) => A], x: A): Parsley[A] = infix.left(p, op, x) // TODO: left(x)(p, op)?
def left[A](p: Parsley[A])(op: =>Parsley[(A, A) => A], x: A): Parsley[A] = infix.left(p)(op, x)

/** This combinator handles right-assocative parsing, and application of, '''zero''' or more prefix unary operators to a single value.
*
Expand All @@ -158,7 +158,7 @@ object chain {
* scala> case class Negate(x: Expr) extends Expr
* scala> case class Id(x: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.prefix(char('-').as(Negate) <|> char('+').as(Id), digit.map(d => Num(d.asDigit)))
* scala> val expr = chain.prefix(digit.map(d => Num(d.asDigit))(char('-').as(Negate) <|> char('+').as(Id))
* scala> expr.parse("--+1")
* val res0 = Success(Negate(Negate(Id(Num(1)))))
* scala> expr.parse("1")
Expand All @@ -173,7 +173,7 @@ object chain {
* @since 2.2.0
* @group unary
*/
def prefix[A](op: Parsley[A => A], p: Parsley[A]): Parsley[A] = new Parsley(new frontend.ChainPre(p.internal, op.internal))
def prefix[A](p: Parsley[A])(op: Parsley[A => A]): Parsley[A] = new Parsley(new frontend.ChainPre(p.internal, op.internal))

/** This combinator handles left-assocative parsing, and application of, '''zero''' or more postfix unary operators to a single value.
*
Expand All @@ -189,7 +189,7 @@ object chain {
* scala> case class Inc(x: Expr) extends Expr
* scala> case class Dec(x: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.postfix(digit.map(d => Num(d.asDigit)), string("++").as(Inc) <|> string("--").as(Dec))
* scala> val expr = chain.postfix(digit.map(d => Num(d.asDigit)))(string("++").as(Inc) <|> string("--").as(Dec))
* scala> expr.parse("1++----")
* val res0 = Success(Dec(Dec(Inc(Num(1)))))
* scala> expr.parse("1")
Expand All @@ -204,7 +204,7 @@ object chain {
* @since 2.2.0
* @group unary
*/
def postfix[A](p: Parsley[A], op: =>Parsley[A => A]): Parsley[A] = new Parsley(new frontend.ChainPost(p.internal, op.internal))
def postfix[A](p: Parsley[A])(op: =>Parsley[A => A]): Parsley[A] = new Parsley(new frontend.ChainPost(p.internal, op.internal))

/** This combinator handles right-assocative parsing, and application of, '''one''' or more prefix unary operators to a single value.
*
Expand All @@ -220,7 +220,7 @@ object chain {
* scala> case class Negate(x: Expr) extends Expr
* scala> case class Id(x: Expr) extends Expr
* scala> case class Num(x: Int) extends Expr
* scala> val expr = chain.prefix1(char('-').as(Negate) <|> char('+').as(Id), digit.map(d => Num(d.asDigit)))
* scala> val expr = chain.prefix1(digit.map(d => Num(d.asDigit)))(char('-').as(Negate) <|> char('+').as(Id))
* scala> expr.parse("--+1")
* val res0 = Success(Negate(Negate(Id(Num(1)))))
* scala> expr.parse("1")
Expand All @@ -235,7 +235,7 @@ object chain {
* @since 3.0.0
* @group unary
*/
def prefix1[A, B <: A](op: Parsley[A => B], p: =>Parsley[A]): Parsley[B] = op <*> prefix(op, p)
def prefix1[A, B <: A](p: =>Parsley[A])(op: Parsley[A => B]): Parsley[B] = op <*> prefix(p)(op)

/** This combinator handles left-assocative parsing, and application of, '''one''' or more postfix unary operators to a single value.
*
Expand Down Expand Up @@ -266,8 +266,8 @@ object chain {
* @since 3.0.0
* @group unary
*/
def postfix1[A, B <: A](p: Parsley[A], op: =>Parsley[A => B]): Parsley[B] = {
def postfix1[A, B <: A](p: Parsley[A])(op: =>Parsley[A => B]): Parsley[B] = {
lazy val op_ = op
postfix(p <**> op_, op_)
postfix(p <**> op_)(op_)
}
}
Loading

0 comments on commit 342f582

Please sign in to comment.