def AddPure(b, mem, procs, mutable_opts, aliases, search_path, errfmt): # type: (Dict[int, vm._Builtin], state.Mem, Dict[str, command__ShFunction], state.MutableOpts, Dict[str, str], state.SearchPath, ui.ErrorFormatter) -> None b[builtin_i.set] = builtin_pure.Set(mutable_opts, mem) b[builtin_i.shopt] = builtin_pure.Shopt(mutable_opts) b[builtin_i.alias] = builtin_pure.Alias(aliases, errfmt) b[builtin_i.unalias] = builtin_pure.UnAlias(aliases, errfmt) b[builtin_i.hash] = builtin_pure.Hash(search_path) b[builtin_i.getopts] = builtin_pure.GetOpts(mem, errfmt) true_ = builtin_pure.Boolean(0) b[builtin_i.colon] = true_ # a "special" builtin b[builtin_i.true_] = true_ b[builtin_i.false_] = builtin_pure.Boolean(1) b[builtin_i.shift] = builtin_assign.Shift(mem) b[builtin_i.type] = builtin_meta.Type(procs, aliases, search_path, errfmt)
def ShellMain(lang, argv0, argv, login_shell): """Used by bin/osh and bin/oil. Args: lang: 'osh' or 'oil' argv0, argv: So we can also invoke bin/osh as 'oil.ovm osh'. Like busybox. login_shell: Was - on the front? """ # Differences between osh and oil: # - --help? I guess Oil has a SUPERSET of OSH options. # - oshrc vs oilrc # - the parser and executor # - Change the prompt in the interactive shell? assert lang in ('osh', 'oil'), lang arg_r = args.Reader(argv) try: opts = OSH_SPEC.Parse(arg_r) except args.UsageError as e: ui.Stderr('osh usage error: %s', e.msg) return 2 # NOTE: This has a side effect of deleting _OVM_* from the environment! # TODO: Thread this throughout the program, and get rid of the global # variable in core/util.py. Rename to InitResourceLaoder(). It's now only # used for the 'help' builtin and --version. loader = pyutil.GetResourceLoader() if opts.help: builtin.Help(['%s-usage' % lang], loader) return 0 if opts.version: # OSH version is the only binary in Oil right now, so it's all one version. _ShowVersion() return 0 if arg_r.AtEnd(): dollar0 = argv0 has_main = False else: dollar0 = arg_r.Peek() # the script name, or the arg after -c has_main = True arena = alloc.Arena() errfmt = ui.ErrorFormatter(arena) # NOTE: has_main is only for ${BASH_SOURCE[@} and family. Could be a # required arg. mem = state.Mem(dollar0, argv[arg_r.i + 1:], posix.environ, arena, has_main=has_main) builtin_funcs.Init(mem) procs = {} job_state = process.JobState() fd_state = process.FdState(errfmt, job_state) parse_opts = parse_lib.OilParseOptions() exec_opts = state.ExecOpts(mem, parse_opts, line_input) if opts.show_options: # special case: sh -o exec_opts.ShowOptions([]) return 0 builtin_pure.SetExecOpts(exec_opts, opts.opt_changes, opts.shopt_changes) aliases = {} # feedback between runtime and parser oil_grammar = meta.LoadOilGrammar(loader) if opts.one_pass_parse and not exec_opts.noexec: raise args.UsageError('--one-pass-parse requires noexec (-n)') parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar, one_pass_parse=opts.one_pass_parse) # Three ParseContext instances SHARE aliases. comp_arena = alloc.Arena() comp_arena.PushSource(source.Unused('completion')) trail1 = parse_lib.Trail() # one_pass_parse needs to be turned on to complete inside backticks. TODO: # fix the issue where ` gets erased because it's not part of # set_completer_delims(). comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases, oil_grammar, trail=trail1, one_pass_parse=True) hist_arena = alloc.Arena() hist_arena.PushSource(source.Unused('history')) trail2 = parse_lib.Trail() hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases, oil_grammar, trail=trail2) # Deps helps manages dependencies. These dependencies are circular: # - ex and word_ev, arith_ev -- for command sub, arith sub # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 )) # - ex and builtins (which execute code, like eval) # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P exec_deps = cmd_exec.Deps() # TODO: In general, exec_deps are shared between the mutually recursive # evaluators. Some of the four below are only shared between a builtin and # the Executor, so we could put them somewhere else. exec_deps.traps = {} exec_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates exec_deps.job_state = job_state # note: exec_opts.interactive set later exec_deps.waiter = process.Waiter(job_state, exec_opts) exec_deps.errfmt = errfmt my_pid = posix.getpid() debug_path = '' debug_dir = posix.environ.get('OSH_DEBUG_DIR') if opts.debug_file: # --debug-file takes precedence over OSH_DEBUG_DIR debug_path = opts.debug_file elif debug_dir: debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid) if debug_path: # This will be created as an empty file if it doesn't exist, or it could be # a pipe. try: debug_f = util.DebugFile(fd_state.Open(debug_path, mode='w')) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", debug_path, posix.strerror(e.errno)) return 2 else: debug_f = util.NullDebugFile() exec_deps.debug_f = debug_f # Not using datetime for dependency reasons. TODO: maybe show the date at # the beginning of the log, and then only show time afterward? To save # space, and make space for microseconds. (datetime supports microseconds # but time.strftime doesn't). iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S") debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, argv) if debug_path: debug_f.log('Writing logs to %r', debug_path) interp = posix.environ.get('OSH_HIJACK_SHEBANG', '') exec_deps.search_path = state.SearchPath(mem) exec_deps.ext_prog = process.ExternalProgram(interp, fd_state, exec_deps.search_path, errfmt, debug_f) splitter = split.SplitContext(mem) exec_deps.splitter = splitter # split() builtin builtin_funcs.SetGlobalFunc(mem, 'split', lambda s: splitter.SplitForWordEval(s)) # This could just be OSH_DEBUG_STREAMS='debug crash' ? That might be # stuffing too much into one, since a .json crash dump isn't a stream. crash_dump_dir = posix.environ.get('OSH_CRASH_DUMP_DIR', '') exec_deps.dumper = dev.CrashDumper(crash_dump_dir) if opts.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(sys.stderr) exec_deps.trace_f = trace_f comp_lookup = completion.Lookup() # Various Global State objects to work around readline interfaces compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() prompt_state = comp_ui.PromptState() dir_stack = state.DirStack() new_var = builtin_assign.NewVar(mem, procs, errfmt) builtins = { # Lookup builtin_e.ECHO: builtin_pure.Echo(exec_opts), builtin_e.PRINTF: builtin_printf.Printf(mem, parse_ctx, errfmt), builtin_e.PUSHD: builtin.Pushd(mem, dir_stack, errfmt), builtin_e.POPD: builtin.Popd(mem, dir_stack, errfmt), builtin_e.DIRS: builtin.Dirs(mem, dir_stack, errfmt), builtin_e.PWD: builtin.Pwd(mem, errfmt), builtin_e.READ: builtin.Read(splitter, mem), builtin_e.HELP: builtin.Help(loader, errfmt), builtin_e.HISTORY: builtin.History(line_input), # Completion (more added below) builtin_e.COMPOPT: builtin_comp.CompOpt(compopt_state, errfmt), builtin_e.COMPADJUST: builtin_comp.CompAdjust(mem), # test / [ differ by need_right_bracket builtin_e.TEST: builtin_bracket.Test(False, errfmt), builtin_e.BRACKET: builtin_bracket.Test(True, errfmt), # Assignment (which are pure) builtin_e.DECLARE: new_var, builtin_e.TYPESET: new_var, builtin_e.LOCAL: new_var, builtin_e.EXPORT: builtin_assign.Export(mem, errfmt), builtin_e.READONLY: builtin_assign.Readonly(mem, errfmt), builtin_e.UNSET: builtin_assign.Unset(mem, procs, errfmt), builtin_e.SHIFT: builtin_assign.Shift(mem), # Pure builtin_e.SET: builtin_pure.Set(exec_opts, mem), builtin_e.SHOPT: builtin_pure.Shopt(exec_opts), builtin_e.ALIAS: builtin_pure.Alias(aliases, errfmt), builtin_e.UNALIAS: builtin_pure.UnAlias(aliases, errfmt), builtin_e.TYPE: builtin_pure.Type(procs, aliases, exec_deps.search_path), builtin_e.HASH: builtin_pure.Hash(exec_deps.search_path), builtin_e.GETOPTS: builtin_pure.GetOpts(mem, errfmt), builtin_e.COLON: lambda arg_vec: 0, # a "special" builtin builtin_e.TRUE: lambda arg_vec: 0, builtin_e.FALSE: lambda arg_vec: 1, # Process builtin_e.WAIT: builtin_process.Wait(exec_deps.waiter, exec_deps.job_state, mem, errfmt), builtin_e.JOBS: builtin_process.Jobs(exec_deps.job_state), builtin_e.FG: builtin_process.Fg(exec_deps.job_state, exec_deps.waiter), builtin_e.BG: builtin_process.Bg(exec_deps.job_state), builtin_e.UMASK: builtin_process.Umask, # Oil builtin_e.REPR: builtin_oil.Repr(mem, errfmt), builtin_e.PUSH: builtin_oil.Push(mem, errfmt), builtin_e.USE: builtin_oil.Use(mem, errfmt), } ex = cmd_exec.Executor(mem, fd_state, procs, builtins, exec_opts, parse_ctx, exec_deps) exec_deps.ex = ex word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, exec_deps, arena) exec_deps.word_ev = word_ev arith_ev = osh_expr_eval.ArithEvaluator(mem, exec_opts, word_ev, errfmt) exec_deps.arith_ev = arith_ev word_ev.arith_ev = arith_ev # Another circular dependency bool_ev = osh_expr_eval.BoolEvaluator(mem, exec_opts, word_ev, errfmt) exec_deps.bool_ev = bool_ev expr_ev = expr_eval.OilEvaluator(mem, procs, ex, word_ev, errfmt) exec_deps.expr_ev = expr_ev tracer = dev.Tracer(parse_ctx, exec_opts, mem, word_ev, trace_f) exec_deps.tracer = tracer # HACK for circular deps ex.word_ev = word_ev ex.arith_ev = arith_ev ex.bool_ev = bool_ev ex.expr_ev = expr_ev ex.tracer = tracer word_ev.expr_ev = expr_ev spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter, comp_lookup) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) builtins[builtin_e.COMPLETE] = complete_builtin builtins[builtin_e.COMPGEN] = builtin_comp.CompGen(spec_builder) builtins[builtin_e.CD] = builtin.Cd(mem, dir_stack, ex, errfmt) builtins[builtin_e.JSON] = builtin_oil.Json(mem, ex, errfmt) sig_state = process.SignalState() sig_state.InitShell() builtins[builtin_e.TRAP] = builtin_process.Trap(sig_state, exec_deps.traps, exec_deps.trap_nodes, ex, errfmt) if lang == 'oil': # The Oil executor wraps an OSH executor? It needs to be able to source # it. ex = oil_cmd_exec.OilExecutor(ex) # PromptEvaluator rendering is needed in non-interactive shells for @P. prompt_ev = prompt.Evaluator(lang, parse_ctx, ex, mem) exec_deps.prompt_ev = prompt_ev word_ev.prompt_ev = prompt_ev # HACK for circular deps # History evaluation is a no-op if line_input is None. hist_ev = history.Evaluator(line_input, hist_ctx, debug_f) if opts.c is not None: arena.PushSource(source.CFlag()) line_reader = reader.StringLineReader(opts.c, arena) if opts.i: # -c and -i can be combined exec_opts.interactive = True elif opts.i: # force interactive arena.PushSource(source.Stdin(' -i')) line_reader = reader.InteractiveLineReader(arena, prompt_ev, hist_ev, line_input, prompt_state, sig_state) exec_opts.interactive = True else: try: script_name = arg_r.Peek() except IndexError: if sys.stdin.isatty(): arena.PushSource(source.Interactive()) line_reader = reader.InteractiveLineReader( arena, prompt_ev, hist_ev, line_input, prompt_state, sig_state) exec_opts.interactive = True else: arena.PushSource(source.Stdin('')) line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(source.MainFile(script_name)) try: f = fd_state.Open(script_name) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. if lang == 'osh': c_parser = parse_ctx.MakeOshParser(line_reader) else: c_parser = parse_ctx.MakeOilParser(line_reader) if exec_opts.interactive: # Calculate ~/.config/oil/oshrc or oilrc # Use ~/.config/oil to avoid cluttering the user's home directory. Some # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc. # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other home_dir = process.GetHomeDir() assert home_dir is not None rc_path = opts.rcfile or os_path.join(home_dir, '.config/oil', lang + 'rc') history_filename = os_path.join(home_dir, '.config/oil', 'history_' + lang) if line_input: # NOTE: We're using a different WordEvaluator here. ev = word_eval.CompletionWordEvaluator(mem, exec_opts, exec_deps, arena) root_comp = completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, comp_ctx, debug_f) term_width = 0 if opts.completion_display == 'nice': try: term_width = libc.get_terminal_width() except IOError: # stdin not a terminal pass if term_width != 0: display = comp_ui.NiceDisplay(term_width, comp_ui_state, prompt_state, debug_f, line_input) else: display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) _InitReadline(line_input, history_filename, root_comp, display, debug_f) _InitDefaultCompletions(ex, complete_builtin, comp_lookup) else: # Without readline module display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) sig_state.InitInteractiveShell(display) # NOTE: Call this AFTER _InitDefaultCompletions. try: SourceStartupFile(rc_path, lang, parse_ctx, ex) except util.UserExit as e: return e.status line_reader.Reset() # After sourcing startup file, render $PS1 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, ex) try: status = main_loop.Interactive(opts, ex, c_parser, display, prompt_plugin, errfmt) if ex.MaybeRunExitTrap(): status = ex.LastStatus() except util.UserExit as e: status = e.status return status nodes_out = [] if exec_opts.noexec else None if nodes_out is None and opts.parser_mem_dump: raise args.UsageError('--parser-mem-dump can only be used with -n') _tlog('Execute(node)') try: status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out) if ex.MaybeRunExitTrap(): status = ex.LastStatus() except util.UserExit as e: status = e.status # Only print nodes if the whole parse succeeded. if nodes_out is not None and status == 0: if opts.parser_mem_dump: # only valid in -n mode # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.parser_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--parser-mem-dump)', input_path, opts.parser_mem_dump) ui.PrintAst(nodes_out, opts) # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub # don't because they call sys.exit(). if opts.runtime_mem_dump: # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.runtime_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--runtime-mem-dump)', input_path, opts.runtime_mem_dump) # NOTE: We haven't closed the file opened with fd_state.Open return status
def AddBlock(builtins, mem, mutable_opts, dir_stack, cmd_ev, errfmt): # type: (Dict[int, vm._Builtin], state.Mem, state.MutableOpts, state.DirStack, cmd_eval.CommandEvaluator, ui.ErrorFormatter) -> None # These builtins take blocks, and thus need cmd_ev. builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt) builtins[builtin_i.shopt] = builtin_pure.Shopt(mutable_opts, cmd_ev)
def ShellMain(lang, argv0, argv, login_shell): # type: (str, str, List[str], bool) -> int """Used by bin/osh and bin/oil. Args: lang: 'osh' or 'oil' argv0, argv: So we can also invoke bin/osh as 'oil.ovm osh'. Like busybox. login_shell: Was - on the front? """ # Differences between osh and oil: # - --help? I guess Oil has a SUPERSET of OSH options. # - oshrc vs oilrc # - the parser and executor # - Change the prompt in the interactive shell? assert lang in ('osh', 'oil'), lang arg_r = args.Reader(argv) try: opts = OSH_SPEC.Parse(arg_r) except args.UsageError as e: ui.Stderr('osh usage error: %s', e.msg) return 2 # NOTE: This has a side effect of deleting _OVM_* from the environment! # TODO: Thread this throughout the program, and get rid of the global # variable in core/util.py. Rename to InitResourceLaoder(). It's now only # used for the 'help' builtin and --version. loader = pyutil.GetResourceLoader() if opts.help: builtin_misc.Help(['%s-usage' % lang], loader) return 0 if opts.version: # OSH version is the only binary in Oil right now, so it's all one version. _ShowVersion() return 0 no_str = None # type: str debug_stack = [] if arg_r.AtEnd(): dollar0 = argv0 else: dollar0 = arg_r.Peek() # the script name, or the arg after -c # Copy quirky bash behavior. frame0 = state.DebugFrame(dollar0, 'main', no_str, state.LINE_ZERO, 0, 0) debug_stack.append(frame0) # Copy quirky bash behavior. frame1 = state.DebugFrame(no_str, no_str, no_str, runtime.NO_SPID, 0, 0) debug_stack.append(frame1) arena = alloc.Arena() errfmt = ui.ErrorFormatter(arena) mem = state.Mem(dollar0, argv[arg_r.i + 1:], arena, debug_stack) state.InitMem(mem, posix.environ) builtin_funcs.Init(mem) procs = {} job_state = process.JobState() fd_state = process.FdState(errfmt, job_state) opt_hook = ShellOptHook(line_input) parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # TODO: only MutableOpts needs mem, so it's not a true circular dep. mem.exec_opts = exec_opts # circular dep if opts.show_options: # special case: sh -o mutable_opts.ShowOptions([]) return 0 # Set these BEFORE processing flags, so they can be overridden. if lang == 'oil': mutable_opts.SetShoptOption('oil:all', True) builtin_pure.SetShellOpts(mutable_opts, opts.opt_changes, opts.shopt_changes) aliases = {} # feedback between runtime and parser oil_grammar = meta.LoadOilGrammar(loader) if opts.one_pass_parse and not exec_opts.noexec(): raise args.UsageError('--one-pass-parse requires noexec (-n)') parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) parse_ctx.Init_OnePassParse(opts.one_pass_parse) # Three ParseContext instances SHARE aliases. comp_arena = alloc.Arena() comp_arena.PushSource(source.Unused('completion')) trail1 = parse_lib.Trail() # one_pass_parse needs to be turned on to complete inside backticks. TODO: # fix the issue where ` gets erased because it's not part of # set_completer_delims(). comp_ctx = parse_lib.ParseContext(comp_arena, parse_opts, aliases, oil_grammar) comp_ctx.Init_Trail(trail1) comp_ctx.Init_OnePassParse(True) hist_arena = alloc.Arena() hist_arena.PushSource(source.Unused('history')) trail2 = parse_lib.Trail() hist_ctx = parse_lib.ParseContext(hist_arena, parse_opts, aliases, oil_grammar) hist_ctx.Init_Trail(trail2) # Deps helps manages dependencies. These dependencies are circular: # - ex and word_ev, arith_ev -- for command sub, arith sub # - arith_ev and word_ev -- for $(( ${a} )) and $x$(( 1 )) # - ex and builtins (which execute code, like eval) # - prompt_ev needs word_ev for $PS1, which needs prompt_ev for @P exec_deps = cmd_exec.Deps() exec_deps.mutable_opts = mutable_opts # TODO: In general, exec_deps are shared between the mutually recursive # evaluators. Some of the four below are only shared between a builtin and # the Executor, so we could put them somewhere else. exec_deps.traps = {} exec_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates exec_deps.job_state = job_state exec_deps.waiter = process.Waiter(job_state, exec_opts) exec_deps.errfmt = errfmt my_pid = posix.getpid() debug_path = '' debug_dir = posix.environ.get('OSH_DEBUG_DIR') if opts.debug_file: # --debug-file takes precedence over OSH_DEBUG_DIR debug_path = opts.debug_file elif debug_dir: debug_path = os_path.join(debug_dir, '%d-osh.log' % my_pid) if debug_path: # This will be created as an empty file if it doesn't exist, or it could be # a pipe. try: debug_f = util.DebugFile(fd_state.Open(debug_path, mode='w')) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", debug_path, posix.strerror(e.errno)) return 2 else: debug_f = util.NullDebugFile() exec_deps.debug_f = debug_f # Not using datetime for dependency reasons. TODO: maybe show the date at # the beginning of the log, and then only show time afterward? To save # space, and make space for microseconds. (datetime supports microseconds # but time.strftime doesn't). iso_stamp = time.strftime("%Y-%m-%d %H:%M:%S") debug_f.log('%s [%d] OSH started with argv %s', iso_stamp, my_pid, argv) if debug_path: debug_f.log('Writing logs to %r', debug_path) interp = posix.environ.get('OSH_HIJACK_SHEBANG', '') exec_deps.search_path = state.SearchPath(mem) exec_deps.ext_prog = process.ExternalProgram(interp, fd_state, exec_deps.search_path, errfmt, debug_f) splitter = split.SplitContext(mem) # split() builtin # TODO: Accept IFS as a named arg? split('a b', IFS=' ') builtin_funcs.SetGlobalFunc( mem, 'split', lambda s, ifs=None: splitter.SplitForWordEval(s, ifs=ifs)) # glob() builtin # TODO: This is instantiation is duplicated in osh/word_eval.py globber = glob_.Globber(exec_opts) builtin_funcs.SetGlobalFunc(mem, 'glob', lambda s: globber.OilFuncCall(s)) # This could just be OSH_DEBUG_STREAMS='debug crash' ? That might be # stuffing too much into one, since a .json crash dump isn't a stream. crash_dump_dir = posix.environ.get('OSH_CRASH_DUMP_DIR', '') exec_deps.dumper = dev.CrashDumper(crash_dump_dir) if opts.xtrace_to_debug_file: trace_f = debug_f else: trace_f = util.DebugFile(sys.stderr) exec_deps.trace_f = trace_f comp_lookup = completion.Lookup() # Various Global State objects to work around readline interfaces compopt_state = completion.OptionState() comp_ui_state = comp_ui.State() prompt_state = comp_ui.PromptState() dir_stack = state.DirStack() new_var = builtin_assign.NewVar(mem, procs, errfmt) builtins = { # Lookup builtin_i.echo: builtin_pure.Echo(exec_opts), builtin_i.printf: builtin_printf.Printf(mem, parse_ctx, errfmt), builtin_i.pushd: builtin_misc.Pushd(mem, dir_stack, errfmt), builtin_i.popd: builtin_misc.Popd(mem, dir_stack, errfmt), builtin_i.dirs: builtin_misc.Dirs(mem, dir_stack, errfmt), builtin_i.pwd: builtin_misc.Pwd(mem, errfmt), builtin_i.times: builtin_misc.Times(), builtin_i.read: builtin_misc.Read(splitter, mem), builtin_i.help: builtin_misc.Help(loader, errfmt), builtin_i.history: builtin_misc.History(line_input), # Completion (more added below) builtin_i.compopt: builtin_comp.CompOpt(compopt_state, errfmt), builtin_i.compadjust: builtin_comp.CompAdjust(mem), # test / [ differ by need_right_bracket builtin_i.test: builtin_bracket.Test(False, exec_opts, errfmt), builtin_i.bracket: builtin_bracket.Test(True, exec_opts, errfmt), # ShAssignment (which are pure) builtin_i.declare: new_var, builtin_i.typeset: new_var, builtin_i.local: new_var, builtin_i.export_: builtin_assign.Export(mem, errfmt), builtin_i.readonly: builtin_assign.Readonly(mem, errfmt), builtin_i.unset: builtin_assign.Unset(mem, procs, errfmt), builtin_i.shift: builtin_assign.Shift(mem), # Pure builtin_i.set: builtin_pure.Set(mutable_opts, mem), builtin_i.shopt: builtin_pure.Shopt(mutable_opts), builtin_i.alias: builtin_pure.Alias(aliases, errfmt), builtin_i.unalias: builtin_pure.UnAlias(aliases, errfmt), builtin_i.type: builtin_pure.Type(procs, aliases, exec_deps.search_path), builtin_i.hash: builtin_pure.Hash(exec_deps.search_path), builtin_i.getopts: builtin_pure.GetOpts(mem, errfmt), builtin_i.colon: builtin_pure.Boolean(0), # a "special" builtin builtin_i.true_: builtin_pure.Boolean(0), builtin_i.false_: builtin_pure.Boolean(1), # Process builtin_i.wait: builtin_process.Wait(exec_deps.waiter, exec_deps.job_state, mem, errfmt), builtin_i.jobs: builtin_process.Jobs(exec_deps.job_state), builtin_i.fg: builtin_process.Fg(exec_deps.job_state, exec_deps.waiter), builtin_i.bg: builtin_process.Bg(exec_deps.job_state), builtin_i.umask: builtin_process.Umask(), # Oil builtin_i.push: builtin_oil.Push(mem, errfmt), builtin_i.append: builtin_oil.Append(mem, errfmt), builtin_i.write: builtin_oil.Write(mem, errfmt), builtin_i.getline: builtin_oil.Getline(mem, errfmt), builtin_i.repr: builtin_oil.Repr(mem, errfmt), builtin_i.use: builtin_oil.Use(mem, errfmt), builtin_i.opts: builtin_oil.Opts(mem, errfmt), } arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, errfmt) expr_ev = expr_eval.OilEvaluator(mem, procs, errfmt) word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) ex = cmd_exec.Executor(mem, fd_state, procs, builtins, exec_opts, parse_ctx, exec_deps) # PromptEvaluator rendering is needed in non-interactive shells for @P. prompt_ev = prompt.Evaluator(lang, parse_ctx, mem) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) # Wire up circular dependencies. vm.InitCircularDeps(arith_ev, bool_ev, expr_ev, word_ev, ex, prompt_ev, tracer) spec_builder = builtin_comp.SpecBuilder(ex, parse_ctx, word_ev, splitter, comp_lookup) # Add some builtins that depend on the executor! complete_builtin = builtin_comp.Complete(spec_builder, comp_lookup) builtins[builtin_i.complete] = complete_builtin builtins[builtin_i.compgen] = builtin_comp.CompGen(spec_builder) builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, ex, errfmt) builtins[builtin_i.json] = builtin_oil.Json(mem, ex, errfmt) sig_state = process.SignalState() sig_state.InitShell() builtins[builtin_i.trap] = builtin_process.Trap(sig_state, exec_deps.traps, exec_deps.trap_nodes, ex, errfmt) # History evaluation is a no-op if line_input is None. hist_ev = history.Evaluator(line_input, hist_ctx, debug_f) if opts.c is not None: arena.PushSource(source.CFlag()) line_reader = reader.StringLineReader(opts.c, arena) if opts.i: # -c and -i can be combined mutable_opts.set_interactive() elif opts.i: # force interactive arena.PushSource(source.Stdin(' -i')) line_reader = py_reader.InteractiveLineReader(arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: script_name = arg_r.Peek() if script_name is None: if sys.stdin.isatty(): arena.PushSource(source.Interactive()) line_reader = py_reader.InteractiveLineReader( arena, prompt_ev, hist_ev, line_input, prompt_state) mutable_opts.set_interactive() else: arena.PushSource(source.Stdin('')) line_reader = reader.FileLineReader(sys.stdin, arena) else: arena.PushSource(source.MainFile(script_name)) try: f = fd_state.Open(script_name) except OSError as e: ui.Stderr("osh: Couldn't open %r: %s", script_name, posix.strerror(e.errno)) return 1 line_reader = reader.FileLineReader(f, arena) # TODO: assert arena.NumSourcePaths() == 1 # TODO: .rc file needs its own arena. c_parser = parse_ctx.MakeOshParser(line_reader) if exec_opts.interactive(): # Calculate ~/.config/oil/oshrc or oilrc # Use ~/.config/oil to avoid cluttering the user's home directory. Some # users may want to ln -s ~/.config/oil/oshrc ~/oshrc or ~/.oshrc. # https://unix.stackexchange.com/questions/24347/why-do-some-applications-use-config-appname-for-their-config-data-while-other home_dir = passwd.GetMyHomeDir() assert home_dir is not None rc_path = opts.rcfile or os_path.join(home_dir, '.config/oil', lang + 'rc') history_filename = os_path.join(home_dir, '.config/oil', 'history_' + lang) if line_input: # NOTE: We're using a different WordEvaluator here. ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter, errfmt) ev.arith_ev = arith_ev ev.expr_ev = expr_ev ev.prompt_ev = prompt_ev ev.CheckCircularDeps() root_comp = completion.RootCompleter(ev, mem, comp_lookup, compopt_state, comp_ui_state, comp_ctx, debug_f) term_width = 0 if opts.completion_display == 'nice': try: term_width = libc.get_terminal_width() except IOError: # stdin not a terminal pass if term_width != 0: display = comp_ui.NiceDisplay(term_width, comp_ui_state, prompt_state, debug_f, line_input) else: display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) _InitReadline(line_input, history_filename, root_comp, display, debug_f) _InitDefaultCompletions(ex, complete_builtin, comp_lookup) else: # Without readline module display = comp_ui.MinimalDisplay(comp_ui_state, prompt_state, debug_f) sig_state.InitInteractiveShell(display) # NOTE: Call this AFTER _InitDefaultCompletions. try: SourceStartupFile(rc_path, lang, parse_ctx, ex) except util.UserExit as e: return e.status line_reader.Reset() # After sourcing startup file, render $PS1 prompt_plugin = prompt.UserPlugin(mem, parse_ctx, ex) try: status = main_loop.Interactive(opts, ex, c_parser, display, prompt_plugin, errfmt) if ex.MaybeRunExitTrap(): status = ex.LastStatus() except util.UserExit as e: status = e.status return status nodes_out = [] if exec_opts.noexec() else None if nodes_out is None and opts.parser_mem_dump: raise args.UsageError('--parser-mem-dump can only be used with -n') _tlog('Execute(node)') try: status = main_loop.Batch(ex, c_parser, arena, nodes_out=nodes_out) if ex.MaybeRunExitTrap(): status = ex.LastStatus() except util.UserExit as e: status = e.status # Only print nodes if the whole parse succeeded. if nodes_out is not None and status == 0: if opts.parser_mem_dump: # only valid in -n mode # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.parser_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--parser-mem-dump)', input_path, opts.parser_mem_dump) ui.PrintAst(nodes_out, opts) # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub # don't because they call sys.exit(). if opts.runtime_mem_dump: # This might be superstition, but we want to let the value stabilize # after parsing. bash -c 'cat /proc/$$/status' gives different results # with a sleep. time.sleep(0.001) input_path = '/proc/%d/status' % posix.getpid() with open(input_path) as f, open(opts.runtime_mem_dump, 'w') as f2: contents = f.read() f2.write(contents) log('Wrote %s to %s (--runtime-mem-dump)', input_path, opts.runtime_mem_dump) # NOTE: We haven't closed the file opened with fd_state.Open return status
def main(argv): # type: (List[str]) -> int arena = alloc.Arena() dollar0 = argv[0] debug_stack = [] # type: List[state.DebugFrame] argv = argv[1:] # remove binary name i, flag_a, flag_c, flag_n = Parse(argv) argv = argv[i:] # truncate mem = state.Mem(dollar0, argv, arena, debug_stack) # TODO: look at extern char** environ; environ = {} # type: Dict[str, str] environ['PWD'] = posix.getcwd() state.InitMem(mem, environ, 'VERSION') opt_hook = state.OptHook() parse_opts, exec_opts, mutable_opts = state.MakeOpts(mem, opt_hook) # Dummy value; not respecting aliases! aliases = {} # type: Dict[str, str] # parse `` and a[x+1]=bar differently oil_grammar = None # type: Grammar if mylib.PYTHON: loader = pyutil.GetResourceLoader() oil_grammar = meta.LoadOilGrammar(loader) parse_ctx = parse_lib.ParseContext(arena, parse_opts, aliases, oil_grammar) if flag_c: # This path is easier to run through GDB line_reader = reader.StringLineReader(flag_c, arena) src = source.CFlag() # type: source_t elif len(argv) == 0: line_reader = reader.FileLineReader(mylib.Stdin(), arena) src = source.Stdin('') elif len(argv) == 1: path = argv[0] f = mylib.open(path) line_reader = reader.FileLineReader(f, arena) src = source.MainFile(path) else: raise AssertionError(argv) arena.PushSource(src) c_parser = parse_ctx.MakeOshParser(line_reader) # C++ doesn't have the abbreviations yet (though there are some differences # like omitting spids) #tree = node.AbbreviatedTree() if flag_n: try: node = main_loop.ParseWholeFile(c_parser) except error.Parse as e: ui.PrettyPrintError(e, arena) return 2 assert node is not None if flag_a: tree = node.PrettyTree() ast_f = fmt.DetectConsoleOutput(mylib.Stdout()) fmt.PrintTree(tree, ast_f) ast_f.write('\n') return 0 # New osh_eval.py instantiations errfmt = ui.ErrorFormatter(arena) splitter = split.SplitContext(mem) arith_ev = sh_expr_eval.ArithEvaluator(mem, exec_opts, parse_ctx, errfmt) bool_ev = sh_expr_eval.BoolEvaluator(mem, exec_opts, parse_ctx, errfmt) word_ev = word_eval.NormalWordEvaluator(mem, exec_opts, splitter, errfmt) prompt_ev = prompt.Evaluator('osh', parse_ctx, mem) arith_ev.word_ev = word_ev word_ev.arith_ev = arith_ev word_ev.prompt_ev = prompt_ev prompt_ev.word_ev = word_ev procs = {} # type: Dict[str, command__ShFunction] assign_builtins = {} # type: Dict[int, _AssignBuiltin] new_var = builtin_assign.NewVar(mem, procs, errfmt) assign_builtins[builtin_i.declare] = new_var assign_builtins[builtin_i.typeset] = new_var assign_builtins[builtin_i.local] = new_var assign_builtins[builtin_i.export_] = builtin_assign.Export(mem, errfmt) assign_builtins[builtin_i.readonly] = builtin_assign.Readonly(mem, errfmt) #assign_builtins = { # # ShAssignment (which are pure) # builtin_i.declare: new_var, # builtin_i.typeset: new_var, # builtin_i.local: new_var, # builtin_i.export_: builtin_assign.Export(mem, errfmt), # builtin_i.readonly: builtin_assign.Readonly(mem, errfmt), #} cmd_deps = cmd_eval.Deps() cmd_deps.mutable_opts = mutable_opts cmd_deps.traps = {} cmd_deps.trap_nodes = [] # TODO: Clear on fork() to avoid duplicates cmd_deps.dumper = dev.CrashDumper('') search_path = state.SearchPath(mem) builtins = {} # type: Dict[int, vm._Builtin] builtins[builtin_i.echo] = builtin_pure.Echo(exec_opts) builtins[builtin_i.set] = Set(mutable_opts) # DUMMY until ParseMore() if mylib.PYTHON: # Use the real one builtins[builtin_i.set] = builtin_pure.Set(mutable_opts, mem) builtins[builtin_i.shopt] = builtin_pure.Shopt(mutable_opts) builtins[builtin_i.alias] = builtin_pure.Alias(aliases, errfmt) builtins[builtin_i.unalias] = builtin_pure.UnAlias(aliases, errfmt) builtins[builtin_i.hash] = builtin_pure.Hash(search_path) builtins[builtin_i.getopts] = builtin_pure.GetOpts(mem, errfmt) builtins[builtin_i.shift] = builtin_assign.Shift(mem) builtins[builtin_i.unset] = builtin_assign.Unset( mem, exec_opts, procs, parse_ctx, arith_ev, errfmt) true_ = builtin_pure.Boolean(0) builtins[builtin_i.colon] = true_ # a "special" builtin builtins[builtin_i.true_] = true_ builtins[builtin_i.false_] = builtin_pure.Boolean(1) # builtin_meta builtins[builtin_i.type] = builtin_meta.Type(procs, aliases, search_path, errfmt) shell_ex = NullExecutor(exec_opts, mutable_opts, procs, builtins) trace_f = util.DebugFile(mylib.Stderr()) tracer = dev.Tracer(parse_ctx, exec_opts, mutable_opts, mem, word_ev, trace_f) cmd_ev = cmd_eval.CommandEvaluator(mem, exec_opts, errfmt, procs, assign_builtins, arena, cmd_deps) # TODO: can't instantiate this yet #fd_state = None # needs cmd_ev builtins[builtin_i.eval] = builtin_meta.Eval(parse_ctx, exec_opts, cmd_ev) #source_builtin = builtin_meta.Source( # parse_ctx, search_path, cmd_ev, fd_state, errfmt) #builtins[builtin_i.source] = source_builtin #builtins[builtin_i.dot] = source_builtin builtins[builtin_i.builtin] = builtin_meta.Builtin(shell_ex, errfmt) builtins[builtin_i.command] = builtin_meta.Command(shell_ex, procs, aliases, search_path) builtins[builtin_i.printf] = builtin_printf.Printf(mem, parse_ctx, errfmt) builtins[builtin_i.test] = builtin_bracket.Test(False, exec_opts, mem, errfmt) builtins[builtin_i.bracket] = builtin_bracket.Test(True, exec_opts, mem, errfmt) dir_stack = state.DirStack() builtins[builtin_i.pushd] = builtin_misc.Pushd(mem, dir_stack, errfmt) builtins[builtin_i.popd] = builtin_misc.Popd(mem, dir_stack, errfmt) builtins[builtin_i.dirs] = builtin_misc.Dirs(mem, dir_stack, errfmt) builtins[builtin_i.pwd] = builtin_misc.Pwd(mem, errfmt) builtins[builtin_i.times] = builtin_misc.Times() builtins[builtin_i.read] = builtin_misc.Read(splitter, mem) builtins[builtin_i.cat] = builtin_misc.Cat() # for $(<file) builtins[builtin_i.cd] = builtin_misc.Cd(mem, dir_stack, cmd_ev, errfmt) # vm.InitCircularDeps cmd_ev.arith_ev = arith_ev cmd_ev.bool_ev = bool_ev cmd_ev.word_ev = word_ev cmd_ev.tracer = tracer cmd_ev.shell_ex = shell_ex shell_ex.cmd_ev = cmd_ev bool_ev.word_ev = word_ev try: status = main_loop.Batch(cmd_ev, c_parser, arena, cmd_flags=cmd_eval.IsMainProgram) except util.UserExit as e: # TODO: fix this #status = e.status status = 1 return status