Skip to content

Commit efe9ee7

Browse files
author
Andy C
committed
[main] First pass of $sh --eval flag
Still need: - multiple --eval flags - the --eval-pure flag - should fail after parse error, unlike --rcfile
1 parent aeed2b2 commit efe9ee7

File tree

5 files changed

+194
-16
lines changed

5 files changed

+194
-16
lines changed

core/main_loop.py

+56-9
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@
1212

1313
from _devbuild.gen import arg_types
1414
from _devbuild.gen.syntax_asdl import (command, command_t, parse_result,
15-
parse_result_e)
15+
parse_result_e, loc, source)
16+
from core import alloc
1617
from core import error
1718
from core import process
18-
from display import ui
19+
from core import pyutil
20+
from core import state
1921
from core import util
22+
from display import ui
2023
from frontend import reader
2124
from osh import cmd_eval
2225
from mycpp import mylib
@@ -28,9 +31,10 @@
2831
from typing import cast, Any, List, TYPE_CHECKING
2932
if TYPE_CHECKING:
3033
from core.comp_ui import _IDisplay
34+
from core import process
3135
from frontend import parse_lib
32-
from osh.cmd_parse import CommandParser
33-
from osh.cmd_eval import CommandEvaluator
36+
from osh import cmd_parse
37+
from osh import cmd_eval
3438
from osh.prompt import UserPlugin
3539

3640
_ = log
@@ -100,7 +104,7 @@ class Headless(object):
100104
"""Main loop for headless mode."""
101105

102106
def __init__(self, cmd_ev, parse_ctx, errfmt):
103-
# type: (CommandEvaluator, parse_lib.ParseContext, ui.ErrorFormatter) -> None
107+
# type: (cmd_eval.CommandEvaluator, parse_lib.ParseContext, ui.ErrorFormatter) -> None
104108
self.cmd_ev = cmd_ev
105109
self.parse_ctx = parse_ctx
106110
self.errfmt = errfmt
@@ -191,8 +195,8 @@ def _Loop(self):
191195

192196
def Interactive(
193197
flag, # type: arg_types.main
194-
cmd_ev, # type: CommandEvaluator
195-
c_parser, # type: CommandParser
198+
cmd_ev, # type: cmd_eval.CommandEvaluator
199+
c_parser, # type: cmd_parse.CommandParser
196200
display, # type: _IDisplay
197201
prompt_plugin, # type: UserPlugin
198202
waiter, # type: process.Waiter
@@ -321,7 +325,7 @@ def Interactive(
321325

322326

323327
def Batch(cmd_ev, c_parser, errfmt, cmd_flags=0):
324-
# type: (CommandEvaluator, CommandParser, ui.ErrorFormatter, int) -> int
328+
# type: (cmd_eval.CommandEvaluator, cmd_parse.CommandParser, ui.ErrorFormatter, int) -> int
325329
"""Loop for batch execution.
326330
327331
Returns:
@@ -387,7 +391,7 @@ def Batch(cmd_ev, c_parser, errfmt, cmd_flags=0):
387391

388392

389393
def ParseWholeFile(c_parser):
390-
# type: (CommandParser) -> command_t
394+
# type: (cmd_parse.CommandParser) -> command_t
391395
"""Parse an entire shell script.
392396
393397
This uses the same logic as Batch(). Used by:
@@ -411,3 +415,46 @@ def ParseWholeFile(c_parser):
411415
return children[0]
412416
else:
413417
return command.CommandList(children)
418+
419+
def EvalFile(fs_path, fd_state, parse_ctx, cmd_ev):
420+
# type: (str, process.FdState, parse_lib.ParseContext, cmd_eval.CommandEvaluator) -> bool
421+
"""Evaluate a disk file, for --eval --eval-pure
422+
423+
Copied and adapted from the 'source' builtin in builtin/meta_oils.py.
424+
425+
(Note that bind -x has to eval from a string, like Eval)
426+
427+
Raises:
428+
util.UserExit
429+
Returns:
430+
ok: whether processing should continue
431+
"""
432+
mem = cmd_ev.mem
433+
arena = cmd_ev.arena
434+
errfmt = cmd_ev.errfmt
435+
436+
# _LoadDiskFile
437+
blame_loc = loc.Missing
438+
try:
439+
f = fd_state.Open(fs_path)
440+
except (IOError, OSError) as e:
441+
errfmt.Print_(
442+
'Error reading %r: %s' % (fs_path, pyutil.strerror(e)),
443+
blame_loc=blame_loc)
444+
return False
445+
446+
line_reader = reader.FileLineReader(f, arena)
447+
c_parser = parse_ctx.MakeOshParser(line_reader)
448+
449+
# TODO:
450+
# - Improve error locations
451+
# - parse error should be fatal
452+
453+
with process.ctx_FileCloser(f):
454+
with state.ctx_ThisDir(mem, fs_path):
455+
src = source.MainFile(fs_path)
456+
with alloc.ctx_SourceCode(arena, src):
457+
# May raise util.UserExit
458+
unused = Batch(cmd_ev, c_parser, errfmt)
459+
460+
return True

core/shell.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,8 @@ def SourceStartupFile(
164164
rc_c_parser = parse_ctx.MakeOshParser(rc_line_reader)
165165

166166
with alloc.ctx_SourceCode(arena, source.MainFile(rc_path)):
167-
# TODO: handle status, e.g. 2 for ParseError
167+
# Note: bash keep going after parse error in startup file. Should we
168+
# have a strict mode for this?
168169
unused = main_loop.Batch(cmd_ev, rc_c_parser, errfmt)
169170

170171
f.close()
@@ -957,9 +958,23 @@ def Main(
957958
# First, process --eval flags. In interactive mode, this comes before --rcfile.
958959
# (It could be used for the headless shell. Although terminals have a bootstrap process.)
959960
# Note that --eval
961+
962+
# TODO: process these in a loop, in order
960963
if flag.eval is not None:
961-
raise AssertionError()
964+
try:
965+
ok = main_loop.EvalFile(flag.eval, fd_state, parse_ctx, cmd_ev)
966+
except util.UserExit as e:
967+
# TODO:
968+
# do we need this?
969+
# should there be a different shopt? Could be verbose in YSH
970+
if exec_opts.verbose_errexit():
971+
errfmt.StderrLine('oils: --eval exit')
972+
return e.status
973+
if not ok: # parse error or I/O error was already printed
974+
return 1
975+
962976
if flag.eval_pure is not None:
977+
# TODO: Same as above, except we restrict
963978
raise AssertionError('pure')
964979

965980
#

frontend/syntax.asdl

+6-4
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ module syntax
4646
| CFlag
4747
| Stdin(str comment)
4848

49-
# MainFile is for main.{osh,ysh}, as well as oshrc/yshrc. They're files
50-
# loaded directly by the shell.
49+
# MainFile is for main.{osh,ysh}, --eval oshrc/yshrc. They're files loaded
50+
# directly by the shell.
5151
| MainFile(str path)
5252
# A file loaded by 'source' or 'use'.
53-
# TODO: provide a chain of locations back to the sourced script! I guess
54-
# the debug_stack can do that too.
53+
# TODO: we probably don't need this location? The debug stack provides a
54+
# chain of locations back to the sourced script. Maybe we need to point to
55+
# a debug_frame instead?
56+
# It could be DiskFileShell and DiskFileUser, or just DiskFile.
5557
| OtherFile(str path, loc location)
5658

5759
# Code parsed from a word. (TODO: rename source.Word?)

spec/interactive.test.sh

+13
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,19 @@ RCFILE
3737
2
3838
## END
3939

40+
#### --rcfile with parse error - shell is executed anyway
41+
cat >$TMP/rcfile <<EOF
42+
echo RCFILE; ( echo
43+
EOF
44+
45+
$SH --rcfile $TMP/rcfile -i -c 'echo flag -c'
46+
echo status=$?
47+
48+
## STDOUT:
49+
flag -c
50+
status=0
51+
## END
52+
4053
#### interactive shell loads files in rcdir (when combined with -c)
4154

4255
$SH -c 'echo A'

spec/ysh-usage.test.sh

+102-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## our_shell: ysh
2+
## oils_failures_allowed: 2
23

3-
#### ysh usage
4+
#### ysh --location-str --location-start-line
45

56
set +o errexit
67

@@ -21,6 +22,106 @@ line 10
2122
-- [ stdin ]:11: Unexpected
2223
## END
2324

25+
#### ysh --eval
26+
27+
echo 'echo one --eval' >one.ysh
28+
$[ENV.SH] --eval one.ysh -c 'echo flag -c'
29+
echo
30+
31+
echo 'echo myscript' >myscript.sh
32+
$[ENV.SH] --eval one.ysh myscript.sh
33+
echo
34+
35+
# eval comes before oshrc
36+
echo 'echo oshrc' >oshrc
37+
$[ENV.SH] --rcfile oshrc --eval one.ysh -i -c 'echo flag -c'
38+
echo
39+
40+
exit
41+
42+
echo 'echo two --eval' >two.ysh
43+
44+
$[ENV.SH] --eval one.ysh --eval two.ysh -c 'echo flag -c'
45+
46+
47+
## STDOUT:
48+
one --eval
49+
flag -c
50+
51+
one --eval
52+
myscript
53+
54+
one --eval
55+
oshrc
56+
flag -c
57+
58+
## END
59+
60+
#### ysh --eval-pure
61+
62+
echo TODO
63+
64+
## STDOUT:
65+
## END
66+
67+
#### ysh --eval cannot load file
68+
69+
$[ENV.SH] --eval nonexistent.ysh -c 'echo flag -c'
70+
71+
## status: 1
72+
## STDOUT:
73+
## END
74+
75+
#### ysh --eval parse error
76+
77+
echo 'echo zz; ( echo' >bad.ysh
78+
79+
$[ENV.SH] --eval bad.ysh -c 'echo hi'
80+
81+
## STDOUT:
82+
## END
83+
84+
#### ysh --eval runtime error
85+
86+
echo 'echo flag --eval; false; echo bye' >bad.ysh
87+
88+
$[ENV.SH] --eval bad.ysh -c 'echo flag -c'
89+
90+
## STDOUT:
91+
flag --eval
92+
flag -c
93+
## END
94+
95+
#### ysh --eval exit status
96+
97+
echo 'echo hi; exit 99; echo bye' >e.ysh
98+
99+
$[ENV.SH] --eval e.ysh -c 'echo hi'
100+
101+
## status: 99
102+
## STDOUT:
103+
hi
104+
## END
105+
106+
#### ysh --eval respects _this_dir
107+
108+
#echo tmp=$[ENV.TMP]
109+
110+
var dir = "$[ENV.TMP]/code"
111+
mkdir -p $dir
112+
113+
echo 'echo one; source $_this_dir/two.ysh' > $dir/one.ysh
114+
echo 'echo two' > $dir/two.ysh
115+
116+
$[ENV.SH] --eval $dir/one.ysh -c 'echo flag -c'
117+
118+
## STDOUT:
119+
one
120+
two
121+
flag -c
122+
## END
123+
124+
24125
#### --debug-file
25126
var TMP = ENV.TMP
26127
$[ENV.SH] --debug-file $TMP/debug.txt -c 'true'

0 commit comments

Comments
 (0)