|
|
@@ -29,7 +29,7 @@ |
|
|
def _ValueToPartValue(val, quoted):
|
|
|
"""Helper for VarSub evaluation.
|
|
|
|
|
|
Called by _EvalBracedVarSub and _EvalWordPart for SimpleVarSub.
|
|
|
Called by _EvalBracedVarSub and __EvalWordPart for SimpleVarSub.
|
|
|
"""
|
|
|
assert isinstance(val, runtime.value), val
|
|
|
|
|
|
@@ -44,30 +44,6 @@ def _ValueToPartValue(val, quoted): |
|
|
raise AssertionError
|
|
|
|
|
|
|
|
|
# TODO: Move to RootSplitter?
|
|
|
def _GetJoinChar(mem):
|
|
|
"""
|
|
|
For decaying arrays by joining, eg. "$@" -> $@.
|
|
|
array
|
|
|
"""
|
|
|
# https://www.gnu.org/software/bash/manual/bashref.html#Special-Parameters
|
|
|
# http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_05_02
|
|
|
# "When the expansion occurs within a double-quoted string (see
|
|
|
# Double-Quotes), it shall expand to a single field with the value of
|
|
|
# each parameter separated by the first character of the IFS variable, or
|
|
|
# by a <space> if IFS is unset. If IFS is set to a null string, this is
|
|
|
# not equivalent to unsetting it; its first character does not exist, so
|
|
|
# the parameter values are concatenated."
|
|
|
val = mem.GetVar('IFS')
|
|
|
if val.tag == value_e.Undef:
|
|
|
return ''
|
|
|
elif val.tag == value_e.Str:
|
|
|
return val.s[0]
|
|
|
else:
|
|
|
# TODO: Raise proper error
|
|
|
raise AssertionError("IFS shouldn't be an array")
|
|
|
|
|
|
|
|
|
def _MakeWordFrames(part_vals):
|
|
|
"""
|
|
|
A word evaluates to a flat list of word parts (StringPartValue or
|
|
|
@@ -115,6 +91,7 @@ def _MakeWordFrames(part_vals): |
|
|
return frames
|
|
|
|
|
|
|
|
|
# TODO: This could be _MakeWordFrames and then sep.join(). It's redunant.
|
|
|
def _DecayPartValuesToString(part_vals, join_char):
|
|
|
# Decay ${a=x"$@"x} to string.
|
|
|
out = []
|
|
|
@@ -181,7 +158,8 @@ def _DoUnarySuffixOp(s, op, arg): |
|
|
return s
|
|
|
|
|
|
elif op.op_id == Id.VOp1_Percent: # shortest suffix
|
|
|
# NOTE: This is different than re.search, which will find the longest suffix.
|
|
|
# NOTE: This is different than re.search, which will find the longest
|
|
|
# suffix.
|
|
|
r = re.compile('^(.*)' + pat_re + '$')
|
|
|
m = r.match(s)
|
|
|
if m:
|
|
|
@@ -239,20 +217,27 @@ def _PatSub(s, op, pat, replace_str): |
|
|
return pat_re.sub(replace_str, s, count)
|
|
|
|
|
|
|
|
|
# Eval is for ${a-} and ${a+}, Error is for ${a?}, and Assign is for ${a=}
|
|
|
# SliceParts is for ${a-} and ${a+}, Error is for ${a?}, and SliceAndAssign is
|
|
|
# for ${a=}.
|
|
|
|
|
|
Effect = util.Enum('Effect', 'SpliceParts Error SpliceAndAssign NoOp'.split())
|
|
|
|
|
|
|
|
|
class _WordPartEvaluator:
|
|
|
"""Abstract base class."""
|
|
|
class _WordEvaluator:
|
|
|
"""Abstract base class for word evaluators.
|
|
|
|
|
|
def __init__(self, mem, exec_opts, word_ev):
|
|
|
Public entry points:
|
|
|
EvalWordToString
|
|
|
EvalRhsWord
|
|
|
EvalWordSequence
|
|
|
"""
|
|
|
def __init__(self, mem, exec_opts, splitter):
|
|
|
self.mem = mem # for $HOME, $1, etc.
|
|
|
self.exec_opts = exec_opts # for nounset
|
|
|
self.word_ev = word_ev # for arith words, var op words
|
|
|
self.splitter = splitter
|
|
|
self.globber = glob_.Globber(exec_opts)
|
|
|
# NOTE: Executor also instantiates one.
|
|
|
self.arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, word_ev)
|
|
|
self.arith_ev = expr_eval.ArithEvaluator(mem, exec_opts, self)
|
|
|
|
|
|
def _EvalCommandSub(self, part, quoted):
|
|
|
"""Abstract since it has a side effect.
|
|
|
@@ -371,7 +356,7 @@ def _ApplyTestOp(self, val, op, quoted, part_vals): |
|
|
#print('!!',id, is_falsey)
|
|
|
if op.op_id in (Id.VTest_ColonHyphen, Id.VTest_Hyphen):
|
|
|
if is_falsey:
|
|
|
self.word_ev._EvalWordToParts(op.arg_word, quoted, part_vals)
|
|
|
self._EvalWordToParts(op.arg_word, quoted, part_vals)
|
|
|
return None, Effect.SpliceParts
|
|
|
else:
|
|
|
return None, Effect.NoOp
|
|
|
@@ -381,14 +366,14 @@ def _ApplyTestOp(self, val, op, quoted, part_vals): |
|
|
if is_falsey:
|
|
|
return None, Effect.NoOp
|
|
|
else:
|
|
|
self.word_ev._EvalWordToParts(op.arg_word, quoted, part_vals)
|
|
|
self._EvalWordToParts(op.arg_word, quoted, part_vals)
|
|
|
return None, Effect.SpliceParts
|
|
|
|
|
|
elif op.op_id in (Id.VTest_ColonEquals, Id.VTest_Equals):
|
|
|
if is_falsey:
|
|
|
# Collect new part vals.
|
|
|
assign_part_vals = []
|
|
|
self.word_ev._EvalWordToParts(op.arg_word, quoted, assign_part_vals)
|
|
|
self._EvalWordToParts(op.arg_word, quoted, assign_part_vals)
|
|
|
|
|
|
# Append them to out param and return them.
|
|
|
part_vals.extend(assign_part_vals)
|
|
|
@@ -443,7 +428,7 @@ def _ApplyUnarySuffixOp(self, val, op): |
|
|
|
|
|
if op_kind == Kind.VOp1:
|
|
|
#log('%s', op)
|
|
|
arg_val = self.word_ev.EvalWordToString(op.arg_word, do_fnmatch=True)
|
|
|
arg_val = self.EvalWordToString(op.arg_word, do_fnmatch=True)
|
|
|
assert arg_val.tag == value_e.Str
|
|
|
|
|
|
if val.tag == value_e.Str:
|
|
|
@@ -486,11 +471,11 @@ def _EvalDoubleQuotedPart(self, part, part_vals): |
|
|
return
|
|
|
|
|
|
for p in part.parts:
|
|
|
self.EvalWordPart(p, part_vals, quoted=True)
|
|
|
self._EvalWordPart(p, part_vals, quoted=True)
|
|
|
|
|
|
def _DecayArray(self, val):
|
|
|
sep = _GetJoinChar(self.mem)
|
|
|
assert val.tag == value_e.StrArray, val
|
|
|
sep = self.splitter.GetJoinChar()
|
|
|
return runtime.Str(sep.join(val.strs))
|
|
|
|
|
|
def _EmptyStrOrError(self, val, token=None):
|
|
|
@@ -636,7 +621,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): |
|
|
# NOTE: This decays arrays too! 'set -o strict_array' could
|
|
|
# avoid it.
|
|
|
rhs_str = _DecayPartValuesToString(assign_part_vals,
|
|
|
_GetJoinChar(self.mem))
|
|
|
self.splitter.GetJoinChar())
|
|
|
state.SetLocalString(self.mem, var_name, rhs_str)
|
|
|
return # EARLY RETURN, part_vals mutated
|
|
|
|
|
|
@@ -656,12 +641,11 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): |
|
|
elif op.tag == suffix_op_e.PatSub: # PatSub, vectorized
|
|
|
val = self._EmptyStrOrError(val)
|
|
|
|
|
|
pat_val = self.word_ev.EvalWordToString(op.pat, do_fnmatch=True)
|
|
|
pat_val = self.EvalWordToString(op.pat, do_fnmatch=True)
|
|
|
assert pat_val.tag == value_e.Str, pat_val
|
|
|
|
|
|
if op.replace:
|
|
|
replace_val = self.word_ev.EvalWordToString(op.replace,
|
|
|
do_fnmatch=True)
|
|
|
replace_val = self.EvalWordToString(op.replace, do_fnmatch=True)
|
|
|
assert replace_val.tag == value_e.Str, replace_val
|
|
|
replace_str = replace_val.s
|
|
|
else:
|
|
|
@@ -718,7 +702,7 @@ def _EvalBracedVarSub(self, part, part_vals, quoted): |
|
|
part_val = _ValueToPartValue(val, quoted)
|
|
|
part_vals.append(part_val)
|
|
|
|
|
|
def EvalWordPart(self, part, part_vals, quoted=False):
|
|
|
def _EvalWordPart(self, part, part_vals, quoted=False):
|
|
|
"""Evaluate a word part.
|
|
|
|
|
|
Args:
|
|
|
@@ -801,24 +785,6 @@ def EvalWordPart(self, part, part_vals, quoted=False): |
|
|
else:
|
|
|
raise AssertionError(part.__class__.__name__)
|
|
|
|
|
|
|
|
|
class _WordEvaluator:
|
|
|
"""Abstract base class for word evaluators.
|
|
|
|
|
|
Public entry points:
|
|
|
EvalWordToString
|
|
|
EvalRhsWord
|
|
|
EvalWordSequence
|
|
|
Error
|
|
|
"""
|
|
|
def __init__(self, mem, exec_opts, part_ev, splitter):
|
|
|
self.mem = mem
|
|
|
self.exec_opts = exec_opts
|
|
|
|
|
|
self.part_ev = part_ev
|
|
|
self.splitter = splitter
|
|
|
self.globber = glob_.Globber(exec_opts)
|
|
|
|
|
|
def _EvalWordToParts(self, word, quoted, part_vals):
|
|
|
"""Helper for EvalRhsWord, EvalWordSequence, etc.
|
|
|
|
|
|
@@ -830,7 +796,7 @@ def _EvalWordToParts(self, word, quoted, part_vals): |
|
|
"Expected CompoundWord, got %s" % word
|
|
|
|
|
|
for p in word.parts:
|
|
|
v = self.part_ev.EvalWordPart(p, part_vals, quoted=quoted)
|
|
|
v = self._EvalWordPart(p, part_vals, quoted=quoted)
|
|
|
|
|
|
def EvalWordToString(self, word, do_fnmatch=False, decay=False):
|
|
|
"""
|
|
|
@@ -846,7 +812,7 @@ def EvalWordToString(self, word, do_fnmatch=False, decay=False): |
|
|
"""
|
|
|
part_vals = []
|
|
|
for part in word.parts:
|
|
|
self.part_ev.EvalWordPart(part, part_vals, quoted=False)
|
|
|
self._EvalWordPart(part, part_vals, quoted=False)
|
|
|
|
|
|
strs = []
|
|
|
for part_val in part_vals:
|
|
|
@@ -1012,11 +978,10 @@ def EvalWordSequence(self, words): |
|
|
return self._EvalWordSequence(words)
|
|
|
|
|
|
|
|
|
class _NormalPartEvaluator(_WordPartEvaluator):
|
|
|
"""The Executor uses this to evaluate words."""
|
|
|
class NormalWordEvaluator(_WordEvaluator):
|
|
|
|
|
|
def __init__(self, mem, exec_opts, ex, word_ev):
|
|
|
_WordPartEvaluator.__init__(self, mem, exec_opts, word_ev)
|
|
|
def __init__(self, mem, exec_opts, splitter, ex):
|
|
|
_WordEvaluator.__init__(self, mem, exec_opts, splitter)
|
|
|
self.ex = ex
|
|
|
|
|
|
def _EvalCommandSub(self, node, quoted):
|
|
|
@@ -1034,26 +999,14 @@ def _EvalProcessSub(self, node, id_): |
|
|
return runtime.StringPartValue(dev_path, False, False)
|
|
|
|
|
|
|
|
|
class NormalWordEvaluator(_WordEvaluator):
|
|
|
|
|
|
def __init__(self, mem, exec_opts, splitter, ex):
|
|
|
part_ev = _NormalPartEvaluator(mem, exec_opts, ex, self)
|
|
|
_WordEvaluator.__init__(self, mem, exec_opts, part_ev, splitter)
|
|
|
|
|
|
|
|
|
class _CompletionPartEvaluator(_WordPartEvaluator):
|
|
|
"""For completion.
|
|
|
|
|
|
We are disabling command substitution for now.
|
|
|
|
|
|
TODO: Also disable side effects! Like ${a:=b} rather than ${a:-b}
|
|
|
And also $(( a+=1 ))
|
|
|
|
|
|
TODO: Unify with static_eval? Completion allows more stuff like var names,
|
|
|
and maybe words within arrays as well.
|
|
|
class CompletionWordEvaluator(_WordEvaluator):
|
|
|
"""
|
|
|
Difference from NormalWordEvaluator: No access to executor! But they both
|
|
|
have a splitter.
|
|
|
"""
|
|
|
def __init__(self, mem, exec_opts, word_ev):
|
|
|
_WordPartEvaluator.__init__(self, mem, exec_opts, word_ev)
|
|
|
|
|
|
def __init__(self, mem, exec_opts, splitter):
|
|
|
_WordEvaluator.__init__(self, mem, exec_opts, splitter)
|
|
|
|
|
|
def _EvalCommandSub(self, node, quoted):
|
|
|
# Just return a dummy string?
|
|
|
@@ -1063,14 +1016,3 @@ def _EvalCommandSub(self, node, quoted): |
|
|
def _EvalProcessSub(self, node, id_):
|
|
|
return runtime.StringPartValue(
|
|
|
'__PROCESS_SUB_NOT_EXECUTED__', False, False)
|
|
|
|
|
|
|
|
|
class CompletionWordEvaluator(_WordEvaluator):
|
|
|
"""
|
|
|
Difference from NormalWordEvaluator: No access to executor! But they both
|
|
|
have a splitter.
|
|
|
"""
|
|
|
|
|
|
def __init__(self, mem, exec_opts, splitter):
|
|
|
part_ev = _CompletionPartEvaluator(mem, exec_opts, self)
|
|
|
_WordEvaluator.__init__(self, mem, exec_opts, part_ev, splitter)
|