def _SplitSimpleCommandPrefix(self, words): """ Second pass of SimpleCommand parsing: look for assignment words. """ prefix_bindings = [] suffix_words = [] done_prefix = False for w in words: if done_prefix: suffix_words.append(w) continue 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 prefix_bindings.append((k, op, t, left_spid)) else: prefix_bindings.append((k, op, v, left_spid)) # v is unevaluated word else: done_prefix = True suffix_words.append(w) return prefix_bindings, suffix_words
def _MakeSimpleCommand(self, prefix_bindings, suffix_words, redirects): # FOO=(1 2 3) ls is not allowed for k, v, _ in prefix_bindings: if word.HasArrayPart(v): self.AddErrorContext( 'Unexpected array literal in binding: %s', v, word=v) return None # echo FOO=(1 2 3) is not allowed # NOTE: Other checks can be inserted here. Can resolve builtins, # functions, aliases, static PATH, etc. for w in suffix_words: kv = word.LooksLikeAssignment(w) if kv: k, v = kv if word.HasArrayPart(v): self.AddErrorContext('Unexpected array literal: %s', v, word=v) return None words2 = self._BraceExpand(suffix_words) # NOTE: Must do tilde detection after brace expansion, e.g. # {~bob,~jane}/src should work, even though ~ isn't the leading character # of the initial word. words3 = self._TildeDetectAll(words2) node = ast.SimpleCommand() node.words = words3 node.redirects = redirects for name, val, left_spid in prefix_bindings: pair = ast.env_pair(name, val) pair.spids.append(left_spid) node.more_env.append(pair) return node
def ParseCommand(self): """ command : simple_command | compound_command io_redirect* | function_def | ksh_function_def ; """ if not self._Peek(): return None if self.c_id == Id.KW_Function: return self.ParseKshFunctionDef() if self.c_id in ( Id.KW_DLeftBracket, Id.Op_DLeftParen, Id.Op_LParen, Id.Lit_LBrace, Id.KW_For, Id.KW_While, Id.KW_Until, Id.KW_If, Id.KW_Case, Id.KW_Time): node = self.ParseCompoundCommand() if not node: return None if node.tag != command_e.TimeBlock: # The only one without redirects redirects = self._ParseRedirectList() if redirects is None: return None node.redirects = redirects return node # NOTE: I added this to fix cases in parse-errors.test.sh, but it doesn't # work because Lit_RBrace is in END_LIST below. # TODO: KW_Do is also invalid here. if self.c_id == Id.Lit_RBrace: self.AddErrorContext('Unexpected }', word=self.cur_word) return None if self.c_kind == Kind.Redir: # Leading redirect return self.ParseSimpleCommand() if self.c_kind == Kind.Word: if self.w_parser.LookAhead() == Id.Op_LParen: # ( kov = word.LooksLikeAssignment(self.cur_word) if kov: return self.ParseSimpleCommand() # f=(a b c) # array else: return self.ParseFunctionDef() # f() { echo; } # function return self.ParseSimpleCommand() # echo foo self.AddErrorContext( "ParseCommand: Expected to parse a command, got %s", self.cur_word, word=self.cur_word) return None
def _MakeSimpleCommand(self, prefix_bindings, suffix_words, redirects): # FOO=(1 2 3) ls is not allowed for k, _, v, _ in prefix_bindings: if word.HasArrayPart(v): self.AddErrorContext( 'Unexpected array literal in binding: %s', v, word=v) return None # echo FOO=(1 2 3) is not allowed # NOTE: Other checks can be inserted here. Can resolve builtins, # functions, aliases, static PATH, etc. for w in suffix_words: kov = word.LooksLikeAssignment(w) if kov: _, _, v = kov if word.HasArrayPart(v): self.AddErrorContext('Unexpected array literal: %s', v, word=w) return None # NOTE: # In bash, {~bob,~jane}/src works, even though ~ isn't the leading # character of the initial word. # However, this means we must do tilde detection AFTER brace EXPANSION, not # just after brace DETECTION like we're doing here. # The BracedWordTree instances have to be expanded into CompoundWord instances # for the tilde detection to work. words2 = braces.BraceDetectAll(suffix_words) words3 = word.TildeDetectAll(words2) node = ast.SimpleCommand() node.words = words3 node.redirects = redirects for name, op, val, left_spid in prefix_bindings: if op != assign_op_e.Equal: # NOTE: Using spid of RHS for now, since we don't have one for op. self.AddErrorContext('Expected = in environment binding, got +=', word=val) return None pair = ast.env_pair(name, val) pair.spids.append(left_spid) node.more_env.append(pair) return node
def ParseCommand(self): """ command : simple_command | compound_command io_redirect* | function_def | ksh_function_def ; """ if not self._Peek(): return None if self.c_id == Id.KW_Function: return self.ParseKshFunctionDef() if self.c_id in ( Id.KW_DLeftBracket, Id.Op_DLeftParen, Id.Op_LParen, Id.Lit_LBrace, Id.KW_For, Id.KW_While, Id.KW_Until, Id.KW_If, Id.KW_Case): node = self.ParseCompoundCommand() if not node: return None redirects = self._ParseRedirectList() if redirects is None: return None node.redirects = redirects return node if self.c_kind == Kind.Redir: # Leading redirect return self.ParseSimpleCommand() if self.c_kind == Kind.Word: if self.w_parser.LookAhead() == Id.Op_LParen: # ( kv = word.LooksLikeAssignment(self.cur_word) if kv: return self.ParseSimpleCommand() # f=(a b c) # array else: return self.ParseFunctionDef() # f() { echo; } # function return self.ParseSimpleCommand() # echo foo self.AddErrorContext( "ParseCommand: Expected to parse a command, got %s", self.cur_word, word=self.cur_word) return None
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 _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