diff --git a/gdb-port/ast.py b/gdb-port/ast.py
new file mode 100644
index 0000000000000000000000000000000000000000..59810a1682630759f7d9a98af0c9ba82bbea1fc7
--- /dev/null
+++ b/gdb-port/ast.py
@@ -0,0 +1,117 @@
+class HParseResult:
+	#HParseResult_t = gdb.lookup_type("HParseResult")
+	HParseResult_t_p = gdb.lookup_type("HParseResult").pointer()
+	HParsedToken_t_p = gdb.lookup_type("HParsedToken").pointer()
+
+	def __init__(self, address):
+		# Note to self: Address has to be an integer and not string
+		# Otherwise all hell breaks loose
+		self.address = address
+		self.has_token = self.read_AST_not_null()
+		self.ast = None
+		if self.has_token:
+			self.ast = self.make_HParsedToken()
+
+		self.bit_length = self.read_bit_length()
+		self.arena = self.read_arena()
+
+	# AST is not null
+	def read_AST_not_null(self):
+		res = gdb.Value(self.address).cast(HParseResult_t_p)
+		if res['ast'] == 0:
+			return False
+		# Note that the ast could still be an invalid pointer
+		# This is for combinators that return a valid HParseResult with NULL ast
+		return True
+
+	def read_token(self):
+		res = gdb.Value(self.address).cast(HParseResult_t_p)
+		return gdb.Value(res['ast'])
+
+	def make_HParsedToken(self):
+		if self.has_token:
+			tok = self.read_token()
+			return gdb.Value(tok).dereference()
+			#TODO: instantiate HParsedToken class
+
+	def read_bit_length(self):
+		res = gdb.Value(self.address).cast(HParseResult_t_p)
+		return res['bit_length']
+
+	def read_arena(self):
+		res = gdb.Value(self.address).cast(HParseResult_t_p)
+		return res['arena']
+
+class HParsedToken:
+	# This will break if the HTokenType enum changes in Hammer
+	# It's not expected to change, but the workaround for it would be to extract it at runtime.
+	# Additionally, parsers may declare their own HTokenTypes, which *do* have to be handled at runtime.
+	# TODO: unneeded, remove
+	token_types = {
+		0: gdb.lookup_type("void").pointer(),	# TT_INVALID, shouldn't access data
+		1: gdb.lookup_type("void").pointer(),	# TT_NONE, shouldn't access data
+		2: gdb.lookup_type("HBytes"),		# TT_BYTES
+		4: gdb.lookup_type("int64_t"),		# TT_SINT
+		8: gdb.lookup_type("uint64_t"),		# TT_UINT
+		12: gdb.lookup_type("double"),		# TT_DOUBLE
+		13: gdb.lookup_type("float"),		# TT_FLOAT
+		16: gdb.lookup_type("HCountedArray").pointer(),	# TT_SEQUENCE
+		# TT_RESERVED_1, value determined by compiler
+		32: gdb.lookup_type("void").pointer(),	# TT_ERR, shouldn't access data
+		64: gdb.lookup_type("void").pointer(),	# TT_USER, this one actually uses void*
+		# TT_MAX, value determined at compile time
+	}
+
+	token_union_members = {
+		2: 'bytes',
+		4: 'sint',
+		8: 'uint',
+		12: 'dbl',
+		13: 'flt',
+		16: 'seq',
+		64: 'user'
+	}
+
+	TT_MAX = gdb.lookup_type("enum HTokenType_").fields()[-1].enumval
+	# These enum values have no token data
+	no_token_data = [v.enumval for v in gdb.lookup_type("enum HTokenType_").fields() if v.name in ["TT_INVALID", "TT_RESERVED_1", "TT_ERR", "TT_NONE", "TT_MAX"]]
+
+	#void_ptr = gdb.lookup_type("void").pointer()
+	#HParsedToken_t = gdb.lookup_type("HParsedToken")
+	HParsedToken_t_p = gdb.lookup_type("HParsedToken").pointer()
+
+
+	def __init__(self, address, parent, token_type=None, children=[]):
+		self.address = address
+		self.parent = parent
+		self.children = children
+		self.token_type = token_type or self.read_token_type()
+
+		#TODO
+		self.token = self.read_token_val()
+		self.data = self.read_token_data()
+
+	def read_token_val(self):
+		tok = gdb.Value(self.address).cast(HParsedToken_t_p)
+		return tok
+
+	def read_token_type(self):
+		tok = gdb.Value(self.address).cast(HParsedToken_t_p)
+		return tok['token_type']
+
+	def has_token_data(self, token_type):
+		return token_type not in no_token_data
+
+
+	def read_token_data(self):
+		tok = gdb.Value(self.address).cast(HParsedToken_t_p)
+		if self.has_token_data(self.token_type):
+			# We default to using the 'user' field. Also covers custom token types
+			member = 'user'
+			if self.token_type < TT_MAX:
+				member = token_union_members.setdefault(self.token_type, "user")
+			data = tok[member]
+			return data
+		# Token type is one of the enum values known not to have data
+		else:
+			return None