Files
nx_post_support/server/libs/tclint/commands/builtin.py
T

1079 lines
35 KiB
Python

"""Parse-time handling of Tcl's builtin commands.
Based on Tcl 8.6, https://www.tcl-lang.org/man/tcl8.6/TclCmd/contents.htm.
Note that the following commands are not currently supported. If support for any of
these would be helpful for your use case, please file an issue.
- Anything related to TclOO:
- https://www.tcl.tk/man/tcl/TclCmd/my.html
- https://www.tcl.tk/man/tcl/TclCmd/next.html
- https://www.tcl.tk/man/tcl/TclCmd/class.html
- https://www.tcl.tk/man/tcl/TclCmd/copy.html
- https://www.tcl.tk/man/tcl/TclCmd/define.html
- https://www.tcl.tk/man/tcl/TclCmd/object.html
- https://www.tcl.tk/man/tcl/TclCmd/self.html
- Things that are imported via `package require`
- https://www.tcl.tk/man/tcl/TclCmd/dde.html
- https://www.tcl.tk/man/tcl/TclCmd/http.html
- https://www.tcl.tk/man/tcl/TclCmd/msgcat.html
- https://www.tcl.tk/man/tcl/TclCmd/platform.html
- https://www.tcl.tk/man/tcl/TclCmd/platform_shell.html
- https://www.tcl.tk/man/tcl/TclCmd/transchan.html
- https://www.tcl.tk/man/tcl/TclCmd/tcltest.html
- Tcl library commands: https://www.tcl.tk/man/tcl/TclCmd/library.html
- The "unknown" command: https://www.tcl.tk/man/tcl/TclCmd/unknown.html
- Math ops:
- https://www.tcl.tk/man/tcl/TclCmd/mathfunc.html
- https://www.tcl.tk/man/tcl/TclCmd/mathop.html
"""
from tclint.commands.checks import (
CommandArgError,
check_count,
eval,
)
from tclint.commands.schema import commands_schema
from tclint.syntax_tree import BareWord
def _check_code(arg):
"""Check 'code' argument used by return and try."""
val = arg.contents
if val is None:
return
try:
int(val)
except ValueError:
pass
else:
return
if val in {"ok", "error", "return", "break", "continue"}:
return
raise CommandArgError(
f"got {val}, expected one of ok, error, return, break, continue, or an integer"
)
def _after(args, parser):
"""after ms [script...]"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/after.html
script_arg = []
if len(args) > 1:
script_arg = eval(args[1:], parser, "after")
return args[0:1] + script_arg
def _after_cancel(args, parser):
"""after id|(script...)"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/after.html
check_count("after cancel", 1, None)
# TODO: raise warning about not checking code
return None
def _after_idle(args, parser):
"""after idle [script...]"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/after.html
return eval(args, parser, "after idle")
def _apply(args, parser):
"""apply func [arg...]"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/apply.html
if len(args) < 1:
raise CommandArgError(
f"not enough args to apply: got {len(args)}, expected at least 1"
)
func_list = parser.parse_list(args[0])
list_len = len(func_list.children)
if list_len < 2 or list_len > 3:
raise CommandArgError(
f"Invalid first argument to apply: got list of {list_len} elements,"
" expected 2 or 3"
)
body = parser.parse_script(func_list.children[1])
func_list.children[1] = body
return [func_list] + args[1:]
_array = {
"subcommands": {
"anymore": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "searchId", "value": {"type": "any"}, "required": True},
]
},
"donesearch": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "searchId", "value": {"type": "any"}, "required": True},
]
},
"exists": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
]
},
"get": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "pattern", "value": {"type": "any"}, "required": False},
]
},
"names": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "mode", "value": {"type": "any"}, "required": False},
{"name": "pattern", "value": {"type": "any"}, "required": False},
]
},
"nextelement": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "searchId", "value": {"type": "any"}, "required": True},
]
},
"set": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "list", "value": {"type": "any"}, "required": True},
]
},
"size": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
]
},
"startsearch": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
]
},
"statistics": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
]
},
"unset": {
"positionals": [
{"name": "arrayName", "value": {"type": "any"}, "required": True},
{"name": "pattern", "value": {"type": "any"}, "required": False},
]
},
},
}
def _catch(args, parser):
"""catch script [resultVarName] [optionsVarName]"""
if len(args) < 1:
raise CommandArgError(
f"not enough args to catch: got {len(args)}, expected at least 1"
)
if len(args) > 3:
raise CommandArgError(
f"too many args to catch: got {len(args)}, expected no more than 3"
)
return [parser.parse_script(args[0])] + args[1:]
_chan = {
"subcommands": {
"blocked": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"close": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "direction", "value": {"type": "any"}, "required": False},
]
},
"configure": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "options", "value": {"type": "variadic"}, "required": False},
],
},
"copy": {
"positionals": [
{"name": "inputChan", "value": {"type": "any"}, "required": True},
{"name": "outputChan", "value": {"type": "any"}, "required": True},
{"name": "options", "value": {"type": "variadic"}, "required": False},
],
},
"create": {
"positionals": [
{"name": "mode", "value": {"type": "any"}, "required": True},
{"name": "cmdPrefix", "value": {"type": "any"}, "required": True},
]
},
"eof": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"event": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "event", "value": {"type": "any"}, "required": True},
# TODO: parse this as script
{"name": "script", "value": {"type": "any"}, "required": False},
]
},
"flush": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"gets": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "varName", "value": {"type": "any"}, "required": False},
]
},
"names": {
"positionals": [
{"name": "pattern", "value": {"type": "any"}, "required": False},
]
},
"pending": {
"positionals": [
{"name": "mode", "value": {"type": "any"}, "required": True},
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"pipe": {},
"pop": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"postevent": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "eventSpec", "value": {"type": "any"}, "required": True},
]
},
"push": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "cmdPrefix", "value": {"type": "any"}, "required": True},
]
},
"puts": {
"positionals": [
{"name": "-nonewline", "value": {"type": "any"}, "required": False},
{"name": "channelId", "value": {"type": "any"}, "required": False},
{"name": "string", "value": {"type": "any"}, "required": True},
],
},
"read": {
"positionals": [
{"name": "-nonewline", "value": {"type": "any"}, "required": False},
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "numChars", "value": {"type": "any"}, "required": False},
],
},
"seek": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "offset", "value": {"type": "any"}, "required": True},
{"name": "origin", "value": {"type": "any"}, "required": False},
]
},
"tell": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
]
},
"truncate": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "length", "value": {"type": "any"}, "required": False},
]
},
},
}
def _dict_filter(args, parser):
"""dict filter <dictionaryValue> <filterType> <arg> [arg...]
dict filter <dictionaryValue> key [globPattern...]
dict filter <dictionaryValue> value [globPattern...]
"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/dict.html#M8
if len(args) < 2:
raise CommandArgError(
f"not enough args to 'dict filter': got {len(args)}, expected at least 2"
)
if args[1].contents not in {"key", "script", "value"}:
raise CommandArgError(
"invalid argument to 'dict filter': expected filter type to be one of key,"
" script, or value"
)
if args[1].contents == "script":
kv_pair = parser.parse_list(args[2])
list_len = len(kv_pair.children)
if len(kv_pair.children) != 2:
raise CommandArgError(
"invalid argument to 'dict filter': expected list of 2 elements in"
f" second-to-last argument, got {list_len}"
)
return args[0:2] + [kv_pair, parser.parse_script(args[3])]
return None
def _dict_map_for(cmd):
def check(args, parser):
if len(args) != 3:
raise CommandArgError(
f"wrong # of args to '{cmd}': got {len(args)}, expected 3"
)
# TODO: might be worth checking that arg[0] is a pair?
return args[0:2] + [parser.parse_script(args[2])]
return check
def _dict_update(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/dict.html#M25
if len(args) < 4:
raise CommandArgError(
f"not enough args to 'dict update': got {len(args)}, expected at least 4"
)
if len(args) % 2 != 0:
raise CommandArgError(
"invalid # of args to 'dict update': expected an even number"
)
return args[0:-1] + [parser.parse_script(args[-1])]
def _dict_with(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/dict.html#M27
if len(args) < 2:
raise CommandArgError(
f"not enough args to 'dict with': got {len(args)}, expected at least 2"
)
return args[0:-1] + [parser.parse_script(args[-1])]
def _eval(args, parser):
return eval(args, parser, "eval")
def _expr(args, parser):
if len(args) == 0:
raise CommandArgError("not enough args to 'expr': got 0, expected at least 1")
# Handle single argument consisting of BareWord, BracedWord, or concrete QuotedWord.
if len(args) == 1 and args[0].contents is not None:
# this method will handle the `node.contents is None` case fine, but
# will throw an error. We'll instead pass thru silently, since that error
# will be caught by a separate lint check.
return [parser.parse_expression(args[0])]
# Handle multiple BareWord arguments. Non-BareWords are hard to handle in this case,
# since we need to pop the contents out of quoted or braced words, but then we have
# no way of storing the original info about these words in the syntax tree.
contents = ""
last_pos = args[0].pos
for arg in args:
if not isinstance(arg, BareWord):
return None
if arg.pos[0] != last_pos[0]:
contents += "\n" * (arg.pos[0] - last_pos[0])
contents += " " * (arg.pos[1] - 1)
else:
contents += " " * (arg.pos[1] - last_pos[1])
contents += arg.contents
last_pos = arg.end_pos
node = BareWord(contents, pos=args[0].pos, end_pos=args[-1].end_pos)
return [parser.parse_expression(node)]
def _fileevent(args, parser):
# ref: https://www.tcl.tk/man/tcl8.4/TclCmd/fileevent.html
# TODO: implement
raise CommandArgError(
"argument parsing for 'fileevent' not implemented, script argument will not be"
" checked for violations"
)
def _for(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/for.html
if len(args) != 4:
raise CommandArgError(f"wrong # of args to for: got {len(args)}, expected 4")
return [
parser.parse_script(args[0]),
parser.parse_expression(args[1]),
parser.parse_script(args[2]),
parser.parse_script(args[3]),
]
def _foreach(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/foreach.html
if len(args) < 3:
raise CommandArgError(
f"insufficient args to foreach: got {len(args)}, expected at least 3"
)
# last argument is script body
return args[0:-1] + [parser.parse_script(args[-1])]
def _if(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/if.html
# TODO: make arg checking strict
new_args = []
new_args.append(parser.parse_expression(args[0]))
while len(new_args) < len(args):
arg = args[len(new_args)]
if arg.contents == "then" or arg.contents == "else":
new_args.append(arg)
continue
if arg.contents == "elseif":
new_args.append(arg)
new_args.append(parser.parse_expression(args[len(new_args)]))
continue
arg = parser.parse_script(arg)
new_args.append(arg)
return new_args
def _interp_eval(args, parser):
if len(args) < 2:
raise CommandArgError(
f"not enough args to 'interp eval': got {len(args)}, expected at least 2"
)
return args[0:1] + eval(args[1:], parser, "interp eval")
def _lmap(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/lmap.html
if len(args) < 3:
raise CommandArgError(
f"not enough args to lmap: got {len(args)}, expected at least 3"
)
return args[:-1] + [parser.parse_script(args[-1])]
def _namespace_code(args, parser):
# ref: https://www.tcl.tk/man/tcl8.4/TclCmd/namespace.html#M6
# TODO: seems like a possible pattern is to execute things in these scripts
# with additional args provided, so command-args checks within this might
# actually be false positive. will keep as-is for now though.
return [parser.parse_script(args[0])]
def _namespace_eval(args, parser):
if len(args) < 2:
raise CommandArgError(
f"not enough args to 'namespace eval': got {len(args)}, expected at least 2"
)
return args[0:1] + eval(args[1:], parser, "namespace eval")
def _namespace_inscope(args, parser):
# ref: https://www.tcl.tk/man/tcl8.4/TclCmd/namespace.html#M14
raise CommandArgError(
"'namespace inscope' is not meant to be called directly, consider using"
" 'namespace code' or 'namespace eval' instead"
)
def _package_ifneeded(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/package.html
# TODO: implement
# one issue with this one - it seems like calls to package ifneeded are
# often generated by pkg_MkIndex and these calls won't lint clean. Probably
# need a special case to ensure that these don't generate violations
raise CommandArgError(
"argument parsing for 'package ifneeded' not implemented, any script argument"
" will not be checked for violations"
)
def _proc(args, parser):
if len(args) != 3:
raise CommandArgError(f"wrong # of args to proc: got {len(args)}, expected 3")
# Parse args as list, then iterate over each item to parse arg specifier lists and
# do some validation. We don't store non-defaulted arguments as Lists so that they
# don't get formatted inside braces.
arg_list = parser.parse_list(args[1])
for i, arg in enumerate(arg_list.children):
if isinstance(arg, BareWord):
continue
arg_specifier = parser.parse_list(arg)
arg_specifier_len = len(arg_specifier.children)
if arg_specifier_len == 2:
arg_list.children[i] = arg_specifier
elif arg_specifier_len != 1:
raise CommandArgError(
f"too many fields in argument specifier: got {arg_specifier_len},"
" expected no more than 2"
)
return args[0:1] + [arg_list, parser.parse_script(args[2])]
def _return(args, parser):
args = list(args)
while len(args) > 0:
option = args.pop(0).contents
try:
if option == "-code":
arg = args.pop(0)
try:
_check_code(arg)
except CommandArgError as e:
raise CommandArgError(f"invalid value for return -code: {e}")
elif option == "-level":
val = args.pop(0).contents
if val is None:
continue
try:
if int(val) >= 0:
continue
except ValueError:
pass
raise CommandArgError(
f"invalid value for return -level: got {val}, expected a"
" non-negative integer"
)
elif option in {"-errorcode", "-errorinfo", "-errorstack", "-options"}:
args.pop(0)
else:
break
except IndexError:
raise CommandArgError(
f"insufficient args to return: expected value after {option}"
)
if len(args) > 0:
raise CommandArgError(
"too many arguments to return: expected no more than 1 argument after"
" explicit options. Provide -options argument if you intend to specify"
" additional return options."
)
return None
def _switch(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/switch.html
# This one's complicated...
# TODO: better checking of malformed switch command
arg_contents = [arg.contents for arg in args]
arg_i = 0
try:
arg_i = arg_contents.index("--") + 1
except ValueError:
while True:
contents = args[arg_i].contents
if contents in {"-exact", "-glob", "-regexp", "-nocase"}:
arg_i += 1
elif contents in {"-matchvar", "-indexvar"}:
arg_i += 2
else:
break
# accounts for string to be matched
arg_i += 1
new_args = args[0:arg_i]
# one argument left => form where patterns and bodies are in list
last_arg_is_list = arg_i == len(args) - 1
if last_arg_is_list:
pattern_and_commands_list = parser.parse_list(args[arg_i])
new_args.append(pattern_and_commands_list)
pattern_and_commands = pattern_and_commands_list.children
else:
pattern_and_commands = args[arg_i:]
if len(pattern_and_commands) % 2 != 0:
raise CommandArgError("Expected even number of patterns and commands")
parsed_patterns_and_commands = []
for i, node in enumerate(pattern_and_commands):
if i % 2 == 0:
parsed_patterns_and_commands.append(node)
else:
parsed_patterns_and_commands.append(parser.parse_script(node))
if last_arg_is_list:
pattern_and_commands_list.children = parsed_patterns_and_commands
else:
new_args.extend(parsed_patterns_and_commands)
return new_args
def _time(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/time.html
if len(args) < 1:
raise CommandArgError(
f"not enough args to time: got {len(args)}, expected at least 1"
)
if len(args) > 2:
raise CommandArgError(
f"too many args to time: got {len(args)}, expected no more than 2"
)
if len(args) == 2:
time = args[1].contents
if time is not None:
try:
int(time)
except ValueError:
raise CommandArgError(
"invalid argument to time: expected integer for last argument"
)
return [parser.parse_script(args[0])] + args[1:]
def _timerate(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/timerate.html
# timerate doesn't seem to be implemented in tclsh 8.6 for me - why?
args = list(args)
new_args = []
while True:
try:
arg = args.pop(0)
except IndexError:
raise CommandArgError("invalid arguments to timerate: expected script body")
if arg.contents in {"-direct", "-calibrate"}:
new_args.append(arg)
elif arg.contents in {"-overhead"}:
new_args.append(arg)
try:
val = args.pop(0)
if val.contents is not None:
float(val.contents)
except (ValueError, IndexError, TypeError):
raise CommandArgError(
"invalid argument to timerate: -overhead must be followed by a"
" double"
)
new_args.append(val)
else:
break
new_args.append(parser.parse_script(arg))
if len(args) > 2:
raise CommandArgError(
"too many arguments to timerate: expected no more than 2 arguments"
" following script body"
)
try:
[int(arg.contents) for arg in args]
except ValueError:
raise CommandArgError(
"invalid argument to timerate: expected one or two integers following"
" script body"
)
return new_args + args
def _try(args, parser):
# ref: https://www.tcl.tk/man/tcl/TclCmd/try.html
args = list(args)
new_args = []
while True:
try:
arg = args.pop(0)
except IndexError:
raise CommandArgError("invalid arguments to try: missing script body")
new_args.append(parser.parse_script(arg))
try:
arg = args.pop(0)
except IndexError:
break
new_args.append(arg)
if arg.contents == "on":
try:
code = args.pop(0)
try:
_check_code(code)
except CommandArgError as e:
raise CommandArgError(
f"invalid code argument to 'on' handler in try: {e}"
)
new_args.append(code)
new_args.append(args.pop(0))
except IndexError:
raise CommandArgError(
"invalid arguments to try: expected 3 arguments after 'on' handler"
)
elif arg.contents == "trap":
try:
new_args.append(args.pop(0))
new_args.append(args.pop(0))
except IndexError:
raise CommandArgError(
"invalid arguments to try: expected 3 arguments after 'trap'"
" handler"
)
elif arg.contents == "finally":
continue
else:
raise CommandArgError(
"invalid handler argument to try: expected one of 'on', 'trap', or"
" 'finally'"
)
return new_args
def _while(args, parser):
if len(args) != 2:
raise CommandArgError(f"wrong # of args to while: got {len(args)}, expected 2")
return [
parser.parse_expression(args[0]),
parser.parse_script(args[1]),
]
commands = commands_schema({
"after": {
"subcommands": {
"cancel": _after_cancel,
"idle": _after_idle,
"info": {
"positionals": [
{"name": "id", "value": {"type": "any"}, "required": False}
]
},
"": _after,
},
},
"append": {
"positionals": [
{"name": "varname", "value": {"type": "any"}, "required": True},
{"name": "value", "value": {"type": "variadic"}, "required": False},
]
},
"apply": _apply,
"array": _array,
"binary": {
"subcommands": {
"decode": check_count("binary decode", 2, None),
"encode": check_count("binary encode", 2, None),
"format": check_count("binary format", 1, None),
"scan": check_count("binary scan", 2, None),
},
},
"break": check_count("break", 0, 0),
"catch": _catch,
"cd": {
"positionals": [
{"name": "dirName", "value": {"type": "any"}, "required": False}
],
},
"chan": _chan,
# TODO: check subcommands
"clock": check_count("clock"),
"close": {
"positionals": [
{"name": "channelId", "value": {"type": "any"}, "required": True},
{"name": "read|write", "value": {"type": "any"}, "required": False},
],
},
"concat": {
"positionals": [
{"name": "arg", "value": {"type": "variadic"}, "required": True},
]
},
"continue": {},
"coroutine": {
"positionals": [
{"name": "name", "value": {"type": "any"}, "required": True},
{"name": "command", "value": {"type": "any"}, "required": True},
{"name": "arg", "value": {"type": "variadic"}, "required": False},
]
},
"dict": {
"subcommands": {
"append": check_count("dict append", 2, None),
"create": check_count("dict create"),
"exists": check_count("dict exists", 2, None),
"filter": _dict_filter,
"for": _dict_map_for("dict for"),
"get": check_count("dict get", 1, None),
"incr": check_count("dict incr", 2, 3),
"info": check_count("dict info", 1, 1),
"keys": check_count("dict keys", 1, 2),
"lappend": check_count("dict lappend", 2, None),
"map": _dict_map_for("dict map"),
"merge": check_count("dict merge"),
"remove": check_count("dict remove", 1, None),
"replace": check_count("dict replace", 1, None),
"set": check_count("dict set", 3, None),
"size": check_count("dict size", 1, 1),
"unset": check_count("dict unset", 2, None),
"update": _dict_update,
"values": check_count("dict values", 1, 2),
"with": _dict_with,
},
},
"encoding": {
"subcommands": {
"convertfrom": check_count("encoding convertfrom", 1, 2),
"convertto": check_count("encoding convertto", 1, 2),
"dirs": check_count("encoding dirs", 0, 1),
"names": check_count("encoding names", 0, 0),
"system": check_count("encoding system", 0, 1),
},
},
"eof": check_count("eof", 1, 1),
"error": check_count("error", 1, 3),
"eval": _eval,
"exec": check_count("exec", 1, None),
"exit": check_count("exit", 0, 1),
"expr": _expr,
"fblocked": check_count("fblocked", 1, 1),
"fconfigure": check_count("fconfigure", 1, None),
"fcopy": check_count("fcopy", 2, 6),
# TODO: check subcommands
"file": check_count("file", 1, None),
"fileevent": _fileevent,
"flush": check_count("flush", 1, 1),
"for": _for,
"foreach": _foreach,
"format": check_count("format", 1, None),
"gets": check_count("gets", 1, 2),
"glob": check_count("glob"),
"global": check_count("global"),
"history": check_count("history"),
"if": _if,
"incr": check_count("incr", 1, 2),
# TODO: check subcommands
"info": check_count("info", 1, None),
# TODO: check other subcommands
"interp": {
"subcommands": {
"eval": _interp_eval,
"": check_count("interp", 1, None),
},
},
"join": check_count("join", 1, 2),
"lappend": check_count("lappend", 1, None),
"lassign": check_count("lassign", 1, None),
"lindex": check_count("lindex", 1, None),
"linsert": check_count("linsert", 2, None),
"list": check_count("list", 0, None),
"llength": check_count("llength", 1, 1),
"lrepeat": check_count("lrepeat", 1, None),
"lreplace": check_count("lreplace", 3, None),
"lreverse": check_count("lreverse", 1, 1),
"lset": check_count("lset", 2, None),
"lsort": check_count("lsort", 1, None),
"lmap": _lmap,
"load": check_count("load", 1, 6),
"lrange": check_count("lrange", 3, 3),
"lsearch": check_count("lsearch", 2, None),
"memory": {
"subcommands": {
"active": check_count("memory active", 1, 1),
"break_on_malloc": check_count("memory break_on_malloc", 1, 1),
"info": check_count("memory info", 0, 0),
# just on or off
"init": check_count("memory init", 1, 1),
"objs": check_count("memory objs", 1, 1),
"onexit": check_count("memory onexit", 1, 1),
"tag": check_count("memory tag", 1, 1),
# just on or off
"trace": check_count("memory trace", 1, 1),
"trace_on_at_malloc": check_count("memory trace_on_at_malloc", 1, 1),
# just on or off
"validate": check_count("memory validate", 1, 1),
},
},
"namespace": {
"subcommands": {
"children": check_count("namespace children", 0, 2),
"code": _namespace_code,
"current": check_count("namespace current", 0, 0),
"delete": None,
"eval": _namespace_eval,
"exists": check_count("namespace exists", 1, 1),
"export": None,
"forget": None,
"import": None,
"inscope": _namespace_inscope,
"origin": check_count("namespace origin", 1, 1),
"parent": check_count("namespace parent", 0, 1),
"qualifiers": check_count("namespace qualifiers", 1, 1),
"tail": check_count("namespace tail", 1, 1),
"which": check_count("namespace which", 1, 2),
"ensemble": {
"subcommands": {
"create": None,
"configure": check_count("namespace ensemble configure", 1, None),
"exists": check_count("namespace ensemble exists", 1, 1),
},
},
},
},
"open": check_count("open", 1, 3),
"package": {
"subcommands": {
"forget": None,
"ifneeded": _package_ifneeded,
"names": check_count("package names", 0, 0),
"present": check_count("package present", 0, None),
"provide": check_count("package provide", 1, 2),
"require": check_count("package require", 1, None),
"unknown": check_count("package unknown", 1, None),
"vcompare": check_count("package vcompare", 2, 2),
"versions": check_count("package versions", 1, 1),
"vsatisfies": check_count("package vsatisfies", 2, None),
"prefer": check_count("package prefer", 1, 1),
},
},
"pid": check_count("pid", 0, 1),
"pkg::create": check_count("pkg::create", 2, None),
"pkg_mkIndex": check_count("pkg_mkIndex", 1, None),
"proc": _proc,
"puts": {
"positionals": [
{"name": "-nonewline", "value": {"type": "any"}, "required": False},
{"name": "channelId", "value": {"type": "any"}, "required": False},
{"name": "string", "value": {"type": "any"}, "required": True},
],
},
"pwd": check_count("pwd", 0, 0),
"read": check_count("read", 1, 2),
"regexp": check_count("regexp", 2, None),
"regsub": check_count("regsub", 3, None),
"rename": check_count("rename", 2, 2),
"return": _return,
# TODO: check subcommands
"safe": check_count("safe", 1, None),
"scan": check_count("scan", 2, None),
"seek": check_count("seek", 2, 3),
"set": check_count("set", 1, 2),
"socket": check_count("socket", 2, None),
"source": check_count("source", 1, 3),
"split": check_count("split", 1, 2),
# TODO: check subcommands
"string": check_count("string", 2, None),
"subst": check_count("subst", 1, 4),
"switch": _switch,
"tailcall": check_count("tailcall", 1, None),
"tcl::prefix": {
"subcommands": {
"all": check_count("tcl::prefix all", 2, 2),
"longest": check_count("tcl::prefix longest", 2, 2),
"match": check_count("tcl::prefix match", 2, None),
},
},
"tell": check_count("tell", 1, 1),
"throw": check_count("throw", 2, 2),
"time": _time,
"timerate": _timerate,
"tcl::tm::path": {
"subcommands": {
"add": check_count("tcl::tm::path add"),
"remove": check_count("tcl::tm::path remove"),
"list": check_count("tcl::tm::path list", 0, 0),
},
},
"tcl::tm::roots": check_count("tcl::tm::roots"),
# TODO: check subcommands
"trace": check_count("trace", 2, None),
"try": _try,
"unload": check_count("unload", 1, 6),
"unset": check_count("unset"),
"update": check_count("update", 0, 1),
"uplevel": check_count("uplevel", 1, None),
"upvar": check_count("upvar", 2, None),
"variable": check_count("variable", 1, None),
"vwait": check_count("vwait", 1, 1),
"while": _while,
"yield": {
"positionals": [
{"name": "value", "value": {"type": "any"}, "required": False},
]
},
"yieldto": {
"positionals": [
{"name": "command", "value": {"type": "any"}, "required": True},
{"name": "arg", "value": {"type": "variadic"}, "required": False},
]
},
# TODO: check subcommands
"zlib": check_count("zlib", 3, None),
})