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

185 lines, 92 significant
1#!/usr/bin/env python
2"""
3arith_parse.py - Parse shell arithmetic, which is based on C.
4"""
5
6from core import tdop
7from core import util
8from osh.meta import Id
9from core import word
10from osh.meta import ast
11
12p_die = util.p_die
13
14
15def 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
24def 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
30def 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
36def 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
50def 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
77def 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
86COMMA_PREC = 1
87
88def 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
104def 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
185SPEC = MakeShellSpec()