def InitEvaluator(): mem = cmd_exec.Mem('', []) val1 = Value.FromString('xxx') val2 = Value.FromString('yyy') pairs = [(ast.LeftVar('x'), val1), (ast.LeftVar('y'), val2)] mem.SetLocal(pairs, 0) exec_opts = cmd_exec.ExecOpts() # Don't need side effects for most things return word_eval.CompletionEvaluator(mem, exec_opts)
def InitEvaluator(): mem = cmd_exec.Mem('', []) val1 = runtime.Str('xxx') val2 = runtime.Str('yyy') pairs = [(ast.LeftVar('x'), val1), (ast.LeftVar('y'), val2)] mem.SetLocals(pairs) exec_opts = cmd_exec.ExecOpts() # Don't need side effects for most things return word_eval.CompletionWordEvaluator(mem, exec_opts)
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) pairs = [ast.assign_pair(ast.LeftVar('COMPREPLY'), w)] body_node = ast.Assignment(Id.Assign_None, pairs) func_node.body = body_node a = completion.ShellFuncAction(ex, func_node) matches = (list(a.Matches([], 0, 'f'))) self.assertEqual(['f1 ', 'f2 '], matches)
def Matches(self, words, index, prefix): # TODO: # - Set COMP_CWORD etc. in ex.mem -- in the global namespace I guess # - Then parse the reply here # This is like a stack code: # for word in words: # self.ex.PushString(word) # self.ex.PushString('COMP_WORDS') # self.ex.MakeArray() # self.ex.PushString(str(index)) # self.ex.PushString('COMP_CWORD') # TODO: Get the name instead! # self.ex.PushString(self.func_name) # self.ex.Call() # call wit no arguments # self.ex.PushString('COMP_REPLY') # How does this one work? # reply = [] # self.ex.GetArray(reply) self.ex.mem.SetGlobalArray(ast.LeftVar('COMP_WORDS'), words) self.ex.mem.SetGlobalString(ast.LeftVar('COMP_CWORD'), str(index)) self.ex.RunFunc(self.func, []) # call with no arguments # Should be COMP_REPLY to follow naming convention! Lame. defined, val = self.ex.mem.GetGlobal('COMPREPLY') if not defined: print('COMP_REPLY not defined', file=sys.stderr) return is_array, reply = val.AsArray() if not is_array: print('ERROR: COMP_REPLY should be an array, got %s', file=sys.stderr) return print('REPLY', reply) #reply = ['g1', 'g2', 'h1', 'i1'] for name in sorted(reply): if name.startswith(prefix): yield name + ' ' # full word
def SetLocal(self, name, val): """Set a single local. Used for: 1) for loop iteration variables 2) temporary environments like FOO=bar BAR=$FOO cmd, 3) read builtin """ pairs = [(ast.LeftVar(name), val)] self.SetLocals(pairs)
def _Read(self, argv): names = argv[1:] line = sys.stdin.readline() if not line: # EOF return 1 # TODO: split line and do that logic val = Value.FromString(line.strip()) pairs = [(ast.LeftVar(names[0]), val)] self.mem.SetLocal(pairs, 0) # read always uses local variables? return 0
def LeftAssign(p, w, left, rbp): """ Normal binary operator like 1+2 or 2*3, etc. """ # x += 1, or a[i] += 1 if not IsLValue(left): raise TdopParseError("Can't assign to %r (%s)" % (left, IdName(left.id))) # HACK: NullConstant makes this of type RightVar? Change that to something # generic? if left.tag == arith_expr_e.RightVar: lhs = ast.LeftVar(left.name) elif left.tag == arith_expr_e.ArithBinary: assert left.op_id == Id.Arith_LBracket # change a[i] to LeftIndex(a, i) lhs = ast.LeftIndex(left.left, left.right) else: raise AssertionError return ast.ArithAssign(word.ArithId(w), lhs, p.ParseUntil(rbp))
def _MakeAssignment(self, assign_kw, suffix_words): bindings = [] for i, w in enumerate(suffix_words): if i == 0: continue # skip over local, export, etc. left_spid = word.LeftMostSpanForWord(w) kv = word.LooksLikeAssignment(w) if kv: k, v = kv t = word.TildeDetect(v) if t: # t is an unevaluated word with TildeSubPart pair = (k, t, left_spid) else: pair = (k, v, left_spid) # v is unevaluated word else: # In aboriginal in variables/sources: export_if_blank does export "$1". # We should allow that. ok, value, quoted = word.StaticEval(w) if not ok or quoted: self.AddErrorContext( 'Variable names must be constant strings, got %s', w, word=w) return None pair = (value, None, left_spid) # No value is equivalent to '' bindings.append(pair) pairs = [] for lhs, rhs, spid in bindings: p = ast.assign_pair(ast.LeftVar(lhs), rhs) p.spids.append(spid) pairs.append(p) node = ast.Assignment(assign_kw, pairs) return node
def SetGlobalString(self, name, s): """Helper for completion, $PWD, etc.""" assert isinstance(s, str) val = runtime.Str(s) pairs = [(ast.LeftVar(name), val)] self.SetGlobals(pairs)
def SetGlobalArray(self, name, a): """Helper for completion.""" assert isinstance(a, list) val = runtime.StrArray(a) pairs = [(ast.LeftVar(name), val)] self.SetGlobals(pairs)
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) # TODO: Have a strict mode to prevent this? if redirects: # >out.txt g=foo print('WARNING: Got redirects in assignment: %s', redirects) pairs = [] for lhs, rhs, spid in prefix_bindings: p = ast.assign_pair(ast.LeftVar(lhs), 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 assign_kw, keyword_spid = word.AssignmentBuiltinId(suffix_words[0]) if assign_kw == Id.Undefined_Tok: node = self._MakeSimpleCommand(prefix_bindings, suffix_words, redirects) return node if redirects: # TODO: Make it a warning, or do it in the second stage? print( 'WARNING: Got redirects in assignment: %s' % redirects, file=sys.stderr) 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(assign_kw, suffix_words) if not node: return None node.spids.append(keyword_spid) return node
def SetLocal(self, name, val): """Set a single local.""" pairs = [(ast.LeftVar(name), val)] self.SetLocals(pairs)