diff options
| author | Matthew Hall <hallmatthew314@gmail.com> | 2023-04-02 23:43:23 +1200 |
|---|---|---|
| committer | Matthew Hall <hallmatthew314@gmail.com> | 2023-04-02 23:43:23 +1200 |
| commit | 8bd7f4ec8f4885c0d8a915d4693e25935503d5bb (patch) | |
| tree | 24d8c865e320f99fed69a658b46768a324fb7010 | |
| parent | becabe55c4882a32e759430ae399c5e1e4057e2c (diff) | |
Add 'make' and 'pure' arguments to parser_chain
| -rw-r--r-- | spec/parser_chain_spec.cr | 23 | ||||
| -rw-r--r-- | spec/practical/json_spec.cr | 2 | ||||
| -rw-r--r-- | src/parcom.cr | 44 | ||||
| -rw-r--r-- | src/parcom/parser.cr | 4 |
4 files changed, 51 insertions, 22 deletions
diff --git a/spec/parser_chain_spec.cr b/spec/parser_chain_spec.cr index 05156ac..9510897 100644 --- a/spec/parser_chain_spec.cr +++ b/spec/parser_chain_spec.cr @@ -4,7 +4,7 @@ include Parcom describe "parser_chain" do it "works with zero intermediate steps" do - p = parser_chain Char, Char, "test", finally: Parser(Char, Char).pure('x') + p = parser_chain Char, Char, "test", pure: 'x' tokens = Tokens.from_string("abcd") result = p.parse(tokens) @@ -15,7 +15,7 @@ describe "parser_chain" do it "works with one intermediate step" do p = parser_chain Char, Char, "test", {c, Parser(Char, Char).any_token}, - finally: Parser(Char, Char).pure(c) + pure: c tokens = Tokens.from_string("abcd") result = p.parse(tokens) @@ -30,7 +30,7 @@ describe "parser_chain" do {front, digit.many}, {point, Parser.token('.').optional}, {back, digit.many}, - finally: case {front.empty?, point.nil?, back.empty?} + make: case {front.empty?, point.nil?, back.empty?} when {false, _, true} Parser(Char, Float64).pure(front.join.to_f64 * sign) when {true, false, false} @@ -64,7 +64,7 @@ describe "parser_chain" do two_words = parser_chain Char, {String, String}, "two_words", {word, any_word}, {_, ws}, - finally: Parser.token_sequence(word.chars).map_const({word, word}) + make: Parser.token_sequence(word.chars).map_const({word, word}) tokens = Tokens.from_string("foo \n foo") result = two_words.parse(tokens) @@ -75,5 +75,20 @@ describe "parser_chain" do tokens = Tokens.from_string("foofoo") expect_raises(ParserFail) { two_words.parse(tokens) } end + + it "fails without being given either `make` or `pure`" do + expect_raises(ArgumentError) do + _ = parser_chain Char, Char, "fails", {c, Parser.token('t')} + end + end + + it "fails when given both `make` and `pure` at the same time" do + expect_raises(ArgumentError) do + _ = parser_chain Char, Char, "fails", + {c, Parser.token('t')}, + pure: c, + make: Parser(Char, Char).pure('s') + end + end end diff --git a/spec/practical/json_spec.cr b/spec/practical/json_spec.cr index a0ce2db..cdd3425 100644 --- a/spec/practical/json_spec.cr +++ b/spec/practical/json_spec.cr @@ -47,7 +47,7 @@ describe "example: JSON parsing", tags: "example" do {front, digits}, {p, point}, {back, digits}, - finally: + make: case {front.empty?, p.nil?, back.empty?} when {true, false, false} Parser(Char, Float64).pure("0.#{back.join}".to_f64) diff --git a/src/parcom.cr b/src/parcom.cr index 0963184..60b8df0 100644 --- a/src/parcom.cr +++ b/src/parcom.cr @@ -12,9 +12,13 @@ module Parcom # The first and second arguments are types used for the parser's type. # The thirs argument is a string literal used for the name of the parser. # This is followed by any number of 2-tuples containing a variable name and - # an expression resolving to a `Parser(t.Class, _)`, whose success value will - # be stored in the aformentioned variable. The `finally` named argument is an - # expression that resolves to a `Parser(t.class, u.class)`. + # an expression resolving to a `Parser(t.class, _)`, whose success value will + # be stored in the aformentioned variable. The `make` named argument is an + # expression that resolves to a `Parser(t.class, u.class)`. This parser is + # the final step of the parser chain. The `pure` named argument is an + # expression that resolves to a `u.class`. This expression is passed to a + # call to `Parser(t.class, u.class).pure`, which will be the final step + # of the parser chain. # # Example: # ``` @@ -23,7 +27,7 @@ module Parcom # two_of_same_word = parser_chain Char, String, "two words", # {word, any_word}, # {_, ws}, - # finally: Parser.token_sequence(word.chars).map(&.join) + # make: Parser.token_sequence(word.chars).map(&.join) # # tokens = Tokens.from_string("foo foo") # result = two_of_same_word.parse(tokens) @@ -39,17 +43,27 @@ module Parcom # ``` # # This macro is based on Haskell's do-notation. - macro parser_chain(t, u, name, *steps, finally) - Parser({{t}}, {{u}}).new({{name}}) do |tokens| - {% for tup, index in steps %} - {{tup.last}}.and_then do |{{tup.first}}| - {% end %} - {{finally}} - {% for _, _ in steps %} - end - {% end %} - .parse(tokens) - end + macro parser_chain(t, u, name, *steps, make = nil, pure = nil ) + {% if make.nil? && pure.nil? %} + raise ArgumentError.new("Expected exactly one of 'make' and 'pure', but got neither") + {% elsif !make.nil? && !pure.nil? %} + raise ArgumentError.new("Expected exactly one of 'make' and 'pure', but got both") + {% else %} + Parser({{t}}, {{u}}).new({{name}}) do |tokens| + {% for tup, index in steps %} + {{tup.last}}.and_then do |{{tup.first}}| + {% end %} + {% if !make.nil? %} + {{make}} + {% else %} + Parser({{t}}, {{u}}).pure({{pure}}) + {% end %} + {% for _, _ in steps %} + end + {% end %} + .parse(tokens) + end + {% end %} end end diff --git a/src/parcom/parser.cr b/src/parcom/parser.cr index efa9780..398c95d 100644 --- a/src/parcom/parser.cr +++ b/src/parcom/parser.cr @@ -255,7 +255,7 @@ module Parcom parser_chain T, {U, V}, "#{@name} + #{other.name}", {x, self}, {y, other}, - finally: Parser(T, {U, V}).pure({x, y}) + make: Parser(T, {U, V}).pure({x, y}) end # Same as `#+`, but discards the second parser's result. @@ -440,7 +440,7 @@ module Parcom parser_chain T, Array(U), "<#{@name}> sep by <#{sep.name}>", {head, self}, {tail, (sep >> self).many}, - finally: Parser(T, Array(U)).pure(tail.unshift(head)) + make: Parser(T, Array(U)).pure(tail.unshift(head)) end end end |
