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

197 lines, 97 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_e,
8 word_t, Token)
9from core.error import p_die
10from osh import tdop
11from osh import word_
12from mycpp import mylib
13
14from typing import TYPE_CHECKING, cast
15if TYPE_CHECKING:
16 from osh.tdop import TdopParser, ParserSpec
17
18
19def 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
27def 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
34def 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
41def 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
56def 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
86def 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
98if 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
192if mylib.PYTHON:
193 _SPEC = MakeShellSpec()
194
195 def Spec():
196 # type: () -> ParserSpec
197 return _SPEC