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

475 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 return mops.Greater(a, b)
16
17
18def BigInt_Less(a, b):
19 # type: (mops.BigInt, mops.BigInt) -> bool
20 return mops.Greater(b, a)
21
22
23def BigInt_GreaterEq(a, b):
24 # type: (mops.BigInt, mops.BigInt) -> bool
25 return not mops.Greater(b, a)
26
27
28def 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
38def BashArray_IsEmpty(array_val):
39 # type: (value.BashArray) -> bool
40 return len(array_val.strs) == 0
41
42
43def 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
54def BashArray_Length(array_val):
55 # type: (value.BashArray) -> int
56 return len(array_val.strs)
57
58
59def 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
69def BashArray_GetValues(array_val):
70 # type: (value.BashArray) -> List[str]
71 return array_val.strs
72
73
74def BashArray_AppendValues(array_val, strs):
75 # type: (value.BashArray, List[str]) -> None
76 array_val.strs.extend(strs)
77
78
79def _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
96def 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
109def 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
131def 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
153def 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
182def 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
196def _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
206def 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
260def BashAssoc_IsEmpty(assoc_val):
261 # type: (value.BashAssoc) -> bool
262 return len(assoc_val.d) == 0
263
264
265def BashAssoc_Count(assoc_val):
266 # type: (value.BashAssoc) -> int
267 return len(assoc_val.d)
268
269
270def BashAssoc_GetDict(assoc_val):
271 # type: (value.BashAssoc) -> Dict[str, str]
272 return assoc_val.d
273
274
275def 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
281def BashAssoc_GetKeys(assoc_val):
282 # type: (value.BashAssoc) -> List[str]
283 return assoc_val.d.keys()
284
285
286def BashAssoc_GetValues(assoc_val):
287 # type: (value.BashAssoc) -> List[str]
288 return assoc_val.d.values()
289
290
291def BashAssoc_HasElement(assoc_val, s):
292 # type: (value.BashAssoc, str) -> bool
293 return s in assoc_val.d
294
295
296def BashAssoc_GetElement(assoc_val, s):
297 # type: (value.BashAssoc, str) -> Optional[str]
298 return assoc_val.d.get(s)
299
300
301def BashAssoc_SetElement(assoc_val, key, s):
302 # type: (value.BashAssoc, str, str) -> None
303 assoc_val.d[key] = s
304
305
306def BashAssoc_UnsetElement(assoc_val, key):
307 # type: (value.BashAssoc, str) -> None
308 mylib.dict_erase(assoc_val.d, key)
309
310
311def 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
323def 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
347def SparseArray_IsEmpty(sparse_val):
348 # type: (value.SparseArray) -> bool
349 return len(sparse_val.d) == 0
350
351
352def SparseArray_Count(sparse_val):
353 # type: (value.SparseArray) -> int
354 return len(sparse_val.d)
355
356
357def SparseArray_Length(sparse_val):
358 # type: (value.SparseArray) -> mops.BigInt
359 return mops.Add(sparse_val.max_index, mops.ONE)
360
361
362def 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
369def 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
383def 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
390def _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
407def 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
415def 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
423def 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
434def 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
450def 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
464def 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)