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.
- checksums.yaml +4 -4
- data/source/zenml.rb +2 -1
- data/source/zenml/error.rb +2 -3
- data/source/zenml/old_parser.rb +550 -0
- data/source/zenml/parser.rb +302 -428
- data/source/zenml/parser_utility.rb +223 -0
- data/source/zenml/reader.rb +9 -0
- data/source/zenml/utility.rb +40 -0
- metadata +4 -2
@@ -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
|
data/source/zenml/reader.rb
CHANGED
data/source/zenml/utility.rb
CHANGED
@@ -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
|
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-
|
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
|