diff --git a/edn_format/edn_dump.py b/edn_format/edn_dump.py index a52d6c8..b8247b5 100644 --- a/edn_format/edn_dump.py +++ b/edn_format/edn_dump.py @@ -3,6 +3,7 @@ import datetime import decimal +import fractions import itertools import re import sys @@ -104,6 +105,9 @@ def udump(obj, pairs = ((Keyword(k) if isinstance(k, (bytes, basestring)) else k, v) for k, v in pairs) return '{{{}}}'.format(seq(itertools.chain.from_iterable(pairs), **kwargs)) + return "{{{}}}".format(seq(itertools.chain.from_iterable(obj.items()))) + elif isinstance(obj, fractions.Fraction): + return str(obj) elif isinstance(obj, datetime.datetime): return '#inst "{}"'.format(pyrfc3339.generate(obj, microseconds=True)) elif isinstance(obj, datetime.date): diff --git a/edn_format/edn_lex.py b/edn_format/edn_lex.py index a84a60c..afaeb34 100644 --- a/edn_format/edn_lex.py +++ b/edn_format/edn_lex.py @@ -92,6 +92,7 @@ def __str__(self): 'BOOLEAN', 'INTEGER', 'FLOAT', + 'DIVIDE', 'SYMBOL', 'KEYWORD', 'VECTOR_START', @@ -123,8 +124,6 @@ def __str__(self): r"\/" "[{all}]+" "|" - r"\/" - "|" "{start}" "[{all}]*" ")").format(**PARTS) @@ -146,6 +145,7 @@ def __str__(self): "[{all}]*" ")").format(**PARTS) +t_DIVIDE = r'\/' t_VECTOR_START = r'\[' t_VECTOR_END = r'\]' t_LIST_START = r'\(' diff --git a/edn_format/edn_parse.py b/edn_format/edn_parse.py index 07ad728..fc3b2a6 100644 --- a/edn_format/edn_parse.py +++ b/edn_format/edn_parse.py @@ -2,11 +2,12 @@ from __future__ import absolute_import, division, print_function, unicode_literals import datetime +import fractions import sys import uuid +import pyrfc3339 import ply.yacc -import pyrfc3339 from .edn_lex import tokens, lex from .immutable_dict import ImmutableDict @@ -50,7 +51,8 @@ def p_term_leaf(p): | NIL | KEYWORD | SYMBOL - | WHITESPACE""" + | WHITESPACE + | DIVIDE""" p[0] = p[1] @@ -89,6 +91,11 @@ def p_empty_map(p): p[0] = ImmutableDict({}) +def p_fraction(p): + """fraction : INTEGER DIVIDE INTEGER""" + p[0] = fractions.Fraction(p[1], p[3]) + + def p_map(p): """map : MAP_START expressions MAP_OR_SET_END""" terms = p[2] @@ -113,6 +120,7 @@ def p_expression(p): | list | set | map + | fraction | term""" p[0] = p[1] diff --git a/tests.py b/tests.py index 993b13c..a15f51f 100644 --- a/tests.py +++ b/tests.py @@ -6,9 +6,9 @@ from uuid import uuid4 import random import datetime -import unittest - +import fractions import pytz +import unittest from edn_format import edn_lex, edn_parse, \ loads, dumps, Keyword, Symbol, TaggedElement, ImmutableDict, add_tag @@ -57,7 +57,7 @@ def test_lexer(self): "abc") self.check_lex("[LexToken(SYMBOL,Symbol(?abc),1,0)]", "?abc") - self.check_lex("[LexToken(SYMBOL,Symbol(/),1,0)]", + self.check_lex("[LexToken(DIVIDE,'/',1,0)]", "/") self.check_lex("[LexToken(SYMBOL,Symbol(prefix/name),1,0)]", "prefix/name") @@ -65,6 +65,7 @@ def test_lexer(self): "true.") self.check_lex("[LexToken(SYMBOL,Symbol($:ABC?),1,0)]", "$:ABC?") + self.check_lex("[LexToken(MAP_START,'{',1,0), " "LexToken(KEYWORD,Keyword(a),1,2), " "LexToken(BOOLEAN,False,1,5), " @@ -73,6 +74,11 @@ def test_lexer(self): "LexToken(MAP_OR_SET_END,'}',1,21)]", "{ :a false, :b false }") + self.check_lex(("[LexToken(INTEGER,2,1,0), " + "LexToken(DIVIDE,'/',1,1), " + "LexToken(INTEGER,3,1,2)]"), + "2/3") + def check_parse(self, expected_output, actual_input): self.assertEqual(expected_output, edn_parse.parse(actual_input)) @@ -125,6 +131,7 @@ def test_parser(self): self.check_parse('\\', r'"\\"') self.check_parse(["abc", "123"], '["abc", "123"]') self.check_parse({"key": "value"}, '{"key" "value"}') + self.check_parse(fractions.Fraction(2, 3), "2/3") def check_roundtrip(self, data_input, **kw): self.assertEqual(data_input, loads(dumps(data_input, **kw)))