-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v.0.2.0 continued #52
Conversation
Codecov Report
@@ Coverage Diff @@
## master #52 +/- ##
=========================================
+ Coverage 97.59% 97.89% +0.3%
=========================================
Files 7 7
Lines 291 333 +42
Branches 7 9 +2
=========================================
+ Hits 284 326 +42
Misses 7 7
Continue to review full report at Codecov.
|
Will take a look tomorrow, sorry for the delay on this! |
Thanks @jdegoes ! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW =]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there. So, I am now returning from being "off the grid" so I will now take a look.
final def mapOptional[SI1 <: SI, SO1 >: SO, E1 >: E, B]( | ||
es: SI1 => (SO1, E1) | ||
)(to: A => Option[B], from: B => Option[A]): Grammar[SI1, SO1, E1, B] = | ||
MapES[SI1, SO1, E1, A, B](self, es, to, from) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between MapS
and MapES
?
@@ -195,6 +268,27 @@ trait ParsersModule { | |||
from(a._2).fold(e => s -> Left(e), b => printer(value)(s, (a._1, b))) | |||
} | |||
|
|||
case Grammar.MapS(value, _, from) => | |||
(s: S, a: (Input, A)) => { | |||
val (s1, res1) = from(s, a._2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tend to favor using x._n
notation sparingly. Especially here, I think it might help with clarity (at the cost of brevity) to split a
into Input
and A
values (perhaps call them ai
and aa
respectively).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... In fact, I notice you doing this above. I'd argue in favor of doing the same here. for at the very least: consistency's sake
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ryanonsrc I did some renaming as you suggested, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added some comments. I can't say that I fully grok the new impl. yet but that's ok, I do like to see this moving towards a more user-friendly implementation. Approving from my end but curious to hear others, and @jdegoes to chime in.
@@ -195,6 +268,27 @@ trait ParsersModule { | |||
from(a._2).fold(e => s -> Left(e), b => printer(value)(s, (a._1, b))) | |||
} | |||
|
|||
case Grammar.MapS(value, _, from) => | |||
(s: S, a: (Input, A)) => { | |||
val (s1, res1) = from(s, a._2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... In fact, I notice you doing this above. I'd argue in favor of doing the same here. for at the very least: consistency's sake
mapPartial[E1, A](e)({ case a if f(a) => a }, { case a if f(a) => a }) | ||
Map[SI, SO, E1, A, A](self, asEither(e)(Some(_).filter(f)), asEither(e)(Some(_).filter(f))) | ||
|
||
final def mapS[SI1 <: SI, SO1 >: SO, B](to: (SI1, A) => (SO1, B), from: (SI1, B) => (SO1, A)): Grammar[SI1, SO1, E, B] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd tend toward the more verbose, and call this mapStatefully
.
{ case (si, b) => val (so, a) = from(si, b); (so, Right(a)) } | ||
) | ||
|
||
final def mapPartialS[SI1 <: SI, SO1 >: SO, E1 >: E, B]( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapStatefullyPartial
)(to: (SI1, A) =?> (SO1, B), from: (SI1, B) =?> (SO1, A)): Grammar[SI1, SO1, E1, B] = | ||
MapS[SI1, SO1, E1, A, B](self, asEither(es)(to.lift), asEither(es)(from.lift)) | ||
|
||
final def mapOptional[SI1 <: SI, SO1 >: SO, E1 >: E, B]( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapOption
@@ -52,6 +75,8 @@ trait ParsersModule { | |||
private[parserz] case class Delay[SI, SO, E, A](delayed: () => Grammar[SI, SO, E, A]) extends Grammar[SI, SO, E, A] | |||
private[parserz] case class Tag[SI, SO, E, A](value: Grammar[SI, SO, E, A], tag: String) extends Grammar[SI, SO, E, A] | |||
private[parserz] case class Map[SI, SO, E, A, B](value: Grammar[SI, SO, E, A], to: A => E \/ B, from: B => E \/ A) extends Grammar[SI, SO, E, B] | |||
private[parserz] case class MapS[SI, SO, E, A, B](value: Grammar[SI, SO, E, A], to: (SI, A) => (SO, E \/ B), from: (SI, B) => (SO, E \/ A)) extends Grammar[SI, SO, E, B] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one can be implemented in terms of MapES
, right? Also, Map
can be implemented in terms of MapES
.
Unless there is an advantage on the implementation side to separating these cases (and sometimes there is!), it may be best to support a relatively small set of operations, to make maintenance easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jdegoes
Hi John! Thanks a lot for the review!
The reason I have several Map
s is the way state type parameters SI
and SO
are defined.
I cannot implement Map
in terms of MapS
because Map
captures functions like A => B
or A => Option[B]
, but MapS
is about functions (SI, A) => (SO, B)
etc.
I cannot build such function from just A => B
because I have no knowledge of how to go from SI
to SO
since those types are completely unrelated.
Thus Map
and MapS
have to be handled at the implementation level, where SI
and SO
are restricted further.
MapES
is there for convenient use case: when mapping functions are not dealing with state, but error manipulations require state changes. That is why MapES
captures SI => (SO, E)
.
I faced the same problem of not knowing how to go from SI
to SO
when trying to express it in terms of MapS
(unless asking for extra function from user, and that wasn't friendly), and eventually ended up with 3 different mapping constructs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. 👍
@@ -67,12 +92,27 @@ trait ParsersModule { | |||
final def fail[E, A](e: E): Grammar[Any, Nothing, E, A] = | |||
unit.mapPartial(e)(PartialFunction.empty, PartialFunction.empty) | |||
|
|||
final def fail[SI, SO, E, A](es: SI => (SO, E)): Grammar[SI, SO, E, A] = | |||
unit.mapPartial(es)(PartialFunction.empty, PartialFunction.empty) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe failStatefully
, so you can have straight-up fail
that doesn't change state.
final def consume[SI, SO, E, A]( | ||
to: (SI, Input) => (SO, E \/ (Input, A)), | ||
from: (SI, (Input, A)) => (SO, E \/ Input) | ||
): Grammar[SI, SO, E, A] = | ||
Consume(to, from) | ||
|
||
final def consumeOptional[SI, SO, E, A](e: E)( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consumeOption
?
val (so, r) = from(si, x); (so, r.map(Right(_)).getOrElse(Left(e))) | ||
} | ||
) | ||
|
||
final def consume0[E, A](to: Input => E \/ (Input, A), from: ((Input, A)) => E \/ Input): Grammar[Any, Nothing, E, A] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consumeOrFail
?
@@ -21,7 +21,30 @@ trait ParsersModule { | |||
Map[SI, SO, E1, A, B](self, asEither(e)(to.lift), asEither(e)(from.lift)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everywhere in this file, I'd make sure you are aggressively lazy, so we can have recursive grammars. It will require a trick (identity map) to derive the graph from that, but it can be done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have implemented recursive grammars with the explicit Delay
and corresponding combinator, please see
parserz/src/main/scala/org/spartanz/parserz/ParsersModule.scala
Lines 84 to 85 in c2d42d2
final def delay[SI, SO, E, A](g: => Grammar[SI, SO, E, A]): Grammar[SI, SO, E, A] = | |
Delay(() => g) |
I'll try to allow recursion out of the box, without additional work at the call site. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to do a full review sometime over the whole API and make final suggestions. But this is going to be one kick-ass parser combinator library!
@jdegoes |
@sergei-shabanau Yes, let's do it, I'll be around the whole conference! |
Closes #49
Added functionality and example of state being passed through the parser to track parser position and accumulate errors.
This brings the current version of library at par with version 0.1.