Skip to content
Browse files

Fix xml escaping for import

  • Loading branch information...
1 parent 91f4133 commit 0af6c20b1ad350cbe82d5ea2cf8e7880c8be98ba @davidcox committed Apr 13, 2012
Showing with 157 additions and 54 deletions.
  1. +88 −16 mwx/ast/ast.py
  2. +18 −0 mwx/ast/xml_import.py
  3. +51 −38 mwx/parser.py
View
104 mwx/ast/ast.py
@@ -1,12 +1,15 @@
from xml.sax.saxutils import escape
from copy import deepcopy, copy
+import re
PRIMARY_ARG_STRING = "arg"
from mwx.constants import shorthand_actions
isiterable = lambda x: getattr(x, "__iter__", False)
tab = " "
+use_declaration_style_syntax = True
+use_declaration_style_syntax_for_states = True
def flatten(x):
@@ -47,6 +50,9 @@ def quote_once(x):
def mwx_properties_block(props):
+ if len(props.keys()) == 0:
+ return ''
+
props = copy(props)
tag = props.pop('tag', None)
@@ -63,6 +69,24 @@ def mwx_properties_block(props):
return output_string
+def mwx_declaration(obj_type, props, declaration_style_syntax=use_declaration_style_syntax):
+
+ output_string = obj_type
+
+ if declaration_style_syntax:
+ props = copy(props)
+ tag = props.pop('tag')
+
+ if re.search(r'\s|[$@]', tag):
+ props['tag'] = tag # put it back
+ else:
+ output_string += ' ' + tag
+
+ output_string += mwx_properties_block(props)
+
+ return output_string
+
+
def mwx_child_block(children, tablevel=0):
if len(children) == 0:
@@ -141,7 +165,7 @@ class MWASTNode(object):
def __init__(self, obj_type, tag=None, props={}, children=[]):
self.obj_type = obj_type
- self.props = deepcopy(props)
+ self.props = copy(props)
if self.props == None or self.props == '':
self.props = {}
@@ -246,8 +270,7 @@ def to_mwx(self, tablevel=0):
output_string += ''.join([to_mwx(c) for c in self.children])
return output_string
- output_string += self.obj_type
- output_string += mwx_properties_block(self.props)
+ output_string += mwx_declaration(self.obj_type, self.props)
output_string += mwx_child_block(self.children, tablevel)
@@ -306,6 +329,61 @@ def to_mwx(self):
return ''.join([to_mwx(c) for c in self.children])
+class MWVariable(MWASTNode):
+
+ def __init__(self, tag, default=None, scope='global', var_type=None, props={}, children=[]):
+
+ try:
+ props = copy(props)
+ except:
+ print props
+ raise('shit!!')
+ if isinstance(props, str):
+ props = {}
+
+ if props is None:
+ props = {}
+
+ if default is not None:
+ props['default_value'] = default
+
+ if scope is None:
+ scope = 'global'
+
+ if var_type is None or var_type == 'var':
+ var_type = 'float'
+
+ props['scope'] = scope
+
+ props['type'] = var_type
+
+ MWASTNode.__init__(self, 'variable', tag, props=props, children=children)
+
+ def to_mwx(self, tablevel=0):
+
+ props = copy(self.props)
+
+ tag = props.pop('tag')
+ scope = props.pop('scope', 'global').lower()
+ var_type = props.pop('type', 'var').lower()
+
+ default = props.pop('default_value', '0.0')
+
+ s = tab * tablevel
+
+ if scope == 'local':
+ s += 'local '
+
+ s += var_type + ' '
+ s += tag
+ s += mwx_properties_block(props)
+ s += ' = %s' % default
+
+ s += '\n'
+
+ return s
+
+
class Action (MWASTNode):
"""A custom node for representing actions. Provides infrastructure for
handling a default arg, and automatically generating a convenience
@@ -449,10 +527,9 @@ def to_mwx(self, tablevel=0):
tabs = tab * tablevel
output_string = tabs
- output_string += self.obj_type
-
- # [key=val, ...]
- output_string += mwx_properties_block(self.props)
+ output_string += mwx_declaration(self.obj_type,
+ self.props,
+ use_declaration_style_syntax_for_states)
# { action\n action\n ... }
output_string += mwx_child_block(self.actions, tablevel)
@@ -472,7 +549,6 @@ class Transition (MWASTNode):
"""
def __init__(self, condition=None, target=None, **kwargs):
-
alt_tag = "%s -> %s" % (to_mwx(condition), to_mwx(target))
kwargs['alt_tag'] = alt_tag
kwargs['tag'] = alt_tag
@@ -490,7 +566,7 @@ def to_mwx(self, tablevel=0):
condition = self.props['condition']
target = self.props['target']
- output_string += "%s -> %s" % (to_mwx(condition),
+ output_string += "%s -> %s" % (to_mwx(condition, quote_strings=False),
to_mwx(target))
output_string += '\n'
@@ -571,7 +647,7 @@ def __init__(self, identifier, fargs=[], **kwargs):
def to_mwx(self, tablevel=0):
return self.to_infix()
- def to_infix(self):
+ def to_infix(self, quote_strings=False):
out = self.props['tag']
out += '(' + to_mwx(self.children) + ')'
return out
@@ -580,7 +656,6 @@ def __str__(self):
return self.to_mwx()
-
class MWExpression (MWASTNode):
"""A node representing an arithmetic expression"""
def __init__(self, operator=None, operands=None, **kwargs):
@@ -622,10 +697,7 @@ def to_mwx(self, tablevel=0):
def to_xml(self):
return self.to_infix()
- def to_infix(self, strip_quotes=False):
-
- # qs = not strip_quotes
- # output_string = ""
+ def to_infix(self, quote_strings=False):
if len(self.children) == 1:
operand = to_infix(self.children[0])
@@ -638,7 +710,7 @@ def to_infix(self, strip_quotes=False):
# this is a bit of a hack for now
# evaluate an expression at parse time (e.g. for macro control flow)
def eval(self):
- eval_string = self.to_infix(strip_quotes=True)
+ eval_string = self.to_infix(quote_strings=False)
try:
return eval(eval_string)
except:
View
18 mwx/ast/xml_import.py
@@ -49,6 +49,21 @@ def action(self, node, parent=None, parent_ctx=None, index=None):
@registered
+class PromoteVariables (TreeWalker):
+
+ def trigger(self, node):
+ return (isinstance(node, MWASTNode) and
+ node.obj_type == 'variable')
+
+ def action(self, node, parent=None, parent_ctx=None, index=None):
+
+ replacement = MWVariable(node.tag, props=node.props, children=node.children)
+ parent.rewrite(parent_ctx, index, replacement)
+
+ return replacement
+
+
+@registered
class DeleteMarkers (TreeWalker):
def trigger(self, node):
@@ -105,6 +120,9 @@ def action(self, node, parent=None, parent_ctx=None, index=None):
if transition_type == 'conditional':
condition = node.props.get('condition', None)
+ elif transition_type == 'timer_expired':
+ timer = node.props.get('timer')
+ condition = 'timer_expired(%s)' % timer
else:
condition = MWKeyword('always')
View
89 mwx/parser.py
@@ -18,18 +18,6 @@
# Helper functions
-def nested_array_to_dict(a):
- """Convert a nested array of key-value pairs (from pyparsing) to a
- dictionary
- """
- d = {}
-
- if a is not None and a != '':
- for pair in a:
- d[pair[0]] = pair[1]
- return d
-
-
def list_to_literals(list_of_names):
"""Convert a list of strings to an OR'd sequence of pyparsing Literal
objects
@@ -86,6 +74,7 @@ def quoted_string_fn(drop_quotes=True):
return qs
+
class MWXParser:
"""A parser object for the 'MWX' lightweight MWorks DSL."""
@@ -326,7 +315,7 @@ def unary_expression_helper(pr):
generic_action = (action_name("type") + arg_list_open -
Optional(value)("arg") + Optional(property_list)("props") +
arg_list_close)
- generic_action.setParseAction(lambda a: Action(a.type, a.arg, props=a.props))
+ generic_action.setParseAction(lambda a: Action(a.type, a.arg, props=dict(a.props)))
assignment_action = NotAny(def_keyword) + identifier("variable") + assign - value("value")
assignment_action.setParseAction(lambda a: AssignmentAction(a.variable, a.value))
@@ -350,28 +339,28 @@ def unary_expression_helper(pr):
valid_tag = (quoted_string_fn(True) | template_reference)
unquoted_tag = identifier
- std_obj_decl << ((
- (object_name("obj_type") + # "alt" syntax
- unquoted_tag("tag") -
- prop_list_open)
+ def decl_and_properties(object_name_combinator):
+ return ((object_name_combinator + # "alt" syntax
+ unquoted_tag("tag") -
+ Optional(prop_list_open -
+ Optional(property_list)('props') +
+ prop_list_close))
- | # OR
+ | # OR
- (object_name("obj_type") + # "regular" syntax
+ (object_name_combinator + # "regular" syntax
prop_list_open -
- Optional(valid_tag)("tag")) +
- Optional(Suppress(","))
-
- ) +
+ Optional(valid_tag + Optional(Suppress(',')))("tag") +
+ Optional(property_list)("props") +
+ prop_list_close))
- (Optional(property_list)("props") + # remaining syntax
- prop_list_close +
- Optional(block(object_declaration, "children") +
- LineEnd()))
+ std_obj_decl << (decl_and_properties(object_name("obj_type")) +
+ Optional(block(object_declaration, "children") + # remaining syntax
+ LineEnd())
)
std_obj_decl.setParseAction(lambda c: MWASTNode(c.obj_type, c.tag,
- props=nested_array_to_dict(c.props),
+ props=dict(c.props),
children=getattr(c, 'children', [])))
transition = ((dummy_token("transition") | macro_element |
@@ -391,20 +380,31 @@ def unary_expression_helper(pr):
else:
state_payload = block(action, "actions") + transition_list_marker + block(transition, "transitions")
- state = Literal("state") - \
- prop_list_open + \
- Optional(valid_tag)("tag") + Optional(Suppress(",")) + Optional(property_list)("props") + \
- prop_list_close + \
- state_payload
+ state = decl_and_properties(Literal('state')) + state_payload
- state.setParseAction(lambda s: State(s.tag, props=s.props, actions=s.actions, transitions=s.transitions))
+ state.setParseAction(lambda s: State(s.tag, props=dict(s.props), actions=s.actions, transitions=s.transitions))
# ------------------------------
# Variable Declarations
# ------------------------------
- variable_declaration = LineStart() + Literal("var") - identifier("tag") + Optional(assign + value("default"))
- variable_declaration.setParseAction(lambda x: MWASTNode("variable", x.tag, props={'default': x.default}))
+ scope_type = (Literal('global') | Literal('local'))
+ variable_type = (dummy_token('variabletype') |
+ Literal('integer') |
+ Literal('float') |
+ Literal('bool') |
+ Literal('string') |
+ Literal('struct') |
+ Literal('var'))
+
+ variable_declaration = (LineStart() +
+ Optional(scope_type) +
+ variable_type('type') -
+ identifier("tag") +
+ Optional(prop_list_open + Optional(property_list('props')) + prop_list_close) + # property list
+ Optional(assign + value("default"))) # default value assignment
+
+ variable_declaration.setParseAction(lambda x: MWVariable(x.tag, default=x.default, props=dict(x.props)))
# ----------------------------------------------------
# Top-level object declarations and aliases thereof
@@ -495,21 +495,34 @@ class MWXMLParser:
def __init__(self):
self.handlers = {}
+ def mwxml_unescape(self, s):
+ replacements = {'#GT': '>',
+ '#LT': '<',
+ '#GE': '>=',
+ '#LE': '<=',
+ '#AND': '&&',
+ '#OR': '||'
+ }
+ for a, b in replacements.items():
+ s = re.sub(r'%s' % a, b, s)
+
+ return s
+
def xml_element_to_ast(self, element):
"""Converts an xml element to an MWASTNode"""
props = {}
children = []
for key in element.keys():
- props[key] = element.get(key)
+ props[key] = self.mwxml_unescape(element.get(key))
for child in element.getchildren():
children.append(self.xml_element_to_ast(child))
return MWASTNode(element.tag, element.get('tag'),
props=deepcopy(props), children=children)
- def parse_string(self, s, process_templates=True):
+ def parse_string(self, s, process_templates=True, base_path='.'):
"""Process a string containing MW XML, and return a tree of MWASTNode
objects
"""

0 comments on commit 0af6c20

Please sign in to comment.
Something went wrong with that request. Please try again.