2 from notebook_preamble import D, J, V, define
7 Given a Joy program like:
23 How would we go about compiling this code (to Python for now)?
25 ## Naive Call Chaining
26 The simplest thing would be to compose the functions from the library:
30 dup, mul = D['dup'], D['mul']
35 def sqr(stack, expression, dictionary):
36 return mul(*dup(stack, expression, dictionary))
55 It's simple to write a function to emit this kind of crude "compiled" code.
59 def compile_joy(name, expression):
60 term, expression = expression
61 code = term +'(stack, expression, dictionary)'
64 term, expression = expression
65 code = format_ % (term, code)
67 def %s(stack, expression, dictionary):
72 def compile_joy_definition(defi):
73 return compile_joy(defi.name, defi.body)
79 print(compile_joy_definition(old_sqr))
82 def sqr(stack, expression, dictionary):
83 return mul(*dup(stack, expression, dictionary))
87 But what about literals?
93 unit, dip = D['unit'], D['dip']
98 # print compile_joy_definition(D['quoted'])
100 # TypeError: can only concatenate tuple (not "str") to tuple
103 For a program like `foo == bar baz 23 99 baq lerp barp` we would want something like:
107 def foo(stack, expression, dictionary):
108 stack, expression, dictionary = baz(*bar(stack, expression, dictionary))
109 return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))
112 You have to have a little discontinuity when going from a symbol to a literal, because you have to pick out the stack from the arguments to push the literal(s) onto it before you continue chaining function calls.
114 ## Compiling Yin Functions
115 Call-chaining results in code that does too much work. For functions that operate on stacks and only rearrange values, what I like to call "Yin Functions", we can do better.
117 We can infer the stack effects of these functions (or "expressions" or "programs") automatically, and the stack effects completely define the semantics of the functions, so we can directly write out a two-line Python function for them. This is already implemented in the `joy.utils.types.compile_()` function.
121 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
122 from joy.library import SimpleFunctionWrapper
126 ---------------------------------------------------------------------------
128 ModuleNotFoundError Traceback (most recent call last)
130 <ipython-input-14-d5ef3c7560be> in <module>
131 ----> 1 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
132 2 from joy.library import SimpleFunctionWrapper
135 ModuleNotFoundError: No module named 'joy.utils.types'
140 stack_effects = infer_string('tuck over dup')
143 Yin functions have only a single stack effect, they do not branch or loop.
147 for fi, fo in stack_effects:
148 print doc_from_stack_effect(fi, fo)
153 source = compile_('foo', stack_effects[0])
156 All Yin functions can be described in Python as a tuple-unpacking (or "-destructuring") of the stack datastructure followed by building up the new stack structure.
165 exec compile(source, '__main__', 'single')
167 D['foo'] = SimpleFunctionWrapper(foo)
171 File "<ipython-input-9-1a7e90bf2d7b>", line 1
172 exec compile(source, '__main__', 'single')
174 SyntaxError: invalid syntax
183 ## Compiling from Stack Effects
185 There are times when you're deriving a Joy program when you have a stack effect for a Yin function and you need to define it. For example, in the Ordered Binary Trees notebook there is a point where we must derive a function `Ee`:
187 [key old_value left right] new_value key [Tree-add] Ee
188 ------------------------------------------------------------
189 [key new_value left right]
191 While it is not hard to come up with this function manually, there is no necessity. This function can be defined (in Python) directly from its stack effect:
194 --------------------------
197 (I haven't yet implemented a simple interface for this yet. What follow is an exploration of how to do it.)
201 from joy.parser import text_to_expression
206 Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later.
208 E = '[%s] [%s]' % (Ein, Eout)
215 (fi, (fo, _)) = text_to_expression(E)
225 Ein = '[a1 a2 a3 a4] a5 a6 a7'
226 Eout = '[a1 a5 a3 a4]'
227 E = '[%s] [%s]' % (Ein, Eout)
234 (fi, (fo, _)) = text_to_expression(E)
245 from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1
254 from joy.utils.types import reify
259 stack_effect = reify(tv, (fi, fo))
260 print doc_from_stack_effect(*stack_effect)
268 Almost, but what we really want is something like this:
272 stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)
275 Note the change of `()` to `JoyStackType` type variables.
279 print doc_from_stack_effect(*stack_effect)
282 Now we can omit `a3` and `a4` if we like:
286 stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)
289 The `right` and `left` parts of the ordered binary tree node are subsumed in the tail of the node's stack/list.
293 print doc_from_stack_effect(*stack_effect)
298 source = compile_('Ee', stack_effect)
302 Oops! The input stack is backwards...
306 stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)
311 print doc_from_stack_effect(*stack_effect)
316 source = compile_('Ee', stack_effect)
322 [key old_value left right] new_value key [Tree-add] Ee
323 ------------------------------------------------------------
324 [key new_value left right]
329 eval(compile(source, '__main__', 'single'))
330 D['Ee'] = SimpleFunctionWrapper(Ee)
335 V('[a b c d] 1 2 [f] Ee')
343 ## Working with Yang Functions
345 Consider the compiled code of `dup`:
352 return (a1, (a1, s23))
357 To compile `sqr == dup mul` we can compute the stack effect:
361 stack_effects = infer_string('dup mul')
362 for fi, fo in stack_effects:
363 print doc_from_stack_effect(fi, fo)
366 Then we would want something like this:
393 stack_effects = infer_string('mul mul sub')
394 for fi, fo in stack_effects:
395 print doc_from_stack_effect(fi, fo)
402 (n1, (n2, (n3, (n4, s23)))) = stack
412 (n1, (n2, (n3, (n4, s23)))) = stack
413 n5 = sub(mul(mul(n1, n2), n3), n4)
426 stack_effects = infer_string('tuck')
427 for fi, fo in stack_effects:
428 print doc_from_stack_effect(fi, fo)
436 ## Compiling Yin~Yang Functions
438 First, we need a source of Python identifiers. I'm going to reuse `Symbol` class for this.
442 from joy.parser import Symbol
450 yield Symbol('a' + str(n))
453 names = _names().next
456 Now we need an object that represents a Yang function that accepts two args and return one result (we'll implement other kinds a little later.)
462 def __init__(self, name):
465 def __call__(self, stack, expression, code):
466 in1, (in0, stack) = stack
468 code.append(('call', out, self.name, (in0, in1)))
469 return (out, stack), expression, code
472 A crude "interpreter" that translates expressions of args and Yin and Yang functions into a kind of simple dataflow graph.
476 def I(stack, expression, code):
478 term, expression = expression
480 stack, expression, _ = term(stack, expression, code)
483 code.append(('pop', term))
490 code.append(('push',) + tuple(s))
494 Something to convert the graph into Python code.
498 strtup = lambda a, b: '(%s, %s)' % (b, a)
499 strstk = lambda rest: reduce(strtup, rest, 'stack')
506 tag, rest = t[0], t[1:]
509 lines.append(strstk(rest) + ' = stack')
512 lines.append('stack = ' + strstk(rest))
515 #out, name, in_ = rest
516 lines.append('%s = %s%s' % rest)
519 raise ValueError(tag)
521 return '\n'.join(' ' + line for line in lines)
524 def coalesce_pops(code):
525 index = [i for i, t in enumerate(code) if t[0] == 'pop']
526 for start, end in yield_groups(index):
528 [tuple(['pop'] + [t for _, t in code[start:end][::-1]])]
531 def yield_groups(index):
533 Yield slice indices for each group of contiguous ints in the
537 for i, (a, b) in enumerate(zip(index, index[1:])):
540 yield index[k], index[i] + 1
543 yield index[k], index[-1] + 1
546 def compile_yinyang(name, expression):
551 ''' % (name, code_gen(I((), expression, [])))
555 A few functions to try it with...
566 from joy.utils.generated_library import *
569 yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}
573 dup = yin_dict['dup']
575 #def dup(stack, expression, code):
577 # return (n, (n, stack)), expression
580 ... and there we are.
584 print compile_yinyang('mul_', (names(), (names(), (mul, ()))))
589 e = (names(), (dup, (mul, ())))
590 print compile_yinyang('sqr', e)
595 e = (names(), (dup, (names(), (sub, (mul, ())))))
596 print compile_yinyang('foo', e)
601 e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))
602 print compile_yinyang('bar', e)
607 e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))
608 print compile_yinyang('to_the_fifth_power', e)