diff --git a/src/SConscript b/src/SConscript
index 9b5c868c5f2966c9305139c4805245d8c1c85c26..03308dd9dcc7b0559ff3c89dfd5b429f5c4d3a9b 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -49,6 +49,7 @@ misc_hammer_parts = [
     'glue.c',
     'hammer.c',
     'pprint.c',
+    'registry.c',
     'system_allocator.c']
 
 tests = ['t_benchmark.c',
diff --git a/src/hammer.h b/src/hammer.h
index 508653d8fcf213bf97e7dceec7460e3e55a2f1e7..0175142e509da61fadb79b36eb6095cac1265465 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -633,4 +633,15 @@ void h_benchmark_report(FILE* stream, HBenchmarkResults* results);
 void h_benchmark_dump_optimized_code(FILE* stream, HBenchmarkResults* results);
 // }}}
 
+// {{{ Token type registry
+/// Allocate a new, unused (as far as this function knows) token type.
+int h_allocate_token_type(const char* name);
+
+/// Get the token type associated with name. Returns -1 if name is unkown
+int h_get_token_type_number(const char* name);
+
+/// Get the name associated with token_type. Returns NULL if the token type is unkown
+const char* h_get_token_type_name(int token_type);
+// }}}
+
 #endif // #ifndef HAMMER_HAMMER__H
diff --git a/src/registry.c b/src/registry.c
new file mode 100644
index 0000000000000000000000000000000000000000..c59b6ea9f6e0214c279d51b3997b5770278928bc
--- /dev/null
+++ b/src/registry.c
@@ -0,0 +1,87 @@
+/* Parser combinators for binary formats.
+ * Copyright (C) 2012  Meredith L. Patterson, Dan "TQ" Hirsch
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include <search.h>
+#include <stdlib.h>
+#include "hammer.h"
+#include "internal.h"
+
+typedef struct Entry_ {
+  const char* name;
+  int value;
+} Entry;
+
+static void *tt_registry = NULL;
+static Entry** tt_by_id = NULL;
+static int tt_by_id_sz = 0;
+#define TT_START TT_USER
+static int tt_next = TT_START;
+
+/*
+  // TODO: These are for the extension registry, which does not yet have a good name.
+static void *ext_registry = NULL;
+static Entry** ext_by_id = NULL;
+static int ext_by_id_sz = 0;
+static int ext_next = 0;
+*/
+
+
+static int compare_entries(const void* v1, const void* v2) {
+  const Entry *e1 = (Entry*)v1, *e2 = (Entry*)v2;
+  return strcmp(e1->name, e2->name);
+}
+
+int h_allocate_token_type(const char* name) {
+  Entry* new_entry = malloc(sizeof(*new_entry));
+  new_entry->name = name;
+  new_entry->value = -1;
+  Entry* probe = *(Entry**)tsearch(new_entry, &tt_registry, compare_entries);
+  if (probe->value != -1) {
+    // Token type already exists...
+    // TODO: treat this as a bug?
+    free(new_entry);
+    return probe->value;
+  } else {
+    // new value
+    probe->name = strdup(probe->name); // drop ownership of name
+    probe->value = tt_next++;
+    if ((probe->value - TT_START) >= tt_by_id_sz) {
+      if (tt_by_id_sz == 0)
+	tt_by_id = malloc(sizeof(*tt_by_id) * ((tt_by_id_sz = (tt_next - TT_START) * 16)));
+      else
+	tt_by_id = realloc(tt_by_id, sizeof(*tt_by_id) * ((tt_by_id_sz *= 2)));
+    }
+    assert(probe->value - TT_START < tt_by_id_sz);
+    tt_by_id[probe->value - TT_START] = probe;
+    return probe->value;
+  }
+}
+int h_get_token_type_number(const char* name) {
+  Entry e;
+  e.name = name;
+  Entry **ret = (Entry**)tfind(&e, &tt_registry, compare_entries);
+  if (ret == NULL)
+    return -1;
+  else
+    return (*ret)->value;
+}
+const char* h_get_token_type_name(int token_type) {
+  if (token_type >= tt_next || token_type < TT_START)
+    return NULL;
+  else
+    return tt_by_id[token_type - TT_START]->name;
+}
diff --git a/src/t_misc.c b/src/t_misc.c
index c762c07380bc36db4426c6877d784f329d2ac0bd..74a57ca9b438e75fcbf222b4bbbc79a951c39022 100644
--- a/src/t_misc.c
+++ b/src/t_misc.c
@@ -1,4 +1,5 @@
 #include <glib.h>
+#include <string.h>
 #include "test_suite.h"
 #include "hammer.h"
 
@@ -11,6 +12,24 @@ static void test_tt_user(void) {
   g_check_cmp_int32(TT_USER, >, TT_ERR);
 }
 
+static void test_tt_registry(void) {
+  int id = h_allocate_token_type("com.upstandinghackers.test.token_type");
+  g_check_cmp_int32(id, >=, TT_USER);
+  int id2 = h_allocate_token_type("com.upstandinghackers.test.token_type_2");
+  g_check_cmp_int32(id2, !=, id);
+  g_check_cmp_int32(id2, >=, TT_USER);
+  g_check_cmp_int32(id, ==, h_get_token_type_number("com.upstandinghackers.test.token_type"));
+  g_check_cmp_int32(id2, ==, h_get_token_type_number("com.upstandinghackers.test.token_type_2"));
+  g_check_string("com.upstandinghackers.test.token_type", ==, h_get_token_type_name(id));
+  g_check_string("com.upstandinghackers.test.token_type_2", ==, h_get_token_type_name(id2));
+  if (h_get_token_type_name(0) != NULL) {
+    g_test_message("Unknown token type should not return a name");
+    g_test_fail();
+  }
+  g_check_cmp_int32(h_get_token_type_number("com.upstandinghackers.test.unkown_token_type"), ==, -1);
+}
+
 void register_misc_tests(void) {
   g_test_add_func("/core/misc/tt_user", test_tt_user);
+  g_test_add_func("/core/misc/tt_registry", test_tt_registry);
 }