OILS / core / bash_impl.py View on Github | oils.pub

631 lines, 355 significant
1"""bash_impl.py - implements operations on Bash data structures"""
2
3from _devbuild.gen.runtime_asdl import error_code_e, error_code_t
4from _devbuild.gen.value_asdl import value
5from _devbuild.gen.syntax_asdl import loc_t
6
7from core import optview
8from core.error import e_die
9from data_lang import j8_lite
10from mycpp import mops
11from mycpp import mylib
12
13from typing import Dict, List, Optional, Tuple
14
15
16def BigInt_Greater(a, b):
17 # type: (mops.BigInt, mops.BigInt) -> bool
18 return mops.Greater(a, b)
19
20
21def BigInt_Less(a, b):
22 # type: (mops.BigInt, mops.BigInt) -> bool
23 return mops.Greater(b, a)
24
25
26def BigInt_GreaterEq(a, b):
27 # type: (mops.BigInt, mops.BigInt) -> bool
28 return not mops.Greater(b, a)
29
30
31def BigInt_LessEq(a, b):
32 # type: (mops.BigInt, mops.BigInt) -> bool
33 return not mops.Greater(a, b)
34
35
36class ArrayIndexEvaluator(object):
37 """Interface class for implementing the evaluation of array indices in
38 initializer-list items of the form [i]=1."""
39
40 def __init__(self):
41 # type: () -> None
42 """Empty constructor for mycpp."""
43 pass
44
45 def StringToBigInt(self, s, blame_loc):
46 # type: (str, loc_t) -> mops.BigInt
47 """Returns an array index obtained by evaluating the specified string."""
48 raise NotImplementedError()
49
50
51#------------------------------------------------------------------------------
52# All InternalStringArray operations depending on the internal representation
53# of InternalStringArray come here.
54
55
56def InternalStringArray_IsEmpty(array_val):
57 # type: (value.InternalStringArray) -> bool
58 return len(array_val.strs) == 0
59
60
61def InternalStringArray_Count(array_val):
62 # type: (value.InternalStringArray) -> int
63
64 # There can be empty placeholder values in the array.
65 length = 0
66 for s in array_val.strs:
67 if s is not None:
68 length += 1
69 return length
70
71
72def InternalStringArray_Length(array_val):
73 # type: (value.InternalStringArray) -> int
74 return len(array_val.strs)
75
76
77def InternalStringArray_GetKeys(array_val):
78 # type: (value.InternalStringArray) -> List[int]
79 indices = [] # type: List[int]
80 for i, s in enumerate(array_val.strs):
81 if s is not None:
82 indices.append(i)
83
84 return indices
85
86
87def InternalStringArray_GetValues(array_val):
88 # type: (value.InternalStringArray) -> List[str]
89 return array_val.strs
90
91
92def InternalStringArray_AppendValues(array_val, strs):
93 # type: (value.InternalStringArray, List[str]) -> None
94 array_val.strs.extend(strs)
95
96
97def _InternalStringArray_CanonicalizeIndex(array_val, index):
98 # type: (value.InternalStringArray, int) -> Tuple[int, int, error_code_t]
99 """This function returns (-1, n, error_code_e.IndexOutOfRange)
100 when the specified index is out of range. For example, it
101 includes the case where the index is negative and its absolute
102 value is larger than max_index + 1.
103
104 """
105
106 n = len(array_val.strs)
107 if index < 0:
108 index += n
109 if index < 0:
110 return -1, n, error_code_e.IndexOutOfRange
111 return index, n, error_code_e.OK
112
113
114def InternalStringArray_HasElement(array_val, index):
115 # type: (value.InternalStringArray, int) -> Tuple[bool, error_code_t]
116 index, n, error_code = _InternalStringArray_CanonicalizeIndex(
117 array_val, index)
118 if error_code != error_code_e.OK:
119 return False, error_code
120
121 if index < n:
122 return array_val.strs[index] is not None, error_code_e.OK
123
124 # out of range
125 return False, error_code_e.OK
126
127
128def InternalStringArray_GetElement(array_val, index):
129 # type: (value.InternalStringArray, int) -> Tuple[Optional[str], error_code_t]
130 """This function returns a tuple of a string value and an
131 error_code. If the element is found, the value is returned as the
132 first element of the tuple. Otherwise, the first element of the
133 tuple is None.
134
135 """
136
137 index, n, error_code = _InternalStringArray_CanonicalizeIndex(
138 array_val, index)
139 if error_code != error_code_e.OK:
140 return None, error_code
141
142 if index < n:
143 # TODO: strs->index() has a redundant check for (i < 0)
144 s = array_val.strs[index] # type: Optional[str]
145 # note: s could be None because representation is sparse
146 else:
147 s = None
148 return s, error_code_e.OK
149
150
151def InternalStringArray_SetElement(array_val, index, s):
152 # type: (value.InternalStringArray, int, str) -> error_code_t
153 strs = array_val.strs
154
155 # a[-1]++ computes this twice; could we avoid it?
156 index, n, error_code = _InternalStringArray_CanonicalizeIndex(
157 array_val, index)
158 if error_code != error_code_e.OK:
159 return error_code
160
161 if index < n:
162 array_val.strs[index] = s
163 else:
164 # Fill it in with None. It could look like this:
165 # ['1', 2, 3, None, None, '4', None]
166 # Then ${#a[@]} counts the entries that are not None.
167 for i in xrange(index - n + 1):
168 array_val.strs.append(None)
169 array_val.strs[index] = s
170
171 return error_code_e.OK
172
173
174def InternalStringArray_UnsetElement(array_val, index):
175 # type: (value.InternalStringArray, int) -> error_code_t
176 strs = array_val.strs
177
178 n = len(strs)
179 last_index = n - 1
180 if index < 0:
181 index += n
182 if index < 0:
183 return error_code_e.IndexOutOfRange
184
185 if index == last_index:
186 # Special case: The array SHORTENS if you unset from the end. You can
187 # tell with a+=(3 4)
188 strs.pop()
189 while len(strs) > 0 and strs[-1] is None:
190 strs.pop()
191 elif index < last_index:
192 strs[index] = None
193 else:
194 # If it's not found, it's not an error. In other words, 'unset'
195 # ensures that a value doesn't exist, regardless of whether it existed.
196 # It's idempotent. (Ousterhout specifically argues that the strict
197 # behavior was a mistake for Tcl!)
198 pass
199
200 return error_code_e.OK
201
202
203def InternalStringArray_Equals(lhs, rhs):
204 # type: (value.InternalStringArray, value.InternalStringArray) -> bool
205 len_lhs = len(lhs.strs)
206 len_rhs = len(rhs.strs)
207 if len_lhs != len_rhs:
208 return False
209
210 for i in xrange(0, len_lhs):
211 if lhs.strs[i] != rhs.strs[i]:
212 return False
213
214 return True
215
216
217def _InternalStringArray_HasHoles(array_val):
218 # type: (value.InternalStringArray) -> bool
219
220 # mycpp rewrite: None in array_val.strs
221 for s in array_val.strs:
222 if s is None:
223 return True
224 return False
225
226
227def InternalStringArray_ToStrForShellPrint(array_val, name):
228 # type: (value.InternalStringArray, Optional[str]) -> str
229 buff = [] # type: List[str]
230 first = True
231 if _InternalStringArray_HasHoles(array_val):
232 if name is not None:
233 # Note: Arrays with unset elements are printed in the form:
234 # declare -p arr=(); arr[3]='' arr[4]='foo' ...
235 # Note: This form will be deprecated in the future when
236 # InitializerList for the compound assignment a=([i]=v ...) is
237 # implemented.
238 buff.append("()")
239 for i, element in enumerate(array_val.strs):
240 if element is not None:
241 if first:
242 buff.append(";")
243 first = False
244 buff.extend([
245 " ", name, "[",
246 str(i), "]=",
247 j8_lite.MaybeShellEncode(element)
248 ])
249 else:
250 buff.append("(")
251 for i, element in enumerate(array_val.strs):
252 if element is not None:
253 if not first:
254 buff.append(" ")
255 else:
256 first = False
257 buff.extend([
258 "[",
259 str(i), "]=",
260 j8_lite.MaybeShellEncode(element)
261 ])
262 buff.append(")")
263 else:
264 buff.append("(")
265 for element in array_val.strs:
266 if not first:
267 buff.append(" ")
268 else:
269 first = False
270 buff.append(j8_lite.MaybeShellEncode(element))
271 buff.append(")")
272
273 return ''.join(buff)
274
275
276#------------------------------------------------------------------------------
277# All BashAssoc operations depending on the internal representation of
278# BashAssoc come here.
279
280
281def BashAssoc_New():
282 # type: () -> value.BashAssoc
283 # mycpp limitation: NewDict() needs to be typed
284 d = mylib.NewDict() # type: Dict[str, str]
285 return value.BashAssoc(d)
286
287
288def BashAssoc_Copy(val):
289 # type: (value.BashAssoc) -> value.BashAssoc
290 # mycpp limitation: NewDict() needs to be typed
291 d = mylib.NewDict() # type: Dict[str, str]
292 for key in val.d:
293 d[key] = val.d[key]
294 return value.BashAssoc(d)
295
296
297def BashAssoc_ListInitialize(val, initializer, has_plus, exec_opts, blame_loc):
298 # type: (value.BashAssoc, value.InitializerList, bool, optview.Exec, loc_t) -> None
299
300 if not has_plus:
301 val.d.clear()
302
303 if len(initializer.assigns) > 0 and initializer.assigns[0].key is None:
304 if exec_opts.strict_array():
305 e_die(
306 "BashAssoc cannot be list-initialzied by (KEY VALUE ...) (strict_array)",
307 blame_loc)
308
309 # Process the form "a=(key1 value1 key2 value2 ...)"
310 k = None # type: Optional[str]
311 for assign in initializer.assigns:
312 s = assign.rval
313 if assign.key is not None:
314 s = '[%s]%s%s' % (assign.key, '+=' if assign.plus_eq else '=',
315 s)
316 if k is not None:
317 val.d[k] = s
318 k = None
319 else:
320 k = s
321 if k is not None:
322 val.d[k] = ''
323 return
324
325 for triplet in initializer.assigns:
326 if triplet.key is None:
327 e_die(
328 "Key is missing. BashAssoc requires a key for %r" %
329 triplet.rval, blame_loc)
330
331 s = triplet.rval
332 if triplet.plus_eq:
333 old_s = val.d.get(triplet.key)
334 if old_s is not None:
335 s = old_s + s
336 val.d[triplet.key] = s
337
338
339def BashAssoc_IsEmpty(assoc_val):
340 # type: (value.BashAssoc) -> bool
341 return len(assoc_val.d) == 0
342
343
344def BashAssoc_Count(assoc_val):
345 # type: (value.BashAssoc) -> int
346 return len(assoc_val.d)
347
348
349def BashAssoc_GetDict(assoc_val):
350 # type: (value.BashAssoc) -> Dict[str, str]
351 return assoc_val.d
352
353
354def BashAssoc_AppendDict(assoc_val, d):
355 # type: (value.BashAssoc, Dict[str, str]) -> None
356 for key in d:
357 assoc_val.d[key] = d[key]
358
359
360def BashAssoc_GetKeys(assoc_val):
361 # type: (value.BashAssoc) -> List[str]
362 return assoc_val.d.keys()
363
364
365def BashAssoc_GetValues(assoc_val):
366 # type: (value.BashAssoc) -> List[str]
367 return assoc_val.d.values()
368
369
370def BashAssoc_HasElement(assoc_val, s):
371 # type: (value.BashAssoc, str) -> bool
372 return s in assoc_val.d
373
374
375def BashAssoc_GetElement(assoc_val, s):
376 # type: (value.BashAssoc, str) -> Optional[str]
377 return assoc_val.d.get(s)
378
379
380def BashAssoc_SetElement(assoc_val, key, s):
381 # type: (value.BashAssoc, str, str) -> None
382 assoc_val.d[key] = s
383
384
385def BashAssoc_UnsetElement(assoc_val, key):
386 # type: (value.BashAssoc, str) -> None
387 mylib.dict_erase(assoc_val.d, key)
388
389
390def BashAssoc_Equals(lhs, rhs):
391 # type: (value.BashAssoc, value.BashAssoc) -> bool
392 if len(lhs.d) != len(rhs.d):
393 return False
394
395 for k in lhs.d:
396 if k not in rhs.d or rhs.d[k] != lhs.d[k]:
397 return False
398
399 return True
400
401
402def BashAssoc_ToStrForShellPrint(assoc_val):
403 # type: (value.BashAssoc) -> str
404 buff = ["("] # type: List[str]
405 first = True
406 for key in sorted(assoc_val.d):
407 if not first:
408 buff.append(" ")
409 else:
410 first = False
411
412 key_quoted = j8_lite.ShellEncode(key)
413 value_quoted = j8_lite.MaybeShellEncode(assoc_val.d[key])
414
415 buff.extend(["[", key_quoted, "]=", value_quoted])
416
417 buff.append(")")
418 return ''.join(buff)
419
420
421#------------------------------------------------------------------------------
422# All BashArray operations depending on the internal representation of
423# BashArray come here.
424
425
426def BashArray_New():
427 # type: () -> value.BashArray
428 d = {} # type: Dict[mops.BigInt, str]
429 max_index = mops.MINUS_ONE # max index for empty array
430 return value.BashArray(d, max_index)
431
432
433def BashArray_Copy(val):
434 # type: (value.BashArray) -> value.BashArray
435 d = {} # type: Dict[mops.BigInt, str]
436 for index in val.d:
437 d[index] = val.d[index]
438 return value.BashArray(d, val.max_index)
439
440
441def BashArray_FromList(strs):
442 # type: (List[str]) -> value.BashArray
443 d = {} # type: Dict[mops.BigInt, str]
444 max_index = mops.MINUS_ONE # max index for empty array
445 for s in strs:
446 max_index = mops.Add(max_index, mops.ONE)
447 if s is not None:
448 d[max_index] = s
449
450 return value.BashArray(d, max_index)
451
452
453def BashArray_ListInitialize(val, initializer, has_plus, blame_loc, arith_ev):
454 # type: (value.BashArray, value.InitializerList, bool, loc_t, ArrayIndexEvaluator) -> None
455 if not has_plus:
456 val.d.clear()
457 val.max_index = mops.MINUS_ONE
458
459 array_index = val.max_index
460 for triplet in initializer.assigns:
461 if triplet.key is not None:
462 array_index = arith_ev.StringToBigInt(triplet.key, blame_loc)
463 else:
464 array_index = mops.Add(array_index, mops.ONE)
465
466 s = triplet.rval
467 if triplet.plus_eq:
468 old_s = val.d.get(array_index)
469 if old_s is not None:
470 s = old_s + s
471
472 val.d[array_index] = s
473 if BigInt_Greater(array_index, val.max_index):
474 val.max_index = array_index
475
476
477def BashArray_IsEmpty(sparse_val):
478 # type: (value.BashArray) -> bool
479 return len(sparse_val.d) == 0
480
481
482def BashArray_Count(sparse_val):
483 # type: (value.BashArray) -> int
484 return len(sparse_val.d)
485
486
487def BashArray_Length(sparse_val):
488 # type: (value.BashArray) -> mops.BigInt
489 return mops.Add(sparse_val.max_index, mops.ONE)
490
491
492def BashArray_GetKeys(sparse_val):
493 # type: (value.BashArray) -> List[mops.BigInt]
494 keys = sparse_val.d.keys()
495 mylib.BigIntSort(keys)
496 return keys
497
498
499def BashArray_GetValues(sparse_val):
500 # type: (value.BashArray) -> List[str]
501 """Get the list of values. This function does not fill None for
502 the unset elements, so the index in the returned list does not
503 match the index in a sparse array.
504
505 """
506
507 values = [] # type: List[str]
508 for index in BashArray_GetKeys(sparse_val):
509 values.append(sparse_val.d[index])
510 return values
511
512
513def BashArray_AppendValues(sparse_val, strs):
514 # type: (value.BashArray, List[str]) -> None
515 for s in strs:
516 sparse_val.max_index = mops.Add(sparse_val.max_index, mops.ONE)
517 sparse_val.d[sparse_val.max_index] = s
518
519
520def _BashArray_CanonicalizeIndex(sparse_val, index):
521 # type: (value.BashArray, mops.BigInt) -> Tuple[mops.BigInt, error_code_t]
522 """This function returns (mops.BigInt(-1),
523 error_code_e.IndexOutOfRange) when
524 the specified index is out of range. For example, it includes the
525 case where the index is negative and its absolute value is larger
526 than max_index + 1.
527
528 """
529
530 if BigInt_Less(index, mops.ZERO):
531 index = mops.Add(index, mops.Add(sparse_val.max_index, mops.ONE))
532 if BigInt_Less(index, mops.ZERO):
533 return mops.MINUS_ONE, error_code_e.IndexOutOfRange
534 return index, error_code_e.OK
535
536
537def BashArray_HasElement(sparse_val, index):
538 # type: (value.BashArray, mops.BigInt) -> Tuple[bool, error_code_t]
539 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
540 if error_code != error_code_e.OK:
541 return False, error_code
542 return index in sparse_val.d, error_code_e.OK
543
544
545def BashArray_GetElement(sparse_val, index):
546 # type: (value.BashArray, mops.BigInt) -> Tuple[Optional[str], error_code_t]
547 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
548 if error_code != error_code_e.OK:
549 return None, error_code
550 return sparse_val.d.get(index), error_code_e.OK
551
552
553def BashArray_SetElement(sparse_val, index, s):
554 # type: (value.BashArray, mops.BigInt, str) -> error_code_t
555 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
556 if error_code != error_code_e.OK:
557 return error_code
558 if BigInt_Greater(index, sparse_val.max_index):
559 sparse_val.max_index = index
560 sparse_val.d[index] = s
561 return error_code_e.OK
562
563
564def BashArray_UnsetElement(sparse_val, index):
565 # type: (value.BashArray, mops.BigInt) -> error_code_t
566 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
567 if error_code != error_code_e.OK:
568 return error_code
569 mylib.dict_erase(sparse_val.d, index)
570
571 # update max_index
572 if mops.Equal(index, sparse_val.max_index):
573 sparse_val.max_index = mops.MINUS_ONE
574 for index in sparse_val.d:
575 if mops.Greater(index, sparse_val.max_index):
576 sparse_val.max_index = index
577 return error_code_e.OK
578
579
580def BashArray_Equals(lhs, rhs):
581 # type: (value.BashArray, value.BashArray) -> bool
582 len_lhs = len(lhs.d)
583 len_rhs = len(rhs.d)
584 if len_lhs != len_rhs:
585 return False
586
587 for index in lhs.d:
588 if index not in rhs.d or rhs.d[index] != lhs.d[index]:
589 return False
590
591 return True
592
593
594def BashArray_ToStrForShellPrint(sparse_val):
595 # type: (value.BashArray) -> str
596 body = [] # type: List[str]
597
598 is_sparse = not mops.Equal(mops.IntWiden(BashArray_Count(sparse_val)),
599 BashArray_Length(sparse_val))
600
601 for index in BashArray_GetKeys(sparse_val):
602 if len(body) > 0:
603 body.append(" ")
604 if is_sparse:
605 body.extend(["[", mops.ToStr(index), "]="])
606
607 body.append(j8_lite.MaybeShellEncode(sparse_val.d[index]))
608 return "(%s)" % ''.join(body)
609
610
611#------------------------------------------------------------------------------
612# InitializerList operations depending on its internal representation come
613# here.
614
615
616def InitializerList_ToStrForShellPrint(val):
617 # type: (value.InitializerList) -> str
618 body = [] # type: List[str]
619
620 for init in val.assigns:
621 if len(body) > 0:
622 body.append(" ")
623 if init.key is not None:
624 key = j8_lite.MaybeShellEncode(init.key)
625 if init.plus_eq:
626 body.extend(["[", key, "]+="])
627 else:
628 body.extend(["[", key, "]="])
629 body.append(j8_lite.MaybeShellEncode(init.rval))
630
631 return "(%s)" % ''.join(body)