Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/TurtleArt
diff options
context:
space:
mode:
authorMarion 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)
commitaad539f9f3b9d6ac2a441873f319e6c20a302ed8 (patch)
tree389fce70caf5a3b6be780e57974b70ef27746871 /TurtleArt
parentd74569b8c23bf48f1c59eea70b0c0281ca6acd5b (diff)
export python
Diffstat (limited to 'TurtleArt')
-rw-r--r--TurtleArt/taexportpython.py232
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))
+
+