Skip to content

Commit

Permalink
compiler_improvements (#8)
Browse files Browse the repository at this point in the history
* Changed expansion level count to 1

* Changed operations to be tree characters instead of one

* Removed expansion level count variable and added check before expanding sub-snippet

* Added cleaning of sub-snippets for right subtree

* Changed docs to be reflect multiple characters for operator in snippet definitions

* Changed docs to reflect changes made to sub-snippet expansion logic
  • Loading branch information
Ahhhhmed committed Apr 12, 2018
1 parent 6bcf986 commit 3cb88bf
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 48 deletions.
42 changes: 24 additions & 18 deletions docs/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ Snippets definition are writen in json files as shown is the following example.
.. code-block:: json
[
{"name": "for","language": "C++","snippet": "for(#){$}"},
{"name": "if","language": "C++","snippet": "if(#){$}"}
{"name": "for","language": "C++","snippet": "for(###){$$$}"},
{"name": "if","language": "C++","snippet": "if(###){$$$}"}
]
Implementation
Expand All @@ -157,7 +157,7 @@ Using this class should be straightforward. Look.
provider = SnippetProvider("C++", ["folder1", "folder2"])
snippet = "for"
snippetExpansion = provider[snippet] # snippetExpansion == "for(#){$}" if used json from above
snippetExpansion = provider[snippet] # snippetExpansion == "for(###){$$$}" if used with json from above
Compiler
^^^^^^^^
Expand All @@ -172,16 +172,19 @@ Consider the following snippet definition.

.. code-block:: json
{"name": "if","language": "C++","snippet": "if(#){$}"}
{"name": "if","language": "C++","snippet": "if(###){$$$}"}
Snippet :code:`if#true$i=3;` is expanded in the following way:

* :code:`if` becomes :code:`if(#){$}` from the definition.
* :code:`#` gets replaced by :code:`true` to get :code:`if(true){$}`.
* :code:`$` gets replaced by :code:`i=3;` to get the final output :code:`if(true){i=3;}`.
* :code:`if` becomes :code:`if(###){$$$}` from the definition.
* :code:`###` gets replaced by :code:`true` to get :code:`if(true){$$$}`.
* :code:`$$$` gets replaced by :code:`i=3;` to get the final output :code:`if(true){i=3;}`.

The value of an operator gets replace by the value provided in the snippet.
This is done for every operator to get the final result.
Note that there are 3 characters in snippet definition and only one in the snippet.
The reason for this is that special characters used by homotopy are also used by other programming languages.
For example, :code:`$` is a part of a variable name in php.

Definition expansion
""""""""""""""""""""
Expand All @@ -194,25 +197,28 @@ This is possible using snippet definitions inside snippets.
.. code-block:: json
[
{"name": "fun","language": "python","snippet": "!({{params}})"},
{"name": "params","language": "python","snippet": "#{{opt_params}}"},
{"name": "opt_params","language": "python","snippet": ", #{{opt_params}}"}
{"name": "fun","language": "python","snippet": "!!!({{params}})"},
{"name": "params","language": "python","snippet": "###{{opt_params}}"},
{"name": "opt_params","language": "python","snippet": ", ###{{opt_params}}"}
]
Snippet :code:`fun!foo#a#b` is expanded in the following way:

* :code:`fun` becomes :code:`!({{params}})` from the definition.
* :code:`!` gets replaced by :code:`foo` to get :code:`foo({{params}})`.
* :code:`#` does not exist in :code:`foo({{params}})` so :code:`{{params}}` get expanded to :code:`#{{opt_params}}`.
* :code:`#` gets replaced by :code:`a;` to get :code:`foo(a{{opt_params}})`
* :code:`#` does not exist in :code:`foo(a{{opt_params}})` so :code:`{{opt_params}}`
get expanded to :code:`, #{{opt_params}}`.
* :code:`#` gets replaced by :code:`b;` to get :code:`foo(a, b{{opt_params}})`.
* :code:`fun` becomes :code:`!!!({{params}})` from the definition.
* :code:`!!!` gets replaced by :code:`foo` to get :code:`foo({{params}})`.
* :code:`###` does not exist in :code:`foo({{params}})` so :code:`{{params}}` get expanded to :code:`###{{opt_params}}`.
* :code:`###` gets replaced by :code:`a;` to get :code:`foo(a{{opt_params}})`
* :code:`###` does not exist in :code:`foo(a{{opt_params}})` so :code:`{{opt_params}}`
get expanded to :code:`, ###{{opt_params}}`.
* :code:`###` gets replaced by :code:`b;` to get :code:`foo(a, b{{opt_params}})`.
* :code:`{{opt_params}}` gets removed from final result to get :code:`foo(a,b)`.

Expansion is done in case `simple substitution`_ can't be done.
This enables recursive constructs as shown in the example above.
Number of expansion performed is capped to prevent infinite recursions.

Note that there might be multiple sub-snippets inside a single snippet.
In that case the first one containing required operator in its definition gets expanded.
Other sub-snippets do not get expanded.

Utilities
^^^^^^^^^
Expand Down
35 changes: 21 additions & 14 deletions homotopy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,34 @@


class Compiler(SnippetVisitor):
def __init__(self, expansion_level_count=2):
self.expansion_level_count = expansion_level_count

def visit_composite_snippet(self, composite_snippet):
left_side = snippetProvider[self.visit(composite_snippet.left)]
right_side = snippetProvider[self.visit(composite_snippet.right)]
right_side = snippetProvider[self.compile(composite_snippet.right)]

operation_text = composite_snippet.operation*3

if composite_snippet.operation in left_side:
return left_side.replace(composite_snippet.operation, right_side)
if operation_text in left_side:
return left_side.replace(operation_text, right_side)
else:
expanded_left_side = left_side
match_found = False

def expansion_function(match_object):
nonlocal match_found

if not match_found and operation_text in snippetProvider[match_object.group(1)]:
match_found = True
return snippetProvider[match_object.group(1)]

return match_object.group(0)

for _ in range(self.expansion_level_count):
expanded_left_side = re.sub(
r'{{(.*?)}}',
lambda match_object: snippetProvider[match_object.group(1)],
expanded_left_side,
count=1)
expanded_left_side = re.sub(
r'{{(.*?)}}',
expansion_function,
expanded_left_side)

if composite_snippet.operation in expanded_left_side:
return expanded_left_side.replace(composite_snippet.operation, right_side)
if operation_text in expanded_left_side:
return expanded_left_side.replace(operation_text, right_side)

logging.warning("No match found. Ignoring right side of the snippet.")
return left_side
Expand Down
59 changes: 43 additions & 16 deletions test/testCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ def test_compile(self, mock_provider):
Compiler().compile(st.Snippet())

data = {
"for": "for # in !:\n\tpass",
"def": "def !({{params}}):\n\tpass",
"params": "#{{opt_params}}",
"opt_params": ", #{{opt_params}}",
"a1": "{{a2}}",
"a2": "{{a3}}",
"a3": "{{a4}}",
"a4": "#"
"for": "for ### in !!!:\n\tpass",
"def": "def !!!({{params}}):\n\tpass",
"params": "###{{opt_params}}",
"opt_params": ", ###{{opt_params}}",
"multiple": "{{goo}}{{doo}}",
"goo": "goo ###",
"doo": "doo $$$"
}

mock_provider.side_effect = lambda x: x if x not in data else data[x]
Expand All @@ -33,27 +32,55 @@ def test_compile(self, mock_provider):
'for i in data:\n\tpass'
)

self.assertEqual(
Compiler().compile(st.CompositeSnippet(
st.SimpleSnippet('multiple'),
'$',
st.SimpleSnippet('asd')
)),
'doo asd'
)

self.assertEqual(
Compiler().compile(st.CompositeSnippet(
st.SimpleSnippet('multiple'),
'#',
st.SimpleSnippet('asd')
)),
'goo asd'
)

with patch('logging.warning', MagicMock()) as m:
self.assertEqual(
Compiler().compile(st.CompositeSnippet(
st.CompositeSnippet(st.SimpleSnippet('for'), '#', st.SimpleSnippet('i')),
'%',
st.SimpleSnippet('data')
)),
'for i in !:\n\tpass'
'for i in !!!:\n\tpass'
)

m.assert_called_once_with("No match found. Ignoring right side of the snippet.")

with patch('logging.warning', MagicMock()) as m:
self.assertEqual(
Compiler().compile(st.CompositeSnippet(st.SimpleSnippet('a1'), '#', st.SimpleSnippet('i'))),
''
)

self.assertEqual(
Compiler(expansion_level_count=3).compile(st.CompositeSnippet(st.SimpleSnippet('a1'), '#', st.SimpleSnippet('i'))),
'i'
Compiler().compile(st.CompositeSnippet(
st.CompositeSnippet(
st.SimpleSnippet('doo'),
'$',
st.CompositeSnippet(
st.CompositeSnippet(
st.SimpleSnippet('def'),
'!',
st.SimpleSnippet('foo')
),
'#',
st.SimpleSnippet('a')
)),
'#',
st.SimpleSnippet('b')
)),
'doo def foo(a):\n\tpass'
)

m.assert_called_once_with("No match found. Ignoring right side of the snippet.")
Expand Down

0 comments on commit 3cb88bf

Please sign in to comment.