@@ -471,10 +471,8 @@ defmodule NimbleParsec do
471471
472472 `parsec/2` is useful to implement recursive definitions.
473473
474- Note, while `parsec/2` can be used to compose smaller combinators,
475- the preferred mechanism for doing composition is via regular functions
476- and not via `parsec/2`. Let's see a practical example. Imagine
477- that you have this module:
474+ `parsec/2` is also useful to compile *large* combinators upfront,
475+ therefore reducing compilation time. Imagine that you have this module:
478476
479477 defmodule MyParser do
480478 import NimbleParsec
@@ -497,9 +495,8 @@ defmodule NimbleParsec do
497495 defparsec :datetime, date |> ignore(string("T")) |> concat(time), debug: true
498496 end
499497
500- Now imagine that you want to break `date` and `time` apart
501- into helper functions, as you use them in other occasions.
502- Generally speaking, you should **NOT** do this:
498+ If you use `date` and `time` as combinators, over and over again,
499+ it may be beneficial to compile and reuse them:
503500
504501 defmodule MyParser do
505502 import NimbleParsec
@@ -523,52 +520,16 @@ defmodule NimbleParsec do
523520 parsec(:date) |> ignore(string("T")) |> concat(parsec(:time))
524521 end
525522
526- The reason why the above is not recommended is because each
527- `parsec/2` combinator ends-up adding a stacktrace entry during
528- parsing, which affects the ability of `NimbleParsec` to optimize
529- code. If the goal is to compose combinators, you can do so
530- with modules and functions:
523+ While the `date` and `time` above are likely too small to be worthy
524+ of compiling into a separate combinator in practice, that's the general
525+ approach one should take. In practice, it is best to build your parser
526+ and combinators without `parsec/2`, and then, as compile-time increases,
527+ you find the largest combinators which are used multiple times and
528+ refactor them as `parsec/2`.
531529
532- defmodule MyParser.Helpers do
533- import NimbleParsec
534-
535- def date do
536- integer(4)
537- |> ignore(string("-"))
538- |> integer(2)
539- |> ignore(string("-"))
540- |> integer(2)
541- end
542-
543- def time do
544- integer(2)
545- |> ignore(string(":"))
546- |> integer(2)
547- |> ignore(string(":"))
548- |> integer(2)
549- |> optional(string("Z"))
550- end
551- end
552-
553- defmodule MyParser do
554- import NimbleParsec
555- import MyParser.Helpers
556-
557- defparsec :datetime,
558- date() |> ignore(string("T")) |> concat(time())
559- end
560-
561- The implementation above will be able to compile to the most
562- efficient format as possible without forcing new stacktrace
563- entries.
564-
565- The only situation where you should use `parsec/2` for composition
566- is when a large parser is used over and over again in a way
567- compilation times are high. In this sense, you can use `parsec/2`
568- to improve compilation time at the cost of runtime performance.
569- By using `parsec/2`, the tree size built at compile time will be
570- reduced although runtime performance is degraded as `parsec`
571- introduces a stacktrace entry.
530+ Overall, you can use `parsec/2` to improve compilation time at the cost
531+ of runtime performance. By using `parsec/2`, the tree size built at compile
532+ time will be reduced although runtime performance is slightly degraded.
572533
573534 ## Remote combinators
574535
0 commit comments