Skip to content

Instantly share code, notes, and snippets.

@misfo
Created March 7, 2012 02:36
Show Gist options
  • Select an option

  • Save misfo/1990531 to your computer and use it in GitHub Desktop.

Select an option

Save misfo/1990531 to your computer and use it in GitHub Desktop.
import re, string
from functools import partial
from clojure_types import Keyword, Symbol, UnknownObject, Vector
debug = False
re_symbol = re.compile(r"[-\w*+!?/<>&.]+")
re_keyword = re.compile(r":([-\w*+!?/<>&]+)")
re_number = re.compile(r"\-?\d+(\.\d+)?")
re_string = re.compile(r"\"(([^\\\"]|\\.)*)\"")
re_unknown = re.compile(r"#<[\w\s.]+>")
re_whitespace = re.compile(r"[\s,]+")
special_literals = {'nil': None, 'true': True, 'false': False}
escaped_chars = [("\\t", "\t"), ("\\b", "\b"), ("\\n", "\n"), ("\\r", "\r"),
("\\f", "\f"), ("\\\"", "\""), ("\\\\", "\\")]
def eat_whitespace(clj, i, require_some = False):
if i < len(clj) and re_whitespace.match(clj[i]):
return re_whitespace.search(clj, i).end()
elif require_some:
raise UserWarning("Expected whitespace at col " + str(i)
+ ", got: " + repr(clj[i:]))
return i
def read_symbol(clj, i = 0):
match = re_symbol.search(clj, i)
if special_literals.has_key(match.group(0)):
return (special_literals[match.group(0)], match.end())
else:
return (Symbol(match.group(0)), match.end())
def read_keyword(clj, i = 0):
match = re_keyword.search(clj, i)
return (Keyword(match.group(1)), match.end())
def read_string(clj, i = 0):
match = re_string.search(clj, i)
contents = match.group(1)
for (escape_sequence, char) in escaped_chars:
contents = contents.replace(escape_sequence, char)
return (contents, match.end())
def read_dashed(clj, i = 0):
func = read_number if re_number.search(clj, i) else read_symbol
return func(clj, i)
def read_number(clj, i = 0):
#TODO fancy numbers
match = re_number.search(clj, i)
return ((float if match.group(1) else int)(match.group(0)), match.end())
def read_seq(seq_type, closing_delim, clj, i = 0):
l = seq_type([])
conj = l.add if hasattr(l, 'add') else l.append
i += 1
while 1:
i = eat_whitespace(clj, i)
if clj[i] == closing_delim:
return (l, i + 1)
else:
val, i = read_val(clj, i)
conj(val)
def read_map(clj, i = 0):
i += 1
d = {}
while 1:
i = eat_whitespace(clj, i)
if clj[i] == "}":
return (d, i + 1)
else:
key, i = read_val(clj, i)
i = eat_whitespace(clj, i, True)
val, i = read_val(clj, i)
d[key] = val
def read_dispatch_macro(clj, i = 0):
if clj[i + 1] == "(":
return read_seq(set, ")", clj, i + 1)
match = re_unknown.search(clj, i)
if match:
return (UnknownObject(), match.end())
else:
raise UserWarning("Unknown dispatch macro: " + clj[i:])
reader_funcs = {"(": partial(read_seq, list, ")"),
"[": partial(read_seq, Vector, "]"),
"{": read_map,
":": read_keyword,
'"': read_string,
"#": read_dispatch_macro,
"-": read_dashed}
for char in (string.ascii_letters + "_*+!?/.<>&"):
reader_funcs[char] = read_symbol
for n in range(10):
reader_funcs[str(n)] = read_number
def read_val(clj, i = 0):
i = eat_whitespace(clj, i)
char = clj[i]
if debug: print "read_val", repr(char)
func = reader_funcs.get(char)
if func:
return func(clj, i)
else:
raise UserWarning("Unexpected char: " + repr(clj[i:]))
def read(clj):
val, i = read_val(clj)
if eat_whitespace(clj, i) < len(clj):
UserWarning("Unexpected chars after val: " + repr(clj[i:]))
return val
class NamedType(object):
def __init__(self, name):
self.name = name
def __eq__(self, other):
return (isinstance(other, self.__class__)
and other.name == self.name)
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.__class__, self.name))
class Keyword(NamedType):
def __str__(self):
return ":" + self.name
class Symbol(NamedType):
def __str__(self):
return self.name
class Vector(list):
def __eq__(self, other):
return (isinstance(other, self.__class__)
and list.__eq__(self, other))
def __ne__(self, other):
return not self.__eq__(other)
class UnknownObject(object):
pass
import sys, unittest
sys.path.insert(0, "../")
from clojure_reader import Keyword, read, Symbol, UnknownObject, Vector
class ReaderTest(unittest.TestCase):
def setUp(self):
self.maxDiff = None
def test_whitespace(self):
self.assertEqual("whoa", read(' "whoa" '))
self.assertEqual(["1", "a"], read(' ( "1",,, "a" ) '))
self.assertEqual(["hi", 11], read('("hi"\n11)'))
self.assertEqual(["hi", 11], read(' ("hi"\n 11 ) '))
def test_strings(self):
self.assertEqual("whoa", read('"whoa"'))
self.assertEqual("whoa\"Dave", read('"whoa\\"Dave"'))
self.assertEqual("whoa\nDave", read('"whoa\nDave"'))
self.assertEqual("whoa\nDave", read('"whoa\\nDave"'))
def test_numbers(self):
self.assertEqual(11, read('11'))
self.assertEqual(-42, read('-42'))
self.assertEqual(1.01, read('1.01'))
#TODO fancy numbers
def test_keyword(self):
self.assertEqual("whoa", read(":whoa").name)
self.assertEqual(Keyword("whoa"), read(":whoa"))
self.assertEqual([Keyword("whoa")], read("(:whoa)"))
def test_symbols(self):
self.assertEqual(Symbol("whoa"), read("whoa"))
self.assertEqual([Symbol("whoa")], read("(whoa)"))
def test_lists(self):
self.assertEqual([], read('()'))
self.assertEqual([1, 2], read('(1 2)'))
self.assertEqual([["w"], "h", [["o"], "a"]],
read('(("w") "h" (("o") "a"))'))
def test_vectors(self):
self.assertEqual(Vector([]), read('[]'))
self.assertEqual(Vector(["w", "h", Vector(["o", "a"])]),
read('["w" "h" ["o" "a"]]'))
def test_sets(self):
self.assertEqual(set(), read('#()'))
self.assertEqual(set([3, 9, "a"]), read('#(3 "a" 9)'))
def test_maps(self):
self.assertEqual({'a': '1', 'b': '2'}, read('{"a" "1", "b" "2"}'))
def test_other_literals(self):
self.assertEqual(None, read('nil'))
self.assertEqual(True, read('true'))
self.assertEqual(False, read('false'))
def test_invalid(self):
self.assertRaises(UserWarning, read, ("{:a#(123)}"))
def test_use_cases(self):
d = read('{:arglists ([req id]), :ns #<Namespace jv3.controllers.admin>, :name delete-painting, :line 78, :file "jv3/controllers/admin.clj"}')
self.assertEqual(5, len(d))
self.assertEqual([Vector([Symbol('req'), Symbol('id')])], d[Keyword('arglists')])
self.assertIsInstance(d[Keyword('ns')], UnknownObject)
self.assertEqual(Symbol('delete-painting'), d[Keyword('name')])
self.assertEqual(78, d[Keyword('line')])
self.assertEqual("jv3/controllers/admin.clj", d[Keyword('file')])
if __name__ == "__main__":
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment