Example #1
0
  def ParseSubshell(self):
    left_spid = word.LeftMostSpanForWord(self.cur_word)
    self._Next()  # skip past (

    # Ensure that something $( (cd / && pwd) ) works.  If ) is already on the
    # translation stack, we want to delay it.

    #print('ParseSubshell lexer.PushHint ) -> )')
    self.lexer.PushHint(Id.Op_RParen, Id.Right_Subshell)

    c_list = self.ParseCommandList()
    if not c_list: return None

    # Remove singleton CommandList as an optimization.
    if len(c_list.children) == 1:
      child = c_list.children[0]
    else:
      child = c_list
    node = ast.Subshell(child)

    right_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Right_Subshell): return None

    node.spids.extend((left_spid, right_spid))
    return node
Example #2
0
  def ParseIf(self):
    """
    if_clause        : If command_list Then command_list else_part? Fi ;
    """
    if_node = ast.If()
    self._Next()  # skip if

    cond = self.ParseCommandList()
    if not cond: return None

    then_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.KW_Then): return None

    body = self.ParseCommandList()
    if not body: return None

    arm = ast.if_arm(cond.children, body.children)
    arm.spids.extend((const.NO_INTEGER, then_spid))  # no if spid at first?
    if_node.arms.append(arm)

    if self.c_id in (Id.KW_Elif, Id.KW_Else):
      if not self._ParseElifElse(if_node):
        return None
    else:
      if_node.spids.append(const.NO_INTEGER)  # no else spid

    fi_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.KW_Fi): return None

    if_node.spids.append(fi_spid)
    return if_node
Example #3
0
  def ParseKshFunctionDef(self):
    """
    ksh_function_def : 'function' fname ( '(' ')' )? newline_ok function_body
    """
    left_spid = word.LeftMostSpanForWord(self.cur_word)

    self._Next()  # skip past 'function'

    if not self._Peek(): return None
    ok, name = word.AsFuncName(self.cur_word)
    if not ok:
      self.AddErrorContext("Invalid function name: %r", self.cur_word)
      return None
    after_name_spid = word.LeftMostSpanForWord(self.cur_word) + 1
    self._Next()  # skip past 'function name

    if not self._Peek(): return None
    if self.c_id == Id.Op_LParen:
      self.lexer.PushHint(Id.Op_RParen, Id.Right_FuncDef)
      self._Next()
      if not self._Eat(Id.Right_FuncDef): return None
      # Change it: after )
      after_name_spid = word.LeftMostSpanForWord(self.cur_word) + 1

    if not self._NewlineOk(): return None

    func = ast.FuncDef()
    func.name = name

    if not self.ParseFunctionBody(func):
      return None

    func.spids.append(left_spid)
    func.spids.append(after_name_spid)
    return func
Example #4
0
  def ParseCase(self):
    """
    case_clause      : Case WORD newline_ok in newline_ok case_list? Esac ;
    """
    case_node = ast.Case()

    case_spid = word.LeftMostSpanForWord(self.cur_word)
    self._Next()  # skip case

    if not self._Peek(): return None
    case_node.to_match = self.cur_word
    self._Next()

    if not self._NewlineOk(): return None
    in_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.KW_In): return None
    if not self._NewlineOk(): return None

    if self.c_id != Id.KW_Esac:  # empty case list
      if not self.ParseCaseList(case_node.arms):
        self.AddErrorContext("ParseCase: error parsing case list")
        return None
      # TODO: should it return a list of nodes, and extend?
      if not self._Peek(): return None

    esac_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.KW_Esac): return None
    self._Next()

    case_node.spids.extend((case_spid, in_spid, esac_spid))
    return case_node
Example #5
0
  def ParseCaseItem(self):
    """
    case_item: '('? pattern ('|' pattern)* ')'
               newline_ok command_term? trailer? ;
    """
    self.lexer.PushHint(Id.Op_RParen, Id.Right_CasePat)

    left_spid = word.LeftMostSpanForWord(self.cur_word)
    if self.c_id == Id.Op_LParen:
      self._Next()

    pat_words = []
    while True:
      if not self._Peek(): return None
      pat_words.append(self.cur_word)
      self._Next()

      if not self._Peek(): return None
      if self.c_id == Id.Op_Pipe:
        self._Next()
      else:
        break

    rparen_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Right_CasePat): return None
    if not self._NewlineOk(): return None

    if self.c_id not in (Id.Op_DSemi, Id.KW_Esac):
      c_list = self.ParseCommandTerm()
      if not c_list: return None
      action_children = c_list.children
    else:
      action_children = []

    dsemi_spid = const.NO_INTEGER
    last_spid = const.NO_INTEGER
    if not self._Peek(): return None
    if self.c_id == Id.KW_Esac:
      last_spid = word.LeftMostSpanForWord(self.cur_word)
    elif self.c_id == Id.Op_DSemi:
      dsemi_spid = word.LeftMostSpanForWord(self.cur_word)
      self._Next()
    else:
      self.AddErrorContext('Expected DSEMI or ESAC, got %s', self.cur_word,
          word=self.cur_word)
      return None

    if not self._NewlineOk(): return None

    arm = ast.case_arm(pat_words, action_children)
    arm.spids.extend((left_spid, rparen_spid, dsemi_spid, last_spid))
    return arm
Example #6
0
  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
Example #7
0
    def _ValToArithOrError(self,
                           val,
                           int_coerce=True,
                           blame_word=None,
                           span_id=const.NO_INTEGER):
        if span_id == const.NO_INTEGER and blame_word:
            span_id = word.LeftMostSpanForWord(blame_word)
        #log('_ValToArithOrError span=%s blame=%s', span_id, blame_word)

        try:
            i = self._ValToArith(val, span_id, int_coerce=int_coerce)
        except util.FatalRuntimeError as e:
            if self.exec_opts.strict_arith:
                raise
            else:
                i = 0

                span_id = dev.SpanIdFromError(e)
                if self.arena:  # BoolEvaluator for test builtin doesn't have it.
                    if span_id != const.NO_INTEGER:
                        ui.PrintFilenameAndLine(span_id, self.arena)
                    else:
                        log('*** Warning has no location info ***')
                warn(e.UserErrorString())
        return i
Example #8
0
def PrettyPrintError(parse_error, arena, f):
    #print(parse_error)
    if parse_error.token:
        span_id = parse_error.token.span_id
    elif parse_error.word:
        # Can be -1
        span_id = word.LeftMostSpanForWord(parse_error.word)
    else:
        span_id = -1

    if span_id == -1:
        line = '<no position info for token>'
        path = '<unknown>'
        line_num = -1
        col = -1
        length = -1
    else:
        line_span = arena.GetLineSpan(span_id)
        line_id = line_span.line_id
        line = arena.GetLine(line_id)
        path, line_num = arena.GetDebugInfo(line_id)
        col = line_span.col
        length = line_span.length

    print('Line %d of %r' % (line_num + 1, path), file=f)
    print('  ' + line.rstrip(), file=f)
    if col != -1:
        f.write('  ')
        # preserve tabs
        for c in line[:col]:
            f.write('\t' if c == '\t' else ' ')
        f.write('^')
        f.write('~' * (length - 1))
        f.write('\n')
Example #9
0
  def ParseFunctionDef(self):
    """
    function_header : fname '(' ')'
    function_def     : function_header newline_ok function_body ;

    Precondition: Looking at the function name.
    Post condition:

    NOTE: There is an ambiguity with:

    function foo ( echo hi ) and
    function foo () ( echo hi )

    Bash only accepts the latter, though it doesn't really follow a grammar.
    """
    left_spid = word.LeftMostSpanForWord(self.cur_word)

    ok, name = word.AsFuncName(self.cur_word)
    if not ok:
      self.AddErrorContext("Invalid function name: %r",
          self.cur_word, word=self.cur_word)
      return None
    self._Next()  # skip function name

    # Must be true beacuse of lookahead
    if not self._Peek(): return None
    assert self.c_id == Id.Op_LParen, self.cur_word

    self.lexer.PushHint(Id.Op_RParen, Id.Right_FuncDef)
    self._Next()

    if not self._Eat(Id.Right_FuncDef): return None
    after_name_spid = word.LeftMostSpanForWord(self.cur_word) + 1

    if not self._NewlineOk(): return None

    func = ast.FuncDef()
    func.name = name

    if not self.ParseFunctionBody(func):
      return None

    func.spids.append(left_spid)
    func.spids.append(after_name_spid)
    return func
Example #10
0
  def ParseDoGroup(self):
    """
    Used by ForEach, ForExpr, While, Until.  Should this be a Do node?

    do_group         : Do command_list Done ;          /* Apply rule 6 */
    """
    if not self._Eat(Id.KW_Do): return None
    do_spid = word.LeftMostSpanForWord(self.cur_word)  # after _Eat

    c_list = self.ParseCommandList()  # could be any thing
    if not c_list: return None

    if not self._Eat(Id.KW_Done): return None
    done_spid = word.LeftMostSpanForWord(self.cur_word)  # after _Eat

    node = ast.DoGroup(c_list.children)
    node.spids.extend((do_spid, done_spid))
    return node
Example #11
0
  def _ParseForEachLoop(self):
    node = ast.ForEach()
    node.do_arg_iter = False

    ok, iter_name, quoted = word.StaticEval(self.cur_word)
    if not ok or quoted:
      self.AddErrorContext(
          "Invalid for loop variable", word=self.cur_word)
      return None
    if not VAR_NAME_RE.match(iter_name):
      self.AddErrorContext(
          "Invalid for loop variable name", word=self.cur_word)
      return None
    node.iter_name = iter_name
    self._Next()  # skip past name

    if not self._NewlineOk(): return None

    in_spid = const.NO_INTEGER
    semi_spid = const.NO_INTEGER

    if not self._Peek(): return None
    if self.c_id == Id.KW_In:
      self._Next()  # skip in

      in_spid = word.LeftMostSpanForWord(self.cur_word) + 1
      x = self.ParseForWords()
      if x is None:
        return None
      iter_words, semi_spid = x
      words2 = braces.BraceDetectAll(iter_words)
      words3 = word.TildeDetectAll(words2)

      if iter_words is None:  # empty list of words is OK
        return None
      node.iter_words = words3

    elif self.c_id == Id.Op_Semi:
      node.do_arg_iter = True  # implicit for loop
      self._Next()

    elif self.c_id == Id.KW_Do:
      node.do_arg_iter = True  # implicit for loop
      # do not advance

    else:
      self.AddErrorContext("Unexpected word in for loop: %s", self.cur_word,
          word=self.cur_word)
      return None

    node.spids.extend((in_spid, semi_spid))

    body_node = self.ParseDoGroup()
    if not body_node: return None

    node.body = body_node
    return node
Example #12
0
def _assertSpanForWord(test, code_str):
  arena, w = _assertReadWordWithArena(test, code_str)
  span_id = word.LeftMostSpanForWord(w)

  print(code_str)
  print(span_id)

  if span_id != -1:
    span = arena.GetLineSpan(span_id)
    print(span)
Example #13
0
File: dev.py Project: jedahan/oil
def SpanIdFromError(error):
    #print(parse_error)
    if error.span_id != const.NO_INTEGER:
        return error.span_id
    if error.token:
        return error.token.span_id
    if error.part:
        return word.LeftMostSpanForPart(error.part)
    if error.word:
        return word.LeftMostSpanForWord(error.word)

    return const.NO_INTEGER
Example #14
0
  def ParseSubshell(self):
    left_spid = word.LeftMostSpanForWord(self.cur_word)
    self._Next()  # skip past (

    # Ensure that something $( (cd / && pwd) ) works.  If ) is already on the
    # translation stack, we want to delay it.

    #print('ParseSubshell lexer.PushHint ) -> )')
    self.lexer.PushHint(Id.Op_RParen, Id.Right_Subshell)

    child = self.ParseCommandList()
    if not child: return None

    #print('SUB', self.cur_word)
    node = ast.Subshell()
    node.children.append(child)

    right_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Right_Subshell): return None

    node.spids.extend((left_spid, right_spid))
    return node
Example #15
0
  def _ParseElifElse(self, if_node):
    """
    else_part: (Elif command_list Then command_list)* Else command_list ;
    """
    arms = if_node.arms

    self._Peek()
    while self.c_id == Id.KW_Elif:
      elif_spid = word.LeftMostSpanForWord(self.cur_word)

      self._Next()  # skip elif
      cond = self.ParseCommandList()
      if not cond: return None

      then_spid = word.LeftMostSpanForWord(self.cur_word)
      if not self._Eat(Id.KW_Then): return None

      body = self.ParseCommandList()
      if not body: return None

      arm = ast.if_arm(cond.children, body.children)
      arm.spids.extend((elif_spid, then_spid))
      arms.append(arm)

    if self.c_id == Id.KW_Else:
      else_spid = word.LeftMostSpanForWord(self.cur_word)
      self._Next()
      body = self.ParseCommandList()
      if not body: return None
      if_node.else_action = body.children
    else:
      else_spid = const.NO_INTEGER

    if_node.spids.append(else_spid)

    return True
Example #16
0
def PrintError(error_stack, arena, f):
    """
  NOTE: Parse errors always occur within a single arena.  Runtime errors may
  span arenas (e.g. the function stack).
  """
    # TODO:
    # - rename to PrintParseError()
    #   - although parse errors happen at runtime because of 'source'
    #   - should there be a distinction then?
    for parse_error in error_stack:
        print(parse_error)
        if parse_error.token:
            span_id = parse_error.token.span_id
        elif parse_error.word:
            # Can be -1
            span_id = word.LeftMostSpanForWord(parse_error.word)
        else:
            span_id = -1

        if span_id == -1:
            line = '<token had no position info>'
            path = '<unknown>'
            line_num = -1
            col = -1
            length = -1
        else:
            line_span = arena.GetLineSpan(span_id)
            line_id = line_span.line_id
            line = arena.GetLine(line_id)
            path, line_num = arena.GetDebugInfo(line_id)
            col = line_span.col
            length = line_span.length

        print('Line %d of %r' % (line_num + 1, path))
        print('  ' + line.rstrip())
        if col == -1:
            print('NO COL')
        else:
            sys.stdout.write('  ')
            # preserve tabs
            for c in line[:col]:
                sys.stdout.write('\t' if c == '\t' else ' ')
            sys.stdout.write('^')
            sys.stdout.write('~' * (length - 1))
            sys.stdout.write('\n')

        print(parse_error.UserErrorString(), file=f)
        print('---')
Example #17
0
  def _ParseForEachLoop(self):
    node = ast.ForEach()
    node.do_arg_iter = False

    ok, value, quoted = word.StaticEval(self.cur_word)
    if not ok or quoted:
      self.AddErrorContext(
          "Invalid for loop variable: %s", self.cur_word, word=self.cur_word)
      return None
    node.iter_name = value
    self._Next()  # skip past name

    if not self._NewlineOk(): return None

    in_spid = -1
    semi_spid = -1

    if not self._Peek(): return None
    if self.c_id == Id.KW_In:
      self._Next()  # skip in

      in_spid = word.LeftMostSpanForWord(self.cur_word) + 1
      iter_words, semi_spid = self.ParseForWords()
      if iter_words is None:  # empty list of words is OK
        return None
      node.iter_words = iter_words

    elif self.c_id == Id.Op_Semi:
      node.do_arg_iter = True  # implicit for loop
      self._Next()

    elif self.c_id == Id.KW_Do:
      node.do_arg_iter = True  # implicit for loop
      # do not advance

    else:
      self.AddErrorContext("Unexpected word in for loop: %s", self.cur_word,
          word=self.cur_word)
      return None

    node.spids.extend((in_spid, semi_spid))

    body_node = self.ParseDoGroup()
    if not body_node: return None

    node.body = body_node
    return node
Example #18
0
  def ParseBraceGroup(self):
    """
    brace_group      : LBrace command_list RBrace ;
    """
    left_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Lit_LBrace): return None

    c_list = self.ParseCommandList()
    if not c_list: return None

    # Not needed
    #right_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Lit_RBrace): return None

    node = ast.BraceGroup(c_list.children)
    node.spids.append(left_spid)
    return node
Example #19
0
  def _StringToIntegerOrError(self, s, blame_word=None,
                              span_id=const.NO_INTEGER):
    """Used by both [[ $x -gt 3 ]] and (( $x ))."""
    if span_id == const.NO_INTEGER and blame_word:
      span_id = word.LeftMostSpanForWord(blame_word)

    try:
      i = _StringToInteger(s, span_id=span_id)
    except util.FatalRuntimeError as e:
      if self.exec_opts.strict_arith:
        raise
      else:
        i = 0
        # TODO: Need the arena for printing this error?
        #ui.PrettyPrintError(e)
        warn(e.UserErrorString())
    return i
Example #20
0
    def DoArithExpr(self, node, local_symbols):
        if node.tag == arith_expr_e.ArithBinary:
            # Maybe I should just write the left span and right span for each word?
            #self.f.write(str(node.left))

            if node.op_id == Id.Arith_Plus:
                # NOTE: Right isn't necessarily a word!
                r_id = word.LeftMostSpanForWord(node.right.w)
                #self.cursor.SkipUntil(r_id)
                #self.f.write('PLUS')

            #self.f.write(str(node.right))
        elif node.tag == arith_expr_e.ArithWord:
            self.DoWordInCommand(node.w, local_symbols)

        else:
            log("Unhandled: %s", node)
            raise AssertionError(node.tag)
Example #21
0
File: ui.py Project: waldyrious/oil
def PrettyPrintError(parse_error, arena, f=sys.stderr):
    #print(parse_error)
    if parse_error.span_id != const.NO_INTEGER:
        span_id = parse_error.span_id
    elif parse_error.token:
        span_id = parse_error.token.span_id
    elif parse_error.part:
        span_id = word.LeftMostSpanForPart(parse_error.part)
    elif parse_error.word:
        span_id = word.LeftMostSpanForWord(parse_error.word)
    else:
        span_id = const.NO_INTEGER

    if span_id == const.NO_INTEGER:  # Any clause above might return this.
        # This is usually a bug.
        print('*** Error has no source location info ***', file=f)
        return

    PrintFilenameAndLine(span_id, arena, f=f)
Example #22
0
  def ParseBraceGroup(self):
    """
    brace_group      : LBrace command_list RBrace ;
    """
    left_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Lit_LBrace): return None

    node = self.ParseCommandList()
    if not node: return None

    # Not needed
    #right_spid = word.LeftMostSpanForWord(self.cur_word)
    if not self._Eat(Id.Lit_RBrace): return None

    # OPTIMIZATION of AST.
    # CommandList has no redirects; BraceGroup may have redirects.
    if node.tag == command_e.CommandList:
      n = ast.BraceGroup(node.children)
    else:
      n = ast.BraceGroup([node])

    n.spids.append(left_spid)
    return n
Example #23
0
  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
Example #24
0
  def DoRedirect(self, node, local_symbols):
    #print(node, file=sys.stderr)
    op_spid = node.op.span_id
    op_id = node.op.id
    self.cursor.PrintUntil(op_spid)

    # TODO:
    # - Do < and <& the same way.
    # - How to handle here docs and here docs?
    # - >> becomes >+ or >-, or maybe >>>

    if node.tag == redir_e.Redir:
      if node.fd == const.NO_INTEGER:
        if op_id == Id.Redir_Great:
          self.f.write('>')  # Allow us to replace the operator
          self.cursor.SkipUntil(op_spid + 1)
        elif op_id == Id.Redir_GreatAnd:
          self.f.write('> !')  # Replace >& 2 with > !2
          spid = word.LeftMostSpanForWord(node.arg_word)
          self.cursor.SkipUntil(spid)
          #self.DoWordInCommand(node.arg_word)

      else:
        # NOTE: Spacing like !2>err.txt vs !2 > err.txt can be done in the
        # formatter.
        self.f.write('!%d ' % node.fd)
        if op_id == Id.Redir_Great:
          self.f.write('>')
          self.cursor.SkipUntil(op_spid + 1)
        elif op_id == Id.Redir_GreatAnd:
          self.f.write('> !')  # Replace 1>& 2 with !1 > !2
          spid = word.LeftMostSpanForWord(node.arg_word)
          self.cursor.SkipUntil(spid)

      self.DoWordInCommand(node.arg_word, local_symbols)

    elif node.tag == redir_e.HereDoc:
      ok, delimiter, delim_quoted = word.StaticEval(node.here_begin)
      if not ok:
        p_die('Invalid here doc delimiter', word=node.here_begin)

      # Turn everything into <<.  We just change the quotes
      self.f.write('<<')

      #here_begin_spid2 = word.RightMostSpanForWord(node.here_begin)
      if delim_quoted:
        self.f.write(" '''")
      else:
        self.f.write(' """')

      delim_end_spid = word.RightMostSpanForWord(node.here_begin)
      self.cursor.SkipUntil(delim_end_spid + 1)

      #self.cursor.SkipUntil(here_begin_spid + 1)

      # Now print the lines.  TODO: Have a flag to indent these to the level of
      # the owning command, e.g.
      #   cat <<EOF
      # EOF
      # Or since most here docs are the top level, you could just have a hack
      # for a fixed indent?  TODO: Look at real use cases.
      for part in node.stdin_parts:
        self.DoWordPart(part, local_symbols)

      self.cursor.SkipUntil(node.here_end_span_id + 1)
      if delim_quoted:
        self.f.write("'''\n")
      else:
        self.f.write('"""\n')

      # Need
      #self.cursor.SkipUntil(here_end_spid2)

    else:
      raise AssertionError(node.__class__.__name__)

    # <<< 'here word'
    # << 'here word'
    #
    # 2> out.txt
    # !2 > out.txt

    # cat 1<< EOF
    # hello $name
    # EOF
    # cat !1 << """
    # hello $name
    # """
    #
    # cat << 'EOF'
    # no expansion
    # EOF
    #   cat <<- 'EOF'
    #   no expansion and indented
    #
    # cat << '''
    # no expansion
    # '''
    #   cat << '''
    #   no expansion and indented
    #   '''

    # Warn about multiple here docs on a line.
    # As an obscure feature, allow
    # cat << \'ONE' << \"TWO"
    # 123
    # ONE
    # 234
    # TWO
    # The _ is an indicator that it's not a string to be piped in.
    pass
Example #25
0
  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
Example #26
0
  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
Example #27
0
  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
Example #28
0
    def DoRedirect(self, node, local_symbols):
        #print(node, file=sys.stderr)
        self.cursor.PrintUntil(node.spids[0])

        # TODO:
        # - Do < and <& the same way.
        # - How to handle here docs and here docs?
        # - >> becomes >+ or >-, or maybe >>>

        if node.fd == -1:
            if node.op_id == Id.Redir_Great:
                self.f.write('>')  # Allow us to replace the operator
                self.cursor.SkipUntil(node.spids[0] + 1)
            elif node.op_id == Id.Redir_GreatAnd:
                self.f.write('> !')  # Replace >& 2 with > !2
                spid = word.LeftMostSpanForWord(node.arg_word)
                self.cursor.SkipUntil(spid)
                #self.DoWordInCommand(node.arg_word)

        else:
            # NOTE: Spacing like !2>err.txt vs !2 > err.txt can be done in the
            # formatter.
            self.f.write('!%d ' % node.fd)
            if node.op_id == Id.Redir_Great:
                self.f.write('>')
                self.cursor.SkipUntil(node.spids[0] + 1)
            elif node.op_id == Id.Redir_GreatAnd:
                self.f.write('> !')  # Replace 1>& 2 with !1 > !2
                spid = word.LeftMostSpanForWord(node.arg_word)
                self.cursor.SkipUntil(spid)

        self.DoWordInCommand(node.arg_word, local_symbols)

        # <<< 'here word'
        # << 'here word'
        #
        # 2> out.txt
        # !2 > out.txt

        # cat 1<< EOF
        # hello $name
        # EOF
        # cat !1 << """
        # hello $name
        # """
        #
        # cat << 'EOF'
        # no expansion
        # EOF
        #   cat <<- 'EOF'
        #   no expansion and indented
        #
        # cat << '''
        # no expansion
        # '''
        #   cat << '''
        #   no expansion and indented
        #   '''

        # Warn about multiple here docs on a line.
        # As an obscure feature, allow
        # cat << \'ONE' << \"TWO"
        # 123
        # ONE
        # 234
        # TWO
        # The _ is an indicator that it's not a string to be piped in.
        pass
Example #29
0
  def _Dispatch(self, node, fork_external):
    # If we call RunCommandSub in a recursive call to the executor, this will
    # be set true (if strict-errexit is false).  But it only lasts for one
    # command.
    self.check_command_sub_status = False

    #argv0 = None  # for error message
    check_errexit = False  # for errexit

    if node.tag == command_e.SimpleCommand:
      check_errexit = True

      # Find span_id for a basic implementation of $LINENO, e.g.
      # PS4='+$SOURCE_NAME:$LINENO:'
      # NOTE: osh2oil uses node.more_env, but we don't need that.
      span_id = const.NO_INTEGER
      if node.words:
        first_word = node.words[0]
        span_id = word.LeftMostSpanForWord(first_word)

      self.mem.SetCurrentSpanId(span_id)

      # PROBLEM: We want to log argv in 'xtrace' mode, but we may have already
      # redirected here, which screws up logging.  For example, 'echo hi
      # >/dev/null 2>&1'.  We want to evaluate argv and log it BEFORE applying
      # redirects.

      # Another problem:
      # - tracing can be called concurrently from multiple processes, leading
      # to overlap.  Maybe have a mode that creates a file per process.
      # xtrace-proc
      # - line numbers for every command would be very nice.  But then you have
      # to print the filename too.

      words = braces.BraceExpandWords(node.words)
      argv = self.word_ev.EvalWordSequence(words)

      # This comes before evaluating env, in case there are problems evaluating
      # it.  We could trace the env separately?  Also trace unevaluated code
      # with set-o verbose?
      self.tracer.OnSimpleCommand(argv)

      if node.more_env:
        self.mem.PushTemp()
      try:
        for env_pair in node.more_env:
          val = self.word_ev.EvalWordToString(env_pair.val)
          # Set each var so the next one can reference it.  Example:
          # FOO=1 BAR=$FOO ls /
          self.mem.SetVar(ast.LhsName(env_pair.name), val,
                          (var_flags_e.Exported,), scope_e.TempEnv)

        # NOTE: This might never return!  In the case of fork_external=False.
        status = self._RunSimpleCommand(argv, fork_external, span_id)
      finally:
        if node.more_env:
          self.mem.PopTemp()

    elif node.tag == command_e.Sentence:
      # Don't check_errexit since this isn't a real node!
      if node.terminator.id == Id.Op_Semi:
        status = self._Execute(node.child)
      else:
        status = self._RunJobInBackground(node.child)

    elif node.tag == command_e.Pipeline:
      check_errexit = True
      if node.stderr_indices:
        raise NotImplementedError('|&')

      if node.negated:
        self._PushErrExit()
        try:
          status2 = self._RunPipeline(node)
        finally:
          self._PopErrExit()

        # errexit is disabled for !.
        check_errexit = False
        status = 1 if status2 == 0 else 0
      else:
        status = self._RunPipeline(node)

    elif node.tag == command_e.Subshell:
      check_errexit = True
      # This makes sure we don't waste a process if we'd launch one anyway.
      p = self._MakeProcess(node.child)
      status = p.Run(self.waiter)

    elif node.tag == command_e.DBracket:
      check_errexit = True
      result = self.bool_ev.Eval(node.expr)
      status = 0 if result else 1

    elif node.tag == command_e.DParen:
      check_errexit = True
      i = self.arith_ev.Eval(node.child)
      status = 0 if i != 0 else 1

    elif node.tag == command_e.Assignment:
      flags = word_compile.ParseAssignFlags(node.flags)

      if node.keyword == Id.Assign_Local:
        lookup_mode = scope_e.LocalOnly
      # typeset and declare are synonyms?  I see typeset -a a=() the most.
      elif node.keyword in (Id.Assign_Declare, Id.Assign_Typeset):
        # declare is like local, except it can also be used outside functions?
        if var_flags_e.Global in flags:
          lookup_mode = scope_e.GlobalOnly
        else:
          lookup_mode = scope_e.LocalOnly
      elif node.keyword == Id.Assign_Readonly:
        lookup_mode = scope_e.Dynamic
        flags.append(var_flags_e.ReadOnly)
      elif node.keyword == Id.Assign_None:  # mutate existing local or global
        lookup_mode = scope_e.Dynamic
      else:
        raise AssertionError(node.keyword)

      for pair in node.pairs:
        if pair.op == assign_op_e.PlusEqual:
          assert pair.rhs, pair.rhs  # I don't think a+= is valid?
          val = self.word_ev.EvalRhsWord(pair.rhs)
          old_val, lval = expr_eval.EvalLhsAndLookup(pair.lhs, self.arith_ev,
                                                     self.mem, self.exec_opts)
          sig = (old_val.tag, val.tag)
          if sig == (value_e.Undef, value_e.Str):
            pass  # val is RHS
          elif sig == (value_e.Undef, value_e.StrArray):
            pass  # val is RHS
          elif sig == (value_e.Str, value_e.Str):
            val = runtime.Str(old_val.s + val.s)
          elif sig == (value_e.Str, value_e.StrArray):
            e_die("Can't append array to string")
          elif sig == (value_e.StrArray, value_e.Str):
            e_die("Can't append string to array")
          elif sig == (value_e.StrArray, value_e.StrArray):
            val = runtime.StrArray(old_val.strs + val.strs)

        else:  # plain assignment
          spid = pair.spids[0]  # Source location for tracing
          lval = self._EvalLhs(pair.lhs, spid, lookup_mode)

          # RHS can be a string or array.
          if pair.rhs:
            val = self.word_ev.EvalRhsWord(pair.rhs)
            assert isinstance(val, runtime.value), val

          else:  # e.g. 'readonly x' or 'local x'
            val = None

        # NOTE: In bash and mksh, declare -a myarray makes an empty cell with
        # Undef value, but the 'array' attribute.

        #log('setting %s to %s with flags %s', lval, val, flags)
        self.mem.SetVar(lval, val, flags, lookup_mode,
                        strict_array=self.exec_opts.strict_array)

        # Assignment always appears to have a spid.
        if node.spids:
          current_spid = node.spids[0]
        else:
          current_spid = const.NO_INTEGER
        self.mem.SetCurrentSpanId(current_spid)
        self.tracer.OnAssignment(lval, pair.op, val, flags, lookup_mode)

      # PATCH to be compatible with existing shells: If the assignment had a
      # command sub like:
      #
      # s=$(echo one; false)
      #
      # then its status will be in mem.last_status, and we can check it here.
      # If there was NOT a command sub in the assignment, then we don't want to
      # check it.
      if node.keyword == Id.Assign_None:  # mutate existing local or global
        # Only do this if there was a command sub?  How?  Look at node?
        # Set a flag in mem?   self.mem.last_status or
        if self.check_command_sub_status:
          self._CheckStatus(self.mem.last_status, node)
          # A global assignment shouldn't clear $?.
          status = self.mem.last_status
        else:
          status = 0
      else:
        # To be compatible with existing shells, local assignments DO clear
        # $?.  Even in strict mode, we don't need to bother setting
        # check_errexit = True, because we would have already checked the
        # command sub in RunCommandSub.
        status = 0
        # TODO: maybe we should have a "sane-status" that respects this:
        # false; echo $?; local f=x; echo $?

    elif node.tag == command_e.ControlFlow:
      if node.arg_word:  # Evaluate the argument
        val = self.word_ev.EvalWordToString(node.arg_word)
        assert val.tag == value_e.Str
        arg = int(val.s)  # They all take integers
      else:
        arg = 0  # return 0, exit 0, break 0 levels, etc.

      # NOTE: We don't do anything about a top-level 'return' here.  Unlike in
      # bash, that is OK.  If you can return from a sourced script, it makes
      # sense to return from a main script.
      ok = True
      tok = node.token
      if (tok.id in (Id.ControlFlow_Break, Id.ControlFlow_Continue) and
          self.loop_level == 0):
        ok = False
        msg = 'Invalid control flow at top level'

      if ok:
        raise _ControlFlow(tok, arg)

      if self.exec_opts.strict_control_flow:
        e_die(msg, token=tok)
      else:
        # Only print warnings, never fatal.
        # Bash oddly only exits 1 for 'return', but no other shell does.
        ui.PrintFilenameAndLine(tok.span_id, self.arena)
        util.warn(msg)
        status = 0

    # The only difference between these two is that CommandList has no
    # redirects.  We already took care of that above.
    elif node.tag in (command_e.CommandList, command_e.BraceGroup):
      status = self._ExecuteList(node.children)
      check_errexit = False

    elif node.tag == command_e.AndOr:
      # NOTE: && and || have EQUAL precedence in command mode.  See case #13
      # in dbracket.test.sh.

      left = node.children[0]

      # Suppress failure for every child except the last one.
      self._PushErrExit()
      try:
        status = self._Execute(left)
      finally:
        self._PopErrExit()

      i = 1
      n = len(node.children)
      while i < n:
        #log('i %d status %d', i, status)
        child = node.children[i]
        op_id = node.ops[i-1]

        #log('child %s op_id %s', child, op_id)

        if op_id == Id.Op_DPipe and status == 0:
          i += 1
          continue  # short circuit

        elif op_id == Id.Op_DAmp and status != 0:
          i += 1
          continue  # short circuit

        if i == n - 1:  # errexit handled differently for last child
          status = self._Execute(child)
          check_errexit = True
        else:
          self._PushErrExit()
          try:
            status = self._Execute(child)
          finally:
            self._PopErrExit()

        i += 1

    elif node.tag == command_e.WhileUntil:
      if node.keyword.id == Id.KW_While:
        _DonePredicate = lambda status: status != 0
      else:
        _DonePredicate = lambda status: status == 0

      status = 0

      self.loop_level += 1
      try:
        while True:
          self._PushErrExit()
          try:
            cond_status = self._ExecuteList(node.cond)
          finally:
            self._PopErrExit()

          done = cond_status != 0
          if _DonePredicate(cond_status):
            break
          try:
            status = self._Execute(node.body)  # last one wins
          except _ControlFlow as e:
            if e.IsBreak():
              status = 0
              break
            elif e.IsContinue():
              status = 0
              continue
            else:  # return needs to pop up more
              raise
      finally:
        self.loop_level -= 1

    elif node.tag == command_e.ForEach:
      iter_name = node.iter_name
      if node.do_arg_iter:
        iter_list = self.mem.GetArgv()
      else:
        words = braces.BraceExpandWords(node.iter_words)
        iter_list = self.word_ev.EvalWordSequence(words)
        # We need word splitting and so forth
        # NOTE: This expands globs too.  TODO: We should pass in a Globber()
        # object.

      status = 0  # in case we don't loop
      self.loop_level += 1
      try:
        for x in iter_list:
          #log('> ForEach setting %r', x)
          state.SetLocalString(self.mem, iter_name, x)
          #log('<')

          try:
            status = self._Execute(node.body)  # last one wins
          except _ControlFlow as e:
            if e.IsBreak():
              status = 0
              break
            elif e.IsContinue():
              status = 0
            else:  # return needs to pop up more
              raise
      finally:
        self.loop_level -= 1

    elif node.tag == command_e.ForExpr:
      status = 0
      init, cond, body, update = node.init, node.cond, node.body, node.update
      if init:
        self.arith_ev.Eval(init)

      self.loop_level += 1
      try:
        while True:
          if cond:
            b = self.arith_ev.Eval(cond)
            if not b:
              break

          try:
            status = self._Execute(body)
          except _ControlFlow as e:
            if e.IsBreak():
              status = 0
              break
            elif e.IsContinue():
              status = 0
            else:  # return needs to pop up more
              raise

          if update:
            self.arith_ev.Eval(update)

      finally:
        self.loop_level -= 1

    elif node.tag == command_e.DoGroup:
      status = self._ExecuteList(node.children)
      check_errexit = False  # not real statements

    elif node.tag == command_e.FuncDef:
      # NOTE: Would it make sense to evaluate the redirects BEFORE entering?
      # It will save time on function calls.
      self.funcs[node.name] = node
      status = 0

    elif node.tag == command_e.If:
      done = False
      for arm in node.arms:
        self._PushErrExit()
        try:
          status = self._ExecuteList(arm.cond)
        finally:
          self._PopErrExit()

        if status == 0:
          status = self._ExecuteList(arm.action)
          done = True
          break
      # TODO: The compiler should flatten this
      if not done and node.else_action is not None:
        status = self._ExecuteList(node.else_action)

    elif node.tag == command_e.NoOp:
      status = 0  # make it true

    elif node.tag == command_e.Case:
      val = self.word_ev.EvalWordToString(node.to_match)
      to_match = val.s

      status = 0  # If there are no arms, it should be zero?
      done = False

      for arm in node.arms:
        for pat_word in arm.pat_list:
          # NOTE: Is it OK that we're evaluating these as we go?

          # TODO: case "$@") shouldn't succeed?  That's a type error?
          # That requires strict-array?

          pat_val = self.word_ev.EvalWordToString(pat_word, do_fnmatch=True)
          #log('Matching word %r against pattern %r', to_match, pat_val.s)
          if libc.fnmatch(pat_val.s, to_match):
            status = self._ExecuteList(arm.action)
            done = True  # TODO: Parse ;;& and for fallthrough and such?
            break  # Only execute action ONCE
        if done:
          break

    elif node.tag == command_e.TimeBlock:
      # TODO:
      # - When do we need RUSAGE_CHILDREN?
      # - Respect TIMEFORMAT environment variable.
      # "If this variable is not set, Bash acts as if it had the value"
      # $'\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS'
      # "A trailing newline is added when the format string is displayed."

      start_t = time.time()  # calls gettimeofday() under the hood
      start_u = resource.getrusage(resource.RUSAGE_SELF)
      status = self._Execute(node.pipeline)

      end_t = time.time()
      end_u = resource.getrusage(resource.RUSAGE_SELF)

      real = end_t - start_t
      user = end_u.ru_utime - start_u.ru_utime
      sys_ = end_u.ru_stime - start_u.ru_stime
      libc.print_time(real, user, sys_)

    else:
      raise NotImplementedError(node.__class__.__name__)

    return status, check_errexit
Example #30
0
  def DoCommand(self, node, local_symbols, at_top_level=False):
    if node.tag == command_e.CommandList:
      # TODO: How to distinguish between echo hi; echo bye; and on separate
      # lines
      for child in node.children:
        self.DoCommand(child, local_symbols, at_top_level=at_top_level)

    elif node.tag == command_e.SimpleCommand:
      # How to preserve spaces between words?  Do you want to do it?
      # Well you need to test this:
      #
      # echo foo \
      #   bar

      # TODO: Need to print until the left most part of the phrase?  the phrase
      # is a word, binding, redirect.
      #self.cursor.PrintUntil()

      if node.more_env:
        (left_spid,) = node.more_env[0].spids
        self.cursor.PrintUntil(left_spid)
        self.f.write('env ')

        # We only need to transform the right side, not left side.
        for pair in node.more_env:
          self.DoWordInCommand(pair.val, local_symbols)

      # More translations:
      # - . to source
      # - eval to sh-eval

      if node.words:
        first_word = node.words[0]
        ok, val, quoted = word.StaticEval(first_word)
        word0_spid = word.LeftMostSpanForWord(first_word)
        if ok and not quoted:
          if val == '[':
            last_word = node.words[-1]
            # Check if last word is ]
            ok, val, quoted = word.StaticEval(last_word)
            if ok and not quoted and val == ']':
              # Replace [ with 'test'
              self.cursor.PrintUntil(word0_spid)
              self.cursor.SkipUntil(word0_spid + 1)
              self.f.write('test')

              for w in node.words[1:-1]:
                self.DoWordInCommand(w, local_symbols)

              # Now omit ]
              last_spid = word.LeftMostSpanForWord(last_word)
              self.cursor.PrintUntil(last_spid - 1)  # Get the space before
              self.cursor.SkipUntil(last_spid + 1)  # ] takes one spid
              return
            else:
              raise RuntimeError('Got [ without ]')

          elif val == '.':
            self.cursor.PrintUntil(word0_spid)
            self.cursor.SkipUntil(word0_spid + 1)
            self.f.write('source')
            return

      for w in node.words:
        self.DoWordInCommand(w, local_symbols)

      # NOTE: This will change to "phrase"?  Word or redirect.
      for r in node.redirects:
        self.DoRedirect(r, local_symbols)

      # TODO: Print the terminator.  Could be \n or ;
      # Need to print env like PYTHONPATH = 'foo' && ls
      # Need to print redirects:
      # < > are the same.  << is here string, and >> is assignment.
      # append is >+

      # TODO: static_eval of simple command
      # - [ -> "test".  Eliminate trailing ].
      # - . -> source, etc.

    elif node.tag == command_e.Assignment:
      self.DoAssignment(node, at_top_level, local_symbols)

    elif node.tag == command_e.Pipeline:
      # Obscure: |& turns into |- or |+ for stderr.
      # TODO:
      # if ! true; then -> if not true {

      # if ! echo | grep; then -> if not { echo | grep } {
      # }
      # not is like do {}, but it negates the return value I guess.

      for child in node.children:
        self.DoCommand(child, local_symbols)

    elif node.tag == command_e.AndOr:
      for child in node.children:
        self.DoCommand(child, local_symbols)

    elif node.tag == command_e.Sentence:
      # 'ls &' to 'fork ls'
      # Keep ; the same.
      self.DoCommand(node.child, local_symbols)

    # This has to be different in the function case.
    elif node.tag == command_e.BraceGroup:
      # { echo hi; } -> do { echo hi }
      # For now it might be OK to keep 'do { echo hi; }
      #left_spid, right_spid = node.spids
      (left_spid,) = node.spids

      self.cursor.PrintUntil(left_spid)
      self.cursor.SkipUntil(left_spid + 1)
      self.f.write('do {')

      for child in node.children:
        self.DoCommand(child, local_symbols)

    elif node.tag == command_e.Subshell:
      # (echo hi) -> shell echo hi
      # (echo hi; echo bye) -> shell {echo hi; echo bye}

      (left_spid, right_spid) = node.spids

      self.cursor.PrintUntil(left_spid)
      self.cursor.SkipUntil(left_spid + 1)
      self.f.write('shell {')

      self.DoCommand(node.child, local_symbols)

      #self._DebugSpid(right_spid)
      #self._DebugSpid(right_spid + 1)

      #print('RIGHT SPID', right_spid)
      self.cursor.PrintUntil(right_spid)
      self.cursor.SkipUntil(right_spid + 1)
      self.f.write('}')

    elif node.tag == command_e.DParen:
      # (( a == 0 )) is sh-expr ' a == 0 '
      #
      # NOTE: (( n++ )) is auto-translated to sh-expr 'n++', but could be set
      # n++.
      left_spid, right_spid = node.spids
      self.cursor.PrintUntil(left_spid)
      self.cursor.SkipUntil(left_spid + 1)
      self.f.write("sh-expr '")
      self.cursor.PrintUntil(right_spid - 1)  # before ))
      self.cursor.SkipUntil(right_spid + 1)  # after )) -- each one is a token
      self.f.write("'")

    elif node.tag == command_e.DBracket:
      # [[ 1 -eq 2 ]] to (1 == 2)
      self.DoBoolExpr(node.expr)

    elif node.tag == command_e.FuncDef:
      # TODO: skip name
      #self.f.write('proc %s' % node.name)

      # New symbol table for every function.
      new_local_symbols = {}

      # Should be the left most span, including 'function'
      self.cursor.PrintUntil(node.spids[0])

      self.f.write('proc ')
      self.f.write(node.name)
      self.cursor.SkipUntil(node.spids[1])

      if node.body.tag == command_e.BraceGroup:
        # Don't add "do" like a standalone brace group.  Just use {}.
        for child in node.body.children:
          self.DoCommand(child, new_local_symbols)
      else:
        pass
        # Add {}.
        # proc foo {
        #   shell {echo hi; echo bye}
        # }
        #self.DoCommand(node.body)

    elif node.tag == command_e.BraceGroup:
      for child in node.children:
        self.DoCommand(child, local_symbols)

    elif node.tag == command_e.DoGroup:
      do_spid, done_spid = node.spids
      self.cursor.PrintUntil(do_spid)
      self.cursor.SkipUntil(do_spid + 1)
      self.f.write('{')

      for child in node.children:
        self.DoCommand(child, local_symbols)

      self.cursor.PrintUntil(done_spid)
      self.cursor.SkipUntil(done_spid + 1)
      self.f.write('}')

    elif node.tag == command_e.ForEach:
      # Need to preserve spaces between words, because there can be line
      # wrapping.
      # for x in a b c \
      #    d e f; do

      in_spid, semi_spid = node.spids

      if in_spid == const.NO_INTEGER:
        #self.cursor.PrintUntil()  # 'for x' and then space
        self.f.write('for %s in @Argv ' % node.iter_name)
        self.cursor.SkipUntil(node.body.spids[0])
      else:
        self.cursor.PrintUntil(in_spid + 1)  # 'for x in' and then space
        self.f.write('[')
        for w in node.iter_words:
          self.DoWordInCommand(w, local_symbols)
        self.f.write(']')
        #print("SKIPPING SEMI %d" % semi_spid, file=sys.stderr)

      if semi_spid != const.NO_INTEGER:
        self.cursor.PrintUntil(semi_spid)
        self.cursor.SkipUntil(semi_spid + 1)

      self.DoCommand(node.body, local_symbols)

    elif node.tag == command_e.ForExpr:
      # Change (( )) to ( ), and then _FixDoGroup
      pass

    elif node.tag == command_e.WhileUntil:

      # Skip 'until', and replace it with 'while not'
      if node.keyword.id == Id.KW_Until:
        kw_spid = node.keyword.span_id
        self.cursor.PrintUntil(kw_spid)
        self.f.write('while not')
        self.cursor.SkipUntil(kw_spid + 1)

      cond = node.cond
      # Skip the semi-colon in the condition, which is ususally a Sentence
      if len(cond) == 1 and cond[0].tag == command_e.Sentence:
        self.DoCommand(cond[0].child, local_symbols)
        semi_spid = cond[0].terminator.span_id
        self.cursor.SkipUntil(semi_spid + 1)

      self.DoCommand(node.body, local_symbols)

    elif node.tag == command_e.If:
      else_spid, fi_spid = node.spids

      # if foo; then -> if foo {
      # elif foo; then -> } elif foo {
      for arm in node.arms:
        elif_spid, then_spid = arm.spids
        if elif_spid != const.NO_INTEGER:
          self.cursor.PrintUntil(elif_spid)
          self.f.write('} ')

        cond = arm.cond
        if len(cond) == 1 and cond[0].tag == command_e.Sentence:
          sentence = cond[0]
          self.DoCommand(sentence, local_symbols)

          # Remove semi-colon
          semi_spid = sentence.terminator.span_id
          self.cursor.PrintUntil(semi_spid)
          self.cursor.SkipUntil(semi_spid + 1)
        else:
          for child in arm.cond:
            self.DoCommand(child, local_symbols)

        self.cursor.PrintUntil(then_spid)
        self.cursor.SkipUntil(then_spid + 1)
        self.f.write('{')

        for child in arm.action:
          self.DoCommand(child, local_symbols)

      # else -> } else {
      if node.else_action:
        self.cursor.PrintUntil(else_spid)
        self.f.write('} ')
        self.cursor.PrintUntil(else_spid + 1)
        self.f.write(' {')

        for child in node.else_action:
          self.DoCommand(child, local_symbols)

      # fi -> }
      self.cursor.PrintUntil(fi_spid)
      self.cursor.SkipUntil(fi_spid + 1)
      self.f.write('}')

    elif node.tag == command_e.Case:
      case_spid, in_spid, esac_spid = node.spids
      self.cursor.PrintUntil(case_spid)
      self.cursor.SkipUntil(case_spid + 1)
      self.f.write('match')

      # Reformat "$1" to $1
      self.DoWordInCommand(node.to_match, local_symbols)

      self.cursor.PrintUntil(in_spid)
      self.cursor.SkipUntil(in_spid + 1)
      self.f.write('{')  # matchstr $var {

      # each arm needs the ) and the ;; node to skip over?
      for arm in node.arms:
        left_spid, rparen_spid, dsemi_spid, last_spid = arm.spids
        #print(left_spid, rparen_spid, dsemi_spid)

        self.cursor.PrintUntil(left_spid)
        # Hm maybe keep | because it's semi-deprecated?  You acn use
        # reload|force-relaod {
        # }
        # e/reload|force-reload/ {
        # }
        # / 'reload' or 'force-reload' / {
        # }
        #
        # Yeah it's the more abbreviated syntax.

        # change | to 'or'
        for pat in arm.pat_list:
          pass

        self.f.write('with ')
        # Remove the )
        self.cursor.PrintUntil(rparen_spid)
        self.cursor.SkipUntil(rparen_spid + 1)

        for child in arm.action:
          self.DoCommand(child, local_symbols)

        if dsemi_spid != const.NO_INTEGER:
          # Remove ;;
          self.cursor.PrintUntil(dsemi_spid)
          self.cursor.SkipUntil(dsemi_spid + 1)
        elif last_spid != const.NO_INTEGER:
          self.cursor.PrintUntil(last_spid)
        else:
          raise AssertionError(
              "Expected with dsemi_spid or last_spid in case arm")

      self.cursor.PrintUntil(esac_spid)
      self.cursor.SkipUntil(esac_spid + 1)
      self.f.write('}')  # strmatch $var {

    elif node.tag == command_e.NoOp:
      pass

    elif node.tag == command_e.ControlFlow:
      # No change for break / return / continue
      pass

    elif node.tag == command_e.TimeBlock:
      self.DoCommand(node.pipeline, local_symbols)

    else:
      #log('Command not handled: %s', node)
      raise AssertionError(node.__class__.__name__)