OILS / builtin / bracket_osh.py View on Github | oilshell.org

292 lines, 172 significant
1"""Builtin_bracket.py."""
2from __future__ import print_function
3
4from _devbuild.gen.id_kind_asdl import Id
5from _devbuild.gen.syntax_asdl import loc, word, word_e, word_t, bool_expr
6from _devbuild.gen.types_asdl import lex_mode_e
7from _devbuild.gen.value_asdl import value
8
9from core import error
10from core.error import e_usage, p_die
11from core import vm
12from frontend import match
13from frontend import typed_args
14from mycpp.mylib import log
15from osh import bool_parse
16from osh import sh_expr_eval
17from osh import word_parse
18from osh import word_eval
19
20_ = log
21
22from typing import cast, TYPE_CHECKING
23
24if TYPE_CHECKING:
25 from _devbuild.gen.runtime_asdl import cmd_value
26 from _devbuild.gen.syntax_asdl import bool_expr_t
27 from _devbuild.gen.types_asdl import lex_mode_t
28 from core import optview
29 from core import state
30 from display import ui
31
32
33class _StringWordEmitter(word_parse.WordEmitter):
34 """For test/[, we need a word parser that returns String.
35
36 The BoolParser calls word_.BoolId(w), and deals with Kind.BoolUnary,
37 Kind.BoolBinary, etc. This is instead of Compound/Token (as in the
38 [[ case.
39 """
40
41 def __init__(self, cmd_val):
42 # type: (cmd_value.Argv) -> None
43 self.cmd_val = cmd_val
44 self.i = 0
45 self.n = len(cmd_val.argv)
46
47 def ReadWord(self, unused_lex_mode):
48 # type: (lex_mode_t) -> word.String
49 """Interface for bool_parse.py.
50
51 TODO: This should probably be word_t
52 """
53 if self.i == self.n:
54 # Does it make sense to define Eof_Argv or something?
55 # TODO: Add a way to show this location.
56 w = word.String(Id.Eof_Real, '', None)
57 return w
58
59 #log('ARGV %s i %d', self.argv, self.i)
60 s = self.cmd_val.argv[self.i]
61 arg_loc = self.cmd_val.arg_locs[self.i]
62
63 self.i += 1
64
65 # chained lookup; default is an operand word
66 id_ = match.BracketUnary(s)
67 if id_ == Id.Undefined_Tok:
68 id_ = match.BracketBinary(s)
69 if id_ == Id.Undefined_Tok:
70 id_ = match.BracketOther(s)
71 if id_ == Id.Undefined_Tok:
72 id_ = Id.Word_Compound
73
74 return word.String(id_, s, arg_loc)
75
76 def Read(self):
77 # type: () -> word.String
78 """Interface used for special cases below."""
79 return self.ReadWord(lex_mode_e.ShCommand)
80
81 def Peek(self, offset):
82 # type: (int) -> str
83 """For special cases."""
84 return self.cmd_val.argv[self.i + offset]
85
86 def Rewind(self, offset):
87 # type: (int) -> None
88 """For special cases."""
89 self.i -= offset
90
91
92class _WordEvaluator(word_eval.StringWordEvaluator):
93
94 def __init__(self):
95 # type: () -> None
96 word_eval.StringWordEvaluator.__init__(self)
97
98 def EvalWordToString(self, w, eval_flags=0):
99 # type: (word_t, int) -> value.Str
100 # do_fnmatch: for the [[ == ]] semantics which we don't have!
101 # I think I need another type of node
102 # Maybe it should be BuiltinEqual and BuiltinDEqual? Parse it into a
103 # different tree.
104 assert w.tag() == word_e.String
105 string_word = cast(word.String, w)
106 return value.Str(string_word.s)
107
108
109def _TwoArgs(w_parser):
110 # type: (_StringWordEmitter) -> bool_expr_t
111 """Returns an expression tree to be evaluated."""
112 w0 = w_parser.Read()
113 w1 = w_parser.Read()
114
115 s0 = w0.s
116 if s0 == '!':
117 return bool_expr.LogicalNot(bool_expr.WordTest(w1))
118
119 unary_id = Id.Undefined_Tok
120
121 # YSH prefers long flags
122 if w0.s.startswith('--'):
123 if s0 == '--dir':
124 unary_id = Id.BoolUnary_d
125 elif s0 == '--exists':
126 unary_id = Id.BoolUnary_e
127 elif s0 == '--file':
128 unary_id = Id.BoolUnary_f
129 elif s0 == '--symlink':
130 unary_id = Id.BoolUnary_L
131 elif s0 == '--true':
132 unary_id = Id.BoolUnary_true
133 elif s0 == '--false':
134 unary_id = Id.BoolUnary_false
135
136 if unary_id == Id.Undefined_Tok:
137 unary_id = match.BracketUnary(w0.s)
138
139 if unary_id == Id.Undefined_Tok:
140 p_die('Expected unary operator, got %r (2 args)' % w0.s, loc.Word(w0))
141
142 return bool_expr.Unary(unary_id, w1)
143
144
145def _ThreeArgs(w_parser):
146 # type: (_StringWordEmitter) -> bool_expr_t
147 """Returns an expression tree to be evaluated."""
148 w0 = w_parser.Read()
149 w1 = w_parser.Read()
150 w2 = w_parser.Read()
151
152 # NOTE: Order is important here.
153
154 binary_id = match.BracketBinary(w1.s)
155 if binary_id != Id.Undefined_Tok:
156 return bool_expr.Binary(binary_id, w0, w2)
157
158 if w1.s == '-a':
159 return bool_expr.LogicalAnd(bool_expr.WordTest(w0),
160 bool_expr.WordTest(w2))
161
162 if w1.s == '-o':
163 return bool_expr.LogicalOr(bool_expr.WordTest(w0),
164 bool_expr.WordTest(w2))
165
166 if w0.s == '!':
167 w_parser.Rewind(2)
168 child = _TwoArgs(w_parser)
169 return bool_expr.LogicalNot(child)
170
171 if w0.s == '(' and w2.s == ')':
172 return bool_expr.WordTest(w1)
173
174 p_die('Expected binary operator, got %r (3 args)' % w1.s, loc.Word(w1))
175
176
177class Test(vm._Builtin):
178
179 def __init__(self, need_right_bracket, exec_opts, mem, errfmt):
180 # type: (bool, optview.Exec, state.Mem, ui.ErrorFormatter) -> None
181 self.need_right_bracket = need_right_bracket
182 self.exec_opts = exec_opts
183 self.mem = mem
184 self.errfmt = errfmt
185
186 def Run(self, cmd_val):
187 # type: (cmd_value.Argv) -> int
188 """The test/[ builtin.
189
190 The only difference between test and [ is that [ needs a
191 matching ].
192 """
193 typed_args.DoesNotAccept(cmd_val.proc_args) # Disallow test (42)
194
195 if self.need_right_bracket: # Preprocess right bracket
196 if self.exec_opts.simple_test_builtin():
197 e_usage("should be invoked as 'test' (simple_test_builtin)",
198 loc.Missing)
199
200 strs = cmd_val.argv
201 if len(strs) == 0 or strs[-1] != ']':
202 self.errfmt.Print_('missing closing ]',
203 blame_loc=cmd_val.arg_locs[0])
204 return 2
205 # Remove the right bracket
206 cmd_val.argv.pop()
207 cmd_val.arg_locs.pop()
208
209 w_parser = _StringWordEmitter(cmd_val)
210 w_parser.Read() # dummy: advance past argv[0]
211 b_parser = bool_parse.BoolParser(w_parser)
212
213 # There is a fundamental ambiguity due to poor language design, in cases like:
214 # [ -z ]
215 # [ -z -a ]
216 # [ -z -a ] ]
217 #
218 # See posixtest() in bash's test.c:
219 # "This is an implementation of a Posix.2 proposal by David Korn."
220 # It dispatches on expressions of length 0, 1, 2, 3, 4, and N args. We do
221 # the same here.
222 #
223 # Another ambiguity:
224 # -a is both a unary prefix operator and an infix operator. How to fix this
225 # ambiguity?
226
227 bool_node = None # type: bool_expr_t
228 n = len(cmd_val.argv) - 1
229
230 if self.exec_opts.simple_test_builtin() and n > 3:
231 e_usage(
232 "should only have 3 arguments or fewer (simple_test_builtin)",
233 loc.Missing)
234
235 try:
236 if n == 0:
237 return 1 # [ ] is False
238 elif n == 1:
239 w = w_parser.Read()
240 bool_node = bool_expr.WordTest(w)
241 elif n == 2:
242 bool_node = _TwoArgs(w_parser)
243 elif n == 3:
244 bool_node = _ThreeArgs(w_parser)
245 if n == 4:
246 a0 = w_parser.Peek(0)
247 if a0 == '!':
248 w_parser.Read() # skip !
249 child = _ThreeArgs(w_parser)
250 bool_node = bool_expr.LogicalNot(child)
251 elif a0 == '(' and w_parser.Peek(3) == ')':
252 w_parser.Read() # skip ')'
253 bool_node = _TwoArgs(w_parser)
254 else:
255 pass # fallthrough
256
257 if bool_node is None:
258 bool_node = b_parser.ParseForBuiltin()
259
260 except error.Parse as e:
261 self.errfmt.PrettyPrintError(e, prefix='(test) ')
262 return 2
263
264 word_ev = _WordEvaluator()
265
266 # We technically don't need mem because we don't support BASH_REMATCH here.
267 # We want [ a -eq a ] to always be an error, unlike [[ a -eq a ]]. This is
268 # a weird case of [[ being less strict.
269 bool_ev = sh_expr_eval.BoolEvaluator(self.mem,
270 self.exec_opts,
271 None,
272 None,
273 self.errfmt,
274 bracket=True)
275 bool_ev.word_ev = word_ev
276 bool_ev.CheckCircularDeps()
277 try:
278 b = bool_ev.EvalB(bool_node)
279 except error._ErrorWithLocation as e:
280 # We want to catch e_die() and e_strict(). Those are both FatalRuntime
281 # errors now, but it might not make sense later.
282
283 # NOTE: This doesn't seem to happen. We have location info for all
284 # errors that arise out of [.
285 #if not e.HasLocation():
286 # raise
287
288 self.errfmt.PrettyPrintError(e, prefix='(test) ')
289 return 2 # 1 means 'false', and this usage error is like a parse error.
290
291 status = 0 if b else 1
292 return status