| 1 | #!/usr/bin/env python
 | 
| 2 | """
 | 
| 3 | arith_parse.py - Parse shell arithmetic, which is based on C.
 | 
| 4 | """
 | 
| 5 | 
 | 
| 6 | from core import tdop
 | 
| 7 | from core import util
 | 
| 8 | from osh.meta import Id
 | 
| 9 | from core import word
 | 
| 10 | from osh.meta import ast
 | 
| 11 | 
 | 
| 12 | p_die = util.p_die
 | 
| 13 | 
 | 
| 14 | 
 | 
| 15 | def NullIncDec(p, w, bp):
 | 
| 16 |   """ ++x or ++x[1] """
 | 
| 17 |   right = p.ParseUntil(bp)
 | 
| 18 |   child = tdop.ToLValue(right)
 | 
| 19 |   if child is None:
 | 
| 20 |     p_die("This value can't be assigned to", word=w)
 | 
| 21 |   return ast.UnaryAssign(word.ArithId(w), child)
 | 
| 22 | 
 | 
| 23 | 
 | 
| 24 | def NullUnaryPlus(p, t, bp):
 | 
| 25 |   """ +x, to distinguish from binary operator. """
 | 
| 26 |   right = p.ParseUntil(bp)
 | 
| 27 |   return ast.ArithUnary(Id.Node_UnaryPlus, right)
 | 
| 28 | 
 | 
| 29 | 
 | 
| 30 | def NullUnaryMinus(p, t, bp):
 | 
| 31 |   """ -1, to distinguish from binary operator. """
 | 
| 32 |   right = p.ParseUntil(bp)
 | 
| 33 |   return ast.ArithUnary(Id.Node_UnaryMinus, right)
 | 
| 34 | 
 | 
| 35 | 
 | 
| 36 | def LeftIncDec(p, w, left, rbp):
 | 
| 37 |   """ For i++ and i--
 | 
| 38 |   """
 | 
| 39 |   if word.ArithId(w) == Id.Arith_DPlus:
 | 
| 40 |     op_id = Id.Node_PostDPlus
 | 
| 41 |   elif word.ArithId(w) == Id.Arith_DMinus:
 | 
| 42 |     op_id = Id.Node_PostDMinus
 | 
| 43 |   else:
 | 
| 44 |     raise AssertionError
 | 
| 45 | 
 | 
| 46 |   child = tdop.ToLValue(left)
 | 
| 47 |   return ast.UnaryAssign(op_id, child)
 | 
| 48 | 
 | 
| 49 | 
 | 
| 50 | def LeftIndex(p, w, left, unused_bp):
 | 
| 51 |   """Array indexing, in both LValue and RValue context.
 | 
| 52 | 
 | 
| 53 |   LValue: f[0] = 1  f[x+1] = 2
 | 
| 54 |   RValue: a = f[0]  b = f[x+1]
 | 
| 55 | 
 | 
| 56 |   On RHS, you can have:
 | 
| 57 |   1. a = f[0]
 | 
| 58 |   2. a = f(x, y)[0]
 | 
| 59 |   3. a = f[0][0]  # in theory, if we want character indexing?
 | 
| 60 |      NOTE: a = f[0].charAt() is probably better
 | 
| 61 | 
 | 
| 62 |   On LHS, you can only have:
 | 
| 63 |   1. a[0] = 1
 | 
| 64 | 
 | 
| 65 |   Nothing else is valid:
 | 
| 66 |   2. function calls return COPIES.  They need a name, at least in osh.
 | 
| 67 |   3. strings don't have mutable characters.
 | 
| 68 |   """
 | 
| 69 |   if not tdop.IsIndexable(left):
 | 
| 70 |     p_die("%s can't be indexed", left, word=w)
 | 
| 71 |   index = p.ParseUntil(0)
 | 
| 72 |   p.Eat(Id.Arith_RBracket)
 | 
| 73 | 
 | 
| 74 |   return ast.ArithBinary(word.ArithId(w), left, index)
 | 
| 75 | 
 | 
| 76 | 
 | 
| 77 | def LeftTernary(p, t, left, bp):
 | 
| 78 |   """ Function call f(a, b). """
 | 
| 79 |   true_expr = p.ParseUntil(bp)
 | 
| 80 |   p.Eat(Id.Arith_Colon)
 | 
| 81 |   false_expr = p.ParseUntil(bp)
 | 
| 82 |   return ast.TernaryOp(left, true_expr, false_expr)
 | 
| 83 | 
 | 
| 84 | 
 | 
| 85 | # For overloading of , inside function calls
 | 
| 86 | COMMA_PREC = 1
 | 
| 87 | 
 | 
| 88 | def LeftFuncCall(p, t, left, unused_bp):
 | 
| 89 |   """ Function call f(a, b). """
 | 
| 90 |   children = []
 | 
| 91 |   # f(x) or f[i](x)
 | 
| 92 |   if not tdop.IsCallable(left):
 | 
| 93 |     raise tdop.ParseError("%s can't be called" % left)
 | 
| 94 |   while not p.AtToken(Id.Arith_RParen):
 | 
| 95 |     # We don't want to grab the comma, e.g. it is NOT a sequence operator.  So
 | 
| 96 |     # set the precedence to 5.
 | 
| 97 |     children.append(p.ParseUntil(COMMA_PREC))
 | 
| 98 |     if p.AtToken(Id.Arith_Comma):
 | 
| 99 |       p.Next()
 | 
| 100 |   p.Eat(Id.Arith_RParen)
 | 
| 101 |   return ast.FuncCall(left, children)
 | 
| 102 | 
 | 
| 103 | 
 | 
| 104 | def MakeShellSpec():
 | 
| 105 |   """
 | 
| 106 |   Following this table:
 | 
| 107 |   http://en.cppreference.com/w/c/language/operator_precedence
 | 
| 108 | 
 | 
| 109 |   Bash has a table in expr.c, but it's not as cmoplete (missing grouping () and
 | 
| 110 |   array[1]).  Although it has the ** exponentiation operator, not in C.
 | 
| 111 | 
 | 
| 112 |   - Extensions:
 | 
| 113 |     - function calls f(a,b)
 | 
| 114 | 
 | 
| 115 |   - Possible extensions (but save it for oil):
 | 
| 116 |     - could allow attribute/object access: obj.member and obj.method(x)
 | 
| 117 |     - could allow extended indexing: t[x,y] -- IN PLACE OF COMMA operator.
 | 
| 118 |       - also obj['member'] because dictionaries are objects
 | 
| 119 |   """
 | 
| 120 |   spec = tdop.ParserSpec()
 | 
| 121 | 
 | 
| 122 |   # -1 precedence -- doesn't matter
 | 
| 123 |   spec.Null(-1, tdop.NullConstant, [
 | 
| 124 |       Id.Word_Compound,
 | 
| 125 |       Id.Arith_Semi,  # for loop
 | 
| 126 |   ])
 | 
| 127 |   spec.Null(-1, tdop.NullError, [
 | 
| 128 |       Id.Arith_RParen, Id.Arith_RBracket, Id.Arith_Colon,
 | 
| 129 |       Id.Eof_Real, Id.Eof_RParen, Id.Eof_Backtick,
 | 
| 130 |       # Not in the arithmetic language, but is a common terminator, e.g.
 | 
| 131 |       # ${foo:1}
 | 
| 132 |       Id.Arith_RBrace,
 | 
| 133 |   ])
 | 
| 134 | 
 | 
| 135 |   # 0 precedence -- doesn't bind until )
 | 
| 136 |   spec.Null(0, tdop.NullParen, [Id.Arith_LParen])  # for grouping
 | 
| 137 | 
 | 
| 138 |   spec.Left(33, LeftIncDec, [Id.Arith_DPlus, Id.Arith_DMinus])
 | 
| 139 |   spec.Left(33, LeftFuncCall, [Id.Arith_LParen])
 | 
| 140 |   spec.Left(33, LeftIndex, [Id.Arith_LBracket])
 | 
| 141 | 
 | 
| 142 |   # 31 -- binds to everything except function call, indexing, postfix ops
 | 
| 143 |   spec.Null(31, NullIncDec, [Id.Arith_DPlus, Id.Arith_DMinus])
 | 
| 144 |   spec.Null(31, NullUnaryPlus, [Id.Arith_Plus])
 | 
| 145 |   spec.Null(31, NullUnaryMinus, [Id.Arith_Minus])
 | 
| 146 |   spec.Null(31, tdop.NullPrefixOp, [Id.Arith_Bang, Id.Arith_Tilde])
 | 
| 147 | 
 | 
| 148 |   # Right associative: 2 ** 3 ** 2 == 2 ** (3 ** 2)
 | 
| 149 |   # NOTE: This isn't in C
 | 
| 150 |   spec.LeftRightAssoc(29, tdop.LeftBinaryOp, [Id.Arith_DStar])
 | 
| 151 | 
 | 
| 152 |   # * / %
 | 
| 153 |   spec.Left(27, tdop.LeftBinaryOp, [
 | 
| 154 |       Id.Arith_Star, Id.Arith_Slash, Id.Arith_Percent])
 | 
| 155 | 
 | 
| 156 |   spec.Left(25, tdop.LeftBinaryOp, [Id.Arith_Plus, Id.Arith_Minus])
 | 
| 157 |   spec.Left(23, tdop.LeftBinaryOp, [Id.Arith_DLess, Id.Arith_DGreat])
 | 
| 158 |   spec.Left(21, tdop.LeftBinaryOp, [
 | 
| 159 |       Id.Arith_Less, Id.Arith_Great, Id.Arith_LessEqual, Id.Arith_GreatEqual])
 | 
| 160 | 
 | 
| 161 |   spec.Left(19, tdop.LeftBinaryOp, [Id.Arith_NEqual, Id.Arith_DEqual])
 | 
| 162 | 
 | 
| 163 |   spec.Left(15, tdop.LeftBinaryOp, [Id.Arith_Amp])
 | 
| 164 |   spec.Left(13, tdop.LeftBinaryOp, [Id.Arith_Caret])
 | 
| 165 |   spec.Left(11, tdop.LeftBinaryOp, [Id.Arith_Pipe])
 | 
| 166 |   spec.Left(9, tdop.LeftBinaryOp, [Id.Arith_DAmp])
 | 
| 167 |   spec.Left(7, tdop.LeftBinaryOp, [Id.Arith_DPipe])
 | 
| 168 | 
 | 
| 169 |   spec.Left(5, LeftTernary, [Id.Arith_QMark])
 | 
| 170 | 
 | 
| 171 |   # Right associative: a = b = 2 is a = (b = 2)
 | 
| 172 |   spec.LeftRightAssoc(3, tdop.LeftAssign, [
 | 
| 173 |       Id.Arith_Equal,
 | 
| 174 |       Id.Arith_PlusEqual, Id.Arith_MinusEqual, Id.Arith_StarEqual,
 | 
| 175 |       Id.Arith_SlashEqual, Id.Arith_PercentEqual, Id.Arith_DLessEqual,
 | 
| 176 |       Id.Arith_DGreatEqual, Id.Arith_AmpEqual, Id.Arith_CaretEqual,
 | 
| 177 |       Id.Arith_PipeEqual
 | 
| 178 |   ])
 | 
| 179 | 
 | 
| 180 |   spec.Left(COMMA_PREC, tdop.LeftBinaryOp, [Id.Arith_Comma])
 | 
| 181 | 
 | 
| 182 |   return spec
 | 
| 183 | 
 | 
| 184 | 
 | 
| 185 | SPEC = MakeShellSpec()
 |