Created
March 7, 2012 02:36
-
-
Save misfo/1990531 to your computer and use it in GitHub Desktop.
Clojure reader in Python I wrote before I found https://github.com/halgari/clojure-py/blob/master/clojure/lang/lispreader.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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