From e62f99cc16d858466c3ef44bc7264f8a41469cfb Mon Sep 17 00:00:00 2001 From: Andy Chu Date: Sun, 3 Feb 2019 23:46:33 -0800 Subject: [PATCH] [regex] Parse quoted regex metacharacters as bash does. bash-completion relies on this odd behavior. Also extract a gold test case and a benchmark from it (testdata/parse-help/excerpt.sh). The benchmark still needs to be automated. --- benchmarks/parse-help.sh | 77 +++++++++++ core/id_kind.py | 2 +- frontend/lex.py | 11 +- osh/expr_eval.py | 1 + osh/word_eval.py | 5 +- spec/regex.test.sh | 55 ++++++++ test/gold.sh | 9 ++ testdata/parse-help/excerpt.sh | 77 +++++++++++ testdata/parse-help/ls-short.txt | 2 + testdata/parse-help/ls.txt | 116 ++++++++++++++++ testdata/parse-help/mypy.txt | 224 +++++++++++++++++++++++++++++++ 11 files changed, 571 insertions(+), 8 deletions(-) create mode 100755 benchmarks/parse-help.sh create mode 100755 testdata/parse-help/excerpt.sh create mode 100644 testdata/parse-help/ls-short.txt create mode 100644 testdata/parse-help/ls.txt create mode 100644 testdata/parse-help/mypy.txt diff --git a/benchmarks/parse-help.sh b/benchmarks/parse-help.sh new file mode 100755 index 0000000000..563529f322 --- /dev/null +++ b/benchmarks/parse-help.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# +# A pure string-processing benchmark extracted from bash-completion. +# +# Usage: +# ./parse-help.sh + +set -o nounset +set -o pipefail +set -o errexit + +readonly DATA_DIR='testdata/parse-help' +readonly EXCERPT=testdata/parse-help/excerpt.sh + +# TODO: Check these in to testdata/parse-help +collect() { + mkdir -p $DATA_DIR + + ls --help > $DATA_DIR/ls.txt + ~/.local/bin/mypy --help > $DATA_DIR/mypy.txt + + wc -l $DATA_DIR/* +} + +shorten() { + egrep '^[ ]+-' $DATA_DIR/ls.txt | head -n 2 | tee $DATA_DIR/ls-short.txt +} + +run-cmd() { + local sh=$1 + local cmd=$2 + # read from stdin + time cat $DATA_DIR/$cmd.txt \ + | $sh $EXCERPT _parse_help - +} + +# Geez: +# ls mypy +# bash 25ms 25ms +# OSH 600ms 900ms There is a lot of variance here too. + +# Well I guess that is 25x slower? It's a computationally expensive thing. +# Oh part of this is because printf is not a builtin! Doh. +# +# TODO +# - count the number of printf invocations. But you have to do it recursively! +# - Turn this into a proper benchmark with an HTML page. + +all() { + wc -l $DATA_DIR/* + + for sh in bash bin/osh; do + echo + echo "--- $sh --- " + echo + + for cmd in ls-short ls mypy; do + run-cmd $sh $cmd >/dev/null + done + done +} + +one() { + local sh='bin/osh' + local cmd='ls-short' + export PS4='+[${LINENO}:${FUNCNAME[0]}] ' + time cat $DATA_DIR/$cmd.txt | $sh -x $EXCERPT _parse_help - +} + +compare-one() { + local cmd='ls-short' + time cat $DATA_DIR/$cmd.txt | bin/osh $EXCERPT _parse_help - + echo --- + time cat $DATA_DIR/$cmd.txt | bash $EXCERPT _parse_help - +} + +"$@" diff --git a/core/id_kind.py b/core/id_kind.py index d7bc98817c..59b76a8d56 100755 --- a/core/id_kind.py +++ b/core/id_kind.py @@ -149,7 +149,7 @@ def AddKinds(spec): spec.AddKind('Lit', [ 'Chars', 'VarLike', 'ArrayLhsOpen', 'ArrayLhsClose', - 'Other', 'EscapedChar', + 'Other', 'EscapedChar', 'RegexMeta', # Either brace expansion or keyword for { and } 'LBrace', 'RBrace', 'Comma', 'DRightBracket', # the ]] that matches [[, NOT a keyword diff --git a/frontend/lex.py b/frontend/lex.py index 7c6580a2b0..46666cdc93 100644 --- a/frontend/lex.py +++ b/frontend/lex.py @@ -374,15 +374,14 @@ def IsKeyword(name): R(r'[a-zA-Z0-9_/-]+', Id.Lit_Chars), # not including period R(r'[ \t\r]+', Id.WS_Space), - # From _BACKSLASH + # Normally, \x evalutes to x. But quoted regex metacharacters like \* should + # evaluate to \*. Compare with ( | ). + R(r'\\[*+?.^$\[\]]', Id.Lit_RegexMeta), + + # Everything else is an escape. R(r'\\[^\n\0]', Id.Lit_EscapedChar), C('\\\n', Id.Ignored_LineCont), - #C('{', Id.Lit_RegexMeta), # { -> \{ - #C('}', Id.Lit_RegexMeta), # } -> \} - # In [[ foo =~ foo$ ]], the $ doesn't get escaped - #C('$', Id.Lit_RegexMeta), - # NOTE: ( | and ) aren't operators! R(r'[^\0]', Id.Lit_Other), # everything else is literal ] diff --git a/osh/expr_eval.py b/osh/expr_eval.py index cf5220e886..e13238d451 100755 --- a/osh/expr_eval.py +++ b/osh/expr_eval.py @@ -734,6 +734,7 @@ def Eval(self, node): return s1 != s2 if op_id == Id.BoolBinary_EqualTilde: + # TODO: This should go to --debug-file #log('Matching %r against regex %r', s1, s2) try: matches = libc.regex_match(s2, s1) diff --git a/osh/word_eval.py b/osh/word_eval.py index 220eae7d36..f9c8fc0abc 100644 --- a/osh/word_eval.py +++ b/osh/word_eval.py @@ -680,7 +680,10 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): regex, warnings = glob_.GlobToERE(pat_val.s) if warnings: - # TODO: Add strict mode and expose warnings. + # TODO: + # - Add 'set -o strict-glob' mode and expose warnings. + # "Glob is not in CANONICAL FORM". + # - Propagate location info back to the 'op.pat' word. pass replacer = string_ops.GlobReplacer(regex, replace_str, op.spids[0]) diff --git a/spec/regex.test.sh b/spec/regex.test.sh index 71c8ad8d6a..69cd915b81 100644 --- a/spec/regex.test.sh +++ b/spec/regex.test.sh @@ -91,6 +91,14 @@ pat="^(a b)$" ## OK zsh stdout: true ## OK zsh status: 0 +#### Mixing quoted and unquoted parts +[[ 'a b' =~ 'a 'b ]] && echo true +[[ "a b" =~ "a "'b' ]] && echo true +## STDOUT: +true +true +## END + #### Regex with == and not =~ is parse error, different lexer mode required # They both give a syntax error. This is lame. [[ '^(a b)$' == ^(a\ b)$ ]] && echo true @@ -125,6 +133,53 @@ pat="^(a b)$" ## N-I zsh stdout-json: "" ## N-I zsh status: 1 +#### Regex to match literal brackets [] + +# bash-completion relies on this, so we're making it match bash. +# zsh understandably differs. +[[ '[]' =~ \[\] ]] && echo true + +# Another way to write this. +pat='\[\]' +[[ '[]' =~ $pat ]] && echo true +## STDOUT: +true +true +## END +## OK zsh STDOUT: +true +## END + +#### Regex to match literals . ^ $ etc. +[[ 'x' =~ \. ]] || echo false +[[ '.' =~ \. ]] && echo true + +[[ 'xx' =~ \^\$ ]] || echo false +[[ '^$' =~ \^\$ ]] && echo true + +[[ 'xxx' =~ \+\*\? ]] || echo false +[[ '*+?' =~ \*\+\? ]] && echo true + +[[ 'xx' =~ \{\} ]] || echo false +[[ '{}' =~ \{\} ]] && echo true +## STDOUT: +false +true +false +true +false +true +false +true +## END +## BUG zsh STDOUT: +true +false +false +false +## END +## BUG zsh status: 1 + #### Unquoted { is parse error in bash/zsh [[ { =~ { ]] && echo true echo status=$? diff --git a/test/gold.sh b/test/gold.sh index c9195e2d51..e3762783cb 100755 --- a/test/gold.sh +++ b/test/gold.sh @@ -127,6 +127,13 @@ errexit-confusion() { _compare gold/errexit-confusion.sh run-for-release-FIXED } +parse-help() { + local dir=testdata/parse-help + + # This is not hermetic since it calls 'ls' + _compare $dir/excerpt.sh _parse_help ls +} + readonly -a PASSING=( # FLAKY: This one differs by timestamp #version-text @@ -158,6 +165,8 @@ readonly -a PASSING=( errexit-confusion + parse-help + # This one takes a little long, but it's realistic. #wild diff --git a/testdata/parse-help/excerpt.sh b/testdata/parse-help/excerpt.sh new file mode 100755 index 0000000000..c4679b9573 --- /dev/null +++ b/testdata/parse-help/excerpt.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# +# A string processing test case copied from bash_completion. + +# This function shell-quotes the argument +quote() +{ + local quoted=${1//\'/\'\\\'\'} + printf "'%s'" "$quoted" +} + +# This function shell-dequotes the argument +dequote() +{ + eval printf %s "$1" 2> /dev/null +} + +# Helper function for _parse_help and _parse_usage. +__parse_options() +{ + local option option2 i IFS=$' \t\n,/|' + + # Take first found long option, or first one (short) if not found. + option= + local -a array + read -a array <<<"$1" + for i in "${array[@]}"; do + case "$i" in + ---*) break ;; + --?*) option=$i ; break ;; + -?*) [[ $option ]] || option=$i ;; + *) break ;; + esac + done + [[ $option ]] || return + + IFS=$' \t\n' # affects parsing of the regexps below... + + # Expand --[no]foo to --foo and --nofoo etc + if [[ $option =~ (\[((no|dont)-?)\]). ]]; then + option2=${option/"${BASH_REMATCH[1]}"/} + option2=${option2%%[<{().[]*} + printf '%s\n' "${option2/=*/=}" + option=${option/"${BASH_REMATCH[1]}"/"${BASH_REMATCH[2]}"} + fi + + option=${option%%[<{().[]*} + printf '%s\n' "${option/=*/=}" +} + +# Parse GNU style help output of the given command. +# @param $1 command; if "-", read from stdin and ignore rest of args +# @param $2 command options (default: --help) +# +_parse_help() +{ + eval local cmd=$( quote "$1" ) + local line + { case $cmd in + -) cat ;; + *) LC_ALL=C "$( dequote "$cmd" )" ${2:---help} 2>&1 ;; + esac } \ + | while read -r line; do + + [[ $line == *([[:blank:]])-* ]] || continue + # transform "-f FOO, --foo=FOO" to "-f , --foo=FOO" etc + while [[ $line =~ \ + ((^|[^-])-[A-Za-z0-9?][[:space:]]+)\[?[A-Z0-9]+\]? ]]; do + line=${line/"${BASH_REMATCH[0]}"/"${BASH_REMATCH[1]}"} + done + __parse_options "${line// or /, }" + + done +} + +"$@" + diff --git a/testdata/parse-help/ls-short.txt b/testdata/parse-help/ls-short.txt new file mode 100644 index 0000000000..fb0ad56d2e --- /dev/null +++ b/testdata/parse-help/ls-short.txt @@ -0,0 +1,2 @@ + -a, --all do not ignore entries starting with . + -A, --almost-all do not list implied . and .. diff --git a/testdata/parse-help/ls.txt b/testdata/parse-help/ls.txt new file mode 100644 index 0000000000..f6fcb2b5a1 --- /dev/null +++ b/testdata/parse-help/ls.txt @@ -0,0 +1,116 @@ +Usage: ls [OPTION]... [FILE]... +List information about the FILEs (the current directory by default). +Sort entries alphabetically if none of -cftuvSUX nor --sort is specified. + +Mandatory arguments to long options are mandatory for short options too. + -a, --all do not ignore entries starting with . + -A, --almost-all do not list implied . and .. + --author with -l, print the author of each file + -b, --escape print C-style escapes for nongraphic characters + --block-size=SIZE scale sizes by SIZE before printing them; e.g., + '--block-size=M' prints sizes in units of + 1,048,576 bytes; see SIZE format below + -B, --ignore-backups do not list implied entries ending with ~ + -c with -lt: sort by, and show, ctime (time of last + modification of file status information); + with -l: show ctime and sort by name; + otherwise: sort by ctime, newest first + -C list entries by columns + --color[=WHEN] colorize the output; WHEN can be 'always' (default + if omitted), 'auto', or 'never'; more info below + -d, --directory list directories themselves, not their contents + -D, --dired generate output designed for Emacs' dired mode + -f do not sort, enable -aU, disable -ls --color + -F, --classify append indicator (one of */=>@|) to entries + --file-type likewise, except do not append '*' + --format=WORD across -x, commas -m, horizontal -x, long -l, + single-column -1, verbose -l, vertical -C + --full-time like -l --time-style=full-iso + -g like -l, but do not list owner + --group-directories-first + group directories before files; + can be augmented with a --sort option, but any + use of --sort=none (-U) disables grouping + -G, --no-group in a long listing, don't print group names + -h, --human-readable with -l and/or -s, print human readable sizes + (e.g., 1K 234M 2G) + --si likewise, but use powers of 1000 not 1024 + -H, --dereference-command-line + follow symbolic links listed on the command line + --dereference-command-line-symlink-to-dir + follow each command line symbolic link + that points to a directory + --hide=PATTERN do not list implied entries matching shell PATTERN + (overridden by -a or -A) + --indicator-style=WORD append indicator with style WORD to entry names: + none (default), slash (-p), + file-type (--file-type), classify (-F) + -i, --inode print the index number of each file + -I, --ignore=PATTERN do not list implied entries matching shell PATTERN + -k, --kibibytes default to 1024-byte blocks for disk usage + -l use a long listing format + -L, --dereference when showing file information for a symbolic + link, show information for the file the link + references rather than for the link itself + -m fill width with a comma separated list of entries + -n, --numeric-uid-gid like -l, but list numeric user and group IDs + -N, --literal print raw entry names (don't treat e.g. control + characters specially) + -o like -l, but do not list group information + -p, --indicator-style=slash + append / indicator to directories + -q, --hide-control-chars print ? instead of nongraphic characters + --show-control-chars show nongraphic characters as-is (the default, + unless program is 'ls' and output is a terminal) + -Q, --quote-name enclose entry names in double quotes + --quoting-style=WORD use quoting style WORD for entry names: + literal, locale, shell, shell-always, + shell-escape, shell-escape-always, c, escape + -r, --reverse reverse order while sorting + -R, --recursive list subdirectories recursively + -s, --size print the allocated size of each file, in blocks + -S sort by file size, largest first + --sort=WORD sort by WORD instead of name: none (-U), size (-S), + time (-t), version (-v), extension (-X) + --time=WORD with -l, show time as WORD instead of default + modification time: atime or access or use (-u); + ctime or status (-c); also use specified time + as sort key if --sort=time (newest first) + --time-style=STYLE with -l, show times using style STYLE: + full-iso, long-iso, iso, locale, or +FORMAT; + FORMAT is interpreted like in 'date'; if FORMAT + is FORMAT1FORMAT2, then FORMAT1 applies + to non-recent files and FORMAT2 to recent files; + if STYLE is prefixed with 'posix-', STYLE + takes effect only outside the POSIX locale + -t sort by modification time, newest first + -T, --tabsize=COLS assume tab stops at each COLS instead of 8 + -u with -lt: sort by, and show, access time; + with -l: show access time and sort by name; + otherwise: sort by access time, newest first + -U do not sort; list entries in directory order + -v natural sort of (version) numbers within text + -w, --width=COLS set output width to COLS. 0 means no limit + -x list entries by lines instead of by columns + -X sort alphabetically by entry extension + -Z, --context print any security context of each file + -1 list one file per line. Avoid '\n' with -q or -b + --help display this help and exit + --version output version information and exit + +The SIZE argument is an integer and optional unit (example: 10K is 10*1024). +Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000). + +Using color to distinguish file types is disabled both by default and +with --color=never. With --color=auto, ls emits color codes only when +standard output is connected to a terminal. The LS_COLORS environment +variable can change the settings. Use the dircolors command to set it. + +Exit status: + 0 if OK, + 1 if minor problems (e.g., cannot access subdirectory), + 2 if serious trouble (e.g., cannot access command-line argument). + +GNU coreutils online help: +Full documentation at: +or available locally via: info '(coreutils) ls invocation' diff --git a/testdata/parse-help/mypy.txt b/testdata/parse-help/mypy.txt new file mode 100644 index 0000000000..009984ea61 --- /dev/null +++ b/testdata/parse-help/mypy.txt @@ -0,0 +1,224 @@ +usage: mypy [-h] [-v] [-V] [more options; see below] + [-m MODULE] [-p PACKAGE] [-c PROGRAM_TEXT] [files ...] + +Mypy is a program that will type check your Python code. + +Pass in any files or folders you want to type check. Mypy will +recursively traverse any provided folders to find .py files: + + $ mypy my_program.py my_src_folder + +For more information on getting started, see: + +- http://mypy.readthedocs.io/en/latest/getting_started.html + +For more details on both running mypy and using the flags below, see: + +- http://mypy.readthedocs.io/en/latest/running_mypy.html +- http://mypy.readthedocs.io/en/latest/command_line.html + +You can also use a config file to configure mypy instead of using +command line flags. For more details, see: + +- http://mypy.readthedocs.io/en/latest/config_file.html + +Optional arguments: + -h, --help Show this help message and exit + -v, --verbose More verbose messages + -V, --version Show program's version number and exit + +Config file: + Use a config file instead of command line arguments. This is useful if you + are using many flags or want to set different options per each module. + + --config-file CONFIG_FILE + Configuration file, must have a [mypy] section + (defaults to mypy.ini, setup.cfg, ~/.mypy.ini) + --warn-unused-configs Warn about unused '[mypy-]' config + sections (inverse: --no-warn-unused-configs) + +Import discovery: + Configure how imports are discovered and followed. + + --ignore-missing-imports Silently ignore imports of missing modules + --follow-imports {normal,silent,skip,error} + How to treat imports (default normal) + --python-executable EXECUTABLE + Python executable used for finding PEP 561 + compliant installed packages and stubs + --no-site-packages Do not search for installed PEP 561 compliant + packages + --no-silence-site-packages + Do not silence errors in PEP 561 compliant + installed packages + --namespace-packages Support namespace packages (PEP 420, __init__.py- + less) (inverse: --no-namespace-packages) + +Platform configuration: + Type check code assuming it will be run under certain runtime conditions. + By default, mypy assumes your code will be run using the same operating + system and Python version you are using to run mypy itself. + + --python-version x.y Type check code assuming it will be running on + Python x.y + -2, --py2 Use Python 2 mode (same as --python-version 2.7) + --platform PLATFORM Type check special-cased code for the given OS + platform (defaults to sys.platform) + --always-true NAME Additional variable to be considered True (may be + repeated) + --always-false NAME Additional variable to be considered False (may be + repeated) + +Dynamic typing: + Disallow the use of the dynamic 'Any' type under certain conditions. + + --disallow-any-unimported + Disallow Any types resulting from unfollowed + imports + --disallow-subclassing-any + Disallow subclassing values of type 'Any' when + defining classes (inverse: --allow-subclassing- + any) + --disallow-any-expr Disallow all expressions that have type Any + --disallow-any-decorated Disallow functions that have Any in their + signature after decorator transformation + --disallow-any-explicit Disallow explicit Any in type positions + --disallow-any-generics Disallow usage of generic types that do not + specify explicit type parameters (inverse: + --allow-any-generics) + +Untyped definitions and calls: + Configure how untyped definitions and calls are handled. Note: by default, + mypy ignores any untyped function definitions and assumes any calls to + such functions have a return type of 'Any'. + + --disallow-untyped-calls Disallow calling functions without type + annotations from functions with type annotations + (inverse: --allow-untyped-calls) + --disallow-untyped-defs Disallow defining functions without type + annotations or with incomplete type annotations + (inverse: --allow-untyped-defs) + --disallow-incomplete-defs + Disallow defining functions with incomplete type + annotations (inverse: --allow-incomplete-defs) + --check-untyped-defs Type check the interior of functions without type + annotations (inverse: --no-check-untyped-defs) + --disallow-untyped-decorators + Disallow decorating typed functions with untyped + decorators (inverse: --allow-untyped-decorators) + +None and Optional handling: + Adjust how values of type 'None' are handled. For more context on how mypy + handles values of type 'None', see: + mypy.readthedocs.io/en/latest/kinds_of_types.html#no-strict-optional + + --no-implicit-optional Don't assume arguments with default values of None + are Optional (inverse: --implicit-optional) + --no-strict-optional Disable strict Optional checks (inverse: --strict- + optional) + --strict-optional-whitelist [GLOB [GLOB ...]] + Suppress strict Optional errors in all but the + provided files; implies --strict-optional (may + suppress certain other errors in non-whitelisted + files) + +Warnings: + Detect code that is sound but redundant or problematic. + + --warn-redundant-casts Warn about casting an expression to its inferred + type (inverse: --no-warn-redundant-casts) + --warn-unused-ignores Warn about unneeded '# type: ignore' comments + (inverse: --no-warn-unused-ignores) + --no-warn-no-return Do not warn about functions that end without + returning (inverse: --warn-no-return) + --warn-return-any Warn about returning values of type Any from non- + Any typed functions (inverse: --no-warn-return- + any) + +Other strictness checks: + --allow-untyped-globals Suppress toplevel errors caused by missing + annotations (inverse: --disallow-untyped-globals) + --strict Strict mode; enables the following flags: --warn- + unused-configs, --disallow-subclassing-any, + --disallow-any-generics, --disallow-untyped-calls, + --disallow-untyped-defs, --disallow-incomplete- + defs, --check-untyped-defs, --disallow-untyped- + decorators, --no-implicit-optional, --warn- + redundant-casts, --warn-unused-ignores, --warn- + return-any + +Incremental mode: + Adjust how mypy incrementally type checks and caches modules. Mypy caches + type information about modules into a cache to let you speed up future + invocations of mypy. Also see mypy's daemon mode: + mypy.readthedocs.io/en/latest/mypy_daemon.html#mypy-daemon + + --no-incremental Disable module cache (inverse: --incremental) + --cache-dir DIR Store module cache info in the given folder in + incremental mode (defaults to '.mypy_cache') + --sqlite-cache Use a sqlite database to store the cache (inverse: + --no-sqlite-cache) + --cache-fine-grained Include fine-grained dependency information in the + cache for the mypy daemon + --skip-version-check Allow using cache written by older mypy version + +Mypy internals: + Debug and customize mypy internals. + + --pdb Invoke pdb on fatal error + --show-traceback, --tb Show traceback on fatal error + --raise-exceptions Raise exception on fatal error + --custom-typing MODULE Use a custom typing module + --custom-typeshed-dir DIR + Use the custom typeshed in DIR + --warn-incomplete-stub Warn if missing type annotation in typeshed, only + relevant with --disallow-untyped-defs or + --disallow-incomplete-defs enabled (inverse: --no- + warn-incomplete-stub) + --shadow-file SOURCE_FILE SHADOW_FILE + When encountering SOURCE_FILE, read and type check + the contents of SHADOW_FILE instead. + +Error reporting: + Adjust the amount of detail shown in error messages. + + --show-error-context Precede errors with "note:" messages explaining + context (inverse: --hide-error-context) + --show-column-numbers Show column numbers in error messages (inverse: + --hide-column-numbers) + +Report generation: + Generate a report in the specified format. + + --any-exprs-report DIR + --cobertura-xml-report DIR + --html-report DIR + --linecount-report DIR + --linecoverage-report DIR + --memory-xml-report DIR + --txt-report DIR + --xml-report DIR + --xslt-html-report DIR + --xslt-txt-report DIR + +Miscellaneous: + --junit-xml JUNIT_XML Write junit.xml to the given file + --scripts-are-modules Script x becomes module x instead of __main__ + --find-occurrences CLASS.MEMBER + Print out all usages of a class member + (experimental) + +Running code: + Specify the code you want to type check. For more details, see + mypy.readthedocs.io/en/latest/running_mypy.html#running-mypy + + -m MODULE, --module MODULE + Type-check module; can repeat for more modules + -p PACKAGE, --package PACKAGE + Type-check package recursively; can be repeated + -c PROGRAM_TEXT, --command PROGRAM_TEXT + Type-check program passed in as string + files Type-check given files or directories + +Environment variables: + Define MYPYPATH for additional module search path entries.