zenml 1.2.1 → 1.3.0

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: 588639c3751d09fb4485184ed90ff37680b490d7e5a4417142ca8587eb48242b
4
- data.tar.gz: 63c74b6fee69636440dc0c26a3c74c2145c39a7e879c3d3094913fe8c12e44ad
3
+ metadata.gz: dc0c51d446a26f7b640f5a4ab3c4054d131ab89f98ab7c0effca28387e06b9b9
4
+ data.tar.gz: 43860c32ec5abf88af046191eeb57941ebc67cfde701229b459dfb2c9439f525
5
5
  SHA512:
6
- metadata.gz: b62748fe59945691582eaec70ac2f1870133d4e4f661373dc10a8e3b40b2d8b879248b1a1f74571bb7e31acb314fb7c5b16171eebb303648984f6c55ce396008
7
- data.tar.gz: 0e4042446f70cf50db75874407aa111be819d1c0683bcd96ee68affc059fc8993d22ce6f14edb46aed16d39c253ab3b6b1a1d7e81045bd2e4b9e9248f9c46d70
6
+ metadata.gz: baf2791fe4d95b718a2251152d8edb917e92c3d0da35ed9578ae1b4ac8fb2c48964223819eb9e06bd6619a25ee1e4bf84c4b10c81cf955f4a7b877a3a0db727d
7
+ data.tar.gz: 0e8b566980d4870679e70426c5da2114abb3927c9c59f077f0e100b5c6685fab507bc141708744fb1d37f5294eb401900bf5019fa76a39e6d5e4e24a84ff5986
@@ -18,8 +18,8 @@ class ZenithalConverter
18
18
  @configs = {}
19
19
  @templates = {}
20
20
  @functions = {}
21
- @default_element_template = lambda{|s| (@type == :text) ? "" : Nodes[]}
22
- @default_text_template = lambda{|s| (@type == :text) ? "" : Nodes[]}
21
+ @default_element_template = lambda{|_| empty_nodes}
22
+ @default_text_template = lambda{|_| empty_nodes}
23
23
  end
24
24
 
25
25
  def convert(initial_scope = "")
@@ -59,7 +59,7 @@ class ZenithalConverter
59
59
  end
60
60
 
61
61
  def apply(element, scope, *args)
62
- nodes = (@type == :text) ? "" : Nodes[]
62
+ nodes = empty_nodes
63
63
  element.children.each do |child|
64
64
  case child
65
65
  when Element
@@ -78,7 +78,7 @@ class ZenithalConverter
78
78
  end
79
79
 
80
80
  def apply_select(element, xpath, scope, *args)
81
- nodes = (@type == :text) ? "" : Nodes[]
81
+ nodes = empty_nodes
82
82
  element.each_xpath(xpath) do |child|
83
83
  case child
84
84
  when Element
@@ -97,7 +97,7 @@ class ZenithalConverter
97
97
  end
98
98
 
99
99
  def call(element, name, *args)
100
- nodes = (@type == :text) ? "" : Nodes[]
100
+ nodes = empty_nodes
101
101
  @functions.each do |function_name, block|
102
102
  if function_name == name
103
103
  nodes = instance_exec(element, *args, &block)
@@ -123,6 +123,11 @@ class ZenithalConverter
123
123
  end
124
124
  end
125
125
 
126
+ def empty_nodes
127
+ return (@type == :text) ? "" : Nodes[]
128
+ end
129
+
130
+ ## Returns a simple converter that converts an XML document to the equivalent HTML document.
126
131
  def self.simple_html(document)
127
132
  converter = ZenithalConverter.new(document, :text)
128
133
  converter.add([//], [""]) do |element|
@@ -44,262 +44,271 @@ module ZenithalParserMethod
44
44
  0x3001..0xD7FF, 0xF900..0xFDCF, 0xFDF0..0xFFFD, 0x10000..0xEFFFF
45
45
  ]
46
46
 
47
+ def parse
48
+ unless @inside_run
49
+ warn("This method is now only for internal use. Use 'run' instead.", uplevel: 1)
50
+ end
51
+ if @whole
52
+ parse_document
53
+ else
54
+ parse_nodes({})
55
+ end
56
+ end
57
+
47
58
  private
48
59
 
49
60
  def parse_document
50
- parser = Parser.build(self) do
51
- document = Document.new
52
- children = +parse_nodes({})
53
- +parse_eof
54
- children.each do |child|
55
- document.add(child)
56
- end
57
- next document
61
+ document = Document.new
62
+ children = parse_nodes({})
63
+ if @exact
64
+ parse_eof
58
65
  end
59
- return parser
66
+ children.each do |child|
67
+ document.add(child)
68
+ end
69
+ return document
60
70
  end
61
71
 
62
72
  def parse_nodes(options)
63
- parser = Parser.build(self) do
64
- parsers = [parse_text(options)]
65
- unless options[:verbal]
66
- parsers.push(parse_element(options), parse_line_comment(options), parse_block_comment(options))
67
- @special_element_names.each do |kind, name|
68
- parsers.push(parse_special_element(kind, options))
73
+ nodes = nil
74
+ if options[:plugin]
75
+ nodes = options[:plugin].parse
76
+ elsif options[:verbal]
77
+ raw_nodes = many(->{parse_text(options)})
78
+ nodes = raw_nodes.inject(Nodes[], :<<)
79
+ else
80
+ parsers = []
81
+ parsers << ->{parse_element(options)}
82
+ if options[:in_slash]
83
+ next_options = options.clone
84
+ next_options.delete(:in_slash)
85
+ else
86
+ next_options = options
87
+ end
88
+ @special_element_names.each do |kind, name|
89
+ unless kind == :slash && options[:in_slash]
90
+ parsers << ->{parse_special_element(kind, next_options)}
69
91
  end
70
92
  end
71
- raw_nodes = +parsers.inject(:|).many
93
+ parsers << ->{parse_comment(options)}
94
+ parsers << ->{parse_text(options)}
95
+ raw_nodes = many(->{choose(*parsers)})
72
96
  nodes = raw_nodes.inject(Nodes[], :<<)
73
- next nodes
74
97
  end
75
- return parser
98
+ return nodes
76
99
  end
77
100
 
78
101
  def parse_element(options)
79
- parser = Parser.build(self) do
80
- start_char = +parse_char_any([ELEMENT_START, MACRO_START])
81
- name = +parse_identifier(options)
82
- marks = +parse_marks(options)
83
- attributes = +parse_attributes(options).maybe || {}
84
- next_options = determine_options(name, marks, attributes, start_char == MACRO_START, options)
85
- children_list = +parse_children_list(next_options)
86
- if name == SYSTEM_INSTRUCTION_NAME
87
- +parse_space
88
- end
89
- if start_char == MACRO_START
90
- next process_macro(name, marks, attributes, children_list, options)
91
- else
92
- next create_element(name, marks, attributes, children_list, options)
93
- end
102
+ start_char = parse_char_any([ELEMENT_START, MACRO_START])
103
+ name = parse_identifier(options)
104
+ marks = parse_marks(options)
105
+ attributes = maybe(->{parse_attributes(options)}) || {}
106
+ macro = start_char == MACRO_START
107
+ next_options = determine_options(name, marks, attributes, macro, options)
108
+ children_list = parse_children_list(next_options)
109
+ if name == SYSTEM_INSTRUCTION_NAME
110
+ parse_space
111
+ end
112
+ if start_char == MACRO_START
113
+ element = process_macro(name, marks, attributes, children_list, options)
114
+ else
115
+ element = create_element(name, marks, attributes, children_list, options)
94
116
  end
95
- return parser
117
+ return element
96
118
  end
97
119
 
98
120
  def parse_special_element(kind, options)
99
- parser = Parser.build(self) do
100
- unless @special_element_names[kind]
101
- +parse_none
102
- end
103
- +parse_char(SPECIAL_ELEMENT_STARTS[kind])
104
- children = +parse_nodes(options)
105
- +parse_char(SPECIAL_ELEMENT_ENDS[kind])
106
- next create_special_element(kind, children, options)
121
+ parse_char(SPECIAL_ELEMENT_STARTS[kind])
122
+ if kind == :slash
123
+ next_options = options.clone
124
+ next_options[:in_slash] = true
125
+ else
126
+ next_options = options
107
127
  end
108
- return parser
128
+ children = parse_nodes(next_options)
129
+ parse_char(SPECIAL_ELEMENT_ENDS[kind])
130
+ element = create_special_element(kind, children, options)
131
+ return element
109
132
  end
110
133
 
111
134
  def parse_marks(options)
112
- return parse_mark(options).many
135
+ marks = many(->{parse_mark(options)})
136
+ return marks
113
137
  end
114
138
 
115
139
  def parse_mark(options)
116
140
  parsers = MARKS.map do |mark, query|
117
- next parse_char(query).map{|_| mark}
141
+ next ->{parse_char(query).yield_self{|_| mark}}
118
142
  end
119
- return parsers.inject(:|)
143
+ mark = choose(*parsers)
144
+ return mark
120
145
  end
121
146
 
122
147
  def parse_attributes(options)
123
- parser = Parser.build(self) do
124
- +parse_char(ATTRIBUTE_START)
125
- first_attribute = +parse_attribute(true, options)
126
- rest_attribtues = +parse_attribute(false, options).many
127
- attributes = first_attribute.merge(*rest_attribtues)
128
- +parse_char(ATTRIBUTE_END)
129
- next attributes
130
- end
131
- return parser
148
+ parse_char(ATTRIBUTE_START)
149
+ first_attribute = parse_attribute(true, options)
150
+ rest_attribtues = many(->{parse_attribute(false, options)})
151
+ attributes = first_attribute.merge(*rest_attribtues)
152
+ parse_char(ATTRIBUTE_END)
153
+ return attributes
132
154
  end
133
155
 
134
156
  def parse_attribute(first, options)
135
- parser = Parser.build(self) do
136
- +parse_char(ATTRIBUTE_SEPARATOR) unless first
137
- +parse_space
138
- name = +parse_identifier(options)
139
- +parse_space
140
- value = +parse_attribute_value(options).maybe || name
141
- +parse_space
142
- next {name => value}
157
+ unless first
158
+ parse_char(ATTRIBUTE_SEPARATOR)
143
159
  end
144
- return parser
160
+ parse_space
161
+ name = parse_identifier(options)
162
+ parse_space
163
+ value = maybe(->{parse_attribute_value(options)}) || name
164
+ parse_space
165
+ attribute = {name => value}
166
+ return attribute
145
167
  end
146
168
 
147
169
  def parse_attribute_value(options)
148
- parser = Parser.build(self) do
149
- +parse_char(ATTRIBUTE_EQUAL)
150
- +parse_space
151
- value = +parse_string(options)
152
- next value
153
- end
154
- return parser
170
+ parse_char(ATTRIBUTE_EQUAL)
171
+ parse_space
172
+ value = parse_string(options)
173
+ return value
155
174
  end
156
175
 
157
176
  def parse_string(options)
158
- parser = Parser.build(self) do
159
- +parse_char(STRING_START)
160
- strings = +(parse_string_plain(options) | parse_escape(:string, options)).many
161
- +parse_char(STRING_END)
162
- next strings.join
163
- end
164
- return parser
177
+ parse_char(STRING_START)
178
+ strings = many(->{parse_string_plain_or_escape(options)})
179
+ parse_char(STRING_END)
180
+ string = strings.join
181
+ return string
165
182
  end
166
183
 
167
184
  def parse_string_plain(options)
168
- parser = Parser.build(self) do
169
- chars = +parse_char_out([STRING_END, ESCAPE_START]).many(1)
170
- next chars.join
171
- end
172
- return parser
185
+ chars = many(->{parse_char_out([STRING_END, ESCAPE_START])}, 1..)
186
+ string = chars.join
187
+ return string
188
+ end
189
+
190
+ def parse_string_plain_or_escape(options)
191
+ string = choose(->{parse_escape(:string, options)}, ->{parse_string_plain(options)})
192
+ return string
173
193
  end
174
194
 
175
195
  def parse_children_list(options)
176
- parser = Parser.build(self) do
177
- first_children = +(parse_empty_children(options) | parse_children(options))
178
- rest_children_list = +parse_children(options).many
179
- children_list = [first_children] + rest_children_list
180
- next children_list
181
- end
182
- return parser
196
+ first_children = choose(->{parse_empty_children(options)}, ->{parse_children(options)})
197
+ rest_children_list = many(->{parse_children(options)})
198
+ children_list = [first_children] + rest_children_list
199
+ return children_list
183
200
  end
184
201
 
185
202
  def parse_children(options)
186
- parser = Parser.build(self) do
187
- +parse_char(CONTENT_START)
188
- children = +parse_nodes(options)
189
- +parse_char(CONTENT_END)
190
- next children
191
- end
192
- return parser
203
+ parse_char(CONTENT_START)
204
+ children = parse_nodes(options)
205
+ parse_char(CONTENT_END)
206
+ return children
193
207
  end
194
208
 
195
209
  def parse_empty_children(options)
196
- parser = Parser.build(self) do
197
- +parse_char(CONTENT_END)
198
- next Nodes[]
199
- end
200
- return parser
210
+ parse_char(CONTENT_END)
211
+ children = Nodes[]
212
+ return children
201
213
  end
202
214
 
203
215
  def parse_text(options)
204
- parser = Parser.build(self) do
205
- raw_texts = +(parse_text_plain(options) | parse_escape(:text, options)).many(1)
206
- next create_text(raw_texts.join, options)
207
- end
208
- return parser
216
+ raw_texts = many(->{parse_text_plain_or_escape(options)}, 1..)
217
+ text = create_text(raw_texts.join, options)
218
+ return text
209
219
  end
210
220
 
211
221
  def parse_text_plain(options)
212
- parser = Parser.build(self) do
213
- out_chars = [ESCAPE_START, CONTENT_END]
214
- unless options[:verbal]
215
- out_chars.push(ELEMENT_START, MACRO_START, CONTENT_START, COMMENT_DELIMITER)
216
- @special_element_names.each do |kind, name|
217
- out_chars.push(SPECIAL_ELEMENT_STARTS[kind], SPECIAL_ELEMENT_ENDS[kind]) if name
218
- end
222
+ out_chars = [ESCAPE_START, CONTENT_END]
223
+ unless options[:verbal]
224
+ out_chars.push(ELEMENT_START, MACRO_START, CONTENT_START, COMMENT_DELIMITER)
225
+ @special_element_names.each do |kind, name|
226
+ out_chars.push(SPECIAL_ELEMENT_STARTS[kind], SPECIAL_ELEMENT_ENDS[kind])
219
227
  end
220
- chars = +parse_char_out(out_chars).many(1)
221
- next chars.join
222
228
  end
223
- return parser
229
+ chars = many(->{parse_char_out(out_chars)}, 1..)
230
+ string = chars.join
231
+ return string
232
+ end
233
+
234
+ def parse_text_plain_or_escape(options)
235
+ string = choose(->{parse_escape(:text, options)}, ->{parse_text_plain(options)})
236
+ return string
237
+ end
238
+
239
+ def parse_comment(options)
240
+ parse_char(COMMENT_DELIMITER)
241
+ comment = choose(->{parse_line_comment(options)}, ->{parse_block_comment(options)})
242
+ return comment
224
243
  end
225
244
 
226
245
  def parse_line_comment(options)
227
- parser = Parser.build(self) do
228
- +parse_char(COMMENT_DELIMITER)
229
- +parse_char(COMMENT_DELIMITER)
230
- content = +parse_line_comment_content(options)
231
- +parse_char("\n")
232
- next create_comment(:line, content, options)
233
- end
234
- return parser
246
+ parse_char(COMMENT_DELIMITER)
247
+ content = parse_line_comment_content(options)
248
+ parse_char("\n")
249
+ comment = create_comment(:line, content, options)
250
+ return comment
235
251
  end
236
252
 
237
253
  def parse_line_comment_content(options)
238
- parser = Parser.build(self) do
239
- chars = +parse_char_out(["\n"]).many
240
- next chars.join
241
- end
242
- return parser
254
+ chars = many(->{parse_char_out(["\n"])})
255
+ content = chars.join
256
+ return content
243
257
  end
244
258
 
245
259
  def parse_block_comment(options)
246
- parser = Parser.build(self) do
247
- +parse_char(COMMENT_DELIMITER)
248
- +parse_char(CONTENT_START)
249
- content = +parse_block_comment_content(options)
250
- +parse_char(CONTENT_END)
251
- +parse_char(COMMENT_DELIMITER)
252
- next create_comment(:block, content, options)
253
- end
254
- return parser
260
+ parse_char(CONTENT_START)
261
+ content = parse_block_comment_content(options)
262
+ parse_char(CONTENT_END)
263
+ parse_char(COMMENT_DELIMITER)
264
+ comment = create_comment(:block, content, options)
265
+ return comment
255
266
  end
256
267
 
257
268
  def parse_block_comment_content(options)
258
- parser = Parser.build(self) do
259
- chars = +parse_char_out([CONTENT_END]).many
260
- next chars.join
261
- end
262
- return parser
269
+ chars = many(->{parse_char_out([CONTENT_END])})
270
+ content = chars.join
271
+ return content
263
272
  end
264
273
 
265
274
  def parse_escape(place, options)
266
- parser = Parser.build(self) do
267
- +parse_char(ESCAPE_START)
268
- char = +parse_char
269
- next create_escape(place, char, options)
270
- end
271
- return parser
275
+ parse_char(ESCAPE_START)
276
+ char = parse_char
277
+ escape = create_escape(place, char, options)
278
+ return escape
272
279
  end
273
280
 
274
281
  def parse_identifier(options)
275
- parser = Parser.build(self) do
276
- first_char = +parse_first_identifier_char(options)
277
- rest_chars = +parse_middle_identifier_char(options).many
278
- identifier = first_char + rest_chars.join
279
- next identifier
280
- end
281
- return parser
282
+ first_char = parse_first_identifier_char(options)
283
+ rest_chars = many(->{parse_middle_identifier_char(options)})
284
+ identifier = first_char + rest_chars.join
285
+ return identifier
282
286
  end
283
287
 
284
288
  def parse_first_identifier_char(options)
285
- return parse_char_any(VALID_FIRST_IDENTIFIER_CHARS)
289
+ char = parse_char_any(VALID_FIRST_IDENTIFIER_CHARS)
290
+ return char
286
291
  end
287
292
 
288
293
  def parse_middle_identifier_char(options)
289
- return parse_char_any(VALID_MIDDLE_IDENTIFIER_CHARS)
294
+ char = parse_char_any(VALID_MIDDLE_IDENTIFIER_CHARS)
295
+ return char
290
296
  end
291
297
 
292
298
  def parse_space
293
- return parse_char_any(SPACE_CHARS).many
299
+ space = many(->{parse_char_any(SPACE_CHARS)})
300
+ return space
294
301
  end
295
302
 
296
- # Determines options which are used when parsing the children nodes.
297
- # This method may be overrided in order to change the parsing behaviour for another format based on ZenML.
298
303
  def determine_options(name, marks, attributes, macro, options)
299
304
  if marks.include?(:verbal)
300
305
  options = options.clone
301
306
  options[:verbal] = true
302
307
  end
308
+ if macro && @plugins.key?(name)
309
+ options = options.clone
310
+ options[:plugin] = @plugins[name].call(attributes)
311
+ end
303
312
  return options
304
313
  end
305
314
 
@@ -312,12 +321,12 @@ module ZenithalParserMethod
312
321
  end
313
322
  if marks.include?(:instruction)
314
323
  unless children_list.size <= 1
315
- throw(:error, error_message("Processing instruction cannot have more than one argument"))
324
+ throw_custom(error_message("Processing instruction cannot have more than one argument"))
316
325
  end
317
326
  nodes = create_instruction(name, attributes, children_list.first, options)
318
327
  else
319
328
  unless marks.include?(:multiple) || children_list.size <= 1
320
- throw(:error, error_message("Normal node cannot have more than one argument"))
329
+ throw_custom(error_message("Normal element cannot have more than one argument"))
321
330
  end
322
331
  nodes = create_normal_element(name, attributes, children_list, options)
323
332
  end
@@ -369,21 +378,27 @@ module ZenithalParserMethod
369
378
 
370
379
  def create_special_element(kind, children, options)
371
380
  name = @special_element_names[kind]
372
- nodes = create_element(name, [], {}, [children], options)
381
+ if name
382
+ nodes = create_element(name, [], {}, [children], options)
383
+ else
384
+ throw_custom(error_message("No name specified for #{kind} elements"))
385
+ end
373
386
  return nodes
374
387
  end
375
388
 
376
389
  def create_text(raw_text, options)
377
- return Text.new(raw_text, true, nil, false)
390
+ text = Text.new(raw_text, true, nil, false)
391
+ return text
378
392
  end
379
393
 
380
394
  def create_comment(kind, content, options)
381
- return Comment.new(" " + content.strip + " ")
395
+ comment = Comment.new(" " + content.strip + " ")
396
+ return comment
382
397
  end
383
398
 
384
399
  def create_escape(place, char, options)
385
400
  unless ESCAPE_CHARS.include?(char)
386
- throw(:error, "Invalid escape")
401
+ throw_custom(error_message("Invalid escape"))
387
402
  end
388
403
  return char
389
404
  end
@@ -395,44 +410,57 @@ module ZenithalParserMethod
395
410
  raw_elements.each do |raw_element|
396
411
  elements << raw_element
397
412
  end
413
+ elsif @plugins.key?(name)
414
+ elements = children_list.first
398
415
  else
399
- throw(:error, error_message("No such macro"))
416
+ throw_custom(error_message("No such macro '#{name}'"))
400
417
  end
401
418
  return elements
402
419
  end
403
420
 
404
- def error_message(message)
405
- return "[line #{@source.lineno}] #{message}"
406
- end
407
-
408
421
  end
409
422
 
410
423
 
411
- class ZenithalParser
424
+ class ZenithalParser < Parser
412
425
 
413
- include CommonParserMethod
414
426
  include ZenithalParserMethod
415
427
 
416
- attr_reader :source
428
+ attr_accessor :exact
429
+ attr_accessor :whole
417
430
 
418
431
  def initialize(source)
419
- @source = StringReader.new(source)
432
+ super(source)
420
433
  @version = nil
434
+ @exact = true
435
+ @whole = true
421
436
  @special_element_names = {:brace => nil, :bracket => nil, :slash => nil}
422
437
  @macros = {}
438
+ @plugins = {}
423
439
  end
424
440
 
425
- def parse
426
- result = parse_document.exec
427
- if result.success?
428
- return result.value
429
- else
430
- raise ZenithalParseError.new(result.message)
441
+ # Registers a macro.
442
+ # To the argument block will be passed two arguments: the first is a hash of the attributes, the second is a list of the children nodes.
443
+ def register_macro(name, &block)
444
+ @macros.store(name, block)
445
+ end
446
+
447
+ def unregister_macro(name)
448
+ @macros.delete(name)
449
+ end
450
+
451
+ # Registers a plugin, which enables us to apply another parser in certain macros.
452
+ # If a class instance is passed, simply an instance of that class will be created and used as a custom parser.
453
+ # If a block is passed, it will be called to create a custom parser.
454
+ # To this block will be passed one argument: the attributes which are specified to the macro.
455
+ def register_plugin(name, clazz = nil, &block)
456
+ if clazz
457
+ block = lambda{|_| clazz.new(@source)}
431
458
  end
459
+ @plugins.store(name, block)
432
460
  end
433
461
 
434
- def register_macro(name, &block)
435
- @macros.store(name, block)
462
+ def unregister_plugin(name)
463
+ @plugins.delete(name)
436
464
  end
437
465
 
438
466
  def brace_name=(name)
@@ -3,227 +3,209 @@
3
3
 
4
4
  class Parser
5
5
 
6
- attr_reader :builder
7
-
8
- def initialize(builder, &method)
9
- @builder = builder
10
- @method = method
11
- end
12
-
13
- def self.build(source, &block)
14
- parser = Parser.new(source) do
15
- value = nil
16
- message = catch(:error) do
17
- value = block.call
18
- end
19
- if value
20
- next Result.success(value)
21
- else
22
- next Result.error(message)
23
- end
24
- end
25
- return parser
26
- end
27
-
28
- def exec
29
- return @builder.instance_eval(&@method)
30
- end
31
-
32
- def exec_get
33
- result = self.exec
34
- if result.success?
35
- return result.value
6
+ ERROR_TAG = Object.new
7
+
8
+ def initialize(source)
9
+ case source
10
+ when StringReader
11
+ @source = source
12
+ when File
13
+ @source = StringReader.new(source.read)
36
14
  else
37
- throw(:error, result.message)
15
+ @source = StringReader.new(source.to_s)
38
16
  end
17
+ @inside_run = false
39
18
  end
40
19
 
41
- alias -@ exec
42
- alias +@ exec_get
43
-
44
- def |(other)
45
- this = self
46
- if this.builder.equal?(other.builder)
47
- parser = Parser.new(this.builder) do
48
- mark = source.mark
49
- result = this.exec
50
- if result.success?
51
- next result
52
- else
53
- source.reset(mark)
54
- result = other.exec
55
- next result
56
- end
20
+ def run
21
+ value = nil
22
+ message = catch(ERROR_TAG) do
23
+ begin
24
+ @inside_run = true
25
+ value = parse
26
+ ensure
27
+ @inside_run = false
57
28
  end
58
- return parser
59
- else
60
- raise StandardError.new("Different source")
61
29
  end
62
- end
63
-
64
- def many(lower_limit = 0, upper_limit = nil)
65
- this = self
66
- parser = Parser.new(this.builder) do
67
- values, count = [], 0
68
- loop do
69
- mark = source.mark
70
- each_result = this.exec
71
- if each_result.success?
72
- values << each_result.value
73
- count += 1
74
- if upper_limit && count >= upper_limit
75
- break
76
- end
77
- else
78
- source.reset(mark)
79
- break
80
- end
81
- end
82
- if count >= lower_limit
83
- next Result.success(values)
84
- else
85
- next Result.error("")
86
- end
30
+ unless value
31
+ raise ZenithalParseError.new(message)
87
32
  end
88
- return parser
89
- end
90
-
91
- def maybe
92
- return self.many(0, 1).map{|s| s.first}
33
+ return value
93
34
  end
94
35
 
95
- def map(&block)
96
- this = self
97
- parser = Parser.new(this.builder) do
98
- result = this.exec
99
- if result.success?
100
- next Result.success(block.call(result.value))
101
- else
102
- next result
103
- end
104
- end
105
- return parser
36
+ # Parses a whole data.
37
+ # This method is intended to be overridden in subclasses.
38
+ def parse
39
+ throw_custom("Not implemented")
40
+ return nil
106
41
  end
107
42
 
108
- end
109
-
110
-
111
- module CommonParserMethod
112
-
113
43
  private
114
44
 
115
45
  # Parses a single character which matches the specified query.
116
- # If the next character does not match the query or the end of file is reached, an error result is returned.
117
- # Otherwise, a success result with a string containing the matched chracter is returned.
46
+ # If the next character does not match the query or the end of file is reached, then an error occurs and no input is consumed.
47
+ # Otherwise, a string which consists of the matched single chracter is returned.
118
48
  def parse_char(query = nil)
119
- parser = Parser.new(self) do
120
- char = source.read
121
- unless char == nil
122
- predicate, message = false, nil
123
- case query
124
- when String
125
- predicate = query == char
126
- message = "Expected '#{query}'"
127
- when Regexp
128
- predicate = query =~ char
129
- message = "Expected /#{query}/"
130
- when Integer
131
- predicate = query == char.ord
132
- message = "Expected '#{query.chr}'"
133
- when Range
134
- predicate = query.cover?(char.ord)
135
- message = "Expected '#{query.begin}'..'#{query.end}'"
136
- when NilClass
137
- predicate = true
138
- message = ""
139
- end
140
- if predicate
141
- next Result.success(char)
142
- else
143
- next Result.error(error_message(message))
144
- end
145
- else
146
- next Result.error(error_message("Unexpected end of file"))
49
+ char = @source.peek
50
+ if char
51
+ predicate, message = false, nil
52
+ case query
53
+ when String
54
+ predicate = query == char
55
+ message = "Expected '#{query}' but got '#{char}'"
56
+ when Regexp
57
+ predicate = query =~ char
58
+ message = "Expected /#{query}/ but got '#{char}'"
59
+ when Integer
60
+ predicate = query == char.ord
61
+ message = "Expected '##{query}' but got '#{char}'"
62
+ when Range
63
+ predicate = query.cover?(char.ord)
64
+ symbol = (query.exclude_end?) ? "..." : ".."
65
+ message = "Expected '##{query.begin}'#{symbol}'##{query.end}' but got '#{char}'"
66
+ when NilClass
67
+ predicate = true
68
+ message = ""
69
+ end
70
+ unless predicate
71
+ throw_custom(error_message(message))
147
72
  end
73
+ else
74
+ throw_custom(error_message("Unexpected end of file"))
148
75
  end
149
- return parser
76
+ char = @source.read
77
+ return char
150
78
  end
151
79
 
152
80
  # Parses a single character which matches any of the specified queries.
153
81
  def parse_char_any(queries)
154
- return queries.map{|s| parse_char(s)}.inject(:|)
82
+ parsers = []
83
+ queries.each do |query|
84
+ parsers << ->{parse_char(query)}
85
+ end
86
+ char = choose(*parsers)
87
+ return char
155
88
  end
156
89
 
157
90
  # Parses a single character other than the specified characters.
158
- # If the next character coincides with any of the elements of the arguments, an error result is returned.
159
- # Otherwise, a success result with a string containing the next chracter is returned.
91
+ # If the next character coincides with any of the elements of the arguments, then an error occurs and no input is consumed.
92
+ # Otherwise, a string which consists of the next single chracter is returned.
160
93
  def parse_char_out(chars)
161
- parser = Parser.new(self) do
162
- char = source.read
163
- if char && chars.all?{|s| s != char}
164
- next Result.success(char)
165
- else
166
- message = "Expected other than " + chars.map{|s| "'#{s}'"}.join(", ")
167
- next Result.error(error_message(message))
94
+ char = @source.peek
95
+ if char
96
+ if chars.any?{|s| s == char}
97
+ chars_string = chars.map{|s| "'#{s}'"}.join(", ")
98
+ throw_custom(error_message("Expected other than #{chars_string} but got '#{char}'"))
168
99
  end
100
+ else
101
+ throw_custom(error_message("Unexpected end of file"))
169
102
  end
170
- return parser
103
+ char = @source.read
104
+ return char
171
105
  end
172
106
 
173
107
  def parse_eof
174
- parser = Parser.new(self) do
175
- char = source.read
176
- if char == nil
177
- next Result.success(true)
178
- else
179
- next Result.error(error_message("Document ends before reaching end of file"))
180
- end
108
+ char = @source.peek
109
+ if char
110
+ throw_custom(error_message("Document ends before reaching end of file"))
181
111
  end
182
- return parser
112
+ char = @source.read
113
+ return true
183
114
  end
184
115
 
185
- # Returns a parser which always fails.
186
- # That is, it always returns an error result.
116
+ # Parses nothing; thus an error always occur.
187
117
  def parse_none
188
- parser = Parser.new(self) do
189
- next Result.error(error_message("This cannot happen"))
118
+ throw_custom(error_message("This cannot happen"))
119
+ return nil
120
+ end
121
+
122
+ # Simply executes the specified parser, but additionally performs backtracking on error.
123
+ # If an error occurs in executing the parser, this method rewinds the state of the input to that before executing, and then raises an error.
124
+ # Otherwise, a result obtained by the parser is returned.
125
+ def try(parser)
126
+ mark = @source.mark
127
+ value = nil
128
+ message = catch_custom do
129
+ value = parser.call
190
130
  end
191
- return parser
131
+ unless value
132
+ @source.reset(mark)
133
+ throw_custom(message)
134
+ end
135
+ return value
192
136
  end
193
137
 
194
- end
195
-
196
-
197
- class Result
198
-
199
- attr_reader :value
200
- attr_reader :message
201
-
202
- def initialize(value, message)
203
- @value = value
204
- @message = message
138
+ # First this method executes the first specified parser.
139
+ # If it fails without consuming any input, then this method tries the next specified parser and repeats this procedure.
140
+ def choose(*parsers)
141
+ value, message = nil, ""
142
+ parsers.each do |parser|
143
+ mark = @source.mark
144
+ message = catch_custom do
145
+ value = parser.call
146
+ end
147
+ if value
148
+ break
149
+ elsif mark != @source.mark
150
+ break
151
+ end
152
+ end
153
+ unless value
154
+ throw_custom(message)
155
+ end
156
+ return value
205
157
  end
206
-
207
- def self.success(value)
208
- return Result.new(value, nil)
158
+
159
+ def many(parser, range = 0..)
160
+ values, message, count = [], "", 0
161
+ lower_limit, upper_limit = range.begin, range.end
162
+ if upper_limit && range.exclude_end?
163
+ upper_limit -= 1
164
+ end
165
+ loop do
166
+ mark = @source.mark
167
+ value = nil
168
+ message = catch_custom do
169
+ value = parser.call
170
+ end
171
+ if value
172
+ values << value
173
+ count += 1
174
+ if upper_limit && count >= upper_limit
175
+ break
176
+ end
177
+ else
178
+ if mark != @source.mark
179
+ throw_custom(message)
180
+ end
181
+ break
182
+ end
183
+ end
184
+ unless count >= lower_limit
185
+ throw_custom(message)
186
+ end
187
+ return values
209
188
  end
210
189
 
211
- def self.error(message)
212
- return Result.new(nil, message)
190
+ def maybe(parser)
191
+ value = many(parser, 0..1).first
192
+ return value
213
193
  end
214
194
 
215
- def value=(value)
216
- if self.success?
217
- @value = value
218
- end
195
+ # Catch a parse error.
196
+ # Do not use the standard exception mechanism during parsing.
197
+ def catch_custom(&block)
198
+ catch(ERROR_TAG, &block)
219
199
  end
220
200
 
221
- def success?
222
- return !@message
201
+ # Raises a parse error.
202
+ # Do not use the standard exception mechanism during parsing, and always use this method to avoid creating an unnecessary stacktrace.
203
+ def throw_custom(message)
204
+ throw(ERROR_TAG, message)
223
205
  end
224
206
 
225
- def error?
226
- return !!@message
207
+ def error_message(message)
208
+ return "[line #{@source.lineno}, column #{@source.columnno}] #{message}"
227
209
  end
228
210
 
229
211
  end
@@ -4,18 +4,22 @@
4
4
  class StringReader
5
5
 
6
6
  attr_reader :lineno
7
+ attr_reader :columnno
7
8
 
8
9
  def initialize(string)
9
10
  @string = string.chars
10
11
  @pos = -1
11
12
  @lineno = 1
13
+ @columnno = 1
12
14
  end
13
15
 
14
16
  def read
15
17
  @pos += 1
18
+ @columnno += 1
16
19
  char = @string[@pos]
17
20
  if char == "\n"
18
21
  @lineno += 1
22
+ @columnno = 1
19
23
  end
20
24
  return char
21
25
  end
@@ -25,23 +29,14 @@ class StringReader
25
29
  return char
26
30
  end
27
31
 
28
- def unread(size = 1)
29
- size.times do
30
- char = @string[@pos]
31
- @pos -= 1
32
- if char == "\n"
33
- @lineno -= 1
34
- end
35
- end
36
- end
37
-
38
32
  def mark
39
- return [@pos, @lineno]
33
+ return [@pos, @lineno, @columnno]
40
34
  end
41
35
 
42
36
  def reset(mark)
43
37
  @pos = mark[0]
44
38
  @lineno = mark[1]
39
+ @columnno = mark[2]
45
40
  end
46
41
 
47
42
  end
data/source/zenml.rb CHANGED
@@ -6,7 +6,7 @@ require 'zenml/utility'
6
6
 
7
7
  module Zenithal
8
8
 
9
- VERSION = "1.2.1"
9
+ VERSION = "1.3.0"
10
10
 
11
11
  require 'zenml/error'
12
12
  require 'zenml/reader'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zenml
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ziphil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-28 00:00:00.000000000 Z
11
+ date: 2020-02-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  This gem serves a tool for parsing a document written in ZenML, an alternative new syntax for XML, to an XML node tree.