pegparse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,337 @@
1
+ require_relative '../lib/pegparse'
2
+ require 'set'
3
+
4
+ module Pegparse::Sample
5
+ end
6
+
7
+ class Pegparse::Sample::BshParser < Pegparse::ParserBase
8
+ def initialize(context_or_scanner)
9
+ super(context_or_scanner)
10
+ self.start_rule_symbol = :start_rule
11
+ end
12
+
13
+ def line_comment
14
+ read(/#.*/)
15
+ end
16
+
17
+ def inline_sp
18
+ read(/( |\\\n)*/)
19
+ end
20
+
21
+ def lf
22
+ choice(
23
+ ->{ super() },
24
+ ->{
25
+ sp()
26
+ read(';')
27
+ },
28
+ )
29
+ end
30
+
31
+ def keyword(key)
32
+ read(key)
33
+ backtrack if peek(/[A-Za-z0-9_]/)
34
+ end
35
+
36
+ def start_rule
37
+ ret = pipelines()
38
+ sp()
39
+ ret
40
+ end
41
+
42
+ def pipelines
43
+ zero_or_more {
44
+ sp()
45
+ pipeline()
46
+ }
47
+ end
48
+
49
+ BshBiopParser = Pegparse::BiopRuleChain.based_on(self) do
50
+ def operator_sp
51
+ inline_sp()
52
+ end
53
+
54
+ def operand_sp
55
+ inline_sp()
56
+ end
57
+ end
58
+ rule def pipeline
59
+ biop_parser = BshBiopParser.new(nil)
60
+ biop_parser
61
+ .left_op(['&&', '||'])
62
+ .left_op('|')
63
+ .term(->{ sentence() })
64
+ ret = biop_parser.parse(@context)
65
+
66
+ pipeline_closer = optional{
67
+ inline_sp()
68
+ read(/[&;]/)
69
+ }
70
+ if pipeline_closer == '&'
71
+ [:bg, ret]
72
+ else
73
+ ret
74
+ end
75
+ end
76
+
77
+ rule def sentence
78
+ choice(
79
+ ->{ if_sentence() },
80
+ ->{ while_sentence() },
81
+ ->{ for_sentence() },
82
+ ->{ case_sentence() },
83
+ ->{ command_sentence() },
84
+ )
85
+ end
86
+
87
+ rule def if_sentence
88
+ keyword('if')
89
+ sp()
90
+ cond = pipeline()
91
+ sp()
92
+ keyword('then')
93
+ sp()
94
+ if_body = pipelines()
95
+ elif_cond_bodies = zero_or_more {
96
+ sp()
97
+ keyword('elif')
98
+ sp()
99
+ elif_cond = pipeline()
100
+ sp()
101
+ keyword('then')
102
+ sp()
103
+ elif_body = pipelines()
104
+ [elif_cond, elif_body]
105
+ }
106
+ else_body = optional{
107
+ sp()
108
+ keyword('else')
109
+ sp()
110
+ pipelines()
111
+ }
112
+ sp()
113
+ keyword('fi')
114
+ if else_body
115
+ [:if, [cond, if_body], *elif_cond_bodies, [nil, else_body]]
116
+ else
117
+ [:if, [cond, if_body], *elif_cond_bodies]
118
+ end
119
+ end
120
+
121
+ rule def while_sentence
122
+ keyword('while')
123
+ sp()
124
+ backtrack
125
+ end
126
+
127
+ rule def for_sentence
128
+ keyword('for')
129
+ sp()
130
+ var = assign_varname()
131
+ sp()
132
+ keyword('in')
133
+ list = zero_or_more {
134
+ inline_sp()
135
+ normal_operand()
136
+ }
137
+ lf()
138
+ sp()
139
+ keyword('do')
140
+ body = pipelines()
141
+ keyword('done')
142
+ [:for, var, list, body]
143
+ end
144
+
145
+ rule def case_sentence
146
+ keyword('case')
147
+ sp()
148
+ backtrack
149
+ end
150
+
151
+ rule def command_sentence
152
+ envs = zero_or_more {
153
+ inline_sp()
154
+ assign_exp()
155
+ }
156
+ main = optional{
157
+ inline_sp()
158
+ cmd = execute_target_exp()
159
+
160
+ operands = zero_or_more {
161
+ inline_sp()
162
+ choice(
163
+ ->{ redirect_operand() },
164
+ ->{ normal_operand() }
165
+ )
166
+ }
167
+
168
+ redirects, normals = operands.partition{|op| op[0] == :redirect}
169
+ [cmd, redirects, operands]
170
+ }
171
+ backtrack if envs.empty? && !main
172
+
173
+ main ? [:command, envs, main[1], main[0], *main[2]] : [:env, envs]
174
+ end
175
+
176
+ rule def assign_exp
177
+ varname = assign_varname()
178
+ read('=')
179
+ val = assign_value()
180
+ ['=', varname, val]
181
+ end
182
+
183
+ def assign_varname
184
+ read(/[A-Za-z][A-Za-z0-9_]*/)
185
+ end
186
+
187
+ def assign_value
188
+ normal_operand()
189
+ end
190
+
191
+ def execute_target_exp
192
+ normal_operand()
193
+ end
194
+
195
+ rule def redirect_operand
196
+ src_fd = optional{
197
+ read(/[0-9]+/)
198
+ }
199
+ redirect = read(/>|>>|<|&>/)
200
+ inline_sp()
201
+ dest = normal_operand()
202
+ [:redirect, redirect, src_fd, dest]
203
+ end
204
+
205
+ rule def normal_operand
206
+ concats = one_or_more {
207
+ choice(
208
+ ->{ string_double_quote() },
209
+ ->{ string_single_quote() },
210
+ ->{ backquote_operand() },
211
+ ->{ raw_operand() },
212
+ )
213
+ }
214
+ if concats.size > 1
215
+ [:concat, *concats]
216
+ else
217
+ concats.first
218
+ end
219
+ end
220
+
221
+ def string_double_quote_special_process
222
+ choice(
223
+ ->{
224
+ read('\\')
225
+ escaped = read(/./m)
226
+ case escaped
227
+ when 'n'
228
+ "\n"
229
+ when "\n"
230
+ ""
231
+ else
232
+ escaped
233
+ end
234
+ },
235
+ ->{
236
+ inline_command()
237
+ },
238
+ ->{
239
+ variable_reference()
240
+ }
241
+ )
242
+ end
243
+
244
+ rule def string_double_quote
245
+ read('"')
246
+ ret = string_like('"', /[^"\\$]*/) {
247
+ string_double_quote_special_process()
248
+ }
249
+ read('"')
250
+ [:dquote, *ret]
251
+ end
252
+
253
+ rule def inline_command
254
+ read('$(')
255
+ body = pipelines()
256
+ read(')')
257
+ [:inline, body]
258
+ end
259
+
260
+ rule def variable_reference
261
+ read('$')
262
+ ref = choice(
263
+ ->{
264
+ name = read(/[A-Za-z0-9_]+/)
265
+ [name, nil]
266
+ },
267
+ ->{
268
+ read('{')
269
+ name = read(/[A-Za-z0-9_]+/)
270
+ substr = optional {
271
+ variable_expansion()
272
+ }
273
+ read('}')
274
+ [name, substr]
275
+ },
276
+ )
277
+ [:var, *ref]
278
+ end
279
+
280
+ rule def variable_expansion
281
+ choice(
282
+ ->{
283
+ read(':-')
284
+ backtrack
285
+ },
286
+ ->{
287
+ read('-')
288
+ exp = variable_expansion_string()
289
+ ['-', exp]
290
+ },
291
+ )
292
+ end
293
+
294
+ rule def variable_expansion_string
295
+ ret = string_like('}', /[^\\$}]*/) {
296
+ string_double_quote_special_process()
297
+ }
298
+ if ret.size > 1
299
+ ret
300
+ elsif ret.size == 1
301
+ ret.first
302
+ else
303
+ ''
304
+ end
305
+ end
306
+
307
+ rule def string_single_quote
308
+ read("'")
309
+ val = read(/[^']*/)
310
+ read("'")
311
+ val
312
+ end
313
+
314
+ rule def backquote_operand
315
+ read('`')
316
+ read('`')
317
+ backtrack
318
+ end
319
+
320
+ RESERVED_WORDS = %w(
321
+ if then elif else fi while do for in done
322
+ ).to_set
323
+ rule def raw_operand
324
+ backtrack if RESERVED_WORDS.include?(peek(/[^\s]*/))
325
+
326
+ ret = string_like(/[\s&|><;]/, /[^\s&|><;\\$]*/) {
327
+ string_double_quote_special_process()
328
+ }
329
+ if ret.size > 1
330
+ ret
331
+ elsif ret.size == 0 || ret.first.size == 0
332
+ backtrack()
333
+ else
334
+ ret.first
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,55 @@
1
+ require_relative '../lib/pegparse'
2
+
3
+ module Pegparse::Sample
4
+ end
5
+
6
+ class Pegparse::Sample::CalcParser < Pegparse::ParserBase
7
+ def initialize(context_or_scanner)
8
+ super(context_or_scanner)
9
+ self.start_rule_symbol = :expression
10
+ end
11
+
12
+ CalcBiopParser = Pegparse::BiopRuleChain.based_on(self) do
13
+ def operator_sp
14
+ read(/ */)
15
+ end
16
+ def operand_sp
17
+ read(/ */)
18
+ end
19
+ def construct_result(lhs, op, rhs)
20
+ case op
21
+ when '+'
22
+ lhs + rhs
23
+ when '-'
24
+ lhs - rhs
25
+ when '*'
26
+ lhs * rhs
27
+ when '/'
28
+ lhs / rhs
29
+ end
30
+ end
31
+ end
32
+ rule def expression
33
+ biop = CalcBiopParser.new(nil)
34
+ biop
35
+ .left_op(['+', '-'])
36
+ .left_op(['*', '/'])
37
+ .term(->{ term() })
38
+ biop.parse(@context)
39
+ end
40
+
41
+ rule def term
42
+ choice(
43
+ ->{
44
+ read(/[0-9]+(\.[0-9]+)?/).to_f
45
+ },
46
+ ->{
47
+ read('(')
48
+ exp = expression()
49
+ read(')')
50
+ exp
51
+ }
52
+ )
53
+ end
54
+
55
+ end
@@ -0,0 +1,92 @@
1
+ require_relative '../lib/pegparse'
2
+
3
+ module Pegparse::Sample
4
+ end
5
+
6
+ class Pegparse::Sample::JsonParser < Pegparse::ParserBase
7
+ class JsonNullNode
8
+ end
9
+
10
+ def initialize(context_or_scanner)
11
+ super(context_or_scanner)
12
+ self.start_rule_symbol = :start_rule
13
+ end
14
+
15
+ def start_rule
16
+ sp()
17
+ json_node()
18
+ end
19
+
20
+ rule def json_node
21
+ choice(
22
+ ->{ json_number_node() },
23
+ ->{ json_string_node() },
24
+ ->{ json_null_node() },
25
+ ->{ json_bool_node() },
26
+ ->{ json_object_node() },
27
+ ->{ json_array_node() },
28
+ )
29
+ end
30
+
31
+ def json_number_node
32
+ str = read(/[0-9]+(\.[0-9]+)?/)
33
+ str.to_f
34
+ end
35
+
36
+ rule def json_string_node
37
+ read('"')
38
+ ret = string_like('"', /[^"\\]*/){
39
+ read("\\")
40
+ read(/./m)
41
+ }
42
+ read('"')
43
+ ret[0]
44
+ end
45
+
46
+ def json_null_node
47
+ read('null')
48
+ JsonNullNode.new
49
+ end
50
+
51
+ def json_bool_node
52
+ choice(
53
+ ->{
54
+ read('true')
55
+ true
56
+ },
57
+ ->{
58
+ read('false')
59
+ false
60
+ },
61
+ )
62
+ end
63
+
64
+ rule def json_object_node
65
+ read('{')
66
+ sp()
67
+ pairs = separative(',') {
68
+ key = json_object_key()
69
+ sp()
70
+ read(':')
71
+ sp()
72
+ val = json_node()
73
+ [key, val]
74
+ }
75
+ sp()
76
+ read('}')
77
+ pairs.to_h
78
+ end
79
+
80
+ def json_object_key
81
+ json_string_node()
82
+ end
83
+
84
+ rule def json_array_node
85
+ read('[')
86
+ sp()
87
+ elements = separative(',') { json_node() }
88
+ sp()
89
+ read(']')
90
+ elements
91
+ end
92
+ end
@@ -0,0 +1,182 @@
1
+ require_relative '../lib/pegparse'
2
+
3
+ module Pegparse::Sample
4
+ end
5
+
6
+ class Pegparse::Sample::XmlParser < Pegparse::ParserBase
7
+ XmlNode = Struct.new(
8
+ :tag, :attrs, :inner_nodes,
9
+ keyword_init: true,
10
+ )
11
+
12
+ XmlAttr = Struct.new(
13
+ :name, :value,
14
+ keyword_init: true,
15
+ )
16
+
17
+ def initialize(context_or_scanner)
18
+ super(context_or_scanner)
19
+ self.start_rule_symbol = :start_rule
20
+ end
21
+
22
+ rule def block_comment
23
+ read('<!--')
24
+ read(/.*?-->/m)
25
+ end
26
+
27
+ def start_rule
28
+ sp()
29
+ decl = optional{ doc_declaration() }
30
+ nodes = xml_nodes()
31
+ [decl, nodes]
32
+ end
33
+
34
+ def intag_sp!
35
+ read(/\s+/)
36
+ end
37
+
38
+ def intag_sp
39
+ read(/\s*/)
40
+ end
41
+
42
+ def doc_declaration
43
+ choice(
44
+ ->{ xml_declaration() },
45
+ ->{ html_declaration() },
46
+ )
47
+ end
48
+
49
+ rule def xml_declaration
50
+ read('<?xml')
51
+ attrs = xml_attributes()
52
+ intag_sp()
53
+ read('>')
54
+ attrs
55
+ end
56
+
57
+ rule def html_declaration
58
+ read(/<!doctype html>/i)
59
+ end
60
+
61
+ def xml_attributes
62
+ attrs = zero_or_more {
63
+ intag_sp!()
64
+ xml_attribute_pair()
65
+ }
66
+ attrs
67
+ end
68
+
69
+ def xml_attribute_pair
70
+ name = xml_attribute_name()
71
+ read('=')
72
+ val = xml_attribute_value()
73
+ XmlAttr.new(name: name, value: val)
74
+ end
75
+
76
+ rule def xml_attribute_name
77
+ read(/[A-Za-z][A-Za-z0-9\-]*/)
78
+ end
79
+
80
+ rule def xml_tag_name
81
+ read(/[A-Za-z][A-Za-z0-9\-]*/)
82
+ end
83
+
84
+ rule def xml_attribute_value
85
+ choice(
86
+ ->{ single_quote_string() },
87
+ ->{ double_quote_string() },
88
+ )
89
+ end
90
+
91
+ def single_quote_string
92
+ read("'")
93
+ ret = string_like("'", /[^'\\]*/){
94
+ read('\\')
95
+ read(/./m)
96
+ }
97
+ read("'")
98
+ ret[0]
99
+ end
100
+
101
+ def double_quote_string
102
+ read('"')
103
+ ret = string_like('"', /[^"\\]*/){
104
+ read('\\')
105
+ read(/./m)
106
+ }
107
+ read('"')
108
+ ret[0]
109
+ end
110
+
111
+ rule def xml_open_tag
112
+ read('<')
113
+ name = xml_tag_name()
114
+ attrs = xml_attributes()
115
+ intag_sp()
116
+ read('>')
117
+ [name, attrs]
118
+ end
119
+
120
+ rule def xml_close_tag
121
+ read('</')
122
+ intag_sp()
123
+ name = xml_tag_name()
124
+ intag_sp()
125
+ read('>')
126
+ name
127
+ end
128
+
129
+ rule def xml_empty_element_tag
130
+ read('<')
131
+ name = xml_tag_name()
132
+ attrs = xml_attributes()
133
+ intag_sp()
134
+ read('/>')
135
+ [name, attrs]
136
+ end
137
+
138
+ SPECIAL_NONEST_TAG = %w(meta br)
139
+ rule def html_special_empty_element_tag
140
+ read('<')
141
+ name = xml_tag_name()
142
+ backtrack() unless SPECIAL_NONEST_TAG.include?(name)
143
+ attrs = xml_attributes()
144
+ intag_sp()
145
+ read(/\/?>/)
146
+ [name, attrs]
147
+ end
148
+
149
+ def xml_node
150
+ choice(
151
+ ->{
152
+ block_comment()
153
+ },
154
+ ->{
155
+ text = read(/[^<]+/)
156
+ text
157
+ },
158
+ ->{
159
+ tag = xml_empty_element_tag()
160
+ XmlNode.new(tag: tag[0], attrs: tag[1], inner_nodes: nil)
161
+ },
162
+ ->{
163
+ tag = html_special_empty_element_tag()
164
+ XmlNode.new(tag: tag[0], attrs: tag[1], inner_nodes: nil)
165
+ },
166
+ ->{
167
+ opentag = xml_open_tag()
168
+ inner = xml_nodes()
169
+ closetag = xml_close_tag()
170
+ backtrack if opentag[0] != closetag
171
+ XmlNode.new(tag: opentag[0], attrs: opentag[1], inner_nodes: inner)
172
+ }
173
+ )
174
+ end
175
+
176
+ def xml_nodes
177
+ nodes = zero_or_more {
178
+ xml_node()
179
+ }
180
+ nodes
181
+ end
182
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pegparse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Riki Ishikawa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: provide base class for PEG like recursive descent parser.
14
+ email:
15
+ - riki.ishikawa@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rubocop.yml"
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - bin/console
27
+ - bin/setup
28
+ - lib/pegparse.rb
29
+ - lib/pegparse/biop_rule_chain.rb
30
+ - lib/pegparse/borrowed_areas.rb
31
+ - lib/pegparse/line_counter.rb
32
+ - lib/pegparse/parser_base.rb
33
+ - lib/pegparse/parser_context.rb
34
+ - lib/pegparse/parser_core.rb
35
+ - lib/pegparse/parser_errors.rb
36
+ - lib/pegparse/version.rb
37
+ - pegparse.gemspec
38
+ - samples/bsh_parser.rb
39
+ - samples/calc_parser.rb
40
+ - samples/json_parser.rb
41
+ - samples/xml_parser.rb
42
+ homepage: https://github.com/jljse/pegparse
43
+ licenses:
44
+ - MIT
45
+ metadata:
46
+ homepage_uri: https://github.com/jljse/pegparse
47
+ source_code_uri: https://github.com/jljse/pegparse
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 3.0.0
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubygems_version: 3.2.22
64
+ signing_key:
65
+ specification_version: 4
66
+ summary: library to create recursive descent parser.
67
+ test_files: []