Skip to content
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

Merged
merged 4 commits into from
Oct 9, 2019
Merged

v.0.2.0 continued #52

merged 4 commits into from
Oct 9, 2019

Conversation

sergei-shabanau
Copy link
Member

@sergei-shabanau sergei-shabanau commented Oct 1, 2019

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.

@sergei-shabanau sergei-shabanau changed the title #49 - State example works 🎉🎉🎉 v.0.2.0 continued Oct 1, 2019
@codecov-io
Copy link

codecov-io commented Oct 1, 2019

Codecov Report

Merging #52 into master will increase coverage by 0.3%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
...ain/scala/org/spartanz/parserz/ParsersModule.scala 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c2d42d2...06a6487. Read the comment docs.

@jdegoes
Copy link

jdegoes commented Oct 6, 2019

Will take a look tomorrow, sorry for the delay on this!

@sergei-shabanau
Copy link
Member Author

Thanks @jdegoes !
It's based on the code you showed me in Boulder, with all the functionality from v.0.1 implemented with that new approach.

Copy link
Collaborator

@soujiro32167 soujiro32167 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW =]

Copy link
Collaborator

@ryanonsrc ryanonsrc left a 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)
Copy link
Collaborator

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)
Copy link
Collaborator

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).

Copy link
Collaborator

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

Copy link
Member Author

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!

Copy link
Collaborator

@ryanonsrc ryanonsrc left a 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)
Copy link
Collaborator

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] =
Copy link

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](
Copy link

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](
Copy link

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]
Copy link

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.

Copy link
Member Author

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 Maps 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.

Copy link

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)
Copy link

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)(
Copy link

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] =
Copy link

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))
Copy link

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.

Copy link
Member Author

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

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!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#53

Copy link

@jdegoes jdegoes left a 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!

@sergei-shabanau
Copy link
Member Author

@jdegoes
I'd love to get more feedback from you, maybe we can have a review session at some point during ScalaIO?
Thanks again!

@jdegoes
Copy link

jdegoes commented Oct 9, 2019

@sergei-shabanau Yes, let's do it, I'll be around the whole conference!

@sergei-shabanau sergei-shabanau merged commit e73afe1 into master Oct 9, 2019
@sergei-shabanau sergei-shabanau deleted the 49-v.0.2.0-cont branch October 9, 2019 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

V.0.2
6 participants