diff --git a/README.md b/README.md
index ef6e24d..858d6d7 100755
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ S-Expressions derive from LISP, and include some basic datatypes common to all v
Symbols
Of the form with-hyphen ?@!$ a\ symbol\ with\ spaces
Strings
- Of the form "Hello, world!"
+ Of the form "Hello, world!"
or 'Hello, world!'
Strings may include the following special characters:
\b
— Backspace
@@ -36,6 +36,7 @@ S-Expressions derive from LISP, and include some basic datatypes common to all v
\uxxxx
— 2-byte Unicode character escape
\Uxxxxxxxx
— 4-byte Unicode character escape
\"
— Double-quote character
+ \'
— Single-quote character
\\
— Backspace
Additionally, any other character may follow \
, representing the character itself.
@@ -124,6 +125,7 @@ In addition to the standard datatypes, the SPARQL dialect supports the following
Strings are interpreted as an RDF Literal with datatype xsd:string
. It can be followed by @lang
to create a language-tagged string, or ^^IRI
to create a datatyped-literal. Examples:
"a plain literal"
+ 'another plain literal'
"a literal with a language"@en
"a typed literal"^^<http://example/>
"a typed literal with a PNAME"^^xsd:string
diff --git a/lib/sxp/extensions.rb b/lib/sxp/extensions.rb
index 5669510..d5e6c24 100644
--- a/lib/sxp/extensions.rb
+++ b/lib/sxp/extensions.rb
@@ -56,6 +56,7 @@ def to_sxp(**options)
class String
##
# Returns the SXP representation of this object. Uses SPARQL-like escaping.
+ # Uses any recorded quote style from an originally parsed string.
#
# @return [String]
def to_sxp(**options)
@@ -69,14 +70,29 @@ def to_sxp(**options)
when (0x0C) then '\f'
when (0x0D) then '\r'
when (0x0E..0x1F) then sprintf("\\u%04X", u.ord)
- when (0x22) then '\"'
+ when (0x22) then as_dquote? ? '\"' : '"'
+ when (0x27) then as_squote? ? "\'" : "'"
when (0x5C) then '\\\\'
when (0x7F) then sprintf("\\u%04X", u.ord)
else u.chr
end
end
- '"' + buffer + '"'
+ if as_dquote?
+ '"' + buffer + '"'
+ else
+ "'" + buffer + "'"
+ end
end
+
+ # Record quote style used when parsing
+ # @return [:dquote, :squote]
+ attr_accessor :quote_style
+
+ # Render string using double quotes
+ def as_squote?; quote_style == :squote; end
+
+ # Render string using single quotes
+ def as_dquote?; quote_style != :squote; end
end
##
diff --git a/lib/sxp/reader/basic.rb b/lib/sxp/reader/basic.rb
index 21ef63a..abc128b 100644
--- a/lib/sxp/reader/basic.rb
+++ b/lib/sxp/reader/basic.rb
@@ -15,7 +15,7 @@ class Basic < Reader
def read_token
case peek_char
when ?(, ?) then [:list, read_char]
- when ?" then [:atom, read_string] #"
+ when ?", ?' then [:atom, read_string] #" or '
else super
end
end
@@ -36,16 +36,18 @@ def read_atom
# @return [String]
def read_string
buffer = ""
- skip_char # '"'
- until peek_char == ?" #"
+ quote_char = read_char
+ until peek_char == quote_char # " or '
buffer <<
case char = read_char
when ?\\ then read_character
else char
end
end
- skip_char # '"'
- buffer
+ skip_char # " or '
+
+ # Return string, annotating it with the quotation style used
+ buffer.tap {|s| s.quote_style = (quote_char == '"' ? :dquote : :squote)}
end
##
diff --git a/lib/sxp/reader/common_lisp.rb b/lib/sxp/reader/common_lisp.rb
index 275b463..aef8b4d 100644
--- a/lib/sxp/reader/common_lisp.rb
+++ b/lib/sxp/reader/common_lisp.rb
@@ -115,7 +115,7 @@ def read_vector
# @return [Array]
def read_quote
skip_char # "'"
- [options[:quote] || :quote, read]
+ [options[:quote] || :quote, read.tap {|s| s.quote_style = :squote if s.is_a?(String)}]
end
##
diff --git a/lib/sxp/reader/sparql.rb b/lib/sxp/reader/sparql.rb
index b87ed19..2e4cbd3 100644
--- a/lib/sxp/reader/sparql.rb
+++ b/lib/sxp/reader/sparql.rb
@@ -94,6 +94,7 @@ def initialize(input, **options, &block)
def read_token
case peek_char
when ?" then [:atom, read_rdf_literal] # "
+ when ?' then [:atom, read_rdf_literal] # '
when ?< then [:atom, read_rdf_uri]
else
tok = super
@@ -144,6 +145,7 @@ def read_token
#
# @example
# "a plain literal"
+ # 'another plain literal'
# "a literal with a language"@en
# "a typed literal"^^
# "a typed literal with a PNAME"^^xsd:string
diff --git a/spec/common_lisp_spec.rb b/spec/common_lisp_spec.rb
index 68bf016..2d6842c 100644
--- a/spec/common_lisp_spec.rb
+++ b/spec/common_lisp_spec.rb
@@ -61,7 +61,9 @@
context "when reading strings" do
it "reads `\"foo\"` as a string" do
- expect(read(%q("foo"))).to eq "foo"
+ res = read(%q("foo"))
+ expect(res).to eq "foo"
+ expect(res.quote_style == :squote)
end
end
diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb
index 971116f..60af942 100644
--- a/spec/reader_spec.rb
+++ b/spec/reader_spec.rb
@@ -60,6 +60,7 @@
%q{"\n"} => "\n",
%q{"\r"} => "\r",
%q{"\t"} => "\t",
+ %q{"\'"} => "\'",
%q{"\u0080"} => "\u0080",
%q("\u07FF") => "\u07FF",
%q("\u0800") => "\u0800",
@@ -75,9 +76,18 @@
%q("\U000FFFFD") => "\u{FFFFD}",
%q("\U00100000") => "\u{100000}",
%q("\U0010FFFD") => "\u{10FFFD}",
+
+ %q{'\b'} => "\b",
+ %q{'\f'} => "\f",
+ %q{'\n'} => "\n",
+ %q{'\r'} => "\r",
+ %q{'\t'} => "\t",
+ %q{'\''} => "\'",
}.each do |input, output|
it "reads #{input} as #{output.inspect}" do
- expect(read(input)).to eq output
+ res = read(input)
+ expect(res).to eq output
+ expect(res.quote_style).to eql (input.start_with?('"') ? :dquote : :squote)
end
end
end