1 #!/usr/bin/env python2
2 """Builtin_assign.py."""
3 from __future__ import print_function
4
5 from _devbuild.gen import arg_types
6 from _devbuild.gen.option_asdl import builtin_i
7 from _devbuild.gen.runtime_asdl import (
8 value,
9 value_e,
10 value_t,
11 scope_e,
12 cmd_value,
13 AssignArg,
14 )
15 from _devbuild.gen.syntax_asdl import loc, loc_t, word_t
16
17 from core import error
18 from core.error import e_usage
19 from core import state
20 from core import vm
21 from frontend import flag_spec
22 from frontend import location
23 from frontend import args
24 from mycpp import mylib
25 from mycpp.mylib import log
26 from osh import cmd_eval
27 from osh import sh_expr_eval
28 from data_lang import qsn
29
30 from typing import cast, Optional, Dict, List, TYPE_CHECKING
31 if TYPE_CHECKING:
32 from _devbuild.gen.runtime_asdl import Proc
33 from core.state import Mem
34 from core.ui import ErrorFormatter
35 from frontend.args import _Attributes
36
37 _ = log
38
39 _OTHER = 0
40 _READONLY = 1
41 _EXPORT = 2
42
43
44 def _PrintVariables(mem, cmd_val, attrs, print_flags, builtin=_OTHER):
45 # type: (Mem, cmd_value.Assign, _Attributes, bool, int) -> int
46 """
47 Args:
48 print_flags: whether to print flags
49 builtin: is it the readonly or export builtin?
50 """
51 flag = attrs.attrs
52
53 # Turn dynamic vars to static.
54 tmp_g = flag.get('g')
55 tmp_a = flag.get('a')
56 tmp_A = flag.get('A')
57
58 flag_g = cast(value.Bool,
59 tmp_g).b if tmp_g and tmp_g.tag() == value_e.Bool else False
60 flag_a = cast(value.Bool,
61 tmp_a).b if tmp_a and tmp_a.tag() == value_e.Bool else False
62 flag_A = cast(value.Bool,
63 tmp_A).b if tmp_A and tmp_A.tag() == value_e.Bool else False
64
65 tmp_n = flag.get('n')
66 tmp_r = flag.get('r')
67 tmp_x = flag.get('x')
68
69 #log('FLAG %r', flag)
70
71 # SUBTLE: export -n vs. declare -n. flag vs. OPTION.
72 # flags are value.Bool, while options are Undef or Str.
73 # '+', '-', or None
74 flag_n = cast(
75 value.Str, tmp_n
76 ).s if tmp_n and tmp_n.tag() == value_e.Str else None # type: Optional[str]
77 flag_r = cast(
78 value.Str, tmp_r
79 ).s if tmp_r and tmp_r.tag() == value_e.Str else None # type: Optional[str]
80 flag_x = cast(
81 value.Str, tmp_x
82 ).s if tmp_x and tmp_x.tag() == value_e.Str else None # type: Optional[str]
83
84 if cmd_val.builtin_id == builtin_i.local:
85 if flag_g and not mem.IsGlobalScope():
86 return 1
87 which_scopes = scope_e.LocalOnly
88 elif flag_g:
89 which_scopes = scope_e.GlobalOnly
90 else:
91 which_scopes = mem.ScopesForReading() # reading
92
93 if len(cmd_val.pairs) == 0:
94 print_all = True
95 cells = mem.GetAllCells(which_scopes)
96 names = sorted(cells) # type: List[str]
97 else:
98 print_all = False
99 names = []
100 cells = {}
101 for pair in cmd_val.pairs:
102 name = pair.var_name
103 if pair.rval and pair.rval.tag() == value_e.Str:
104 # Invalid: declare -p foo=bar
105 # Add a sentinel so we skip it, but know to exit with status 1.
106 s = cast(value.Str, pair.rval).s
107 invalid = "%s=%s" % (name, s)
108 names.append(invalid)
109 cells[invalid] = None
110 else:
111 names.append(name)
112 cells[name] = mem.GetCell(name, which_scopes)
113
114 count = 0
115 for name in names:
116 cell = cells[name]
117 if cell is None:
118 continue # Invalid
119 val = cell.val
120 #log('name %r %s', name, val)
121
122 if val.tag() == value_e.Undef:
123 continue
124 if builtin == _READONLY and not cell.readonly:
125 continue
126 if builtin == _EXPORT and not cell.exported:
127 continue
128
129 if flag_n == '-' and not cell.nameref:
130 continue
131 if flag_n == '+' and cell.nameref:
132 continue
133 if flag_r == '-' and not cell.readonly:
134 continue
135 if flag_r == '+' and cell.readonly:
136 continue
137 if flag_x == '-' and not cell.exported:
138 continue
139 if flag_x == '+' and cell.exported:
140 continue
141
142 if flag_a and val.tag() != value_e.BashArray:
143 continue
144 if flag_A and val.tag() != value_e.BashAssoc:
145 continue
146
147 decl = [] # type: List[str]
148 if print_flags:
149 flags = [] # type: List[str]
150 if cell.nameref:
151 flags.append('n')
152 if cell.readonly:
153 flags.append('r')
154 if cell.exported:
155 flags.append('x')
156 if val.tag() == value_e.BashArray:
157 flags.append('a')
158 elif val.tag() == value_e.BashAssoc:
159 flags.append('A')
160 if len(flags) == 0:
161 flags.append('-')
162
163 decl.extend(["declare -", ''.join(flags), " ", name])
164 else:
165 decl.append(name)
166
167 if val.tag() == value_e.Str:
168 str_val = cast(value.Str, val)
169 decl.extend(["=", qsn.maybe_shell_encode(str_val.s)])
170
171 elif val.tag() == value_e.BashArray:
172 array_val = cast(value.BashArray, val)
173
174 # mycpp rewrite: None in array_val.strs
175 has_holes = False
176 for s in array_val.strs:
177 if s is None:
178 has_holes = True
179 break
180
181 if has_holes:
182 # Note: Arrays with unset elements are printed in the form:
183 # declare -p arr=(); arr[3]='' arr[4]='foo' ...
184 decl.append("=()")
185 first = True
186 for i, element in enumerate(array_val.strs):
187 if element is not None:
188 if first:
189 decl.append(";")
190 first = False
191 decl.extend([
192 " ", name, "[",
193 str(i), "]=",
194 qsn.maybe_shell_encode(element)
195 ])
196 else:
197 body = [] # type: List[str]
198 for element in array_val.strs:
199 if len(body) > 0:
200 body.append(" ")
201 body.append(qsn.maybe_shell_encode(element))
202 decl.extend(["=(", ''.join(body), ")"])
203
204 elif val.tag() == value_e.BashAssoc:
205 assoc_val = cast(value.BashAssoc, val)
206 body = []
207 for key in sorted(assoc_val.d):
208 if len(body) > 0:
209 body.append(" ")
210 key_quoted = qsn.maybe_shell_encode(key, flags=qsn.MUST_QUOTE)
211 value_quoted = qsn.maybe_shell_encode(assoc_val.d[key])
212 body.extend(["[", key_quoted, "]=", value_quoted])
213 if len(body) > 0:
214 decl.extend(["=(", ''.join(body), ")"])
215
216 else:
217 pass # note: other types silently ignored
218
219 print(''.join(decl))
220 count += 1
221
222 if print_all or count == len(names):
223 return 0
224 else:
225 return 1
226
227
228 def _ExportReadonly(mem, pair, flags):
229 # type: (Mem, AssignArg, int) -> None
230 """For 'export' and 'readonly' to respect += and flags.
231
232 Like 'setvar' (scope_e.LocalOnly), unless dynamic scope is on. That is, it
233 respects shopt --unset dynamic_scope.
234
235 Used for assignment builtins, (( a = b )), {fd}>out, ${x=}, etc.
236 """
237 which_scopes = mem.ScopesForWriting()
238
239 lval = location.LName(pair.var_name)
240 if pair.plus_eq:
241 old_val = sh_expr_eval.OldValue(lval, mem, None) # ignore set -u
242 # When 'export e+=', then rval is value.Str('')
243 # When 'export foo', the pair.plus_eq flag is false.
244 assert pair.rval is not None
245 val = cmd_eval.PlusEquals(old_val, pair.rval)
246 else:
247 # NOTE: when rval is None, only flags are changed
248 val = pair.rval
249
250 mem.SetValue(lval, val, which_scopes, flags=flags)
251
252
253 class Export(vm._AssignBuiltin):
254 def __init__(self, mem, errfmt):
255 # type: (Mem, ErrorFormatter) -> None
256 self.mem = mem
257 self.errfmt = errfmt
258
259 def Run(self, cmd_val):
260 # type: (cmd_value.Assign) -> int
261 arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
262 arg_r.Next()
263 attrs = flag_spec.Parse('export_', arg_r)
264 arg = arg_types.export_(attrs.attrs)
265 #arg = attrs
266
267 if arg.f:
268 e_usage(
269 "doesn't accept -f because it's dangerous. "
270 "(The code can usually be restructured with 'source')",
271 loc.Missing)
272
273 if arg.p or len(cmd_val.pairs) == 0:
274 return _PrintVariables(self.mem,
275 cmd_val,
276 attrs,
277 True,
278 builtin=_EXPORT)
279
280 if arg.n:
281 for pair in cmd_val.pairs:
282 if pair.rval is not None:
283 e_usage("doesn't accept RHS with -n",
284 loc.Word(pair.blame_word))
285
286 # NOTE: we don't care if it wasn't found, like bash.
287 self.mem.ClearFlag(pair.var_name, state.ClearExport)
288 else:
289 for pair in cmd_val.pairs:
290 _ExportReadonly(self.mem, pair, state.SetExport)
291
292 return 0
293
294
295 def _ReconcileTypes(rval, flag_a, flag_A, blame_word):
296 # type: (Optional[value_t], bool, bool, word_t) -> value_t
297 """Check that -a and -A flags are consistent with RHS.
298
299 Special case: () is allowed to mean empty indexed array or empty assoc array
300 if the context is clear.
301
302 Shared between NewVar and Readonly.
303 """
304 if flag_a and rval is not None and rval.tag() != value_e.BashArray:
305 e_usage("Got -a but RHS isn't an array", loc.Word(blame_word))
306
307 if flag_A and rval:
308 # Special case: declare -A A=() is OK. The () is changed to mean an empty
309 # associative array.
310 if rval.tag() == value_e.BashArray:
311 array_val = cast(value.BashArray, rval)
312 if len(array_val.strs) == 0:
313 return value.BashAssoc({})
314 #return value.BashArray([])
315
316 if rval.tag() != value_e.BashAssoc:
317 e_usage("Got -A but RHS isn't an associative array",
318 loc.Word(blame_word))
319
320 return rval
321
322
323 class Readonly(vm._AssignBuiltin):
324 def __init__(self, mem, errfmt):
325 # type: (Mem, ErrorFormatter) -> None
326 self.mem = mem
327 self.errfmt = errfmt
328
329 def Run(self, cmd_val):
330 # type: (cmd_value.Assign) -> int
331 arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
332 arg_r.Next()
333 attrs = flag_spec.Parse('readonly', arg_r)
334 arg = arg_types.readonly(attrs.attrs)
335
336 if arg.p or len(cmd_val.pairs) == 0:
337 return _PrintVariables(self.mem,
338 cmd_val,
339 attrs,
340 True,
341 builtin=_READONLY)
342
343 for pair in cmd_val.pairs:
344 if pair.rval is None:
345 if arg.a:
346 rval = value.BashArray([]) # type: value_t
347 elif arg.A:
348 rval = value.BashAssoc({})
349 else:
350 rval = None
351 else:
352 rval = pair.rval
353
354 rval = _ReconcileTypes(rval, arg.a, arg.A, pair.blame_word)
355
356 # NOTE:
357 # - when rval is None, only flags are changed
358 # - dynamic scope because flags on locals can be changed, etc.
359 _ExportReadonly(self.mem, pair, state.SetReadOnly)
360
361 return 0
362
363
364 class NewVar(vm._AssignBuiltin):
365 """declare/typeset/local."""
366
367 def __init__(self, mem, procs, errfmt):
368 # type: (Mem, Dict[str, Proc], ErrorFormatter) -> None
369 self.mem = mem
370 self.procs = procs
371 self.errfmt = errfmt
372
373 def _PrintFuncs(self, names):
374 # type: (List[str]) -> int
375 status = 0
376 for name in names:
377 if name in self.procs:
378 print(name)
379 # TODO: Could print LST for -f, or render LST. Bash does this. 'trap'
380 # could use that too.
381 else:
382 status = 1
383 return status
384
385 def Run(self, cmd_val):
386 # type: (cmd_value.Assign) -> int
387 arg_r = args.Reader(cmd_val.argv, locs=cmd_val.arg_locs)
388 arg_r.Next()
389 attrs = flag_spec.Parse('new_var', arg_r)
390 arg = arg_types.new_var(attrs.attrs)
391
392 status = 0
393
394 if arg.f:
395 names = arg_r.Rest()
396 if len(names):
397 # This is only used for a STATUS QUERY now. We only show the name,
398 # not the body.
399 status = self._PrintFuncs(names)
400 else:
401 # Disallow this since it would be incompatible.
402 e_usage('with -f expects function names', loc.Missing)
403 return status
404
405 if arg.F:
406 names = arg_r.Rest()
407 if len(names):
408 status = self._PrintFuncs(names)
409 else:
410 # bash quirk: with no names, they're printed in a different format!
411 for func_name in sorted(self.procs):
412 print('declare -f %s' % (func_name))
413 return status
414
415 if arg.p: # Lookup and print variables.
416 return _PrintVariables(self.mem, cmd_val, attrs, True)
417 elif len(cmd_val.pairs) == 0:
418 return _PrintVariables(self.mem, cmd_val, attrs, False)
419
420 #
421 # Set variables
422 #
423
424 #raise error.Usage("doesn't understand %s" % cmd_val.argv[1:])
425 if cmd_val.builtin_id == builtin_i.local:
426 which_scopes = scope_e.LocalOnly
427 else: # declare/typeset
428 if arg.g:
429 which_scopes = scope_e.GlobalOnly
430 else:
431 which_scopes = scope_e.LocalOnly
432
433 flags = 0
434 if arg.x == '-':
435 flags |= state.SetExport
436 if arg.r == '-':
437 flags |= state.SetReadOnly
438 if arg.n == '-':
439 flags |= state.SetNameref
440
441 if arg.x == '+':
442 flags |= state.ClearExport
443 if arg.r == '+':
444 flags |= state.ClearReadOnly
445 if arg.n == '+':
446 flags |= state.ClearNameref
447
448 for pair in cmd_val.pairs:
449 rval = pair.rval
450 # declare -a foo=(a b); declare -a foo; should not reset to empty array
451 if rval is None and (arg.a or arg.A):
452 old_val = self.mem.GetValue(pair.var_name)
453 if arg.a:
454 if old_val.tag() != value_e.BashArray:
455 rval = value.BashArray([])
456 elif arg.A:
457 if old_val.tag() != value_e.BashAssoc:
458 rval = value.BashAssoc({})
459
460 lval = location.LName(pair.var_name)
461 if pair.plus_eq:
462 old_val = sh_expr_eval.OldValue(lval, self.mem,
463 None) # ignore set -u
464 # When 'typeset e+=', then rval is value.Str('')
465 # When 'typeset foo', the pair.plus_eq flag is false.
466 assert pair.rval is not None
467 rval = cmd_eval.PlusEquals(old_val, pair.rval)
468 else:
469 rval = _ReconcileTypes(rval, arg.a, arg.A, pair.blame_word)
470
471 self.mem.SetValue(lval, rval, which_scopes, flags=flags)
472
473 return status
474
475
476 # TODO:
477 # - It would make more sense to treat no args as an error (bash doesn't.)
478 # - Should we have strict builtins? Or just make it stricter?
479 # - Typed args: unset (mylist[0]) is like Python's del
480 # - It has the same word as 'setvar', which makes sense
481
482
483 class Unset(vm._Builtin):
484 def __init__(self, mem, procs, unsafe_arith, errfmt):
485 # type: (Mem, Dict[str, Proc], sh_expr_eval.UnsafeArith, ErrorFormatter) -> None
486 self.mem = mem
487 self.procs = procs
488 self.unsafe_arith = unsafe_arith
489 self.errfmt = errfmt
490
491 def _UnsetVar(self, arg, location, proc_fallback):
492 # type: (str, loc_t, bool) -> bool
493 """
494 Returns:
495 bool: whether the 'unset' builtin should succeed with code 0.
496 """
497 lval = self.unsafe_arith.ParseLValue(arg, location)
498
499 #log('lval %s', lval)
500 found = False
501 try:
502 found = self.mem.Unset(lval, scope_e.Shopt)
503 except error.Runtime as e:
504 # note: in bash, myreadonly=X fails, but declare myreadonly=X doesn't
505 # fail because it's a builtin. So I guess the same is true of 'unset'.
506 msg = e.UserErrorString()
507 self.errfmt.Print_(msg, blame_loc=location)
508 return False
509
510 if proc_fallback and not found:
511 mylib.dict_erase(self.procs, arg)
512
513 return True
514
515 def Run(self, cmd_val):
516 # type: (cmd_value.Argv) -> int
517 attrs, arg_r = flag_spec.ParseCmdVal('unset', cmd_val)
518 arg = arg_types.unset(attrs.attrs)
519
520 argv, arg_locs = arg_r.Rest2()
521 for i, name in enumerate(argv):
522 location = arg_locs[i]
523
524 if arg.f:
525 mylib.dict_erase(self.procs, name)
526
527 elif arg.v:
528 if not self._UnsetVar(name, location, False):
529 return 1
530
531 else:
532 # proc_fallback: Try to delete var first, then func.
533 if not self._UnsetVar(name, location, True):
534 return 1
535
536 return 0
537
538
539 class Shift(vm._Builtin):
540 def __init__(self, mem):
541 # type: (Mem) -> None
542 self.mem = mem
543
544 def Run(self, cmd_val):
545 # type: (cmd_value.Argv) -> int
546 num_args = len(cmd_val.argv) - 1
547 if num_args == 0:
548 n = 1
549 elif num_args == 1:
550 arg = cmd_val.argv[1]
551 try:
552 n = int(arg)
553 except ValueError:
554 e_usage("Invalid shift argument %r" % arg, loc.Missing)
555 else:
556 e_usage('got too many arguments', loc.Missing)
557
558 return self.mem.Shift(n)