parser_combinator_dsl 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: