|
7 | 7 | import unittest |
8 | 8 | import unittest.mock |
9 | 9 |
|
| 10 | +from flitter import configure_logger |
10 | 11 | from flitter.model import Vector, Node, StateDict, null, true, false |
11 | 12 | from flitter.language import functions |
12 | 13 | from flitter.language.tree import (Literal, Name, Sequence, |
|
20 | 21 | Binding, PolyBinding, IfCondition) |
21 | 22 |
|
22 | 23 |
|
| 24 | +configure_logger('ERROR') |
| 25 | + |
| 26 | + |
23 | 27 | class SimplifierTestCase(unittest.TestCase): |
24 | 28 | def assertSimplifiesTo(self, x, y, state=None, dynamic=None, static=None, with_errors=None, with_dependencies=None, unbound=None): |
25 | 29 | aliases = set() |
@@ -1036,58 +1040,76 @@ def test_non_callable_literal(self): |
1036 | 1040 |
|
1037 | 1041 | def test_simple_named_inlining(self): |
1038 | 1042 | """Calls to names that resolve to Function objects are inlined as let expressions""" |
1039 | | - func = Function('func', (Binding('x', Literal(null)),), Add(Name('x'), Literal(5)), captures=(), inlineable=True) |
1040 | | - self.assertSimplifiesTo(Call(Name('func'), (Add(Literal(1), Name('y')),), ()), |
| 1043 | + f = Function('f', (Binding('x', Literal(null)),), Add(Name('x'), Literal(5)), captures=(), inlineable=True) |
| 1044 | + self.assertSimplifiesTo(Call(Name('f'), (Add(Literal(1), Name('y')),), ()), |
1041 | 1045 | Let((PolyBinding(('x',), Add(Literal(1), Name('y'))),), Add(Name('x'), Literal(5))), |
1042 | | - static={'func': func}, dynamic={'y'}) |
| 1046 | + static={'f': f}, dynamic={'y'}) |
1043 | 1047 |
|
1044 | 1048 | def test_simple_anonymous_inlining(self): |
1045 | 1049 | """Direct calls to anonymous functions are inlined as let expressions""" |
1046 | | - func = Function('<anon>', (Binding('x', Literal(null)),), Add(Name('x'), Literal(5)), captures=(), inlineable=True) |
1047 | | - self.assertSimplifiesTo(Call(func, (Add(Literal(1), Name('y')),), ()), |
| 1050 | + f = Function('<anon>', (Binding('x', Literal(null)),), Add(Name('x'), Literal(5)), captures=(), inlineable=True) |
| 1051 | + self.assertSimplifiesTo(Call(f, (Add(Literal(1), Name('y')),), ()), |
1048 | 1052 | Let((PolyBinding(('x',), Add(Literal(1), Name('y'))),), Add(Name('x'), Literal(5))), |
1049 | 1053 | dynamic={'y'}) |
1050 | 1054 |
|
1051 | 1055 | def test_simple_inlined_missing_parameter_default(self): |
1052 | 1056 | """An inlined function with a default parameter value used""" |
1053 | | - func = Function('func', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
1054 | | - self.assertSimplifiesTo(Call(Name('func'), (Literal(5),), ()), |
| 1057 | + f = Function('f', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
| 1058 | + self.assertSimplifiesTo(Call(Name('f'), (Literal(5),), ()), |
1055 | 1059 | Literal(6), |
1056 | | - static={'func': func}) |
| 1060 | + static={'f': f}) |
1057 | 1061 |
|
1058 | 1062 | def test_simple_inlined_keyword_argument(self): |
1059 | 1063 | """An inlined function with a keyword argument given""" |
1060 | | - func = Function('func', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
1061 | | - self.assertSimplifiesTo(Call(Name('func'), (Literal(1),), (Binding('y', Literal(2)),)), |
| 1064 | + f = Function('f', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
| 1065 | + self.assertSimplifiesTo(Call(Name('f'), (Literal(1),), (Binding('y', Literal(2)),)), |
1062 | 1066 | Literal(3), |
1063 | | - static={'func': func}) |
| 1067 | + static={'f': f}) |
1064 | 1068 |
|
1065 | 1069 | def test_simple_inlined_missing_default_parameter_used(self): |
1066 | 1070 | """An inlined function with a keyword argument given""" |
1067 | | - func = Function('func', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
1068 | | - self.assertSimplifiesTo(Call(Name('func'), (), ()), |
| 1071 | + f = Function('f', (Binding('x', None), Binding('y', Literal(1))), Add(Name('x'), Name('y')), captures=(), inlineable=True) |
| 1072 | + self.assertSimplifiesTo(Call(Name('f'), (), ()), |
1069 | 1073 | Literal(null), |
1070 | | - static={'func': func}) |
1071 | | - |
1072 | | - def test_inlineable_recursive_non_literal(self): |
1073 | | - """Calls to inlineable, recursive functions are *not* inlined if arguments are not all literal""" |
1074 | | - func = Function( |
1075 | | - 'func', |
1076 | | - (Binding('x', Literal(null)),), |
1077 | | - IfElse((IfCondition(GreaterThan(Name('x'), Literal(0)), Add(Name('x'), Call(Name('func'), (Subtract(Name('x'), Literal(1)),)))),), Literal(0)), |
1078 | | - captures=(), inlineable=True, recursive=True |
1079 | | - ) |
1080 | | - self.assertSimplifiesTo(Call(Name('func'), (Name('y'),)), Call(Name('func'), (Name('y'),)), static={'func': func}, dynamic={'y'}) |
1081 | | - |
1082 | | - def test_inlineable_recursive_literal(self): |
1083 | | - """Calls to inlineable, recursive functions *are* inlined if arguments are all literal""" |
1084 | | - func = Function( |
1085 | | - 'func', |
1086 | | - (Binding('x', Literal(null)),), |
1087 | | - IfElse((IfCondition(GreaterThan(Name('x'), Literal(0)), Add(Name('x'), Call(Name('func'), (Subtract(Name('x'), Literal(1)),)))),), Literal(0)), |
| 1074 | + static={'f': f}) |
| 1075 | + |
| 1076 | + |
| 1077 | +class TestRecursiveCall(SimplifierTestCase): |
| 1078 | + def setUp(self): |
| 1079 | + self.f = Function( |
| 1080 | + 'f', |
| 1081 | + (Binding('x', Literal(null)), Binding('y', Literal(null))), |
| 1082 | + IfElse((IfCondition(GreaterThan(Name('x'), Literal(0)), |
| 1083 | + Add(Name('y'), Call(Name('f'), (Subtract(Name('x'), Literal(1)), Divide(Name('y'), Literal(2)))))),), |
| 1084 | + Literal(0)), |
1088 | 1085 | captures=(), inlineable=True, recursive=True |
1089 | 1086 | ) |
1090 | | - self.assertSimplifiesTo(Call(Name('func'), (Literal(5),)), Literal(15), static={'func': func}) |
| 1087 | + |
| 1088 | + def test_inlineable_recursive_all_non_literal(self): |
| 1089 | + """A call to a recursive function with no literal arguments will not be inlined""" |
| 1090 | + self.assertSimplifiesTo(Call(Name('f'), (Name('z'), Name('w'))), |
| 1091 | + Call(Name('f'), (Name('z'), Name('w'))), |
| 1092 | + static={'f': self.f}, dynamic={'z', 'w'}) |
| 1093 | + |
| 1094 | + def test_inlineable_recursive_literal_bound(self): |
| 1095 | + """A call to a recursive function with at least literal arguments will cause an inline attempt. |
| 1096 | + In this case the literal argument determines the bounds and so recursive inlining will succeed.""" |
| 1097 | + self.assertSimplifiesTo(Call(Name('f'), (Literal(2), Name('w'))), |
| 1098 | + Add(Name('w'), Let((PolyBinding(('y',), Multiply(Literal(0.5), Name('w'))),), Positive(Name('y')))), |
| 1099 | + static={'f': self.f}, dynamic={'w'}) |
| 1100 | + |
| 1101 | + def test_inlineable_recursive_dynamic_bound(self): |
| 1102 | + """A call to a recursive function with at least literal arguments will cause an inline attempt. |
| 1103 | + In this case the literal argument does not determine the bounds and so recursive inlining will fail.""" |
| 1104 | + self.assertSimplifiesTo(Call(Name('f'), (Name('z'), Literal(1))), |
| 1105 | + Call(Name('f'), (Name('z'), Literal(1))), |
| 1106 | + static={'f': self.f}, dynamic={'z'}) |
| 1107 | + |
| 1108 | + def test_inlineable_recursive_all_literal(self): |
| 1109 | + """A call to a recursive function with all literal arguments should be fully simplified""" |
| 1110 | + self.assertSimplifiesTo(Call(Name('f'), (Literal(4), Literal(32))), |
| 1111 | + Literal(32+16+8+4+0), |
| 1112 | + static={'f': self.f}) |
1091 | 1113 |
|
1092 | 1114 |
|
1093 | 1115 | class TestFastFunctions(SimplifierTestCase): |
|
0 commit comments