zenml 1.0.1 → 1.1.0

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.
@@ -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