#!/usr/bin/env python
"""
A gdb-compatible frontend for lldb that implements just enough
commands to run the tests in the debuginfo-tests repository with lldb.
"""

# ----------------------------------------------------------------------
# Auto-detect lldb python module.
import commands, platform, os,  sys
try:
    # Just try for LLDB in case PYTHONPATH is already correctly setup.
    import lldb
except ImportError:
    lldb_python_dirs = list()
    # lldb is not in the PYTHONPATH, try some defaults for the current platform.
    platform_system = platform.system()
    if platform_system == 'Darwin':
        # On Darwin, try the currently selected Xcode directory
        xcode_dir = commands.getoutput("xcode-select --print-path")
        if xcode_dir:
            lldb_python_dirs.append(os.path.realpath(xcode_dir +
'/../SharedFrameworks/LLDB.framework/Resources/Python'))
            lldb_python_dirs.append(xcode_dir +
'/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
        lldb_python_dirs.append(
'/System/Library/PrivateFrameworks/LLDB.framework/Resources/Python')
    success = False
    for lldb_python_dir in lldb_python_dirs:
        if os.path.exists(lldb_python_dir):
            if not (sys.path.__contains__(lldb_python_dir)):
                sys.path.append(lldb_python_dir)
                try:
                    import lldb
                except ImportError:
                    pass
                else:
                    print 'imported lldb from: "%s"' % (lldb_python_dir)
                    success = True
                    break
    if not success:
        print "error: couldn't locate the 'lldb' module, please set PYTHONPATH correctly"
        sys.exit(1)
# ----------------------------------------------------------------------

# Command line option handling.
import argparse
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--quiet', '-q', action="store_true", help='ignored')
parser.add_argument('-batch', action="store_true",
                    help='exit after processing comand line')
parser.add_argument('-n', action="store_true", help='ignore .lldb file')
parser.add_argument('-x', dest='script', type=file, help='execute commands from file')
parser.add_argument("target", help="the program to debug")
args = parser.parse_args()


# Create a new debugger instance.
debugger = lldb.SBDebugger.Create()
debugger.SkipLLDBInitFiles(args.n)

# Make sure to clean up the debugger on exit.
import atexit
def on_exit():
    debugger.Terminate()
atexit.register(on_exit)

# Don't return from lldb function calls until the process stops.
debugger.SetAsync(False)

# Create a target from a file and arch.
arch = os.popen("file "+args.target).read().split()[-1]
target = debugger.CreateTargetWithFileAndArch(args.target, arch)

if not target:
    print "Could not create target", args.target
    sys.exit(1)

if not args.script:
    print "Interactive mode is not implemented."
    sys.exit(1)

import re
for command in args.script:
    # Strip newline and whitespaces and split into words.
    cmd = command[:-1].strip().split()
    if not cmd:
        continue

    print '> %s'% command[:-1]

    try:
        if re.match('^r|(run)$', cmd[0]):
            error = lldb.SBError()
            launchinfo = lldb.SBLaunchInfo([])
            launchinfo.SetWorkingDirectory(os.getcwd())
            process = target.Launch(launchinfo, error)
            print error
            if not process or error.fail:
                state = process.GetState()
                print "State = %d" % state
                print """
ERROR: Could not launch process.
NOTE: There are several reasons why this may happen:
  * Root needs to run "DevToolsSecurity --enable".
  * Older versions of lldb cannot launch more than one process simultaneously.
"""
                sys.exit(1)

        elif re.match('^b|(break)$', cmd[0]) and len(cmd) == 2:
            if re.match('[0-9]+', cmd[1]):
                # b line
                mainfile = target.FindFunctions('main')[0].compile_unit.file
                print target.BreakpointCreateByLocation(mainfile, int(cmd[1]))
            else:
                # b file:line
                file, line = cmd[1].split(':')
                print target.BreakpointCreateByLocation(file, int(line))

        elif re.match('^ptype$', cmd[0]) and len(cmd) == 2:
            # GDB's ptype has multiple incarnations depending on its
            # argument (global variable, function, type).  The definition
            # here is for looking up the signature of a function and only
            # if that fails it looks for a type with that name.
            # Type lookup in LLDB would be "image lookup --type".
            for elem in target.FindFunctions(cmd[1]):
                print elem.function.type
                continue
            print target.FindFirstType(cmd[1])

        elif re.match('^po$', cmd[0]) and len(cmd) > 1:
            try:
                opts = lldb.SBExpressionOptions()
                opts.SetFetchDynamicValue(True)
                opts.SetCoerceResultToId(True)
                print target.EvaluateExpression(' '.join(cmd[1:]), opts)
            except:
                # FIXME: This is a fallback path for the lab.llvm.org
                # buildbot running OS X 10.7; it should be removed.
                thread = process.GetThreadAtIndex(0)
                frame = thread.GetFrameAtIndex(0)
                print frame.EvaluateExpression(' '.join(cmd[1:]))

        elif re.match('^p|(print)$', cmd[0]) and len(cmd) > 1:
            thread = process.GetThreadAtIndex(0)
            frame = thread.GetFrameAtIndex(0)
            print frame.EvaluateExpression(' '.join(cmd[1:]))

        elif re.match('^n|(next)$', cmd[0]):
            thread = process.GetThreadAtIndex(0)
            thread.StepOver()

        elif re.match('^q|(quit)$', cmd[0]):
            sys.exit(0)

        else:
            print debugger.HandleCommand(' '.join(cmd))

    except SystemExit:
        raise
    except:
        print 'Could not handle the command "%s"' % ' '.join(cmd)

