Skip to content

Commit

Permalink
context (#14)
Browse files Browse the repository at this point in the history
* Add failing test

* Implement ContextManager class

* Add docstrings to ContextManager class

* Implement scoping in compiler

* Add another test case
  • Loading branch information
Ahhhhmed committed Apr 16, 2018
1 parent 27b1890 commit e0cba2c
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 7 deletions.
87 changes: 82 additions & 5 deletions homotopy/compiler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from homotopy.syntax_tree import SnippetVisitor
from homotopy.snippet_provider import snippetProvider
from homotopy.parser import Parser

import re
import logging
Expand All @@ -9,20 +10,32 @@ class Compiler(SnippetVisitor):
"""
Compiler for snippets. Turns syntax tree into text.
"""
def __init__(self):
self.context_manager = ContextManager()
self.context_manager.new_scope()

def visit_composite_snippet(self, composite_snippet):
"""
Replace right site in the appropriate place in the left side.
Expand left side if needed. Manage scopes.
:param composite_snippet: Composite snippet
:return: Text of left side replaced with right side
"""
left_side = snippetProvider[self.visit(composite_snippet.left)]
left_side = self.expand_variable_operators(snippetProvider[self.visit(composite_snippet.left)])

if composite_snippet.operation == Parser.in_operator:
self.context_manager.new_scope()

right_side = snippetProvider[self.compile(composite_snippet.right)]

if composite_snippet.operation == Parser.in_operator:
self.context_manager.remove_scope()

operation_text = composite_snippet.operation*3

if operation_text in left_side:
return left_side.replace(operation_text, right_side)
return self.substitute(left_side, operation_text, right_side)
else:
expanded_left_side = left_side
match_found = False
Expand All @@ -42,7 +55,7 @@ def expansion_function(match_object):
expanded_left_side)

if operation_text in expanded_left_side:
return expanded_left_side.replace(operation_text, right_side)
return self.substitute(expanded_left_side, operation_text, right_side)

logging.warning("No match found. Ignoring right side of the snippet.")
return left_side
Expand All @@ -54,11 +67,28 @@ def visit_simple_snippet(self, simple_snippet):
:param simple_snippet: Simple snippet
:return: Text of compile snippet
"""
return snippetProvider[simple_snippet.value]
return self.expand_variable_operators(snippetProvider[simple_snippet.value])

def expand_variable_operators(self, text):
"""
Expend parameter variables.
:param text: Text
:return: Expended text
"""
return re.sub(
r'{{[?](.*?)}}',
lambda match_group: self.context_manager[match_group.group(1)],
text)

def substitute(self, left, operation, right):
if operation != Parser.in_operator:
self.context_manager.add_variable(operation, right)
return left.replace(operation, right)

def compile(self, snippet):
"""
Compile a snippet.
Compile a snippet. Visit and then perform a clean.
:param snippet: Snippet
:return: Text of compiled snippet
Expand All @@ -67,3 +97,50 @@ def compile(self, snippet):

return re.sub(r'({{.*?}})', "", compiled_snippet)


class ContextManager:
"""
Class for managing substituted parameters to be used again.
"""
def __init__(self):
"""
Initialize stack.
"""
self.stack = []

def new_scope(self):
"""
Add new scope.
"""
self.stack.append({})

def remove_scope(self):
"""
Remove last scope.
"""
if self.stack:
self.stack.pop()

def add_variable(self, name, value):
"""
Add variable to current scope.
:param name: Variable name
:param value: Variable value
"""
if self.stack:
self.stack[-1][name] = value
else:
raise Exception("No scope to add variable to")

def __getitem__(self, item):
"""
Get variable from last scope.
:param item: Variable name
:return: Variable value if it exists. Empty string otherwise
"""
if len(self.stack) < 2 or item not in self.stack[-2]:
return ""

return self.stack[-2][item]
91 changes: 89 additions & 2 deletions test/testCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from unittest.mock import patch, MagicMock

import homotopy.syntax_tree as st
from homotopy.compiler import Compiler
from homotopy.compiler import Compiler, ContextManager


class TestCompiler(TestCase):
Expand All @@ -18,7 +18,9 @@ def test_compile(self, mock_provider):
"opt_params": ", ###{{opt_params}}",
"multiple": "{{goo}}{{doo}}",
"goo": "goo ###",
"doo": "doo $$$"
"doo": "doo $$$",
"outer": "param: ###, inside: >>>",
"inner": "{{?###}} in: >>>"
}

mock_provider.side_effect = lambda x: x if x not in data else data[x]
Expand Down Expand Up @@ -105,3 +107,88 @@ def test_compile(self, mock_provider):
st.SimpleSnippet('b'))),
'def foo(a, b):\n\tpass'
)

self.assertEqual(
Compiler().compile(st.CompositeSnippet(
st.CompositeSnippet(
st.SimpleSnippet('outer'),
'#',
st.SimpleSnippet('test')
),
'>',
st.SimpleSnippet('inner'))),
'param: test, inside: test in: >>>'
)

self.assertEqual(
Compiler().compile(st.CompositeSnippet(
st.CompositeSnippet(
st.SimpleSnippet('outer'),
'#',
st.SimpleSnippet('test')
),
'>',
st.CompositeSnippet(
st.SimpleSnippet('inner'),
'>',
st.SimpleSnippet('inner')
))),
'param: test, inside: test in: in: >>>'
)


class TestContextManager(TestCase):
def setUp(self):
self.cm = ContextManager()

def test_init(self):
self.assertEqual([], self.cm.stack)

def test_new_scope(self):
self.cm.new_scope()

self.assertEqual([{}], self.cm.stack)

def test_remove_scope(self):
self.cm.new_scope()
self.cm.remove_scope()

self.assertEqual([], self.cm.stack)

self.cm.remove_scope()
self.assertEqual([], self.cm.stack)

def test_add_variable(self):
with self.assertRaises(Exception) as context:
self.cm.add_variable("x", "3")

self.assertEqual("No scope to add variable to", str(context.exception))

self.cm.new_scope()

self.cm.add_variable("x", "3")
self.assertEqual([{"x": "3"}], self.cm.stack)

self.cm.add_variable("x", "4")
self.assertEqual([{"x": "4"}], self.cm.stack)

self.cm.add_variable("y", "3")
self.assertEqual([{"x": "4", "y": "3"}], self.cm.stack)

def test_get_item(self):
self.assertEqual("", self.cm["x"])

self.cm.new_scope()
self.cm.add_variable("x", "3")
self.cm.add_variable("y", "3")

self.assertEqual("", self.cm["x"])

self.cm.new_scope()
self.assertEqual("3", self.cm["x"])

self.cm.add_variable("x", "4")
self.assertEqual("3", self.cm["x"])

self.cm.remove_scope()
self.assertEqual("", self.cm["x"])

0 comments on commit e0cba2c

Please sign in to comment.