1 #!/usr/bin/env python2
2 """
3 val_ops.py
4 """
5 from __future__ import print_function
6
7 from _devbuild.gen.runtime_asdl import value, value_e, value_t
8 from _devbuild.gen.syntax_asdl import loc, loc_t, command_t
9
10 from core import error
11 from mycpp.mylib import tagswitch
12 from ysh import regex_translate
13
14 from typing import TYPE_CHECKING, cast, Dict, List, Optional
15
16 import libc
17
18 if TYPE_CHECKING:
19 from core import state
20
21
22 def ToInt(val, msg, blame_loc):
23 # type: (value_t, str, loc_t) -> int
24 UP_val = val
25 if val.tag() == value_e.Int:
26 val = cast(value.Int, UP_val)
27 return val.i
28
29 raise error.TypeErr(val, msg, blame_loc)
30
31
32 def ToFloat(val, msg, blame_loc):
33 # type: (value_t, str, loc_t) -> float
34 UP_val = val
35 if val.tag() == value_e.Float:
36 val = cast(value.Float, UP_val)
37 return val.f
38
39 raise error.TypeErr(val, msg, blame_loc)
40
41
42 def ToStr(val, msg, blame_loc):
43 # type: (value_t, str, loc_t) -> str
44 UP_val = val
45 if val.tag() == value_e.Str:
46 val = cast(value.Str, UP_val)
47 return val.s
48
49 raise error.TypeErr(val, msg, blame_loc)
50
51
52 def ToList(val, msg, blame_loc):
53 # type: (value_t, str, loc_t) -> List[value_t]
54 UP_val = val
55 if val.tag() == value_e.List:
56 val = cast(value.List, UP_val)
57 return val.items
58
59 raise error.TypeErr(val, msg, blame_loc)
60
61
62 def ToDict(val, msg, blame_loc):
63 # type: (value_t, str, loc_t) -> Dict[str, value_t]
64 UP_val = val
65 if val.tag() == value_e.Dict:
66 val = cast(value.Dict, UP_val)
67 return val.d
68
69 raise error.TypeErr(val, msg, blame_loc)
70
71
72 def ToCommand(val, msg, blame_loc):
73 # type: (value_t, str, loc_t) -> command_t
74 UP_val = val
75 if val.tag() == value_e.Block:
76 val = cast(value.Block, UP_val)
77 return val.body
78
79 raise error.TypeErr(val, msg, blame_loc)
80
81
82 def Stringify(val, blame_loc, prefix=''):
83 # type: (value_t, loc_t, str) -> str
84 """
85 Used by
86
87 $[x] stringify operator
88 @[x] expression splice - each element is stringified
89 @x splice value
90 """
91 if blame_loc is None:
92 blame_loc = loc.Missing
93
94 UP_val = val
95 with tagswitch(val) as case:
96 if case(value_e.Str): # trivial case
97 val = cast(value.Str, UP_val)
98 return val.s
99
100 elif case(value_e.Null):
101 s = 'null' # JSON spelling
102
103 elif case(value_e.Bool):
104 val = cast(value.Bool, UP_val)
105 s = 'true' if val.b else 'false' # JSON spelling
106
107 elif case(value_e.Int):
108 val = cast(value.Int, UP_val)
109 s = str(val.i) # Decimal '42', the only sensible representation
110
111 elif case(value_e.Float):
112 val = cast(value.Float, UP_val)
113 # TODO: what precision does this have?
114 # The default could be like awk or Python, and then we also allow
115 # ${myfloat %.3f} and more.
116 # Python 3 seems to give a few more digits than Python 2 for str(1.0/3)
117 s = str(val.f)
118
119 elif case(value_e.Eggex):
120 val = cast(value.Eggex, UP_val)
121 s = regex_translate.AsPosixEre(val) # lazily converts to ERE
122
123 else:
124 raise error.TypeErr(
125 val, "%sexpected Null, Bool, Int, Float, Eggex" % prefix,
126 blame_loc)
127
128 return s
129
130
131 def ToShellArray(val, blame_loc, prefix=''):
132 # type: (value_t, loc_t, str) -> List[str]
133 """
134 Used by
135
136 @[x] expression splice
137 @x splice value
138
139 Dicts do NOT get spliced, but they iterate over their keys
140 So this function NOT use Iterator.
141 """
142 UP_val = val
143 with tagswitch(val) as case2:
144 if case2(value_e.List):
145 val = cast(value.List, UP_val)
146 strs = [] # type: List[str]
147 # Note: it would be nice to add the index to the error message
148 # prefix, WITHOUT allocating a string for every item
149 for item in val.items:
150 strs.append(Stringify(item, blame_loc, prefix=prefix))
151
152 # I thought about getting rid of this to keep OSH and YSH separate,
153 # but:
154 # - readarray/mapfile returns bash array (ysh-user-feedback depends on it)
155 # - ysh-options tests parse_at too
156 elif case2(value_e.BashArray):
157 val = cast(value.BashArray, UP_val)
158 strs = val.strs
159
160 else:
161 raise error.TypeErr(val, "%sexpected List" % prefix, blame_loc)
162
163 return strs
164
165
166 class _ContainerIter(object):
167 """Interface for various types of for loop."""
168
169 def __init__(self):
170 # type: () -> None
171 self.i = 0
172
173 def Index(self):
174 # type: () -> int
175 return self.i
176
177 def Next(self):
178 # type: () -> None
179 self.i += 1
180
181 def Done(self):
182 # type: () -> int
183 raise NotImplementedError()
184
185 def FirstValue(self):
186 # type: () -> value_t
187 """Return Dict key or List value"""
188 raise NotImplementedError()
189
190 def SecondValue(self):
191 # type: () -> value_t
192 """Return Dict value or FAIL"""
193 raise AssertionError("Shouldn't have called this")
194
195
196 class ArrayIter(_ContainerIter):
197 """ for x in 1 2 3 { """
198
199 def __init__(self, strs):
200 # type: (List[str]) -> None
201 _ContainerIter.__init__(self)
202 self.strs = strs
203 self.n = len(strs)
204
205 def Done(self):
206 # type: () -> int
207 return self.i == self.n
208
209 def FirstValue(self):
210 # type: () -> value_t
211 return value.Str(self.strs[self.i])
212
213
214 class RangeIterator(_ContainerIter):
215 """ for x in (m:n) { """
216
217 def __init__(self, val):
218 # type: (value.Range) -> None
219 _ContainerIter.__init__(self)
220 self.val = val
221
222 def Done(self):
223 # type: () -> int
224 return self.val.lower + self.i >= self.val.upper
225
226 def FirstValue(self):
227 # type: () -> value_t
228 return value.Int(self.val.lower + self.i)
229
230
231 class ListIterator(_ContainerIter):
232 """ for x in (mylist) { """
233
234 def __init__(self, val):
235 # type: (value.List) -> None
236 _ContainerIter.__init__(self)
237 self.val = val
238 self.n = len(val.items)
239
240 def Done(self):
241 # type: () -> int
242 return self.i == self.n
243
244 def FirstValue(self):
245 # type: () -> value_t
246 return self.val.items[self.i]
247
248
249 class DictIterator(_ContainerIter):
250 """ for x in (mydict) { """
251
252 def __init__(self, val):
253 # type: (value.Dict) -> None
254 _ContainerIter.__init__(self)
255
256 # TODO: Don't materialize these Lists
257 self.keys = val.d.keys() # type: List[str]
258 self.values = val.d.values() # type: List[value_t]
259
260 self.n = len(val.d)
261 assert self.n == len(self.keys)
262
263 def Done(self):
264 # type: () -> int
265 return self.i == self.n
266
267 def FirstValue(self):
268 # type: () -> value_t
269 return value.Str(self.keys[self.i])
270
271 def SecondValue(self):
272 # type: () -> value_t
273 return self.values[self.i]
274
275
276 def ToBool(val):
277 # type: (value_t) -> bool
278 """Convert any value to a boolean.
279
280 TODO: expose this as Bool(x), like Python's bool(x).
281 """
282 UP_val = val
283 with tagswitch(val) as case:
284 if case(value_e.Undef):
285 return False
286
287 elif case(value_e.Null):
288 return False
289
290 elif case(value_e.Str):
291 val = cast(value.Str, UP_val)
292 return len(val.s) != 0
293
294 # OLD TYPES
295 elif case(value_e.BashArray):
296 val = cast(value.BashArray, UP_val)
297 return len(val.strs) != 0
298
299 elif case(value_e.BashAssoc):
300 val = cast(value.BashAssoc, UP_val)
301 return len(val.d) != 0
302
303 elif case(value_e.Bool):
304 val = cast(value.Bool, UP_val)
305 return val.b
306
307 elif case(value_e.Int):
308 val = cast(value.Int, UP_val)
309 return val.i != 0
310
311 elif case(value_e.Float):
312 val = cast(value.Float, UP_val)
313 return val.f != 0.0
314
315 elif case(value_e.List):
316 val = cast(value.List, UP_val)
317 return len(val.items) > 0
318
319 elif case(value_e.Dict):
320 val = cast(value.Dict, UP_val)
321 return len(val.d) > 0
322
323 else:
324 return True # all other types are Truthy
325
326
327 def ExactlyEqual(left, right):
328 # type: (value_t, value_t) -> bool
329 if left.tag() != right.tag():
330 return False
331
332 UP_left = left
333 UP_right = right
334 with tagswitch(left) as case:
335 if case(value_e.Undef):
336 return True # there's only one Undef
337
338 elif case(value_e.Null):
339 return True # there's only one Null
340
341 elif case(value_e.Bool):
342 left = cast(value.Bool, UP_left)
343 right = cast(value.Bool, UP_right)
344 return left.b == right.b
345
346 elif case(value_e.Int):
347 left = cast(value.Int, UP_left)
348 right = cast(value.Int, UP_right)
349 return left.i == right.i
350
351 elif case(value_e.Float):
352 # Note: could provide floatEquals(), and suggest it
353 # Suggested idiom is abs(f1 - f2) < 0.1
354 raise error.TypeErrVerbose("Equality isn't defined on Float",
355 loc.Missing)
356
357 elif case(value_e.Str):
358 left = cast(value.Str, UP_left)
359 right = cast(value.Str, UP_right)
360 return left.s == right.s
361
362 elif case(value_e.BashArray):
363 left = cast(value.BashArray, UP_left)
364 right = cast(value.BashArray, UP_right)
365 if len(left.strs) != len(right.strs):
366 return False
367
368 for i in xrange(0, len(left.strs)):
369 if left.strs[i] != right.strs[i]:
370 return False
371
372 return True
373
374 elif case(value_e.List):
375 left = cast(value.List, UP_left)
376 right = cast(value.List, UP_right)
377 if len(left.items) != len(right.items):
378 return False
379
380 for i in xrange(0, len(left.items)):
381 if not ExactlyEqual(left.items[i], right.items[i]):
382 return False
383
384 return True
385
386 elif case(value_e.BashAssoc):
387 left = cast(value.Dict, UP_left)
388 right = cast(value.Dict, UP_right)
389 if len(left.d) != len(right.d):
390 return False
391
392 for k in left.d.keys():
393 if k not in right.d or right.d[k] != left.d[k]:
394 return False
395
396 return True
397
398 elif case(value_e.Dict):
399 left = cast(value.Dict, UP_left)
400 right = cast(value.Dict, UP_right)
401 if len(left.d) != len(right.d):
402 return False
403
404 for k in left.d.keys():
405 if k not in right.d or not ExactlyEqual(right.d[k], left.d[k]):
406 return False
407
408 return True
409
410 raise NotImplementedError(left)
411
412
413 def Contains(needle, haystack):
414 # type: (value_t, value_t) -> bool
415 """Haystack must be a Dict.
416
417 We should have mylist->find(x) !== -1 for searching through a List.
418 Things with different perf characteristics should look different.
419 """
420 UP_haystack = haystack
421 with tagswitch(haystack) as case:
422 if case(value_e.Dict):
423 haystack = cast(value.Dict, UP_haystack)
424 s = ToStr(needle, "LHS of 'in' should be Str", loc.Missing)
425 return s in haystack.d
426
427 else:
428 raise error.TypeErr(haystack, "RHS of 'in' should be Dict",
429 loc.Missing)
430
431 return False
432
433
434 def RegexMatch(left, right, mem):
435 # type: (value_t, value_t, Optional[state.Mem]) -> bool
436 """
437 Args:
438 mem: Whether to set or clear matches
439 """
440 UP_right = right
441 right_s = None # type: str
442 with tagswitch(right) as case:
443 if case(value_e.Str):
444 right = cast(value.Str, UP_right)
445 right_s = right.s
446 elif case(value_e.Eggex):
447 right = cast(value.Eggex, UP_right)
448 right_s = regex_translate.AsPosixEre(right)
449 else:
450 raise error.TypeErr(right, 'Expected Str or Regex for RHS of ~',
451 loc.Missing)
452
453 UP_left = left
454 left_s = None # type: str
455 with tagswitch(left) as case:
456 if case(value_e.Str):
457 left = cast(value.Str, UP_left)
458 left_s = left.s
459 else:
460 raise error.TypeErrVerbose('LHS must be a string', loc.Missing)
461
462 # TODO:
463 # - libc_regex_match should populate _start() and _end() too (out params?)
464 # - What is the ordering for named captures? See demo/ere*.sh
465
466 matches = libc.regex_match(right_s, left_s)
467 if matches is not None:
468 if mem:
469 mem.SetMatches(matches)
470 return True
471 else:
472 if mem:
473 mem.ClearMatches()
474 return False
475
476
477 # vim: sw=4