ソースを参照

Initial commit.

scossu 2 週間 前
コミット
c5fcc0dad2
16 ファイル変更1730 行追加0 行削除
  1. 42 0
      .gitignore
  2. 48 0
      Makefile
  3. 0 0
      build/.keep
  4. 0 0
      lib/.keep
  5. BIN
      lib/lsup.so
  6. 48 0
      lsup-scm-1.rockspec
  7. 60 0
      parse_valgrind_supp.sh
  8. 49 0
      scratch.lua
  9. 371 0
      src/lua_graph.c
  10. 19 0
      src/lua_lsup.c
  11. 43 0
      src/lua_lsup.h
  12. 182 0
      src/lua_namespace.c
  13. 595 0
      src/lua_term.c
  14. 187 0
      src/lua_triple.c
  15. 77 0
      src/stackdump.h
  16. 9 0
      valgrind-lua-noreadline.supp

+ 42 - 0
.gitignore

@@ -0,0 +1,42 @@
+# Compiled Lua sources
+luac.out
+
+# luarocks build files
+*.src.rock
+*.zip
+*.tar.gz
+
+# Object files
+*.o
+*.os
+*.ko
+*.obj
+*.elf
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+*.def
+*.exp
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+vgcore*

+ 48 - 0
Makefile

@@ -0,0 +1,48 @@
+PREFIX=/usr/local
+
+INCLUDE = -I/usr/local/include/lsup
+CFLAGS = -shared -DDEBUG -Og -ggdb -Wall -fPIC
+LDFLAGS = -L/usr/local/lib -llua -llsuprdf_dbg
+
+LUAC_SRC = $(wildcard src/*.c)
+#OBJ = $(patsubst src/%.c, lib/%.so, $(LUAC_SRC))
+OBJ = lsup.so
+OBJPATH = lib/$(OBJ)
+
+LUA_VER=5.4
+INSTALL_DIR=$(PREFIX)/lib/lua/$(LUA_VER)
+
+VALGRIND_LOG=/tmp/lua_lsup_valgrind.log
+
+.DEFAULT_GOAL := lib
+
+
+.PHONY: lib
+lib: $(OBJPATH)
+
+
+$(OBJPATH): src/*.c src/lua_lsup.h
+	$(CC) $(INCLUDE) $(CFLAGS) $(LDFLAGS) -o $@ src/*.c
+
+
+install: $(OBJPATH)
+	mkdir -p $(INSTALL_DIR)
+	cp $(OBJPATH) $(INSTALL_DIR)
+
+
+.PHONY: uninstall
+uninstall:
+	$(RM) $(INSTALL_DIR)/$(OBJ)
+
+
+.PHONY: clean
+clean:
+	$(RM) lib/*.so
+
+
+.PHONY: memcheck
+memcheck:
+	valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes \
+	--log-fd=9 --suppressions=valgrind-lua-noreadline.supp \
+	lua -e "dofile 'scratch.lua'" 9>|$(VALGRIND_LOG); \
+	echo "Log file in $(VALGRIND_LOG)"

+ 0 - 0
build/.keep


+ 0 - 0
lib/.keep


BIN
lib/lsup.so


+ 48 - 0
lsup-scm-1.rockspec

@@ -0,0 +1,48 @@
+package = "lsup"
+version = "scm-1"
+source = {
+   --url = "./"
+   url = "git://git.knowledgetx.com/scossu/lsup_rdf.git",
+   branch = "master",
+   tag = "HEAD",
+   --tag = "scm",
+}
+description = {
+   summary = "Compact, minimalistic RDF library and persistent store.",
+   detailed = [[
+      `lsup_rdf` is an embedded library to manipulate and permanently store
+      Linked Data. It handles terms, triples, graphs, and has an in-memory and
+      a filesystem-based storage back ends.
+   ]],
+   homepage = "http://git.knowledgetx.com/scossu/lsup_rdf",
+   license = "https://git.knowledgetx.com/scossu/lsup_rdf/src/master/LICENSE"
+}
+dependencies = {
+   "lua >= 5.4, < 6",
+}
+build = {
+   type = "make",
+   --build_target = "lua",
+   --install_target = "lua_install",
+   --[=[
+   modules = {
+      lsup = {
+         sources = {
+             "src/lua_*.c",
+             --[[
+             "src/lua_namespace.c",
+             "src/lua_term.c",
+             "src/lua_triple.c",
+             "src/lua_graph.c",
+             "src/lua_lsup.c",
+             --]]
+         },
+         --defines = {},
+         --libraries = {"lsuprdf_dbg"},
+         --incdirs = {"./include", "./ext/log/src", "./ext/hashmap"},
+         --libdirs = {}
+      }
+   },
+   --copy_directories = {}
+   --]=]
+}

+ 60 - 0
parse_valgrind_supp.sh

@@ -0,0 +1,60 @@
+#! /usr/bin/awk -f
+# Copyright: 2022 David Hart
+# Licence:   wxWindows licence
+#
+# A script to extract the actual suppression info from the output of (for example) valgrind --leak-check=full --show-reachable=yes --error-limit=no --gen-suppressions=all ./minimal
+# The desired bits are between ^{ and ^} (including the braces themselves).
+# The combined output should either be appended to /usr/lib/valgrind/default.supp, or placed in a .supp of its own
+# If the latter, either tell valgrind about it each time with --suppressions=<filename>, or add that line to ~/.valgrindrc
+
+# NB This script uses the |& operator, which I believe is gawk-specific. In case of failure, check that you're using gawk rather than some other awk
+
+# The script looks for suppressions. When it finds one it stores it temporarily in an array,
+# and also feeds it line by line to the external app 'md5sum' which generates a unique checksum for it.
+# The checksum is used as an index in a different array. If an item with that index already exists the suppression must be a duplicate and is discarded.
+
+BEGIN { suppression=0; md5sum = "md5sum" }
+  # If the line begins with '{', it's the start of a supression; so set the var and initialise things
+  /^{/  {
+           suppression=1;  i=0; next 
+        }
+  # If the line begins with '}' its the end of a suppression
+  /^}/  {
+          if (suppression)
+           { suppression=0;
+             close(md5sum, "to")  # We've finished sending data to md5sum, so close that part of the pipe
+             ProcessInput()       # Do the slightly-complicated stuff in functions
+             delete supparray     # We don't want subsequent suppressions to append to it!
+           }
+     }
+  # Otherwise, it's a normal line. If we're inside a supression, store it, and pipe it to md5sum. Otherwise it's cruft, so ignore it
+     { if (suppression)
+         { 
+            supparray[++i] = $0
+            print |& md5sum
+         }
+     }
+
+
+ function ProcessInput()
+ {
+    # Pipe the result from md5sum, then close it     
+    md5sum |& getline result
+    close(md5sum)
+    # gawk can't cope with enormous ints like $result would be, so stringify it first by prefixing a definite string
+    resultstring = "prefix"result
+
+    if (! (resultstring in chksum_array) )
+      { chksum_array[resultstring] = 0;  # This checksum hasn't been seen before, so add it to the array
+        OutputSuppression()              # and output the contents of the suppression
+      }
+ }
+
+ function OutputSuppression()
+ {
+  # A suppression is surrounded by '{' and '}'. Its data was stored line by line in the array  
+  print "{"  
+  for (n=1; n <= i; ++n)
+    { print supparray[n] }
+  print "}" 
+ }

+ 49 - 0
scratch.lua

@@ -0,0 +1,49 @@
+term = require "lsup.term"
+triple = require "lsup.triple"
+graph = require "lsup.graph"
+
+---[[
+t1 = term.new_bnode()
+t2 = term.new_iriref("urn:p:11")
+t3 = term.new_lit("123", "xsd:int")
+t4 = term.new_lit("Hola", nil, "es_ES")
+t5 = term.new_lit("مرحبا", nil, "ar_AR")
+
+trp1 = triple.new (
+    term.new_iriref("urn:s:1"),
+    term.new_iriref("urn:p:1"),
+    term.new_lit("hello", nil, "en_US"))
+
+triples = {
+    trp1,
+    triple.new (t1, t2, t3),
+    triple.new (t1, t2, t4),
+    triple.new (t1, t2, t5),
+}
+
+gr1 = graph.new()
+ct = gr1:add(triples)
+print("Triples added: " .. ct)
+gr2 = graph.new()
+for i in gr1:lookup() do print(i) end
+
+lm = gr1:connections(t1, term.LINK_OUTBOUND)
+print("Connections")
+for t1, ts in pairs(lm) do
+    for t2 in pairs(ts) do print(t1, t2) end
+end
+
+unique_t = gr1:unique_terms(triple.POS_S)
+print("Unique subjects")
+for t in pairs(unique_t) do print(t) end
+print("Unique predicates")
+unique_t = gr1:unique_terms(triple.POS_P)
+for t in pairs(unique_t) do print(t) end
+print("Unique objects")
+unique_t = gr1:unique_terms(triple.POS_O)
+for t in pairs(unique_t) do print(t) end
+
+print("Terms connected to t1 t2:")
+tset = gr1:term_set(t2, triple.POS_P, t1, triple.POS_S)
+for t in pairs(tset) do print(t) end
+--]]

+ 371 - 0
src/lua_graph.c

@@ -0,0 +1,371 @@
+#include "lua_lsup.h"
+
+#define check_graph(L) \
+    *(LSUP_Graph **)luaL_checkudata(L, 1, "LSUP.Graph")
+
+
+static LSUP_Graph **allocate_graph (lua_State *L)
+{
+    LSUP_Graph **gp = lua_newuserdatauv (L, sizeof (*gp), 1);
+    luaL_getmetatable (L, "LSUP.Graph");
+    lua_setmetatable (L, -2);
+
+    return gp;
+}
+
+
+static int l_graph_new (lua_State *L)
+{
+    const LSUP_StoreType store_type = lua_tointeger (L, 1);
+    const char *uri_str = lua_tostring (L, 2);
+
+    LSUP_Graph **gp = allocate_graph (L);
+
+    LSUP_Store *store = NULL;
+    if (store_type) {
+        const LSUP_StoreInt *sif = LSUP_store_int (store_type);
+        if (UNLIKELY (!sif)) return luaL_error (
+                L, "No interface defined for store type: %d.", store_type);
+
+        // TODO Move store creation fn and handle into a separate module.
+        store = LSUP_store_new (store_type, NULL, 0);
+        // Set up the store if a function for that is defined.
+        if (sif->setup_fn) {
+            if (sif->setup_fn(NULL, false) < LSUP_OK)
+                return luaL_error (L, "Error initializing back end store.");
+        }
+    }
+
+    // TODO Make store ID, nsm and initial size accessible.
+    *gp = LSUP_graph_new (store, uri_str, NULL);
+    LUA_NLCHECK (*gp, "Error creating graph.");
+
+    return 1;
+}
+
+
+/*
+ * Class methods.
+ */
+
+static int l_graph_gc (lua_State *L)
+{
+    LSUP_Graph *gr = check_graph (L);
+    if (gr) LSUP_graph_free (gr);
+
+    return 0;
+}
+
+
+static int l_graph_to_string (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    lua_pushfstring (
+            L, "LSUP.Graph @%p <%s>: %d triples",
+            gr, LSUP_graph_uri (gr)->data, LSUP_graph_size (gr));
+
+    return 1;
+}
+
+
+static int l_graph_len (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    lua_pushinteger (L, LSUP_graph_size (gr));
+
+    return 1;
+}
+
+
+static int l_graph_copy (lua_State *L)
+{
+    const LSUP_Graph *src = check_graph (L);
+    LSUP_Graph *dest = *(LSUP_Graph **)luaL_checkudata(L, 2, "LSUP.Graph");
+
+    LUA_PCHECK (LSUP_graph_copy (src, dest), "Error copying graph");
+
+    return 1;
+}
+
+
+static int l_graph_copy_contents (lua_State *L)
+{
+    const LSUP_Graph *src = check_graph (L);
+    LSUP_Graph *dest = *(LSUP_Graph **)luaL_checkudata (L, 2, "LSUP.Graph");
+    const LSUP_Term *s, *p, *o;
+
+    if lua_isnoneornil (L, 3) s = NULL;
+    else s = *(LSUP_Term **)luaL_checkudata (L, 3, "LSUP.Term");
+    if lua_isnoneornil (L, 4) p = NULL;
+    else p = *(LSUP_Term **)luaL_checkudata (L, 4, "LSUP.Term");
+    if lua_isnoneornil (L, 5) o = NULL;
+    else o = *(LSUP_Term **)luaL_checkudata (L, 5, "LSUP.Term");
+
+    LUA_PCHECK (LSUP_graph_copy_contents (
+                src, dest, s, p, o), "Error copying graph.");
+
+    return 1;
+}
+
+
+static int l_graph_equals (lua_State *L)
+{
+    const LSUP_Graph *gr1 = check_graph (L);
+    const LSUP_Graph *gr2 =
+        *(LSUP_Graph **)luaL_checkudata (L, 2, "LSUP.Graph");
+
+    LOG_DEBUG ("Comparing graphs %p %p", gr1, gr2);
+    int eq_rc = LSUP_graph_equals (gr1, gr2);
+    lua_pushboolean (L, eq_rc);
+
+    return 1;
+}
+
+
+static int l_graph_contains (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    const LSUP_Triple *spo =
+        *(LSUP_Triple **)luaL_checkudata (L, 2, "LSUP.Triple");
+
+    lua_pushboolean (L, LSUP_graph_contains (gr, spo));
+
+    return 1;
+}
+
+
+static int l_graph_add (lua_State *L)
+{
+    LSUP_Graph *gr = check_graph (L);
+    LOG_DEBUG ("Triples type: %s", lua_typename (L, lua_type (L, 2)));
+    int rc;
+    LSUP_rc lsup_rc= LSUP_NOACTION;
+    size_t i = 0, ct = 0;
+    LSUP_GraphIterator *it = LSUP_graph_add_init (gr);
+
+    while ((rc = lua_rawgeti (L, 2, ++i)) != LUA_TNIL) {
+        //LOG_DEBUG ("Triple type: %s", lua_typename (L, rc));
+        const LSUP_Triple *spo =
+            *(LSUP_Triple **)luaL_checkudata (L, -1, "LSUP.Triple");
+        LOG_DEBUG (
+                "Got triple %d: {%s %s %s}\n",
+                i, spo->s->data, spo->p->data, spo->o->data);
+        lsup_rc = LSUP_graph_add_iter (it, spo);
+
+        if (lsup_rc < LSUP_OK) break;
+        if (lsup_rc == LSUP_OK) ct++;
+    };
+    LSUP_graph_add_done (it);
+    lua_pushinteger (L, ct);
+
+    if (UNLIKELY (lsup_rc < LSUP_OK)) return luaL_error (
+            L, "Error adding triple at position %d: %s",
+            i, LSUP_strerror (lsup_rc));
+    else return 1;
+}
+
+
+static int graph_iter_next (lua_State *L)
+{
+    LSUP_GraphIterator *it =
+        *(LSUP_GraphIterator **)lua_touserdata (L, lua_upvalueindex (1));
+
+    LSUP_Triple **spo_p = lua_newuserdatauv (L, sizeof (*spo_p), 1);
+    luaL_getmetatable (L, "LSUP.Triple");
+    lua_setmetatable (L, -2);
+    *spo_p = NULL;
+
+    LSUP_rc rc = LSUP_graph_iter_next (it, spo_p);
+
+    if (rc == LSUP_END) {
+        lua_pushnil (L);
+        lua_pushstring (L, "End of lookup results.");
+        return 2;
+    }
+    LUA_PCHECK (rc, "Error retrieving a lookup result.");
+
+    return 1;
+}
+
+
+static int l_graph_lookup (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    const LSUP_Term *s, *p, *o;
+    if lua_isnoneornil (L, 2) s = NULL;
+    else s = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+    if lua_isnoneornil (L, 3) p = NULL;
+    else p = *(LSUP_Term **)luaL_checkudata (L, 3, "LSUP.Term");
+    if lua_isnoneornil (L, 4) o = NULL;
+    else o = *(LSUP_Term **)luaL_checkudata (L, 4, "LSUP.Term");
+
+    LSUP_GraphIterator **it_p =
+        (LSUP_GraphIterator **)lua_newuserdata (L, sizeof *it_p);
+    *it_p = NULL;
+    luaL_getmetatable (L, "LSUP.GraphIterator");
+    lua_setmetatable (L, -2);
+
+    size_t ct;
+    *it_p = LSUP_graph_lookup (gr, s, p, o, &ct);
+    LUA_NLCHECK (*it_p, "Error creating graph iterator.");
+    LOG_DEBUG ("Found triples: %d", ct);
+
+    lua_pushcclosure (L, graph_iter_next, 1);
+
+    return 1;
+}
+
+
+static int graph_iter_gc (lua_State *L)
+{
+    LSUP_GraphIterator **it_p = lua_touserdata (L, 1);
+
+    if (UNLIKELY (!it_p || *it_p)) return 0;
+
+    LSUP_graph_iter_free (*it_p);
+    *it_p = NULL;
+
+    return 0;
+}
+
+
+/** Returns a LinkMap that can be iterated over with iter().
+ */
+static int l_graph_connections (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    LSUP_Term *t = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+    const LSUP_LinkType type = luaL_checkinteger (L, 3);
+    LOG_DEBUG ("Adding term for connections: @%p", *t);
+
+    LSUP_LinkMap **lm_p = lua_newuserdata (L, sizeof *lm_p);
+    *lm_p = LSUP_graph_connections (gr, t, type);
+    luaL_getmetatable (L, "LSUP.LinkMap");
+    lua_setmetatable (L, -2);
+
+    return 1;
+}
+
+
+static int l_graph_term_set (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    const LSUP_Term *t1 = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+    const LSUP_TriplePos t1_pos = luaL_checkinteger (L, 3);
+    const LSUP_Term *t2 = *(LSUP_Term **)luaL_checkudata (L, 4, "LSUP.Term");
+    const LSUP_TriplePos t2_pos = luaL_checkinteger (L, 5);
+
+    LSUP_TermSet **ts_p = lua_newuserdata (L, sizeof *ts_p);
+    luaL_getmetatable (L, "LSUP.TermSet");
+    lua_setmetatable (L, -2);
+
+    *ts_p = LSUP_graph_term_set (gr, t1, t1_pos, t2, t2_pos);
+    LUA_NLCHECK (*ts_p, "Error creating term set.");
+
+    return 1;
+}
+
+
+static int l_graph_unique_terms (lua_State *L)
+{
+    const LSUP_Graph *gr = check_graph (L);
+    const LSUP_TriplePos pos = luaL_checkinteger (L, 2);
+
+    LSUP_TermSet **ts_p = lua_newuserdata (L, sizeof *ts_p);
+    luaL_getmetatable (L, "LSUP.TermSet");
+    lua_setmetatable (L, -2);
+
+    *ts_p = LSUP_graph_unique_terms (gr, pos);
+    LUA_NLCHECK (*ts_p, "Error creating term set.");
+
+    return 1;
+}
+
+
+/*
+ * Library setup.
+ */
+
+static const luaL_Reg graph_lib_fn [] = {
+    {"new", l_graph_new},
+
+    {NULL}
+};
+
+
+/*
+static const luaL_Reg graph_getters [] = {
+    {"uri", l_graph_get_uri},
+    {"namespace", l_graph_get_nsm},
+
+    {NULL}
+};
+*/
+
+
+/*
+static const luaL_Reg graph_setters [] = {
+    {"uri", l_graph_set_uri},
+
+    {NULL}
+};
+*/
+
+static const luaL_Reg graph_lib_meth [] = {
+    {"__eq", l_graph_equals},
+    {"__gc", l_graph_gc},
+    //{"__index", get_attr},
+    //{"__newindex", set_attr},
+    {"__tostring", l_graph_to_string},
+    {"__len", l_graph_len},
+
+    {"copy", l_graph_copy},
+    {"copy_contents", l_graph_copy_contents},
+    {"contains", l_graph_contains},
+
+    {"add", l_graph_add},
+    {"lookup", l_graph_lookup},
+    {"connections", l_graph_connections},
+    {"term_set", l_graph_term_set},
+    {"unique_terms", l_graph_unique_terms},
+
+    //{"to_n3", l_graph_to_n3},
+    {NULL}
+};
+
+
+static const LEnumConst graph_enums[] = {
+    {NULL, 0}
+};
+
+
+int luaopen_lsup_graph (lua_State *L)
+{
+    LSUP_init();  // This is idempotent: no problem calling it multiple times.
+    luaL_newmetatable (L, "LSUP.Graph");
+    lua_pushvalue (L, -1);
+    lua_setfield (L, -2, "__index");
+    luaL_setfuncs (L, graph_lib_meth, 0);
+
+    // Metatables for ancillary types.
+    luaL_newmetatable (L, "LSUP.GraphIterator");
+    lua_pushcfunction (L, graph_iter_gc);
+    lua_setfield (L, -2, "__gc");
+
+    /*
+    // Getters table.
+    lua_newtable (L);
+    for (int i = 0; graph_getters[i].name != NULL; i++) {
+        lua_pushcfunction (L, graph_getters[i].func);
+        lua_setfield (L, -2, graph_getters[i].name);
+    }
+    // Set getters table as a value for the Graph metatable.
+    lua_setfield (L, -2, "getters");
+    */
+    luaL_newlib (L, graph_lib_fn);
+
+    // Module-level constants.
+    push_int_const (L, graph_enums);
+
+    return 1;
+}

+ 19 - 0
src/lua_lsup.c

@@ -0,0 +1,19 @@
+#include "lua_lsup.h"
+
+void push_int_const (lua_State *L, const LEnumConst *list) {
+    for (int i = 0; list[i].k; i++) {
+        lua_pushinteger (L, list[i].v);
+        lua_setfield (L, -2, list[i].k);
+    };
+}
+
+
+void push_string_const (lua_State *L, const LStringConst *list) {
+    for (int i = 0; list[i].k; i++) {
+        lua_pushstring (L, list[i].v);
+        lua_setfield (L, -2, list[i].k);
+    };
+}
+
+
+

+ 43 - 0
src/lua_lsup.h

@@ -0,0 +1,43 @@
+#include <lua.h>
+#include <lauxlib.h>
+
+#include "lsup_rdf.h"
+
+#ifndef _LUA_LSUP_H
+#define _LUA_LSUP_H
+
+/// Raise Lua error including LSUP error message on negative rc.
+#define LUA_PCHECK(exp, message) do {\
+    if (UNLIKELY ((exp) < LSUP_OK)) \
+        return luaL_error (L, "%s: %s", message, LSUP_strerror (exp)); \
+} while (0)
+
+#define LUA_NLCHECK(exp, ...) do {\
+    if (UNLIKELY ((exp) == NULL)) return luaL_error (L, __VA_ARGS__); \
+} while (0)
+
+
+/// Enum (int) constants to be passed to the module.
+typedef struct l_enum_const {
+    const char  *k;
+    const int   v;
+} LEnumConst;
+
+
+/// String constants to be passed to the module.
+typedef struct l_string_const {
+    const char  *k;
+    const char  *v;
+} LStringConst;
+
+
+void push_int_const (lua_State *L, const LEnumConst *list);
+void push_string_const (lua_State *L, const LStringConst *list);
+
+/// Create new namespace map.
+int l_nsmap_new (lua_State *L);
+
+/// Allocate space for a term object.
+LSUP_Term **allocate_term (lua_State *L);
+
+#endif  // _LUA_LSUP_H

+ 182 - 0
src/lua_namespace.c

@@ -0,0 +1,182 @@
+#include "lua_lsup.h"
+
+#define check_nsm(L) \
+    *(LSUP_NSMap **)luaL_checkudata(L, 1, "LSUP.NSMap")
+
+
+/*
+ * Factory methods.
+ */
+
+int l_nsmap_new (lua_State *L)
+{
+    LSUP_NSMap **nsm_p = lua_newuserdatauv (L, sizeof (*nsm_p), 1);
+    luaL_getmetatable (L, "LSUP.NSMap");
+    lua_setmetatable (L, -2);
+
+    *nsm_p = LSUP_nsmap_new ();
+    if (!*nsm_p) return luaL_error (L, "Error while creating a term!");
+
+    return 1;
+}
+
+
+/*
+ * Class methods.
+ */
+
+static int l_nsmap_gc (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    if (nsm) LSUP_nsmap_free (nsm);
+
+    return 0;
+}
+
+
+static int l_nsmap_add (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char
+        *pfx = luaL_checkstring (L, 2),
+        *nsstr = luaL_checkstring (L, 3);
+
+    LUA_PCHECK (
+            LSUP_nsmap_add (nsm, pfx, nsstr),
+            "Error adding member to NS map");
+
+    return 0;
+}
+
+
+static int l_nsmap_remove (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char *pfx = luaL_checkstring (L, 2);
+
+    LUA_PCHECK (
+            LSUP_nsmap_remove (nsm, pfx),
+            "Error removing member to NS map");
+
+    return 0;
+}
+
+
+static int l_nsmap_get_ns (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char *pfx = luaL_checkstring (L, 2);
+
+    const char *ns = LSUP_nsmap_get_ns (nsm, pfx);
+
+    if (ns) lua_pushstring (L, ns);
+    else return 0;
+
+    return 1;
+}
+
+
+static int l_nsmap_get_pfx (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char *ns = luaL_checkstring (L, 2);
+
+    const char *pfx = LSUP_nsmap_get_pfx (nsm, ns);
+
+    if (pfx) lua_pushstring (L, pfx);
+    else lua_pushnil (L);
+
+    return 1;
+}
+
+
+static int l_nsmap_normalize_uri (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char *pfx_uri = luaL_checkstring (L, 2);
+
+    char *fq_uri;
+    LUA_PCHECK (
+            LSUP_nsmap_normalize_uri (nsm, pfx_uri, &fq_uri),
+            "Error normalizing URI");
+
+    if (fq_uri) lua_pushstring (L, fq_uri);
+    else return 0;
+
+    free (fq_uri);
+    return 1;
+}
+
+
+static int l_nsmap_denormalize_uri (lua_State *L)
+{
+    LSUP_NSMap *nsm = check_nsm (L);
+    const char *fq_uri = luaL_checkstring (L, 2);
+
+    char *pfx_uri;
+    LUA_PCHECK (
+            LSUP_nsmap_denormalize_uri (nsm, fq_uri, &pfx_uri),
+            "Error denormalizing URI");
+
+    if (pfx_uri) lua_pushstring (L, pfx_uri);
+    else return 0;
+
+    free (pfx_uri);
+    return 1;
+}
+
+
+/*
+static int l_nsmap_iter_next (lua_State *L)
+{
+    // TODO
+    return 0;
+}
+*/
+
+
+static int l_nsmap_iter (lua_State *L)
+{
+    //LSUP_NSMap *nsm = check_nsm (L);
+    // TODO
+
+    return 1;
+}
+
+
+/*
+ * Library setup.
+ */
+
+static const luaL_Reg nsmap_lib_fn [] = {
+    {"new", l_nsmap_new},
+    {NULL}
+};
+
+
+static const luaL_Reg nsmap_lib_meth [] = {
+    {"__gc", l_nsmap_gc},
+    {"__pairs", l_nsmap_iter},
+
+    {"add", l_nsmap_add},
+    {"remove", l_nsmap_remove},
+    {"get_ns", l_nsmap_get_ns},
+    {"get_pfx", l_nsmap_get_pfx},
+    {"normalize_uri", l_nsmap_normalize_uri},
+    {"denormalize_uri", l_nsmap_denormalize_uri},
+    //{"iter", l_nsmap_iter},
+
+    {NULL}
+};
+
+
+int luaopen_lsup_namespace (lua_State *L)
+{
+    luaL_newmetatable (L, "LSUP.NSMap");
+    lua_pushvalue (L, -1);
+    lua_setfield (L, -2, "__index");
+    luaL_setfuncs (L, nsmap_lib_meth, 0);
+    luaL_newlib (L, nsmap_lib_fn);
+
+    return 1;
+}

+ 595 - 0
src/lua_term.c

@@ -0,0 +1,595 @@
+#include "lua_lsup.h"
+#include "stackdump.h"
+
+#define check_term(L) \
+    *(LSUP_Term **)luaL_checkudata(L, 1, "LSUP.Term")
+
+
+LSUP_Term **allocate_term (lua_State *L)
+{
+    LSUP_Term **tp = lua_newuserdatauv (L, sizeof (*tp), 1);
+    luaL_getmetatable (L, "LSUP.Term");
+    lua_setmetatable (L, -2);
+
+    return tp;
+}
+
+
+LSUP_TermSet **allocate_tset (lua_State *L)
+{
+    LSUP_TermSet **ts_p = lua_newuserdatauv (L, sizeof (*ts_p), 1);
+    luaL_getmetatable (L, "LSUP.TermSet");
+    lua_setmetatable (L, -2);
+
+    return ts_p;
+}
+
+
+/*
+ * Factory methods.
+ */
+
+static int l_new_iriref (lua_State *L)
+{
+    const char *data = luaL_checkstring (L, 1);
+    // TODO handle nsm.
+    LSUP_NSMap *nsm = lua_touserdata (L, 2);
+
+    LSUP_Term **tp = allocate_term (L);
+
+    *tp = LSUP_iriref_new (data, nsm);
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+static int l_new_iriref_abs (lua_State *L)
+{
+    LSUP_Term
+        *root = check_term (L),
+        *iri = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+
+    LSUP_Term **tp = allocate_term (L);
+
+    *tp = LSUP_iriref_absolute (root, iri);
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+static int l_new_iriref_rel (lua_State *L)
+{
+    LSUP_Term
+        *root = check_term (L),
+        *iri = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+
+    LSUP_Term **tp = allocate_term (L);
+
+    *tp = LSUP_iriref_relative (root, iri);
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+/** @brief create a new literal.
+ *
+ * Argument 2 (data type) and 3 (lang) are exclusive. If both are specified,
+ * datatype has precedence. If both are nil, datatype is set to the default
+ * (xsd:string).
+ */
+static int l_new_lit (lua_State *L)
+{
+    const char
+        *data = luaL_checkstring (L, 1),
+        *dtype_str = lua_tostring (L, 2);
+    const char *lang = lua_tostring (L, 3);
+
+    LSUP_Term **tp = allocate_term (L);
+    LSUP_Term *dtype;
+
+    if (dtype_str) {
+        // TODO check memory leak.
+        dtype = LSUP_iriref_new (dtype_str, LSUP_default_nsm);
+        *tp = LSUP_literal_new (data, dtype);
+    }
+    else if (lang) *tp = LSUP_lt_literal_new (data, (char *)lang);
+    else *tp = LSUP_literal_new (data, NULL);
+
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+static int l_new_bnode (lua_State *L)
+{
+    const char *data = lua_tostring (L, 1);
+
+    LSUP_Term **tp = allocate_term (L);
+
+    *tp = LSUP_bnode_new (data);
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+static int l_new_copy (lua_State *L)
+{
+    LSUP_Term *src = check_term (L);
+
+    LSUP_Term **tp = allocate_term (L);
+
+    *tp = LSUP_term_copy (src);
+    LUA_NLCHECK (*tp, "Error creating term.");
+
+    return 1;
+}
+
+
+/*
+ * Class methods.
+ */
+static int l_term_equals (lua_State *L)
+{
+    LSUP_Term
+        *t1 = check_term (L),
+        *t2 = *(LSUP_Term **)luaL_checkudata (L, 2, "LSUP.Term");
+
+    lua_pushboolean (L, LSUP_term_equals (t1, t2));
+
+    return 1;
+}
+
+
+static int l_term_gc (lua_State *L)
+{
+    LSUP_Term **tp = luaL_checkudata(L, 1, "LSUP.Term");
+    LOG_TRACE ("Garbage collecting term @%p", *tp);
+
+    LSUP_term_free (*tp);
+    *tp = NULL;
+
+    return 0;
+}
+
+
+/*
+static int l_term_to_string (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    char *nt_term = NULL;
+
+    CHECK (nt_codec.encode_term (t, NULL, &nt_term), fail);
+    lua_pushfstring (L, "LSUP.Term @%p %s", t, nt_term);
+
+    return 1;
+
+fail:
+    if (nt_term) free (nt_term);
+    return luaL_error (L, "Error encoding term for display!");
+}
+*/
+
+
+static int l_term_to_n3 (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    char *nt_term = NULL;
+
+    CHECK (nt_codec.encode_term (t, NULL, &nt_term), fail);
+    lua_pushstring (L, nt_term);
+    free (nt_term);
+
+    return 1;
+
+fail:
+    if (nt_term) free (nt_term);
+    return luaL_error (L, "Error encoding term for display.");
+}
+
+
+/*
+ * Getters.
+ */
+
+// Forward declaration.
+static const luaL_Reg term_getters [];
+
+
+static int l_term_get_attr (lua_State *L)
+{
+    const char *attr = luaL_checkstring (L, 2);
+    //printf ("Got attr: %s\n", attr);
+
+    if (luaL_getmetafield (L, 1, attr) != LUA_TNIL)
+        return 1;  // Return metamethod to be called as a function.
+
+    luaL_getmetafield (L, 1, "getters");
+    if (lua_getfield (L, -1, attr) != LUA_TNIL)
+        return lua_tocfunction (L, -1)(L);
+
+    return 0;
+}
+
+
+static int get_data (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    lua_pushstring (L, t->data);
+
+    return 1;
+}
+
+
+static int get_type (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    lua_pushinteger (L, t->type);
+
+    return 1;
+}
+
+
+static int get_iriref_nsm (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    // TODO
+    lua_pushlightuserdata (L, LSUP_iriref_nsm (t));
+
+    return 1;
+}
+
+
+static int get_iriref_prefix (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    lua_pushstring (L, LSUP_iriref_prefix (t));
+
+    return 1;
+}
+
+
+static int get_iriref_path (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    lua_pushstring (L, LSUP_iriref_path (t));
+
+    return 1;
+}
+
+
+static int get_iriref_frag (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    lua_pushstring (L, LSUP_iriref_frag (t));
+
+    return 1;
+}
+
+
+static int get_lit_datatype (lua_State *L)
+{
+    printf ("Getting datatype.\n");
+    LSUP_Term *t = check_term (L);
+    if (!LSUP_IS_LITERAL (t))
+        return luaL_error (L, "Term is not a literal.", 2);
+
+    lua_pushstring (L, t->datatype->data);
+
+    return 1;
+}
+
+
+static int get_lit_lang (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+    if (!LSUP_IS_LITERAL (t))
+        return luaL_error (L, "Term is not a literal.", 2);
+
+    lua_pushstring (L, t->lang);
+
+    return 1;
+}
+
+
+static int get_hash (lua_State *L)
+{
+    LSUP_Term *t = check_term (L);
+
+    lua_pushinteger (L, LSUP_term_hash (t));
+
+    return 1;
+}
+
+
+/*
+ * Setters.
+ */
+
+// Very simple for now.
+//static const luaL_Reg term_setters [];
+static int l_term_set_attr (lua_State *L)
+{ return luaL_error (L, "Direct setting is not allowed for this type.", 2); }
+
+
+/*
+ * Ancillary types
+ */
+
+/*
+ * Term set.
+ */
+
+static int term_set_iter_next (lua_State *L)
+{
+    LSUP_TermSet *ts =
+        *(LSUP_TermSet **)lua_touserdata (L, lua_upvalueindex (1));
+    size_t *ip = lua_touserdata (L, lua_upvalueindex (2));
+
+    LSUP_Term *tmp;
+    LSUP_rc rc = LSUP_term_set_next (ts, ip, &tmp);
+
+    LUA_PCHECK (rc, "Error iterating over term set");
+
+    if (rc == LSUP_END) {
+        free (ip);
+        return 0;
+    }
+
+    LSUP_Term **tp = allocate_term (L);
+    *tp = LSUP_term_copy (tmp);
+    if (!*tp) {
+        free (ip);
+        luaL_error (L, "Error allocating term.");
+    }
+
+    return 1;
+}
+
+
+static int l_term_set_iter_init (lua_State *L)
+{
+    size_t *ip = malloc (sizeof (*ip));
+    LUA_NLCHECK (ip, "Error allocating tset iterator.");
+    *ip = 0;
+
+    lua_pushlightuserdata (L, ip);
+    STACK_DUMP(L, "Before pushing tset next closure");
+    lua_pushcclosure (L, term_set_iter_next, 2);
+
+    return 1;
+}
+
+
+static int term_set_gc (lua_State *L)
+{
+    LSUP_TermSet **ts_p = lua_touserdata (L, 1);
+    LSUP_term_set_free (*ts_p);
+    *ts_p = NULL;
+
+    return 0;
+}
+
+
+/*
+ * Link map.
+ */
+
+static int lmap_iter_next (lua_State *L)
+{
+    LSUP_LinkMapIterator *it =
+        *(LSUP_LinkMapIterator **)lua_touserdata (L, lua_upvalueindex (1));
+
+    LSUP_Term **link_p = (LSUP_Term **)lua_newuserdata (L, sizeof *link_p);
+    *link_p = NULL;
+    luaL_getmetatable (L, "LSUP.Term");
+    lua_setmetatable (L, -2);
+
+    LSUP_TermSet *ts = NULL;
+    LSUP_Term *tmp = NULL;
+    LSUP_rc rc = LSUP_link_map_next (it, &tmp, &ts);
+
+    if (rc != LSUP_OK) {
+        if (rc == LSUP_END) return 0;
+        else LUA_PCHECK (rc, "Error iterating over link map");
+    }
+    *link_p = LSUP_term_copy (tmp);
+    LUA_NLCHECK (*link_p, "Error allocating term.");
+
+    size_t i = 0;
+    tmp = NULL;
+
+    lua_newtable (L);
+    while ((rc = LSUP_term_set_next (ts, &i, &tmp)) == LSUP_OK) {
+        LSUP_Term **t2_p = (LSUP_Term **)lua_newuserdata (L, sizeof *t2_p);
+        luaL_getmetatable (L, "LSUP.Term");
+        lua_setmetatable (L, -2);
+        *t2_p = LSUP_term_copy (tmp);
+
+        lua_pushboolean (L, true);
+        lua_rawset (L, -3);
+    }
+    LUA_PCHECK (rc, "Error iterating over term set");
+
+    // linked term + term set.
+    return 2;
+}
+
+
+/**
+ * Internally this function creates a LMapIterator, which is used as the
+ * upvalue for lmap_iter_next(). The iterator is garbage collected at the end
+ * of the iteration loop, the link map can be reused.
+ */
+static int l_lmap_iter_init (lua_State *L)
+{
+    STACK_DUMP (L, "beginning of LMap iter init fn");
+    LSUP_LinkMap *lm = *(LSUP_LinkMap **)luaL_checkudata(L, 1, "LSUP.LinkMap");
+
+    LSUP_LinkMapIterator **lmit_p =
+        (LSUP_LinkMapIterator  **)lua_newuserdata (L, sizeof *lmit_p);
+    *lmit_p = LSUP_link_map_iter_new (lm);
+    luaL_getmetatable (L, "LSUP.LMapIterator");
+    lua_setmetatable (L, -2);
+
+    lua_pushcclosure (L, lmap_iter_next, 1);
+    STACK_DUMP (L, "After pushing iter closure");
+
+    return 1;
+}
+
+
+static int link_map_gc (lua_State *L)
+{
+    LSUP_LinkMap **lm_p = lua_touserdata (L, 1);
+    LOG_DEBUG ("Garbage collecting link map @%p", *lm_p);
+    // FIXME This is to prevent a double-free on shutdown.
+    // Must find the culprit instead.
+    if (UNLIKELY (!lm_p || !*lm_p)) return 0;
+
+    LSUP_link_map_free (*lm_p);
+    *lm_p = NULL;
+
+    return 0;
+}
+
+
+static int lmap_iter_gc (lua_State *L)
+{
+    LSUP_LinkMapIterator *it = *(LSUP_LinkMapIterator **)lua_touserdata (L, 1);
+    LOG_DEBUG ("Garbage collecting link map iterator @%p", it);
+    LSUP_link_map_iter_free (it);
+
+    return 0;
+}
+
+
+/*
+ * Library setup.
+ */
+
+static const luaL_Reg term_lib_fn [] = {
+    {"new_iriref", l_new_iriref},
+    {"new_iriref_abs", l_new_iriref_abs},
+    {"new_iriref_rel", l_new_iriref_rel},
+    {"new_lit", l_new_lit},
+    {"new_bnode", l_new_bnode},
+    {"new_copy", l_new_copy},
+
+    {NULL}
+};
+
+
+static const luaL_Reg term_getters [] = {
+    // General getters.
+    {"data", get_data},
+    {"type", get_type},
+
+    // IRIRef getters.
+    {"nsm", get_iriref_nsm},
+    {"prefix", get_iriref_prefix},
+    {"path", get_iriref_path},
+    {"frag", get_iriref_frag},
+
+    // Literal getters.
+    {"datatype", get_lit_datatype},
+    {"lang", get_lit_lang},
+
+    // Generic getters.
+    {"hash", get_hash},
+
+    {NULL}
+};
+
+
+/*
+static const luaL_Reg term_setters [] = {
+    {NULL}
+};
+*/
+
+static const luaL_Reg term_lib_meth [] = {
+    {"__eq", l_term_equals},
+    {"__gc", l_term_gc},
+    {"__index", l_term_get_attr},
+    {"__tostring", l_term_to_n3},
+    {"__newindex", l_term_set_attr},
+
+    {NULL}
+};
+
+
+static const LEnumConst term_enums[] = {
+    {"TYPE_IRIREF", LSUP_TERM_IRIREF},
+    {"TYPE_NS_IRIREF", LSUP_TERM_NS_IRIREF},
+    {"TYPE_LITERAL", LSUP_TERM_LITERAL},
+    {"TYPE_LT_LITERAL", LSUP_TERM_LT_LITERAL},
+    {"TYPE_BNODE", LSUP_TERM_BNODE},
+
+    {"LINK_INBOUND", LSUP_LINK_INBOUND},
+    {"LINK_OUTBOUND", LSUP_LINK_OUTBOUND},
+    {"LINK_EDGE", LSUP_LINK_EDGE},
+
+    {NULL, 0}
+};
+
+
+static const LStringConst term_strings[] = {
+    {"RDF_TYPE", LSUP_RDF_TYPE},
+    {"RDF_TYPE_NS", LSUP_RDF_TYPE_NS},
+    {"DEFAULT_DTYPE", DEFAULT_DTYPE},
+    {"DEFAULT_DTYPE_NS", DEFAULT_DTYPE_NS},
+
+    {NULL, 0}
+};
+
+
+int luaopen_lsup_term (lua_State *L)
+{
+    LSUP_init();  // This is idempotent: no problem calling it multiple times.
+    luaL_newmetatable (L, "LSUP.Term");
+    luaL_setfuncs (L, term_lib_meth, 0);
+
+    // Getters table.
+    lua_newtable (L);
+    for (int i = 0; term_getters[i].name != NULL; i++) {
+        lua_pushcfunction (L, term_getters[i].func);
+        lua_setfield (L, -2, term_getters[i].name);
+    }
+    // Set getters table as a value for the Term metatable.
+    lua_setfield (L, -2, "getters");
+
+    /*
+     * Metatables for ancillary types.
+     */
+    // Term set.
+    luaL_newmetatable (L, "LSUP.TermSet");
+    lua_pushcfunction (L, term_set_gc);
+    lua_setfield (L, -2, "__gc");
+    lua_pushcfunction (L, l_term_set_iter_init);
+    lua_setfield (L, -2, "__pairs");
+
+    // Link map.
+    luaL_newmetatable (L, "LSUP.LinkMap");
+    lua_pushcfunction (L, link_map_gc);
+    lua_setfield (L, -2, "__gc");
+    lua_pushcfunction (L, l_lmap_iter_init);
+    lua_setfield (L, -2, "__pairs");
+
+    // Link map iterator.
+    luaL_newmetatable (L, "LSUP.LMapIterator");
+    lua_pushcfunction (L, lmap_iter_gc);
+    lua_setfield (L, -2, "__gc");
+
+    luaL_newlib (L, term_lib_fn);
+
+    // Module-level constants.
+    push_string_const (L, term_strings);
+    push_int_const (L, term_enums);
+
+    return 1;
+}

+ 187 - 0
src/lua_triple.c

@@ -0,0 +1,187 @@
+#include "lua_lsup.h"
+
+#define check_triple(L) \
+    *(LSUP_Triple **)luaL_checkudata(L, 1, "LSUP.Triple")
+
+
+/*
+ * Factory methods.
+ */
+
+static int l_triple_new (lua_State *L)
+{
+    LSUP_Triple **trp_p = lua_newuserdatauv (L, sizeof (*trp_p), 1);
+    luaL_getmetatable (L, "LSUP.Triple");
+    lua_setmetatable (L, -2);
+    LSUP_Term
+        *s = *(LSUP_Term **)luaL_checkudata(L, 1, "LSUP.Term"),
+        *p = *(LSUP_Term **)luaL_checkudata(L, 2, "LSUP.Term"),
+        *o = *(LSUP_Term **)luaL_checkudata(L, 3, "LSUP.Term");
+
+    *trp_p = LSUP_triple_new (
+            LSUP_term_copy (s),
+            LSUP_term_copy (p),
+            LSUP_term_copy (o));
+    if (!*trp_p) return luaL_error (L, "Error while creating a triple!");
+
+    return 1;
+}
+
+
+/*
+ * Class methods.
+ */
+
+static int l_triple_gc (lua_State *L)
+{
+    LSUP_Triple *trp = check_triple (L);
+    if (trp) LSUP_triple_free (trp);
+
+    return 0;
+}
+
+
+/** @brief Get information about a triple's term.
+ *
+ * Note that this is only a visual representation, as terms cannot be modified
+ * outside of their containing triple (unlike in the C library).
+ *
+ * @FIXME this doesn't allow chaining access methods, e.g. trp.s.data
+ *
+ * To get an independent copy of a triple term, use triple.copy_term().
+ */
+static int l_triple_get_term (lua_State *L)
+{
+    const LSUP_Triple *trp = check_triple (L);
+    const char pos = luaL_checkstring (L, 2)[0];
+    LSUP_TriplePos pn;
+    if (pos == 's') pn = TRP_POS_S;
+    else if (pos == 'p') pn = TRP_POS_P;
+    else if (pos == 'o') pn = TRP_POS_O;
+    else return luaL_error(L, "Out of range position: %c.", pos);
+
+    LSUP_Term *t = LSUP_triple_pos (trp, pn);
+    char *nt_term = NULL;
+    CHECK (nt_codec.encode_term (t, NULL, &nt_term), fail);
+    lua_pushfstring (L, "LSUP.Term @ %p %s", t, nt_term);
+
+    return 1;
+
+fail:
+    if (nt_term) free (nt_term);
+    return luaL_error (L, "Error encoding triple term for display!");
+}
+
+
+static int l_triple_set_term (lua_State *L)
+{
+    LSUP_Triple *trp = check_triple (L);
+    const char pos = luaL_checkstring (L, 2)[0];
+    const LSUP_Term *t = *(LSUP_Term **)luaL_checkudata(L, 3, "LSUP.Term");
+
+    LSUP_Term *new_t = LSUP_term_copy (t);
+
+    if (pos == 's') {
+        LSUP_term_free (trp->s);
+        trp->s = new_t;
+    } else if (pos == 'p') {
+        LSUP_term_free (trp->p);
+        trp->p = new_t;
+    } else if (pos == 'o') {
+        LSUP_term_free (trp->o);
+        trp->o = new_t;
+    } else return luaL_error(L, "Out of range position: %c.", pos);
+
+    return 1;
+}
+
+
+static int l_triple_copy_term (lua_State *L)
+{
+    const LSUP_Triple *trp = check_triple (L);
+    const char pos = luaL_checkstring (L, 2)[0];
+    LSUP_TriplePos pn;
+    if (pos == 's') pn = TRP_POS_S;
+    else if (pos == 'p') pn = TRP_POS_P;
+    else if (pos == 'o') pn = TRP_POS_O;
+    else return luaL_error(L, "Copy: Out of range position: %c.", pos);
+
+    LSUP_Term **tp = allocate_term (L);
+    *tp = LSUP_term_copy (LSUP_triple_pos (trp, pn));
+    if (!*tp) return luaL_error (L, "Error while copying a triple term!");
+
+    return 1;
+}
+
+
+static int l_triple_to_n3 (lua_State *L)
+{
+    const LSUP_Triple *trp = check_triple (L);
+    char *nt_term = NULL;
+
+    for (int i = TRP_POS_S; i <= TRP_POS_O; i++) {
+        CHECK (nt_codec.encode_term (
+                    LSUP_triple_pos (trp, i), NULL, &nt_term), fail);
+        lua_pushstring (L, nt_term);
+        lua_pushstring (L, " ");
+    }
+    free (nt_term);
+    lua_pushstring (L, ".");  // N3 statement termination.
+    lua_concat (L, 7);
+
+    return 1;
+
+fail:
+    if (nt_term) free (nt_term);
+    return luaL_error (L, "Error encoding triple term.");
+}
+
+
+/*
+ * Library setup.
+ */
+
+static const struct luaL_Reg triple_lib_fn [] = {
+    {"new", l_triple_new},
+    {NULL}
+};
+
+
+static const struct luaL_Reg triple_lib_meth [] = {
+    {"__gc", l_triple_gc},
+    {"__index", l_triple_get_term},
+    {"__newindex", l_triple_set_term},
+    {"__tostring", l_triple_to_n3},
+
+    {"copy_term", l_triple_copy_term},
+
+    {NULL}
+};
+
+
+static const LEnumConst triple_enums[] = {
+    {"POS_S", TRP_POS_S},
+    {"POS_P", TRP_POS_P},
+    {"POS_O", TRP_POS_O},
+    {NULL, 0}
+};
+
+
+int luaopen_lsup_triple (lua_State *L)
+{
+    LSUP_init();  // This is idempotent: no problem calling it multiple times.
+    luaL_newmetatable (L, "LSUP.Triple");
+    lua_pushvalue (L, -1);
+
+    // MT
+    lua_setfield (L, -1, "__index");
+    luaL_setfuncs (L, triple_lib_meth, 0);
+
+    luaL_newlib (L, triple_lib_fn);
+
+    // Module-level constants.
+    push_int_const (L, triple_enums);
+
+    return 1;
+}
+

+ 77 - 0
src/stackdump.h

@@ -0,0 +1,77 @@
+#ifndef _STACK_DUMP_H
+#define _STACK_DUMP_H
+
+#ifdef DEBUG
+#define STACK_DUMP(...) stack_dump (__VA_ARGS__)
+
+static void
+stack_dump (lua_State *L, const char *title)
+{
+    // print out the scalar value or type of args on the stack 
+    int i;
+    int top = lua_gettop(L); /* depth of the stack */
+    printf("%20s : [", title);
+    for (i = 1; i <= top; i++) { /* repeat for each level */
+        int t = lua_type(L, i);
+        switch (t)
+        {
+            case LUA_TNIL:
+                {
+                    printf("nil");
+                    break;
+                }
+            case LUA_TBOOLEAN:
+                {
+                    printf(lua_toboolean(L, i) ? "true" : "false");
+                    break;
+                }
+            case LUA_TLIGHTUSERDATA:
+                {
+                    printf("lud@%p", lua_touserdata(L, i));
+                    break;
+                }
+            case LUA_TNUMBER:
+                {
+                    printf("%g", lua_tonumber(L, i));
+                    break;
+                }
+            case LUA_TSTRING:
+                {
+                    printf("'%s'", lua_tostring(L, i));
+                    break;
+                }
+            case LUA_TTABLE:
+                {
+                    printf("{}");
+                    break;
+                }
+            case LUA_TFUNCTION:
+                {
+                    printf("f@%p", lua_touserdata(L, i));
+                    break;
+                }
+            case LUA_TUSERDATA:
+                {
+                    printf("ud(%p)", lua_touserdata(L, i));
+                    break;
+                }
+            case LUA_TTHREAD:
+                {
+                    printf("Thrd(%p)", lua_touserdata(L, i));
+                    break;
+                }
+            default:
+                { // other values
+                    printf("%s", lua_typename(L, t));
+                    break;
+                }
+        }
+        if (i<top) printf(" "); /* put a separator */
+    }
+    printf("]\n"); /* end the listing */
+}
+#else
+#define STACK_DUMP(...)
+#endif  // DEBUG
+
+#endif  // _STACK_DUMP_H

+ 9 - 0
valgrind-lua-noreadline.supp

@@ -0,0 +1,9 @@
+{
+   <insert_a_suppression_name_here>
+   Memcheck:Leak
+   match-leak-kinds: all
+   fun:*
+   ...
+   fun:luaB_dofile
+   ...
+}