1079 lines
35 KiB
Python
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),
|
|
})
|