diff --git a/server/src/tools/parser.py b/server/src/tools/parser.py index c1f15e8..7363a4e 100644 --- a/server/src/tools/parser.py +++ b/server/src/tools/parser.py @@ -1,7 +1,9 @@ import io +import re from typing import Optional, Tuple from tclint.parser import Parser from tclint.commands import CommandArgError +from tclint.commands.checks import eval as eval_script_args from tclint.syntax_tree import ( BracedWord, BareWord, @@ -13,9 +15,32 @@ from tclint.syntax_tree import ( from tclint.lexer import TclSyntaxError, Lexer, TOK_EOF +_UPLEVEL_LEVEL_RE = re.compile(r"^#?\d+$") + + +def _uplevel(args, parser): + """uplevel ?level? arg ?arg ...?""" + # ref: https://www.tcl.tk/man/tcl/TclCmd/uplevel.html + if len(args) == 0: + raise CommandArgError("not enough args to 'uplevel': got 0, expected at least 1") + + # The level can only be omitted when the first arg doesn't look like one. + # A non-literal first arg (e.g. $level) is treated as a level as well. + level = [] + if len(args) > 1: + first = args[0].contents + if first is None or _UPLEVEL_LEVEL_RE.match(first): + level = args[0:1] + + return level + eval_script_args(args[len(level) :], parser, "uplevel") + + class CustomParser(Parser): def __init__(self, debug=False, command_plugins=None): super().__init__(debug, command_plugins) + # tclint only checks the arg count of uplevel; parse its body as a script + # so it gets formatted and linted like eval/namespace eval bodies. + self._commands = {**self._commands, "uplevel": _uplevel} # Used to normalize newlines consistently with open()'s universal newlines mode. self._decoder = io.IncrementalNewlineDecoder(None, True)