| 1 | #!/usr/bin/env python2
 | 
| 2 | """
 | 
| 3 | arith_parse.py - Parse shell arithmetic, which is based on C.
 | 
| 4 | """
 | 
| 5 | 
 | 
| 6 | from _devbuild.gen.id_kind_asdl import Id
 | 
| 7 | from _devbuild.gen.syntax_asdl import (loc, arith_expr, arith_expr_t, word_e,
 | 
| 8 |                                        word_t, Token)
 | 
| 9 | from core.error import p_die
 | 
| 10 | from osh import tdop
 | 
| 11 | from osh import word_
 | 
| 12 | from mycpp import mylib
 | 
| 13 | 
 | 
| 14 | from typing import TYPE_CHECKING, cast
 | 
| 15 | if TYPE_CHECKING:
 | 
| 16 |     from osh.tdop import TdopParser, ParserSpec
 | 
| 17 | 
 | 
| 18 | 
 | 
| 19 | def NullIncDec(p, w, bp):
 | 
| 20 |     # type: (TdopParser, word_t, int) -> arith_expr_t
 | 
| 21 |     """++x or ++x[1]"""
 | 
| 22 |     right = p.ParseUntil(bp)
 | 
| 23 |     tdop.CheckLhsExpr(right, w)
 | 
| 24 |     return arith_expr.UnaryAssign(word_.ArithId(w), right)
 | 
| 25 | 
 | 
| 26 | 
 | 
| 27 | def NullUnaryPlus(p, t, bp):
 | 
| 28 |     # type: (TdopParser, word_t, int) -> arith_expr_t
 | 
| 29 |     """+x, to distinguish from binary operator."""
 | 
| 30 |     right = p.ParseUntil(bp)
 | 
| 31 |     return arith_expr.Unary(Id.Node_UnaryPlus, right)
 | 
| 32 | 
 | 
| 33 | 
 | 
| 34 | def NullUnaryMinus(p, t, bp):
 | 
| 35 |     # type: (TdopParser, word_t, int) -> arith_expr_t
 | 
| 36 |     """ -1, to distinguish from binary operator. """
 | 
| 37 |     right = p.ParseUntil(bp)
 | 
| 38 |     return arith_expr.Unary(Id.Node_UnaryMinus, right)
 | 
| 39 | 
 | 
| 40 | 
 | 
| 41 | def LeftIncDec(p, w, left, rbp):
 | 
| 42 |     # type: (TdopParser, word_t, arith_expr_t, int) -> arith_expr_t
 | 
| 43 |     """For i++ and i--"""
 | 
| 44 |     arith_id = word_.ArithId(w)
 | 
| 45 |     if arith_id == Id.Arith_DPlus:
 | 
| 46 |         op_id = Id.Node_PostDPlus
 | 
| 47 |     elif arith_id == Id.Arith_DMinus:
 | 
| 48 |         op_id = Id.Node_PostDMinus
 | 
| 49 |     else:
 | 
| 50 |         raise AssertionError()
 | 
| 51 | 
 | 
| 52 |     tdop.CheckLhsExpr(left, w)
 | 
| 53 |     return arith_expr.UnaryAssign(op_id, left)
 | 
| 54 | 
 | 
| 55 | 
 | 
| 56 | def LeftIndex(p, w, left, unused_bp):
 | 
| 57 |     # type: (TdopParser, word_t, arith_expr_t, int) -> arith_expr_t
 | 
| 58 |     """Array indexing, in both LValue and RValue context.
 | 
| 59 | 
 | 
| 60 |     LValue: f[0] = 1  f[x+1] = 2
 | 
| 61 |     RValue: a = f[0]  b = f[x+1]
 | 
| 62 | 
 | 
| 63 |     On RHS, you can have:
 | 
| 64 |     1. a = f[0]
 | 
| 65 |     2. a = f(x, y)[0]
 | 
| 66 |     3. a = f[0][0]  # in theory, if we want character indexing?
 | 
| 67 |        NOTE: a = f[0].charAt() is probably better
 | 
| 68 | 
 | 
| 69 |     On LHS, you can only have:
 | 
| 70 |     1. a[0] = 1
 | 
| 71 | 
 | 
| 72 |     Nothing else is valid:
 | 
| 73 |     2. function calls return COPIES.  They need a name, at least in osh.
 | 
| 74 |     3. strings don't have mutable characters.
 | 
| 75 |     """
 | 
| 76 |     if not tdop.IsIndexable(left):
 | 
| 77 |         p_die("The [ operator doesn't apply to this expression", loc.Word(w))
 | 
| 78 |     index = p.ParseUntil(0)  # ] has bp = -1
 | 
| 79 |     p.Eat(Id.Arith_RBracket)
 | 
| 80 | 
 | 
| 81 |     assert w.tag() == word_e.Operator, w
 | 
| 82 |     tok = cast(Token, w)
 | 
| 83 |     return arith_expr.Binary(tok, left, index)
 | 
| 84 | 
 | 
| 85 | 
 | 
| 86 | def LeftTernary(p, t, left, bp):
 | 
| 87 |     # type: (TdopParser, word_t, arith_expr_t, int) -> arith_expr_t
 | 
| 88 |     """cond ?
 | 
| 89 | 
 | 
| 90 |     true_expr : false_expr
 | 
| 91 |     """
 | 
| 92 |     true_expr = p.ParseUntil(0)  # : has bp = -1
 | 
| 93 |     p.Eat(Id.Arith_Colon)
 | 
| 94 |     false_expr = p.ParseUntil(bp)
 | 
| 95 |     return arith_expr.TernaryOp(left, true_expr, false_expr)
 | 
| 96 | 
 | 
| 97 | 
 | 
| 98 | if mylib.PYTHON:
 | 
| 99 | 
 | 
| 100 |     def MakeShellSpec():
 | 
| 101 |         # type: () -> tdop.ParserSpec
 | 
| 102 |         """Following this table:
 | 
| 103 |         http://en.cppreference.com/w/c/language/operator_precedence.
 | 
| 104 | 
 | 
| 105 |         Bash has a table in expr.c, but it's not as cmoplete (missing grouping () and
 | 
| 106 |         array[1]).  Although it has the ** exponentiation operator, not in C.
 | 
| 107 | 
 | 
| 108 |         - Extensions:
 | 
| 109 |           - function calls f(a,b)
 | 
| 110 | 
 | 
| 111 |         - Possible extensions (but save it for oil):
 | 
| 112 |           - could allow attribute/object access: obj.member and obj.method(x)
 | 
| 113 |           - could allow extended indexing: t[x,y] -- IN PLACE OF COMMA operator.
 | 
| 114 |             - also obj['member'] because dictionaries are objects
 | 
| 115 |         """
 | 
| 116 |         spec = tdop.ParserSpec()
 | 
| 117 | 
 | 
| 118 |         # -1 precedence -- doesn't matter
 | 
| 119 |         spec.Null(-1, tdop.NullConstant, [
 | 
| 120 |             Id.Word_Compound,
 | 
| 121 |         ])
 | 
| 122 |         spec.Null(
 | 
| 123 |             -1,
 | 
| 124 |             tdop.NullError,
 | 
| 125 |             [
 | 
| 126 |                 Id.Arith_RParen,
 | 
| 127 |                 Id.Arith_RBracket,
 | 
| 128 |                 Id.Arith_Colon,
 | 
| 129 |                 Id.Eof_Real,
 | 
| 130 |                 Id.Eof_RParen,
 | 
| 131 |                 Id.Eof_Backtick,
 | 
| 132 | 
 | 
| 133 |                 # Not in the arithmetic language, but useful to define here.
 | 
| 134 |                 Id.Arith_Semi,  # terminates loops like for (( i = 0 ; ... ))
 | 
| 135 |                 Id.Arith_RBrace,  # terminates slices like ${foo:1}
 | 
| 136 |             ])
 | 
| 137 | 
 | 
| 138 |         # 0 precedence -- doesn't bind until )
 | 
| 139 |         spec.Null(0, tdop.NullParen, [Id.Arith_LParen])  # for grouping
 | 
| 140 | 
 | 
| 141 |         spec.Left(33, LeftIncDec, [Id.Arith_DPlus, Id.Arith_DMinus])
 | 
| 142 |         spec.Left(33, LeftIndex, [Id.Arith_LBracket])
 | 
| 143 | 
 | 
| 144 |         # 31 -- binds to everything except function call, indexing, postfix ops
 | 
| 145 |         spec.Null(31, NullIncDec, [Id.Arith_DPlus, Id.Arith_DMinus])
 | 
| 146 |         spec.Null(31, NullUnaryPlus, [Id.Arith_Plus])
 | 
| 147 |         spec.Null(31, NullUnaryMinus, [Id.Arith_Minus])
 | 
| 148 |         spec.Null(31, tdop.NullPrefixOp, [Id.Arith_Bang, Id.Arith_Tilde])
 | 
| 149 | 
 | 
| 150 |         # Right associative: 2 ** 3 ** 2 == 2 ** (3 ** 2)
 | 
| 151 |         # NOTE: This isn't in C
 | 
| 152 |         spec.LeftRightAssoc(29, tdop.LeftBinaryOp, [Id.Arith_DStar])
 | 
| 153 | 
 | 
| 154 |         # * / %
 | 
| 155 |         spec.Left(27, tdop.LeftBinaryOp,
 | 
| 156 |                   [Id.Arith_Star, Id.Arith_Slash, Id.Arith_Percent])
 | 
| 157 | 
 | 
| 158 |         spec.Left(25, tdop.LeftBinaryOp, [Id.Arith_Plus, Id.Arith_Minus])
 | 
| 159 |         spec.Left(23, tdop.LeftBinaryOp, [Id.Arith_DLess, Id.Arith_DGreat])
 | 
| 160 |         spec.Left(21, tdop.LeftBinaryOp, [
 | 
| 161 |             Id.Arith_Less, Id.Arith_Great, Id.Arith_LessEqual,
 | 
| 162 |             Id.Arith_GreatEqual
 | 
| 163 |         ])
 | 
| 164 | 
 | 
| 165 |         spec.Left(19, tdop.LeftBinaryOp, [Id.Arith_NEqual, Id.Arith_DEqual])
 | 
| 166 | 
 | 
| 167 |         # NOTE: Bitwise & | ^ have lower precedence than comparisons!
 | 
| 168 |         # Python and Rust correct this:
 | 
| 169 |         # https://graydon2.dreamwidth.org/218040.html
 | 
| 170 |         spec.Left(15, tdop.LeftBinaryOp, [Id.Arith_Amp])
 | 
| 171 |         spec.Left(13, tdop.LeftBinaryOp, [Id.Arith_Caret])
 | 
| 172 |         spec.Left(11, tdop.LeftBinaryOp, [Id.Arith_Pipe])
 | 
| 173 | 
 | 
| 174 |         spec.Left(9, tdop.LeftBinaryOp, [Id.Arith_DAmp])
 | 
| 175 |         spec.Left(7, tdop.LeftBinaryOp, [Id.Arith_DPipe])
 | 
| 176 | 
 | 
| 177 |         spec.LeftRightAssoc(5, LeftTernary, [Id.Arith_QMark])
 | 
| 178 | 
 | 
| 179 |         # Right associative: a = b = 2 is a = (b = 2)
 | 
| 180 |         spec.LeftRightAssoc(3, tdop.LeftAssign, [
 | 
| 181 |             Id.Arith_Equal, Id.Arith_PlusEqual, Id.Arith_MinusEqual,
 | 
| 182 |             Id.Arith_StarEqual, Id.Arith_SlashEqual, Id.Arith_PercentEqual,
 | 
| 183 |             Id.Arith_DGreatEqual, Id.Arith_DLessEqual, Id.Arith_AmpEqual,
 | 
| 184 |             Id.Arith_CaretEqual, Id.Arith_PipeEqual
 | 
| 185 |         ])
 | 
| 186 | 
 | 
| 187 |         spec.Left(1, tdop.LeftBinaryOp, [Id.Arith_Comma])
 | 
| 188 | 
 | 
| 189 |         return spec
 | 
| 190 | 
 | 
| 191 | 
 | 
| 192 | if mylib.PYTHON:
 | 
| 193 |     _SPEC = MakeShellSpec()
 | 
| 194 | 
 | 
| 195 |     def Spec():
 | 
| 196 |         # type: () -> ParserSpec
 | 
| 197 |         return _SPEC
 |