zenml 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,223 @@
1
+ # coding: utf-8
2
+
3
+
4
+ class Parser
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 !
33
+ result = self.exec
34
+ if result.success?
35
+ return result.value
36
+ else
37
+ throw(:error, result.message)
38
+ end
39
+ end
40
+
41
+ def |(other)
42
+ this = self
43
+ if this.builder.equal?(other.builder)
44
+ parser = Parser.new(this.builder) do
45
+ mark = source.mark
46
+ result = this.exec
47
+ if result.success?
48
+ next result
49
+ else
50
+ source.reset(mark)
51
+ result = other.exec
52
+ next result
53
+ end
54
+ end
55
+ return parser
56
+ else
57
+ raise StandardError.new("Different source")
58
+ end
59
+ end
60
+
61
+ def many(lower_limit = 0, upper_limit = nil)
62
+ this = self
63
+ parser = Parser.new(this.builder) do
64
+ values, count = [], 0
65
+ loop do
66
+ mark = source.mark
67
+ each_result = this.exec
68
+ if each_result.success?
69
+ values << each_result.value
70
+ count += 1
71
+ if upper_limit && count >= upper_limit
72
+ break
73
+ end
74
+ else
75
+ source.reset(mark)
76
+ break
77
+ end
78
+ end
79
+ if count >= lower_limit
80
+ next Result.success(values)
81
+ else
82
+ next Result.error("")
83
+ end
84
+ end
85
+ return parser
86
+ end
87
+
88
+ def maybe
89
+ return self.many(0, 1).map{|s| s.first}
90
+ end
91
+
92
+ def map(&block)
93
+ this = self
94
+ parser = Parser.new(this.builder) do
95
+ result = this.exec
96
+ if result.success?
97
+ next Result.success(block.call(result.value))
98
+ else
99
+ next result
100
+ end
101
+ end
102
+ return parser
103
+ end
104
+
105
+ end
106
+
107
+
108
+ module CommonParserMethod
109
+
110
+ private
111
+
112
+ # Parses a single character which matches the specified query.
113
+ # If the next character does not match the query or the end of file is reached, an error result is returned.
114
+ # Otherwise, a success result with a string containing the matched chracter is returned.
115
+ def parse_char(query)
116
+ parser = Parser.new(self) do
117
+ char = source.read
118
+ unless char == nil
119
+ predicate, message = false, nil
120
+ case query
121
+ when String
122
+ predicate = query == char
123
+ message = "Expected '#{query}'"
124
+ when Regexp
125
+ predicate = query =~ char
126
+ message = "Expected /#{query}/"
127
+ when Integer
128
+ predicate = query == char.ord
129
+ message = "Expected '#{query.chr}'"
130
+ when Range
131
+ predicate = query.cover?(char.ord)
132
+ message = "Expected '#{query.begin}'..'#{query.end}'"
133
+ end
134
+ if predicate
135
+ next Result.success(char)
136
+ else
137
+ next Result.error(error_message(message))
138
+ end
139
+ else
140
+ next Result.error(error_message("Unexpected end of file"))
141
+ end
142
+ end
143
+ return parser
144
+ end
145
+
146
+ # Parses a single character which matches any of the specified queries.
147
+ def parse_char_any(queries)
148
+ return queries.map{|s| parse_char(s)}.inject(:|)
149
+ end
150
+
151
+ # Parses a single character other than the specified characters.
152
+ # If the next character coincides with any of the elements of the arguments, an error result is returned.
153
+ # Otherwise, a success result with a string containing the next chracter is returned.
154
+ def parse_char_out(chars)
155
+ parser = Parser.new(self) do
156
+ char = source.read
157
+ if char && chars.all?{|s| s != char}
158
+ next Result.success(char)
159
+ else
160
+ message = "Expected other than " + chars.map{|s| "'#{s}'"}.join(", ")
161
+ next Result.error(error_message(message))
162
+ end
163
+ end
164
+ return parser
165
+ end
166
+
167
+ def parse_eof
168
+ parser = Parser.new(self) do
169
+ char = source.read
170
+ if char == nil
171
+ next Result.success(true)
172
+ else
173
+ next Result.error(error_message("Document ends before reaching end of file"))
174
+ end
175
+ end
176
+ return parser
177
+ end
178
+
179
+ # Returns a parser which always fails.
180
+ # That is, it always returns an error result.
181
+ def parse_none
182
+ parser = Parser.new(self) do
183
+ next Result.error(error_message("This cannot happen"))
184
+ end
185
+ return parser
186
+ end
187
+
188
+ end
189
+
190
+
191
+ class Result
192
+
193
+ attr_reader :value
194
+ attr_reader :message
195
+
196
+ def initialize(value, message)
197
+ @value = value
198
+ @message = message
199
+ end
200
+
201
+ def self.success(value)
202
+ return Result.new(value, nil)
203
+ end
204
+
205
+ def self.error(message)
206
+ return Result.new(nil, message)
207
+ end
208
+
209
+ def value=(value)
210
+ if self.success?
211
+ @value = value
212
+ end
213
+ end
214
+
215
+ def success?
216
+ return !@message
217
+ end
218
+
219
+ def error?
220
+ return !!@message
221
+ end
222
+
223
+ end
@@ -35,4 +35,13 @@ class StringReader
35
35
  end
36
36
  end
37
37
 
38
+ def mark
39
+ return [@pos, @lineno]
40
+ end
41
+
42
+ def reset(mark)
43
+ @pos = mark[0]
44
+ @lineno = mark[1]
45
+ end
46
+
38
47
  end
@@ -41,6 +41,19 @@ class Element
41
41
  end
42
42
  end
43
43
 
44
+ def get_texts_recursive
45
+ texts = []
46
+ self.children.each do |child|
47
+ case child
48
+ when Text
49
+ texts << child
50
+ when Element
51
+ texts.concat(child.get_texts_recursive)
52
+ end
53
+ end
54
+ return texts
55
+ end
56
+
44
57
  def self.build(name, &block)
45
58
  element = Element.new(name)
46
59
  block.call(element)
@@ -85,6 +98,33 @@ class Nodes < Array
85
98
  return Nodes.new(super(other))
86
99
  end
87
100
 
101
+ def trim_indents
102
+ texts = []
103
+ if self.last.is_a?(Text)
104
+ self.last.value = self.last.value.rstrip
105
+ end
106
+ self.each do |child|
107
+ case child
108
+ when Text
109
+ texts << child
110
+ when Element
111
+ texts.concat(child.get_texts_recursive)
112
+ end
113
+ end
114
+ indent_length = Float::INFINITY
115
+ texts.each do |text|
116
+ text.value.scan(/\n(\x20+)/) do |match|
117
+ indent_length = [match[0].length, indent_length].min
118
+ end
119
+ end
120
+ texts.each do |text|
121
+ text.value = text.value.gsub(/\n(\x20+)/){"\n" + " " * ($1.length - indent_length)}
122
+ end
123
+ if self.first.is_a?(Text)
124
+ self.first.value = self.first.value.lstrip
125
+ end
126
+ end
127
+
88
128
  end
89
129
 
90
130
 
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.0.1
4
+ version: 1.1.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-07-19 00:00:00.000000000 Z
11
+ date: 2019-09-28 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.
@@ -22,7 +22,9 @@ files:
22
22
  - source/zenml.rb
23
23
  - source/zenml/converter.rb
24
24
  - source/zenml/error.rb
25
+ - source/zenml/old_parser.rb
25
26
  - source/zenml/parser.rb
27
+ - source/zenml/parser_utility.rb
26
28
  - source/zenml/reader.rb
27
29
  - source/zenml/utility.rb
28
30
  homepage: https://github.com/Ziphil/Zenithal