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

506 lines, 255 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
5
6from data_lang import j8_lite
7from mycpp import mops
8from mycpp import mylib
9
10from typing import Dict, List, Optional, Tuple
11
12
13def BigInt_Greater(a, b):
14 # type: (mops.BigInt, mops.BigInt) -> bool
15
16 return mops.Greater(a, b)
17
18
19def BigInt_Less(a, b):
20 # type: (mops.BigInt, mops.BigInt) -> bool
21
22 return mops.Greater(b, a)
23
24
25def BigInt_GreaterEq(a, b):
26 # type: (mops.BigInt, mops.BigInt) -> bool
27
28 return not mops.Greater(b, a)
29
30
31def BigInt_LessEq(a, b):
32 # type: (mops.BigInt, mops.BigInt) -> bool
33
34 return not mops.Greater(a, b)
35
36
37#------------------------------------------------------------------------------
38# All BashArray operations depending on the internal
39# representation of SparseArray come here.
40
41
42def BashArray_IsEmpty(array_val):
43 # type: (value.BashArray) -> bool
44
45 return len(array_val.strs) == 0
46
47
48def BashArray_Count(array_val):
49 # type: (value.BashArray) -> int
50
51 # There can be empty placeholder values in the array.
52 length = 0
53 for s in array_val.strs:
54 if s is not None:
55 length += 1
56 return length
57
58
59def BashArray_Length(array_val):
60 # type: (value.BashArray) -> int
61
62 return len(array_val.strs)
63
64
65def BashArray_GetKeys(array_val):
66 # type: (value.BashArray) -> List[int]
67
68 indices = [] # type: List[int]
69 for i, s in enumerate(array_val.strs):
70 if s is not None:
71 indices.append(i)
72
73 return indices
74
75
76def BashArray_GetValues(array_val):
77 # type: (value.BashArray) -> List[str]
78
79 return array_val.strs
80
81
82def BashArray_AppendValues(array_val, strs):
83 # type: (value.BashArray, List[str]) -> None
84
85 array_val.strs.extend(strs)
86
87
88def _BashArray_CanonicalizeIndex(array_val, index):
89 # type: (value.BashArray, int) -> Tuple[int, int, error_code_t]
90 """This function returns (-1, n, error_code_e.IndexOutOfRange)
91 when the specified index is out of range. For example, it
92 includes the case where the index is negative and its absolute
93 value is larger than max_index + 1.
94
95 """
96
97 n = len(array_val.strs)
98 if index < 0:
99 index += n
100 if index < 0:
101 return -1, n, error_code_e.IndexOutOfRange
102 return index, n, error_code_e.OK
103
104
105def BashArray_HasElement(array_val, index):
106 # type: (value.BashArray, int) -> Tuple[bool, error_code_t]
107
108 index, n, error_code = _BashArray_CanonicalizeIndex(array_val, index)
109 if error_code != error_code_e.OK:
110 return False, error_code
111
112 if index < n:
113 return array_val.strs[index] is not None, error_code_e.OK
114
115 # out of range
116 return False, error_code_e.OK
117
118
119def BashArray_GetElement(array_val, index):
120 # type: (value.BashArray, int) -> Tuple[Optional[str], error_code_t]
121 """This function returns a tuple of a string value and an
122 error_code. If the element is found, the value is returned as the
123 first element of the tuple. Otherwise, the first element of the
124 tuple is None.
125
126 """
127
128 index, n, error_code = _BashArray_CanonicalizeIndex(array_val, index)
129 if error_code != error_code_e.OK:
130 return None, error_code
131
132 if index < n:
133 # TODO: strs->index() has a redundant check for (i < 0)
134 s = array_val.strs[index] # type: Optional[str]
135 # note: s could be None because representation is sparse
136 else:
137 s = None
138 return s, error_code_e.OK
139
140
141def BashArray_SetElement(array_val, index, s):
142 # type: (value.BashArray, int, str) -> error_code_t
143
144 strs = array_val.strs
145
146 # a[-1]++ computes this twice; could we avoid it?
147 index, n, error_code = _BashArray_CanonicalizeIndex(array_val, index)
148 if error_code != error_code_e.OK:
149 return error_code
150
151 if index < n:
152 array_val.strs[index] = s
153 else:
154 # Fill it in with None. It could look like this:
155 # ['1', 2, 3, None, None, '4', None]
156 # Then ${#a[@]} counts the entries that are not None.
157 for i in xrange(index - n + 1):
158 array_val.strs.append(None)
159 array_val.strs[index] = s
160
161 return error_code_e.OK
162
163
164def BashArray_UnsetElement(array_val, index):
165 # type: (value.BashArray, int) -> error_code_t
166 strs = array_val.strs
167
168 n = len(strs)
169 last_index = n - 1
170 if index < 0:
171 index += n
172 if index < 0:
173 return error_code_e.IndexOutOfRange
174
175 if index == last_index:
176 # Special case: The array SHORTENS if you unset from the end. You can
177 # tell with a+=(3 4)
178 strs.pop()
179 while len(strs) > 0 and strs[-1] is None:
180 strs.pop()
181 elif index < last_index:
182 strs[index] = None
183 else:
184 # If it's not found, it's not an error. In other words, 'unset'
185 # ensures that a value doesn't exist, regardless of whether it existed.
186 # It's idempotent. (Ousterhout specifically argues that the strict
187 # behavior was a mistake for Tcl!)
188 pass
189
190 return error_code_e.OK
191
192
193def BashArray_Equals(lhs, rhs):
194 # type: (value.BashArray, value.BashArray) -> bool
195
196 len_lhs = len(lhs.strs)
197 len_rhs = len(rhs.strs)
198 if len_lhs != len_rhs:
199 return False
200
201 for i in xrange(0, len_lhs):
202 if lhs.strs[i] != rhs.strs[i]:
203 return False
204
205 return True
206
207
208def _BashArray_HasHoles(array_val):
209 # type: (value.BashArray) -> bool
210
211 # mycpp rewrite: None in array_val.strs
212 for s in array_val.strs:
213 if s is None:
214 return True
215 return False
216
217
218def BashArray_ToStrForShellPrint(array_val, name):
219 # type: (value.BashArray, Optional[str]) -> str
220
221 buff = [] # type: List[str]
222 first = True
223 if _BashArray_HasHoles(array_val):
224 if name is not None:
225 # Note: Arrays with unset elements are printed in the form:
226 # declare -p arr=(); arr[3]='' arr[4]='foo' ...
227 # Note: This form will be deprecated in the future when
228 # InitializerList for the compound assignment a=([i]=v ...) is
229 # implemented.
230 buff.append("()")
231 for i, element in enumerate(array_val.strs):
232 if element is not None:
233 if first:
234 buff.append(";")
235 first = False
236 buff.extend([
237 " ", name, "[",
238 str(i), "]=",
239 j8_lite.MaybeShellEncode(element)
240 ])
241 else:
242 buff.append("(")
243 for i, element in enumerate(array_val.strs):
244 if element is not None:
245 if not first:
246 buff.append(" ")
247 else:
248 first = False
249 buff.extend([
250 "[",
251 str(i), "]=",
252 j8_lite.MaybeShellEncode(element)
253 ])
254 buff.append(")")
255 else:
256 buff.append("(")
257 for element in array_val.strs:
258 if not first:
259 buff.append(" ")
260 else:
261 first = False
262 buff.append(j8_lite.MaybeShellEncode(element))
263 buff.append(")")
264
265 return ''.join(buff)
266
267
268#------------------------------------------------------------------------------
269# All BashAssoc operations depending on the internal
270# representation of SparseArray come here.
271
272
273def BashAssoc_IsEmpty(assoc_val):
274 # type: (value.BashAssoc) -> bool
275 return len(assoc_val.d) == 0
276
277
278def BashAssoc_Count(assoc_val):
279 # type: (value.BashAssoc) -> int
280 return len(assoc_val.d)
281
282
283def BashAssoc_GetDict(assoc_val):
284 # type: (value.BashAssoc) -> Dict[str, str]
285
286 return assoc_val.d
287
288
289def BashAssoc_AppendDict(assoc_val, d):
290 # type: (value.BashAssoc, Dict[str, str]) -> None
291
292 for key in d:
293 assoc_val.d[key] = d[key]
294
295
296def BashAssoc_GetKeys(assoc_val):
297 # type: (value.BashAssoc) -> List[str]
298
299 return assoc_val.d.keys()
300
301
302def BashAssoc_GetValues(assoc_val):
303 # type: (value.BashAssoc) -> List[str]
304
305 return assoc_val.d.values()
306
307
308def BashAssoc_HasElement(assoc_val, s):
309 # type: (value.BashAssoc, str) -> bool
310
311 return s in assoc_val.d
312
313
314def BashAssoc_GetElement(assoc_val, s):
315 # type: (value.BashAssoc, str) -> Optional[str]
316
317 return assoc_val.d.get(s)
318
319
320def BashAssoc_SetElement(assoc_val, key, s):
321 # type: (value.BashAssoc, str, str) -> None
322
323 assoc_val.d[key] = s
324
325
326def BashAssoc_UnsetElement(assoc_val, key):
327 # type: (value.BashAssoc, str) -> None
328
329 mylib.dict_erase(assoc_val.d, key)
330
331
332def BashAssoc_Equals(lhs, rhs):
333 # type: (value.BashAssoc, value.BashAssoc) -> bool
334
335 if len(lhs.d) != len(rhs.d):
336 return False
337
338 for k in lhs.d:
339 if k not in rhs.d or rhs.d[k] != lhs.d[k]:
340 return False
341
342 return True
343
344
345def BashAssoc_ToStrForShellPrint(assoc_val):
346 # type: (value.BashAssoc) -> str
347
348 buff = ["("] # type: List[str]
349 first = True
350 for key in sorted(assoc_val.d):
351 if not first:
352 buff.append(" ")
353 else:
354 first = False
355
356 key_quoted = j8_lite.ShellEncode(key)
357 value_quoted = j8_lite.MaybeShellEncode(assoc_val.d[key])
358
359 buff.extend(["[", key_quoted, "]=", value_quoted])
360
361 buff.append(")")
362 return ''.join(buff)
363
364
365#------------------------------------------------------------------------------
366# All SparseArray operations depending on the internal
367# representation of SparseArray come here.
368
369
370def SparseArray_IsEmpty(sparse_val):
371 # type: (value.SparseArray) -> bool
372 return len(sparse_val.d) == 0
373
374
375def SparseArray_Count(sparse_val):
376 # type: (value.SparseArray) -> int
377 return len(sparse_val.d)
378
379
380def SparseArray_Length(sparse_val):
381 # type: (value.SparseArray) -> mops.BigInt
382
383 return mops.Add(sparse_val.max_index, mops.ONE)
384
385
386def SparseArray_GetKeys(sparse_val):
387 # type: (value.SparseArray) -> List[mops.BigInt]
388
389 keys = sparse_val.d.keys()
390 mylib.BigIntSort(keys)
391 return keys
392
393
394def SparseArray_GetValues(sparse_val):
395 # type: (value.SparseArray) -> List[str]
396 """Get the list of values. This function does not fill None for
397 the unset elements, so the index in the returned list does not
398 match the index in a sparse array.
399
400 """
401
402 values = [] # type: List[str]
403 for index in SparseArray_GetKeys(sparse_val):
404 values.append(sparse_val.d[index])
405 return values
406
407
408def SparseArray_AppendValues(sparse_val, strs):
409 # type: (value.SparseArray, List[str]) -> None
410 for s in strs:
411 sparse_val.max_index = mops.Add(sparse_val.max_index, mops.ONE)
412 sparse_val.d[sparse_val.max_index] = s
413
414
415def _SparseArray_CanonicalizeIndex(sparse_val, index):
416 # type: (value.SparseArray, mops.BigInt) -> Tuple[mops.BigInt, error_code_t]
417 """This function returns (mops.BigInt(-1),
418 error_code_e.IndexOutOfRange) when
419 the specified index is out of range. For example, it includes the
420 case where the index is negative and its absolute value is larger
421 than max_index + 1.
422
423 """
424
425 if BigInt_Less(index, mops.ZERO):
426 index = mops.Add(index, mops.Add(sparse_val.max_index, mops.ONE))
427 if BigInt_Less(index, mops.ZERO):
428 return mops.MINUS_ONE, error_code_e.IndexOutOfRange
429 return index, error_code_e.OK
430
431
432def SparseArray_HasElement(sparse_val, index):
433 # type: (value.SparseArray, mops.BigInt) -> Tuple[bool, error_code_t]
434
435 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
436 if error_code != error_code_e.OK:
437 return False, error_code
438 return index in sparse_val.d, error_code_e.OK
439
440
441def SparseArray_GetElement(sparse_val, index):
442 # type: (value.SparseArray, mops.BigInt) -> Tuple[Optional[str], error_code_t]
443
444 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
445 if error_code != error_code_e.OK:
446 return None, error_code
447 return sparse_val.d.get(index), error_code_e.OK
448
449
450def SparseArray_SetElement(sparse_val, index, s):
451 # type: (value.SparseArray, mops.BigInt, str) -> error_code_t
452
453 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
454 if error_code != error_code_e.OK:
455 return error_code
456 if BigInt_Greater(index, sparse_val.max_index):
457 sparse_val.max_index = index
458 sparse_val.d[index] = s
459 return error_code_e.OK
460
461
462def SparseArray_UnsetElement(sparse_val, index):
463 # type: (value.SparseArray, mops.BigInt) -> error_code_t
464
465 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
466 if error_code != error_code_e.OK:
467 return error_code
468 mylib.dict_erase(sparse_val.d, index)
469
470 # update max_index
471 if mops.Equal(index, sparse_val.max_index):
472 sparse_val.max_index = mops.MINUS_ONE
473 for index in sparse_val.d:
474 if mops.Greater(index, sparse_val.max_index):
475 sparse_val.max_index = index
476 return error_code_e.OK
477
478
479def SparseArray_Equals(lhs, rhs):
480 # type: (value.SparseArray, value.SparseArray) -> bool
481
482 len_lhs = len(lhs.d)
483 len_rhs = len(rhs.d)
484 if len_lhs != len_rhs:
485 return False
486
487 for index in lhs.d:
488 if index not in rhs.d or rhs.d[index] != lhs.d[index]:
489 return False
490
491 return True
492
493
494def SparseArray_ToStrForShellPrint(sparse_val):
495 # type: (value.SparseArray) -> str
496
497 body = [] # type: List[str]
498 for index in SparseArray_GetKeys(sparse_val):
499 if len(body) > 0:
500 body.append(" ")
501 body.extend([
502 "[",
503 mops.ToStr(index), "]=",
504 j8_lite.MaybeShellEncode(sparse_val.d[index])
505 ])
506 return "(%s)" % ''.join(body)