Skip to content

Commit

Permalink
indent (#19)
Browse files Browse the repository at this point in the history
* Implement indent manager

* Add new line to conform to linter

* Add test for indent manager

* Integrate indent manager in compiler

* Integrate indent manager with homotopy class
  • Loading branch information
Ahhhhmed committed Apr 18, 2018
1 parent 0b30725 commit 43b8e02
Show file tree
Hide file tree
Showing 7 changed files with 275 additions and 45 deletions.
40 changes: 24 additions & 16 deletions homotopy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ class Compiler(SnippetVisitor):
"""
Compiler for snippets. Turns syntax tree into text.
"""
def __init__(self, snippet_provider):
def __init__(self, snippet_provider, indent_manager):
self.context_manager = ContextManager()
self.context_manager.new_scope()
self.snippet_provider = snippet_provider
self.indent_manager = indent_manager

def visit_composite_snippet(self, composite_snippet):
"""
Expand All @@ -23,19 +24,10 @@ def visit_composite_snippet(self, composite_snippet):
:return: Text of left side replaced with right side
"""
left_side = self.expand_variable_operators(self.snippet_provider[self.visit(composite_snippet.left)])

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

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

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

operation_text = composite_snippet.operation*3
operation_text = composite_snippet.operation * 3

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

if operation_text in expanded_left_side:
return self.substitute(expanded_left_side, operation_text, right_side)
return self.substitute(expanded_left_side, composite_snippet.operation, composite_snippet.right, operation_text)

logging.warning("No match found. Ignoring right side of the snippet.")
return left_side
Expand All @@ -81,10 +73,26 @@ def expand_variable_operators(self, text):
lambda match_group: self.context_manager[match_group.group(1)],
text)

def substitute(self, left, operation, right):
def substitute(self, left, operation, right_tree, operation_text):
if operation == Parser.in_operator:
self.context_manager.new_scope()

before_operation_text = left[0:left.find(operation_text)]
m = re.search(r"\n([\t ]*)$", before_operation_text)
indent = m.group(1) if m else ""
self.indent_manager.push_indent(indent)

right = self.snippet_provider[self.compile(right_tree)]

if operation != Parser.in_operator:
self.context_manager.add_variable(operation, right)
return left.replace(operation, right)
self.context_manager.add_variable(operation_text, right)
else:
self.context_manager.remove_scope()

right = self.indent_manager.indent_new_lines(right)
self.indent_manager.pop_indent()

return left.replace(operation_text, right)

def compile(self, snippet):
"""
Expand Down
10 changes: 7 additions & 3 deletions homotopy/homotopy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os

from homotopy import preprocessor, parser, compiler, snippet_provider
from homotopy import preprocessor, parser, compiler, snippet_provider, indent_manager


class Homotopy:
Expand Down Expand Up @@ -72,16 +72,20 @@ def compile(self, snippet_text):
snippet_provider_instance = snippet_provider.SnippetProvider(
self.language, self.user_path + [Homotopy.stdlib_path]
)
indent_manager_instance = indent_manager.IndentManager()

snippet_text = indent_manager_instance.take_base_indent(snippet_text)

preprocessor_instance = preprocessor.Preprocessor(snippet_provider_instance)
compiler_instance = compiler.Compiler(snippet_provider_instance)
compiler_instance = compiler.Compiler(snippet_provider_instance, indent_manager_instance)
parser_instance = parser.Parser()

preprocessed_text = preprocessor_instance.expand_decorators(snippet_text)
if self.put_cursor_marker:
preprocessed_text = preprocessor_instance.put_cursor_marker(preprocessed_text)

syntax_tree = parser_instance.parse(preprocessed_text)
compiled_snippet = compiler_instance.compile(syntax_tree)

return compiler_instance.compile(syntax_tree)
return indent_manager_instance.indent_base(compiled_snippet)

72 changes: 72 additions & 0 deletions homotopy/indent_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import re


class IndentManager:
"""
Helper for managing indent.
"""
def __init__(self, base_indent=""):
"""
Initializer.
:param base_indent: Base indent
"""
self.base_indent = base_indent
self.indent_stack = []

def pop_indent(self):
"""
Pop last indent.
"""
if self.indent_stack:
self.indent_stack.pop()

def push_indent(self, indent):
"""
Push indent.
:param indent: Indent
"""
self.indent_stack.append(indent)

def get_current_indent(self):
"""
Combine all indents to get current indent.
:return: Current indent
"""
return "".join(self.indent_stack)

def indent_new_lines(self, text):
"""
Indent all the lines except first one.
:param text: Text to indent
:return: Indented text
"""
lines = text.splitlines()
lines[1:] = [self.get_current_indent() + x if x.strip() != "" else "" for x in lines[1:]]
return "\n".join(lines)

def indent_base(self, text):
"""
Indent all lines with base indent.
:param text: Text to indent
:return: Indented text
"""
lines = text.splitlines()
lines = [self.base_indent + x if x.strip() != "" else "" for x in lines]
return "\n".join(lines)

def take_base_indent(self, line):
"""
Take base indent.
:param line: Line text
:return: Line without the indent
"""
m = re.match(r"^[\t ]*", line)
self.base_indent = m.group()

return line[m.end():]
38 changes: 36 additions & 2 deletions test/testCompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import homotopy.syntax_tree as st
from homotopy.compiler import Compiler, ContextManager
from homotopy.snippet_provider import SnippetProvider
from homotopy.indent_manager import IndentManager


class TestCompiler(TestCase):
def setUp(self):
self.compiler_instance = Compiler(SnippetProvider())
self.compiler_instance = Compiler(SnippetProvider(), IndentManager())

@patch('homotopy.indent_manager.IndentManager.indent_new_lines')
@patch('homotopy.indent_manager.IndentManager.pop_indent')
@patch('homotopy.indent_manager.IndentManager.push_indent')
@patch('homotopy.snippet_provider.SnippetProvider.__getitem__')
def test_compile(self, mock_provider):
def test_compile(self, mock_provider, mock_push_indent, mock_pop_indent, mock_indent_new_lines):
with self.assertRaises(NotImplementedError):
self.compiler_instance.compile(st.Snippet())

Expand All @@ -28,6 +32,7 @@ def test_compile(self, mock_provider):
}

mock_provider.side_effect = lambda x: x if x not in data else data[x]
mock_indent_new_lines.side_effect = lambda x: x

self.assertEqual(
self.compiler_instance.compile(st.CompositeSnippet(
Expand Down Expand Up @@ -140,6 +145,35 @@ def test_compile(self, mock_provider):
'param: test, inside: test in: test in: >>>'
)

@patch('homotopy.indent_manager.IndentManager.indent_new_lines')
@patch('homotopy.indent_manager.IndentManager.pop_indent')
@patch('homotopy.indent_manager.IndentManager.push_indent')
@patch('homotopy.snippet_provider.SnippetProvider.__getitem__')
def test_indentation(self, mock_provider, mock_push_indent, mock_pop_indent, mock_indent_new_lines):
with self.assertRaises(NotImplementedError):
self.compiler_instance.compile(st.Snippet())

data = {
"for": "for item in collection:\n\t \t {{helper}}",
"helper": ">>>"
}

mock_provider.side_effect = lambda x: x if x not in data else data[x]
mock_indent_new_lines.side_effect = lambda x: x

self.assertEqual(
self.compiler_instance.compile(st.CompositeSnippet(
st.SimpleSnippet('for'),
'>',
st.SimpleSnippet('pass')
)),
'for item in collection:\n\t \t pass'
)

mock_push_indent.assert_called_once_with('\t \t ')
mock_indent_new_lines.assert_called_once_with('pass')
mock_pop_indent.assert_called_once_with()


class TestContextManager(TestCase):
def setUp(self):
Expand Down
13 changes: 11 additions & 2 deletions test/testHomotopy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from homotopy.homotopy import Homotopy
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import patch, MagicMock


class TestHomotopy(TestCase):
def setUp(self):
Expand Down Expand Up @@ -53,8 +54,10 @@ def test_set_language(self):
@patch('homotopy.compiler.Compiler.__init__')
@patch('homotopy.compiler.Compiler.compile')
@patch('homotopy.snippet_provider.SnippetProvider')
@patch('homotopy.indent_manager.IndentManager')
def test_compile(
self,
mock_indent_manager,
mock_provider,
compile_method,
compile_init,
Expand All @@ -64,6 +67,10 @@ def test_compile(
preprocessor_expand_decorators,
preprocessor_init,
):
mock_indent_manager_instance = MagicMock()
mock_indent_manager_instance.take_base_indent = MagicMock(side_effect=lambda x: x)
mock_indent_manager_instance.indent_base = MagicMock(side_effect=lambda x: x)
mock_indent_manager.return_value = mock_indent_manager_instance
mock_provider.return_value = "mock_provider"
compile_method.return_value = "compile_method_output"
compile_init.return_value = None
Expand All @@ -80,9 +87,11 @@ def test_compile(

mock_provider.assert_called_once_with("c++", ["test_folder", Homotopy.stdlib_path])
compile_method.assert_called_once_with("parse_method_output")
compile_init.assert_called_once_with("mock_provider")
compile_init.assert_called_once_with("mock_provider", mock_indent_manager_instance)
parser_parse.assert_called_once_with("put_cursor_marker_output")
parser_init.assert_called_once_with()
preprocessor_put_cursor_marker.assert_called_once_with("expand_decorators_output")
preprocessor_expand_decorators.assert_called_once_with("test_snippet")
preprocessor_init.assert_called_once_with("mock_provider")
mock_indent_manager_instance.take_base_indent.assert_called_once_with("test_snippet")
mock_indent_manager_instance.indent_base.assert_called_once_with("compile_method_output")
97 changes: 97 additions & 0 deletions test/testIndent_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from unittest import TestCase
from homotopy.indent_manager import IndentManager


class TestIndentManager(TestCase):
def setUp(self):
self.indent_manager_instance = IndentManager()

def test_init(self):
self.assertEqual("", self.indent_manager_instance.base_indent)
self.assertEqual([], self.indent_manager_instance.indent_stack)

self.assertEqual("indent", IndentManager("indent").base_indent)

def test_push_indent(self):
self.indent_manager_instance.push_indent("level1")
self.assertEqual(["level1"], self.indent_manager_instance.indent_stack)

self.indent_manager_instance.push_indent("level2")
self.assertEqual(["level1", "level2"], self.indent_manager_instance.indent_stack)

def test_pop_indent(self):
self.indent_manager_instance.push_indent("level1")
self.indent_manager_instance.push_indent("level2")

self.indent_manager_instance.pop_indent()
self.assertEqual(["level1"], self.indent_manager_instance.indent_stack)

self.indent_manager_instance.pop_indent()
self.assertEqual([], self.indent_manager_instance.indent_stack)

self.indent_manager_instance.pop_indent()
self.assertEqual([], self.indent_manager_instance.indent_stack)

def test_get_current_indent(self):
self.indent_manager_instance.push_indent("level1")
self.indent_manager_instance.push_indent("level2")

self.assertEqual("level1level2", self.indent_manager_instance.get_current_indent())

self.indent_manager_instance.pop_indent()
self.assertEqual("level1", self.indent_manager_instance.get_current_indent())

self.indent_manager_instance.pop_indent()
self.assertEqual("", self.indent_manager_instance.get_current_indent())

self.indent_manager_instance.pop_indent()
self.assertEqual("", self.indent_manager_instance.get_current_indent())

def test_indent_new_lines(self):
self.indent_manager_instance.push_indent("indent ")

self.assertEqual("test", self.indent_manager_instance.indent_new_lines("test"))
self.assertEqual("line1\nindent line2", self.indent_manager_instance.indent_new_lines("line1\nline2"))

self.assertEqual(
"line1\nindent line2\nindent line3",
self.indent_manager_instance.indent_new_lines("line1\nline2\nline3"))

self.assertEqual("line1\nindent line2", self.indent_manager_instance.indent_new_lines("line1\nline2\n"))
self.assertEqual("line1\nindent line2\n", self.indent_manager_instance.indent_new_lines("line1\nline2\n\n"))
self.assertEqual("line1\nindent line2\n", self.indent_manager_instance.indent_new_lines("line1\nline2\n \n"))

self.assertEqual(
"\nindent line1\nindent line2",
self.indent_manager_instance.indent_new_lines("\nline1\nline2"))

def test_indent_base(self):
self.indent_manager_instance.base_indent = "indent "

self.assertEqual("indent test", self.indent_manager_instance.indent_base("test"))
self.assertEqual("indent line1\nindent line2", self.indent_manager_instance.indent_base("line1\nline2"))

self.assertEqual(
"indent line1\nindent line2\nindent line3",
self.indent_manager_instance.indent_base("line1\nline2\nline3"))

self.assertEqual("indent line1\nindent line2", self.indent_manager_instance.indent_base("line1\nline2\n"))
self.assertEqual("indent line1\nindent line2\n", self.indent_manager_instance.indent_base("line1\nline2\n\n"))
self.assertEqual("indent line1\nindent line2\n", self.indent_manager_instance.indent_base("line1\nline2\n \n"))

self.assertEqual(
"\nindent line1\nindent line2",
self.indent_manager_instance.indent_base("\nline1\nline2"))

def test_take_base_indent(self):
self.assertEqual("", self.indent_manager_instance.take_base_indent(" "))
self.assertEqual(" ", self.indent_manager_instance.base_indent)

self.assertEqual("for", self.indent_manager_instance.take_base_indent(" for"))
self.assertEqual(" ", self.indent_manager_instance.base_indent)

self.assertEqual("for", self.indent_manager_instance.take_base_indent("\tfor"))
self.assertEqual("\t", self.indent_manager_instance.base_indent)

self.assertEqual("for", self.indent_manager_instance.take_base_indent("\t \t for"))
self.assertEqual("\t \t ", self.indent_manager_instance.base_indent)

0 comments on commit 43b8e02

Please sign in to comment.