ソースを参照

More graph functions; standardize fn names.

scossu 6 日 前
コミット
1854c5dbf9
7 ファイル変更332 行追加101 行削除
  1. 14 7
      lua/scratch.lua
  2. 118 48
      lua/src/graph.c
  3. 19 0
      lua/src/lua_lsup.c
  4. 20 2
      lua/src/lua_lsup.h
  5. 66 0
      lua/src/stackdump.h
  6. 54 27
      lua/src/term.c
  7. 41 17
      lua/src/triple.c

+ 14 - 7
lua/scratch.lua

@@ -1,13 +1,20 @@
 term = require "lsup.term"
 triple = require "lsup.triple"
-trp = triple.new (
-    term.iriref_new("urn:s:1"),
-    term.iriref_new("urn:p:1"),
-    term.lit_new("hello", nil, "us-EN"))
+graph = require "lsup.graph"
 
-t1 = term.iriref_new("urn:s:11")
-t2 = term.lit_new("123", "xsd:int")
+trp1 = triple.new (
+    term.new_iriref("urn:s:1"),
+    term.new_iriref("urn:p:1"),
+    term.new_lit("hello", nil, "us-EN"))
+
+t1 = term.new_bnode()
+t2 = term.new_iriref("urn:p:11")
+t3 = term.new_lit("123", "xsd:int")
+trp2 = triple.new (t1, t2, t3)
 
-graph = require "lsup.graph"
 gr1 = graph.new()
+triples = {trp1, trp2}
+ct = gr1:add(triples)
+print("Triples added: " .. ct)
 gr2 = graph.new()
+for i in gr1:lookup() do print(i) end

+ 118 - 48
lua/src/graph.c

@@ -1,4 +1,5 @@
 #include "lua_lsup.h"
+#include "stackdump.h"
 
 #define check_graph(L) \
     *(LSUP_Graph **)luaL_checkudata(L, 1, "LSUP.Graph")
@@ -14,7 +15,7 @@ static LSUP_Graph **allocate_graph (lua_State *L)
 }
 
 
-static int graph_new (lua_State *L)
+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);
@@ -48,7 +49,7 @@ static int graph_new (lua_State *L)
  * Class methods.
  */
 
-static int graph_gc (lua_State *L)
+static int l_graph_gc (lua_State *L)
 {
     LSUP_Graph *gr = check_graph (L);
     if (gr) LSUP_graph_free (gr);
@@ -57,7 +58,7 @@ static int graph_gc (lua_State *L)
 }
 
 
-static int graph_to_string (lua_State *L)
+static int l_graph_to_string (lua_State *L)
 {
     const LSUP_Graph *gr = check_graph (L);
     lua_pushfstring (
@@ -68,7 +69,7 @@ static int graph_to_string (lua_State *L)
 }
 
 
-static int graph_len (lua_State *L)
+static int l_graph_len (lua_State *L)
 {
     const LSUP_Graph *gr = check_graph (L);
     lua_pushinteger (L, LSUP_graph_size (gr));
@@ -77,7 +78,7 @@ static int graph_len (lua_State *L)
 }
 
 
-static int graph_copy (lua_State *L)
+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");
@@ -88,13 +89,18 @@ static int graph_copy (lua_State *L)
 }
 
 
-static int graph_copy_contents (lua_State *L)
+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 = *(LSUP_Term **)luaL_testudata(L, 3, "LSUP.Term");
-    const LSUP_Term *p = *(LSUP_Term **)luaL_testudata(L, 4, "LSUP.Term");
-    const LSUP_Term *o = *(LSUP_Term **)luaL_testudata(L, 5, "LSUP.Term");
+    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.");
@@ -103,69 +109,133 @@ static int graph_copy_contents (lua_State *L)
 }
 
 
-static int graph_equals (lua_State *L)
+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");
+        *(LSUP_Graph **)luaL_checkudata (L, 2, "LSUP.Graph");
 
-    lua_pushboolean (L, LSUP_graph_equals (gr1, gr2));
+    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 graph_iter (lua_State *L)
+static int l_graph_contains (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (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 graph_contains (lua_State *L)
+static int l_graph_add (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (L);
-
-    return 1;
+    LSUP_Graph *gr = check_graph (L);
+    LOG_DEBUG ("Triples type: %s", lua_typename (L, lua_type (L, 2)));
+    int rc;
+    LSUP_rc lsup_rc;
+    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_add (lua_State *L)
+static int l_graph_iter_next (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (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);
+
+    LSUP_rc rc = LSUP_graph_iter_next (it, spo_p);
+
+    if (rc == LSUP_END) {
+        LSUP_graph_iter_free (it);
+        lua_pushnil (L);
+        lua_pushstring (L, "End of lookup results.");
+        return 2;
+    }
+    LUA_PCHECK (rc, "Error retrieving a lookup result.");
 
     return 1;
 }
 
 
-static int graph_lookup (lua_State *L)
+static int l_graph_lookup (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (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, l_graph_iter_next, 1);
 
     return 1;
 }
 
 
-static int graph_connections (lua_State *L)
+static int l_graph_connections (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (L);
+    const LSUP_Graph *gr = check_graph (L);
 
     return 1;
 }
 
 
-static int graph_term_set (lua_State *L)
+static int l_graph_term_set (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (L);
+    const LSUP_Graph *gr = check_graph (L);
 
     return 1;
 }
 
 
-static int graph_unique_terms (lua_State *L)
+static int l_graph_unique_terms (lua_State *L)
 {
-    const LSUP_Graph *gr1 = check_graph (L);
+    const LSUP_Graph *gr = check_graph (L);
 
     return 1;
 }
@@ -176,7 +246,7 @@ static int graph_unique_terms (lua_State *L)
  */
 
 static const luaL_Reg graph_lib_fn [] = {
-    {"new", graph_new},
+    {"new", l_graph_new},
 
     {NULL}
 };
@@ -184,8 +254,8 @@ static const luaL_Reg graph_lib_fn [] = {
 
 /*
 static const luaL_Reg graph_getters [] = {
-    {"uri", graph_get_uri},
-    {"namespace", graph_get_nsm},
+    {"uri", l_graph_get_uri},
+    {"namespace", l_graph_get_nsm},
 
     {NULL}
 };
@@ -194,33 +264,31 @@ static const luaL_Reg graph_getters [] = {
 
 /*
 static const luaL_Reg graph_setters [] = {
-    {"uri", graph_set_uri},
+    {"uri", l_graph_set_uri},
 
     {NULL}
 };
 */
 
 static const luaL_Reg graph_lib_meth [] = {
-    {"__eq", graph_equals},
-    {"__gc", graph_gc},
+    {"__eq", l_graph_equals},
+    {"__gc", l_graph_gc},
     //{"__index", get_attr},
-    {"__tostring", graph_to_string},
-    {"__len", graph_len},
-    {"__ipairs", graph_iter},
     //{"__newindex", set_attr},
+    {"__tostring", l_graph_to_string},
+    {"__len", l_graph_len},
 
-    {"copy", graph_copy},
-    {"copy_contents", graph_copy_contents},
-    {"contains", graph_contains},
+    {"copy", l_graph_copy},
+    {"copy_contents", l_graph_copy_contents},
+    {"contains", l_graph_contains},
 
-    {"add", graph_add},
-    // TODO add graph_add_init, graph_add_iter, graph_add_done
-    {"lookup", graph_lookup},
-    {"connections", graph_connections},
-    {"term_set", graph_term_set},
-    {"unique_terms", graph_unique_terms},
+    {"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", graph_to_n3},
+    //{"to_n3", l_graph_to_n3},
     {NULL}
 };
 
@@ -233,6 +301,8 @@ int luaopen_lsup_graph (lua_State *L)
     lua_setfield (L, -2, "__index");
     luaL_setfuncs (L, graph_lib_meth, 0);
 
+    luaL_newmetatable (L, "LSUP.GraphIterator");
+
     /*
     // Getters table.
     lua_newtable (L);

+ 19 - 0
lua/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);
+    };
+}
+
+
+

+ 20 - 2
lua/src/lua_lsup.h

@@ -12,10 +12,28 @@
         return luaL_error (L, "%s: %s", message, LSUP_strerror (exp)); \
 } while (0)
 
-#define LUA_NLCHECK(exp, message) do {\
-    if (UNLIKELY ((exp) == NULL)) return luaL_error (L, message); \
+#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);
 

+ 66 - 0
lua/src/stackdump.h

@@ -0,0 +1,66 @@
+static void
+stackDump (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 */
+}

+ 54 - 27
lua/src/term.c

@@ -18,7 +18,7 @@ LSUP_Term **allocate_term (lua_State *L)
  * Factory methods.
  */
 
-static int l_iriref_new (lua_State *L)
+static int l_new_iriref (lua_State *L)
 {
     const char *data = luaL_checkstring (L, 1);
     // TODO handle nsm.
@@ -33,7 +33,7 @@ static int l_iriref_new (lua_State *L)
 }
 
 
-static int l_iriref_new_abs (lua_State *L)
+static int l_new_iriref_abs (lua_State *L)
 {
     LSUP_Term
         *root = check_term (L),
@@ -48,7 +48,7 @@ static int l_iriref_new_abs (lua_State *L)
 }
 
 
-static int l_iriref_new_rel (lua_State *L)
+static int l_new_iriref_rel (lua_State *L)
 {
     LSUP_Term
         *root = check_term (L),
@@ -69,7 +69,7 @@ static int l_iriref_new_rel (lua_State *L)
  * datatype has precedence. If both are nil, datatype is set to the default
  * (xsd:string).
  */
-static int l_lit_new (lua_State *L)
+static int l_new_lit (lua_State *L)
 {
     const char
         *data = luaL_checkstring (L, 1),
@@ -93,9 +93,9 @@ static int l_lit_new (lua_State *L)
 }
 
 
-static int l_bnode_new (lua_State *L)
+static int l_new_bnode (lua_State *L)
 {
-    const char *data = luaL_checkstring (L, 1);
+    const char *data = lua_tostring (L, 1);
 
     LSUP_Term **tp = allocate_term (L);
 
@@ -106,7 +106,7 @@ static int l_bnode_new (lua_State *L)
 }
 
 
-static int new_copy (lua_State *L)
+static int l_new_copy (lua_State *L)
 {
     LSUP_Term *src = check_term (L);
 
@@ -122,7 +122,7 @@ static int new_copy (lua_State *L)
 /*
  * Class methods.
  */
-static int equals (lua_State *L)
+static int l_term_equals (lua_State *L)
 {
     LSUP_Term
         *t1 = check_term (L),
@@ -134,7 +134,7 @@ static int equals (lua_State *L)
 }
 
 
-static int term_gc (lua_State *L)
+static int l_term_gc (lua_State *L)
 {
     LSUP_Term *t = check_term (L);
     if (t) LSUP_term_free (t);
@@ -143,13 +143,14 @@ static int term_gc (lua_State *L)
 }
 
 
-static int to_string (lua_State *L)
+/*
+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);
+    lua_pushfstring (L, "LSUP.Term @%p %s", t, nt_term);
 
     return 1;
 
@@ -157,21 +158,23 @@ fail:
     if (nt_term) free (nt_term);
     return luaL_error (L, "Error encoding term for display!");
 }
+*/
 
 
-static int to_n3 (lua_State *L)
+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!");
+    return luaL_error (L, "Error encoding term for display.");
 }
 
 
@@ -183,7 +186,7 @@ fail:
 static const luaL_Reg term_getters [];
 
 
-static int get_attr (lua_State *L)
+static int l_term_get_attr (lua_State *L)
 {
     const char *attr = luaL_checkstring (L, 2);
     //printf ("Got attr: %s\n", attr);
@@ -295,7 +298,7 @@ static int get_hash (lua_State *L)
 
 // Very simple for now.
 //static const luaL_Reg term_setters [];
-static int set_attr (lua_State *L)
+static int l_term_set_attr (lua_State *L)
 { return luaL_error (L, "Direct setting is not allowed for this type.", 2); }
 
 
@@ -304,12 +307,12 @@ static int set_attr (lua_State *L)
  */
 
 static const luaL_Reg term_lib_fn [] = {
-    {"iriref_new", l_iriref_new},
-    {"iriref_new_abs", l_iriref_new_abs},
-    {"iriref_new_rel", l_iriref_new_rel},
-    {"lit_new", l_lit_new},
-    {"bnode_new", l_bnode_new},
-    {"term_new_copy", new_copy},
+    {"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}
 };
@@ -344,17 +347,37 @@ static const luaL_Reg term_setters [] = {
 */
 
 static const luaL_Reg term_lib_meth [] = {
-    {"__eq", equals},
-    {"__gc", term_gc},
-    {"__index", get_attr},
-    {"__tostring", to_string},
-    {"__newindex", set_attr},
+    {"__eq", l_term_equals},
+    {"__gc", l_term_gc},
+    {"__index", l_term_get_attr},
+    {"__tostring", l_term_to_n3},
+    {"__newindex", l_term_set_attr},
 
-    {"to_n3", to_n3},
     {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},
+
+    {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.
@@ -372,5 +395,9 @@ int luaopen_lsup_term (lua_State *L)
 
     luaL_newlib (L, term_lib_fn);
 
+    // Module-level constants.
+    push_string_const (L, term_strings);
+    push_int_const (L, term_enums);
+
     return 1;
 }

+ 41 - 17
lua/src/triple.c

@@ -8,7 +8,7 @@
  * Factory methods.
  */
 
-static int new (lua_State *L)
+static int l_triple_new (lua_State *L)
 {
     LSUP_Triple **trp_p = lua_newuserdatauv (L, sizeof (*trp_p), 1);
     luaL_getmetatable (L, "LSUP.Triple");
@@ -18,7 +18,6 @@ static int new (lua_State *L)
         *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),
@@ -33,7 +32,7 @@ static int new (lua_State *L)
  * Class methods.
  */
 
-static int gc (lua_State *L)
+static int l_triple_gc (lua_State *L)
 {
     LSUP_Triple *trp = check_triple (L);
     if (trp) LSUP_triple_free (trp);
@@ -51,7 +50,7 @@ static int gc (lua_State *L)
  *
  * To get an independent copy of a triple term, use triple.copy_term().
  */
-static int get_term (lua_State *L)
+static int l_triple_get_term (lua_State *L)
 {
     const LSUP_Triple *trp = check_triple (L);
     const char pos = luaL_checkstring (L, 2)[0];
@@ -74,7 +73,7 @@ fail:
 }
 
 
-static int set_term (lua_State *L)
+static int l_triple_set_term (lua_State *L)
 {
     LSUP_Triple *trp = check_triple (L);
     const char pos = luaL_checkstring (L, 2)[0];
@@ -97,7 +96,7 @@ static int set_term (lua_State *L)
 }
 
 
-static int copy_term (lua_State *L)
+static int l_triple_copy_term (lua_State *L)
 {
     const LSUP_Triple *trp = check_triple (L);
     const char pos = luaL_checkstring (L, 2)[0];
@@ -115,6 +114,27 @@ static int copy_term (lua_State *L)
 }
 
 
+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.");
+}
 
 
 /*
@@ -122,22 +142,31 @@ static int copy_term (lua_State *L)
  */
 
 static const struct luaL_Reg triple_lib_fn [] = {
-    {"new", new},
+    {"new", l_triple_new},
     {NULL}
 };
 
 
 static const struct luaL_Reg triple_lib_meth [] = {
-    {"__gc", gc},
-    {"__index", get_term},
-    {"__newindex", set_term},
+    {"__gc", l_triple_gc},
+    {"__index", l_triple_get_term},
+    {"__newindex", l_triple_set_term},
+    {"__tostring", l_triple_to_n3},
 
-    {"copy_term", copy_term},
+    {"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.
@@ -151,12 +180,7 @@ int luaopen_lsup_triple (lua_State *L)
     luaL_newlib (L, triple_lib_fn);
 
     // Module-level constants.
-    lua_pushinteger (L, TRP_POS_S);
-    lua_setfield (L, -2, "TRP_POS_S");
-    lua_pushinteger (L, TRP_POS_P);
-    lua_setfield (L, -2, "TRP_POS_P");
-    lua_pushinteger (L, TRP_POS_O);
-    lua_setfield (L, -2, "TRP_POS_O");
+    push_int_const (L, triple_enums);
 
     return 1;
 }