git.haldean.org ubik / 91fa3a4
more fully-thought-out design for inner recursion Haldean Brown 5 years ago
4 changed file(s) with 81 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
1111 - more tests for rational functions
1212 - short-aliases for arithmetic
1313 - matches against nonatomic expressions (? f x { . A => ...)
14 - fix all applications in codegen to set recursive_app = false
7575 implementation of this feature, anonymous-t does not have a name in the
7676 global namespace; it is fully anonymous and is only defined by its
7777 value.
78
79 In order to accomplish this, the closure transformation code marks the
80 application of the function to itself as the application of a recursion
81 parameter, but otherwise performs the closure transformation normally.
82 In the above example, this then becomes:
83
84 : t = (\t x -> ? {
85 . eq x 0 => "ok\n"
86 . => t 0
87 }) *t
88
89 With the asterisk here being shorthand for is-recursive-parameter. Then,
90 in the code-generation stage, when the code generation visitor sees an
91 apply statement with a recursion parameter, it transforms:
92
93 (f *f)
94
95 Into a new top-level value inserted into the workspace and a reference
96 to that value, replacing the recursion parameter with a reference to the
97 same value.
98
99 &f = f
100 ((const &f) (const &f))
101
102 The closure transformation is maintained on f, allowing it to
103 self-recurse without any self-references.
104
105 This gets a little more complicated when there are multiple closed
106 parameters being applied. Take this useless function as an example:
107
108 {
109 : x = 8
110 : f = \y -> f x
111 }
112
113 Both of these would be valid closure transformations for f:
114
115 : f = (\f x y -> f x) *f x
116 : f = (\x f y -> f x) x *f
117
118 And their associated recusion transformations are:
119
120 : anonymous-f = \f x y -> f x
121 : f = anonymous-f anonymous-f x
122
123 : anonymous-f = \x f y -> f x
124 : f = anonymous-f x anonymous-f
125
126 There are no statements here that look exactly like our (f *f) pattern
127 above, though, in the example where f is not the first closure
128 parameter. We end up with:
129
130 ((f x) *f)
131
132 And (f x) isn't extractable, because x is local. To get around this, the
133 code generation stage needs to examine the full apply tree to check if
134 an apply chain contains a recursion parameter, and if it does, it needs
135 to extract the leftmost function value into its own top-level value.
136
137 Note that top level bindings do not need this transformation, because
138 they can reference themselves by name. So this function at the top
139 level:
140
141 ~ p
142 : f = \t -> f 0
143
144 Gets turned into the following pseudo-bytecode:
145
146 000 INPUT 0
147 001 LOAD "p:f"
148 002 CONST 0
149 003 APPLY 001 002
150
151 and the LOAD breaks the cycle.
113113 {
114114 struct ubik_ast_expr *head;
115115 struct ubik_ast_expr *tail;
116 /* Marks whether this represents an application
117 * of a function to itself. For more
118 * information, see docs/recursion.txt. */
119 bool recursive_app;
116120 } apply;
117121 struct
118122 {
7878 apply1->scope = to_match->scope;
7979 apply1->apply.head = native_func_atom;
8080 apply1->apply.tail = ctor_name_atom;
81 apply1->apply.recursive_app = false;
8182 apply1->loc = res->loc;
8283
8384 res->expr_type = EXPR_APPLY;
8485 res->scope = to_match->scope;
8586 res->apply.head = apply1;
8687 res->apply.tail = to_match_copy;
88 res->apply.recursive_app = false;
8789
8890 return OK;
8991 }