sxp 0.0.1
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.
- data/AUTHORS +1 -0
- data/README +93 -0
- data/Rakefile +8 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/bin/sxp2json +5 -0
- data/bin/sxp2rdf +5 -0
- data/bin/sxp2xml +5 -0
- data/bin/sxp2yaml +5 -0
- data/lib/sxp/extensions.rb +7 -0
- data/lib/sxp/generator.rb +62 -0
- data/lib/sxp/list.rb +327 -0
- data/lib/sxp/pair.rb +39 -0
- data/lib/sxp/reader.rb +210 -0
- data/lib/sxp/version.rb +14 -0
- data/lib/sxp/writer.rb +57 -0
- data/lib/sxp.rb +7 -0
- metadata +79 -0
data/AUTHORS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* Arto Bendiken <arto.bendiken@gmail.com> (Lead developer)
|
data/README
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
SXP.rb: SXP for Ruby
|
|
2
|
+
====================
|
|
3
|
+
|
|
4
|
+
This is the Ruby reference implementation of the SXP data interchange
|
|
5
|
+
format.
|
|
6
|
+
|
|
7
|
+
* <http://sxp.rubyforge.org/>
|
|
8
|
+
* <http://github.com/bendiken/sxp-ruby>
|
|
9
|
+
|
|
10
|
+
### About SXP
|
|
11
|
+
|
|
12
|
+
SXP is a data interchange format based on S-expressions, the simplest and
|
|
13
|
+
most versatile known means of representing complex data structures such as
|
|
14
|
+
lists, trees and graphs.
|
|
15
|
+
|
|
16
|
+
* <http://sxp.cc/>
|
|
17
|
+
* <http://en.wikipedia.org/wiki/S-expression>
|
|
18
|
+
|
|
19
|
+
Features
|
|
20
|
+
--------
|
|
21
|
+
|
|
22
|
+
* Parses S-expressions in SXP format.
|
|
23
|
+
* Adds a `#to_sxp` method to Ruby objects.
|
|
24
|
+
|
|
25
|
+
Examples
|
|
26
|
+
--------
|
|
27
|
+
|
|
28
|
+
require 'sxp'
|
|
29
|
+
|
|
30
|
+
### Parsing S-expressions
|
|
31
|
+
|
|
32
|
+
SXP.read "(+ 1 2)"
|
|
33
|
+
|
|
34
|
+
=> [:+, 1, 2]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
SXP.read <<-EOF
|
|
38
|
+
(define (fact n)
|
|
39
|
+
(if (= n 0)
|
|
40
|
+
1
|
|
41
|
+
(* n (fact (- n 1)))))
|
|
42
|
+
EOF
|
|
43
|
+
|
|
44
|
+
=> [:define, [:fact, :n],
|
|
45
|
+
[:if, [:"=", :n, 0],
|
|
46
|
+
1,
|
|
47
|
+
[:*, :n, [:fact, [:-, :n, 1]]]]]
|
|
48
|
+
|
|
49
|
+
Documentation
|
|
50
|
+
-------------
|
|
51
|
+
|
|
52
|
+
* <http://sxp.rubyforge.org/>
|
|
53
|
+
|
|
54
|
+
Download
|
|
55
|
+
--------
|
|
56
|
+
|
|
57
|
+
To get a local working copy of the development repository, do:
|
|
58
|
+
|
|
59
|
+
% git clone git://github.com/bendiken/sxp-ruby.git
|
|
60
|
+
|
|
61
|
+
Alternatively, you can download the latest development version as a tarball
|
|
62
|
+
as follows:
|
|
63
|
+
|
|
64
|
+
% wget http://github.com/bendiken/sxp-ruby/tarball/master
|
|
65
|
+
|
|
66
|
+
Installation
|
|
67
|
+
------------
|
|
68
|
+
|
|
69
|
+
The recommended installation method is via RubyGems. To install the latest
|
|
70
|
+
official release from Gemcutter, do:
|
|
71
|
+
|
|
72
|
+
% [sudo] gem install sxp
|
|
73
|
+
|
|
74
|
+
Resources
|
|
75
|
+
---------
|
|
76
|
+
|
|
77
|
+
* <http://sxp.rubyforge.org/>
|
|
78
|
+
* <http://github.com/bendiken/sxp>
|
|
79
|
+
* <http://github.com/bendiken/sxp-ruby>
|
|
80
|
+
* <http://gemcutter.org/gems/sxp>
|
|
81
|
+
* <http://rubyforge.org/projects/sxp/>
|
|
82
|
+
* <http://raa.ruby-lang.org/project/sxp>
|
|
83
|
+
|
|
84
|
+
Author
|
|
85
|
+
------
|
|
86
|
+
|
|
87
|
+
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
|
|
88
|
+
|
|
89
|
+
License
|
|
90
|
+
-------
|
|
91
|
+
|
|
92
|
+
SXP.rb is free and unencumbered public domain software. For more
|
|
93
|
+
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
data/Rakefile
ADDED
data/UNLICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.1
|
data/bin/sxp2json
ADDED
data/bin/sxp2rdf
ADDED
data/bin/sxp2xml
ADDED
data/bin/sxp2yaml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module SXP
|
|
2
|
+
class Generator
|
|
3
|
+
def self.string(*sxps)
|
|
4
|
+
require 'stringio' unless defined?(StringIO)
|
|
5
|
+
write(StringIO.new, *sxps).instance_variable_get('@buffer').string
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.print(*sxps)
|
|
9
|
+
write($stdout, *sxps)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.write(out, *sxps)
|
|
13
|
+
generator = self.new(out)
|
|
14
|
+
sxps.each do |sxp|
|
|
15
|
+
generator.send((op = sxp.shift).to_sym, *sxp)
|
|
16
|
+
end
|
|
17
|
+
generator
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(buffer)
|
|
21
|
+
@output = [@buffer = buffer]
|
|
22
|
+
@indent = 0
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def emit(text, options = {})
|
|
28
|
+
if out = @output.last
|
|
29
|
+
out.print(' ' * (indent * 2)) if options[:indent]
|
|
30
|
+
out.print(text)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def captured(&block)
|
|
35
|
+
require 'stringio' unless defined?(StringIO)
|
|
36
|
+
begin
|
|
37
|
+
@output.push(buffer = StringIO.new)
|
|
38
|
+
block.call
|
|
39
|
+
ensure
|
|
40
|
+
@output.pop
|
|
41
|
+
end
|
|
42
|
+
buffer.string
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def indented(&block)
|
|
46
|
+
begin
|
|
47
|
+
increase_indent!
|
|
48
|
+
block.call
|
|
49
|
+
ensure
|
|
50
|
+
decrease_indent!
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def increase_indent!()
|
|
55
|
+
@indent += 1
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def decrease_indent!()
|
|
59
|
+
@indent -= 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/sxp/list.rb
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
require 'sxp/pair'
|
|
2
|
+
|
|
3
|
+
module SXP
|
|
4
|
+
class List < Pair
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def self.[](*elements)
|
|
8
|
+
self.new(elements)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(elements = [], &block)
|
|
12
|
+
@pair = nil
|
|
13
|
+
unshift(*elements) unless elements.empty?
|
|
14
|
+
block.call(self) if block_given?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def inspect
|
|
18
|
+
"(" << map { |value| value.inspect }.join(' ') << ")"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def head() first end
|
|
22
|
+
def tail() rest end
|
|
23
|
+
|
|
24
|
+
def rest
|
|
25
|
+
empty? ? false : @pair.tail
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Array interface
|
|
29
|
+
|
|
30
|
+
def &(other)
|
|
31
|
+
self.class.new(self.to_a & other.to_a)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def |(other)
|
|
35
|
+
self.class.new(self.to_a | other.to_a)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def *(times)
|
|
39
|
+
result = (self.to_a * times)
|
|
40
|
+
result.is_a?(Array) ? self.class.new(result) : result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def +(other)
|
|
44
|
+
self.class.new(self.to_a + other.to_a)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def -(other)
|
|
48
|
+
self.class.new(self.to_a - other.to_a)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def <<(object)
|
|
52
|
+
push(object)
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def <=>(other)
|
|
57
|
+
to_a <=> other.to_a
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def ==(other)
|
|
61
|
+
case other
|
|
62
|
+
when List
|
|
63
|
+
self.length == other.length && to_a == other.to_a
|
|
64
|
+
when other.respond_to?(:to_list)
|
|
65
|
+
other.to_list == self
|
|
66
|
+
else
|
|
67
|
+
false
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def [](*args)
|
|
72
|
+
result = to_a[*args]
|
|
73
|
+
result.is_a?(Array) ? self.class.new(result) : result # FIXME
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def []=(*args)
|
|
77
|
+
raise NotImplementedError # TODO
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def assoc(object)
|
|
81
|
+
raise NotImplementedError # TODO
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def at(index)
|
|
85
|
+
to_a.at(index)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def clear
|
|
89
|
+
@pair = nil
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def collect!(&block)
|
|
94
|
+
raise NotImplementedError # TODO
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def compact
|
|
98
|
+
self.class.new(to_a.compact)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def compact!
|
|
102
|
+
raise NotImplementedError # TODO
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def concat(other)
|
|
106
|
+
raise NotImplementedError # TODO
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def delete(object, &block)
|
|
110
|
+
raise NotImplementedError # TODO
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def delete_at(index)
|
|
114
|
+
raise NotImplementedError # TODO
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def delete_if(&block)
|
|
118
|
+
raise NotImplementedError # TODO
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def each(&block)
|
|
122
|
+
pair = @pair
|
|
123
|
+
while pair != nil
|
|
124
|
+
block.call(pair.head)
|
|
125
|
+
pair = pair.tail
|
|
126
|
+
end
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def each_index(&block)
|
|
131
|
+
index = 0
|
|
132
|
+
each do
|
|
133
|
+
block.call(index)
|
|
134
|
+
index += 1
|
|
135
|
+
end
|
|
136
|
+
self
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def empty?
|
|
140
|
+
@pair.nil?
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def eql?(other)
|
|
144
|
+
case other
|
|
145
|
+
when self then true
|
|
146
|
+
when List
|
|
147
|
+
self.length == other.length && to_a.eql?(other.to_a)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def fetch(*args, &block)
|
|
152
|
+
to_a.fetch(*args, &block)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def fill(*args, &block)
|
|
156
|
+
raise NotImplementedError # TODO
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def first(count = nil)
|
|
160
|
+
case
|
|
161
|
+
when count.nil?
|
|
162
|
+
@pair.head unless empty?
|
|
163
|
+
when count == 1
|
|
164
|
+
empty? ? [] : [first]
|
|
165
|
+
when count > 1
|
|
166
|
+
empty? ? [] : to_a.first(count)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def flatten
|
|
171
|
+
raise NotImplementedError # TODO
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def flatten!
|
|
175
|
+
raise NotImplementedError # TODO
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def include?(object)
|
|
179
|
+
to_a.include?(object)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def index(object)
|
|
183
|
+
to_a.index(object)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def insert(index, *objects)
|
|
187
|
+
raise NotImplementedError # TODO
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def join(separator = $,)
|
|
191
|
+
to_a.join(separator)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def last(count = nil)
|
|
195
|
+
case
|
|
196
|
+
when count.nil?
|
|
197
|
+
to_a.last
|
|
198
|
+
else
|
|
199
|
+
to_a.last(count)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def length
|
|
204
|
+
@length ||= to_a.length
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def map!(&block)
|
|
208
|
+
collect!(&block)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def nitems
|
|
212
|
+
to_a.nitems
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def pack(template)
|
|
216
|
+
to_a.pack(template)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def pop
|
|
220
|
+
raise NotImplementedError # TODO
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def push(*objects)
|
|
224
|
+
raise NotImplementedError # TODO
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def rassoc(key)
|
|
228
|
+
raise NotImplementedError # TODO
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def reject!(&block)
|
|
232
|
+
raise NotImplementedError # TODO
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def replace(other_list)
|
|
236
|
+
case other_list
|
|
237
|
+
when Pair
|
|
238
|
+
@pair = other_list
|
|
239
|
+
when List
|
|
240
|
+
@pair = other_list.to_pair
|
|
241
|
+
when Array
|
|
242
|
+
@pair = nil
|
|
243
|
+
unshift(*other_list)
|
|
244
|
+
else
|
|
245
|
+
# TODO
|
|
246
|
+
end
|
|
247
|
+
self
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def reverse
|
|
251
|
+
self.class.new(to_a.reverse)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def reverse!
|
|
255
|
+
raise NotImplementedError # TODO
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def reverse_each(&block)
|
|
259
|
+
to_a.reverse_each(&block)
|
|
260
|
+
self
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def rindex(object)
|
|
264
|
+
to_a.rindex(object)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def shift
|
|
268
|
+
raise NotImplementedError # TODO
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def size
|
|
272
|
+
length
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def slice(*args)
|
|
276
|
+
self[*args]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def slice!(*args)
|
|
280
|
+
raise NotImplementedError # TODO
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def sort(&block)
|
|
284
|
+
(array = to_a).sort!(&block)
|
|
285
|
+
self.class.new(array)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def sort!
|
|
289
|
+
raise NotImplementedError # TODO
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def to_list
|
|
293
|
+
self
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def to_pair
|
|
297
|
+
@pair
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def to_s
|
|
301
|
+
join
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def transpose
|
|
305
|
+
self.class.new(to_a.transpose)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def uniq
|
|
309
|
+
self.class.new(to_a.uniq)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def uniq!
|
|
313
|
+
raise NotImplementedError # TODO
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def unshift(*objects)
|
|
317
|
+
objects.reverse_each do |object|
|
|
318
|
+
@pair = Pair.new(object, @pair)
|
|
319
|
+
end
|
|
320
|
+
self
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def values_at(*selector)
|
|
324
|
+
self.class.new(to_a.values_at(*selector))
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
data/lib/sxp/pair.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module SXP
|
|
2
|
+
class Pair
|
|
3
|
+
attr_accessor :head
|
|
4
|
+
attr_accessor :tail
|
|
5
|
+
|
|
6
|
+
def initialize(head = nil, tail = nil)
|
|
7
|
+
@head, @tail = head, tail
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def inspect
|
|
11
|
+
case
|
|
12
|
+
when tail.nil?
|
|
13
|
+
"(#{head.inspect})"
|
|
14
|
+
else
|
|
15
|
+
"(#{head.inspect} . #{tail.inspect})"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def empty?
|
|
20
|
+
head.nil? && tail.nil?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# @see http://srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
|
|
25
|
+
def dotted?
|
|
26
|
+
!proper?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# @see http://srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
|
|
31
|
+
def proper?
|
|
32
|
+
tail.nil? || tail.is_a?(Pair)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_a
|
|
36
|
+
[head, tail]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/sxp/reader.rb
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
module SXP
|
|
2
|
+
|
|
3
|
+
# Reads one S-expression from the given input stream.
|
|
4
|
+
def self.read(input)
|
|
5
|
+
Reader.new(input).read
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Reads all S-expressions from the given input stream.
|
|
9
|
+
def self.read_all(input)
|
|
10
|
+
Reader.new(input).read_all
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Reads all S-expressions from the given input files.
|
|
14
|
+
def self.read_files(*filenames)
|
|
15
|
+
filenames.map { |filename| read_file(filename) }.inject { |sxps, sxp| sxps + sxp }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Reads all S-expressions from a given input file.
|
|
19
|
+
def self.read_file(filename)
|
|
20
|
+
File.open(filename, 'rb') { |io| read_all(io) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Reads all S-expressions from a given input URI using the HTTP or FTP protocols.
|
|
24
|
+
def self.read_uri(uri, options = {})
|
|
25
|
+
require 'openuri'
|
|
26
|
+
open(uri, 'rb', nil, options) { |io| read_all(io) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
alias_method :parse, :read
|
|
31
|
+
alias_method :parse_all, :read_all
|
|
32
|
+
alias_method :parse_files, :read_files
|
|
33
|
+
alias_method :parse_file, :read_file
|
|
34
|
+
alias_method :parse_uri, :read_uri
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Reader
|
|
38
|
+
include Enumerable
|
|
39
|
+
|
|
40
|
+
class Error < StandardError; end
|
|
41
|
+
class EOF < Error; end
|
|
42
|
+
|
|
43
|
+
FLOAT = /^[+-]?(?:\d+)?\.\d*$/
|
|
44
|
+
INTEGER_BASE_2 = /^[+-]?[01]+$/
|
|
45
|
+
INTEGER_BASE_8 = /^[+-]?[0-7]+$/
|
|
46
|
+
INTEGER_BASE_10 = /^[+-]?\d+$/
|
|
47
|
+
INTEGER_BASE_16 = /^[+-]?[\da-z]+$/i
|
|
48
|
+
RATIONAL = /^([+-]?\d+)\/(\d+)$/
|
|
49
|
+
ATOM = /^[^\s()]+/
|
|
50
|
+
|
|
51
|
+
attr_reader :input
|
|
52
|
+
|
|
53
|
+
def initialize(input)
|
|
54
|
+
case
|
|
55
|
+
when [:getc, :ungetc, :eof?].all? { |x| input.respond_to?(x) }
|
|
56
|
+
@input = input
|
|
57
|
+
when input.respond_to?(:to_str)
|
|
58
|
+
require 'stringio'
|
|
59
|
+
@input = StringIO.new(input.to_str)
|
|
60
|
+
else
|
|
61
|
+
raise ArgumentError, "expected an IO or String input stream: #{input.inspect}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def each(&block)
|
|
66
|
+
block.call(read)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def read_all(options = {})
|
|
70
|
+
list = []
|
|
71
|
+
catch (:eof) { list << read(:eof => :throw, *options) until eof? }
|
|
72
|
+
list
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def read(options = {})
|
|
76
|
+
token, value = read_token
|
|
77
|
+
case token
|
|
78
|
+
when :eof
|
|
79
|
+
throw :eof if options[:eof] == :throw
|
|
80
|
+
raise EOF, 'unexpected end of input'
|
|
81
|
+
when :list
|
|
82
|
+
if value == ?(
|
|
83
|
+
read_list
|
|
84
|
+
else
|
|
85
|
+
throw :eol if options[:eol] == :throw
|
|
86
|
+
raise Error, 'unexpected list terminator: ?)'
|
|
87
|
+
end
|
|
88
|
+
else value
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
alias skip read
|
|
93
|
+
|
|
94
|
+
def read_token
|
|
95
|
+
skip_comments
|
|
96
|
+
case peek_char
|
|
97
|
+
when nil then :eof
|
|
98
|
+
when ?(, ?) then [:list, read_char]
|
|
99
|
+
when ?# then [:atom, read_sharp]
|
|
100
|
+
when ?" then [:atom, read_string]
|
|
101
|
+
else [:atom, read_atom]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def read_list
|
|
106
|
+
list = []
|
|
107
|
+
catch (:eol) { list << read(:eol => :throw) while true }
|
|
108
|
+
list
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def read_sharp
|
|
112
|
+
skip_char # '#'
|
|
113
|
+
case char = read_char
|
|
114
|
+
when ?n then nil
|
|
115
|
+
when ?f then false
|
|
116
|
+
when ?t then true
|
|
117
|
+
when ?b then read_integer(2)
|
|
118
|
+
when ?o then read_integer(8)
|
|
119
|
+
when ?d then read_integer(10)
|
|
120
|
+
when ?x then read_integer(16)
|
|
121
|
+
when ?\\ then read_character
|
|
122
|
+
when ?; then skip; read
|
|
123
|
+
else raise Error, "invalid sharp-sign read syntax: ##{char.chr}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def read_integer(base = 10)
|
|
128
|
+
case buffer = read_literal
|
|
129
|
+
when self.class.const_get(:"INTEGER_BASE_#{base}")
|
|
130
|
+
buffer.to_i(base)
|
|
131
|
+
else raise Error, "illegal base-#{base} number syntax: #{buffer}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def read_atom
|
|
136
|
+
case buffer = read_literal
|
|
137
|
+
when FLOAT then buffer.to_f
|
|
138
|
+
when INTEGER_BASE_10 then buffer.to_i
|
|
139
|
+
when RATIONAL then Rational($1.to_i, $2.to_i)
|
|
140
|
+
else buffer.to_sym
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def read_string
|
|
145
|
+
buffer = String.new
|
|
146
|
+
skip_char # '"'
|
|
147
|
+
until peek_char == ?"
|
|
148
|
+
buffer <<
|
|
149
|
+
case char = read_char
|
|
150
|
+
when ?\\ then read_character
|
|
151
|
+
else char
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
skip_char # '"'
|
|
155
|
+
buffer
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def read_character
|
|
159
|
+
case char = read_char
|
|
160
|
+
when ?b then ?\b
|
|
161
|
+
when ?f then ?\f
|
|
162
|
+
when ?n then ?\n
|
|
163
|
+
when ?r then ?\r
|
|
164
|
+
when ?t then ?\t
|
|
165
|
+
when ?u then read_chars(4).to_i(16).chr
|
|
166
|
+
when ?U then read_chars(8).to_i(16).chr
|
|
167
|
+
else char
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def read_literal
|
|
172
|
+
buffer = String.new
|
|
173
|
+
buffer << read_char while !eof? && peek_char.chr =~ ATOM
|
|
174
|
+
buffer
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def skip_comments
|
|
178
|
+
until eof?
|
|
179
|
+
case (char = peek_char).chr
|
|
180
|
+
when /;/ then loop { break if eof? || read_char.chr == $/ }
|
|
181
|
+
when /\s+/ then skip_char
|
|
182
|
+
else break
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def read_chars(count = 1)
|
|
188
|
+
buffer = ''
|
|
189
|
+
count.times { buffer << read_char.chr }
|
|
190
|
+
buffer
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def read_char
|
|
194
|
+
char = @input.getc
|
|
195
|
+
raise EOF, 'unexpected end of input' if char.nil?
|
|
196
|
+
char
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
alias skip_char read_char
|
|
200
|
+
|
|
201
|
+
def peek_char
|
|
202
|
+
char = @input.getc
|
|
203
|
+
@input.ungetc char unless char.nil?
|
|
204
|
+
char
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def eof?() @input.eof? end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
end
|
data/lib/sxp/version.rb
ADDED
data/lib/sxp/writer.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
def to_sxp
|
|
3
|
+
to_s.to_json
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class NilClass
|
|
8
|
+
def to_sxp; '#n'; end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class FalseClass
|
|
12
|
+
def to_sxp; '#f'; end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class TrueClass
|
|
16
|
+
def to_sxp; '#t'; end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class String
|
|
20
|
+
def to_sxp; inspect; end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
class Symbol
|
|
24
|
+
def to_sxp; to_s; end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class Integer
|
|
28
|
+
def to_sxp; to_s; end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Float
|
|
32
|
+
def to_sxp
|
|
33
|
+
case
|
|
34
|
+
when nan? then 'nan.'
|
|
35
|
+
when infinite? then (infinite? > 0 ? '+inf.' : '-inf.')
|
|
36
|
+
else to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class Array
|
|
42
|
+
def to_sxp
|
|
43
|
+
'(' << map { |x| x.to_sxp }.join(' ') << ')'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Time
|
|
48
|
+
def to_sxp
|
|
49
|
+
'#@' << (respond_to?(:xmlschema) ? xmlschema : to_i).to_s
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class Regexp
|
|
54
|
+
def to_sxp
|
|
55
|
+
'#' << inspect
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/sxp.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sxp
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Arto Bendiken
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-12-21 00:00:00 +01:00
|
|
13
|
+
default_executable: sxp2json
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: rspec
|
|
17
|
+
type: :development
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: 1.2.9
|
|
24
|
+
version:
|
|
25
|
+
description: " SXP is a data interchange format based on S-expressions, the simplest and\n most versatile known means of representing complex data structures such as\n lists, trees and graphs.\n"
|
|
26
|
+
email: arto.bendiken@gmail.com
|
|
27
|
+
executables:
|
|
28
|
+
- sxp2json
|
|
29
|
+
- sxp2rdf
|
|
30
|
+
- sxp2xml
|
|
31
|
+
- sxp2yaml
|
|
32
|
+
extensions: []
|
|
33
|
+
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
|
|
36
|
+
files:
|
|
37
|
+
- AUTHORS
|
|
38
|
+
- README
|
|
39
|
+
- Rakefile
|
|
40
|
+
- UNLICENSE
|
|
41
|
+
- VERSION
|
|
42
|
+
- lib/sxp/extensions.rb
|
|
43
|
+
- lib/sxp/generator.rb
|
|
44
|
+
- lib/sxp/list.rb
|
|
45
|
+
- lib/sxp/pair.rb
|
|
46
|
+
- lib/sxp/reader.rb
|
|
47
|
+
- lib/sxp/version.rb
|
|
48
|
+
- lib/sxp/writer.rb
|
|
49
|
+
- lib/sxp.rb
|
|
50
|
+
has_rdoc: false
|
|
51
|
+
homepage: http://sxp.rubyforge.org/
|
|
52
|
+
licenses:
|
|
53
|
+
- Public Domain
|
|
54
|
+
post_install_message:
|
|
55
|
+
rdoc_options: []
|
|
56
|
+
|
|
57
|
+
require_paths:
|
|
58
|
+
- lib
|
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: 1.8.2
|
|
64
|
+
version:
|
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - ">="
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: "0"
|
|
70
|
+
version:
|
|
71
|
+
requirements: []
|
|
72
|
+
|
|
73
|
+
rubyforge_project: sxp
|
|
74
|
+
rubygems_version: 1.3.5
|
|
75
|
+
signing_key:
|
|
76
|
+
specification_version: 3
|
|
77
|
+
summary: A pure-Ruby implementation of the SXP data interchange format.
|
|
78
|
+
test_files: []
|
|
79
|
+
|