sxp 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/CREDITS +0 -1
- data/README +3 -3
- data/VERSION +1 -1
- data/lib/sxp/extensions.rb +13 -9
- data/lib/sxp/generator.rb +33 -11
- data/lib/sxp/reader.rb +29 -6
- data/lib/sxp/reader/common_lisp.rb +14 -1
- data/lib/sxp/reader/scheme.rb +26 -0
- data/lib/sxp/writer.rb +55 -48
- metadata +28 -9
data/AUTHORS
CHANGED
data/CREDITS
CHANGED
data/README
CHANGED
@@ -10,7 +10,7 @@ This is a Ruby implementation of a universal [S-expression][] parser.
|
|
10
10
|
* Parses S-expressions in universal, [Scheme][], [Common Lisp][], or
|
11
11
|
[SPARQL][] syntax.
|
12
12
|
* Adds a `#to_sxp` method to Ruby objects.
|
13
|
-
* Compatible with Ruby 1.8.7+, Ruby 1.9.x, and JRuby 1.4/1.5.
|
13
|
+
* Compatible with Ruby 1.8.7+, Ruby 1.9.x, Ruby 2.0.x, and JRuby 1.4/1.5.
|
14
14
|
|
15
15
|
##Examples
|
16
16
|
|
@@ -75,7 +75,7 @@ Dependencies
|
|
75
75
|
------------
|
76
76
|
|
77
77
|
* [Ruby](http://ruby-lang.org/) (>= 1.8.7) or (>= 1.8.1 with [Backports][])
|
78
|
-
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 0.
|
78
|
+
* [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.0.0), only needed for SPARQL
|
79
79
|
S-expressions
|
80
80
|
|
81
81
|
Installation
|
@@ -112,12 +112,12 @@ Author
|
|
112
112
|
------
|
113
113
|
|
114
114
|
* [Arto Bendiken](https://github.com/bendiken) - <http://ar.to/>
|
115
|
+
* [Gregg Kellogg](http://github.com/gkellogg) - <http://kellogg-assoc.com/>
|
115
116
|
|
116
117
|
Contributors
|
117
118
|
------------
|
118
119
|
|
119
120
|
* [Ben Lavender](https://github.com/bhuga) - <http://bhuga.net/>
|
120
|
-
* [Gregg Kellogg](http://github.com/gkellogg) - <http://kellogg-assoc.com/>
|
121
121
|
|
122
122
|
License
|
123
123
|
-------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.2
|
data/lib/sxp/extensions.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'rdf'
|
2
|
-
|
3
1
|
##
|
4
2
|
# Extensions for Ruby's `Symbol` class.
|
5
3
|
class Symbol
|
@@ -12,11 +10,17 @@ class Symbol
|
|
12
10
|
end
|
13
11
|
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# Original lexical value of this URI to allow for round-trip serialization.
|
19
|
-
def lexical=(value); @lexical = value; end
|
20
|
-
def lexical; @lexical; end
|
21
|
-
end
|
13
|
+
# Update RDF::URI if RDF is loaded
|
14
|
+
begin
|
15
|
+
require 'rdf'
|
22
16
|
|
17
|
+
##
|
18
|
+
# Extensions for RDF::URI
|
19
|
+
class RDF::URI
|
20
|
+
# Original lexical value of this URI to allow for round-trip serialization.
|
21
|
+
def lexical=(value); @lexical = value; end
|
22
|
+
def lexical; @lexical; end
|
23
|
+
end
|
24
|
+
rescue LoadError
|
25
|
+
# Ignore
|
26
|
+
end
|
data/lib/sxp/generator.rb
CHANGED
@@ -8,7 +8,7 @@ module SXP
|
|
8
8
|
# A basic block containing constituent
|
9
9
|
# objects, either blocks or strings.
|
10
10
|
class Block
|
11
|
-
BLOCK_MIN_LENGTH =
|
11
|
+
BLOCK_MIN_LENGTH = 80
|
12
12
|
|
13
13
|
# @attr [Integer] amount of indent applied to this block
|
14
14
|
attr_reader :indent
|
@@ -55,27 +55,30 @@ module SXP
|
|
55
55
|
end
|
56
56
|
|
57
57
|
##
|
58
|
-
#
|
59
|
-
# @
|
60
|
-
def
|
58
|
+
# Format block
|
59
|
+
# @return [String]
|
60
|
+
def formatted
|
61
61
|
# Output individual block elements on separate lines
|
62
|
+
buffer = ""
|
63
|
+
|
62
64
|
if sxp? && length > BLOCK_MIN_LENGTH
|
63
|
-
|
65
|
+
buffer += do_indent + '('
|
64
66
|
first, *elems = @elements
|
65
67
|
unless first.sxp?
|
66
68
|
# It's atomic, write out after paren
|
67
|
-
|
69
|
+
buffer += first.to_sxp + "\n"
|
68
70
|
else
|
69
|
-
|
71
|
+
buffer += "\n"
|
70
72
|
elems.unshift(first)
|
71
73
|
end
|
72
74
|
elems.each do |e|
|
73
|
-
e.
|
75
|
+
buffer += e.formatted
|
74
76
|
end
|
75
|
-
|
77
|
+
buffer += do_indent + ")\n"
|
76
78
|
else
|
77
|
-
|
79
|
+
buffer += do_indent + @elements.to_sxp + "\n"
|
78
80
|
end
|
81
|
+
buffer
|
79
82
|
end
|
80
83
|
|
81
84
|
private
|
@@ -140,7 +143,26 @@ module SXP
|
|
140
143
|
def render(sexp)
|
141
144
|
block = Block.new(sexp, 0)
|
142
145
|
if block.length > 40
|
143
|
-
block.
|
146
|
+
buffer = block.formatted
|
147
|
+
|
148
|
+
# Attempt to fold symbols and strings onto proceeding line
|
149
|
+
output = ""
|
150
|
+
prev_length = 0
|
151
|
+
buffer.lines.each do |line|
|
152
|
+
if (stripped = line.strip)[0,1] != '(' &&
|
153
|
+
prev_length + stripped.length + 1 < Block::BLOCK_MIN_LENGTH
|
154
|
+
|
155
|
+
# Append to previous line
|
156
|
+
start, match, rem = output.rpartition(/\S/)
|
157
|
+
output = start + match + " " + stripped + rem
|
158
|
+
prev_length += stripped.length + 1
|
159
|
+
else
|
160
|
+
# Terminate line and append this line
|
161
|
+
output += line
|
162
|
+
prev_length = line.length - 1
|
163
|
+
end
|
164
|
+
end
|
165
|
+
@output.write output.gsub(/\)\s+\)/, '))')
|
144
166
|
else
|
145
167
|
@output.puts(block.to_sxp)
|
146
168
|
end
|
data/lib/sxp/reader.rb
CHANGED
@@ -19,6 +19,7 @@ module SXP
|
|
19
19
|
#
|
20
20
|
# @param [String, #to_s] url
|
21
21
|
# @param [Hash{Symbol => Object}] options
|
22
|
+
# See {#read}
|
22
23
|
# @return [Enumerable<Object>]
|
23
24
|
def self.read_url(url, options = {})
|
24
25
|
require 'open-uri'
|
@@ -34,6 +35,7 @@ module SXP
|
|
34
35
|
# @overload read_files(*filenames, options)
|
35
36
|
# @param [Enumerable<String>] filenames
|
36
37
|
# @param [Hash{Symbol => Object}] options
|
38
|
+
# See {#read}
|
37
39
|
#
|
38
40
|
# @return [Enumerable<Object>]
|
39
41
|
def read_files(*filenames)
|
@@ -46,6 +48,7 @@ module SXP
|
|
46
48
|
#
|
47
49
|
# @param [String, #to_s] filename
|
48
50
|
# @param [Hash{Symbol => Object}] options
|
51
|
+
# See {#read}
|
49
52
|
# @return [Enumerable<Object>]
|
50
53
|
def self.read_file(filename, options = {})
|
51
54
|
File.open(filename.to_s, 'rb') { |io| read_all(io, options).first }
|
@@ -56,6 +59,7 @@ module SXP
|
|
56
59
|
#
|
57
60
|
# @param [IO, StringIO, String] input
|
58
61
|
# @param [Hash{Symbol => Object}] options
|
62
|
+
# See {#read}
|
59
63
|
# @return [Enumerable<Object>]
|
60
64
|
def self.read_all(input, options = {})
|
61
65
|
self.new(input, options).read_all
|
@@ -66,6 +70,7 @@ module SXP
|
|
66
70
|
#
|
67
71
|
# @param [IO, StringIO, String] input
|
68
72
|
# @param [Hash{Symbol => Object}] options
|
73
|
+
# See {#read}
|
69
74
|
# @return [Object]
|
70
75
|
def self.read(input, options = {})
|
71
76
|
self.new(input, options).read
|
@@ -118,6 +123,7 @@ module SXP
|
|
118
123
|
|
119
124
|
##
|
120
125
|
# @param [Hash{Symbol => Object}] options
|
126
|
+
# See {#read}
|
121
127
|
# @return [Array]
|
122
128
|
def read_all(options = {})
|
123
129
|
list = []
|
@@ -129,6 +135,13 @@ module SXP
|
|
129
135
|
|
130
136
|
##
|
131
137
|
# @param [Hash{Symbol => Object}] options
|
138
|
+
# @option options [:throw] :eof
|
139
|
+
# If `:throw`, throw `:eof` on end of file.
|
140
|
+
# @option options [:throw] :eol
|
141
|
+
# If `:throw`, throw `:eol` on end of line.
|
142
|
+
# @option options [String] :list_term
|
143
|
+
# Expected list terminator; it's an error
|
144
|
+
# if another terminator is found
|
132
145
|
# @return [Object]
|
133
146
|
def read(options = {})
|
134
147
|
skip_comments
|
@@ -138,10 +151,11 @@ module SXP
|
|
138
151
|
throw :eof if options[:eof] == :throw
|
139
152
|
raise EOF, "unexpected end of input"
|
140
153
|
when :list
|
141
|
-
if self.class.const_get(:LPARENS).
|
142
|
-
|
154
|
+
if ndx = self.class.const_get(:LPARENS).index(value)
|
155
|
+
list_term = self.class.const_get(:RPARENS)[ndx]
|
156
|
+
read_list(list_term)
|
143
157
|
else
|
144
|
-
throw :eol if options[:eol] == :throw
|
158
|
+
throw :eol if options[:eol] == :throw && value == options[:list_term]
|
145
159
|
raise Error, "unexpected list terminator: ?#{value.chr}"
|
146
160
|
end
|
147
161
|
else value
|
@@ -151,11 +165,13 @@ module SXP
|
|
151
165
|
alias_method :skip, :read
|
152
166
|
|
153
167
|
##
|
154
|
-
# @param [
|
155
|
-
|
168
|
+
# @param [String] list_term (nil)
|
169
|
+
# String expected to terminate the list
|
170
|
+
# @return [Array]
|
171
|
+
def read_list(list_term = nil)
|
156
172
|
list = []
|
157
173
|
catch (:eol) do
|
158
|
-
list << read(:eol => :throw) while true
|
174
|
+
list << read(:eol => :throw, :list_term => list_term) while true
|
159
175
|
end
|
160
176
|
list
|
161
177
|
end
|
@@ -258,6 +274,13 @@ module SXP
|
|
258
274
|
char
|
259
275
|
end
|
260
276
|
|
277
|
+
##
|
278
|
+
# Unread the string, putting back into the input stream
|
279
|
+
# @param [String] string
|
280
|
+
def unread(string)
|
281
|
+
string.reverse.each_char {|c| @input.ungetc(c)}
|
282
|
+
end
|
283
|
+
|
261
284
|
##
|
262
285
|
# @return [Boolean]
|
263
286
|
def eof?
|
@@ -13,6 +13,8 @@ module SXP; class Reader
|
|
13
13
|
INTEGER_BASE_16 = /^[+-]?[\da-z]+$/i
|
14
14
|
RATIONAL = /^([+-]?\d+)\/(\d+)$/
|
15
15
|
|
16
|
+
# Escape characters, used in the form `#\Backspace`. Case is treated
|
17
|
+
# insensitively
|
16
18
|
# @see http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
|
17
19
|
CHARACTERS = {
|
18
20
|
'newline' => "\n",
|
@@ -106,10 +108,21 @@ module SXP; class Reader
|
|
106
108
|
end
|
107
109
|
|
108
110
|
##
|
111
|
+
# Read characters sequences like `#\backspace`. Otherwise,
|
112
|
+
# reads a single character. Requires the ability to put
|
113
|
+
# eroneously read characters back in the input stream
|
114
|
+
#
|
109
115
|
# @return [String]
|
110
116
|
# @see http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
|
111
117
|
def read_character
|
112
|
-
|
118
|
+
lit = read_literal
|
119
|
+
|
120
|
+
return " " if lit.empty? && peek_char == " "
|
121
|
+
CHARACTERS.fetch(lit.downcase) do |string|
|
122
|
+
# Return just the first character
|
123
|
+
unread(string[1..-1])
|
124
|
+
string[0,1]
|
125
|
+
end
|
113
126
|
end
|
114
127
|
|
115
128
|
##
|
data/lib/sxp/reader/scheme.rb
CHANGED
@@ -11,6 +11,14 @@ module SXP; class Reader
|
|
11
11
|
INTEGER_BASE_16 = /^[+-]?[\da-z]+$/i
|
12
12
|
RATIONAL = /^([+-]?\d+)\/(\d+)$/
|
13
13
|
|
14
|
+
# Escape characters, used in the form `#\newline`. Case is treated
|
15
|
+
# insensitively
|
16
|
+
# @see http://people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
|
17
|
+
CHARACTERS = {
|
18
|
+
'newline' => "\n",
|
19
|
+
'space' => " ",
|
20
|
+
}
|
21
|
+
|
14
22
|
##
|
15
23
|
# Initializes the reader.
|
16
24
|
#
|
@@ -60,5 +68,23 @@ module SXP; class Reader
|
|
60
68
|
else raise Error, "invalid sharp-sign read syntax: ##{char.chr}"
|
61
69
|
end
|
62
70
|
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Read characters sequences like `#\space`. Otherwise,
|
74
|
+
# reads a single character. Requires the ability to put
|
75
|
+
# eroneously read characters back in the input stream
|
76
|
+
#
|
77
|
+
# @return [String]
|
78
|
+
# @see http://people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
|
79
|
+
def read_character
|
80
|
+
lit = read_literal
|
81
|
+
|
82
|
+
return " " if lit.empty? && peek_char == " "
|
83
|
+
CHARACTERS.fetch(lit.downcase) do
|
84
|
+
# Return just the first character
|
85
|
+
unread(lit[1..-1])
|
86
|
+
lit[0,1]
|
87
|
+
end
|
88
|
+
end
|
63
89
|
end # Scheme
|
64
90
|
end; end # SXP::Reader
|
data/lib/sxp/writer.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'time'
|
2
4
|
|
3
5
|
##
|
4
6
|
# Extensions for Ruby's `Object` class.
|
@@ -148,62 +150,67 @@ class Regexp
|
|
148
150
|
end
|
149
151
|
end
|
150
152
|
|
151
|
-
|
153
|
+
begin
|
154
|
+
require 'rdf' # For SPARQL
|
152
155
|
|
153
|
-
class RDF::URI
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
156
|
+
class RDF::URI
|
157
|
+
##
|
158
|
+
# Returns the SXP representation of this object.
|
159
|
+
#
|
160
|
+
# @return [String]
|
161
|
+
def to_sxp; lexical || "<#{self}>"; end
|
162
|
+
end
|
160
163
|
|
161
|
-
class RDF::Node
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
164
|
+
class RDF::Node
|
165
|
+
##
|
166
|
+
# Returns the SXP representation of this object.
|
167
|
+
#
|
168
|
+
# @return [String]
|
169
|
+
def to_sxp; to_s; end
|
170
|
+
end
|
168
171
|
|
169
|
-
class RDF::Literal
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
172
|
+
class RDF::Literal
|
173
|
+
##
|
174
|
+
# Returns the SXP representation of a Literal.
|
175
|
+
#
|
176
|
+
# @return [String]
|
177
|
+
def to_sxp
|
178
|
+
case datatype
|
179
|
+
when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double, RDF::XSD.decimal, RDF::XSD.time
|
180
|
+
# Retain stated lexical form if possible
|
181
|
+
valid? ? to_s : object.to_sxp
|
182
|
+
else
|
183
|
+
text = value.dump
|
184
|
+
text << "@#{language}" if self.has_language?
|
185
|
+
text << "^^#{datatype.to_sxp}" if self.has_datatype?
|
186
|
+
text
|
187
|
+
end
|
183
188
|
end
|
184
189
|
end
|
185
|
-
end
|
186
190
|
|
187
|
-
class RDF::Query
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
191
|
+
class RDF::Query
|
192
|
+
# Transform Query into an Array form of an SXP
|
193
|
+
#
|
194
|
+
# If Query is named, it's treated as a GroupGraphPattern, otherwise, a BGP
|
195
|
+
#
|
196
|
+
# @return [Array]
|
197
|
+
def to_sxp
|
198
|
+
res = [:bgp] + patterns
|
199
|
+
(named? ? [:graph, context, res] : res).to_sxp
|
200
|
+
end
|
196
201
|
end
|
197
|
-
end
|
198
202
|
|
199
|
-
class RDF::Query::Pattern
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
203
|
+
class RDF::Query::Pattern
|
204
|
+
# Transform Query Pattern into an SXP
|
205
|
+
# @return [String]
|
206
|
+
def to_sxp
|
207
|
+
[:triple, subject, predicate, object].to_sxp
|
208
|
+
end
|
204
209
|
end
|
205
|
-
end
|
206
210
|
|
207
|
-
class RDF::Query::Variable
|
208
|
-
|
211
|
+
class RDF::Query::Variable
|
212
|
+
def to_sxp; to_s; end
|
213
|
+
end
|
214
|
+
rescue LoadError => e
|
215
|
+
# Ignore if RDF not loaded
|
209
216
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sxp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Arto Bendiken
|
9
|
+
- Gregg Kellogg
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2013-03-12 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: json
|
@@ -18,7 +19,7 @@ dependencies:
|
|
18
19
|
requirements:
|
19
20
|
- - ! '>='
|
20
21
|
- !ruby/object:Gem::Version
|
21
|
-
version: 1.
|
22
|
+
version: 1.1.1
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +27,7 @@ dependencies:
|
|
26
27
|
requirements:
|
27
28
|
- - ! '>='
|
28
29
|
- !ruby/object:Gem::Version
|
29
|
-
version: 1.
|
30
|
+
version: 1.1.1
|
30
31
|
- !ruby/object:Gem::Dependency
|
31
32
|
name: rspec
|
32
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -34,7 +35,7 @@ dependencies:
|
|
34
35
|
requirements:
|
35
36
|
- - ! '>='
|
36
37
|
- !ruby/object:Gem::Version
|
37
|
-
version: 2.
|
38
|
+
version: 2.13.0
|
38
39
|
type: :development
|
39
40
|
prerelease: false
|
40
41
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +43,7 @@ dependencies:
|
|
42
43
|
requirements:
|
43
44
|
- - ! '>='
|
44
45
|
- !ruby/object:Gem::Version
|
45
|
-
version: 2.
|
46
|
+
version: 2.13.0
|
46
47
|
- !ruby/object:Gem::Dependency
|
47
48
|
name: yard
|
48
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,7 +51,7 @@ dependencies:
|
|
50
51
|
requirements:
|
51
52
|
- - ! '>='
|
52
53
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.8.
|
54
|
+
version: 0.8.5
|
54
55
|
type: :development
|
55
56
|
prerelease: false
|
56
57
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,9 +59,27 @@ dependencies:
|
|
58
59
|
requirements:
|
59
60
|
- - ! '>='
|
60
61
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.8.
|
62
|
+
version: 0.8.5
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: rdf
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.0.0
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 1.0.0
|
62
79
|
description: A pure-Ruby implementation of a universal S-expression parser.
|
63
|
-
email:
|
80
|
+
email:
|
81
|
+
- arto@bendiken.net
|
82
|
+
- gregg@greggkellogg.net
|
64
83
|
executables:
|
65
84
|
- sxp2rdf
|
66
85
|
- sxp2json
|