| 1 | #!/usr/bin/env python
 | 
| 2 | # Copyright 2016 Andy Chu. All rights reserved.
 | 
| 3 | # Licensed under the Apache License, Version 2.0 (the "License");
 | 
| 4 | # you may not use this file except in compliance with the License.
 | 
| 5 | # You may obtain a copy of the License at
 | 
| 6 | #
 | 
| 7 | #   http://www.apache.org/licenses/LICENSE-2.0
 | 
| 8 | from __future__ import print_function
 | 
| 9 | """
 | 
| 10 | oil.py - A busybox-like binary for oil.
 | 
| 11 | 
 | 
| 12 | Based on argv[0], it acts like a few different programs.
 | 
| 13 | 
 | 
| 14 | Builtins that can be exposed:
 | 
| 15 | 
 | 
| 16 | - test / [ -- call BoolParser at runtime
 | 
| 17 | - 'time' -- because it has format strings, etc.
 | 
| 18 | - find/xargs equivalents (even if they are not compatible)
 | 
| 19 |   - list/each/every
 | 
| 20 | 
 | 
| 21 | - echo: most likely don't care about this
 | 
| 22 | """
 | 
| 23 | 
 | 
| 24 | import os
 | 
| 25 | import sys
 | 
| 26 | import time  # for perf measurement
 | 
| 27 | 
 | 
| 28 | # TODO: Set PYTHONPATH from outside?
 | 
| 29 | this_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
 | 
| 30 | sys.path.append(os.path.join(this_dir, '..'))
 | 
| 31 | 
 | 
| 32 | _trace_path = os.environ.get('_PY_TRACE')
 | 
| 33 | if _trace_path:
 | 
| 34 |   from benchmarks import pytrace
 | 
| 35 |   _tracer = pytrace.Tracer()
 | 
| 36 |   _tracer.Start()
 | 
| 37 | else:
 | 
| 38 |   _tracer = None
 | 
| 39 | 
 | 
| 40 | # Uncomment this to see startup time problems.
 | 
| 41 | if os.environ.get('OIL_TIMING'):
 | 
| 42 |   start_time = time.time()
 | 
| 43 |   def _tlog(msg):
 | 
| 44 |     pid = os.getpid()  # TODO: Maybe remove PID later.
 | 
| 45 |     print('[%d] %.3f %s' % (pid, (time.time() - start_time) * 1000, msg))
 | 
| 46 | else:
 | 
| 47 |   def _tlog(msg):
 | 
| 48 |     pass
 | 
| 49 | 
 | 
| 50 | _tlog('before imports')
 | 
| 51 | 
 | 
| 52 | import errno
 | 
| 53 | #import traceback  # for debugging
 | 
| 54 | 
 | 
| 55 | # Set in Modules/main.c.
 | 
| 56 | HAVE_READLINE = os.getenv('_HAVE_READLINE') != ''
 | 
| 57 | 
 | 
| 58 | from asdl import format as fmt
 | 
| 59 | from asdl import encode
 | 
| 60 | 
 | 
| 61 | from osh import word_parse  # for tracing
 | 
| 62 | from osh import cmd_parse  # for tracing
 | 
| 63 | 
 | 
| 64 | from osh import ast_lib
 | 
| 65 | from osh import parse_lib
 | 
| 66 | 
 | 
| 67 | from core import alloc
 | 
| 68 | from core import args
 | 
| 69 | from core import builtin
 | 
| 70 | from core import cmd_exec
 | 
| 71 | from osh.meta import Id
 | 
| 72 | from core import legacy
 | 
| 73 | from core import lexer  # for tracing
 | 
| 74 | from core import process
 | 
| 75 | from core import reader
 | 
| 76 | from core import state
 | 
| 77 | from core import word
 | 
| 78 | from core import word_eval
 | 
| 79 | from core import ui
 | 
| 80 | from core import util
 | 
| 81 | 
 | 
| 82 | if HAVE_READLINE:
 | 
| 83 |   from core import completion
 | 
| 84 | else:
 | 
| 85 |   completion = None
 | 
| 86 | 
 | 
| 87 | from tools import deps
 | 
| 88 | from tools import osh2oil
 | 
| 89 | 
 | 
| 90 | log = util.log
 | 
| 91 | 
 | 
| 92 | _tlog('after imports')
 | 
| 93 | 
 | 
| 94 | 
 | 
| 95 | def InteractiveLoop(opts, ex, c_parser, w_parser, line_reader):
 | 
| 96 |   if opts.show_ast:
 | 
| 97 |     ast_f = fmt.DetectConsoleOutput(sys.stdout)
 | 
| 98 |   else:
 | 
| 99 |     ast_f = None
 | 
| 100 | 
 | 
| 101 |   status = 0
 | 
| 102 |   while True:
 | 
| 103 |     try:
 | 
| 104 |       w = c_parser.Peek()
 | 
| 105 |     except KeyboardInterrupt:
 | 
| 106 |       print('Ctrl-C')
 | 
| 107 |       break
 | 
| 108 | 
 | 
| 109 |     if w is None:
 | 
| 110 |       raise RuntimeError('Failed parse: %s' % c_parser.Error())
 | 
| 111 |     c_id = word.CommandId(w)
 | 
| 112 |     if c_id == Id.Op_Newline:
 | 
| 113 |       print('nothing to execute')
 | 
| 114 |     elif c_id == Id.Eof_Real:
 | 
| 115 |       print('EOF')
 | 
| 116 |       break
 | 
| 117 |     else:
 | 
| 118 |       node = c_parser.ParseCommandLine()
 | 
| 119 | 
 | 
| 120 |       # TODO: Need an error for an empty command, which we ignore?  GetLine
 | 
| 121 |       # could do that in the first position?
 | 
| 122 |       # ParseSimpleCommand fails with '\n' token?
 | 
| 123 |       if not node:
 | 
| 124 |         # TODO: PrintError here
 | 
| 125 |         raise RuntimeError('failed parse: %s' % c_parser.Error())
 | 
| 126 | 
 | 
| 127 |       if ast_f:
 | 
| 128 |         ast_lib.PrettyPrint(node)
 | 
| 129 | 
 | 
| 130 |       status, is_control_flow = ex.ExecuteAndCatch(node)
 | 
| 131 |       if is_control_flow:  # exit or return
 | 
| 132 |         break
 | 
| 133 | 
 | 
| 134 |       if opts.print_status:
 | 
| 135 |         print('STATUS', repr(status))
 | 
| 136 | 
 | 
| 137 |     # Reset prompt to PS1.
 | 
| 138 |     line_reader.Reset()
 | 
| 139 | 
 | 
| 140 |     # Reset internal newline state.
 | 
| 141 |     # NOTE: It would actually be correct to reinitialize all objects (except
 | 
| 142 |     # Env) on every iteration.  But we know that the w_parser is the only thing
 | 
| 143 |     # that needs to be reset, for now.
 | 
| 144 |     w_parser.Reset()
 | 
| 145 |     c_parser.Reset()
 | 
| 146 | 
 | 
| 147 |   return status
 | 
| 148 | 
 | 
| 149 | 
 | 
| 150 | # bash --noprofile --norc uses 'bash-4.3$ '
 | 
| 151 | OSH_PS1 = 'osh$ '
 | 
| 152 | 
 | 
| 153 | 
 | 
| 154 | def _ShowVersion():
 | 
| 155 |   util.ShowAppVersion('Oil')
 | 
| 156 | 
 | 
| 157 | 
 | 
| 158 | def OshMain(argv0, argv, login_shell):
 | 
| 159 |   spec = args.FlagsAndOptions()
 | 
| 160 |   spec.ShortFlag('-c', args.Str, quit_parsing_flags=True)  # command string
 | 
| 161 |   spec.ShortFlag('-i')  # interactive
 | 
| 162 | 
 | 
| 163 |   # TODO: -h too
 | 
| 164 |   spec.LongFlag('--help')
 | 
| 165 |   spec.LongFlag('--version')
 | 
| 166 |   spec.LongFlag('--ast-format',
 | 
| 167 |                 ['text', 'abbrev-text', 'html', 'abbrev-html', 'oheap', 'none'],
 | 
| 168 |                 default='abbrev-text')
 | 
| 169 |   spec.LongFlag('--show-ast')  # execute and show
 | 
| 170 |   spec.LongFlag('--fix')
 | 
| 171 |   spec.LongFlag('--debug-spans')  # For oshc translate
 | 
| 172 |   spec.LongFlag('--print-status')
 | 
| 173 |   spec.LongFlag('--trace', ['cmd-parse', 'word-parse', 'lexer'])  # NOTE: can only trace one now
 | 
| 174 |   spec.LongFlag('--hijack-shebang')
 | 
| 175 | 
 | 
| 176 |   # For benchmarks/*.sh
 | 
| 177 |   spec.LongFlag('--parser-mem-dump', args.Str)
 | 
| 178 |   spec.LongFlag('--runtime-mem-dump', args.Str)
 | 
| 179 | 
 | 
| 180 |   builtin.AddOptionsToArgSpec(spec)
 | 
| 181 | 
 | 
| 182 |   try:
 | 
| 183 |     opts, opt_index = spec.Parse(argv)
 | 
| 184 |   except args.UsageError as e:
 | 
| 185 |     util.usage(str(e))
 | 
| 186 |     return 2
 | 
| 187 | 
 | 
| 188 |   if opts.help:
 | 
| 189 |     loader = util.GetResourceLoader()
 | 
| 190 |     builtin.Help(['osh-usage'], loader)
 | 
| 191 |     return 0
 | 
| 192 |   if opts.version:
 | 
| 193 |     # OSH version is the only binary in Oil right now, so it's all one version.
 | 
| 194 |     _ShowVersion()
 | 
| 195 |     return 0
 | 
| 196 | 
 | 
| 197 |   trace_state = util.TraceState()
 | 
| 198 |   if 'cmd-parse' == opts.trace:
 | 
| 199 |     util.WrapMethods(cmd_parse.CommandParser, trace_state)
 | 
| 200 |   if 'word-parse' == opts.trace:
 | 
| 201 |     util.WrapMethods(word_parse.WordParser, trace_state)
 | 
| 202 |   if 'lexer' == opts.trace:
 | 
| 203 |     util.WrapMethods(lexer.Lexer, trace_state)
 | 
| 204 | 
 | 
| 205 |   if opt_index == len(argv):
 | 
| 206 |     dollar0 = argv0
 | 
| 207 |   else:
 | 
| 208 |     dollar0 = argv[opt_index]  # the script name, or the arg after -c
 | 
| 209 | 
 | 
| 210 |   # TODO: Create a --parse action or 'osh parse' or 'oil osh-parse'
 | 
| 211 |   # osh-fix
 | 
| 212 |   # It uses a different memory-management model.  It's a batch program and not
 | 
| 213 |   # an interactive program.
 | 
| 214 | 
 | 
| 215 |   pool = alloc.Pool()
 | 
| 216 |   arena = pool.NewArena()
 | 
| 217 | 
 | 
| 218 |   # TODO: Maybe wrap this initialization sequence up in an oil_State, like
 | 
| 219 |   # lua_State.
 | 
| 220 |   status_lines = ui.MakeStatusLines()
 | 
| 221 |   mem = state.Mem(dollar0, argv[opt_index + 1:], os.environ, arena)
 | 
| 222 |   funcs = {}
 | 
| 223 | 
 | 
| 224 |   # Passed to Executor for 'complete', and passed to completion.Init
 | 
| 225 |   if completion:
 | 
| 226 |     comp_lookup = completion.CompletionLookup()
 | 
| 227 |   else:
 | 
| 228 |     # TODO: NullLookup?
 | 
| 229 |     comp_lookup = None
 | 
| 230 | 
 | 
| 231 |   exec_opts = state.ExecOpts(mem)
 | 
| 232 |   builtin.SetExecOpts(exec_opts, opts.opt_changes)
 | 
| 233 | 
 | 
| 234 |   fd_state = process.FdState()
 | 
| 235 |   ex = cmd_exec.Executor(mem, fd_state, status_lines, funcs, completion,
 | 
| 236 |                          comp_lookup, exec_opts, arena)
 | 
| 237 | 
 | 
| 238 |   # NOTE: The rc file can contain both commands and functions... ideally we
 | 
| 239 |   # would only want to save nodes/lines for the functions.
 | 
| 240 |   try:
 | 
| 241 |     rc_path = 'oilrc'
 | 
| 242 |     arena.PushSource(rc_path)
 | 
| 243 |     with open(rc_path) as f:
 | 
| 244 |       rc_line_reader = reader.FileLineReader(f, arena)
 | 
| 245 |       _, rc_c_parser = parse_lib.MakeParser(rc_line_reader, arena)
 | 
| 246 |       try:
 | 
| 247 |         rc_node = rc_c_parser.ParseWholeFile()
 | 
| 248 |         if not rc_node:
 | 
| 249 |           err = rc_c_parser.Error()
 | 
| 250 |           ui.PrintErrorStack(err, arena, sys.stderr)
 | 
| 251 |           return 2  # parse error is code 2
 | 
| 252 |       finally:
 | 
| 253 |         arena.PopSource()
 | 
| 254 | 
 | 
| 255 |     status = ex.Execute(rc_node)
 | 
| 256 |     #print('oilrc:', status, cflow, file=sys.stderr)
 | 
| 257 |     # Ignore bad status?
 | 
| 258 |   except IOError as e:
 | 
| 259 |     if e.errno != errno.ENOENT:
 | 
| 260 |       raise
 | 
| 261 | 
 | 
| 262 |   if opts.c is not None:
 | 
| 263 |     arena.PushSource('<command string>')
 | 
| 264 |     line_reader = reader.StringLineReader(opts.c, arena)
 | 
| 265 |     interactive = False
 | 
| 266 |   elif opts.i:  # force interactive
 | 
| 267 |     arena.PushSource('<stdin -i>')
 | 
| 268 |     line_reader = reader.InteractiveLineReader(OSH_PS1, arena)
 | 
| 269 |     interactive = True
 | 
| 270 |   else:
 | 
| 271 |     try:
 | 
| 272 |       script_name = argv[opt_index]
 | 
| 273 |     except IndexError:
 | 
| 274 |       if sys.stdin.isatty():
 | 
| 275 |         arena.PushSource('<interactive>')
 | 
| 276 |         line_reader = reader.InteractiveLineReader(OSH_PS1, arena)
 | 
| 277 |         interactive = True
 | 
| 278 |       else:
 | 
| 279 |         arena.PushSource('<stdin>')
 | 
| 280 |         line_reader = reader.FileLineReader(sys.stdin, arena)
 | 
| 281 |         interactive = False
 | 
| 282 |     else:
 | 
| 283 |       arena.PushSource(script_name)
 | 
| 284 |       try:
 | 
| 285 |         f = fd_state.Open(script_name)
 | 
| 286 |       except OSError as e:
 | 
| 287 |         util.error("Couldn't open %r: %s", script_name, os.strerror(e.errno))
 | 
| 288 |         return 1
 | 
| 289 |       line_reader = reader.FileLineReader(f, arena)
 | 
| 290 |       interactive = False
 | 
| 291 | 
 | 
| 292 |   # TODO: assert arena.NumSourcePaths() == 1
 | 
| 293 |   # TODO: .rc file needs its own arena.
 | 
| 294 |   w_parser, c_parser = parse_lib.MakeParser(line_reader, arena)
 | 
| 295 | 
 | 
| 296 |   if interactive:
 | 
| 297 |     # NOTE: We're using a different evaluator here.  The completion system can
 | 
| 298 |     # also run functions... it gets the Executor through Executor._Complete.
 | 
| 299 |     if HAVE_READLINE:
 | 
| 300 |       splitter = legacy.SplitContext(mem)
 | 
| 301 |       ev = word_eval.CompletionWordEvaluator(mem, exec_opts, splitter)
 | 
| 302 |       status_out = completion.StatusOutput(status_lines, exec_opts)
 | 
| 303 |       completion.Init(pool, builtin.BUILTIN_DEF, mem, funcs, comp_lookup,
 | 
| 304 |                       status_out, ev)
 | 
| 305 | 
 | 
| 306 |     return InteractiveLoop(opts, ex, c_parser, w_parser, line_reader)
 | 
| 307 |   else:
 | 
| 308 |     # Parse the whole thing up front
 | 
| 309 |     #print('Parsing file')
 | 
| 310 | 
 | 
| 311 |     _tlog('ParseWholeFile')
 | 
| 312 |     # TODO: Do I need ParseAndEvalLoop?  How is it different than
 | 
| 313 |     # InteractiveLoop?
 | 
| 314 |     try:
 | 
| 315 |       node = c_parser.ParseWholeFile()
 | 
| 316 |     except util.ParseError as e:
 | 
| 317 |       ui.PrettyPrintError(e, arena, sys.stderr)
 | 
| 318 |       print('parse error: %s' % e.UserErrorString(), file=sys.stderr)
 | 
| 319 |       return 2
 | 
| 320 |     else:
 | 
| 321 |       # TODO: Remove this older form of error handling.
 | 
| 322 |       if not node:
 | 
| 323 |         err = c_parser.Error()
 | 
| 324 |         assert err, err  # can't be empty
 | 
| 325 |         ui.PrintErrorStack(err, arena, sys.stderr)
 | 
| 326 |         return 2  # parse error is code 2
 | 
| 327 | 
 | 
| 328 |     do_exec = True
 | 
| 329 |     if opts.fix:
 | 
| 330 |       #log('SPANS: %s', arena.spans)
 | 
| 331 |       osh2oil.PrintAsOil(arena, node, opts.debug_spans)
 | 
| 332 |       do_exec = False
 | 
| 333 |     if exec_opts.noexec:
 | 
| 334 |       do_exec = False
 | 
| 335 | 
 | 
| 336 |     # Do this after parsing the entire file.  There could be another option to
 | 
| 337 |     # do it before exiting runtime?
 | 
| 338 |     if opts.parser_mem_dump:
 | 
| 339 |       # This might be superstition, but we want to let the value stabilize
 | 
| 340 |       # after parsing.  bash -c 'cat /proc/$$/status' gives different results
 | 
| 341 |       # with a sleep.
 | 
| 342 |       time.sleep(0.001)
 | 
| 343 |       input_path = '/proc/%d/status' % os.getpid()
 | 
| 344 |       with open(input_path) as f, open(opts.parser_mem_dump, 'w') as f2:
 | 
| 345 |         contents = f.read()
 | 
| 346 |         f2.write(contents)
 | 
| 347 |         log('Wrote %s to %s (--parser-mem-dump)', input_path,
 | 
| 348 |             opts.parser_mem_dump)
 | 
| 349 | 
 | 
| 350 |     # -n prints AST, --show-ast prints and executes
 | 
| 351 |     if exec_opts.noexec or opts.show_ast:
 | 
| 352 |       if opts.ast_format == 'none':
 | 
| 353 |         print('AST not printed.', file=sys.stderr)
 | 
| 354 |       elif opts.ast_format == 'oheap':
 | 
| 355 |         # TODO: Make this a separate flag?
 | 
| 356 |         if sys.stdout.isatty():
 | 
| 357 |           raise RuntimeError('ERROR: Not dumping binary data to a TTY.')
 | 
| 358 |         f = sys.stdout
 | 
| 359 | 
 | 
| 360 |         enc = encode.Params()
 | 
| 361 |         out = encode.BinOutput(f)
 | 
| 362 |         encode.EncodeRoot(node, enc, out)
 | 
| 363 | 
 | 
| 364 |       else:  # text output
 | 
| 365 |         f = sys.stdout
 | 
| 366 | 
 | 
| 367 |         if opts.ast_format in ('text', 'abbrev-text'):
 | 
| 368 |           ast_f = fmt.DetectConsoleOutput(f)
 | 
| 369 |         elif opts.ast_format in ('html', 'abbrev-html'):
 | 
| 370 |           ast_f = fmt.HtmlOutput(f)
 | 
| 371 |         else:
 | 
| 372 |           raise AssertionError
 | 
| 373 |         abbrev_hook = (
 | 
| 374 |             ast_lib.AbbreviateNodes if 'abbrev-' in opts.ast_format else None)
 | 
| 375 |         tree = fmt.MakeTree(node, abbrev_hook=abbrev_hook)
 | 
| 376 |         ast_f.FileHeader()
 | 
| 377 |         fmt.PrintTree(tree, ast_f)
 | 
| 378 |         ast_f.FileFooter()
 | 
| 379 |         ast_f.write('\n')
 | 
| 380 | 
 | 
| 381 |       #util.log("Execution skipped because 'noexec' is on ")
 | 
| 382 |       status = 0
 | 
| 383 | 
 | 
| 384 |     if do_exec:
 | 
| 385 |       _tlog('Execute(node)')
 | 
| 386 |       status = ex.ExecuteAndRunExitTrap(node)
 | 
| 387 |       # NOTE: 'exit 1' is ControlFlow and gets here, but subshell/commandsub
 | 
| 388 |       # don't because they call sys.exit().
 | 
| 389 |       if opts.runtime_mem_dump:
 | 
| 390 |         # This might be superstition, but we want to let the value stabilize
 | 
| 391 |         # after parsing.  bash -c 'cat /proc/$$/status' gives different results
 | 
| 392 |         # with a sleep.
 | 
| 393 |         time.sleep(0.001)
 | 
| 394 |         input_path = '/proc/%d/status' % os.getpid()
 | 
| 395 |         with open(input_path) as f, open(opts.runtime_mem_dump, 'w') as f2:
 | 
| 396 |           contents = f.read()
 | 
| 397 |           f2.write(contents)
 | 
| 398 |           log('Wrote %s to %s (--runtime-mem-dump)', input_path,
 | 
| 399 |               opts.runtime_mem_dump)
 | 
| 400 | 
 | 
| 401 |     else:
 | 
| 402 |       status = 0
 | 
| 403 | 
 | 
| 404 |   return status
 | 
| 405 | 
 | 
| 406 | 
 | 
| 407 | def OilMain(argv):
 | 
| 408 |   spec = args.FlagsAndOptions()
 | 
| 409 |   # TODO: -h too
 | 
| 410 |   spec.LongFlag('--help')
 | 
| 411 |   spec.LongFlag('--version')
 | 
| 412 |   #builtin.AddOptionsToArgSpec(spec)
 | 
| 413 | 
 | 
| 414 |   try:
 | 
| 415 |     opts, opt_index = spec.Parse(argv)
 | 
| 416 |   except args.UsageError as e:
 | 
| 417 |     util.usage(str(e))
 | 
| 418 |     return 2
 | 
| 419 | 
 | 
| 420 |   if opts.help:
 | 
| 421 |     loader = util.GetResourceLoader()
 | 
| 422 |     builtin.Help(['oil-usage'], loader)
 | 
| 423 |     return 0
 | 
| 424 |   if opts.version:
 | 
| 425 |     # OSH version is the only binary in Oil right now, so it's all one version.
 | 
| 426 |     _ShowVersion()
 | 
| 427 |     return 0
 | 
| 428 | 
 | 
| 429 |   raise NotImplementedError('oil')
 | 
| 430 |   return 0
 | 
| 431 | 
 | 
| 432 | 
 | 
| 433 | def WokMain(main_argv):
 | 
| 434 |   raise NotImplementedError('wok')
 | 
| 435 | 
 | 
| 436 | 
 | 
| 437 | def BoilMain(main_argv):
 | 
| 438 |   raise NotImplementedError('boil')
 | 
| 439 | 
 | 
| 440 | 
 | 
| 441 | # TODO: Hook up to completion.
 | 
| 442 | SUBCOMMANDS = ['translate', 'format', 'deps', 'undefined-vars']
 | 
| 443 | 
 | 
| 444 | def OshCommandMain(argv):
 | 
| 445 |   """Run an 'oshc' tool.
 | 
| 446 | 
 | 
| 447 |   'osh' is short for "osh compiler" or "osh command".
 | 
| 448 | 
 | 
| 449 |   TODO:
 | 
| 450 |   - oshc --help
 | 
| 451 | 
 | 
| 452 |   oshc deps 
 | 
| 453 |     --path: the $PATH to use to find executables.  What about libraries?
 | 
| 454 | 
 | 
| 455 |     NOTE: we're leaving out su -c, find, xargs, etc.?  Those should generally
 | 
| 456 |     run functions using the $0 pattern.
 | 
| 457 |     --chained-command sudo
 | 
| 458 |   """
 | 
| 459 |   try:
 | 
| 460 |     action = argv[0]
 | 
| 461 |   except IndexError:
 | 
| 462 |     raise args.UsageError('oshc: Missing required subcommand.')
 | 
| 463 | 
 | 
| 464 |   if action not in SUBCOMMANDS:
 | 
| 465 |     raise args.UsageError('oshc: Invalid subcommand %r.' % action)
 | 
| 466 | 
 | 
| 467 |   try:
 | 
| 468 |     script_name = argv[1]
 | 
| 469 |   except IndexError:
 | 
| 470 |     script_name = '<stdin>'
 | 
| 471 |     f = sys.stdin
 | 
| 472 |   else:
 | 
| 473 |     try:
 | 
| 474 |       f = open(script_name)
 | 
| 475 |     except IOError as e:
 | 
| 476 |       util.error("Couldn't open %r: %s", script_name, os.strerror(e.errno))
 | 
| 477 |       return 2
 | 
| 478 | 
 | 
| 479 |   pool = alloc.Pool()
 | 
| 480 |   arena = pool.NewArena()
 | 
| 481 |   arena.PushSource(script_name)
 | 
| 482 | 
 | 
| 483 |   line_reader = reader.FileLineReader(f, arena)
 | 
| 484 |   _, c_parser = parse_lib.MakeParser(line_reader, arena)
 | 
| 485 | 
 | 
| 486 |   try:
 | 
| 487 |     node = c_parser.ParseWholeFile()
 | 
| 488 |   except util.ParseError as e:
 | 
| 489 |     ui.PrettyPrintError(e, arena, sys.stderr)
 | 
| 490 |     print('parse error: %s' % e.UserErrorString(), file=sys.stderr)
 | 
| 491 |     return 2
 | 
| 492 |   else:
 | 
| 493 |     # TODO: Remove this older form of error handling.
 | 
| 494 |     if not node:
 | 
| 495 |       err = c_parser.Error()
 | 
| 496 |       assert err, err  # can't be empty
 | 
| 497 |       ui.PrintErrorStack(err, arena, sys.stderr)
 | 
| 498 |       return 2  # parse error is code 2
 | 
| 499 | 
 | 
| 500 |   f.close()
 | 
| 501 | 
 | 
| 502 |   # Columns for list-*
 | 
| 503 |   # path line name
 | 
| 504 |   # where name is the binary path, variable name, or library path.
 | 
| 505 | 
 | 
| 506 |   # bin-deps and lib-deps can be used to make an app bundle.
 | 
| 507 |   # Maybe I should list them together?  'deps' can show 4 columns?
 | 
| 508 |   #
 | 
| 509 |   # path, line, type, name
 | 
| 510 |   #
 | 
| 511 |   # --pretty can show the LST location.
 | 
| 512 | 
 | 
| 513 |   # stderr: show how we're following imports?
 | 
| 514 | 
 | 
| 515 |   if action == 'translate':
 | 
| 516 |     # TODO: FIx this invocation up.
 | 
| 517 |     #debug_spans = opt.debug_spans
 | 
| 518 |     debug_spans = False
 | 
| 519 |     osh2oil.PrintAsOil(arena, node, debug_spans)
 | 
| 520 | 
 | 
| 521 |   elif action == 'format':
 | 
| 522 |     # TODO: autoformat code
 | 
| 523 |     raise NotImplementedError(action)
 | 
| 524 | 
 | 
| 525 |   elif action == 'deps':
 | 
| 526 |     deps.Deps(node)
 | 
| 527 | 
 | 
| 528 |   elif action == 'undefined-vars':  # could be environment variables
 | 
| 529 |     pass
 | 
| 530 | 
 | 
| 531 |   else:
 | 
| 532 |     raise AssertionError  # Checked above
 | 
| 533 | 
 | 
| 534 |   return 0
 | 
| 535 | 
 | 
| 536 | 
 | 
| 537 | # The valid applets right now.
 | 
| 538 | # TODO: Hook up to completion.
 | 
| 539 | APPLETS = ['osh', 'oshc']
 | 
| 540 | 
 | 
| 541 | 
 | 
| 542 | def AppBundleMain(argv):
 | 
| 543 |   login_shell = False
 | 
| 544 | 
 | 
| 545 |   b = os.path.basename(argv[0])
 | 
| 546 |   main_name, ext = os.path.splitext(b)
 | 
| 547 |   if main_name.startswith('-'):
 | 
| 548 |     login_shell = True
 | 
| 549 |     main_name = main_name[1:]
 | 
| 550 | 
 | 
| 551 |   if main_name == 'oil' and ext:  # oil.py or oil.ovm
 | 
| 552 |     try:
 | 
| 553 |       first_arg = argv[1]
 | 
| 554 |     except IndexError:
 | 
| 555 |       raise args.UsageError('Missing required applet name.')
 | 
| 556 | 
 | 
| 557 |     if first_arg in ('-h', '--help'):
 | 
| 558 |       builtin.Help(['bundle-usage'], util.GetResourceLoader())
 | 
| 559 |       sys.exit(0)
 | 
| 560 | 
 | 
| 561 |     if first_arg in ('-V', '--version'):
 | 
| 562 |       _ShowVersion()
 | 
| 563 |       sys.exit(0)
 | 
| 564 | 
 | 
| 565 |     main_name = first_arg
 | 
| 566 |     if main_name.startswith('-'):  # TODO: Remove duplication above
 | 
| 567 |       login_shell = True
 | 
| 568 |       main_name = main_name[1:]
 | 
| 569 |     argv0 = argv[1]
 | 
| 570 |     main_argv = argv[2:]
 | 
| 571 |   else:
 | 
| 572 |     argv0 = argv[0]
 | 
| 573 |     main_argv = argv[1:]
 | 
| 574 | 
 | 
| 575 |   if main_name in ('osh', 'sh'):
 | 
| 576 |     status = OshMain(argv0, main_argv, login_shell)
 | 
| 577 |     _tlog('done osh main')
 | 
| 578 |     return status
 | 
| 579 |   elif main_name == 'oshc':
 | 
| 580 |     return OshCommandMain(main_argv)
 | 
| 581 | 
 | 
| 582 |   elif main_name == 'oil':
 | 
| 583 |     return OilMain(main_argv)
 | 
| 584 |   elif main_name == 'wok':
 | 
| 585 |     return WokMain(main_argv)
 | 
| 586 |   elif main_name == 'boil':
 | 
| 587 |     return BoilMain(main_argv)
 | 
| 588 | 
 | 
| 589 |   # For testing latency
 | 
| 590 |   elif main_name == 'true':
 | 
| 591 |     return 0
 | 
| 592 |   elif main_name == 'false':
 | 
| 593 |     return 1
 | 
| 594 |   else:
 | 
| 595 |     raise args.UsageError('Invalid applet name %r.' % main_name)
 | 
| 596 | 
 | 
| 597 | 
 | 
| 598 | def main(argv):
 | 
| 599 |   try:
 | 
| 600 |     sys.exit(AppBundleMain(argv))
 | 
| 601 |   except NotImplementedError as e:
 | 
| 602 |     raise
 | 
| 603 |   except args.UsageError as e:
 | 
| 604 |     #builtin.Help(['oil-usage'], util.GetResourceLoader())
 | 
| 605 |     log('oil: %s', e)
 | 
| 606 |     sys.exit(2)
 | 
| 607 |   except RuntimeError as e:
 | 
| 608 |     log('FATAL: %s', e)
 | 
| 609 |     sys.exit(1)
 | 
| 610 |   finally:
 | 
| 611 |     _tlog('Exiting main()')
 | 
| 612 |     if _trace_path:
 | 
| 613 |       _tracer.Stop(_trace_path)
 | 
| 614 | 
 | 
| 615 | 
 | 
| 616 | if __name__ == '__main__':
 | 
| 617 |   # NOTE: This could end up as opy.InferTypes(), opy.GenerateCode(), etc.
 | 
| 618 |   if os.getenv('CALLGRAPH') == '1':
 | 
| 619 |     from opy import callgraph
 | 
| 620 |     callgraph.Walk(main, sys.modules)
 | 
| 621 |   else:
 | 
| 622 |     main(sys.argv)
 | 
| 623 | 
 |