diff --git a/changes.md b/changes.md new file mode 100644 index 0000000..67198a2 --- /dev/null +++ b/changes.md @@ -0,0 +1,14 @@ +Changelog +========= + +1.0.2 +----- +Removed unit test from PyPI distribution + +1.0.1 +----- +- Fixed a bug in `ast_ctx_fixer` +- `gen_sym()` is now `gen_sym(name="sym")`, allowing you to override the base name +- Implemented `macropy.case_classes.enum` macro +- Implemented `macropy.quick_lambda.lazy` and `macropy.quick_lambda.interned` macros + diff --git a/macropy/__init__.py b/macropy/__init__.py index fd6a2c3..68d1624 100644 --- a/macropy/__init__.py +++ b/macropy/__init__.py @@ -16,5 +16,5 @@ def console(): import core.exporters -__version__ = "1.0.0" +__version__ = "1.0.2" exporter = core.exporters.NullExporter() \ No newline at end of file diff --git a/macropy/case_classes.py b/macropy/case_classes.py index 9467b5f..66b36b5 100644 --- a/macropy/case_classes.py +++ b/macropy/case_classes.py @@ -1,6 +1,6 @@ """Macro providing an extremely concise way of declaring classes""" from macropy.core.macros import * -from macropy.core.hquotes import macros, hq, name, unhygienic +from macropy.core.hquotes import macros, hq, name, unhygienic, u macros = Macros() @@ -37,6 +37,47 @@ def __iter__(self): yield getattr(self, x) +class Enum(object): + def __new__(cls, *args, **kw): + if not hasattr(cls, "all"): + cls.all = [] + thing = object.__new__(cls, *args, **kw) + cls.all.append(thing) + return thing + + @property + def next(self): + return self.__class__.all[(self.id + 1) % len(self.__class__.all)] + @property + def prev(self): + return self.__class__.all[(self.id - 1) % len(self.__class__.all)] + + def __str__(self): + return self.__class__.__name__ + "." + self.name + + def __repr__(self): + return self.__str__() + + + def __iter__(self): + for x in self.__class__._fields: + yield getattr(self, x) + +def enum_new(cls, **kw): + if len(kw) != 1: + raise TypeError("Enum selection can only take exactly 1 named argument: " + len(kw) + " found.") + + [(k, v)] = kw.items() + + for value in cls.all: + if getattr(value, k) == v: + return value + + raise ValueError("No Enum found for %s=%s" % (k, v)) + +def noop_init(*args, **kw): + pass + def extract_args(init_fun, bases): args = [] vararg = None @@ -62,6 +103,7 @@ def extract_args(init_fun, bases): all_args.append(kwarg) return args, vararg, kwarg, defaults, all_args + @Walker def find_member_assignments(tree, collect, stop, **kw): if type(tree) in [GeneratorExp, Lambda, ListComp, DictComp, SetComp, FunctionDef, ClassDef]: @@ -75,16 +117,14 @@ def find_member_assignments(tree, collect, stop, **kw): ] map(collect, self_assigns) -@macros.decorator -def case(tree, gen_sym, **kw): - """Macro providing an extremely concise way of declaring classes""" - def split_body(tree): + +def split_body(tree, gen_sym): new_body = [] outer = [] init_body = [] for statement in tree.body: if type(statement) is ClassDef: - outer.append(case_transform(statement, [Name(id=tree.name, ctx=Load())])) + outer.append(case_transform(statement, gen_sym, [Name(id=tree.name)])) with hq as a: name[tree.name].b = name[statement.name] a_old = a[0] @@ -98,67 +138,121 @@ def split_body(tree): init_body.append(statement) return new_body, outer, init_body - def prep_initialization(init_fun, args, vararg, kwarg, defaults, all_args): - init_fun.args = arguments( - args = [Name(id="self")] + [Name(id = id) for id in args], - vararg = vararg, - kwarg = kwarg, - defaults = defaults - ) +def prep_initialization(init_fun, args, vararg, kwarg, defaults, all_args): + init_fun.args = arguments( + args = [Name(id="self")] + [Name(id = id) for id in args], + vararg = vararg, + kwarg = kwarg, + defaults = defaults + ) - for x in all_args: - with hq as a: - unhygienic[self.x] = name[x] + for x in all_args: + with hq as a: + unhygienic[self.x] = name[x] - a[0].targets[0].attr = x + a[0].targets[0].attr = x - init_fun.body.append(a[0]) + init_fun.body.append(a[0]) - def case_transform(tree, parents): - with hq as methods: - def __init__(self, *args, **kwargs): - pass +def shared_transform(tree, gen_sym, additional_args=[]): + with hq as methods: + def __init__(self, *args, **kwargs): + pass - _fields = [] - _varargs = None - _kwargs = None - __slots__ = [] + _fields = [] + _varargs = None + _kwargs = None + __slots__ = [] - init_fun, set_fields, set_varargs, set_kwargs, set_slots, = methods + init_fun, set_fields, set_varargs, set_kwargs, set_slots, = methods - args, vararg, kwarg, defaults, all_args = extract_args(init_fun, tree.bases) + args, vararg, kwarg, defaults, all_args = extract_args(init_fun, [Name(id=x) for x in additional_args] + tree.bases) + if vararg: + set_varargs.value = Str(vararg) + if kwarg: + set_kwargs.value = Str(kwarg) + additional_members = find_member_assignments.collect(tree.body) + prep_initialization(init_fun, args, vararg, kwarg, defaults, all_args) + set_fields.value.elts = map(Str, args) + set_slots.value.elts = map(Str, all_args + additional_members) + new_body, outer, init_body = split_body(tree, gen_sym) + init_fun.body.extend(init_body) + tree.body = new_body + tree.body = methods + tree.body + return outer + + +def case_transform(tree, gen_sym, parents): + + outer = shared_transform(tree, gen_sym) + + tree.bases = parents + assign = FunctionDef( + gen_sym("prepare_"+tree.name), + arguments([], None, None, []), + outer, + [hq[apply]] + ) + return [tree] + ([assign] if len(outer) > 0 else []) - if vararg: - set_varargs.value = Str(vararg) - if kwarg: - set_kwargs.value = Str(kwarg) +@macros.decorator +def case(tree, gen_sym, **kw): + """Macro providing an extremely concise way of declaring classes""" + x = case_transform(tree, gen_sym, [hq[CaseClass]]) - additional_members = find_member_assignments.collect(tree.body) + return x - prep_initialization(init_fun, args, vararg, kwarg, defaults, all_args) - set_fields.value.elts = map(Str, args) - set_slots.value.elts = map(Str, all_args + additional_members) - new_body, outer, init_body = split_body(tree) - init_fun.body.extend(init_body) +@macros.decorator +def enum(tree, gen_sym, **kw): + + count = [0] + new_assigns = [] + new_body = [] + def handle(expr): + assert type(expr) in (Name, Call), stmt.value + if type(expr) is Name: + expr.ctx = Store() + self_ref = Attribute(value=Name(id=tree.name), attr=expr.id) + with hq as code: + ast[self_ref] = name[tree.name](u[count[0]], u[expr.id]) + new_assigns.extend(code) + count[0] += 1 + + elif type(expr) is Call: + assert type(expr.func) is Name + self_ref = Attribute(value=Name(id=tree.name), attr=expr.func.id) + id = expr.func.id + expr.func = Name(id=tree.name) + + expr.args = [Num(count[0]), Str(id)] + expr.args + new_assigns.append(Assign([self_ref], expr)) + count[0] += 1 + + assert all(type(x) in (Expr, FunctionDef) for x in tree.body) + + for stmt in tree.body: + if type(stmt) is Expr: + assert type(stmt.value) in (Tuple, Name, Call) + if type(stmt.value) is Tuple: + map(handle, stmt.value.elts) + else: + handle(stmt.value) + else: + new_body.append(stmt) - assign = FunctionDef( - gen_sym(), - arguments([], None, None, []), - outer, - [hq[apply]] - ) + tree.body = new_body + [Pass()] - tree.body = new_body - tree.bases = parents + shared_transform(tree, gen_sym, additional_args=["id", "name"]) - tree.body = methods + tree.body + with hq as code: + name[tree.name].__new__ = staticmethod(enum_new) + name[tree.name].__init__ = noop_init - return [tree] + ([assign] if len(outer) > 0 else []) - x = case_transform(tree, [hq[CaseClass]]) + tree.bases = [hq[Enum]] - return x \ No newline at end of file + return [tree] + new_assigns + code \ No newline at end of file diff --git a/macropy/core/cleanup.py b/macropy/core/cleanup.py index ce142bd..9be8de4 100644 --- a/macropy/core/cleanup.py +++ b/macropy/core/cleanup.py @@ -14,7 +14,7 @@ def fix_ctx(tree, **kw): @Walker -def ast_ctx_fixer(tree, ctx, stop, **kw): +def ast_ctx_fixer(tree, ctx, stop, set_ctx, **kw): """Fix any missing `ctx` attributes within an AST; allows you to build your ASTs without caring about that stuff and just filling it in later.""" if "ctx" in type(tree)._fields and (not hasattr(tree, "ctx") or tree.ctx is None): @@ -35,6 +35,10 @@ def ast_ctx_fixer(tree, ctx, stop, **kw): stop() return tree + if type(tree) is Attribute: + set_ctx(Load()) + return tree + if type(tree) is Assign: for target in tree.targets: ast_ctx_fixer.recurse(target, Store()) diff --git a/macropy/core/gen_sym.py b/macropy/core/gen_sym.py index 4fc525f..adad5eb 100644 --- a/macropy/core/gen_sym.py +++ b/macropy/core/gen_sym.py @@ -21,9 +21,21 @@ def name_finder(tree, collect, **kw): if type(tree) is ImportFrom: names = [x.asname or x.name for x in tree.names] map(collect, names) + if type(tree) in (FunctionDef, ClassDef): + collect(tree.name) - found_names = name_finder.collect(tree) - names = ("sym" + str(i) for i in itertools.count()) - x = itertools.ifilter(lambda x: x not in found_names, names) - return lambda: x.next() + found_names = set(name_finder.collect(tree)) + + def name_for(name="sym"): + + if name not in found_names: + found_names.add(name) + return name + offset = 1 + while name + str(offset) in found_names: + + offset += 1 + found_names.add(name + str(offset)) + return name + str(offset) + return name_for diff --git a/macropy/core/hquotes.py b/macropy/core/hquotes.py index 58b857f..1414770 100644 --- a/macropy/core/hquotes.py +++ b/macropy/core/hquotes.py @@ -22,7 +22,7 @@ def post_proc(tree, captured_registry, gen_sym, **kw): if captured_registry == []: return tree - unpickle_name = gen_sym() + unpickle_name = gen_sym("unpickled") pickle_import = [ ImportFrom( module='pickle', @@ -53,7 +53,7 @@ def hygienator(tree, stop, **kw): if type(tree) is Captured: new_sym = [sym for val, sym in captured_registry if val is tree.val] if not new_sym: - new_sym = gen_sym() + new_sym = gen_sym(tree.name) captured_registry.append((tree.val, new_sym)) else: diff --git a/macropy/core/macros.py b/macropy/core/macros.py index 052c70f..a181039 100644 --- a/macropy/core/macros.py +++ b/macropy/core/macros.py @@ -180,12 +180,12 @@ def macro_searcher(tree, **kw): return tree - file_vars = { - v.func_name: v(tree=tree, src=src, expand_macros=expand_macros) - for v in injected_vars - } + file_vars = {} + + + for v in injected_vars: + file_vars[v.func_name] = v(tree=tree, src=src, expand_macros=expand_macros, **file_vars) - # you don't pay for what you don't use allnames = [ (m, name, asname) diff --git a/macropy/core/test/exporters/pyc_cache.py b/macropy/core/test/exporters/pyc_cache.py index 7d63b48..9de213d 100644 --- a/macropy/core/test/exporters/pyc_cache.py +++ b/macropy/core/test/exporters/pyc_cache.py @@ -3,4 +3,4 @@ exporters.pyc_cache_count += 1 f[1] - \ No newline at end of file + \ No newline at end of file diff --git a/macropy/core/test/gen_sym_macro.py b/macropy/core/test/gen_sym_macro.py index 32bd04b..9c78685 100644 --- a/macropy/core/test/gen_sym_macro.py +++ b/macropy/core/test/gen_sym_macro.py @@ -5,5 +5,9 @@ @macros.expr def f(tree, gen_sym, **kw): symbols = [gen_sym(), gen_sym(), gen_sym(), gen_sym(), gen_sym()] - assert symbols == ["sym0", "sym2", "sym5", "sym6", "sym7"], symbols + assert symbols == ["sym2", "sym5", "sym6", "sym7", "sym8"], symbols + renamed = [gen_sym("max"), gen_sym("max"), gen_sym("run"), gen_sym("run")] + assert renamed == ["max1", "max2", "run1", "run2"], renamed + unchanged = [gen_sym("grar"), gen_sym("grar"), gen_sym("omg"), gen_sym("omg")] + assert unchanged == ["grar", "grar1", "omg", "omg1"], unchanged return Num(n = 10) diff --git a/macropy/experimental/test/__init__.py b/macropy/experimental/test/__init__.py index cf53ca9..bf71328 100644 --- a/macropy/experimental/test/__init__.py +++ b/macropy/experimental/test/__init__.py @@ -1,4 +1,4 @@ -from macropy.test import test_suite, peg +from macropy.test import test_suite #import js_snippets #import pattern import pinq @@ -8,7 +8,6 @@ Tests = test_suite(cases = [ #js_snippets, #pattern, - peg, #pinq, pyxl_snippets, #tco_test diff --git a/macropy/peg.py b/macropy/peg.py index 653c334..be45b6d 100644 --- a/macropy/peg.py +++ b/macropy/peg.py @@ -63,7 +63,7 @@ def PegWalker(tree, stop, collect, **kw): names = distinct(flatten(b_left)) tree.right.args.args = map(f[Name(id = _)], names) tree.right.args.defaults = [hq[[]]] * len(names) - tree.right.args.kwarg = gen_sym() + tree.right.args.kwarg = gen_sym("kw") stop() return tree diff --git a/macropy/quick_lambda.py b/macropy/quick_lambda.py index 4bab467..555213a 100644 --- a/macropy/quick_lambda.py +++ b/macropy/quick_lambda.py @@ -1,6 +1,7 @@ from macropy.core.macros import * -from macropy.core.quotes import macros, q - +from macropy.core.quotes import macros, q, ast, u +from macropy.core.hquotes import macros, hq, ast, u, name +from macropy.core.cleanup import ast_ctx_fixer macros = Macros() def _(): @@ -14,7 +15,7 @@ def f(tree, gen_sym, **kw): @Walker def underscore_search(tree, collect, **kw): if isinstance(tree, Name) and tree.id == "_": - name = gen_sym() + name = gen_sym("_") tree.id = name collect(name) return tree @@ -24,3 +25,52 @@ def underscore_search(tree, collect, **kw): new_tree = q[lambda: ast[tree]] new_tree.args.args = [Name(id = x) for x in used_names] return new_tree + + +@macros.expr +def lazy(tree, **kw): + """Macro to wrap an expression in a lazy memoizing thunk. This can be + called via `thing()` to extract the value. The wrapped expression is + only evaluated the first time the thunk is called and the result cached + for all subsequent evaluations.""" + return hq[Lazy(lambda: ast[tree])] + + +def get_interned(store, index, thunk): + + if store[index] is None: + store[index] = [thunk()] + + return store[index][0] + + +@register(injected_vars) +def interned_count(**kw): + return [0] + +@register(injected_vars) +def interned_name(gen_sym, **kw): + return gen_sym() + +@register(post_processing) +def interned_processing(tree, gen_sym, interned_count, interned_name, **kw): + + if interned_count[0] != 0: + with q as code: + name[interned_name] = [None for x in range(u[interned_count[0]])] + + code = ast_ctx_fixer.recurse(code) + code = map(fix_missing_locations, code) + + tree.body = code + tree.body + + return tree + + + +@macros.expr +def interned(tree, interned_name, interned_count, **kw): + """Macro to intern the wrapped expression on a per-module basis""" + interned_count[0] += 1 + + return hq[get_interned(name[interned_name], interned_count[0] - 1, lambda: ast[tree])] diff --git a/macropy/test/__init__.py b/macropy/test/__init__.py index 4b81c28..b013b29 100644 --- a/macropy/test/__init__.py +++ b/macropy/test/__init__.py @@ -12,13 +12,15 @@ def test_suite(suites=[], cases=[]): import quick_lambda import string_interp import tracing +import peg import macropy.experimental.test import macropy.core.test Tests = test_suite(cases=[ case_classes, quick_lambda, string_interp, - tracing + tracing, + peg ], suites=[ macropy.experimental.test, macropy.core.test diff --git a/macropy/test/case_classes.py b/macropy/test/case_classes.py index f73690b..5d20faa 100644 --- a/macropy/test/case_classes.py +++ b/macropy/test/case_classes.py @@ -1,5 +1,5 @@ -from macropy.case_classes import macros, case - +from macropy.case_classes import macros, case, enum, enum_new +from macropy.tracing import macros, show_expanded import unittest class Tests(unittest.TestCase): @@ -139,4 +139,80 @@ class Point(x, y): pass p = Point(1, 2) - x, y = p \ No newline at end of file + x, y = p + + + def test_enum(self): + + @enum + class Direction: + North, South, East, West + + assert Direction(name="North") is Direction.North + + assert repr(Direction.North) == str(Direction.North) == "Direction.North" + + # getting name + assert Direction.South.name == "South" + + + # selecting by id + assert Direction(id=2) is Direction.East + + # getting id + assert Direction.West.id == 3 + + + # `next` and `prev` properties + assert Direction.North.next is Direction.South + assert Direction.West.prev is Direction.East + + # `next` and `prev` wrap-around + assert Direction.West.next is Direction.North + assert Direction.North.prev is Direction.West + + # `all` + assert Direction.all == [ + Direction.North, + Direction.South, + Direction.East, + Direction.West + ] + + + + def test_multiline_enum(self): + @enum + class Direction: + North + South + East + West + + assert Direction(name="North") is Direction.North + assert Direction(name="West") is Direction.West + + def test_complex_enum(self): + @enum + class Direction(alignment): + North("Vertical") + East("Horizontal") + South("Vertical") + West("Horizontal") + + @property + def opposite(self): + return Direction(id=(self.id + 2) % 4) + + def padded_name(self, n): + return (" " * n) + self.name + (" " * n) + + # members + assert Direction.North.alignment == "Vertical" + assert Direction.East.alignment == "Horizontal" + + # properties + assert Direction.North.opposite is Direction.South + + # methods + assert Direction.South.padded_name(2) == " South " diff --git a/macropy/test/quick_lambda.py b/macropy/test/quick_lambda.py index cf332ea..2fb9499 100644 --- a/macropy/test/quick_lambda.py +++ b/macropy/test/quick_lambda.py @@ -1,7 +1,7 @@ import unittest -from macropy.quick_lambda import macros, f, _ - +from macropy.quick_lambda import macros, f, _, lazy, interned +from macropy.tracing import macros, show_expanded class Tests(unittest.TestCase): def test_basic(self): assert map(f[_ - 1], [1, 2, 3]) == [0, 1, 2] @@ -26,3 +26,32 @@ def test_name_collision(self): assert func1(10) == 11 func2 = f[_ + sym0 + _ + sym1] assert func2(10, 10) == 23 + + def test_lazy(self): + wrapped = [0] + def func(): + wrapped[0] += 1 + + thunk = lazy[func()] + + assert wrapped[0] == 0 + + thunk() + assert wrapped[0] == 1 + thunk() + assert wrapped[0] == 1 + + def test_interned(self): + + wrapped = [0] + def func(): + wrapped[0] += 1 + + def wrapped_func(): + return interned[func()] + + assert wrapped[0] == 0 + wrapped_func() + assert wrapped[0] == 1 + wrapped_func() + assert wrapped[0] == 1 \ No newline at end of file diff --git a/macropy/test/tracing.py b/macropy/test/tracing.py index abb561d..58e5e79 100644 --- a/macropy/test/tracing.py +++ b/macropy/test/tracing.py @@ -11,95 +11,95 @@ def log(x): class Tests(unittest.TestCase): - def test_basic(self): - - log[1 + 2] - log["omg" * 3] - - assert(result[-2:] == [ - "1 + 2 -> 3", - "\"omg\" * 3 -> 'omgomgomg'" - ]) - - def test_combo(self): - - trace[1 + 2 + 3 + 4] - - assert(result[-3:] == [ - "1 + 2 -> 3", - "1 + 2 + 3 -> 6", - "1 + 2 + 3 + 4 -> 10" - ]) - - def test_fancy(self): - trace[[len(x)*3 for x in ['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l']]] - - assert(result[-14:] == [ - "'b' * 2 -> 'bb'", - "'b' * 2 + 'q' -> 'bbq'", - "'lo' * 3 -> 'lololo'", - "'lo' * 3 + 'l' -> 'lololol'", - "['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l'] -> ['omg', 'wtf', 'bbq', 'lololol']", - "len(x) -> 3", - "len(x)*3 -> 9", - "len(x) -> 3", - "len(x)*3 -> 9", - "len(x) -> 3", - "len(x)*3 -> 9", - "len(x) -> 7", - "len(x)*3 -> 21", - "[len(x)*3 for x in ['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l']] -> [9, 9, 9, 21]" - ]) - - def test_function_call(self): - trace[sum([sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)])] - assert(result[-5:] == [ - "sum([1, 2, 3]) -> 6", - "min(4, 5, 6) -> 4", - "max(7, 8, 9) -> 9", - "[sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)] -> [6, 4, 9]", - "sum([sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)]) -> 19" - ]) - - - def test_require(self): - with self.assertRaises(AssertionError) as cm: - require[1 == 10] - - assert cm.exception.message == "Require Failed\n1 == 10 -> False" - - require[1 == 1] - - with self.assertRaises(AssertionError) as cm: - require[3**2 + 4**2 != 5**2] - - - require[3**2 + 4**2 == 5**2] - - def test_require_block(self): - with self.assertRaises(AssertionError) as cm: - a = 10 - b = 2 - with require: - a > 5 - a * b == 20 - a < 2 - assert cm.exception.message == "Require Failed\na < 2 -> False" - - - def test_show_expanded(self): - - from macropy.core import ast_repr - show_expanded[q[1 + 2]] - - assert "BinOp(left=Num(n=1), op=Add(), right=Num(n=2))" in result[-1] - - with show_expanded: - a = 1 - b = 2 - with q as code: - print a + u[b + 1] - - assert result[-3] == '\na = 1' - assert result[-2] == '\nb = 2' - assert "code = [Print(dest=None, values=[BinOp(left=Name(id='a', ctx=Load()), op=Add(), right=ast_repr((b + 1)))], nl=True)]" in result[-1] + def test_basic(self): + + log[1 + 2] + log["omg" * 3] + + assert(result[-2:] == [ + "1 + 2 -> 3", + "\"omg\" * 3 -> 'omgomgomg'" + ]) + + def test_combo(self): + + trace[1 + 2 + 3 + 4] + + assert(result[-3:] == [ + "1 + 2 -> 3", + "1 + 2 + 3 -> 6", + "1 + 2 + 3 + 4 -> 10" + ]) + + def test_fancy(self): + trace[[len(x)*3 for x in ['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l']]] + + assert(result[-14:] == [ + "'b' * 2 -> 'bb'", + "'b' * 2 + 'q' -> 'bbq'", + "'lo' * 3 -> 'lololo'", + "'lo' * 3 + 'l' -> 'lololol'", + "['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l'] -> ['omg', 'wtf', 'bbq', 'lololol']", + "len(x) -> 3", + "len(x)*3 -> 9", + "len(x) -> 3", + "len(x)*3 -> 9", + "len(x) -> 3", + "len(x)*3 -> 9", + "len(x) -> 7", + "len(x)*3 -> 21", + "[len(x)*3 for x in ['omg', 'wtf', 'b' * 2 + 'q', 'lo' * 3 + 'l']] -> [9, 9, 9, 21]" + ]) + + def test_function_call(self): + trace[sum([sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)])] + assert(result[-5:] == [ + "sum([1, 2, 3]) -> 6", + "min(4, 5, 6) -> 4", + "max(7, 8, 9) -> 9", + "[sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)] -> [6, 4, 9]", + "sum([sum([1, 2, 3]), min(4, 5, 6), max(7, 8, 9)]) -> 19" + ]) + + + def test_require(self): + with self.assertRaises(AssertionError) as cm: + require[1 == 10] + + assert cm.exception.message == "Require Failed\n1 == 10 -> False" + + require[1 == 1] + + with self.assertRaises(AssertionError) as cm: + require[3**2 + 4**2 != 5**2] + + + require[3**2 + 4**2 == 5**2] + + def test_require_block(self): + with self.assertRaises(AssertionError) as cm: + a = 10 + b = 2 + with require: + a > 5 + a * b == 20 + a < 2 + assert cm.exception.message == "Require Failed\na < 2 -> False" + + + def test_show_expanded(self): + + from macropy.core import ast_repr + show_expanded[q[1 + 2]] + + assert "BinOp(left=Num(n=1), op=Add(), right=Num(n=2))" in result[-1] + + with show_expanded: + a = 1 + b = 2 + with q as code: + print a + u[b + 1] + + assert result[-3] == '\na = 1' + assert result[-2] == '\nb = 2' + assert "code = [Print(dest=None, values=[BinOp(left=Name(id='a', ctx=Load()), op=Add(), right=ast_repr((b + 1)))], nl=True)]" in result[-1] diff --git a/readme.md b/readme.md index 2944871..c628dc9 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -MacroPy 1.0.0 +MacroPy 1.0.2 ============= **MacroPy** is an implementation of [Syntactic Macros](http://tinyurl.com/cmlls8v) in the [Python Programming Language](http://python.org/). MacroPy provides a mechanism for user-defined functions (macros) to perform transformations on the [abstract syntax tree](http://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST) of a Python program at _import time_. This is an easy way to enhance the semantics of a Python program in ways which are otherwise impossible, for example providing an extremely concise way of declaring classes: @@ -51,7 +51,7 @@ The [Reference Documentation](#reference) contains information about: - [Expansion Order](#expansion-order) of nested macros with a file - [Line Numbers](#line-numbers), or what errors you get when something goes wrong. -Or just skip ahead to the [Discussion](#discussion) and [Conclusion](#macropy-bringing-macros-to-python). We're open to contributions, so send us your ideas/questions/issues/pull-requests and we'll do our best to accommodate you! You can ask questions on the [Google Group](https://groups.google.com/forum/#!forum/macropy) or file bugs on the [issues](issues) page. +Or just skip ahead to the [Discussion](#discussion) and [Conclusion](#macropy-bringing-macros-to-python). We're open to contributions, so send us your ideas/questions/issues/pull-requests and we'll do our best to accommodate you! You can ask questions on the [Google Group](https://groups.google.com/forum/#!forum/macropy) or file bugs on thee [issues](issues) page. MacroPy is tested to run on [CPython 2.7.2](http://en.wikipedia.org/wiki/CPython) and [PyPy 2.0](http://pypy.org/), but not on [Jython](http://www.jython.org/). MacroPy is also available on [PyPI](https://pypi.python.org/pypi/MacroPy), using a standard [setup.py](setup.py) to manage dependencies, installation and other things. Check out [this gist](https://gist.github.com/lihaoyi/5577609) for an example of setting it up on a clean system. @@ -1063,7 +1063,7 @@ Pretty neat! This full example of a JSON parser demonstrates what MacroPEG provi - An extremely clear PEG-like syntax - Extremely concise parser definitions -Not bad for an implementation that spans [350 lines of code](macropy/peg.py)! +Not bad for an implementation that spans [350 lines of code](macropy/experimental/peg.py)! Experimental Macros =================== @@ -2442,7 +2442,7 @@ not supported (e.g. Jython). By using the `SaveExporter`, the macro-using code is expanded into plain Python, and although it may rely on MacroPy as a library (e.g. the `CaseClass` -class in [macropy/peg.py](macropy/peg.py)) it won't +class in [macropy/experimental/peg.py](macropy/experimental/peg.py)) it won't need any of MacroPy's import-code-intercepting AST-transforming capabilities at run-time. diff --git a/setup.py b/setup.py index 428589d..7b6514d 100644 --- a/setup.py +++ b/setup.py @@ -66,11 +66,11 @@ class Point(x, y): pass version=__version__, description='Macros for Python: Quasiquotes, Case Classes, LINQ and more!', long_description=__doc__, - license='BSD', + license='MIT', author='Li Haoyi, Justin Holmgren', author_email='haoyi.sg@gmail.com, justin.holmgren@gmail.com', url='https://github.com/lihaoyi/macropy', - packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), + packages=find_packages(exclude=["*.test", "*.test.*"]), extras_require = { 'pyxl': ["pyxl"], 'pinq': ["SQLAlchemy"],