-- A simple gdb interface for SciTE
-- Steve Donovan, 2007-2008
scite_require 'debugger.lua'

local sub = string.sub
local find = string.find
local len = string.len
local gsub = string.gsub

-- this is not only gdb-specific, but depends on looking for patterns
-- which depend on the implementation of std::string, etc.
function replaceoctal(s)
  local r='"'
  local i=2
  while i<=s:len() do
    if find(s,'^\\[23][0-7][0-7]',i) then
      r=r..string.char(tonumber(s:sub(i+1,i+3),8))
      i=i+4
    elseif s:sub(i,i)=='"' then
      return r..'"'
    elseif s:sub(i,i)=='\\' then
      r=r..s:sub(i,i+1)
      i=i+2
    else
      r=r..s:sub(i,i)
      i=i+1
    end
  end
  return r
end
function simplify_term(s)
    -- std::string
    if find(s,'^{%s*static npos = 4294967295,') or find(s,'^{%s*static npos = <optimized out>,') or find(s,'^%s*"') then
       local i=s:find('"')
       return replaceoctal(s:sub(i))
    end
    -- add your custom patterns here!
    -- arb structure; process recursively
    if sub(s,1,1) == '{' then
        local arg = sub(s,2,-2)
        return '{'..simplify(arg)..'}'
    else
        return s
    end
end

-- apply simplify_term recursively!
function simplify(str)    
    str = simplify_term(str)
    if str:sub(1,1)=='"' then
       return str
    end
    local res = gsub(str,'%b{}',simplify_term)
    return res
end

local inspect_pattern = '^(%$%d+) = '
local symbol_pattern = '[%w_]+'
local locals_pattern = '^'..symbol_pattern..' = '
local pointer_pattern = '%('..symbol_pattern..' %*%)'
local const_pointer_pattern = '%(const '..symbol_pattern..' %*%)'
local inspect_error_pattern = '^Cannot access memory at address 0x'
local last_arg

local function print_process(s,dbg)    
    local enum,expr = s:match(inspect_pattern..'(.*)')
	local argument = dbg,last_arg
	if not enum and not expr then
		expr = '(cannot evaluate)'
	end
	-- if the result was a pointer, then try to evaluate that pointer.
	if (expr:find(pointer_pattern) or expr:find(const_pointer_pattern)) and not expr:find '{' then
		last_arg = dbg.last_arg..' '..expr    
		dbg:inspect('*'..enum)
        -- this operation may not succeed, in which case GDB will output an
        -- error message to stderr. This following harmless command ensures
        -- that there is always some stdout output, avoiding a synch problem
        -- on Windows.
		spawner_command('printf "\n"')
	else
		if last_arg then
			dbg.last_arg = last_arg
			last_arg = nil
		end
		display(dbg.last_arg.." = "..simplify(expr))
	end
end

local function locals_process(s)
    local s1,s2 = find(s,locals_pattern)
	-- split this 'var = expr' line and try simplify the expression
    local var = sub(s,s1,s2 - 3)  -- miss out on the ' = '
    local _,_,expr = find(s,locals_pattern..'(.*)')
    display(var.." = "..simplify(expr))
end

local backtrace_pattern = '^#(%d+)'

function backtrace_process(s)
    local s = gsub(s,'0x%w+ in ','',1)
    print(s)
end

local finish_pattern = '^Value returned is %$%d+ ='

local function finish_process(s)
    local s1,s2 = find(s,finish_pattern)
    local expr = sub(s,s2+1)
    display('returned '..simplify(expr))
end

local was_error = false
-- commands where one ignores gdb's response 
local silent_command = {frame = true}
-- special actions for commands which require postprocessing
local postprocess_command = {
    backtrace = {pattern=backtrace_pattern,action=backtrace_process},
    print = {pattern=inspect_pattern, action=print_process, alt_pat=inspect_error_pattern},
    ['info locals'] = {pattern=locals_pattern, action=locals_process},
    display = {pattern=locals_pattern,action=locals_process},
    finish = {pattern=finish_pattern,action=finish_process,once=true}
}

local GTK = scite_GetProp('PLAT_GTK')

Gdb = class(Dbg)

function Gdb:init(root)
--	print('locals pattern"'..locals_pattern..'"')
	local esc = string.char(26)	
	self.prompt = '(GDB)'
        self.exit_code=0
        self.no_target_ext = ''
	self.cmd_file = root..'\\debug\\debug.ini'
	self.postprocess_command = postprocess_command
	-- commands where one ignores gdb's response
	self.silent_command={frame = true}
	if GTK then
		self.break_line = '^'..esc..esc..'(/[^:]+):(%d+)'
	else
		self.break_line = '^'..esc..esc..'(%a:[^:]+):(%d+)'
	end
	last_arg = nil
end

function Gdb:command_line(target)
	return '"'..props['SciteDefaultHome']..'\\debug.cmd" "'..props['FileDir']..'" "'..props['program.exe']..'" "'..props['1']..'"'
end

function Gdb:special_debugger_setup(out)
	out:write('set prompt (GDB)\\n\n') -- ensure gdb prompt has linefeed
	out:write('set height 0\n') -- disable gdb paging
--        out:write('set print demangle on\n')
--        out:write("python from printers import register_libstdcxx_printers\n")
--        out:write("python register_libstdcxx_printers (None)\n")
	if scite_GetPropBool('debug.breakpoint.pending',GTK) then
		-- unrecognized file:line assumed to be 'pending'
		out:write('set breakpoint pending on\n')
	end
    local env = scite_GetProp('debug.environment')
    if env then
        for _,e in ipairs(split(env,';')) do
            local var,val = splitv(e,'=')
            out:write(('set env %s %s\n'):format(var,val))
        end
    end
	-- normally gdb will not let you set breakpoints if there's no debug information.
	-- Fortunately, it's not picky about exactly what symbols are available, so
	-- we placate it with a little stub.
    if not self.host_symbols then
		local stub = slashify(join(extman_Path(),choose(GTK,'stubby.so','stubby.dll')))
        out:write('symbol-file ',stub,'\n')
    end
	-- @doc under Windows it's usually better to force GDB to create a new console window
	-- for a command-line application.
	if not GTK then
		out:write('set new-console on\n')
	end
end

function Gdb:detect_program_end(line)
    local no_program = find(line,"No executable specified, use `target exec'%.")
    if no_program then
      print 'Не найден debug\\_all_.exe'
    end
	-- detecting normal end of program execution
    local res1 = find(line,'^%[Inferior %d+ %(process %d+%) exited normally%]')
    if res1 then
      self.exit_code=0
    end
    local res2,_,s=find(line,'^%[Inferior %d+ %(process %d+%) exited with code (%d+)%]')
    if res2 then
      self.exit_code=tonumber(s)
    end
    local res=res1 or res2 or no_program
    return res,false
end

function Gdb:detect_program_crash(line)
	return find(line,'^Program received signal ')
end

register_debugger('gdb','*',Gdb)
