sxp 1.0.1 → 1.2.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.
@@ -1,6 +1,95 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'bigdecimal'
3
+ require 'matrix'
4
+ require 'time'
5
+
6
+ ##
7
+ # Extensions for Ruby's `Object` class.
8
+ class Object
9
+ ##
10
+ # Returns the SXP representation of this object.
11
+ #
12
+ # @return [String]
13
+ def to_sxp(**options)
14
+ to_s.to_json
15
+ end
16
+ end
17
+
18
+ ##
19
+ # Extensions for Ruby's `NilClass` class.
20
+ class NilClass
21
+ ##
22
+ # Returns the SXP representation of this object.
23
+ #
24
+ # @return [String]
25
+ def to_sxp(**options)
26
+ '#n'
27
+ end
28
+ end
29
+
30
+ ##
31
+ # Extensions for Ruby's `FalseClass` class.
32
+ class FalseClass
33
+ ##
34
+ # Returns the SXP representation of this object.
35
+ #
36
+ # @return [String]
37
+ def to_sxp(**options)
38
+ '#f'
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Extensions for Ruby's `TrueClass` class.
44
+ class TrueClass
45
+ ##
46
+ # Returns the SXP representation of this object.
47
+ #
48
+ # @return [String]
49
+ def to_sxp(**options)
50
+ '#t'
51
+ end
52
+ end
53
+
54
+ ##
55
+ # Extensions for Ruby's `String` class.
56
+ class String
57
+ ##
58
+ # Returns the SXP representation of this object. Uses SPARQL-like escaping.
59
+ #
60
+ # @return [String]
61
+ def to_sxp(**options)
62
+ buffer = ""
63
+ each_char do |u|
64
+ buffer << case u.ord
65
+ when (0x00..0x07) then sprintf("\\u%04X", u.ord)
66
+ when (0x08) then '\b'
67
+ when (0x09) then '\t'
68
+ when (0x0A) then '\n'
69
+ when (0x0C) then '\f'
70
+ when (0x0D) then '\r'
71
+ when (0x0E..0x1F) then sprintf("\\u%04X", u.ord)
72
+ when (0x22) then '\"'
73
+ when (0x5C) then '\\\\'
74
+ when (0x7F) then sprintf("\\u%04X", u.ord)
75
+ else u.chr
76
+ end
77
+ end
78
+ '"' + buffer + '"'
79
+ end
80
+ end
81
+
1
82
  ##
2
83
  # Extensions for Ruby's `Symbol` class.
3
84
  class Symbol
85
+ ##
86
+ # Returns the SXP representation of this object.
87
+ #
88
+ # @return [String]
89
+ def to_sxp(**options)
90
+ to_s
91
+ end
92
+
4
93
  ##
5
94
  # Returns `true` if this is a keyword symbol.
6
95
  #
@@ -10,17 +99,244 @@ class Symbol
10
99
  end
11
100
  end
12
101
 
13
- # Update RDF::URI if RDF is loaded
102
+ ##
103
+ # Extensions for Ruby's `Integer` class.
104
+ class Integer
105
+ ##
106
+ # Returns the SXP representation of this object.
107
+ #
108
+ # @return [String]
109
+ def to_sxp(**options)
110
+ to_s
111
+ end
112
+ end
113
+
114
+ ##
115
+ # Extensions for Ruby's `BigDecimal` class.
116
+ class BigDecimal
117
+ ##
118
+ # Returns the SXP representation of this object.
119
+ #
120
+ # @return [String]
121
+ def to_sxp(**options)
122
+ to_f.to_s
123
+ end
124
+ end
125
+
126
+ ##
127
+ # Extensions for Ruby's `Float` class.
128
+ class Float
129
+ ##
130
+ # Returns the SXP representation of this object.
131
+ #
132
+ # @return [String]
133
+ def to_sxp(**options)
134
+ case
135
+ when nan? then 'nan.0'
136
+ when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0')
137
+ else to_s
138
+ end
139
+ end
140
+ end
141
+
142
+ ##
143
+ # Extensions for Ruby's `Array` class.
144
+ class Array
145
+ ##
146
+ # Returns the SXP representation of this object.
147
+ #
148
+ # @return [String]
149
+ def to_sxp()
150
+ '(' << map { |x| x.to_sxp(**options) }.join(' ') << ')'
151
+ end
152
+ end
153
+
154
+ ##
155
+ # Extensions for Ruby's `Vector` class.
156
+ class Vector
157
+ ##
158
+ # Returns the SXP representation of this object.
159
+ #
160
+ # @return [String]
161
+ def to_sxp(**options)
162
+ '#(' << to_a.map { |x| x.to_sxp(**options) }.join(' ') << ')'
163
+ end
164
+ end
165
+
166
+ ##
167
+ # Extensions for Ruby's `Hash` class.
168
+ class Hash
169
+ ##
170
+ # Returns the SXP representation of this object.
171
+ #
172
+ # @return [String]
173
+ def to_sxp(**options)
174
+ to_a.to_sxp(**options)
175
+ end
176
+ end
177
+
178
+ ##
179
+ # Extensions for Ruby's `Time` class.
180
+ class Time
181
+ ##
182
+ # Returns the SXP representation of this object.
183
+ #
184
+ # @return [String]
185
+ def to_sxp(**options)
186
+ '#@' << (respond_to?(:xmlschema) ? xmlschema : to_i).to_s
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Extensions for Ruby's `Regexp` class.
192
+ class Regexp
193
+ ##
194
+ # Returns the SXP representation of this object.
195
+ #
196
+ # @return [String]
197
+ def to_sxp(**options)
198
+ '#' << inspect
199
+ end
200
+ end
201
+
14
202
  begin
15
- require 'rdf'
203
+ require 'rdf' # For SPARQL/RDF
16
204
 
17
205
  ##
18
- # Extensions for RDF::URI
206
+ # Extensions for Ruby's `Array` class.
207
+ # These extensions depend on RDF being loaded
208
+ class Array
209
+ ##
210
+ # Returns the SXP representation of this object.
211
+ #
212
+ # If array is of the form `[:base, uri, ..]`, the base_uri is taken from the second value
213
+ #
214
+ # If array is of the form `[:prefix, [..], ..]`, prefixes are taken from the second value
215
+ #
216
+ # Prefixes always are terminated by a ':'
217
+ #
218
+ # @param [Hash{Symbol => RDF::URI}] prefixes(nil)
219
+ # @param [RDF::URI] base_uri(nil)
220
+ # @return [String]
221
+ def to_sxp(prefixes: nil, base_uri: nil, **options)
222
+ if self.first == :base && self.length == 3 && self[1].is_a?(RDF::URI)
223
+ base_uri = self[1]
224
+ '(' << (
225
+ self[0,2].map(&:to_sxp) <<
226
+ self.last.to_sxp(prefixes: prefixes, base_uri: base_uri, **options)
227
+ ).join(' ') << ')'
228
+ elsif self.first == :prefix && self.length == 3 && self[1].is_a?(Array)
229
+ prefixes = prefixes ? prefixes.dup : {}
230
+ self[1].each do |defn|
231
+ prefixes[defn.first.to_s.chomp(':').to_sym] = RDF::URI(defn.last) if
232
+ defn.is_a?(Array) && defn.length == 2
233
+ end
234
+ pfx_sxp = self[1].map {|(p,s)|["#{p.to_s.chomp(':')}:".to_sym, RDF::URI(s)]}.to_sxp
235
+ '(' << [
236
+ :prefix,
237
+ pfx_sxp,
238
+ self.last.to_sxp(prefixes: prefixes, base_uri: base_uri, **options)
239
+ ].join(' ') << ')'
240
+ else
241
+ '(' << map { |x| x.to_sxp(prefixes: prefixes, base_uri: base_uri, **options) }.join(' ') << ')'
242
+ end
243
+ end
244
+ end
245
+
19
246
  class RDF::URI
247
+ ##
248
+ # Returns the SXP representation of this URI. Uses Lexical representation, if set, otherwise, any PName match, otherwise, the relativized version of the URI if a base_uri is given, otherwise just the URI.
249
+ #
250
+ # @param [Hash{Symbol => RDF::URI}] prefixes(nil)
251
+ # @param [RDF::URI] base_uri(nil)
252
+ # @return [String]
253
+ def to_sxp(prefixes: nil, base_uri: nil, **options)
254
+ return lexical if lexical
255
+ pn = pname(prefixes: prefixes || {})
256
+ return pn unless to_s == pn
257
+ md = self == base_uri ? '' : self.relativize(base_uri)
258
+ "<#{md}>"
259
+ end
260
+
20
261
  # Original lexical value of this URI to allow for round-trip serialization.
21
262
  def lexical=(value); @lexical = value; end
22
263
  def lexical; @lexical; end
23
264
  end
265
+
266
+ class RDF::Node
267
+ ##
268
+ # Returns the SXP representation of this object.
269
+ #
270
+ # @return [String]
271
+ def to_sxp(**options)
272
+ to_s
273
+ end
274
+ end
275
+
276
+ class RDF::Literal
277
+ ##
278
+ # Returns the SXP representation of a Literal.
279
+ #
280
+ # @return [String]
281
+ def to_sxp(**options)
282
+ case datatype
283
+ when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double, RDF::XSD.decimal, RDF::XSD.time
284
+ # Retain stated lexical form if possible
285
+ valid? ? to_s : object.to_sxp(**options)
286
+ else
287
+ text = value.to_sxp
288
+ text << "@#{language}" if self.has_language?
289
+ text << "^^#{datatype.to_sxp(**options)}" if self.has_datatype?
290
+ text
291
+ end
292
+ end
293
+
294
+ class Double
295
+ ##
296
+ # Returns the SXP representation of this object.
297
+ #
298
+ # @return [String]
299
+ def to_sxp(**options)
300
+ case
301
+ when nan? then 'nan.0'
302
+ when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0')
303
+ else canonicalize.to_s.downcase
304
+ end
305
+ end
306
+ end
307
+ end
308
+
309
+ class RDF::Query
310
+ # Transform Query into an Array form of an SXP
311
+ #
312
+ # If Query is named, it's treated as a GroupGraphPattern, otherwise, a BGP
313
+ #
314
+ # @return [Array]
315
+ def to_sxp(**options)
316
+ res = [:bgp] + patterns
317
+ (named? ? [:graph, graph_name, res] : res).to_sxp(**options)
318
+ end
319
+ end
320
+
321
+ class RDF::Query::Pattern
322
+ # Transform Query Pattern into an SXP
323
+ #
324
+ # @return [String]
325
+ def to_sxp(**options)
326
+ [:triple, subject, predicate, object].to_sxp(**options)
327
+ end
328
+ end
329
+
330
+ class RDF::Query::Variable
331
+ ##
332
+ # Transform Query variable into an SXP.
333
+ #
334
+ # @return [String]
335
+ def to_sxp(**options)
336
+ prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??')
337
+ unbound? ? "#{prefix}#{name}".to_sym.to_sxp : ["#{prefix}#{name}".to_sym, value].to_sxp
338
+ end
339
+ end
24
340
  rescue LoadError
25
- # Ignore
341
+ # Ignore if RDF not loaded
26
342
  end
data/lib/sxp/generator.rb CHANGED
@@ -16,18 +16,41 @@ module SXP
16
16
 
17
17
  ##
18
18
  # @param [Object] obj
19
- def initialize(obj, indent)
19
+ # @param [Integer] indent
20
+ # @param [Hash{Symbol => RDF::URI}] prefixes (nil)
21
+ # @param [RDF::URI] base_uri (nil)
22
+ def initialize(obj, indent, prefixes: nil, base_uri: nil)
20
23
  @indent = indent
21
24
  @elements = []
25
+ @prefixes = prefixes
26
+ @base_uri = base_uri
22
27
  if obj.is_a?(Array)
23
- obj.compact.each {|o| @elements << Block.new(o, indent + 1)}
28
+ # If this is a base or prefix element, update our representations
29
+ if obj.first == :base && obj.length == 3 && obj[1].is_a?(RDF::URI)
30
+ base_uri = obj[1]
31
+ @elements << Block.new(:base, indent + 1)
32
+ @elements << Block.new(obj[1], indent + 1)
33
+ @elements << Block.new(obj.last, indent + 1, prefixes: prefixes, base_uri: base_uri)
34
+ elsif obj.first == :prefix && obj.length == 3 && obj[1].is_a?(Array)
35
+ prefixes = prefixes ? prefixes.dup : {}
36
+ obj[1].each do |defn|
37
+ prefixes[defn.first.to_s.chomp(':').to_sym] = RDF::URI(defn.last) if defn.is_a?(Array) && defn.length == 2
38
+ end
39
+ @elements << Block.new(:prefix, indent + 1)
40
+ @elements << Block.new(obj[1], indent + 1)
41
+ @elements << Block.new(obj.last, indent + 1, prefixes: prefixes, base_uri: base_uri)
42
+ else
43
+ obj.compact.each do |o|
44
+ @elements << Block.new(o, indent + 1, prefixes: prefixes, base_uri: base_uri)
45
+ end
46
+ end
24
47
  else
25
48
  @elements = obj
26
49
  end
27
50
  end
28
51
 
29
52
  ##
30
- # Agregate length over each element accounting for spaces
53
+ # Aggregate length over each element accounting for spaces
31
54
  #
32
55
  # @return [Integer]
33
56
  # If indent is not not nil, returns zero
@@ -35,7 +58,7 @@ module SXP
35
58
  if @elements.is_a?(Array)
36
59
  @elements.map(&:length).inject(:+).to_i + @elements.length - 1
37
60
  else
38
- @elements.to_sxp.length
61
+ @elements.to_sxp(prefixes: @prefixes, base_uri: @base_uri).length
39
62
  end
40
63
  end
41
64
 
@@ -44,8 +67,8 @@ module SXP
44
67
  # This should only be called on a block when
45
68
  # no indentation is to be applied
46
69
  # @return [String]
47
- def to_sxp
48
- @elements.to_sxp
70
+ def to_sxp(prefixes: nil, base_uri: nil)
71
+ @elements.to_sxp(prefixes: prefixes || @prefixes, base_uri: base_uri || @base_uri)
49
72
  end
50
73
 
51
74
  ##
@@ -67,7 +90,7 @@ module SXP
67
90
  first, *elems = @elements
68
91
  unless first.sxp?
69
92
  # It's atomic, write out after paren
70
- buffer += first.to_sxp + "\n"
93
+ buffer += first.to_sxp(prefixes: @prefixes, base_uri: @base_uri) + "\n"
71
94
  else
72
95
  buffer += "\n"
73
96
  elems.unshift(first)
@@ -77,7 +100,7 @@ module SXP
77
100
  end
78
101
  buffer += do_indent + ")\n"
79
102
  else
80
- buffer += do_indent + @elements.to_sxp + "\n"
103
+ buffer += do_indent + @elements.to_sxp(prefixes: @prefixes, base_uri: @base_uri) + "\n"
81
104
  end
82
105
  buffer
83
106
  end
data/lib/sxp/pair.rb CHANGED
@@ -27,7 +27,7 @@ module SXP
27
27
  # Returns `true` if the tail of this pair is not `nil` or another pair.
28
28
  #
29
29
  # @return [Boolean]
30
- # @see http://srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
30
+ # @see https:/srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
31
31
  def dotted?
32
32
  !proper?
33
33
  end
@@ -36,7 +36,7 @@ module SXP
36
36
  # Returns `true` if the tail of this pair is `nil` or another pair.
37
37
  #
38
38
  # @return [Boolean]
39
- # @see http://srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
39
+ # @see https:/srfi.schemers.org/srfi-1/srfi-1.html#ImproperLists
40
40
  def proper?
41
41
  tail.nil? || tail.is_a?(Pair)
42
42
  end
@@ -35,7 +35,7 @@ module SXP; class Reader
35
35
  ##
36
36
  # @return [String]
37
37
  def read_string
38
- buffer = String.new
38
+ buffer = ""
39
39
  skip_char # '"'
40
40
  until peek_char == ?" #"
41
41
  buffer <<
@@ -57,8 +57,8 @@ module SXP; class Reader
57
57
  when ?n then ?\n
58
58
  when ?r then ?\r
59
59
  when ?t then ?\t
60
- when ?u then read_chars(4).to_i(16).chr
61
- when ?U then read_chars(8).to_i(16).chr
60
+ when ?u then read_chars(4).to_i(16).chr(Encoding::UTF_8)
61
+ when ?U then read_chars(8).to_i(16).chr(Encoding::UTF_8)
62
62
  when ?" then char #"
63
63
  when ?\\ then char
64
64
  else char
@@ -69,7 +69,7 @@ module SXP; class Reader
69
69
  # @return [String]
70
70
  def read_literal
71
71
  grammar = self.class.const_get(:ATOM)
72
- buffer = String.new
72
+ buffer = ""
73
73
  buffer << read_char while !eof? && peek_char.chr =~ grammar
74
74
  buffer
75
75
  end
@@ -1,9 +1,11 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ require 'matrix'
3
+
2
4
  module SXP; class Reader
3
5
  ##
4
6
  # A Common Lisp S-expressions parser.
5
7
  #
6
- # @see http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node14.html
8
+ # @see https:/www.cs.cmu.edu/Groups/AI/html/cltl/clm/node14.html
7
9
  class CommonLisp < Basic
8
10
  OPTIONS = {nil: nil, t: true, quote: :quote, function: :function}
9
11
 
@@ -16,7 +18,7 @@ module SXP; class Reader
16
18
 
17
19
  # Escape characters, used in the form `#\Backspace`. Case is treated
18
20
  # insensitively
19
- # @see http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
21
+ # @see https:/www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
20
22
  CHARACTERS = {
21
23
  'newline' => "\n",
22
24
  'space' => " ",
@@ -37,8 +39,8 @@ module SXP; class Reader
37
39
  # @option options [Object] :t (true)
38
40
  # @option options [Object] :quote (:quote)
39
41
  # @option options [Object] :function (:function)
40
- def initialize(input, options = {}, &block)
41
- super(input, OPTIONS.merge(options), &block)
42
+ def initialize(input, **options, &block)
43
+ super(input, **OPTIONS.merge(options), &block)
42
44
  end
43
45
 
44
46
  ##
@@ -70,7 +72,7 @@ module SXP; class Reader
70
72
  ##
71
73
  # @return [Symbol]
72
74
  def read_symbol(delimiter = nil)
73
- buffer = String.new
75
+ buffer = ""
74
76
  skip_char # '|'
75
77
  until delimiter === peek_char
76
78
  buffer <<
@@ -88,7 +90,8 @@ module SXP; class Reader
88
90
  #
89
91
  # @return [Array]
90
92
  def read_vector
91
- raise NotImplementedError, "#{self.class}#read_vector" # TODO
93
+ list = read_list(')')
94
+ Vector.[](*list)
92
95
  end
93
96
 
94
97
  ##
@@ -114,7 +117,7 @@ module SXP; class Reader
114
117
  # eroneously read characters back in the input stream
115
118
  #
116
119
  # @return [String]
117
- # @see http://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
120
+ # @see https:/www.cs.cmu.edu/Groups/AI/html/cltl/clm/node22.html
118
121
  def read_character
119
122
  lit = read_literal
120
123
 
@@ -3,7 +3,7 @@ module SXP; class Reader
3
3
  ##
4
4
  # A Scheme R4RS S-expressions parser.
5
5
  #
6
- # @see http://people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
6
+ # @see https:/people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
7
7
  class Scheme < Extended
8
8
  DECIMAL = /^[+-]?(\d*)?\.\d*$/
9
9
  INTEGER_BASE_2 = /^[+-]?[01]+$/
@@ -14,7 +14,7 @@ module SXP; class Reader
14
14
 
15
15
  # Escape characters, used in the form `#\newline`. Case is treated
16
16
  # insensitively
17
- # @see http://people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
17
+ # @see https:/people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
18
18
  CHARACTERS = {
19
19
  'newline' => "\n",
20
20
  'space' => " ",
@@ -26,8 +26,8 @@ module SXP; class Reader
26
26
  # @param [IO, StringIO, String] input
27
27
  # @param [Hash{Symbol => Object}] options
28
28
  # @option options [Symbol] :version (:r4rs)
29
- def initialize(input, options = {}, &block)
30
- super(input, {version: :r4rs}.merge(options), &block)
29
+ def initialize(input, version: :r4rs, **options, &block)
30
+ super(input, version: version, **options, &block)
31
31
  end
32
32
 
33
33
  ##
@@ -64,8 +64,8 @@ module SXP; class Reader
64
64
  when ?d, ?D then read_integer(10)
65
65
  when ?x, ?X then read_integer(16)
66
66
  when ?\\ then read_character
67
- when ?; then skip; read
68
- when ?! then skip_line; read # shebang
67
+ when ?; then skip # comment character
68
+ when ?! then skip_line; skip # shebang
69
69
  else raise Error, "invalid sharp-sign read syntax: ##{char.chr}"
70
70
  end
71
71
  end
@@ -76,7 +76,7 @@ module SXP; class Reader
76
76
  # eroneously read characters back in the input stream
77
77
  #
78
78
  # @return [String]
79
- # @see http://people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
79
+ # @see https:/people.csail.mit.edu/jaffer/r4rs_9.html#SEC65
80
80
  def read_character
81
81
  lit = read_literal
82
82