Skip to content

Commit 955b42e

Browse files
author
Andy C
committed
[ysh] io->evalToDict() respects dollar0, pos_args, vars
[doc/ref] Update docs for io->eval() and others Fix the name conflict between the eval builtin, and io->eval()
1 parent 517fe76 commit 955b42e

7 files changed

+175
-75
lines changed

builtin/method_io.py

+38-36
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ def _CheckPosArgs(pos_args_raw, blame_loc):
4141

4242

4343
class EvalExpr(vm._Callable):
44+
"""io->evalExpr(ex) evaluates an expression
45+
46+
Notes compared with io->eval(cmd):
47+
- there is no evalToDict variant - doesn't make sense
48+
- Does it need in_captured_frame=true?
49+
- That is for "inline procs" like cd, but doesn't seem to be necessary
50+
for expressions. Unless we had mutations in expressions.
51+
"""
4452

4553
def __init__(self, expr_ev):
4654
# type: (expr_eval.ExprEvaluator) -> None
@@ -71,21 +79,23 @@ def Call(self, rd):
7179
return result
7280

7381

74-
def _PrintFrame(prefix, frame):
75-
# type: (str, Dict[str, Cell]) -> None
76-
print('%s %s' % (prefix, ' '.join(frame.keys())))
82+
if 0:
83+
84+
def _PrintFrame(prefix, frame):
85+
# type: (str, Dict[str, Cell]) -> None
86+
print('%s %s' % (prefix, ' '.join(frame.keys())))
7787

78-
rear = frame.get('__E__')
79-
if rear:
80-
rear_val = rear.val
81-
if rear_val.tag() == value_e.Frame:
82-
r = cast(value.Frame, rear_val)
83-
_PrintFrame('--> ' + prefix, r.frame)
88+
rear = frame.get('__E__')
89+
if rear:
90+
rear_val = rear.val
91+
if rear_val.tag() == value_e.Frame:
92+
r = cast(value.Frame, rear_val)
93+
_PrintFrame('--> ' + prefix, r.frame)
8494

8595

8696
class EvalInFrame(vm._Callable):
8797
"""
88-
For making "inline procs"
98+
DEPRECATED, replaced by eval(b, in_captured_frame=true)
8999
"""
90100

91101
def __init__(self, mem, cmd_ev):
@@ -140,46 +150,38 @@ def Call(self, rd):
140150
unused = rd.PosValue()
141151
bound = rd.PosCommand()
142152

143-
frag = typed_args.GetCommandFrag(bound)
144-
145153
dollar0 = rd.NamedStr("dollar0", None)
146154
pos_args_raw = rd.NamedList("pos_args", None)
147155
vars_ = rd.NamedDict("vars", None)
148156
in_captured_frame = rd.NamedBool("in_captured_frame", None)
149157
rd.Done()
150158

159+
frag = typed_args.GetCommandFrag(bound)
160+
151161
pos_args = _CheckPosArgs(pos_args_raw, rd.LeftParenToken())
152162

153-
# TODO: Add debug_frame here, with ctx_Eval or ctx_EvalDebugFrame
154163
with state.ctx_TokenDebugFrame(self.mem, rd.LeftParenToken()):
155164
if self.which == EVAL_NULL:
156-
# _PrintFrame('[captured]', captured_frame)
157-
with state.ctx_EnclosedFrame(self.mem,
158-
bound.captured_frame,
159-
bound.module_frame,
160-
None,
161-
inside=in_captured_frame):
162-
# _PrintFrame('[new]', self.cmd_ev.mem.var_stack[-1])
163-
with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
164-
unused_status = self.cmd_ev.EvalCommandFrag(frag)
165-
return value.Null
166-
165+
bindings = None # type: Optional[Dict[str, value_t]]
167166
elif self.which == EVAL_DICT:
168-
# TODO: dollar0, pos_args, vars_ not supported
169-
#
170-
# Does ctx_EnclosedFrame has different scoping rules? For "vars"?
171-
172-
bindings = NewDict() # type: Dict[str, value_t]
173-
with state.ctx_EnclosedFrame(self.mem,
174-
bound.captured_frame,
175-
bound.module_frame,
176-
bindings,
177-
inside=in_captured_frame):
167+
bindings = NewDict()
168+
else:
169+
raise AssertionError()
170+
171+
# _PrintFrame('[captured]', captured_frame)
172+
with state.ctx_EnclosedFrame(self.mem,
173+
bound.captured_frame,
174+
bound.module_frame,
175+
bindings,
176+
inside=in_captured_frame):
177+
# _PrintFrame('[new]', self.cmd_ev.mem.var_stack[-1])
178+
with state.ctx_Eval(self.mem, dollar0, pos_args, vars_):
178179
unused_status = self.cmd_ev.EvalCommandFrag(frag)
179-
return value.Dict(bindings)
180180

181+
if self.which == EVAL_NULL:
182+
return value.Null
181183
else:
182-
raise AssertionError()
184+
return value.Dict(bindings)
183185

184186

185187
class CaptureStdout(vm._Callable):

doc/ref/chap-builtin-cmd.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ and OSH.
859859
[cat-em]: chap-front-end.html#cat-em
860860

861861

862-
### eval
862+
### cmd/eval
863863

864864
eval ARG+
865865

doc/ref/chap-index.md

+10
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ The name `clear` can refer to:
3939
[List/clear]: chap-type-method.html#List/clear
4040
[Dict/clear]: chap-type-method.html#Dict/clear
4141

42+
### eval
43+
44+
The name `eval` can refer to:
45+
46+
- The [eval builtin][cmd/eval]
47+
- The [io method eval][io/eval]
48+
49+
[cmd/eval]: chap-builtin-cmd.html#cmd/eval
50+
[io/eval]: chap-type-method.html#io/eval
51+
4252
### false
4353

4454
The name `false` can refer to:

doc/ref/chap-type-method.md

+38-28
Original file line numberDiff line numberDiff line change
@@ -709,10 +709,6 @@ The `Command` value is bound to a stack frame. This frame will be pushed as an
709709

710710
[block-arg]: chap-cmd-lang.html#block-arg
711711

712-
### CommandFrag
713-
714-
A command that's not bound to a stack frame.
715-
716712
### Expr
717713

718714
A value of type `Expr` represents an unevaluated expression. There are **three**
@@ -750,22 +746,11 @@ The `Expr` value is bound to a stack frame. This frame will be pushed as an
750746

751747
[expr-literal]: chap-expr-lang.html#expr-lit
752748

753-
<!--
754-
755-
### ExprFrag
756-
757-
An expression command that's not bound to a stack frame.
758-
759-
(TODO)
760-
761-
-->
762-
763749
### Frame
764750

765-
A value that represents a stack frame. It can be bound to a `CommandFrag`,
766-
producing a `Command`.
751+
A value that represents a stack frame.
767752

768-
Likewise, it can be found to a `ExprFrag`, producing an `Expr`.
753+
You can turn it into a Dict with `dict(myframe)`.
769754

770755
### DebugFrame
771756

@@ -850,19 +835,22 @@ Note that these expressions that have effects:
850835
- `^[ myplace->setValue(42) ]` - memory operation
851836
- `^[ $(echo 42 > hi) ]` - I/O operation
852837

853-
### eval()
838+
### io/eval()
854839

855-
Evaluate a command, and return `null`.
840+
Given a `Command` value (e.g. a block argument), execute it, and return `null`.
856841

857842
var cmd = ^(echo hi)
858-
call io->eval(cmd)
843+
call io->eval(cmd) # => hi
859844

860-
It's similar to the `eval` builtin, and is meant to be used in pure functions.
845+
This method is more principled and flexible than shell's [eval][] builtin.
846+
It's especially useful in pure functions.
847+
848+
[eval]: chap-builtin-cmd.html#cmd/eval
861849

862850
It accepts optional args that let you control name binding:
863851

864-
- `pos_args` for `$1 $2 $3`
865852
- `dollar0` for `$0`
853+
- `pos_args` for `$1 $2 $3`
866854
- `vars` for named variables
867855

868856
Example:
@@ -871,6 +859,25 @@ Example:
871859
call io->eval(cmd, dollar0="z", pos_args=['one'], vars={x: "x"})
872860
# => zero z, one one, named x
873861

862+
Scoping rules:
863+
864+
- The frame that contains the `Command`, e.g. `^(echo hi)` or `p { echo hi }`,
865+
is called the *captured* frame.
866+
- Normally, a `Command` is evaluated in a new stack frame, which "encloses" the
867+
captured frame. That is, a `Command` is a *closure*.
868+
869+
The `in_captured_frame` argument changes this behavior:
870+
871+
call io->eval(cmd, in_captured_frame=true)
872+
873+
In this case, the captured frame becomes the local frame. It's useful for
874+
creating procs that behave like builtins:
875+
876+
my-cd /tmp { # my-cd is a proc, which pushes a new stack frame
877+
var listing = $(ls) # This variable is created in the captured frame
878+
}
879+
echo $listing # It's still visible after the proc returns
880+
874881
<!--
875882
TODO: document in_captured_frame=true
876883
-->
@@ -880,10 +887,12 @@ TODO: document in_captured_frame=true
880887
The `evalToDict()` method is like the `eval()` method, but it returns a
881888
Dict of bindings.
882889

883-
It pushes a new "enclosed frame", and executes the given code.
890+
By default:
884891

885-
Then it copies the frame's bindings into a Dict, and returns it. Only the
886-
names that don't end with an underscore `_` are copied.
892+
1. It pushes a new stack frame, enclosing the captured frame
893+
1. Executes the given block of code (`Command`)
894+
1. Copies the top frame's bindings into a Dict, and returns it.
895+
- Only the names that don't end with an underscore `_` are copied.
887896

888897
Example:
889898

@@ -894,9 +903,10 @@ Example:
894903

895904
pp (d) # => {a: 42, b: 11}
896905

897-
<!--
898-
TODO: support pos_args
899-
-->
906+
It supports the same optional arguments as `io->eval()`:
907+
908+
- `dollar0 pos_args vars`
909+
- `in_captured_frame`
900910

901911
### captureStdout()
902912

doc/ref/toc-osh.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,21 @@ preserve_anchor_case: yes
5555
</h2>
5656

5757
```chapter-links-builtin-cmd
58-
[I/O] read echo printf
58+
[I/O] read echo printf
5959
readarray mapfile
60-
[Run Code] source . eval trap
60+
[Run Code] source . cmd/eval trap
6161
[Set Options] set shopt
62-
[Working Dir] cd pwd pushd popd dirs
63-
[Completion] complete compgen compopt compadjust compexport
62+
[Working Dir] cd pwd pushd popd dirs
63+
[Completion] complete compgen compopt compadjust compexport
6464
[Shell Process] exec X logout
65-
umask ulimit times
65+
umask ulimit times
6666
[Child Process] jobs wait
67-
fg X bg X kill X disown
67+
fg X bg X kill X disown
6868
[External] test [ getopts
6969
[Conditional] cmd/true cmd/false colon :
70-
[Introspection] help hash cmd/type X caller
70+
[Introspection] help hash cmd/type X caller
7171
[Word Lookup] command builtin
72-
[Interactive] alias unalias history X fc X bind
72+
[Interactive] alias unalias history X fc X bind
7373
X [Unsupported] enable
7474
```
7575

doc/ref/toc-ysh.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,11 @@ error handling, and more.
6666
[Objects] Obj __invoke__ new
6767
X __call__ __index__ X __str__
6868
[Reflection] Command
69-
CommandFrag
7069
Expr
7170
Frame
7271
DebugFrame toString()
7372
io stdin evalExpr()
74-
eval() evalToDict()
73+
io/eval() evalToDict()
7574
captureStdout()
7675
promptVal()
7776
X time() X strftime() X glob()

spec/ysh-builtin-eval.test.sh

+79
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,85 @@ p param
318318
(Dict) {"foo":42,"g":"-global","p":"-param","L":"-local"}
319319
## END
320320

321+
#### io->evalToDict() with dollar0, pos_args, vars
322+
323+
proc Dict ( ; out; ; block) {
324+
var d = io->evalToDict(block, dollar0='zero', pos_args=:|a b c|,
325+
vars={X: 'X', _Y: '_Y'})
326+
call out->setValue(d)
327+
}
328+
329+
var global = 'global'
330+
331+
Dict (&d) {
332+
foo = global
333+
z = $0
334+
one = $1
335+
two = ${2}
336+
three = "+$3"
337+
# Note: X does NOT appear in the output, ctx_Eval makes it work.
338+
x = X
339+
y = _Y
340+
}
341+
342+
json write (d)
343+
344+
## STDOUT:
345+
{
346+
"foo": "global",
347+
"z": "zero",
348+
"one": "a",
349+
"two": "b",
350+
"three": "+c",
351+
"x": "X",
352+
"y": "_Y"
353+
}
354+
## END
355+
356+
#### io->evalToDict() with in_captured_frame=true
357+
358+
proc Dict ( ; out; ; block) {
359+
var d = io->evalToDict(block, dollar0='zero', pos_args=:|a b c|,
360+
vars={X: 'X', _Y: '_Y'}, in_captured_frame=true)
361+
call out->setValue(d)
362+
}
363+
364+
var global = 'global'
365+
366+
func makeDict() {
367+
var var_in_p_frame = 'p'
368+
369+
Dict (&d) {
370+
foo = global
371+
z = $0
372+
one = $1
373+
two = ${2}
374+
three = "+$3"
375+
# Note: X does NOT appear in the output, ctx_Eval makes it work.
376+
x = X
377+
y = _Y
378+
}
379+
380+
return (d)
381+
}
382+
383+
var d = makeDict()
384+
json write (d)
385+
386+
## STDOUT:
387+
{
388+
"var_in_p_frame": "p",
389+
"foo": "global",
390+
"z": "zero",
391+
"one": "a",
392+
"two": "b",
393+
"three": "+c",
394+
"x": "X",
395+
"y": "_Y"
396+
}
397+
## END
398+
399+
321400
#### parseCommand then io->evalToDict() - in global scope
322401

323402
var g = 'global'

0 commit comments

Comments
 (0)