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