From 99da5165e9331f27c685c936ebc80b4d694459b9 Mon Sep 17 00:00:00 2001 From: Ben1152000 Date: Tue, 20 Sep 2022 00:53:43 -0500 Subject: [PATCH] Implement function call syntax, fix -w flag --- sootty/__main__.py | 13 ++++++------- sootty/{limits.py => parser.py} | 34 ++++++++++++++++++++------------- sootty/static/grammar.lark | 14 ++++++++++---- sootty/storage/wiretrace.py | 16 +++++++++++++--- sootty/visualizer.py | 21 ++++++++++++++++---- 5 files changed, 67 insertions(+), 31 deletions(-) rename sootty/{limits.py => parser.py} (68%) diff --git a/sootty/__main__.py b/sootty/__main__.py index 625864b..80694b0 100644 --- a/sootty/__main__.py +++ b/sootty/__main__.py @@ -141,9 +141,6 @@ def main(): if breakpoints is not None: breakpoints = wiretrace.evaluate(breakpoints) - if wires is not None: - wires = set([name.strip() for name in wires.split(",")]) - # Convert wiretrace to graphical vector image. image = Visualizer().to_svg( wiretrace, @@ -154,10 +151,12 @@ def main(): vector_radix=radix, ) - if wires is not None and len(wires): - raise Exception( - f"Unknown wires {wires.__repr__()}\nThe following wires were detected in the wiretrace:\n{wiretrace.get_wire_names()}" - ) + # This was the previous way of handling invalid wire names. It is no longer needed, + # but I'm keeping it here as a reminder to review the error handling system. + # if wires is not None and len(wires): + # raise Exception( + # f"Unknown wires {wires.__repr__()}\nThe following wires were detected in the wiretrace:\n{wiretrace.get_wire_names()}" + # ) if not output: image.display() # Show image in terminal (works in kitty, iterm) diff --git a/sootty/limits.py b/sootty/parser.py similarity index 68% rename from sootty/limits.py rename to sootty/parser.py index ecf6809..e85aa03 100644 --- a/sootty/limits.py +++ b/sootty/parser.py @@ -6,10 +6,8 @@ except ImportError: import importlib_resources as pkg_resources -# Read and interpret grammar file. -from . import static - -parser = Lark(pkg_resources.open_text(static, "grammar.lark").read()) +from . import static # Read and interpret grammar file. +from .exceptions import SoottyError class Prune(Visitor): @@ -26,7 +24,7 @@ def binexp(self, tree): tree.data = tree.children[1] tree.children = [tree.children[0], tree.children[2]] - def start(self, tree): + def expr(self, tree): self.binexp(tree) def lexp(self, tree): @@ -64,13 +62,23 @@ def wire(self, tree): tree.children = [tree.children[0], tree.children[2]] -class LimitExpression: - """Parses an expression representing the limits of a wiretrace window.""" +class ExpressionParser(Lark): + """Implementation of Lark parser class for limit expressions.""" + + def __init__(self): + grammar = pkg_resources.open_text(static, "grammar.lark").read() + super().__init__(grammar, start=["expr", "exprs"]) + + def parse(self, expression: str): + tree = super().parse(expression, start="expr") + tree = Prune().visit(tree) + # print(self.tree.pretty(), file=sys.stderr) + return tree + + def parse_list(self, expressions: str): + tree = super().parse(expressions, start="exprs") + tree = Prune().visit(tree) + return tree.children - def __init__(self, expression): - parsed = parser.parse(expression) - self.tree = Prune().visit(parsed) - def display(self): - """Display parse tree for debugging purposes.""" - print(self.tree.pretty(), file=sys.stderr) +parser = ExpressionParser() # initialize global parser object diff --git a/sootty/static/grammar.lark b/sootty/static/grammar.lark index 1e92aa6..04bc16c 100644 --- a/sootty/static/grammar.lark +++ b/sootty/static/grammar.lark @@ -4,8 +4,7 @@ %import common.DIGIT -> DIGIT %import common.NUMBER -> NUM -start : lexp -NAME: ("_"|LETTER) ("_"|LETTER|DIGIT)* ("." ("_"|LETTER|DIGIT)+)* +NAME : ("_"|LETTER) ("_"|LETTER|DIGIT)* ("." ("_"|LETTER|DIGIT)+)* AND : "&" OR : "|" @@ -58,5 +57,12 @@ uop : INV | NEG | NOT type : CONST | TIME top : FROM | AFTER | UNTIL | BEFORE tsop : NEXT | PREV -wire : NAME | "(" lexp ")" | uop wire | type NUM - | top wire | tsop wire | NUM tsop wire | ACC wire + +call : NAME "(" args ")" +args : wire ("," wire)* + +wire : NAME | "(" lexp ")" | uop wire | type NUM | top wire | tsop wire + | NUM tsop wire | ACC wire | call + +expr : lexp +exprs : expr ("," expr)* diff --git a/sootty/storage/wiretrace.py b/sootty/storage/wiretrace.py index feee377..91d59b6 100644 --- a/sootty/storage/wiretrace.py +++ b/sootty/storage/wiretrace.py @@ -2,7 +2,7 @@ from vcd.reader import * from ..exceptions import * -from ..limits import LimitExpression +from ..parser import parser from .wiregroup import WireGroup from .wire import Wire from ..utils import evcd2vcd @@ -188,6 +188,12 @@ def _compute_wire(self, node): """Evaluate a limit expression""" if node.data == "wire": return self.find(node.children[0]) + elif node.data == "call": + name = node.children[0] + args = list(map(self._compute_wire, node.children[1].children)) + if name == "AXI": + return args[0] # TODO: implement axi protocol + raise SoottyError(f'Function "{name}" does not exist.') elif node.data.type == "NEG": return self._compute_wire(node.children[0]).__neg__() elif node.data.type == "INV": @@ -278,8 +284,12 @@ def _compute_wire(self, node): return Wire.time(int(node.children[0])) def compute_wire(self, expr: str): - """Evaluate a limit expression""" - return self._compute_wire(LimitExpression(expr).tree) + """Evaluate a limit expression to a wire.""" + return self._compute_wire(parser.parse(expr)) + + def compute_wires(self, exprs: str): + """Evaluate comma-separated limit expressions as a list of wires.""" + return list(map(self._compute_wire, parser.parse_list(exprs))) def evaluate(self, expr: str): return self.compute_wire(expr).times(self.length()) diff --git a/sootty/visualizer.py b/sootty/visualizer.py index 502761b..7ff2171 100644 --- a/sootty/visualizer.py +++ b/sootty/visualizer.py @@ -71,13 +71,27 @@ def to_svg( wiretrace, start=0, length=None, - wires=None, + wires="", breakpoints=None, vector_radix=10, ): + """ + Converts the provided wiretrace object to a VectorImage object (svg). + + :param wires: comma-seperated list of wires to include + """ if length is None: length = wiretrace.length() - """Converts the provided wiretrace object to a VectorImage object (svg).""" + + if wires: # include all wires if empty list provided + wires = ( + None + if len(wires) == 0 + else set( + map(lambda wire: wire.name, wiretrace.compute_wires(wires.strip())) + ) + ) + return VectorImage( self._wiretrace_to_svg( wiretrace, start, length, wires, breakpoints, vector_radix @@ -87,11 +101,10 @@ def to_svg( def _wiretrace_to_svg( self, wiretrace, start, length, wires=None, breakpoints=None, vector_radix=10 ): - if wires and len(wires) == 0: # include all wires if empty list provided - wires = None width = ( 2 * self.style.LEFT_MARGIN + self.style.TEXT_WIDTH + self.style.FULL_WIDTH ) + height = ( 2 * self.style.TOP_MARGIN - self.style.WIRE_MARGIN