Skip to content

Commit 18ddc70

Browse files
author
Andy C
committed
[core/state refactor] Use simpler mutation methods for YSH.
- Mem::SetNamedYsh - Mem::ResolveNameForYshMutation Reading still uses GetValue(). This preserves the semantic fix for var and const. They define new variables in the current scope, not the enclosed scope.
1 parent da4c475 commit 18ddc70

File tree

5 files changed

+338
-49
lines changed

5 files changed

+338
-49
lines changed

core/state.py

+96-36
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@
5656
ClearExport = 1 << 3
5757
SetNameref = 1 << 4
5858
ClearNameref = 1 << 5
59+
60+
# For SetNamedYsh
5961
YshDecl = 1 << 6
6062

6163

@@ -1228,14 +1230,17 @@ def _FrameLookup(frame, name, ysh_decl):
12281230
if cell:
12291231
return cell, frame
12301232

1233+
# var, const are declarations
1234+
# TODO: what about proc, func?
12311235
if not ysh_decl:
12321236
rear_cell = frame.get('__E__') # ctx_EnclosedFrame() sets this
12331237
if rear_cell:
12341238
rear_val = rear_cell.val
12351239
assert rear_val, rear_val
12361240
if rear_val.tag() == value_e.Frame:
12371241
to_enclose = cast(value.Frame, rear_val).frame
1238-
return _FrameLookup(to_enclose, name, ysh_decl) # recursive call
1242+
return _FrameLookup(to_enclose, name,
1243+
ysh_decl) # recursive call
12391244

12401245
return None, None
12411246

@@ -1721,43 +1726,57 @@ def GetSpecialVar(self, op_id):
17211726
# Named Vars
17221727
#
17231728

1724-
def _ResolveNameOnly(self, name, which_scopes, flags):
1725-
# type: (str, scope_t, int) -> Tuple[Optional[Cell], Dict[str, Cell]]
1726-
"""Given a variable name, and scope rule, return a Cell and Frame.
1729+
def _ResolveNameForYshMutation(self, name, which_scopes, ysh_decl):
1730+
# type: (str, scope_t, bool) -> Tuple[Optional[Cell], Dict[str, Cell]]
1731+
"""Simpler version of _ResolveNameOnly for YSH.
1732+
1733+
YSH has no namerefs, and it only has local and global mutation (setvar
1734+
and setglobal).
1735+
"""
1736+
if which_scopes == scope_e.LocalOnly:
1737+
var_frame = self.var_stack[-1]
1738+
cell, result_frame = _FrameLookup(var_frame, name, ysh_decl)
1739+
if cell:
1740+
return cell, result_frame
1741+
return None, var_frame
1742+
1743+
if which_scopes == scope_e.GlobalOnly:
1744+
var_frame = self.var_stack[0]
1745+
cell, result_frame = _FrameLookup(var_frame, name, ysh_decl)
1746+
if cell:
1747+
return cell, result_frame
1748+
1749+
return None, var_frame
1750+
1751+
raise AssertionError()
17271752

1728-
This function does not resolve 'nameref'. It's used to get and set
1729-
variables.
1753+
def _ResolveNameOnly(self, name, which_scopes):
1754+
# type: (str, scope_t) -> Tuple[Optional[Cell], Dict[str, Cell]]
1755+
"""Helper for getting and setting variable.
17301756
17311757
Returns:
1732-
cell: The cell corresponding to looking up 'name' with the given scope, or
1758+
cell: The cell corresponding to looking up 'name' with the given mode, or
17331759
None if it's not found.
17341760
var_frame: The frame it should be set to or deleted from.
17351761
"""
17361762
if which_scopes == scope_e.Dynamic:
17371763
for i in xrange(len(self.var_stack) - 1, -1, -1):
17381764
var_frame = self.var_stack[i]
1739-
cell, result_frame = _FrameLookup(var_frame, name, bool(flags & YshDecl))
1765+
cell, result_frame = _FrameLookup(var_frame, name, False)
17401766
if cell:
17411767
return cell, result_frame
17421768
return None, self.var_stack[0] # set in global var_frame
17431769

17441770
if which_scopes == scope_e.LocalOnly:
1745-
# Stack:
1746-
# CommandEvaluator::_DoVarDecl
1747-
# Mem::SetNamed - do we need a flag for 'var'?
1748-
# Mem::_ResolveNameOrRef
1749-
# Mem::_ResolveNameOnly
1750-
#raise AssertionError()
1751-
17521771
var_frame = self.var_stack[-1]
1753-
cell, result_frame = _FrameLookup(var_frame, name, bool(flags & YshDecl))
1772+
cell, result_frame = _FrameLookup(var_frame, name, False)
17541773
if cell:
17551774
return cell, result_frame
17561775
return None, var_frame
17571776

17581777
if which_scopes == scope_e.GlobalOnly:
17591778
var_frame = self.var_stack[0]
1760-
cell, result_frame = _FrameLookup(var_frame, name, bool(flags & YshDecl))
1779+
cell, result_frame = _FrameLookup(var_frame, name, False)
17611780
if cell:
17621781
return cell, result_frame
17631782

@@ -1766,13 +1785,13 @@ def _ResolveNameOnly(self, name, which_scopes, flags):
17661785
if which_scopes == scope_e.LocalOrGlobal:
17671786
# Local
17681787
var_frame = self.var_stack[-1]
1769-
cell, result_frame = _FrameLookup(var_frame, name, bool(flags & YshDecl))
1788+
cell, result_frame = _FrameLookup(var_frame, name, False)
17701789
if cell:
17711790
return cell, result_frame
17721791

17731792
# Global
17741793
var_frame = self.var_stack[0]
1775-
cell, result_frame = _FrameLookup(var_frame, name, bool(flags & YshDecl))
1794+
cell, result_frame = _FrameLookup(var_frame, name, False)
17761795
if cell:
17771796
return cell, result_frame
17781797

@@ -1784,15 +1803,14 @@ def _ResolveNameOrRef(
17841803
self,
17851804
name, # type: str
17861805
which_scopes, # type: scope_t
1787-
flags, # type: int
17881806
ref_trail=None, # type: Optional[List[str]]
17891807
):
17901808
# type: (...) -> Tuple[Optional[Cell], Dict[str, Cell], str]
1791-
"""Look up a cell and frame, but respect the nameref flag.
1809+
"""Look up a cell and namespace, but respect the nameref flag.
17921810
17931811
Resolving namerefs does RECURSIVE calls.
17941812
"""
1795-
cell, var_frame = self._ResolveNameOnly(name, which_scopes, flags)
1813+
cell, var_frame = self._ResolveNameOnly(name, which_scopes)
17961814

17971815
if cell is None or not cell.nameref:
17981816
return cell, var_frame, name # not a nameref
@@ -1840,7 +1858,7 @@ def _ResolveNameOrRef(
18401858

18411859
# 'declare -n' uses dynamic scope.
18421860
cell, var_frame, cell_name = self._ResolveNameOrRef(
1843-
new_name, scope_e.Dynamic, flags, ref_trail=ref_trail)
1861+
new_name, scope_e.Dynamic, ref_trail=ref_trail)
18441862
return cell, var_frame, cell_name
18451863

18461864
def IsBashAssoc(self, name):
@@ -1850,7 +1868,7 @@ def IsBashAssoc(self, name):
18501868
We need to know this to evaluate the index expression properly
18511869
-- should it be coerced to an integer or not?
18521870
"""
1853-
cell, _, _ = self._ResolveNameOrRef(name, self.ScopesForReading(), 0)
1871+
cell, _, _ = self._ResolveNameOrRef(name, self.ScopesForReading())
18541872
# foo=([key]=value)
18551873
return cell is not None and cell.val.tag() == value_e.BashAssoc
18561874

@@ -1903,8 +1921,6 @@ def SetLocalName(self, lval, val):
19031921

19041922
# Equivalent to
19051923
# self._ResolveNameOnly(lval.name, scope_e.LocalOnly)
1906-
1907-
# Note: doesn't use _FrameLookup
19081924
var_frame = self.var_stack[-1]
19091925
cell = var_frame.get(lval.name)
19101926

@@ -1917,11 +1933,55 @@ def SetLocalName(self, lval, val):
19171933
cell = Cell(False, False, False, val)
19181934
var_frame[lval.name] = cell
19191935

1920-
def SetNamed(self, lval, val, which_scopes, flags=0):
1936+
def SetNamedYsh(self, lval, val, which_scopes, flags=0):
19211937
# type: (LeftName, value_t, scope_t, int) -> None
1938+
"""Set the value of a named variable, for YSH.
1939+
1940+
This has simpler logic than Mem.SetNamed(). It also handles 'const'
1941+
and 'var' in closures, via the YshDecl flag.
1942+
"""
1943+
# Scopes to handle: LocalOnly (setvar), GlobalOnly (setglobal)
1944+
assert which_scopes in (scope_e.LocalOnly,
1945+
scope_e.GlobalOnly), which_scopes
1946+
1947+
# Flags to handle: SetReadOnly (const), YshDecl (const, var)
1948+
assert flags & ClearReadOnly == 0, flags
1949+
1950+
assert flags & SetExport == 0, flags
1951+
assert flags & ClearExport == 0, flags
1952+
1953+
assert flags & SetNameref == 0, flags
1954+
assert flags & ClearNameref == 0, flags
1955+
1956+
cell, var_frame = self._ResolveNameForYshMutation(
1957+
lval.name, which_scopes, bool(flags & YshDecl))
1958+
1959+
if cell:
1960+
# Note: this DYNAMIC check means we can't have 'const' in a loop.
1961+
# But that's true for 'readonly' too, and hoisting it makes more
1962+
# sense anyway.
1963+
if cell.readonly:
1964+
e_die("Can't assign to readonly value %r" % lval.name,
1965+
lval.blame_loc)
1966+
cell.val = val # CHANGE VAL
1967+
1968+
if flags & SetReadOnly:
1969+
cell.readonly = True
1970+
1971+
else:
1972+
cell = Cell(False, bool(flags & SetReadOnly), False, val)
1973+
var_frame[lval.name] = cell
1974+
1975+
# Maintain invariant that only strings and undefined cells can be
1976+
# exported.
1977+
assert cell.val is not None, cell
1978+
1979+
def SetNamed(self, lval, val, which_scopes, flags=0):
1980+
# type: (LeftName, Optional[value_t], scope_t, int) -> None
1981+
"""Set the value of a named variable."""
19221982
if flags & SetNameref or flags & ClearNameref:
19231983
# declare -n ref=x # refers to the ref itself
1924-
cell, var_frame = self._ResolveNameOnly(lval.name, which_scopes, flags)
1984+
cell, var_frame = self._ResolveNameOnly(lval.name, which_scopes)
19251985
cell_name = lval.name
19261986
else:
19271987
# ref=x # mutates THROUGH the reference
@@ -1933,7 +1993,7 @@ def SetNamed(self, lval, val, which_scopes, flags=0):
19331993
# 3. Turn BracedVarSub into an sh_lvalue, and call
19341994
# self.unsafe_arith.SetValue() wrapper with ref_trail
19351995
cell, var_frame, cell_name = self._ResolveNameOrRef(
1936-
lval.name, which_scopes, flags)
1996+
lval.name, which_scopes)
19371997

19381998
if cell:
19391999
# Clear before checking readonly bit.
@@ -2036,7 +2096,7 @@ def SetValue(self, lval, val, which_scopes, flags=0):
20362096
# Undef, which then turns into an INDEXED array. (Undef means that set
20372097
# -o nounset fails.)
20382098
cell, var_frame, _ = self._ResolveNameOrRef(
2039-
lval.name, which_scopes, flags)
2099+
lval.name, which_scopes)
20402100
if not cell:
20412101
self._BindNewArrayWithEntry(var_frame, lval, rval, flags,
20422102
left_loc)
@@ -2098,7 +2158,7 @@ def SetValue(self, lval, val, which_scopes, flags=0):
20982158
left_loc = lval.blame_loc
20992159

21002160
cell, var_frame, _ = self._ResolveNameOrRef(
2101-
lval.name, which_scopes, flags)
2161+
lval.name, which_scopes)
21022162
if cell.readonly:
21032163
e_die("Can't assign to readonly associative array",
21042164
left_loc)
@@ -2310,7 +2370,7 @@ def GetValue(self, name, which_scopes=scope_e.Shopt):
23102370
# 1. Call self.unsafe_arith.ParseVarRef() -> BracedVarSub
23112371
# 2. Call self.unsafe_arith.GetNameref(bvs_part), and get a value_t
23122372
# We still need a ref_trail to detect cycles.
2313-
cell, _, _ = self._ResolveNameOrRef(name, which_scopes, 0)
2373+
cell, _, _ = self._ResolveNameOrRef(name, which_scopes)
23142374
if cell:
23152375
return cell.val
23162376

@@ -2336,7 +2396,7 @@ def GetCell(self, name, which_scopes=scope_e.Shopt):
23362396
if which_scopes == scope_e.Shopt:
23372397
which_scopes = self.ScopesForReading()
23382398

2339-
cell, _ = self._ResolveNameOnly(name, which_scopes, 0)
2399+
cell, _ = self._ResolveNameOnly(name, which_scopes)
23402400
return cell
23412401

23422402
def GetCellDeref(self, name, which_scopes=scope_e.Shopt):
@@ -2347,7 +2407,7 @@ def GetCellDeref(self, name, which_scopes=scope_e.Shopt):
23472407
if which_scopes == scope_e.Shopt:
23482408
which_scopes = self.ScopesForReading()
23492409

2350-
cell, _, _ = self._ResolveNameOrRef(name, which_scopes, 0)
2410+
cell, _, _ = self._ResolveNameOrRef(name, which_scopes)
23512411
return cell
23522412

23532413
def Unset(self, lval, which_scopes):
@@ -2376,7 +2436,7 @@ def Unset(self, lval, which_scopes):
23762436
which_scopes = self.ScopesForWriting()
23772437

23782438
cell, var_frame, cell_name = self._ResolveNameOrRef(
2379-
var_name, which_scopes, 0)
2439+
var_name, which_scopes)
23802440
if not cell:
23812441
return False # 'unset' builtin falls back on functions
23822442
if cell.readonly:
@@ -2460,7 +2520,7 @@ def ClearFlag(self, name, flag):
24602520
We don't use SetValue() because even if rval is None, it will make an
24612521
Undef value in a scope.
24622522
"""
2463-
cell, var_frame = self._ResolveNameOnly(name, self.ScopesForReading(), 0)
2523+
cell, var_frame = self._ResolveNameOnly(name, self.ScopesForReading())
24642524
if cell:
24652525
if flag & ClearExport:
24662526
cell.exported = False

demo/survey-closure.sh

+45
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,51 @@ assert nested(3) == 6, "Test 2 failed"
254254
'
255255
}
256256

257+
js-mutate-var-let() {
258+
nodejs -e '
259+
function outer() {
260+
var x = "X_outer";
261+
var y = "Y_outer";
262+
263+
function inner() {
264+
var x = "X_inner";
265+
y = "Y_inner";
266+
267+
console.log(`inner: x=${x} y=${y}`);
268+
}
269+
270+
inner();
271+
272+
console.log(`outer: x=${x} y=${y}`);
273+
}
274+
275+
outer();
276+
'
277+
278+
echo
279+
280+
# Does not change eanything
281+
nodejs -e '
282+
function outer() {
283+
let x = "X_outer";
284+
let y = "Y_outer";
285+
286+
function inner() {
287+
let x = "X_inner";
288+
y = "Y_inner";
289+
290+
console.log(`let inner: x=${x} y=${y}`);
291+
}
292+
293+
inner();
294+
295+
console.log(`let outer: x=${x} y=${y}`);
296+
}
297+
298+
outer();
299+
'
300+
}
301+
257302
value-or-var() {
258303
# Good point from HN thread, this doesn't work
259304
#

0 commit comments

Comments
 (0)