Skip to content

Commit ccdcfcf

Browse files
author
Andy C
committed
[core] Skeleton for purity
It will be used in at least 3 places: --eval-pure when calling any func the free functions eval() and evalToDict(). We might also want evalExpr()
1 parent 834c184 commit ccdcfcf

File tree

6 files changed

+148
-24
lines changed

6 files changed

+148
-24
lines changed

core/alloc.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""
2-
alloc.py - strategies for managing SourceLine and Token
3-
2+
alloc.py - Arena manages SourceLine and Token instances (could rename)
43
"""
54

65
from _devbuild.gen.syntax_asdl import source_t, Token, SourceLine, loc
@@ -19,7 +18,9 @@ def SnipCodeBlock(left, right, lines):
1918
2019
Meaning { } are not included.
2120
22-
Used for Hay evaluation. Similar to SnipCodeString().
21+
Used for Command.sourceCode() and Hay evaluation. Similar to SnipCodeString().
22+
23+
TODO: This algorithm is wrong when re-parsing occurs, e.g. bacticks, aliases, a[i++]=1.
2324
"""
2425
pieces = [] # type: List[str]
2526

core/executor.py

+21
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,27 @@ def MaybeWaitOnProcessSubs(self, waiter, status_array):
209209
]
210210

211211

212+
class PureExecutor(vm._Executor):
213+
214+
def __init__(
215+
self,
216+
mem, # type: state.Mem
217+
exec_opts, # type: optview.Exec
218+
mutable_opts, # type: state.MutableOpts
219+
procs, # type: state.Procs
220+
hay_state, # type: hay_ysh.HayState
221+
builtins, # type: Dict[int, vm._Builtin]
222+
tracer, # type: dev.Tracer
223+
errfmt # type: ui.ErrorFormatter
224+
):
225+
pass
226+
227+
def RunSimpleCommand(self, cmd_val, cmd_st, run_flags):
228+
# type: (cmd_value.Argv, CommandStatus, int) -> int
229+
log('RunSimpleCommand')
230+
return 0
231+
232+
212233
class ShellExecutor(vm._Executor):
213234
"""An executor combined with the OSH language evaluators in osh/ to create
214235
a shell interpreter."""

core/shell.py

+13-9
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,9 @@ def Main(
499499
ext_prog, waiter, tracer, job_control,
500500
job_list, fd_state, trap_state, errfmt)
501501

502+
pure_ex = executor.PureExecutor(mem, exec_opts, mutable_opts, procs,
503+
hay_state, builtins, tracer, errfmt)
504+
502505
arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, mutable_opts,
503506
parse_ctx, errfmt)
504507
bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, mutable_opts,
@@ -961,15 +964,16 @@ def Main(
961964
# Note that --eval
962965

963966
for path, is_pure in attrs.eval_flags:
964-
# TODO: respect is_pure
965-
try:
966-
ok, status = main_loop.EvalFile(path, fd_state, parse_ctx, cmd_ev,
967-
lang)
968-
except util.UserExit as e:
969-
# Doesn't seem like we need this, and verbose_errexit isn't the right option
970-
#if exec_opts.verbose_errexit():
971-
# print-stderr('oils: --eval exit')
972-
return e.status
967+
ex = pure_ex if is_pure else None
968+
with vm.ctx_MaybePure(ex, cmd_ev, word_ev, expr_ev):
969+
try:
970+
ok, status = main_loop.EvalFile(path, fd_state, parse_ctx,
971+
cmd_ev, lang)
972+
except util.UserExit as e:
973+
# Doesn't seem like we need this, and verbose_errexit isn't the right option
974+
#if exec_opts.verbose_errexit():
975+
# print-stderr('oils: --eval exit')
976+
return e.status
973977

974978
# I/O error opening file, parse error. Message was # already printed.
975979
if not ok:

core/vm.py

+58-10
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
from core import pyos
1111
from mycpp.mylib import log
1212

13-
from typing import List, Tuple, Any, TYPE_CHECKING
13+
from typing import List, Tuple, Optional, Any, TYPE_CHECKING
1414
if TYPE_CHECKING:
1515
from _devbuild.gen.runtime_asdl import cmd_value, RedirValue
1616
from _devbuild.gen.syntax_asdl import (command, command_t, CommandSub)
1717
from frontend import typed_args
1818
from osh import sh_expr_eval
1919
from osh.sh_expr_eval import ArithEvaluator
2020
from osh.sh_expr_eval import BoolEvaluator
21-
from ysh.expr_eval import ExprEvaluator
22-
from osh.word_eval import NormalWordEvaluator
23-
from osh.cmd_eval import CommandEvaluator
21+
from ysh import expr_eval
22+
from osh import word_eval
23+
from osh import cmd_eval
2424
from osh import prompt
2525
from core import dev
2626
from core import state
@@ -109,7 +109,7 @@ def __repr__(self):
109109

110110

111111
def InitUnsafeArith(mem, word_ev, unsafe_arith):
112-
# type: (state.Mem, NormalWordEvaluator, sh_expr_eval.UnsafeArith) -> None
112+
# type: (state.Mem, word_eval.NormalWordEvaluator, sh_expr_eval.UnsafeArith) -> None
113113
"""Wire up circular dependencies for UnsafeArith."""
114114
mem.unsafe_arith = unsafe_arith # for 'declare -n' nameref expansion of a[i]
115115
word_ev.unsafe_arith = unsafe_arith # for ${!ref} expansion of a[i]
@@ -118,10 +118,10 @@ def InitUnsafeArith(mem, word_ev, unsafe_arith):
118118
def InitCircularDeps(
119119
arith_ev, # type: ArithEvaluator
120120
bool_ev, # type: BoolEvaluator
121-
expr_ev, # type: ExprEvaluator
122-
word_ev, # type: NormalWordEvaluator
123-
cmd_ev, # type: CommandEvaluator
124-
shell_ex, # type: _Executor
121+
expr_ev, # type: expr_eval.ExprEvaluator
122+
word_ev, # type: word_eval.NormalWordEvaluator
123+
cmd_ev, # type: cmd_eval.CommandEvaluator
124+
shell_ex, # type: _Executor
125125
prompt_ev, # type: prompt.Evaluator
126126
global_io, # type: Obj
127127
tracer, # type: dev.Tracer
@@ -171,7 +171,7 @@ class _Executor(object):
171171

172172
def __init__(self):
173173
# type: () -> None
174-
self.cmd_ev = None # type: CommandEvaluator
174+
self.cmd_ev = None # type: cmd_eval.CommandEvaluator
175175

176176
def CheckCircularDeps(self):
177177
# type: () -> None
@@ -274,6 +274,54 @@ def Call(self, args):
274274
raise NotImplementedError()
275275

276276

277+
class ctx_MaybePure(object):
278+
"""Enforce purity of the shell interpreter
279+
280+
Use this for:
281+
282+
--eval-pure
283+
func - pure functions
284+
eval() evalToDict() - builtin pure functions, not methods
285+
"""
286+
287+
def __init__(
288+
self,
289+
pure_ex, # type: Optional[_Executor]
290+
cmd_ev, # type: cmd_eval.CommandEvaluator
291+
word_ev, # type: word_eval.NormalWordEvaluator
292+
expr_ev # type: expr_eval.ExprEvaluator
293+
):
294+
# type: (...) -> None
295+
self.pure_ex = pure_ex
296+
if not pure_ex:
297+
return # do nothing
298+
299+
self.saved = cmd_ev.shell_ex
300+
assert self.saved is word_ev.shell_ex
301+
assert self.saved is expr_ev.shell_ex
302+
303+
cmd_ev.shell_ex = pure_ex
304+
word_ev.shell_ex = pure_ex
305+
expr_ev.shell_ex = pure_ex
306+
307+
self.cmd_ev = cmd_ev
308+
self.word_ev = word_ev
309+
self.expr_ev = expr_ev
310+
311+
def __enter__(self):
312+
# type: () -> None
313+
pass
314+
315+
def __exit__(self, type, value, traceback):
316+
# type: (Any, Any, Any) -> None
317+
if not self.pure_ex:
318+
return # do nothing
319+
320+
self.cmd_ev.shell_ex = self.saved
321+
self.word_ev.shell_ex = self.saved
322+
self.expr_ev.shell_ex = self.saved
323+
324+
277325
class ctx_Redirect(object):
278326
"""For closing files.
279327

spec/ysh-method-other.test.sh

+16
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ proc p ( ; ; ; block) {
8080

8181
# Re-parsing messes this up
8282
echo $[src.code_str]
83+
echo
8384
}
8485

8586
shopt --set parse_backticks
@@ -88,5 +89,20 @@ p {
8889
echo "greeting `echo hi`"
8990
}
9091

92+
shopt --set expand_aliases
93+
94+
95+
alias e='echo hi'
96+
97+
p {
98+
e foo
99+
}
100+
101+
shopt --set parse_sh_arith
102+
103+
p {
104+
a[i++]=y
105+
}
106+
91107
## STDOUT:
92108
## END

spec/ysh-usage.test.sh

+36-2
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,47 @@ flag -c: P Q
5151

5252
## END
5353

54-
#### ysh --eval-pure
54+
#### ysh --eval-pure can evaluate funcs and procs
5555

56-
echo TODO
56+
echo >pure.ysh '
57+
proc my-proc { echo "my-proc" }
58+
func myFunc() { return ("myFunc") }
59+
'
60+
61+
$[ENV.SH] --eval-pure pure.ysh -c 'my-proc; echo $[myFunc()]'
5762

5863
## STDOUT:
64+
my-proc
65+
myFunc
5966
## END
6067

68+
#### ysh --eval-pure can't run impure
69+
70+
echo >pure.ysh '
71+
proc my-proc { echo "my-proc" }
72+
func myFunc() { return ("myFunc") }
73+
'
74+
75+
echo >impure.ysh 'my-proc; echo $[myFunc()]'
76+
77+
# There should be an error on 'echo' - it can't even write to stdout?
78+
#
79+
# Or definitely ls
80+
#
81+
# Can this print to stdout?
82+
#
83+
# call myFunc()
84+
#
85+
# Right now we can do this:
86+
# { call myFunc() } | wc -l
87+
88+
$[ENV.SH] --eval-pure pure.ysh --eval-pure impure.ysh -c ''
89+
90+
## status: 1
91+
## STDOUT:
92+
## END
93+
94+
6195
#### ysh --eval cannot load file
6296

6397
$[ENV.SH] --eval nonexistent.ysh -c 'echo flag -c'

0 commit comments

Comments
 (0)