From da4a42e0e2d21c35405028250c97b8a18ebdad68 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Tue, 22 Sep 2020 22:28:36 -0700 Subject: [PATCH 01/13] add common combinators to readme --- README.md | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1076b5b..06e2a98 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Parser combinator library for Ruby, based on Haskell's Parsec. - [Installation](#installation) - [Examples](#examples) - [Introduction](#introduction) + - [Some commonly used combinators](#some-commonly-used-combinators) - [Defining combinators](#defining-combinators) - [`Parsby.new`](#parsbynew) - [Defining parsers as modules](#defining-parsers-as-modules) @@ -60,8 +61,89 @@ between(lit("<"), lit(">"), decimal).parse "<100>" #=> 100 ``` -`lit` is a combinator that takes a string and returns a parser for -`lit`erally that string. +## Some commonly used combinators + +```ruby + # Parse argument string literally + lit("foo").parse "foo" + #=> "foo" + + # Case insensitive lit + ilit("Foo").parse "fOo" + #=> "fOo" + + # Parse foo or bar + (lit("foo") | lit("bar")).parse "bar" + #=> "bar" + + # Like `|`, parse one of foo or bar. `choice` is better when you have + # many choices to chose from. You can pass it any number of parsers or + # array of parsers. + choice(lit("foo"), lit("bar")).parse "bar" + #=> "bar" + + # Parses each argument in succesion and groups them in an array. + group(lit("foo"), lit("bar")).parse "foobar" + #=> ["foo", "bar"] + + # Parse foo and bar, returning bar. + (lit("foo") > lit("bar")).parse "foobar" + #=> "bar" + + # Parse foo and bar, returning foo. + (lit("foo") < lit("bar")).parse "foobar" + #=> "foo" + + # Parse foo and transform result according to block. + lit("foo").fmap {|x| x.upcase }.parse "foo" + #=> "FOO" + + # Parse a character from the choices in a string + char_in(" \t\r\n").parse "\t" + #=> "\t" + + # Make parser optional + group(optional(lit("foo")), lit("bar")).parse "bar" + #=> [nil, "bar"] + + # Use parser zero or more times, grouping results in array. many_1, does + # the same, but requires parsing at least once. + many(lit("foo")).parse "foofoo" + #=> ["foo", "foo"] + + # Parse many, but each separated by something. + sep_by(lit(","), lit("foo")).parse "foo,foo" + #=> ["foo", "foo"] + + # `whitespace` (alias `ws`) is zero or more whitespace characters. + # `whitespace_1` (alias `ws_1`) is one or more whitespace characters. + # `spaced` allows a parser to be surrounded by optional whitespace. + # `whitespace_1` is the base definition. If you extend it to e.g. add the + # parsing of comments, the other combinators will also recognize that + # change. + (whitespace > lit("foo")).parse " foo" + #=> "foo" + group(lit("foo"), ws_1 > lit("bar")).parse "foo bar" + #=> ["foo", "bar"] + spaced(lit("foo")).parse " foo " + #=> "foo" + + # Parse any one character + any_char.parse "f" + #=> "f" + + # Require eof at end of parse + (lit("foo") < eof).parse "foobar" + #=> Parsby::ExpectationFailed + + # join(p) is the same as p.fmap {|xs| xs.join} + join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" + #=> "foobar" + + # p1 + p2 is the same as group(p1, p2).fmap {|(r1, r2)| r1 + r2 } + lit("foo") + (ws > lit("bar")).parse "foo bar" + #=> "foobar" +``` ## Defining combinators From a605af3bbe40c1051ca49cb9d829a160e0e7bb96 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 22:11:35 -0700 Subject: [PATCH 02/13] add more to examples of common combinators in readme... and fix splicing of < while we're at it. --- README.md | 19 +++++++++++++++---- lib/parsby.rb | 4 +++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 06e2a98..2cad70d 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,11 @@ between(lit("<"), lit(">"), decimal).parse "<100>" ilit("Foo").parse "fOo" #=> "fOo" + # Make any value into a parser that results in that value without + # consuming input. + pure("foo").parse "" + #=> "foo" + # Parse foo or bar (lit("foo") | lit("bar")).parse "bar" #=> "bar" @@ -94,7 +99,7 @@ between(lit("<"), lit(">"), decimal).parse "<100>" (lit("foo") < lit("bar")).parse "foobar" #=> "foo" - # Parse foo and transform result according to block. + # Parse transform result according to block. lit("foo").fmap {|x| x.upcase }.parse "foo" #=> "FOO" @@ -111,7 +116,8 @@ between(lit("<"), lit(">"), decimal).parse "<100>" many(lit("foo")).parse "foofoo" #=> ["foo", "foo"] - # Parse many, but each separated by something. + # Parse many, but each separated by something. sep_by_1 requires at least + # one element. sep_by(lit(","), lit("foo")).parse "foo,foo" #=> ["foo", "foo"] @@ -129,12 +135,17 @@ between(lit("<"), lit(">"), decimal).parse "<100>" #=> "foo" # Parse any one character - any_char.parse "f" + any_char.parse "foo" #=> "f" # Require eof at end of parse (lit("foo") < eof).parse "foobar" - #=> Parsby::ExpectationFailed + #=> Parsby::ExpectationFailed: line 1: + foobar + | * failure: eof + \-/ *| success: lit("foo") + \| + | * failure: (lit("foo") < eof) # join(p) is the same as p.fmap {|xs| xs.join} join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" diff --git a/lib/parsby.rb b/lib/parsby.rb index 3880a00..7e60615 100644 --- a/lib/parsby.rb +++ b/lib/parsby.rb @@ -657,7 +657,9 @@ def |(p) # x < y runs parser x then y and returns x. def <(p) - self.then {|r| p.then { pure r } } % "(#{label} < #{p.label})" + ~splicer.start do |m| + m.end(self).then {|r| m.end(p).then { pure r } } + end % "(#{label} < #{p.label})" end # x > y runs parser x then y and returns y. From 3bad3cbf86e9c8102358fd338170dfcf33123252 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 22:24:14 -0700 Subject: [PATCH 03/13] add missing article --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2cad70d..d3104eb 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ FoobarParser.foo.parse "foo" ``` Being able to use subparsers directly is useful for when you want to e.g. -parse JSON array, instead of any JSON value. +parse a JSON array, instead of any JSON value. Writing the parser as a module like that also makes it easy to make a new parser based on it: From 26ca98443e5f4350471b6b4e763c3981263889d5 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 22:24:48 -0700 Subject: [PATCH 04/13] move section of Parsby.new to bottom... since it probably raises more questions than it answers. --- README.md | 60 +++++++++++++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d3104eb..865b3a9 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,13 @@ Parser combinator library for Ruby, based on Haskell's Parsec. - [Introduction](#introduction) - [Some commonly used combinators](#some-commonly-used-combinators) - [Defining combinators](#defining-combinators) - - [`Parsby.new`](#parsbynew) - [Defining parsers as modules](#defining-parsers-as-modules) - [`ExpectationFailed`](#expectationfailed) - [Cleaning up the parse tree for the trace](#cleaning-up-the-parse-tree-for-the-trace) - [`splicer.start` combinator](#splicerstart-combinator) - [Recursive parsers with `lazy`](#recursive-parsers-with-lazy) - [Parsing left-recursive languages with `reduce` combinator](#parsing-leftrecursive-languages-with-reduce-combinator) + - [`Parsby.new`](#parsbynew) - [Parsing from a string, a file, a pipe, a socket, ...](#parsing-from-a-string-a-file-a-pipe-a-socket-) - [Comparing with Haskell's Parsec](#comparing-with-haskells-parsec) - [Development](#development) @@ -199,35 +199,6 @@ between(lit("<"), lit(">"), lit("foo")).label.to_s => "" ``` -## `Parsby.new` - -Now, normally one ought to be able to define parsers using just -combinators, but there are times when one might need more control. For -those times, the most raw way to define a parser is using `Parsby.new`. - -Here's `lit` as an example: - -```ruby -define_combinator :lit, wrap: false do |e, case_sensitive: true| - Parsby.new do |c| - a = c.bio.read e.length - if case_sensitive ? a == e : a.to_s.downcase == e.downcase - a - else - raise ExpectationFailed.new c - end - end -end -``` - -It takes a string argument for what it `e`xpects to parse, and returns what -was `a`ctually parsed if it matches the expectation. - -The block parameter `c` is a `Parsby::Context`. `c.bio` holds a -`Parsby::BackedIO`. The `parse` method of `Parsby` objects accepts ideally -any `IO` (and `String`s, which it turns into `StringIO`) and then wraps -them with `BackedIO` to give the `IO` the ability to backtrack. - ## Defining parsers as modules The typical pattern I use is something like this: @@ -635,6 +606,35 @@ returning the result of the last successful parse. In effect, we're parsing left operands bottom-up and right operands top-down. +## `Parsby.new` + +Normally one ought to be able to define parsers using just combinators, but +there are times when one might need more control. For those times, the most +raw way to define a parser is using `Parsby.new`. + +Here's `lit` as an example: + +```ruby +define_combinator :lit, wrap: false do |e, case_sensitive: true| + Parsby.new do |c| + a = c.bio.read e.length + if case_sensitive ? a == e : a.to_s.downcase == e.downcase + a + else + raise ExpectationFailed.new c + end + end +end +``` + +It takes a string argument for what it `e`xpects to parse, and returns what +was `a`ctually parsed if it matches the expectation. + +The block parameter `c` is a `Parsby::Context`. `c.bio` holds a +`Parsby::BackedIO`. The `parse` method of `Parsby` objects accepts ideally +any `IO` (and `String`s, which it turns into `StringIO`) and then wraps +them with `BackedIO` to give the `IO` the ability to backtrack. + ## Parsing from a string, a file, a pipe, a socket, ... Any `IO` ought to work (unit tests currently have only checked pipes, From 364857d964c8bdcd47171201b64d422c0317aa9a Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 22:44:00 -0700 Subject: [PATCH 05/13] add more argument options to char_in --- README.md | 5 ++++- lib/parsby/combinators.rb | 27 ++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 865b3a9..c3f4afc 100644 --- a/README.md +++ b/README.md @@ -103,9 +103,12 @@ between(lit("<"), lit(">"), decimal).parse "<100>" lit("foo").fmap {|x| x.upcase }.parse "foo" #=> "FOO" - # Parse a character from the choices in a string + # Parse a character from the choices in a set of strings or ranges char_in(" \t\r\n").parse "\t" #=> "\t" + typical_identifier_characters = ['a'..'z', 'A'..'Z', 0..9, "_"] + join(many(char_in("!?", typical_identifier_characters))).parse "foo23? bar" + #=> "foo23?" # Make parser optional group(optional(lit("foo")), lit("bar")).parse "bar" diff --git a/lib/parsby/combinators.rb b/lib/parsby/combinators.rb index 9d28b63..5e9c59b 100644 --- a/lib/parsby/combinators.rb +++ b/lib/parsby/combinators.rb @@ -185,12 +185,33 @@ def splicer Parsby::Splicer end - # Parses a single char from those contained in the string argument. - define_combinator :char_in do |s| + # Accepts any number of strings or ranges optionally arbitrarily nested + # in arrays and parses a single char from the char options in the + # resulting joined string. + # + # join(many(char_in('a'..'z', 0..9))).parse "foo23 bar" + # #=> "foo23" + # + # char_options = ['a'..'z', "!@#$%^"] + # join(many(char_in(0..9, char_options))).parse "foo23!@ bar" + # #=> "foo23!@" + # + define_combinator :char_in do |*strings| + string = strings + .flatten + .map do |s| + if s.is_a?(Range) + s.to_a.join + else + s + end + end + .join + ~splicer.start do Parsby.new do |c| char = any_char.parse c - unless s.chars.include? char + unless string.chars.include? char raise ExpectationFailed.new c end char From 4efaf43ff35e62404f6b8255b23eaee49d49b3f8 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 22:48:36 -0700 Subject: [PATCH 06/13] simplify description --- lib/parsby/combinators.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/parsby/combinators.rb b/lib/parsby/combinators.rb index 5e9c59b..d90cb77 100644 --- a/lib/parsby/combinators.rb +++ b/lib/parsby/combinators.rb @@ -185,9 +185,8 @@ def splicer Parsby::Splicer end - # Accepts any number of strings or ranges optionally arbitrarily nested - # in arrays and parses a single char from the char options in the - # resulting joined string. + # Parses a single char from the char options provided as string and + # range arguments optionally arbitrarily nested in arrays. # # join(many(char_in('a'..'z', 0..9))).parse "foo23 bar" # #=> "foo23" From f20f345b27860936ecd16a2aecb6145fd5da3bf8 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 23:53:20 -0700 Subject: [PATCH 07/13] update doc after having gotten rid of Parsby::Token --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3f4afc..5a51af4 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ def between(left, right, p) end between(lit("<"), lit(">"), lit("foo")).label.to_s -=> "" +=> "unknown" ``` ## Defining parsers as modules From e01450defe936369a4cccc5c7151e5beba0f380b Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Thu, 24 Sep 2020 23:54:08 -0700 Subject: [PATCH 08/13] mention why labels from define_combinator are important --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5a51af4..6b1c689 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,9 @@ between(lit("<"), lit(">"), lit("foo")).label #=> 'between(lit("<"), lit(">"), lit("foo"))' ``` +Having labels that resemble the source code is helpful for [the error +messages](#expectationfailed). + If we use `def` instead of `define_combinator`, then the label would be that of its definition. In the following case, it would be that assigned by `<`. From 60684c24e6dd3981a0379f8d7851d38061a18715 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Fri, 25 Sep 2020 00:11:38 -0700 Subject: [PATCH 09/13] added CHANGELOG --- .gitignore | 1 + CHANGELOG.md | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 CHANGELOG.md diff --git a/.gitignore b/.gitignore index 775bab6..eb6f4c5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ .rspec_status /README.md.html +/CHANGELOG.md.html /*.gem diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e83afa --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +## CHANGELOG + +### v1.1.0 + +##### feature additions + +- `char_in` now allows multiple strings, ranges, and arrays of such as arguments + +##### bugs and minor things + +- Added more to README. From 2a59f754c38dfb0ba2306b1451c349541f8c5608 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Fri, 25 Sep 2020 00:12:33 -0700 Subject: [PATCH 10/13] increase version to 1.1.0 --- lib/parsby/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/parsby/version.rb b/lib/parsby/version.rb index 5b9990b..929dd4e 100644 --- a/lib/parsby/version.rb +++ b/lib/parsby/version.rb @@ -1,3 +1,3 @@ class Parsby - VERSION = "1.0.0" + VERSION = "1.1.0" end From dea8726f9013a71380d5f31dbaf236fa03debe93 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Fri, 25 Sep 2020 22:24:08 -0700 Subject: [PATCH 11/13] revise readme once more --- README.md | 74 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6b1c689..bc9412d 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ Parser combinator library for Ruby, based on Haskell's Parsec. - [Defining parsers as modules](#defining-parsers-as-modules) - [`ExpectationFailed`](#expectationfailed) - [Cleaning up the parse tree for the trace](#cleaning-up-the-parse-tree-for-the-trace) - - [`splicer.start` combinator](#splicerstart-combinator) - [Recursive parsers with `lazy`](#recursive-parsers-with-lazy) - [Parsing left-recursive languages with `reduce` combinator](#parsing-leftrecursive-languages-with-reduce-combinator) - [`Parsby.new`](#parsbynew) @@ -87,7 +86,8 @@ between(lit("<"), lit(">"), decimal).parse "<100>" choice(lit("foo"), lit("bar")).parse "bar" #=> "bar" - # Parses each argument in succesion and groups them in an array. + # Parse with each argument in succesion and group the results in an + # array. group(lit("foo"), lit("bar")).parse "foobar" #=> ["foo", "bar"] @@ -99,17 +99,6 @@ between(lit("<"), lit(">"), decimal).parse "<100>" (lit("foo") < lit("bar")).parse "foobar" #=> "foo" - # Parse transform result according to block. - lit("foo").fmap {|x| x.upcase }.parse "foo" - #=> "FOO" - - # Parse a character from the choices in a set of strings or ranges - char_in(" \t\r\n").parse "\t" - #=> "\t" - typical_identifier_characters = ['a'..'z', 'A'..'Z', 0..9, "_"] - join(many(char_in("!?", typical_identifier_characters))).parse "foo23? bar" - #=> "foo23?" - # Make parser optional group(optional(lit("foo")), lit("bar")).parse "bar" #=> [nil, "bar"] @@ -120,7 +109,7 @@ between(lit("<"), lit(">"), decimal).parse "<100>" #=> ["foo", "foo"] # Parse many, but each separated by something. sep_by_1 requires at least - # one element. + # one element to be parsed. sep_by(lit(","), lit("foo")).parse "foo,foo" #=> ["foo", "foo"] @@ -137,11 +126,26 @@ between(lit("<"), lit(">"), decimal).parse "<100>" spaced(lit("foo")).parse " foo " #=> "foo" + # Parse transform result according to block. + lit("foo").fmap {|x| x.upcase }.parse "foo" + #=> "FOO" + + # join(p) is the same as p.fmap {|xs| xs.join } + join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" + #=> "foobar" + + # Parse a character from the choices in a set of strings or ranges + char_in(" \t\r\n").parse "\t" + #=> "\t" + typical_identifier_characters = ['a'..'z', 'A'..'Z', 0..9, "_"] + join(many(char_in("!?", typical_identifier_characters))).parse "foo23? bar" + #=> "foo23?" + # Parse any one character any_char.parse "foo" #=> "f" - # Require eof at end of parse + # Require end of input at end of parse. (lit("foo") < eof).parse "foobar" #=> Parsby::ExpectationFailed: line 1: foobar @@ -150,18 +154,24 @@ between(lit("<"), lit(">"), decimal).parse "<100>" \| | * failure: (lit("foo") < eof) - # join(p) is the same as p.fmap {|xs| xs.join} - join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" - #=> "foobar" + # Parse only when other parser fails. + join(many(any_char.that_fails(whitespace_1))).parse "foo bar" + #=> "foo" + + # single(p) is the same as p.fmap {|x| [x] } + single(lit("foo")).parse "foo" + #=> ["foo"] # p1 + p2 is the same as group(p1, p2).fmap {|(r1, r2)| r1 + r2 } - lit("foo") + (ws > lit("bar")).parse "foo bar" + (lit("foo") + (ws > lit("bar"))).parse "foo bar" #=> "foobar" + (single(lit("foo")) + many(ws > lit("bar"))).parse "foo bar bar" + #=> ["foo", "bar", "bar"] ``` ## Defining combinators -If you look at the examples in this source, you'll notice that all +If you look at the examples in this source, you'll notice that almost all combinators are defined with `define_combinator`. Strictly speaking, it's not necessary to use that to define combinators. You can do it with variable assignment or `def` syntax. Nevertheless, `define_combinator` is @@ -328,16 +338,9 @@ as so. There are at least 6 ancestors/descendant parsers between `list` and `sexp`. It'd be very much pointless to show them all. They convey little additional information and their labels are very verbose. -### `splicer.start` combinator - -The reason why they don't appear is because `splicer` is used to make the -tree look a little cleaner. - -The name comes from JS's `Array.prototype.splice`, to which you can give a -starting position, and a count specifying the end, and it'll remove the -specified elements from an Array. We use `splicer` likewise, only it works -on parse trees. To show an example, here's a simplified definition of -`choice`: +The reason why they don't appear is because the `splicer.start` combinator +is used to make the tree look a little cleaner. To show an example of how +it works, here's a simplified definition of `choice`: ```ruby define_combinator :choice do |*ps| @@ -383,7 +386,8 @@ clearer. Let's use `splicer` to remove those: end ``` -Let's fail it, again: +This makes the `p` parsers appear as direct children of the `splicer.start` +parser in the trace. Let's fail it, again: ``` pry(main)> choice(lit("foo"), lit("bar"), lit("baz")).parse "qux" @@ -397,9 +401,10 @@ Parsby::ExpectationFailed: line 1: | * failure: choice(lit("foo"), lit("bar"), lit("baz")) ``` -Now, the only issue left is that `define_combinator` wraps the result of -the parser in another parser. Let's disable that wrapping by passing `wrap: -false` to it: +Now, the only issue left is that `define_combinator` wraps the resulting +parser in another parser. It does this so you can see the label assigned to +the combinator and to its definition separately. Let's disable that +wrapping by passing `wrap: false` to it: ```ruby define_combinator :choice, wrap: false do |*ps| @@ -413,6 +418,7 @@ false` to it: end ``` +That causes it to overwrite the label to the resulting parser of the block. Let's fail it, again: ``` From fb85b2524d49ea0e0968ce1c17d53c8a45b6b5f0 Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Fri, 25 Sep 2020 22:28:13 -0700 Subject: [PATCH 12/13] fix minor formatting issues in readme --- README.md | 212 +++++++++++++++++++++++++++--------------------------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/README.md b/README.md index bc9412d..97dfc28 100644 --- a/README.md +++ b/README.md @@ -63,110 +63,110 @@ between(lit("<"), lit(">"), decimal).parse "<100>" ## Some commonly used combinators ```ruby - # Parse argument string literally - lit("foo").parse "foo" - #=> "foo" - - # Case insensitive lit - ilit("Foo").parse "fOo" - #=> "fOo" - - # Make any value into a parser that results in that value without - # consuming input. - pure("foo").parse "" - #=> "foo" - - # Parse foo or bar - (lit("foo") | lit("bar")).parse "bar" - #=> "bar" - - # Like `|`, parse one of foo or bar. `choice` is better when you have - # many choices to chose from. You can pass it any number of parsers or - # array of parsers. - choice(lit("foo"), lit("bar")).parse "bar" - #=> "bar" - - # Parse with each argument in succesion and group the results in an - # array. - group(lit("foo"), lit("bar")).parse "foobar" - #=> ["foo", "bar"] - - # Parse foo and bar, returning bar. - (lit("foo") > lit("bar")).parse "foobar" - #=> "bar" - - # Parse foo and bar, returning foo. - (lit("foo") < lit("bar")).parse "foobar" - #=> "foo" - - # Make parser optional - group(optional(lit("foo")), lit("bar")).parse "bar" - #=> [nil, "bar"] - - # Use parser zero or more times, grouping results in array. many_1, does - # the same, but requires parsing at least once. - many(lit("foo")).parse "foofoo" - #=> ["foo", "foo"] - - # Parse many, but each separated by something. sep_by_1 requires at least - # one element to be parsed. - sep_by(lit(","), lit("foo")).parse "foo,foo" - #=> ["foo", "foo"] - - # `whitespace` (alias `ws`) is zero or more whitespace characters. - # `whitespace_1` (alias `ws_1`) is one or more whitespace characters. - # `spaced` allows a parser to be surrounded by optional whitespace. - # `whitespace_1` is the base definition. If you extend it to e.g. add the - # parsing of comments, the other combinators will also recognize that - # change. - (whitespace > lit("foo")).parse " foo" - #=> "foo" - group(lit("foo"), ws_1 > lit("bar")).parse "foo bar" - #=> ["foo", "bar"] - spaced(lit("foo")).parse " foo " - #=> "foo" - - # Parse transform result according to block. - lit("foo").fmap {|x| x.upcase }.parse "foo" - #=> "FOO" - - # join(p) is the same as p.fmap {|xs| xs.join } - join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" - #=> "foobar" - - # Parse a character from the choices in a set of strings or ranges - char_in(" \t\r\n").parse "\t" - #=> "\t" - typical_identifier_characters = ['a'..'z', 'A'..'Z', 0..9, "_"] - join(many(char_in("!?", typical_identifier_characters))).parse "foo23? bar" - #=> "foo23?" - - # Parse any one character - any_char.parse "foo" - #=> "f" - - # Require end of input at end of parse. - (lit("foo") < eof).parse "foobar" - #=> Parsby::ExpectationFailed: line 1: - foobar - | * failure: eof - \-/ *| success: lit("foo") - \| - | * failure: (lit("foo") < eof) - - # Parse only when other parser fails. - join(many(any_char.that_fails(whitespace_1))).parse "foo bar" - #=> "foo" - - # single(p) is the same as p.fmap {|x| [x] } - single(lit("foo")).parse "foo" - #=> ["foo"] - - # p1 + p2 is the same as group(p1, p2).fmap {|(r1, r2)| r1 + r2 } - (lit("foo") + (ws > lit("bar"))).parse "foo bar" - #=> "foobar" - (single(lit("foo")) + many(ws > lit("bar"))).parse "foo bar bar" - #=> ["foo", "bar", "bar"] +# Parse argument string literally +lit("foo").parse "foo" +#=> "foo" + +# Case insensitive lit +ilit("Foo").parse "fOo" +#=> "fOo" + +# Make any value into a parser that results in that value without +# consuming input. +pure("foo").parse "" +#=> "foo" + +# Parse foo or bar +(lit("foo") | lit("bar")).parse "bar" +#=> "bar" + +# Like `|`, parse one of foo or bar. `choice` is better when you have +# many choices to chose from. You can pass it any number of parsers or +# array of parsers. +choice(lit("foo"), lit("bar")).parse "bar" +#=> "bar" + +# Parse with each argument in succesion and group the results in an +# array. +group(lit("foo"), lit("bar")).parse "foobar" +#=> ["foo", "bar"] + +# Parse foo and bar, returning bar. +(lit("foo") > lit("bar")).parse "foobar" +#=> "bar" + +# Parse foo and bar, returning foo. +(lit("foo") < lit("bar")).parse "foobar" +#=> "foo" + +# Make parser optional +group(optional(lit("foo")), lit("bar")).parse "bar" +#=> [nil, "bar"] + +# Use parser zero or more times, grouping results in array. many_1, does +# the same, but requires parsing at least once. +many(lit("foo")).parse "foofoo" +#=> ["foo", "foo"] + +# Parse many, but each separated by something. sep_by_1 requires at least +# one element to be parsed. +sep_by(lit(","), lit("foo")).parse "foo,foo" +#=> ["foo", "foo"] + +# `whitespace` (alias `ws`) is zero or more whitespace characters. +# `whitespace_1` (alias `ws_1`) is one or more whitespace characters. +# `spaced` allows a parser to be surrounded by optional whitespace. +# `whitespace_1` is the base definition. If you extend it to e.g. add the +# parsing of comments, the other combinators will also recognize that +# change. +(whitespace > lit("foo")).parse " foo" +#=> "foo" +group(lit("foo"), ws_1 > lit("bar")).parse "foo bar" +#=> ["foo", "bar"] +spaced(lit("foo")).parse " foo " +#=> "foo" + +# Parse transform result according to block. +lit("foo").fmap {|x| x.upcase }.parse "foo" +#=> "FOO" + +# join(p) is the same as p.fmap {|xs| xs.join } +join(sep_by(lit(","), lit("foo") | lit("bar"))).parse "foo,bar" +#=> "foobar" + +# Parse a character from the choices in a set of strings or ranges +char_in(" \t\r\n").parse "\t" +#=> "\t" +typical_identifier_characters = ['a'..'z', 'A'..'Z', 0..9, "_"] +join(many(char_in("!?", typical_identifier_characters))).parse "foo23? bar" +#=> "foo23?" + +# Parse any one character +any_char.parse "foo" +#=> "f" + +# Require end of input at end of parse. +(lit("foo") < eof).parse "foobar" +#=> Parsby::ExpectationFailed: line 1: + foobar + | * failure: eof + \-/ *| success: lit("foo") + \| + | * failure: (lit("foo") < eof) + +# Parse only when other parser fails. +join(many(any_char.that_fails(whitespace_1))).parse "foo bar" +#=> "foo" + +# single(p) is the same as p.fmap {|x| [x] } +single(lit("foo")).parse "foo" +#=> ["foo"] + +# p1 + p2 is the same as group(p1, p2).fmap {|(r1, r2)| r1 + r2 } +(lit("foo") + (ws > lit("bar"))).parse "foo bar" +#=> "foobar" +(single(lit("foo")) + many(ws > lit("bar"))).parse "foo bar bar" +#=> ["foo", "bar", "bar"] ``` ## Defining combinators @@ -200,7 +200,7 @@ def between(left, right, p) end between(lit("<"), lit(">"), lit("foo")).label -=> '((lit("<") > lit("foo")) < lit(">"))' +#=> '((lit("<") > lit("foo")) < lit(">"))' ``` If we're to wrap that parser in a new one, then the label would be simply @@ -212,7 +212,7 @@ def between(left, right, p) end between(lit("<"), lit(">"), lit("foo")).label.to_s -=> "unknown" +#=> "unknown" ``` ## Defining parsers as modules From 8594bcf63e88f6b0bd28e2bc80ccd87dc34f595c Mon Sep 17 00:00:00 2001 From: Jorge Luis Martinez Gomez Date: Fri, 25 Sep 2020 22:36:01 -0700 Subject: [PATCH 13/13] specify missing stuff in gemspec --- parsby.gemspec | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/parsby.gemspec b/parsby.gemspec index 59eab73..977d67b 100644 --- a/parsby.gemspec +++ b/parsby.gemspec @@ -6,25 +6,26 @@ require "parsby/version" Gem::Specification.new do |spec| spec.name = "parsby" spec.version = Parsby::VERSION + spec.licenses = ["MIT"] spec.authors = ["Jorge Luis Martinez Gomez"] spec.email = ["jol@jol.dev"] spec.summary = %q{Parser combinator library inspired by Haskell's Parsec} #spec.description = %q{TODO: Write a longer description or delete this line.} - #spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.homepage = "https://github.com/jolmg/parsby" # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host' # to allow pushing to a single host or delete this section to allow pushing to any host. - #if spec.respond_to?(:metadata) - # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" + if spec.respond_to?(:metadata) + #spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'" - # #spec.metadata["homepage_uri"] = spec.homepage - # #spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - # #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." - #else - # raise "RubyGems 2.0 or newer is required to protect against " \ - # "public gem pushes." - #end + #spec.metadata["homepage_uri"] = spec.homepage + #spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." + spec.metadata["changelog_uri"] = "https://github.com/jolmg/parsby/blob/master/CHANGELOG.md" + else + raise "RubyGems 2.0 or newer is required to protect against " \ + "public gem pushes." + end # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. @@ -38,5 +39,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.17" spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "pry" + spec.add_development_dependency "pry", "~> 0" end