1 | """bash_impl.py - implements operations on Bash data structures"""
|
2 |
|
3 | from _devbuild.gen.runtime_asdl import error_code_e, error_code_t
|
4 | from _devbuild.gen.value_asdl import value
|
5 |
|
6 | from data_lang import j8_lite
|
7 | from mycpp import mops
|
8 | from mycpp import mylib
|
9 |
|
10 | from typing import Dict, List, Optional, Tuple
|
11 |
|
12 |
|
13 | def BigInt_Greater(a, b):
|
14 | # type: (mops.BigInt, mops.BigInt) -> bool
|
15 |
|
16 | return mops.Greater(a, b)
|
17 |
|
18 |
|
19 | def BigInt_Less(a, b):
|
20 | # type: (mops.BigInt, mops.BigInt) -> bool
|
21 |
|
22 | return mops.Greater(b, a)
|
23 |
|
24 |
|
25 | def BigInt_GreaterEq(a, b):
|
26 | # type: (mops.BigInt, mops.BigInt) -> bool
|
27 |
|
28 | return not mops.Greater(b, a)
|
29 |
|
30 |
|
31 | def 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 |
|
42 | def BashArray_IsEmpty(array_val):
|
43 | # type: (value.BashArray) -> bool
|
44 |
|
45 | return len(array_val.strs) == 0
|
46 |
|
47 |
|
48 | def 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 |
|
59 | def BashArray_Length(array_val):
|
60 | # type: (value.BashArray) -> int
|
61 |
|
62 | return len(array_val.strs)
|
63 |
|
64 |
|
65 | def 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 |
|
76 | def BashArray_GetValues(array_val):
|
77 | # type: (value.BashArray) -> List[str]
|
78 |
|
79 | return array_val.strs
|
80 |
|
81 |
|
82 | def BashArray_AppendValues(array_val, strs):
|
83 | # type: (value.BashArray, List[str]) -> None
|
84 |
|
85 | array_val.strs.extend(strs)
|
86 |
|
87 |
|
88 | def _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 |
|
105 | def 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 |
|
119 | def 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 |
|
141 | def 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 |
|
164 | def 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 |
|
193 | def 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 |
|
208 | def _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 |
|
218 | def 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 |
|
273 | def BashAssoc_IsEmpty(assoc_val):
|
274 | # type: (value.BashAssoc) -> bool
|
275 | return len(assoc_val.d) == 0
|
276 |
|
277 |
|
278 | def BashAssoc_Count(assoc_val):
|
279 | # type: (value.BashAssoc) -> int
|
280 | return len(assoc_val.d)
|
281 |
|
282 |
|
283 | def BashAssoc_GetDict(assoc_val):
|
284 | # type: (value.BashAssoc) -> Dict[str, str]
|
285 |
|
286 | return assoc_val.d
|
287 |
|
288 |
|
289 | def 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 |
|
296 | def BashAssoc_GetKeys(assoc_val):
|
297 | # type: (value.BashAssoc) -> List[str]
|
298 |
|
299 | return assoc_val.d.keys()
|
300 |
|
301 |
|
302 | def BashAssoc_GetValues(assoc_val):
|
303 | # type: (value.BashAssoc) -> List[str]
|
304 |
|
305 | return assoc_val.d.values()
|
306 |
|
307 |
|
308 | def BashAssoc_HasElement(assoc_val, s):
|
309 | # type: (value.BashAssoc, str) -> bool
|
310 |
|
311 | return s in assoc_val.d
|
312 |
|
313 |
|
314 | def BashAssoc_GetElement(assoc_val, s):
|
315 | # type: (value.BashAssoc, str) -> Optional[str]
|
316 |
|
317 | return assoc_val.d.get(s)
|
318 |
|
319 |
|
320 | def BashAssoc_SetElement(assoc_val, key, s):
|
321 | # type: (value.BashAssoc, str, str) -> None
|
322 |
|
323 | assoc_val.d[key] = s
|
324 |
|
325 |
|
326 | def BashAssoc_UnsetElement(assoc_val, key):
|
327 | # type: (value.BashAssoc, str) -> None
|
328 |
|
329 | mylib.dict_erase(assoc_val.d, key)
|
330 |
|
331 |
|
332 | def 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 |
|
345 | def 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 |
|
370 | def SparseArray_Count(sparse_val):
|
371 | # type: (value.SparseArray) -> int
|
372 | return len(sparse_val.d)
|
373 |
|
374 |
|
375 | def SparseArray_Length(sparse_val):
|
376 | # type: (value.SparseArray) -> mops.BigInt
|
377 |
|
378 | return mops.Add(sparse_val.max_index, mops.ONE)
|
379 |
|
380 |
|
381 | def 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 |
|
389 | def 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 |
|
403 | def 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 |
|
410 | def _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 |
|
427 | def 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 |
|
436 | def 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 |
|
445 | def 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 |
|
457 | def 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 |
|
474 | def 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)
|