def _InitEnviron(self, environ): # This is the way dash and bash work -- at startup, they turn everything in # 'environ' variable into shell variables. Bash has an export_env # variable. Dash has a loop through environ in init.c for n, v in environ.iteritems(): self.SetVar(ast.LhsName(n), runtime.Str(v), (var_flags.Exported,), scope.GlobalOnly)
def testShellFuncExecution(self): ex = cmd_exec_test.InitExecutor() func_node = ast.FuncDef() c1 = ast.CompoundWord() t1 = ast.token(Id.Lit_Chars, 'f1') c1.parts.append(ast.LiteralPart(t1)) c2 = ast.CompoundWord() t2 = ast.token(Id.Lit_Chars, 'f2') c2.parts.append(ast.LiteralPart(t2)) a = ast.ArrayLiteralPart() a.words = [c1, c2] w = ast.CompoundWord() w.parts.append(a) # Set global COMPREPLY=(f1 f2) pair = ast.assign_pair(ast.LhsName('COMPREPLY'), assign_op_e.Equal, w) pair.spids.append(0) # dummy pairs = [pair] body_node = ast.Assignment(Id.Assign_None, [], pairs) func_node.name = 'myfunc' func_node.body = body_node a = completion.ShellFuncAction(ex, func_node) matches = list(a.Matches([], 0, 'f')) self.assertEqual(['f1 ', 'f2 '], matches)
def SetLocalString(mem, name, s): """Set a local string. Used for: 1) for loop iteration variables 2) temporary environments like FOO=bar BAR=$FOO cmd, 3) read builtin """ assert isinstance(s, str) mem.SetVar(ast.LhsName(name), runtime.Str(s), (), scope.LocalOnly)
def _InitVarsFromEnv(self, environ): # This is the way dash and bash work -- at startup, they turn everything in # 'environ' variable into shell variables. Bash has an export_env # variable. Dash has a loop through environ in init.c for n, v in environ.iteritems(): self.SetVar(ast.LhsName(n), runtime.Str(v), (var_flags_e.Exported,), scope_e.GlobalOnly) # If it's not in the environment, initialize it. This makes it easier to # update later in ExecOpts. # TODO: IFS, PWD, etc. should follow this pattern. Maybe need a SysCall # interface? self.syscall.getcwd() etc. v = self.GetVar('SHELLOPTS') if v.tag == value_e.Undef: SetGlobalString(self, 'SHELLOPTS', '') # Now make it readonly self.SetVar( ast.LhsName('SHELLOPTS'), None, (var_flags_e.ReadOnly,), scope_e.GlobalOnly)
def ToLValue(node): """Determine if a node is a valid L-value by whitelisting tags. Args: node: ExprNode (could be VarExprNode or BinaryExprNode) """ # foo = bar, foo[1] = bar if node.tag == arith_expr_e.ArithVarRef: return ast.LhsName(node.name) if node.tag == arith_expr_e.ArithBinary: # For example, a[0][0] = 1 is NOT valid. if (node.op_id == Id.Arith_LBracket and node.left.tag == arith_expr_e.ArithVarRef): return ast.LhsIndexedName(node.left.name, node.right) return None
def ToLValue(node): """Determine if a node is a valid L-value by whitelisting tags. Args: node: ExprNode (could be VarExprNode or BinaryExprNode) """ # foo = bar, foo[1] = bar if node.tag == arith_expr_e.ArithVarRef: return ast.LhsName(node.name) if node.tag == arith_expr_e.ArithBinary: # For example, a[0][0] = 1 is NOT valid. if (node.op_id == Id.Arith_LBracket and node.left.tag == arith_expr_e.ArithVarRef): return ast.LhsIndexedName(node.left.name, node.right) # TODO: parse error context here. raise TdopParseError("Can't assign to %r" % node)
def _EvalEnv(self, node_env, out_env): """Evaluate environment variable bindings. Args: node_env: list of ast.env_pair out_env: mutated. """ # NOTE: Env evaluation is done in new scope so it doesn't persist. It also # pushes argv. Don't need that? self.mem.PushTemp() for env_pair in node_env: name = env_pair.name rhs = env_pair.val # Could pass extra bindings like out_env here? But PushTemp should work? val = self.ev.EvalWordToString(rhs) # Set each var so the next one can reference it. Example: # FOO=1 BAR=$FOO ls / self.mem.SetVar(ast.LhsName(name), val, (), scope.LocalOnly) out_env[name] = val.s self.mem.PopTemp()
def ParseSimpleCommand(self): """ Fixed transcription of the POSIX grammar (TODO: port to grammar/Shell.g) io_file : '<' filename | LESSAND filename ... io_here : DLESS here_end | DLESSDASH here_end redirect : IO_NUMBER (io_redirect | io_here) prefix_part : ASSIGNMENT_WORD | redirect cmd_part : WORD | redirect assign_kw : Declare | Export | Local | Readonly # Without any words it is parsed as a command, not an assigment assign_listing : assign_kw # Now we have something to do (might be changing assignment flags too) # NOTE: any prefixes should be a warning, but they are allowed in shell. assignment : prefix_part* assign_kw (WORD | ASSIGNMENT_WORD)+ # an external command, a function call, or a builtin -- a "word_command" word_command : prefix_part* cmd_part+ simple_command : assign_listing | assignment | proc_command Simple imperative algorithm: 1) Read a list of words and redirects. Append them to separate lists. 2) Look for the first non-assignment word. If it's declare, etc., then keep parsing words AND assign words. Otherwise, just parse words. 3) If there are no non-assignment words, then it's a global assignment. { redirects, global assignments } OR { redirects, prefix_bindings, words } OR { redirects, ERROR_prefix_bindings, keyword, assignments, words } THEN CHECK that prefix bindings don't have any array literal parts! global assignment and keyword assignments can have the of course. well actually EXPORT shouldn't have them either -- WARNING 3 cases we want to warn: prefix_bindings for assignment, and array literal in prefix bindings, or export A command can be an assignment word, word, or redirect on its own. ls >out.txt >out.txt FOO=bar # this touches the file, and hten Or any sequence: ls foo bar <in.txt ls foo bar >out.txt <in.txt ls >out.txt foo bar Or add one or more environment bindings: VAR=val env >out.txt VAR=val env here_end vs filename is a matter of whether we test that it's quoted. e.g. <<EOF vs <<'EOF'. """ result = self._ScanSimpleCommand() if not result: return None redirects, words = result if not words: # e.g. >out.txt # redirect without words node = ast.SimpleCommand() node.redirects = redirects return node prefix_bindings, suffix_words = self._SplitSimpleCommandPrefix(words) if not suffix_words: # ONE=1 TWO=2 (with no other words) if redirects: binding1 = prefix_bindings[0] _, _, _, spid = binding1 self.AddErrorContext('Got redirects in global assignment', span_id=spid) return None pairs = [] for lhs, op, rhs, spid in prefix_bindings: p = ast.assign_pair(ast.LhsName(lhs), op, rhs) p.spids.append(spid) pairs.append(p) node = ast.Assignment(Id.Assign_None, [], pairs) left_spid = word.LeftMostSpanForWord(words[0]) node.spids.append(left_spid) # no keyword spid to skip past return node kind, kw_token = word.KeywordToken(suffix_words[0]) if kind == Kind.Assign: # Here we StaticEval suffix_words[1] to see if it's a command like # 'typeset -p'. Then it becomes a SimpleCommand node instead of an # Assignment. Note we're not handling duplicate flags like 'typeset # -pf'. I see this in bashdb (bash debugger) but it can just be changed # to 'typeset -p -f'. is_command = False if len(suffix_words) > 1: ok, val, _ = word.StaticEval(suffix_words[1]) if ok and (kw_token.id, val) in self._ASSIGN_COMMANDS: is_command = True if is_command: # declare -f, declare -p, typeset -p, etc. node = self._MakeSimpleCommand(prefix_bindings, suffix_words, redirects) return node else: # declare str='', declare -a array=() if redirects: # Attach the error location to the keyword. It would be more precise # to attach it to the self.AddErrorContext('Got redirects in assignment', token=kw_token) return None if prefix_bindings: # FOO=bar local spam=eggs not allowed # Use the location of the first value. TODO: Use the whole word before # splitting. _, _, v0, _ = prefix_bindings[0] self.AddErrorContext( 'Invalid prefix bindings in assignment: %s', prefix_bindings, word=v0) return None node = self._MakeAssignment(kw_token.id, suffix_words) if not node: return None node.spids.append(kw_token.span_id) return node elif kind == Kind.ControlFlow: if redirects: self.AddErrorContext('Got redirects in control flow: %s', redirects) return None if prefix_bindings: # FOO=bar local spam=eggs not allowed # Use the location of the first value. TODO: Use the whole word before # splitting. _, _, v0, _ = prefix_bindings[0] self.AddErrorContext( 'Invalid prefix bindings in control flow: %s', prefix_bindings, word=v0) return None # Attach the token for errors. (Assignment may not need it.) if len(suffix_words) == 1: arg_word = None elif len(suffix_words) == 2: arg_word = suffix_words[1] else: self.AddErrorContext('Too many arguments') return None return ast.ControlFlow(kw_token, arg_word) else: node = self._MakeSimpleCommand(prefix_bindings, suffix_words, redirects) return node
def _MakeAssignment(self, assign_kw, suffix_words): # First parse flags, e.g. -r -x -a -A. None of the flags have arguments. flags = [] n = len(suffix_words) i = 1 while i < n: w = suffix_words[i] ok, static_val, quoted = word.StaticEval(w) if not ok or quoted: break # can't statically evaluate if static_val.startswith('-'): flags.append(static_val) else: break # not a flag, rest are args i += 1 # Now parse bindings or variable names assignments = [] while i < n: w = suffix_words[i] left_spid = word.LeftMostSpanForWord(w) kov = word.LooksLikeAssignment(w) if kov: k, op, v = kov t = word.TildeDetect(v) if t: # t is an unevaluated word with TildeSubPart a = (k, op, t, left_spid) else: a = (k, op, v, left_spid) # v is unevaluated word else: # In aboriginal in variables/sources: export_if_blank does export "$1". # We should allow that. # Parse this differently then? # dynamic-export? # It sets global variables. ok, static_val, quoted = word.StaticEval(w) if not ok or quoted: self.AddErrorContext( 'Variable names must be constant strings, got %s', w, word=w) return None # No value is equivalent to '' m = VAR_NAME_RE.match(static_val) if not m: self.AddErrorContext('Invalid variable name %r', static_val, word=w) return None a = (static_val, assign_op_e.Equal, None, left_spid) assignments.append(a) i += 1 # TODO: Also make with LhsIndexedName pairs = [] for lhs, op, rhs, spid in assignments: p = ast.assign_pair(ast.LhsName(lhs), op, rhs) p.spids.append(spid) pairs.append(p) node = ast.Assignment(assign_kw, flags, pairs) return node
def SetGlobalArray(mem, name, a): """Helper for completion.""" assert isinstance(a, list) mem.SetVar(ast.LhsName(name), runtime.StrArray(a), (), scope.GlobalOnly)
def SetGlobalString(mem, name, s): """Helper for completion, $PWD, etc.""" assert isinstance(s, str) val = runtime.Str(s) mem.SetVar(ast.LhsName(name), val, (), scope.GlobalOnly)