Files
nx_post_support/server/libs/tclint/syntax_tree.py
T
Christoph Brandau 53ebc5d055 chore(lsprotocol): migrate to 2025.0.0 and cleanup artifacts
The changes align the project with the 2025.0.0 lsprotocol
release, removing the old backport and updating type hints
in the protocol hooks to use Sequence where appropriate. The
dist-info and packaging metadata for older lsprotocol
versions are replaced with the new 2025.0.0 artifacts.

- Remove exceptiongroup backport used on Python <3.11
- Use Sequence instead of List in LS protocol hooks
- Replace old dist-info with 2025.0.0 metadata
2026-09-03 08:39:12 +02:00

479 lines
13 KiB
Python

"""Classes for representing and interacting with Tcl syntax trees."""
from __future__ import annotations
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)
def _pos_match(self, line: int, col: int) -> bool:
"""Return True if pos is within this node's block"""
if self.pos is None:
return False
if self.end_pos is None:
return line == self.pos[0] and col == self.pos[1]
return (
line >= self.pos[0]
and line <= self.end_pos[0]
and (col >= self.pos[1] or line > self.pos[0])
and (col < self.end_pos[1] or line < self.end_pos[0])
)
def find_by_pos(self, line: int, col: int) -> Node | None:
"""Find the deepest child node in the tree (i.e. most granular match) that
matches the given position."""
if not self._pos_match(line, col):
return None
for child in self.children:
if child._pos_match(line, col):
return child.find_by_pos(line, col)
return self
class Script(Node):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Used by formatter.
self.braced = False
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_script(self)
class Comment(Node):
value: str
def __init__(self, value: str, pos=None, end_pos=None):
super().__init__(value, pos=pos, end_pos=end_pos)
def accept(self, visitor, recurse=False):
if recurse:
self._recurse(visitor)
visitor.visit_comment(self)
class Command(Node):
def __init__(self, routine: Node, *args: Node, 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):
if self.line is None or self.col is None:
return None
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
if self.line is None or self.col 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: Node, 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)
@property
def contents(self):
return self.list.contents
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)