diff --git a/CHANGELOG.md b/CHANGELOG.md index 510c138..6e584e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.2] - 2020-08-06 + ### Added - Completing chapter 5 -- Completed including chapter 5.3.3 - Created `tools/generate_ast.py`, added Visitor class +- Added the generated `expr.py` +- Not implemented methods in ABC base classes will raise an `NotImplementedError` that + is ignored by coverage tests ### Fixed -- CTRL-D now works on OSX terminal +- CTRL-D now works in the OSX terminal - Refactor: Exit method is called in a single place ## [0.0.1] - 2020-08-01 @@ -30,5 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Token`, `Scanner`, `TokenType` - Support `strings`, `numbers` and `identifiers` -[Unreleased]: https://github.com/RoelAdriaans/yaplox/compare/v0.0.1...HEAD +[Unreleased]: https://github.com/RoelAdriaans/yaplox/compare/v0.0.2...HEAD +[0.0.2]: https://github.com/RoelAdriaans/yaplox/releases/tag/v0.0.2 [0.0.1]: https://github.com/RoelAdriaans/yaplox/releases/tag/v0.0.1 diff --git a/src/yaplox/ast_printer.py b/src/yaplox/ast_printer.py new file mode 100644 index 0000000..81a2d09 --- /dev/null +++ b/src/yaplox/ast_printer.py @@ -0,0 +1,31 @@ +from yaplox.expr import Binary, Expr, ExprVisitor, Grouping, Literal, Unary + + +class AstPrinter(ExprVisitor): + def print(self, expr: Expr) -> str: + return expr.accept(self) + + def visit_grouping_expr(self, expr: Grouping): + return self._parenthesize("group", expr.expression) + + def visit_literal_expr(self, expr: Literal): + if expr.value is None: + return "nil" + return str(expr.value) + + def visit_unary_expr(self, expr: Unary): + return self._parenthesize(expr.operator.lexeme, expr.right) + + def visit_binary_expr(self, expr: Binary): + return self._parenthesize(expr.operator.lexeme, expr.left, expr.right) + + def _parenthesize(self, name: str, *args: Expr): + expressions = ["(", name] + + for expr in args: + expressions.append(" ") + expressions.append(expr.accept(self)) + + expressions.append(")") + + return "".join(expressions) diff --git a/src/yaplox/expr.py b/src/yaplox/expr.py new file mode 100644 index 0000000..e58aee0 --- /dev/null +++ b/src/yaplox/expr.py @@ -0,0 +1,72 @@ +# This file has been auto-generated by tools/generate_ast.py +# Do not edit this file by hand. Or do, but it will be overwritten + +from abc import ABC, abstractmethod +from typing import Any + +from yaplox.token import Token + + +class ExprVisitor(ABC): + """This class is used as an Vistor for the Expr class""" + + @abstractmethod + def visit_binary_expr(self, expr: "Binary"): + raise NotImplementedError + + @abstractmethod + def visit_grouping_expr(self, expr: "Grouping"): + raise NotImplementedError + + @abstractmethod + def visit_literal_expr(self, expr: "Literal"): + raise NotImplementedError + + @abstractmethod + def visit_unary_expr(self, expr: "Unary"): + raise NotImplementedError + + +class Expr(ABC): + @abstractmethod + def accept(self, visitor: ExprVisitor): + raise NotImplementedError + + +class Binary(Expr): + def __init__(self, left: Expr, operator: Token, right: Expr): + self.left = left + self.operator = operator + self.right = right + + def accept(self, visitor: ExprVisitor): + """ Create a accept method that calls the visitor. """ + return visitor.visit_binary_expr(self) + + +class Grouping(Expr): + def __init__(self, expression: Expr): + self.expression = expression + + def accept(self, visitor: ExprVisitor): + """ Create a accept method that calls the visitor. """ + return visitor.visit_grouping_expr(self) + + +class Literal(Expr): + def __init__(self, value: Any): + self.value = value + + def accept(self, visitor: ExprVisitor): + """ Create a accept method that calls the visitor. """ + return visitor.visit_literal_expr(self) + + +class Unary(Expr): + def __init__(self, operator: Token, right: Expr): + self.operator = operator + self.right = right + + def accept(self, visitor: ExprVisitor): + """ Create a accept method that calls the visitor. """ + return visitor.visit_unary_expr(self) diff --git a/tests/test_ast_printer.py b/tests/test_ast_printer.py new file mode 100644 index 0000000..80f41ec --- /dev/null +++ b/tests/test_ast_printer.py @@ -0,0 +1,27 @@ +from yaplox import expr +from yaplox.ast_printer import AstPrinter +from yaplox.token import Token +from yaplox.token_type import TokenType + + +class TestAstPrinter: + def test_astrinter(self): + # This is the main/test function given in the chapter for the + # ast_printer in chapter A (Not Very) pretty printer. + # This code might be obsolete in the future, but it is a lot cleaner then + # creating a main function to test our code. + expression = expr.Binary( + expr.Unary(Token(TokenType.MINUS, "-", None, 1), expr.Literal(123)), + Token(TokenType.STAR, "*", None, 1), + expr.Grouping(expr.Literal(45.67)), + ) + + result = AstPrinter().print(expression) + assert result == "(* (- 123) (group 45.67))" + + def test_astrinter_nill(self): + # Test a single edge case None + expression = expr.Literal(None) + + result = AstPrinter().print(expression) + assert result == "nil" diff --git a/tools/generate_ast.py b/tools/generate_ast.py index a3d7933..c15dda2 100644 --- a/tools/generate_ast.py +++ b/tools/generate_ast.py @@ -50,7 +50,14 @@ def _define_ast(self, base_name: str, types: List): ] lines.extend(self._define_visitor(base_name, types)) - lines.extend([f"class {base_name}(ABC):", " ...", ""]) + lines.extend( + [ + f"class {base_name}(ABC):", + " @abstractmethod", + " def accept(self, visitor: ExprVisitor):", + " raise NotImplementedError", + ] + ) for class_type in types: class_name = class_type.split(":")[0].strip() @@ -73,7 +80,7 @@ def _define_visitor(self, base_name: str, types: List) -> List: " @abstractmethod", f" def visit_{class_name.lower()}_{base_name.lower()}" f'(self, expr: "{class_name}"):', - " pass", + " raise NotImplementedError", ] vistor_lines.extend(lines)