OILS / osh / arith_parse.py View on Github | oilshell.org

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