3 from notebook_preamble import D, J, V, define
8 Given a Joy program like:
28 How would we go about compiling this code (to Python for now)?
33 The simplest thing would be to compose the functions from the library:
37 dup, mul = D['dup'], D['mul']
41 def sqr(stack, expression, dictionary):
42 return mul(*dup(stack, expression, dictionary))
61 It's simple to write a function to emit this kind of crude "compiled"
66 def compile_joy(name, expression):
67 term, expression = expression
68 code = term +'(stack, expression, dictionary)'
71 term, expression = expression
72 code = format_ % (term, code)
74 def %s(stack, expression, dictionary):
79 def compile_joy_definition(defi):
80 return compile_joy(defi.name, defi.body)
85 print(compile_joy_definition(old_sqr))
90 def sqr(stack, expression, dictionary):
91 return mul(*dup(stack, expression, dictionary))
95 But what about literals?
103 unit, dip = D['unit'], D['dip']
107 # print compile_joy_definition(D['quoted'])
109 # TypeError: can only concatenate tuple (not "str") to tuple
111 For a program like ``foo == bar baz 23 99 baq lerp barp`` we would want
116 def foo(stack, expression, dictionary):
117 stack, expression, dictionary = baz(*bar(stack, expression, dictionary))
118 return barp(*lerp(*baq((99, (23, stack)), expression, dictionary)))
120 You have to have a little discontinuity when going from a symbol to a
121 literal, because you have to pick out the stack from the arguments to
122 push the literal(s) onto it before you continue chaining function calls.
124 Compiling Yin Functions
125 -----------------------
127 Call-chaining results in code that does too much work. For functions
128 that operate on stacks and only rearrange values, what I like to call
129 "Yin Functions", we can do better.
131 We can infer the stack effects of these functions (or "expressions" or
132 "programs") automatically, and the stack effects completely define the
133 semantics of the functions, so we can directly write out a two-line
134 Python function for them. This is already implemented in the
135 ``joy.utils.types.compile_()`` function.
139 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
140 from joy.library import SimpleFunctionWrapper
146 ---------------------------------------------------------------------------
148 ModuleNotFoundError Traceback (most recent call last)
150 <ipython-input-14-d5ef3c7560be> in <module>
151 ----> 1 from joy.utils.types import compile_, doc_from_stack_effect, infer_string
152 2 from joy.library import SimpleFunctionWrapper
155 ModuleNotFoundError: No module named 'joy.utils.types'
160 stack_effects = infer_string('tuck over dup')
162 Yin functions have only a single stack effect, they do not branch or
167 for fi, fo in stack_effects:
168 print doc_from_stack_effect(fi, fo)
172 source = compile_('foo', stack_effects[0])
174 All Yin functions can be described in Python as a tuple-unpacking (or
175 "-destructuring") of the stack datastructure followed by building up the
184 exec compile(source, '__main__', 'single')
186 D['foo'] = SimpleFunctionWrapper(foo)
192 File "<ipython-input-9-1a7e90bf2d7b>", line 1
193 exec compile(source, '__main__', 'single')
195 SyntaxError: invalid syntax
203 Compiling from Stack Effects
204 ----------------------------
206 There are times when you're deriving a Joy program when you have a stack
207 effect for a Yin function and you need to define it. For example, in the
208 Ordered Binary Trees notebook there is a point where we must derive a
213 [key old_value left right] new_value key [Tree-add] Ee
214 ------------------------------------------------------------
215 [key new_value left right]
217 While it is not hard to come up with this function manually, there is no
218 necessity. This function can be defined (in Python) directly from its
224 --------------------------
227 (I haven't yet implemented a simple interface for this yet. What follow
228 is an exploration of how to do it.)
232 from joy.parser import text_to_expression
236 Ein = '[a b c d] e a [f]' # The terms should be reversed here but I don't realize that until later.
238 E = '[%s] [%s]' % (Ein, Eout)
244 (fi, (fo, _)) = text_to_expression(E)
252 Ein = '[a1 a2 a3 a4] a5 a6 a7'
253 Eout = '[a1 a5 a3 a4]'
254 E = '[%s] [%s]' % (Ein, Eout)
260 (fi, (fo, _)) = text_to_expression(E)
269 from joy.library import a1, a2, a3, a4, a5, a6, a7, s0, s1
277 from joy.utils.types import reify
281 stack_effect = reify(tv, (fi, fo))
282 print doc_from_stack_effect(*stack_effect)
288 Almost, but what we really want is something like this:
292 stack_effect = eval('(((a1, (a2, (a3, (a4, s1)))), (a5, (a6, (a7, s0)))), ((a1, (a5, (a3, (a4, s1)))), s0))', tv)
294 Note the change of ``()`` to ``JoyStackType`` type variables.
298 print doc_from_stack_effect(*stack_effect)
300 Now we can omit ``a3`` and ``a4`` if we like:
304 stack_effect = eval('(((a1, (a2, s1)), (a5, (a6, (a7, s0)))), ((a1, (a5, s1)), s0))', tv)
306 The ``right`` and ``left`` parts of the ordered binary tree node are
307 subsumed in the tail of the node's stack/list.
311 print doc_from_stack_effect(*stack_effect)
315 source = compile_('Ee', stack_effect)
318 Oops! The input stack is backwards...
322 stack_effect = eval('((a7, (a6, (a5, ((a1, (a2, s1)), s0)))), ((a1, (a5, s1)), s0))', tv)
326 print doc_from_stack_effect(*stack_effect)
330 source = compile_('Ee', stack_effect)
337 [key old_value left right] new_value key [Tree-add] Ee
338 ------------------------------------------------------------
339 [key new_value left right]
343 eval(compile(source, '__main__', 'single'))
344 D['Ee'] = SimpleFunctionWrapper(Ee)
348 V('[a b c d] 1 2 [f] Ee')
351 Working with Yang Functions
352 ---------------------------
354 Consider the compiled code of ``dup``:
361 return (a1, (a1, s23))
365 To compile ``sqr == dup mul`` we can compute the stack effect:
369 stack_effects = infer_string('dup mul')
370 for fi, fo in stack_effects:
371 print doc_from_stack_effect(fi, fo)
373 Then we would want something like this:
391 stack_effects = infer_string('mul mul sub')
392 for fi, fo in stack_effects:
393 print doc_from_stack_effect(fi, fo)
399 (n1, (n2, (n3, (n4, s23)))) = stack
409 (n1, (n2, (n3, (n4, s23)))) = stack
410 n5 = sub(mul(mul(n1, n2), n3), n4)
418 stack_effects = infer_string('tuck')
419 for fi, fo in stack_effects:
420 print doc_from_stack_effect(fi, fo)
423 Compiling Yin~Yang Functions
424 ----------------------------
426 First, we need a source of Python identifiers. I'm going to reuse
427 ``Symbol`` class for this.
431 from joy.parser import Symbol
438 yield Symbol('a' + str(n))
441 names = _names().next
443 Now we need an object that represents a Yang function that accepts two
444 args and return one result (we'll implement other kinds a little later.)
450 def __init__(self, name):
453 def __call__(self, stack, expression, code):
454 in1, (in0, stack) = stack
456 code.append(('call', out, self.name, (in0, in1)))
457 return (out, stack), expression, code
459 A crude "interpreter" that translates expressions of args and Yin and
460 Yang functions into a kind of simple dataflow graph.
464 def I(stack, expression, code):
466 term, expression = expression
468 stack, expression, _ = term(stack, expression, code)
471 code.append(('pop', term))
478 code.append(('push',) + tuple(s))
481 Something to convert the graph into Python code.
485 strtup = lambda a, b: '(%s, %s)' % (b, a)
486 strstk = lambda rest: reduce(strtup, rest, 'stack')
493 tag, rest = t[0], t[1:]
496 lines.append(strstk(rest) + ' = stack')
499 lines.append('stack = ' + strstk(rest))
502 #out, name, in_ = rest
503 lines.append('%s = %s%s' % rest)
506 raise ValueError(tag)
508 return '\n'.join(' ' + line for line in lines)
511 def coalesce_pops(code):
512 index = [i for i, t in enumerate(code) if t[0] == 'pop']
513 for start, end in yield_groups(index):
515 [tuple(['pop'] + [t for _, t in code[start:end][::-1]])]
518 def yield_groups(index):
520 Yield slice indices for each group of contiguous ints in the
524 for i, (a, b) in enumerate(zip(index, index[1:])):
527 yield index[k], index[i] + 1
530 yield index[k], index[-1] + 1
533 def compile_yinyang(name, expression):
538 ''' % (name, code_gen(I((), expression, [])))
541 A few functions to try it with...
551 from joy.utils.generated_library import *
554 yin_dict = {name: SimpleFunctionWrapper(func) for name, func in import_yin().iteritems()}
558 dup = yin_dict['dup']
560 #def dup(stack, expression, code):
562 # return (n, (n, stack)), expression
564 ... and there we are.
568 print compile_yinyang('mul_', (names(), (names(), (mul, ()))))
572 e = (names(), (dup, (mul, ())))
573 print compile_yinyang('sqr', e)
577 e = (names(), (dup, (names(), (sub, (mul, ())))))
578 print compile_yinyang('foo', e)
582 e = (names(), (names(), (mul, (dup, (sub, (dup, ()))))))
583 print compile_yinyang('bar', e)
587 e = (names(), (dup, (dup, (mul, (dup, (mul, (mul, ())))))))
588 print compile_yinyang('to_the_fifth_power', e)