# SXP.rb: S-Expressions for Ruby
This is a Ruby implementation of a universal [S-expression][] parser.
[![Gem Version](https://badge.fury.io/rb/sxp.png)](https:/badge.fury.io/rb/sxp)
[![Build Status](https://github.com/dryruby/sxp.rb/workflows/CI/badge.svg?branch=develop)](https://github.com/dryruby/sxp.rb/actions?query=workflow%3ACI)
[![Coverage Status](https://coveralls.io/repos/dryruby/sxp.rb/badge.svg?branch=develop)](https://coveralls.io/r/dryruby/sxp.rb?branch=develop)
## Features
* Parses S-expressions in universal, [Scheme][], [Common Lisp][], or
[SPARQL][] syntax.
* Adds a `#to_sxp` method to Ruby objects.
* Compatible with Ruby >= 2.6, Rubinius >= 3.0, and JRuby 9+.
## Basic syntax
S-Expressions derive from LISP, and include some basic datatypes common to all variants:
- Pairs
- Of the form
(2 . 3)
- Lists
- Of the form
(1 (2 3))
- Symbols
- Of the form
with-hyphen ?@!$ a\ symbol\ with\ spaces
- Strings
- Of the form
"Hello, world!"
Strings may include the following special characters:
\b
— Backspace
\f
— Form Feed
\n
— New Line
\r
— Carriage Return
\t
— Horizontal Tab
\uxxxx
— 2-byte Unicode character escape
\Uxxxxxxxx
— 4-byte Unicode character escape
\"
— Double-quote character
\\
— Backspace
Additionally, any other character may follow \
, representing the character itself.
- Characters
- Of the form
...
- Integers
- Of the form
-9876543210
- Floating-point numbers
- Of the form
-0.0 6.28318 6.022e23
- Rationals
- Of the form
1/3
Additionally, variation-specific formats support additional datatypes:
### Scheme
In addition to the standard datatypes, the Scheme dialect supports the following:
- Lists
- In addition to
( ... )
, a square bracket pair may be used for reading lists of the form [ ... ]
.
- Comments
- A comment starts with
;
and continues to the end of the line.
- Sharp character sequences
- Such as
#t
, #n
, and #xXXX
.
#n
— Null
#f
— False
#t
— True
#bBBB
— Binary number
#oOOO
— Octal number
#dDDD
— Decimal number
#xXXX
— Hexadecimal number
#\C
— A single Unicode character
#\space
— A space character
#\newline
— A newline character
#;
— Skipped character
#!
— Skipped to end of line
### Common Lisp
In addition to the standard datatypes, the Common Lisp dialect supports the following:
- Comments
- A comment starts with
;
and continues to the end of the line.
- Symbols
- In addition to base symbols, any character sequence delimited by
|
is treated as a symbol.
- Sharp character sequences
- Such as
#t
, #n
, and #xXXX
.
#bBBB
— Binary number
#oOOO
— Octal number
#xXXX
— Hexadecimal number
#C
— A single Unicode character
#\newline
— A newline character
#\space
— A space character
#\backspace
— A backspace character
#\tab
— A tab character
#\linefeed
— A linefeed character
#\page
— A page feed character
#\return
— A carriage return character
#\rubout
— A rubout character
#'function
— A function definition
### SPARQL/RDF
In addition to the standard datatypes, the SPARQL dialect supports the following:
- Lists
- In addition to
( ... )
, a square bracket pair may be used for reading lists of the form [ ... ]
.
- Comments
- A comment starts with
#
or ;
and continues to the end of the line.
- Literals
- 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"
"a literal with a language"@en
"a typed literal"^^<http://example/>
"a typed literal with a PNAME"^^xsd:string
- IRIs
- An IRI is formed as in SPARQL, either enclosed by
<...>
, or having the form of a PNAME
. If a base iri is defined in a containing base expression, IRIs using the <...>
are resolved against that base iri. If the PNAME
form is used, the prefix must be defined in a containing prefix expression. Examples:
<http://example/foo>
(base <http://example.com> <foo>)
(prefix ((foo: <http://example.com/>)) foo:bar)
a
# synonym for rdf:type
- Blank Nodes
- An blank node is formed as in SPARQL. Examples:
- Variables
- A SPARQL variable is defined using either
?
or $
prefixes, as in SPARQL. Examples:
- Numbers and booleans
- As with SPARQL. Examples:
- true, false
- 123, -18
- 123.0, 456.
- 1.0e0, 1.0E+6
## Examples
require 'sxp'
### Parsing basic S-expressions
SXP.read "(* 6 7)" #=> [:*, 6, 7]
SXP.read <<-EOF
(define (fact n)
(if (= n 0)
1
(* n (fact (- n 1)))))
EOF
#=> [:define, [:fact, :n],
[:if, [:"=", :n, 0],
1,
[:*, :n, [:fact, [:-, :n, 1]]]]]
### Parsing Scheme S-expressions
SXP::Reader::Scheme.read %q((and #t #f)) #=> [:and, true, false]
### Parsing Common Lisp S-expressions
SXP::Reader::CommonLisp.read %q((or t nil)) #=> [:or, true, nil]
### Parsing SPARQL S-expressions
require 'rdf'
SXP::Reader::SPARQL.read %q((base )) #=> [:base, RDF::URI('https://ar.to/')]
### Writing an SXP with formatting
SXP::Generator.print([:and, true, false]) #=> (and #t #f)
## Documentation
* Full documentation available on [RubyDoc](https://dryruby.github.io/sxp)
* {SXP}
### Parsing SXP
* {SXP::Reader}
* {SXP::Reader::Basic}
* {SXP::Reader::CommonLisp}
* {SXP::Reader::Extended}
* {SXP::Reader::Scheme}
* {SXP::Reader::SPARQL}
### Manipulating SXP
* {SXP::Pair}
* {SXP::List}
### Generating SXP
* {SXP::Generator}
# Dependencies
* [Ruby](https:/ruby-lang.org/) (>= 2.6)
* [RDF.rb](https:/rubygems.org/gems/rdf) (~> 3.2), only needed for SPARQL
S-expressions
# Installation
The recommended installation method is via [RubyGems](https:/rubygems.org/).
To install the latest official release of the SXP.rb gem, do:
% [sudo] gem install sxp
## Download
To get a local working copy of the development repository, do:
% git clone git://github.com/dryruby/sxp.rb.git
Alternatively, you can download the latest development version as a tarball
as follows:
% wget https:/github.com/dryruby/sxp.rb/tarball/master
## Resources
*
*
*
## Authors
* [Arto Bendiken](https://github.com/artob) -
* [Gregg Kellogg](https://github.com/gkellogg) -
## Contributors
* [Ben Lavender](https://github.com/bhuga) -
## Contributing
This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange development and release activity. All submissions _must_ be on a feature branch based on the _develop_ branch to ease staging and integration.
* Do your best to adhere to the existing coding conventions and idioms.
* Don't use hard tabs, and don't leave trailing whitespace on any line.
* Do document every method you add using [YARD][] annotations. Read the
[tutorial][YARD-GS] or just look at the existing code for examples.
* Don't touch the `.gemspec`, `VERSION` or `AUTHORS` files. If you need to
change them, do so on your private branch only.
* Do feel free to add yourself to the `CREDITS` file and the corresponding
list in the the `README`. Alphabetical order applies.
* Do note that in order for us to merge any non-trivial changes (as a rule
of thumb, additions larger than about 15 lines of code), we need an
explicit [public domain dedication][PDD] on record from you,
which you will be asked to agree to on the first commit to a repo within the organization.
## License
SXP.rb is free and unencumbered public domain software. For more
information, see or the accompanying UNLICENSE file.
[S-expression]: https://en.wikipedia.org/wiki/S-expression
[Scheme]: https://scheme.info/
[Common Lisp]: https://en.wikipedia.org/wiki/Common_Lisp
[SPARQL]: https://jena.apache.org/documentation/notes/sse.html
[YARD]: https://yardoc.org/
[YARD-GS]: https://rubydoc.info/docs/yard/file/docs/GettingStarted.md
[PDD]: https://lists.w3.org/Archives/Public/public-rdf-ruby/2010May/0013.html