Files
nx_post_support/server/libs/tclint/syntax_tree.py
T

439 lines
12 KiB
Python

"""Classes for representing and interacting with Tcl syntax trees. """
class Visitor:
"""Abstract base class for Visitors that operate on syntax tree."""
def visit_script(self, script):
pass
def visit_comment(self, comment):
pass
def visit_command(self, command):
pass
def visit_command_sub(self, command_sub):
pass
def visit_bare_word(self, word):
pass
def visit_braced_word(self, word):
pass
def visit_quoted_word(self, word):
pass
def visit_compound_bare_word(self, word):
pass
def visit_var_sub(self, var_sub):
pass
def visit_arg_expansion(self, arg_expansion):
pass
def visit_list(self, list):
pass
def visit_expression(self, expression):
pass
def visit_braced_expression(self, expression):
pass
def visit_paren_expression(self, expression):
pass
def visit_unary_op(self, unary_op):
pass
def visit_binary_op(self, binary_op):
pass
def visit_ternary_op(self, ternary_op):
pass
def visit_function(self, function):
pass
class Node:
"""
Invariants:
- self.value is some sort of base Python type
- self.children is a list of Node types
"""
def __init__(self, *init, pos=None, end_pos=None):
"""pos: line, column of first character of parsed region (1-indexed)
end_pos: line, column of first character after parsed region (1-indexed)
"""
self.line = None
self.col = None
if pos is not None:
self.line, self.col = pos
self.end_pos = end_pos
self.value = None
if len(init) > 0 and not isinstance(init[0], Node):
self.value = init[0]
init = init[1:]
if not all(isinstance(v, Node) for v in init):
raise TypeError("Children must be Node instances")
self.children = list(init)
def add(self, node):
self.children.append(node)
@property
def contents(self):
"""This is overloaded by word Nodes that may have concrete contents.
TODO: I prefer the name value, but that's currently taken...
"""
return None
@property
def contents_pos(self):
"""This is overloaded by word Nodes that may have concrete contents.
Returns the position at which the contents start.
TODO: consider combining with with `contents`?
"""
return None
def _pos_str(self):
start_pos_str = "?"
if self.pos is not None:
start_pos_str = f"{self.pos[0]}:{self.pos[1]}"
end_pos_str = "?"
if self.end_pos is not None:
end_pos_str = f"{self.end_pos[0]}:{self.end_pos[1]}"
return f" # {start_pos_str}-{end_pos_str}"
def _make_str(self, indent=None, positions=False):
if indent is not None:
s = " " * indent
else:
s = ""
s += self.__class__.__name__
s += "("
if self.value:
s += repr(self.value)
if self.children:
s += ", "
if positions and self.children:
s += self._pos_str()
for i, child in enumerate(self.children):
if indent is not None:
s += "\n"
s += child._make_str(
indent=None if indent is None else indent + 1, positions=positions
)
if i < len(self.children) - 1:
s += ", "
s += ")"
if positions and not self.children:
s += self._pos_str()
return s
def pretty(self, positions=False):
return self._make_str(indent=0, positions=positions)
def __str__(self):
return self._make_str()
def __eq__(self, other):
if type(self) is not type(other):
return False
if self.value != other.value:
return False
if len(self.children) != len(other.children):
return False
for my_child, other_child in zip(self.children, other.children):
if my_child != other_child:
return False
return True
def diff(self, other, indent_depth=0):
lines = []
indent = " " * indent_depth
my_cls = self.__class__.__name__
other_cls = other.__class__.__name__
if my_cls != other_cls:
lines += [f"{indent}-{my_cls}("]
lines += [f"{indent}+{other_cls}("]
return lines
if self.value != other.value:
lines += [f'{indent}-{my_cls}("{self.value}"']
lines += [f'{indent}+{other_cls}("{other.value}"']
return lines
if len(self.children) != len(other.children):
my_children = ",".join([
child.__class__.__name__ for child in self.children
])
other_children = ",".join([
child.__class__.__name__ for child in other.children
])
lines += [f"{indent}-{my_cls}({my_children})"]
lines += [f"{indent}+{other_cls}({other_children})"]
return lines
if self.value is not None:
lines += [f"{indent}{my_cls}({self.value}"]
else:
lines += [f"{indent}{my_cls}("]
for my_child, other_child in zip(self.children, other.children):
lines += my_child.diff(other_child, indent_depth=indent_depth + 1)
lines += [f"{indent})"]
return lines
@property
def pos(self):
if self.line is None or self.col is None:
return None
return (self.line, self.col)
def _recurse(self, visitor):
for child in self.children:
child.accept(visitor, recurse=True)
class Script(Node):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hack for spaces-in-braces check
self.braced = False
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_script(self)
class Comment(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_comment(self)
class Command(Node):
def __init__(self, routine, *args, pos=None, end_pos=None):
self.routine = routine
self.args = args
super().__init__(routine, *args, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_command(self)
class CommandSub(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_command_sub(self)
class BareWord(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_bare_word(self)
@property
def contents(self):
return self.value
@property
def contents_pos(self):
return self.pos
class BracedWord(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_braced_word(self)
@property
def contents(self):
return self.value
@property
def contents_pos(self):
return (self.line, self.col + 1)
class QuotedWord(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_quoted_word(self)
@property
def contents(self):
"""A QuotedWord with concrete contents is put in the tree as
QuotedWord(BareWord()), so return contents of its child."""
if len(self.children) > 1:
return None
if len(self.children) == 0:
# weird special case to handle blank quoted string ("") - it doesn't
# make sense to put in a child, but setting/returning the value is
# also inconsistent
return ""
return self.children[0].contents
@property
def contents_pos(self):
if self.contents is None:
return None
return (self.line, self.col + 1)
class CompoundBareWord(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_compound_bare_word(self)
class VarSub(Node):
def __init__(self, *args, braced=False, **kwargs):
self.braced = braced
return super().__init__(*args, **kwargs)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_var_sub(self)
class ArgExpansion(Node):
def __init__(self, list, pos=None, end_pos=None):
self.list = list
super().__init__(list, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_arg_expansion(self)
class List(Node):
"""This Node currently exists exclusively for implementing the switch
command in a way that facilitates style checks. Might be nice to find
another way to handle this that doesn't require a special Node."""
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_list(self)
class Expression(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_expression(self)
class BracedExpression(Node):
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_braced_expression(self)
class ParenExpression(Node):
def __init__(self, body: Expression, pos=None, end_pos=None):
self.body = body
super().__init__(body, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_paren_expression(self)
class UnaryOp(Node):
def __init__(self, operator, operand, pos=None, end_pos=None):
self.operator = operator
self.operand = operand
super().__init__(operator, operand, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_unary_op(self)
class BinaryOp(Node):
def __init__(self, operator1, operand, operator2, pos=None, end_pos=None):
self.operator1 = operator1
self.operand = operand
self.operator2 = operator2
super().__init__(operator1, operand, operator2, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_binary_op(self)
class TernaryOp(Node):
def __init__(self, condition, question, true, colon, false, pos=None, end_pos=None):
self.condition = condition
self.question = question
self.true = true
self.colon = colon
self.false = false
super().__init__(
condition, question, true, colon, false, pos=pos, end_pos=end_pos
)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_ternary_op(self)
class Function(Node):
def __init__(self, name, *args, pos=None, end_pos=None):
self.name = name
self.args = args
super().__init__(name, *args, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_function(self)