parser_combinator_dsl 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9dfb04f7c0fc4cc1cf5308cb11f7d4a48a8234e1491841d15d552e8ad2ae4235
4
- data.tar.gz: 8e455c3c496ab8a5a979658cb1cfe0410e34140835b6f7aa6016cefa63b30cdf
3
+ metadata.gz: edaf570ec26adf2f4bf73b9dc01e54631d30ec848bc54ee064dc514af2f776e8
4
+ data.tar.gz: 44264ddfb196dc449b64e1d3cc50103046655332c8224603649901062faa0a28
5
5
  SHA512:
6
- metadata.gz: 9b0b86387edca3f6b7929cde658aca2d4175773d7e4b162da9f9a0e979d5bd610299f5099e1b5248dfb0bb56a36c4a5d7df112e6fbfab6c24530aa591bd082d3
7
- data.tar.gz: f1a2c0ba308168f3d45cd8c1c3d8c09288f5f1cddf1fc9ed7178abf670f85c664feb497ede7456e0f2a0acc26db5b018c4d6bcdd2116f07bbaa2782eed132b17
6
+ metadata.gz: 136543bfc38ee26b7c979514526efafc527731424f22d8db7fe2e9ae7aa94b516e20ae83acaddbaee75a71dd4dbbdd97153aab1d29f8df9523d6b547857c6421
7
+ data.tar.gz: 97abd30a6674af378ba2a5f6717a391a86524b79b20f486284f36c9f0631b99d024b801f19c232c5bc31a8339ee3f57b0ea07323175e156dcc7a7c112ef2c04f
@@ -18,7 +18,7 @@ module BaseParsers
18
18
  end
19
19
 
20
20
  def whitespace
21
- many0 { anyChar([' '] + %w[\b \f \n \r \t]) }
21
+ many0 { anyChar([" ", "\b", "\f", "\n", "\r", "\t"]) }
22
22
  end
23
23
 
24
24
  def one(char)
@@ -52,6 +52,7 @@ module BaseParsers
52
52
  def many1(&wrapper)
53
53
  Parser.new do |input|
54
54
  matched = ""
55
+ output = []
55
56
  remaining = input
56
57
  parser = wrapper.call
57
58
 
@@ -59,10 +60,14 @@ module BaseParsers
59
60
  result = parser.run(remaining)
60
61
  break if remaining.nil? || result.fail?
61
62
  matched = matched + result.matched
63
+ output += result.output unless (result.output.length == 1 and result.output[0].length == 1)
62
64
  remaining = result.remaining
63
65
  end
64
66
 
65
- ParserResult.new(!matched.empty?, remaining, matched)
67
+ #puts "output: " + output.to_s unless output.empty?
68
+ output = nil if output.empty?
69
+
70
+ ParserResult.new(!matched.empty?, remaining, matched, output)
66
71
  end
67
72
  end
68
73
 
@@ -85,17 +90,25 @@ module BaseParsers
85
90
  Parser.new do |input|
86
91
  remaining = input
87
92
  matched = ""
93
+ fail = nil
88
94
 
89
95
  new_args = parsers.map do |parser|
90
96
  result = parser.run(remaining)
91
- return ParserResult.fail(input) unless result.ok?
97
+ unless result.ok?
98
+ fail = ParserResult.fail(input)
99
+ break
100
+ end
92
101
  remaining = result.remaining
93
102
  matched += result.matched
94
- result.matched
103
+ result.output
95
104
  end
96
105
 
97
- output = callback.call(*new_args)
98
- ParserResult.ok(output, matched: matched, remaining: remaining)
106
+ if fail.nil?
107
+ output = callback.call(*new_args)
108
+ ParserResult.ok(output, matched: matched, remaining: remaining)
109
+ else
110
+ fail
111
+ end
99
112
  end
100
113
  end
101
114
 
@@ -24,13 +24,16 @@ module Combinators
24
24
  def >>(other)
25
25
  Parser.new do |input|
26
26
  first = run(input)
27
- matched = ""
27
+ matched = ""
28
+ output = []
28
29
  if first.ok?
29
30
  matched = matched + first.matched
31
+ output += first.output
30
32
  second = other.run(first.remaining)
31
33
  if second.ok?
32
34
  matched = matched + second.matched
33
- ParserResult.ok(matched: matched, remaining: second.remaining)
35
+ output = [*output, second.output]
36
+ ParserResult.ok(output, matched: matched, remaining: second.remaining)
34
37
  else
35
38
  ParserResult.fail(input)
36
39
  end
@@ -45,12 +48,15 @@ module Combinators
45
48
  Parser.new do |input|
46
49
  first = run(input)
47
50
  matched = ""
51
+ output = []
48
52
  if first.ok?
49
53
  matched = first.matched
54
+ output += first.output
50
55
  second = other.run(first.remaining)
51
56
  if second.ok?
52
57
  matched = matched + second.matched
53
- ParserResult.ok(matched: matched, remaining: second.remaining)
58
+ output = [*output, second.output]
59
+ ParserResult.ok(output, matched: matched, remaining: second.remaining)
54
60
  else
55
61
  first
56
62
  end
@@ -65,17 +71,20 @@ module Combinators
65
71
  Parser.new do |input|
66
72
  first = run(input)
67
73
  matched = ""
74
+ output = []
68
75
  remaining = input
69
76
 
70
77
  if first.ok?
71
78
  matched = first.matched
79
+ output += first.output
72
80
  remaining = first.remaining
73
81
  end
74
82
 
75
83
  second = other.run(remaining)
76
84
  if second.ok?
77
85
  matched = matched + second.matched
78
- ParserResult.ok(matched: matched, remaining: second.remaining)
86
+ output = [*output, second.output]
87
+ ParserResult.ok(output, matched: matched, remaining: second.remaining)
79
88
  else
80
89
  ParserResult.fail(input)
81
90
  end
@@ -89,7 +98,7 @@ module Combinators
89
98
  if first.ok?
90
99
  second = other.run(first.remaining)
91
100
  if second.ok?
92
- ParserResult.ok(matched: first.matched, remaining: second.remaining)
101
+ ParserResult.ok(first.output, matched: first.matched, remaining: second.remaining)
93
102
  else
94
103
  first
95
104
  end
@@ -109,4 +118,59 @@ module Combinators
109
118
  second.ok? ? second : ParserResult.fail(input)
110
119
  end
111
120
  end
121
+
122
+ # Match this, other is QUIETLY consumed
123
+ def /(other)
124
+ Parser.new do |input|
125
+ first = run(input)
126
+ matched = ""
127
+ if first.ok?
128
+ matched = matched + first.matched
129
+ second = other.run(first.remaining)
130
+ if second.ok?
131
+ matched = matched + second.matched
132
+ ParserResult.ok(first.output, matched: matched, remaining: second.remaining)
133
+ else
134
+ ParserResult.fail(input)
135
+ end
136
+ else
137
+ first
138
+ end
139
+ end
140
+ end
141
+
142
+ # Match other, this is QUIETLY consumed
143
+ def *(other)
144
+ Parser.new do |input|
145
+ first = run(input)
146
+ matched = ""
147
+
148
+ if first.ok?
149
+ matched += first.matched
150
+ second = other.run(first.remaining)
151
+ if second.ok?
152
+ matched += second.matched
153
+ ParserResult.ok(second.output, matched: matched, remaining: second.remaining)
154
+ else
155
+ ParserResult.fail(input)
156
+ end
157
+ else
158
+ first
159
+ end
160
+ end
161
+ end
162
+
163
+ # other needs to fail for this to succeed; other is only peeking, not consuming
164
+ def !=(other)
165
+ Parser.new do |input|
166
+ second = other.run(input)
167
+
168
+ if second.ok?
169
+ ParserResult.fail(input)
170
+ else
171
+ run(input)
172
+ end
173
+ end
174
+ end
175
+
112
176
  end
@@ -14,6 +14,7 @@ class Grammar
14
14
  instance_eval &block
15
15
  end
16
16
 
17
+ # Adds or references a new rule
17
18
  def rule(name, &wrapper)
18
19
  return @rules.fetch(name.to_sym) { raise "Could not find rule: #{name}"} if wrapper.nil?
19
20
  @rules[name.to_sym] = Parser.new { |input| wrapper.call.run(input) }
@@ -1,14 +1,15 @@
1
1
  class ParserResult
2
2
  attr_reader :success, :remaining, :matched, :output
3
- def initialize(success, remaining, matched, output=[])
3
+ def initialize(success, remaining, matched, output=nil)
4
4
  @success = success
5
5
  @remaining = remaining
6
6
  @matched = matched
7
- @output = output
7
+ @output = output.nil? ? [matched] : output
8
8
  end
9
9
 
10
10
  def self.ok(output=nil, matched:, remaining:)
11
11
  # yield matched if block_given?
12
+ output = [matched] if output.nil?
12
13
  ParserResult.new(true, remaining, matched, output)
13
14
  end
14
15
 
@@ -27,4 +28,12 @@ class ParserResult
27
28
  def ==(other)
28
29
  return other.instance_of?(self.class) && other.success == success && other.remaining == remaining && other.matched == matched
29
30
  end
31
+
32
+ def to_s()
33
+ "ParserResult: {\n" +
34
+ "\tSuccess: " + success.to_s +
35
+ "\n\tRemaining: '" + remaining.to_s +
36
+ "'\n\tMatched: '" + matched.to_s +
37
+ "'\n\tOutput: " + output.to_s + "\n}"
38
+ end
30
39
  end
@@ -1,9 +1,10 @@
1
- def assert_parses(parser, with:, remaining:, matched: nil, should_fail: false)
1
+ def assert_parses(parser, with:, remaining:, matched: nil, output: nil, should_fail: false)
2
2
  result = parser.run(with)
3
- puts "with: " + with.to_s + " matched: " + result.matched.to_s + " remaining: " + result.remaining.to_s + " success: " + result.success.to_s
3
+ puts result.to_s
4
4
  assert_equal !should_fail, result.success
5
5
  assert_equal remaining, result.remaining
6
6
  assert_equal matched, result.matched unless matched.nil?
7
+ assert_equal output, result.output unless output.nil?
7
8
  end
8
9
 
9
10
  def test_parser(parser, with:, should_fail: false)
@@ -0,0 +1,57 @@
1
+ require "minitest/autorun"
2
+ require "pry"
3
+ require_relative "spec_helpers"
4
+
5
+ describe Grammar do
6
+ describe "Real world examples" do
7
+ it "output matches" do
8
+ parser = Grammar.build do
9
+ rule(:eol) { many0 { anyChar([" ", "\t"]) } < anyChar(["\n"]) }
10
+ rule(:instructions) { whitespace <= ( (str("Zubereitung") | str("Anweisungen")) / rule(:eol)) >> many1 { whitespace <= many1 { anyCharBut(["\n"]) } >= rule(:eol) } }
11
+ rule(:ingredient) { whitespace <= many1 { anyCharBut(["\n"]) } / rule(:eol) }
12
+ rule(:ingredients) { whitespace <= (str("Zutaten") / rule(:eol)) < many1 { rule(:ingredient) != rule(:instructions) } >= rule(:eol) }
13
+ rule(:recipe_name) { (str("Rezept ") <= many1 { anyCharBut(["\n"]) }) / many1 { rule(:eol) } }
14
+ rule(:recipe) { seq rule(:recipe_name), rule(:ingredients), rule(:instructions), lambda { |name, ingredients, instructions| [name, ingredients, instructions] } }
15
+
16
+ start(:recipe)
17
+ end
18
+
19
+ assert_parses parser, with:
20
+ "Rezept Tomatensahne
21
+
22
+ Zutaten
23
+ 1 kg, Tomaten
24
+ 100g, Zwiebeln
25
+ 30g, Butter
26
+ 1, Knoblauchzehe
27
+ 1 EL, Mehl
28
+ 2-3 EL, Tomatenmark
29
+ 2 EL, Tomatenketchup
30
+ 1/4 L, Brühe
31
+ 1/4 L, Schlagsahne
32
+
33
+ Salz
34
+ Cayennepfeffer
35
+ 1 TL, Zucker
36
+ 1 TL, Zitronensaft
37
+ 1 Bd., Basilikum
38
+
39
+
40
+
41
+ Anweisungen
42
+ Zwiebeln fein würfeln.
43
+ Tomaten häuten und würfeln.
44
+ Zwiebeln glasig dünsten, Knoblauch dazupressen.
45
+ Mit Mehl überstäuben, kurz anschwitzen.
46
+ Tomatenmark und -ketchup zugeben. Mit Brühe und Sahne auffüllen.
47
+ Sauce unter Rühren 5 Min. kochen.
48
+ Inzwischen Nudeln kochen, am besten Linguine.
49
+ Tomaten in der Sauce 5 Min. garen.
50
+ Mit Salz, Cayennepfeffer, Zucker und Zitronensaft herzhaft würzen.
51
+ Basilikumblätter abzupfen, in Streifen schneiden und auf die Sauce streuen.",
52
+ remaining: "",
53
+ output:
54
+ [["Tomatensahne"], ["Zutaten", ["1 kg, Tomaten", "100g, Zwiebeln", "30g, Butter", "1, Knoblauchzehe", "1 EL, Mehl", "2-3 EL, Tomatenmark", "2 EL, Tomatenketchup", "1/4 L, Brühe", "1/4 L, Schlagsahne", "Salz", "Cayennepfeffer", "1 TL, Zucker", "1 TL, Zitronensaft", "1 Bd., Basilikum"]], ["Anweisungen", ["Zwiebeln fein würfeln.", "Tomaten häuten und würfeln.", "Zwiebeln glasig dünsten, Knoblauch dazupressen.", "Mit Mehl überstäuben, kurz anschwitzen.", "Tomatenmark und -ketchup zugeben. Mit Brühe und Sahne auffüllen.", "Sauce unter Rühren 5 Min. kochen.", "Inzwischen Nudeln kochen, am besten Linguine.", "Tomaten in der Sauce 5 Min. garen.", "Mit Salz, Cayennepfeffer, Zucker und Zitronensaft herzhaft würzen.", "Basilikumblätter abzupfen, in Streifen schneiden und auf die Sauce streuen."]]]
55
+ end
56
+ end
57
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: parser_combinator_dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Federico Ramirez
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-05-22 00:00:00.000000000 Z
12
+ date: 2018-05-31 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: "\n\tThis library provides a DSL which you can use to easily generate
15
15
  parsers in Ruby.\n\n\tAt it's core, it's a parser combinator library, but you don't
@@ -30,6 +30,7 @@ files:
30
30
  - test/test_base_parsers.rb
31
31
  - test/test_combinators.rb
32
32
  - test/test_json_demo.rb
33
+ - test/test_recipe_parser.rb
33
34
  - test/test_tutorial.rb
34
35
  homepage: https://github.com/moddx/parser-combinator
35
36
  licenses: