diff options
author | Marion Zepf <marion.zepf@gmail.com> | 2013-10-27 02:40:01 (GMT) |
---|---|---|
committer | Walter Bender <walter@sugarlabs.org> | 2013-10-27 02:40:01 (GMT) |
commit | aad539f9f3b9d6ac2a441873f319e6c20a302ed8 (patch) | |
tree | 389fce70caf5a3b6be780e57974b70ef27746871 /TurtleArt/taexportpython.py | |
parent | d74569b8c23bf48f1c59eea70b0c0281ca6acd5b (diff) |
export python
Diffstat (limited to 'TurtleArt/taexportpython.py')
-rw-r--r-- | TurtleArt/taexportpython.py | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/TurtleArt/taexportpython.py b/TurtleArt/taexportpython.py new file mode 100644 index 0000000..88a73a0 --- /dev/null +++ b/TurtleArt/taexportpython.py @@ -0,0 +1,232 @@ +#Copyright (c) 2013 Marion Zepf + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in +#all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +""" Python export tool """ + +import ast +from gettext import gettext as _ +from os import linesep +import re +import traceback +import util.codegen as codegen + +#from ast_pprint import * # only used for debugging, safe to comment out + +from talogo import LogoCode +from taprimitive import (Primitive, PyExportError, value_to_ast) +from tautils import (debug_output, find_group, find_top_block, get_stack_name) + + + +_SETUP_CODE_START = """\ +#!/usr/bin/env python + +from math import sqrt +from random import uniform + +from pyexported.window_setup import * + + +tw = get_tw() + +BOX = {} +ACTION = {} + + + +""" +_SETUP_CODE_END = """\ + + + +if __name__ == '__main__': + tw.lc.icall(start) + gobject.idle_add(tw.lc.doevalstep) + gtk.main() + + +""" +_ACTION_STACK_START = """\ +def %s(): + turtle = tw.turtles.get_active_turtle() + turtles = tw.turtles + canvas = tw.canvas + logo = tw.lc + +""" +_ACTION_STACK_END = """\ +ACTION["%s"] = %s +""" +# character that is illegal in a Python identifier +PAT_IDENTIFIER_ILLEGAL_CHAR = re.compile("[^A-Za-z0-9_]") + + + +def save_python(tw): + """ Find all the action stacks and turn each into python code """ + all_blocks = tw.just_blocks() + blocks_covered = set() + tops_of_stacks = [] + for block in all_blocks: + if block not in blocks_covered: + top = find_top_block(block) + tops_of_stacks.append(top) + block_stack = find_group(top) + blocks_covered.update(set(block_stack)) + + snippets = [_SETUP_CODE_START] + for block in tops_of_stacks: + stack_name = get_stack_name(block) + if stack_name: + pythoncode = _action_stack_to_python(block, tw.lc, name=stack_name) + snippets.append(pythoncode) + snippets.append(linesep) + snippets.append(_SETUP_CODE_END) + return "".join(snippets) + +def _action_stack_to_python(block, lc, name="start"): + """ Turn a stack of blocks into python code + name -- the name of the action stack (defaults to "start") """ + # traverse the block stack and get the AST for every block + ast_list = _walk_action_stack(block, lc) + ast_list.append(_ast_yield_true()) + action_stack_ast = ast.Module(body=ast_list) + #debug_output(str(action_stack_ast)) + + # serialize the ASTs into python code + generated_code = codegen.to_source(action_stack_ast) + + # wrap the action stack setup code around everything + name_id = _make_identifier(name) + generated_code = _indent(generated_code, 1) + if generated_code.endswith(linesep): + newline = "" + else: + newline = linesep + snippets = [_ACTION_STACK_START % (name_id), + generated_code, + newline, + _ACTION_STACK_END % (name, name_id)] + return "".join(snippets) + +def _walk_action_stack(top_block, lc): + """ Turn a stack of blocks into a list of ASTs """ + block = top_block + + # value blocks don't have a primitive + if block.is_value_block(): + raw_value = block.get_value(add_type_prefix=False) + value_ast = value_to_ast(raw_value) + if value_ast is not None: + return [value_ast] + else: + return [] + + def _get_prim(block): + prim = lc.get_prim_callable(block.primitive) + # fail gracefully if primitive is not a Primitive object + if not isinstance(prim, Primitive): + raise PyExportError(_("block is not exportable"), block=block) + return prim + + prim = _get_prim(block) + + ast_list = [] + arg_asts = [] + + def _finish_off(block, prim=None): + """ Convert block to an AST and add it to the ast_list. Raise a + PyExportError on failure. """ + if prim is None: + prim = _get_prim(block) + if prim.export_me: + try: + new_ast = prim.get_ast(*arg_asts) + except ValueError: + traceback.print_exc() + raise PyExportError(_("error while exporting block"), + block=block) + if isinstance(new_ast, (list, tuple)): + ast_list.extend(new_ast) + elif new_ast is not None: + ast_list.append(new_ast) + elif arg_asts: + new_ast = ast.List(elts=arg_asts, ctx=ast.Load) + ast_list.append(new_ast) + + # skip the very first dock/ connection - it's either the previous block or + # the return value of this block + dock_queue = block.docks[1:] + conn_queue = block.connections[1:] + while dock_queue and conn_queue: + dock = dock_queue.pop(0) + conn = conn_queue.pop(0) + if conn is None or dock[0] == 'unavailable': + continue + elif not dock_queue and dock[0] == 'flow': + # finish off this block + _finish_off(block, prim) + arg_asts = [] + # next block + block = conn + prim = _get_prim(block) + dock_queue = block.docks[1:] + conn_queue = block.connections[1:] + else: + # embedded stack of blocks (body of conditional or loop) or + # argument block + new_arg_asts = _walk_action_stack(conn, lc) + if dock[0] == 'flow': + # body of conditional or loop + if prim == LogoCode.prim_loop: + new_arg_asts.append(_ast_yield_true()) + arg_asts.append(new_arg_asts) + else: + # argument block + arg_asts.append(*new_arg_asts) + + # finish off last block + _finish_off(block, prim) + + return ast_list + +def _make_identifier(name): + """ Turn name into a Python identifier name by replacing illegal + characters """ + replaced = re.sub(PAT_IDENTIFIER_ILLEGAL_CHAR, "_", name) + # TODO find better strategy to avoid number at beginning + if re.match("[0-9]", replaced): + replaced = "_" + replaced + return replaced + +def _indent(code, num_levels=1): + """ Indent each line of code with num_levels * 4 spaces + code -- some python code as a (multi-line) string """ + indentation = " " * (4 * num_levels) + line_list = code.split(linesep) + new_line_list = [] + for line in line_list: + new_line_list.append(indentation + line) + return linesep.join(new_line_list) + +def _ast_yield_true(): + return ast.Yield(value=ast.Name(id='True', ctx=ast.Load)) + + |