OILS / core / bash_impl.py View on Github | oilshell.org

486 lines, 244 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_Count(sparse_val):
371 # type: (value.SparseArray) -> int
372 return len(sparse_val.d)
373
374
375def SparseArray_Length(sparse_val):
376 # type: (value.SparseArray) -> mops.BigInt
377
378 return mops.Add(sparse_val.max_index, mops.ONE)
379
380
381def SparseArray_GetKeys(sparse_val):
382 # type: (value.SparseArray) -> List[mops.BigInt]
383
384 keys = sparse_val.d.keys()
385 mylib.BigIntSort(keys)
386 return keys
387
388
389def SparseArray_GetValues(sparse_val):
390 # type: (value.SparseArray) -> List[str]
391 """Get the list of values. This function does not fill None for
392 the unset elements, so the index in the returned list does not
393 match the index in a sparse array.
394
395 """
396
397 values = [] # type: List[str]
398 for index in SparseArray_GetKeys(sparse_val):
399 values.append(sparse_val.d[index])
400 return values
401
402
403def SparseArray_AppendValues(sparse_val, strs):
404 # type: (value.SparseArray, List[str]) -> None
405 for s in strs:
406 sparse_val.max_index = mops.Add(sparse_val.max_index, mops.ONE)
407 sparse_val.d[sparse_val.max_index] = s
408
409
410def _SparseArray_CanonicalizeIndex(sparse_val, index):
411 # type: (value.SparseArray, mops.BigInt) -> Tuple[mops.BigInt, error_code_t]
412 """This function returns (mops.BigInt(-1),
413 error_code_e.IndexOutOfRange) when
414 the specified index is out of range. For example, it includes the
415 case where the index is negative and its absolute value is larger
416 than max_index + 1.
417
418 """
419
420 if BigInt_Less(index, mops.ZERO):
421 index = mops.Add(index, mops.Add(sparse_val.max_index, mops.ONE))
422 if BigInt_Less(index, mops.ZERO):
423 return mops.MINUS_ONE, error_code_e.IndexOutOfRange
424 return index, error_code_e.OK
425
426
427def SparseArray_HasElement(sparse_val, index):
428 # type: (value.SparseArray, mops.BigInt) -> Tuple[bool, error_code_t]
429
430 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
431 if error_code != error_code_e.OK:
432 return False, error_code
433 return index in sparse_val.d, error_code_e.OK
434
435
436def SparseArray_GetElement(sparse_val, index):
437 # type: (value.SparseArray, mops.BigInt) -> Tuple[Optional[str], error_code_t]
438
439 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
440 if error_code != error_code_e.OK:
441 return None, error_code
442 return sparse_val.d.get(index), error_code_e.OK
443
444
445def SparseArray_SetElement(sparse_val, index, s):
446 # type: (value.SparseArray, mops.BigInt, str) -> error_code_t
447
448 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
449 if error_code != error_code_e.OK:
450 return error_code
451 if BigInt_Greater(index, sparse_val.max_index):
452 sparse_val.max_index = index
453 sparse_val.d[index] = s
454 return error_code_e.OK
455
456
457def SparseArray_UnsetElement(sparse_val, index):
458 # type: (value.SparseArray, mops.BigInt) -> error_code_t
459
460 index, error_code = _SparseArray_CanonicalizeIndex(sparse_val, index)
461 if error_code != error_code_e.OK:
462 return error_code
463 mylib.dict_erase(sparse_val.d, index)
464
465 # update max_index
466 if mops.Equal(index, sparse_val.max_index):
467 sparse_val.max_index = mops.MINUS_ONE
468 for index in sparse_val.d:
469 if mops.Greater(index, sparse_val.max_index):
470 sparse_val.max_index = index
471 return error_code_e.OK
472
473
474def SparseArray_ToStrForShellPrint(sparse_val):
475 # type: (value.SparseArray) -> str
476
477 body = [] # type: List[str]
478 for index in SparseArray_GetKeys(sparse_val):
479 if len(body) > 0:
480 body.append(" ")
481 body.extend([
482 "[",
483 mops.ToStr(index), "]=",
484 j8_lite.MaybeShellEncode(sparse_val.d[index])
485 ])
486 return "(%s)" % ''.join(body)