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

629 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 its internal representation
53# 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 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 strs.append(None)
169 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 its internal representation come here.
278
279
280def BashAssoc_New():
281 # type: () -> value.BashAssoc
282 # mycpp limitation: NewDict() needs to be typed
283 d = mylib.NewDict() # type: Dict[str, str]
284 return value.BashAssoc(d)
285
286
287def BashAssoc_Copy(val):
288 # type: (value.BashAssoc) -> value.BashAssoc
289 # mycpp limitation: NewDict() needs to be typed
290 d = mylib.NewDict() # type: Dict[str, str]
291 for key in val.d:
292 d[key] = val.d[key]
293 return value.BashAssoc(d)
294
295
296def BashAssoc_ListInitialize(val, initializer, has_plus, exec_opts, blame_loc):
297 # type: (value.BashAssoc, value.InitializerList, bool, optview.Exec, loc_t) -> None
298
299 if not has_plus:
300 val.d.clear()
301
302 if len(initializer.assigns) > 0 and initializer.assigns[0].key is None:
303 if exec_opts.strict_array():
304 e_die(
305 "BashAssoc cannot be list-initialzied by (KEY VALUE ...) (strict_array)",
306 blame_loc)
307
308 # Process the form "a=(key1 value1 key2 value2 ...)"
309 k = None # type: Optional[str]
310 for assign in initializer.assigns:
311 s = assign.rval
312 if assign.key is not None:
313 s = '[%s]%s%s' % (assign.key, '+=' if assign.plus_eq else '=',
314 s)
315 if k is not None:
316 val.d[k] = s
317 k = None
318 else:
319 k = s
320 if k is not None:
321 val.d[k] = ''
322 return
323
324 for triplet in initializer.assigns:
325 if triplet.key is None:
326 e_die(
327 "Key is missing. BashAssoc requires a key for %r" %
328 triplet.rval, blame_loc)
329
330 s = triplet.rval
331 if triplet.plus_eq:
332 old_s = val.d.get(triplet.key)
333 if old_s is not None:
334 s = old_s + s
335 val.d[triplet.key] = s
336
337
338def BashAssoc_IsEmpty(assoc_val):
339 # type: (value.BashAssoc) -> bool
340 return len(assoc_val.d) == 0
341
342
343def BashAssoc_Count(assoc_val):
344 # type: (value.BashAssoc) -> int
345 return len(assoc_val.d)
346
347
348def BashAssoc_GetDict(assoc_val):
349 # type: (value.BashAssoc) -> Dict[str, str]
350 return assoc_val.d
351
352
353def BashAssoc_AppendDict(assoc_val, d):
354 # type: (value.BashAssoc, Dict[str, str]) -> None
355 for key in d:
356 assoc_val.d[key] = d[key]
357
358
359def BashAssoc_GetKeys(assoc_val):
360 # type: (value.BashAssoc) -> List[str]
361 return assoc_val.d.keys()
362
363
364def BashAssoc_GetValues(assoc_val):
365 # type: (value.BashAssoc) -> List[str]
366 return assoc_val.d.values()
367
368
369def BashAssoc_HasElement(assoc_val, s):
370 # type: (value.BashAssoc, str) -> bool
371 return s in assoc_val.d
372
373
374def BashAssoc_GetElement(assoc_val, s):
375 # type: (value.BashAssoc, str) -> Optional[str]
376 return assoc_val.d.get(s)
377
378
379def BashAssoc_SetElement(assoc_val, key, s):
380 # type: (value.BashAssoc, str, str) -> None
381 assoc_val.d[key] = s
382
383
384def BashAssoc_UnsetElement(assoc_val, key):
385 # type: (value.BashAssoc, str) -> None
386 mylib.dict_erase(assoc_val.d, key)
387
388
389def BashAssoc_Equals(lhs, rhs):
390 # type: (value.BashAssoc, value.BashAssoc) -> bool
391 if len(lhs.d) != len(rhs.d):
392 return False
393
394 for k in lhs.d:
395 if k not in rhs.d or rhs.d[k] != lhs.d[k]:
396 return False
397
398 return True
399
400
401def BashAssoc_ToStrForShellPrint(assoc_val):
402 # type: (value.BashAssoc) -> str
403 buff = ["("] # type: List[str]
404 first = True
405 for key in sorted(assoc_val.d):
406 if not first:
407 buff.append(" ")
408 else:
409 first = False
410
411 key_quoted = j8_lite.ShellEncode(key)
412 value_quoted = j8_lite.MaybeShellEncode(assoc_val.d[key])
413
414 buff.extend(["[", key_quoted, "]=", value_quoted])
415
416 buff.append(")")
417 return ''.join(buff)
418
419
420#------------------------------------------------------------------------------
421# All BashArray operations depending on its internal representation come here.
422
423
424def BashArray_New():
425 # type: () -> value.BashArray
426 d = {} # type: Dict[mops.BigInt, str]
427 max_index = mops.MINUS_ONE # max index for empty array
428 return value.BashArray(d, max_index)
429
430
431def BashArray_Copy(val):
432 # type: (value.BashArray) -> value.BashArray
433 d = {} # type: Dict[mops.BigInt, str]
434 for index in val.d:
435 d[index] = val.d[index]
436 return value.BashArray(d, val.max_index)
437
438
439def BashArray_FromList(strs):
440 # type: (List[str]) -> value.BashArray
441 d = {} # type: Dict[mops.BigInt, str]
442 max_index = mops.MINUS_ONE # max index for empty array
443 for s in strs:
444 max_index = mops.Add(max_index, mops.ONE)
445 if s is not None:
446 d[max_index] = s
447
448 return value.BashArray(d, max_index)
449
450
451def BashArray_ListInitialize(val, initializer, has_plus, blame_loc, arith_ev):
452 # type: (value.BashArray, value.InitializerList, bool, loc_t, ArrayIndexEvaluator) -> None
453 if not has_plus:
454 val.d.clear()
455 val.max_index = mops.MINUS_ONE
456
457 array_index = val.max_index
458 for triplet in initializer.assigns:
459 if triplet.key is not None:
460 array_index = arith_ev.StringToBigInt(triplet.key, blame_loc)
461 else:
462 array_index = mops.Add(array_index, mops.ONE)
463
464 s = triplet.rval
465 if triplet.plus_eq:
466 old_s = val.d.get(array_index)
467 if old_s is not None:
468 s = old_s + s
469
470 val.d[array_index] = s
471 if BigInt_Greater(array_index, val.max_index):
472 val.max_index = array_index
473
474
475def BashArray_IsEmpty(sparse_val):
476 # type: (value.BashArray) -> bool
477 return len(sparse_val.d) == 0
478
479
480def BashArray_Count(sparse_val):
481 # type: (value.BashArray) -> int
482 return len(sparse_val.d)
483
484
485def BashArray_Length(sparse_val):
486 # type: (value.BashArray) -> mops.BigInt
487 return mops.Add(sparse_val.max_index, mops.ONE)
488
489
490def BashArray_GetKeys(sparse_val):
491 # type: (value.BashArray) -> List[mops.BigInt]
492 keys = sparse_val.d.keys()
493 mylib.BigIntSort(keys)
494 return keys
495
496
497def BashArray_GetValues(sparse_val):
498 # type: (value.BashArray) -> List[str]
499 """Get the list of values. This function does not fill None for
500 the unset elements, so the index in the returned list does not
501 match the index in a sparse array.
502
503 """
504
505 values = [] # type: List[str]
506 for index in BashArray_GetKeys(sparse_val):
507 values.append(sparse_val.d[index])
508 return values
509
510
511def BashArray_AppendValues(sparse_val, strs):
512 # type: (value.BashArray, List[str]) -> None
513 for s in strs:
514 sparse_val.max_index = mops.Add(sparse_val.max_index, mops.ONE)
515 sparse_val.d[sparse_val.max_index] = s
516
517
518def _BashArray_CanonicalizeIndex(sparse_val, index):
519 # type: (value.BashArray, mops.BigInt) -> Tuple[mops.BigInt, error_code_t]
520 """This function returns (mops.BigInt(-1),
521 error_code_e.IndexOutOfRange) when
522 the specified index is out of range. For example, it includes the
523 case where the index is negative and its absolute value is larger
524 than max_index + 1.
525
526 """
527
528 if BigInt_Less(index, mops.ZERO):
529 index = mops.Add(index, mops.Add(sparse_val.max_index, mops.ONE))
530 if BigInt_Less(index, mops.ZERO):
531 return mops.MINUS_ONE, error_code_e.IndexOutOfRange
532 return index, error_code_e.OK
533
534
535def BashArray_HasElement(sparse_val, index):
536 # type: (value.BashArray, mops.BigInt) -> Tuple[bool, error_code_t]
537 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
538 if error_code != error_code_e.OK:
539 return False, error_code
540 return index in sparse_val.d, error_code_e.OK
541
542
543def BashArray_GetElement(sparse_val, index):
544 # type: (value.BashArray, mops.BigInt) -> Tuple[Optional[str], error_code_t]
545 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
546 if error_code != error_code_e.OK:
547 return None, error_code
548 return sparse_val.d.get(index), error_code_e.OK
549
550
551def BashArray_SetElement(sparse_val, index, s):
552 # type: (value.BashArray, mops.BigInt, str) -> error_code_t
553 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
554 if error_code != error_code_e.OK:
555 return error_code
556 if BigInt_Greater(index, sparse_val.max_index):
557 sparse_val.max_index = index
558 sparse_val.d[index] = s
559 return error_code_e.OK
560
561
562def BashArray_UnsetElement(sparse_val, index):
563 # type: (value.BashArray, mops.BigInt) -> error_code_t
564 index, error_code = _BashArray_CanonicalizeIndex(sparse_val, index)
565 if error_code != error_code_e.OK:
566 return error_code
567 mylib.dict_erase(sparse_val.d, index)
568
569 # update max_index
570 if mops.Equal(index, sparse_val.max_index):
571 sparse_val.max_index = mops.MINUS_ONE
572 for index in sparse_val.d:
573 if mops.Greater(index, sparse_val.max_index):
574 sparse_val.max_index = index
575 return error_code_e.OK
576
577
578def BashArray_Equals(lhs, rhs):
579 # type: (value.BashArray, value.BashArray) -> bool
580 len_lhs = len(lhs.d)
581 len_rhs = len(rhs.d)
582 if len_lhs != len_rhs:
583 return False
584
585 for index in lhs.d:
586 if index not in rhs.d or rhs.d[index] != lhs.d[index]:
587 return False
588
589 return True
590
591
592def BashArray_ToStrForShellPrint(sparse_val):
593 # type: (value.BashArray) -> str
594 body = [] # type: List[str]
595
596 is_sparse = not mops.Equal(mops.IntWiden(BashArray_Count(sparse_val)),
597 BashArray_Length(sparse_val))
598
599 for index in BashArray_GetKeys(sparse_val):
600 if len(body) > 0:
601 body.append(" ")
602 if is_sparse:
603 body.extend(["[", mops.ToStr(index), "]="])
604
605 body.append(j8_lite.MaybeShellEncode(sparse_val.d[index]))
606 return "(%s)" % ''.join(body)
607
608
609#------------------------------------------------------------------------------
610# InitializerList operations depending on its internal representation come
611# here.
612
613
614def InitializerList_ToStrForShellPrint(val):
615 # type: (value.InitializerList) -> str
616 body = [] # type: List[str]
617
618 for init in val.assigns:
619 if len(body) > 0:
620 body.append(" ")
621 if init.key is not None:
622 key = j8_lite.MaybeShellEncode(init.key)
623 if init.plus_eq:
624 body.extend(["[", key, "]+="])
625 else:
626 body.extend(["[", key, "]="])
627 body.append(j8_lite.MaybeShellEncode(init.rval))
628
629 return "(%s)" % ''.join(body)