SXP.rb: S-Expressions for Ruby

This is a Ruby implementation of a universal S-expression parser.

Gem Version Build Status Coverage Status

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 [email protected]!$ 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:
  • _:
  • _:id
Variables
A SPARQL variable is defined using either ? or $ prefixes, as in SPARQL. Examples:
  • ?var
  • $var
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 <https://ar.to/>))  #=> [:base, RDF::URI('https://ar.to/')]

Writing an SXP with formatting

SXP::Generator.print([:and, true, false])   #=> (and #t #f)

Documentation

Parsing SXP

Manipulating SXP

Generating SXP

Dependencies

  • Ruby (>= 2.6)
  • RDF.rb (~> 3.2), only needed for SPARQL S-expressions

Installation

The recommended installation method is via RubyGems. 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

Contributors

Contributing

This repository uses Git Flow 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 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 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 https://unlicense.org/ or the accompanying UNLICENSE file.