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