sxp 1.0.0 → 1.2.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.
@@ -1,6 +1,79 @@
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.
59
+ #
60
+ # @return [String]
61
+ def to_sxp(**options)
62
+ inspect
63
+ end
64
+ end
65
+
1
66
  ##
2
67
  # Extensions for Ruby's `Symbol` class.
3
68
  class Symbol
69
+ ##
70
+ # Returns the SXP representation of this object.
71
+ #
72
+ # @return [String]
73
+ def to_sxp(**options)
74
+ to_s
75
+ end
76
+
4
77
  ##
5
78
  # Returns `true` if this is a keyword symbol.
6
79
  #
@@ -10,17 +83,244 @@ class Symbol
10
83
  end
11
84
  end
12
85
 
13
- # Update RDF::URI if RDF is loaded
86
+ ##
87
+ # Extensions for Ruby's `Integer` class.
88
+ class Integer
89
+ ##
90
+ # Returns the SXP representation of this object.
91
+ #
92
+ # @return [String]
93
+ def to_sxp(**options)
94
+ to_s
95
+ end
96
+ end
97
+
98
+ ##
99
+ # Extensions for Ruby's `BigDecimal` class.
100
+ class BigDecimal
101
+ ##
102
+ # Returns the SXP representation of this object.
103
+ #
104
+ # @return [String]
105
+ def to_sxp(**options)
106
+ to_f.to_s
107
+ end
108
+ end
109
+
110
+ ##
111
+ # Extensions for Ruby's `Float` class.
112
+ class Float
113
+ ##
114
+ # Returns the SXP representation of this object.
115
+ #
116
+ # @return [String]
117
+ def to_sxp(**options)
118
+ case
119
+ when nan? then 'nan.0'
120
+ when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0')
121
+ else to_s
122
+ end
123
+ end
124
+ end
125
+
126
+ ##
127
+ # Extensions for Ruby's `Array` class.
128
+ class Array
129
+ ##
130
+ # Returns the SXP representation of this object.
131
+ #
132
+ # @return [String]
133
+ def to_sxp()
134
+ '(' << map { |x| x.to_sxp(**options) }.join(' ') << ')'
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Extensions for Ruby's `Vector` class.
140
+ class Vector
141
+ ##
142
+ # Returns the SXP representation of this object.
143
+ #
144
+ # @return [String]
145
+ def to_sxp(**options)
146
+ '#(' << to_a.map { |x| x.to_sxp(**options) }.join(' ') << ')'
147
+ end
148
+ end
149
+
150
+ ##
151
+ # Extensions for Ruby's `Hash` class.
152
+ class Hash
153
+ ##
154
+ # Returns the SXP representation of this object.
155
+ #
156
+ # @return [String]
157
+ def to_sxp(**options)
158
+ to_a.to_sxp(**options)
159
+ end
160
+ end
161
+
162
+ ##
163
+ # Extensions for Ruby's `Time` class.
164
+ class Time
165
+ ##
166
+ # Returns the SXP representation of this object.
167
+ #
168
+ # @return [String]
169
+ def to_sxp(**options)
170
+ '#@' << (respond_to?(:xmlschema) ? xmlschema : to_i).to_s
171
+ end
172
+ end
173
+
174
+ ##
175
+ # Extensions for Ruby's `Regexp` class.
176
+ class Regexp
177
+ ##
178
+ # Returns the SXP representation of this object.
179
+ #
180
+ # @return [String]
181
+ def to_sxp(**options)
182
+ '#' << inspect
183
+ end
184
+ end
185
+
14
186
  begin
15
- require 'rdf'
187
+ require 'rdf' # For SPARQL/RDF
16
188
 
17
189
  ##
18
- # Extensions for RDF::URI
190
+ # Extensions for Ruby's `Array` class.
191
+ # These extensions depend on RDF being loaded
192
+ class Array
193
+ ##
194
+ # Returns the SXP representation of this object.
195
+ #
196
+ # If array is of the form `[:base, uri, ..]`, the base_uri is taken from the second value
197
+ #
198
+ # If array is of the form `[:prefix, [..], ..]`, prefixes are taken from the second value
199
+ #
200
+ # Prefixes always are terminated by a ':'
201
+ #
202
+ # @param [Hash{Symbol => RDF::URI}] prefixes(nil)
203
+ # @param [RDF::URI] base_uri(nil)
204
+ # @return [String]
205
+ def to_sxp(prefixes: nil, base_uri: nil, **options)
206
+ if self.first == :base && self.length == 3 && self[1].is_a?(RDF::URI)
207
+ base_uri = self[1]
208
+ '(' << (
209
+ self[0,2].map(&:to_sxp) <<
210
+ self.last.to_sxp(prefixes: prefixes, base_uri: base_uri, **options)
211
+ ).join(' ') << ')'
212
+ elsif self.first == :prefix && self.length == 3 && self[1].is_a?(Array)
213
+ prefixes = prefixes ? prefixes.dup : {}
214
+ self[1].each do |defn|
215
+ prefixes[defn.first.to_s.chomp(':').to_sym] = RDF::URI(defn.last) if
216
+ defn.is_a?(Array) && defn.length == 2
217
+ end
218
+ pfx_sxp = self[1].map {|(p,s)|["#{p.to_s.chomp(':')}:".to_sym, RDF::URI(s)]}.to_sxp
219
+ '(' << [
220
+ :prefix,
221
+ pfx_sxp,
222
+ self.last.to_sxp(prefixes: prefixes, base_uri: base_uri, **options)
223
+ ].join(' ') << ')'
224
+ else
225
+ '(' << map { |x| x.to_sxp(prefixes: prefixes, base_uri: base_uri, **options) }.join(' ') << ')'
226
+ end
227
+ end
228
+ end
229
+
19
230
  class RDF::URI
231
+ ##
232
+ # Returns the SXP representation of this a 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.
233
+ #
234
+ # @param [Hash{Symbol => RDF::URI}] prefixes(nil)
235
+ # @param [RDF::URI] base_uri(nil)
236
+ # @return [String]
237
+ def to_sxp(prefixes: nil, base_uri: nil, **options)
238
+ return lexical if lexical
239
+ pn = pname(prefixes: prefixes || {})
240
+ return pn unless to_s == pn
241
+ md = self == base_uri ? '' : self.relativize(base_uri)
242
+ "<#{md}>"
243
+ end
244
+
20
245
  # Original lexical value of this URI to allow for round-trip serialization.
21
246
  def lexical=(value); @lexical = value; end
22
247
  def lexical; @lexical; end
23
248
  end
249
+
250
+ class RDF::Node
251
+ ##
252
+ # Returns the SXP representation of this object.
253
+ #
254
+ # @return [String]
255
+ def to_sxp(**options)
256
+ to_s
257
+ end
258
+ end
259
+
260
+ class RDF::Literal
261
+ ##
262
+ # Returns the SXP representation of a Literal.
263
+ #
264
+ # @return [String]
265
+ def to_sxp(**options)
266
+ case datatype
267
+ when RDF::XSD.boolean, RDF::XSD.integer, RDF::XSD.double, RDF::XSD.decimal, RDF::XSD.time
268
+ # Retain stated lexical form if possible
269
+ valid? ? to_s : object.to_sxp(**options)
270
+ else
271
+ text = value.dump
272
+ text << "@#{language}" if self.has_language?
273
+ text << "^^#{datatype.to_sxp(**options)}" if self.has_datatype?
274
+ text
275
+ end
276
+ end
277
+
278
+ class Double
279
+ ##
280
+ # Returns the SXP representation of this object.
281
+ #
282
+ # @return [String]
283
+ def to_sxp(**options)
284
+ case
285
+ when nan? then 'nan.0'
286
+ when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0')
287
+ else canonicalize.to_s.downcase
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ class RDF::Query
294
+ # Transform Query into an Array form of an SXP
295
+ #
296
+ # If Query is named, it's treated as a GroupGraphPattern, otherwise, a BGP
297
+ #
298
+ # @return [Array]
299
+ def to_sxp(**options)
300
+ res = [:bgp] + patterns
301
+ (named? ? [:graph, graph_name, res] : res).to_sxp(**options)
302
+ end
303
+ end
304
+
305
+ class RDF::Query::Pattern
306
+ # Transform Query Pattern into an SXP
307
+ #
308
+ # @return [String]
309
+ def to_sxp(**options)
310
+ [:triple, subject, predicate, object].to_sxp(**options)
311
+ end
312
+ end
313
+
314
+ class RDF::Query::Variable
315
+ ##
316
+ # Transform Query variable into an SXP.
317
+ #
318
+ # @return [String]
319
+ def to_sxp(**options)
320
+ prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??')
321
+ unbound? ? "#{prefix}#{name}".to_sym.to_sxp : ["#{prefix}#{name}".to_sym, value].to_sxp
322
+ end
323
+ end
24
324
  rescue LoadError
25
- # Ignore
325
+ # Ignore if RDF not loaded
26
326
  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
@@ -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
  ##
@@ -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
 
@@ -1,13 +1,13 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require 'rdf' # @see http://rubygems.org/gems/rdf
2
+ require 'rdf' # @see https:/rubygems.org/gems/rdf
3
3
 
4
4
  module SXP; class Reader
5
5
  ##
6
6
  # A SPARQL Syntax Expressions (SSE) parser.
7
7
  #
8
- # Requires [RDF.rb](http://rdf.rubyforge.org/).
8
+ # Requires [RDF.rb](https:/rubygems.org/gems/rdf/).
9
9
  #
10
- # @see http://openjena.org/wiki/SSE
10
+ # @see https:/openjena.org/wiki/SSE
11
11
  class SPARQL < Extended
12
12
  # Alias for rdf:type
13
13
  A = /^a$/
@@ -29,7 +29,11 @@ module SXP; class Reader
29
29
  # Distinguished variable
30
30
  VAR_ID = /^\?(.*)/
31
31
  # Non-distinguished variable
32
- ND_VAR = /^\?(:?\?([0-9]+)?|(\.[0-9]+))/
32
+ ND_VAR = /^\?(?:[\?\.])(.*)/
33
+ # Distinguished existential variable
34
+ EVAR_ID = /^\$(.*)/
35
+ # Non-distinguished existential variable
36
+ ND_EVAR = /^\$(?:[\$\.])(.*)/
33
37
  # A QName, subject to expansion to URIs using {PREFIX}
34
38
  PNAME = /([^:]*):(.*)/
35
39
 
@@ -67,7 +71,7 @@ module SXP; class Reader
67
71
  #
68
72
  # @param [IO, StringIO, String] input
69
73
  # @param [Hash{Symbol => Object}] options
70
- def initialize(input, options = {}, &block)
74
+ def initialize(input, **options, &block)
71
75
  super { @prefixes = {}; @bnodes = {}; @list_depth = 0 }
72
76
 
73
77
  if block_given?
@@ -79,12 +83,12 @@ module SXP; class Reader
79
83
  end
80
84
 
81
85
  ##
82
- # Reads SSE Tokens, including {RDF::Literal}, {RDF::URI} and RDF::Node.
86
+ # Reads SSE Tokens, including `RDF::Literal`, `RDF::URI` and `RDF::Node`.
83
87
  #
84
88
  # Performs forward reference for prefix and base URI representations and saves in
85
89
  # {#base_uri} and {#prefixes} accessors.
86
90
  #
87
- # Transforms tokens matching a {PNAME} pattern into {RDF::URI} instances if a match is
91
+ # Transforms tokens matching a {PNAME} pattern into `RDF::URI` instances if a match is
88
92
  # found with a previously identified {PREFIX}.
89
93
  # @return [Object]
90
94
  def read_token
@@ -125,8 +129,6 @@ module SXP; class Reader
125
129
  uri = RDF::URI(base.to_s + suffix)
126
130
  #STDERR.puts "read_tok lexical uri: #{uri.inspect}"
127
131
 
128
- # Cause URI to be serialized as a lexical
129
- uri.lexical = value
130
132
  [:atom, uri]
131
133
  else
132
134
  tok
@@ -158,7 +160,7 @@ module SXP; class Reader
158
160
  {datatype: read_token.last}
159
161
  else {}
160
162
  end
161
- RDF::Literal(value, options)
163
+ RDF::Literal(value, **options)
162
164
  end
163
165
 
164
166
  ##
@@ -180,9 +182,7 @@ module SXP; class Reader
180
182
 
181
183
  # If we have a base URI, use that when constructing a new URI
182
184
  uri = if self.base_uri && RDF::URI(buffer).relative?
183
- u = self.base_uri.join(buffer)
184
- u.lexical = "<#{buffer}>" unless u.to_s == buffer # So that it can be re-serialized properly
185
- u
185
+ self.base_uri.join(buffer)
186
186
  else
187
187
  RDF::URI(buffer)
188
188
  end
@@ -207,7 +207,7 @@ module SXP; class Reader
207
207
  #
208
208
  # Atoms parsed including `base`, `prefix`, `true`, `false`, numeric, BNodes and variables.
209
209
  #
210
- # Creates {RDF::Literal}, RDF::Node, or {RDF::Query::Variable} instances where appropriate.
210
+ # Creates `RDF::Literal`, `RDF::Node`, or `RDF::Query::Variable` instances where appropriate.
211
211
  #
212
212
  # @return [Object]
213
213
  def read_atom
@@ -223,8 +223,10 @@ module SXP; class Reader
223
223
  when INTEGER then RDF::Literal::Integer.new(buffer)
224
224
  when BNODE_ID then @bnodes[$1] ||= RDF::Node($1)
225
225
  when BNODE_NEW then RDF::Node.new
226
- when ND_VAR then variable($1, false)
227
- when VAR_ID then variable($1, true)
226
+ when ND_VAR then variable($1, distinguished: false)
227
+ when VAR_ID then variable($1, distinguished: true)
228
+ when ND_EVAR then variable($1, existential: true, distinguished: false)
229
+ when EVAR_ID then variable($1, existential: true, distinguished: true)
228
230
  else buffer.to_sym
229
231
  end
230
232
  end
@@ -251,20 +253,16 @@ module SXP; class Reader
251
253
  # is a disinguished or non-distinguished variable. Non-distinguished
252
254
  # variables are effectively the same as BNodes.
253
255
  # @return [RDF::Query::Variable]
254
- def variable(id, distinguished = true)
256
+ def variable(id, distinguished: true, existential: false)
255
257
  id = nil if id.to_s.empty?
256
258
 
257
259
  if id
258
260
  @vars ||= {}
259
261
  @vars[id] ||= begin
260
- v = RDF::Query::Variable.new(id)
261
- v.distinguished = distinguished
262
- v
262
+ RDF::Query::Variable.new(id, distinguished: distinguished, existential: existential)
263
263
  end
264
264
  else
265
- v = RDF::Query::Variable.new
266
- v.distinguished = distinguished
267
- v
265
+ RDF::Query::Variable.new(distinguished: distinguished, existential: existential)
268
266
  end
269
267
  end
270
268
  end # SPARQL