Skip to content

Latest commit

 

History

History
160 lines (141 loc) · 6.25 KB

README.md

File metadata and controls

160 lines (141 loc) · 6.25 KB

Resource Description Language (RDF) library implemented with the Rust programming language.

The library contains an implementation of a triple store and a large subset of the SPARQL 1.1 query language. There is not currently any support for serialization via XML or Turtle.

SPARQL support includes:

  • Nearly all the operators and functions.
  • User defined extension functions.
  • Pattern groups.
  • Optional, bind, filter, and order by clauses.
  • Distinct and limit modifiers.
  • Namespaces.

SPARQL support does not include:

  • Construct, describe, and ask queries.
  • Union patterns
  • Offset and having clauses.
  • As in select.
  • Aggregate expressions.
  • Path operators (i.e. ^, /, and |).
  • The regex and replace functions.
  • Hash functions.
  • Nil literals.

Here is a usage example:

import io::writer_util;
import rrdf::*;

// Creates a triple store and adds monsters to it.
fn monsters() -> Store
{
    // Namespaces allow subjects and predicates to be added to the 
    // store using a prefixed name instead of a full URL.
    let namespaces = ~[Namespace {prefix: ~"game", path: ~"http://game/ns#"}];
    
    // Vector of function name and function pointer tuples. These
    // represent user defined functions that may be called from
    // within SPARQL queries.
    let extensions = HashMap();
    
    // Create an empty triple store.
    let store = Store(namespaces, &extensions);
    
    // Start adding statements to the store. Individual triples may be added,
    // containers, aggregates, reified statements, and predicates/objects
    // associated with a subject (which is what we use here).
    store.add(~"game:snake", ~[
        (~"game:name", @StringValue(~"King Snake", ~"")),    // "" is for an optional language
        (~"game:min_level", @IntValue(1)),
        (~"game:max_level", @IntValue(5)),
        (~"game:weight", @IntValue(4)),                // relative probability
        (~"game:habitat", @StringValue(~"|land|water|", ~"")),
    ]);
    
    store.add(~"game:bear", ~[
        (~"game:name", @StringValue(~"Grizzly Bear", ~"")),
        (~"game:min_level", @IntValue(3)),
        (~"game:max_level", @IntValue(6)),
        (~"game:weight", @IntValue(3)),
        (~"game:habitat", @StringValue(~"|land|", ~"")),
    ]);
    
    store.add(~"game:naga", ~[
        (~"game:name", @StringValue(~"Naga Warrior", ~"")),
        (~"game:min_level", @IntValue(7)),
        (~"game:max_level", @IntValue(15)),
        (~"game:weight", @IntValue(2)),
        (~"game:habitat", @StringValue(~"|land|water|", ~"")),
    ]);
    
    store.add(~"game:shark", ~[
        (~"game:name", @StringValue(~"Hammerhead Shark", ~"")),
        (~"game:min_level", @IntValue(5)),
        (~"game:max_level", @IntValue(21)),
        (~"game:weight", @IntValue(1)),
        (~"game:habitat", @StringValue(~"|water|", ~"")),
    ]);
    
    store.add(~"game:mummy", ~[
        (~"game:name", @StringValue(~"Mummy", ~"")),
        (~"game:min_level", @IntValue(10)),
        (~"game:max_level", @IntValue(20)),
        (~"game:weight", @IntValue(2)),
        (~"game:habitat", @StringValue(~"|land|", ~"")),
    ]);
    
    store.add(~"game:lich", ~[
        (~"game:name", @StringValue(~"Lich", ~"")),
        (~"game:min_level", @IntValue(15)),
        (~"game:max_level", @IntValue(30)),
        (~"game:weight", @IntValue(3)),
        (~"game:habitat", @StringValue(~"|land|", ~"")),
        (~"game:announce", @StringValue(~"You feel a chill.", ~"")),
    ]);
    
    store.add(~"game:necromancer", ~[
        (~"game:name", @StringValue(~"Necromancer", ~"")),
        (~"game:min_level", @IntValue(20)),
        (~"game:max_level", @IntValue(30)),
        (~"game:weight", @IntValue(2)),
        (~"game:habitat", @StringValue(~"|land|", ~"")),
    ]);
    
    return store;
}

#[test]
fn query_monsters()
{
    // Return the names and weights for all land monsters allowed on level 20.
    // If the monster has an announcement then return that as well
    let expr = ~"PREFIX game: <http://game/ns#>
        SELECT
            ?name ?weight ?announcement
        WHERE
        {
            ?subject game:name ?name .
            ?subject game:weight ?weight .
            ?subject game:min_level ?min .
            ?subject game:max_level ?max .
            ?subject game:habitat ?habitat .
            OPTIONAL
            {
                ?subject game:announce ?announcement
            }
            FILTER (CONTAINS(?habitat, \"|land|\") && ?min <= 20 && 20 <= ?max)
        } ORDER BY ?name";
    
    // Parse the query expression and return a result with either a function
    // that will run the query against a store or parse error.
    let store = monsters();
    match compile(expr)
    {
        result::Ok(selector) =>
        {
            // Run the query function against the store. This will either return
            // a row for each monster that matched the query or an eval error.
            match selector(&store)
            {
                result::Ok(ref solution) =>
                {
                    // This will print:
                    // 0 name: "Lich", weight: 3, announcement: "You feel a chill."
                    // 1 name: "Mummy", weight: 2, announcement:  unbound
                    // 2 name: "Necromancer", weight: 2, announcement:  unbound
                    io::println(fmt!("%s", solution.to_str()));
                }
                result::Err(ref err) =>
                {
                    io::stderr().write_line(fmt!("Eval error: %s", *err));
                }
            }
        }
        result::Err(ref err) =>
        {
            io::stderr().write_line(fmt!("Parse error: expected %s", *err));
        }
    }
}