Skip to content

Commit fa068fe

Browse files
author
Andy C
committed
[ysh] Implement io->evalInFrame()
It has similar use cases to Tcl's uplevel. See doc/ref TODO: probably needs dollar0, pos_args, vars?
1 parent 6431d76 commit fa068fe

File tree

7 files changed

+102
-33
lines changed

7 files changed

+102
-33
lines changed

builtin/method_io.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,23 @@ def __init__(self, mem, cmd_ev):
9595

9696
def Call(self, rd):
9797
# type: (typed_args.Reader) -> value_t
98-
frag = rd.PosCommandFrag()
99-
frame = rd.PosFrame()
98+
unused = rd.PosValue()
99+
100+
#frag = rd.PosCommandFrag()
101+
cmd = rd.PosCommand()
102+
in_frame = rd.PosFrame()
100103

101-
# TODO: EvalCommandFrag()
104+
#cmd.captured_frame = frame
105+
frag = typed_args.GetCommandFrag(cmd)
106+
107+
# Note that 'cd' uses cmd_ev.EvalCommandFrag(), because a builtin does
108+
# NOT push a new stack frame. But a proc does. So we need
109+
# evalInFrame().
110+
111+
with state.ctx_TokenDebugFrame(self.mem, rd.LeftParenToken()):
112+
# Evaluate with the given frame at the TOP of the stack
113+
with state.ctx_EvalInFrame(self.mem, in_frame):
114+
unused_status = self.cmd_ev.EvalCommandFrag(frag)
102115

103116
return value.Null
104117

core/shell.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,14 @@ def Main(
954954
# Special case for testing
955955
mem.AddBuiltin('module-invoke', value.BuiltinProc(module_invoke))
956956

957+
# First, process --eval flags. In interactive mode, this comes before --rcfile.
958+
# (It could be used for the headless shell. Although terminals have a bootstrap process.)
959+
# Note that --eval
960+
if flag.eval:
961+
raise AssertionError()
962+
if flag.eval_pure:
963+
raise AssertionError('pure')
964+
957965
#
958966
# Is the shell interactive?
959967
#
@@ -1203,7 +1211,7 @@ def Main(
12031211
return 0
12041212

12051213
#
1206-
# Run a shell script
1214+
# Batch mode: shell script or -c
12071215
#
12081216

12091217
with state.ctx_ThisDir(mem, script_name):

core/state.py

+17
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,23 @@ def __exit__(self, type, value, traceback):
807807
self.mem.var_stack[0] = self.saved_globals
808808

809809

810+
class ctx_EvalInFrame(object):
811+
812+
def __init__(self, mem, frame):
813+
# type: (Mem, Dict[str, Cell]) -> None
814+
mem.var_stack.append(frame)
815+
816+
self.mem = mem
817+
818+
def __enter__(self):
819+
# type: () -> None
820+
pass
821+
822+
def __exit__(self, type, value, traceback):
823+
# type: (Any, Any, Any) -> None
824+
self.mem.var_stack.pop()
825+
826+
810827
class ctx_Temp(object):
811828
""" POSIX shell FOO=bar mycommand """
812829

doc/ref/chap-type-method.md

+31
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,37 @@ Example:
890890

891891
pp (d) # => {a: 42, b: 11}
892892

893+
### evalInFrame()
894+
895+
Evaluate a block in the given frame. Example:
896+
897+
proc my-cd (new_dir; ; ; block) {
898+
pushd $new_dir >/dev/null
899+
900+
# Get calling frame. (The top-most frame, this one, has index -1)
901+
var calling_frame = vm.getFrame(-2)
902+
903+
call io->evalInFrame(block, calling_frame)
904+
905+
popd >/dev/null
906+
}
907+
908+
var i = 42
909+
my-cd /tmp {
910+
var my_pwd = PWD
911+
var j = i + 1
912+
}
913+
914+
# In contrast to using io->eval(), vars in the block are still visible.
915+
# The block was evaluated in this frame.
916+
echo "$my_pwd" # => /tmp
917+
echo "$j" # => 43
918+
919+
Design note: this API has similar use cases as Tcl's [uplevel][].
920+
921+
[uplevel]: https://www.tcl-lang.org/man/tcl8.4/TclCmd/uplevel.htm
922+
923+
893924
### captureStdout()
894925

895926
Capture stdout of a command a string.

doc/ref/toc-ysh.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ error handling, and more.
7171
Frame
7272
DebugFrame toString()
7373
io stdin evalExpr()
74-
eval() evalToDict()
74+
eval() evalToDict() evalInFrame()
7575
captureStdout()
7676
promptVal()
7777
X time() X strftime() X glob()

frontend/flag_def.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -265,14 +265,22 @@ def _AddShellOptions(spec):
265265
MAIN_SPEC.LongFlag('--login') # login - currently no-op
266266
MAIN_SPEC.LongFlag('--headless') # accepts ECMD, etc.
267267

268+
# --eval is not processed in interactive mode
269+
# TODO: This should be a list
270+
MAIN_SPEC.LongFlag('--eval', args.String)
271+
272+
# TODO: relative order of --eval and --eval-pure should be preserved
273+
MAIN_SPEC.LongFlag('--eval-pure', args.String)
274+
268275
# TODO: -h too
269276
# the output format when passing -n
270277
MAIN_SPEC.LongFlag('--ast-format',
271278
['text', 'abbrev-text', 'none', '__perf', '__dumpdoc'],
272279
default='abbrev-text')
273280

274281
# Defines completion style.
275-
MAIN_SPEC.LongFlag('--completion-display', ['minimal', 'nice'], default='minimal')
282+
MAIN_SPEC.LongFlag('--completion-display', ['minimal', 'nice'],
283+
default='minimal')
276284
# TODO: Add option for YSH prompt style? RHS prompt?
277285

278286
MAIN_SPEC.LongFlag('--completion-demo')

spec/ysh-builtin-eval.test.sh

+19-27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# YSH specific features of eval
22

33
## our_shell: ysh
4-
## oils_failures_allowed: 4
4+
## oils_failures_allowed: 2
55

66
#### eval builtin does not take a literal block - can restore this later
77

@@ -575,66 +575,58 @@ inner2=z
575575

576576
#### io->evalInFrame() can express try, cd builtins
577577

578-
var frag = ^(echo $i)
579-
580578
proc my-cd (new_dir; ; ; block) {
581-
pushd $new_dir
579+
pushd $new_dir >/dev/null
582580

581+
# Get calling frame. (The top-most frame, this one, has index -1)
583582
var calling_frame = vm.getFrame(-2)
584583

585-
# could call this "unbound"? or unbind()? What about procs and funcs and
586-
# exprs?
587-
var frag = getCommandFrag(block)
588-
589-
call io->evalInFrame(frag, calling_frame)
584+
call io->evalInFrame(block, calling_frame)
590585

591-
popd
586+
popd >/dev/null
592587
}
593588

594589
var i = 42
595590
my-cd /tmp {
596591
echo $PWD
592+
var my_pwd = PWD
597593
var j = i + 1
598594
}
595+
echo "my_pwd=$my_pwd"
599596
echo "j = $j"
600597

601598
## STDOUT:
602-
x: i = 0, j = 2
603-
x: i = 1, j = 3
604-
x: i = 2, j = 4
599+
/tmp
600+
my_pwd=/tmp
601+
j = 43
605602
## END
606603

607604

608-
#### parseCommand(), io->evalInFrame(frag, frame) can behave like eval $mystr
609-
610-
# NO LONGER WORKS, but is this a feature rather than a bug?
605+
#### io->evalInFrame(frag, frame) can behave like eval $mystr
611606

612607
proc p2(code_str) {
613608
var mylocal = 42
609+
# mylocal is visible
614610
eval $code_str
615611
}
616612

617-
p2 'echo mylocal=$mylocal'
613+
p2 'echo "eval string mylocal=$mylocal"'
618614

619615
proc p (;;; block) {
620-
# To behave like eval $code_str, without variable capture:
621-
#
622-
# var frag = getCommandFrag(block)
623-
# var this_frame = vm.getFrame(-1)
624-
# call io->evalInFrame(frag, this_frame)
616+
var this_frame = vm.getFrame(-1)
625617

618+
# mylocal is visible
626619
var mylocal = 99
627-
call io->eval(block)
620+
call io->evalInFrame(block, this_frame)
628621
}
629622

630623
p {
631-
echo mylocal=$mylocal
624+
echo "evalInFrame mylocal=$mylocal"
632625
}
633626

634-
635627
## STDOUT:
636-
mylocal=42
637-
mylocal=99
628+
eval string mylocal=42
629+
evalInFrame mylocal=99
638630
## END
639631

640632
#### eval should have a sandboxed mode

0 commit comments

Comments
 (0)