/* //next lines (up to first line with '* /') are used for including to debugging program use io,proc,ui .kybldebuggerinput=proc.pipe('KyblDebuggerOut',false); .kybldebuggeroutput=proc.pipe('KyblDebuggerIn',false); .kybldebuggerfdepth=0; .kybldebuggermaxfdepth=99999; .kybldebuggermaxbdepth=99999; .kybldebuggergotoline=null; function KyblDebugInfo(linenum,blockdepth,fileposition=0,variables=[]) if isstr(linenum) then //there is some message, not line number case linenum in 'quit': io.writem(.kybldebuggeroutput,['quit']); else throw null; end; else //send line # and depth if .kybldebuggerfdepth > .kybldebuggermaxfdepth or (.kybldebuggerfdepth = .kybldebuggermaxfdepth and blockdepth > .kybldebuggermaxbdepth) then return; end; if .kybldebuggergotoline # null then if .kybldebuggergotoline # linenum then return; else .kybldebuggergotoline=null; end; end; io.writem(.kybldebuggeroutput,['breakpoint',linenum,.kybldebuggerfdepth,fileposition]); while true do response=io.readm(.kybldebuggerinput); case response[0] in 'showvariables': io.writem(.kybldebuggeroutput,['variablesstart',len(variables)]); names=keys(variables); i=0; for variable in variables do try io.writem(.kybldebuggeroutput,['variable',names[i],variable]); catch e by io.writem(.kybldebuggeroutput,['variable',names[i],'Contains native object']); end; i++; end; in 'showoutput': while true do ui.keys(true); key=ui.cmd(); if not isarray(key) and key < 0 then break; end; end; io.writem(.kybldebuggeroutput,['ok']); in 'stepover': .kybldebuggermaxfdepth=.kybldebuggerfdepth; .kybldebuggermaxbdepth=99999; break; in 'stepinto': .kybldebuggermaxfdepth=99999; .kybldebuggermaxbdepth=99999; break; in 'outoffunc': .kybldebuggermaxfdepth=.kybldebuggerfdepth-1; .kybldebuggermaxbdepth=99999; break; in 'outofblock': .kybldebuggermaxfdepth=.kybldebuggerfdepth; .kybldebuggermaxbdepth=blockdepth-1; break; in 'runtothisline': .kybldebuggermaxfdepth=99999; .kybldebuggermaxbdepth=99999; .kybldebuggergotoline=linenum; break; else print response; throw response; end; end; if io.avail(.kybldebuggerinput) # 0 then //some waste in pipe io.read(.kybldebuggerinput,io.avail(.kybldebuggerinput)); ui.msg('Some waste in pipe.'); end; end; end; */ use array,files,graph,io,math,proc,ui const fontsize=13; const textpadding=1; .thisprogram='KyblDebugger'; .debuggerversion='1.3'; .lastused=[]; .maxlastused=3; .debugprogram=null; .kybldebuglines=null; .displayeddebugapp=true; .line_ignoreuntil=null; .actualdepth=null; .insidefunction=null; .iscomment=null; .activevariables=null; .watchingvariables=true; .programstoremove=[]; function LoadSettings() try f=io.open(.thisprogram+'.cfg'); if f = null then return false; end; .keys=io.readm(f); .lastused=io.readm(f); .watchingvariables=io.readm(f); io.close(f); catch e by return false; end; return true; end; function SaveSettings() try f=io.create(.thisprogram+'.cfg'); io.writem(f,.keys); io.writem(f,.lastused); io.writem(f,.watchingvariables); io.close(f); catch e by return false; end; return true; end; function SetKeys() types=['stepover': 'Step over','stepinto': 'Step into','outoffunc': 'Step out of function','outofblock': 'Step out of block','runtothisline': 'Run to this line','showoutput': 'Show output','showvariables': 'Show variables','editfile': 'Edit file','quit': 'Quit debugging']; .keys=[]; ts=keys(types); for t in ts do while true do io.write(io.stdout,'Press key to: '+types[t]); do ui.keys(false); key=ui.cmd(); until key >= 0; io.writeln(io.stdout,' <'+key+'>'); //check if exists already alreadyused=false; for t2 in ts do if t2 = t then //only previous break; end; if .keys[t2] = key then alreadyused=true; ui.msg('Key is already used for action "'+types[t2]+'". Please press other key.','Key used'); break; end; end; if alreadyused = false then break; end; end; .keys[t]=key; end; io.writeln(io.stdout,''); end; function PrintCode(linenum) lines=math.floor(graph.size()[1]/(.fontsize+2*.textpadding))-1; graph.font(['LatinPlain12',.fontsize,false,false]); startline=linenum-math.floor((lines-1)/2); if startline < 0 then startline=0; end; graph.clear(); for i=0 to lines-1 do if i+startline = len(.kybldebuglines) then break; end; if i+startline = linenum then graph.brush(0x30D0FF); graph.pen(0x30D0FF); graph.rect(0,i*(2*.textpadding+.fontsize)+.textpadding,graph.size()[0],2*.textpadding+.fontsize); graph.pen(graph.black); end; graph.text(5,i*(2*.textpadding+.fontsize)+.textpadding+.fontsize,.kybldebuglines[startline+i]); end; graph.show(); end; function Breakpoint(linenum,depth) PrintCode(linenum); graph.brush(0xFFD030); graph.pen(0xFFD030); graph.rect(0,graph.size()[1]-1.2*.fontsize,graph.size()[0],1.2*.fontsize); graph.pen(graph.black); graph.text(5,graph.size()[1]-.fontsize/3,'Line '+(linenum+1)+', depth '+depth); if .displayeddebugapp then proc.show(.thisprogram); end; while true do ui.keys(false); key=ui.cmd(); .displayeddebugapp=false; keyfunc=array.index(.keys,key); if keyfunc # -1 then keyfunc=keys(.keys)[keyfunc]; return keyfunc; //stepinto etc. end; end; end; function IsntIgnored(content,onlyremove=false) if .iscomment then return false; end; if len(.line_ignoreuntil) > 0 then last=len(.line_ignoreuntil)-1; if content = .line_ignoreuntil[last] then array.remove(.line_ignoreuntil,last); end; if onlyremove then return true; end; return false; end; return true; end; function ShiftBy(line,length,data) if not data['valid'] then data['position']+=length; end; return substr(line,length); end; function SetActiveVariables(data) if data['activevariables_line'] = null then activevariables_line=''; for variable in .activevariables do activevariables_line=activevariables_line+'\''+variable+'\':'+variable+','; end; if len(activevariables_line) > 0 then activevariables_line=substr(activevariables_line,0,len(activevariables_line)-1); end; data['activevariables_line']=activevariables_line; end; end; function SetNewVariable(variable) pos=array.index(.activevariables,variable); if pos # -1 then array.remove(.activevariables,pos); end; array.insert(.activevariables,0,variable); end; function GetValidPosOfLine(line,watchingvariables) l=line; data=['valid': false,'position': 0,'activevariables_line': null]; while true do prevpos=data['position']; if len(l) = 0 then break; end; ch=code(substr(l,0,1))[0]; l=ShiftBy(l,1,data); if ch = code('*')[0] then //check if end of comment if len(l) > 0 then if substr(l,0,1) = '/' and .iscomment then .iscomment=false; l=ShiftBy(l,1,data); end; end; if IsntIgnored(char(ch)) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; elsif .iscomment then //nothing elsif ch = code(' ')[0] or ch = code(' ')[0] or ch = code(';')[0] then //nothing elsif ch = code('=')[0] then if watchingvariables and IsntIgnored(char(ch),true) and len(.line_ignoreuntil) = 0 then //not in cond., it's variable setting variable=trim(substr(line,0,len(line)-len(l)-1)); pos=rindex(variable,';'); if pos # -1 then variable=substr(variable,pos+1); end; pos=rindex(variable,']'); if pos # -1 then pos2=index(variable,'['); if pos2 # -1 and pos2 < pos then variable=trim(delete(variable,pos2,pos-pos2+1)); end; end; case substr(variable,len(variable)-1) in '+','-','*','/': variable=trim(substr(variable,0,len(variable)-1)); end; pos=rindex(variable,' '); if pos # -1 then variable=substr(variable,pos+1); end; if not .insidefunction and substr(variable,0,1) # '.' then variable='.'+variable; end; SetNewVariable(variable); end; elsif ch = code('(')[0] then if IsntIgnored(char(ch),true) then append(.line_ignoreuntil,')'); end; elsif ch = code('[')[0] then if IsntIgnored(char(ch),true) then append(.line_ignoreuntil,']'); end; elsif ch = code(')')[0] then IsntIgnored(char(ch)); elsif ch = code(']')[0] then IsntIgnored(char(ch)); elsif ch = code('/')[0] then //check if comment if len(l) > 0 then case substr(l,0,1) in '*': .iscomment=true; in '/': break; //comment to end of line end; end; if IsntIgnored(char(ch)) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; elsif ch = code('\'')[0] or ch = code('\"')[0] then while len(l) > 0 do pos=index(l,char(ch)); if pos = -1 then l=''; break; end; i=pos-1; while i >= 0 and substr(l,i,1) = '\\' do i--; end; l=ShiftBy(l,pos+1,data); if (pos-i+1)%2 = 0 then //not escaped, end of string break; end; end; if IsntIgnored(char(ch)) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; else string=char([ch]); if len(l) = 0 then ch=-1; else ch=code(substr(l,0,1))[0]; end; while (ch >= code('a')[0] and ch <= code('z')[0]) or (ch >= code('A')[0] and ch <= code('Z')[0]) or (ch >= code('0')[0] and ch <= code('9')[0]) do string=string+char([ch]); l=ShiftBy(l,1,data); if len(l) = 0 then ch=-1; else ch=code(substr(l,0,1))[0]; end; end; case string in 'case': .actualdepth++; if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; append(.line_ignoreuntil,'in'); in 'catch': IsntIgnored(string); append(.line_ignoreuntil,'by'); in 'do': if IsntIgnored(string) then .actualdepth++; end; in 'else': //nothing in 'elsif': IsntIgnored(string); append(.line_ignoreuntil,'then'); in 'end': //nothing if .insidefunction and .actualdepth = 1 then if data['depthdecr_in'] = null then data['depthdecr_in']=[]; end; append(data['depthdecr_in'],len(line)-len(l)-len('end')); //remove local vars for i=len(.activevariables)-1 to 0 by -1 do if substr(.activevariables[i],0,1) # '.' then array.remove(.activevariables,i); end; end; .insidefunction=false; end; if .actualdepth < 1 then print 'error on line '+line+')'; end; .actualdepth--; in 'for': .actualdepth++; if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; append(.line_ignoreuntil,'do'); if watchingvariables then vartmp=trim(l); pos=index(vartmp,' '); if pos # -1 then vartmp=substr(vartmp,0,pos); end; pos=index(vartmp,'='); if pos # -1 then vartmp=substr(vartmp,0,pos); end; if vartmp # '' then SetNewVariable(vartmp); end; end; in 'forward': .actualdepth--; array.remove(data,'depthincr'); .insidefunction=false; //remove local vars for i=len(.activevariables)-1 to 0 by -1 do if substr(.activevariables[i],0,1) # '.' then array.remove(.activevariables,i); end; end; in 'function': append(.line_ignoreuntil,'('); .actualdepth++; data['depthincr']=true; .insidefunction=true; if watchingvariables then varstmp=trim(l); pos=index(varstmp,')'); if pos # -1 then varstmp=trim(substr(varstmp,0,pos)); end; pos=index(varstmp,'('); if pos # -1 then varstmp=trim(substr(varstmp,pos+1)); end; varsarray=[]; while varstmp # '' do pos=index(varstmp,','); if pos # -1 then vartmp=trim(substr(varstmp,0,pos)); varstmp=trim(substr(varstmp,pos+1)); else vartmp=varstmp; varstmp=''; end; pos=index(vartmp,'='); if pos # -1 then vartmp=trim(substr(vartmp,0,pos)); end; if vartmp # '' then append(varsarray,vartmp); end; end; for i=len(varsarray)-1 to 0 by -1 do SetNewVariable(varsarray[i]); end; end; in 'if': .actualdepth++; if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; append(.line_ignoreuntil,'then'); in 'in': IsntIgnored(string); if len(.line_ignoreuntil) = 0 or .line_ignoreuntil[len(.line_ignoreuntil)-1] # 'do' then //in 'case', not 'for' append(.line_ignoreuntil,':'); end; in 'return': //decr. before if data['depthdecr_in'] = null then data['depthdecr_in']=[]; end; append(data['depthdecr_in'],len(line)-len(l)-len('return')); if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; end; in 'try': .actualdepth++; IsntIgnored(string); in 'until': if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); .actualdepth--; end; in 'while': .actualdepth++; if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; append(.line_ignoreuntil,'do'); else if IsntIgnored(string) then data['position']=prevpos; data['valid']=true; SetActiveVariables(data); end; end; end; end; return data; end; function VarAsString(variable) if isstr(variable) then return '\''+variable+'\''; elsif isarray(variable) and len(variable) = 0 then return 'empty array'; else return str(variable); end; end; function ShowVariables(variables,where) if where = null then title='Variables'; else title='Variable '+where; end; if isarray(variables) and len(variables) > 0 then listtext=[]; wheres=[]; varkeys=keys(variables); i=0; for variable in variables do if varkeys[i] = null then varkey=i; else varkey=VarAsString(varkeys[i]); end; if where = null then append(listtext,varkeys[i]+'='+VarAsString(variable)); append(wheres,varkeys[i]); else append(listtext,varkey+':'+VarAsString(variable)); append(wheres,where+'['+varkey+']'); end; i++; end; choice=0; while true do choice=ui.list(listtext,false,[choice],title); if choice = null then break; end; choice=choice[0]; if where = null or true then newwhere=wheres[choice]; else newwhere=where+'['+VarAsString(wheres[choice])+']'; end; ShowVariables(variables[choice],newwhere); end; else ui.msg(VarAsString(variables),title); end; end; function CreateDebugProgram(name,watchingvariables) infile=.thisprogram+'.m'; outfile=.debugprogram+'.m'; quickprepare=false; if files.exists(outfile) and files.time(outfile) > files.time(infile) then out=io.open(outfile); pos=io.size(out)-14-2; if pos >= 0 then io.seek(out,pos); if io.read(out,14) = '//KyblDebugEnd' then //correct end of file quickprepare=true; end; end; io.close(out); end; print 'Preparing '+name+'...'; if not quickprepare then out=io.create(outfile,io.raw); io.write(out,char([239,187,191])); //utf-8 signature io.ces(out,io.utf8); inp=io.open(infile,false,io.bom); io.read(inp,2); // skip comment while true do line=io.readln(inp,1000); if line = '*/' then break; end; io.println(out,line); end; io.close(inp); end; inp=io.open(name+'.m',false,io.bom); linenum=0; fileposition=0; while true do line=io.readln(inp,1000); if line = null then break; end; if not quickprepare then actualdepthbefore=.actualdepth; data=GetValidPosOfLine(line,watchingvariables); linetowrite=line; if data['depthdecr_in'] # null then //inserted returns for i=len(data['depthdecr_in'])-1 to 0 by -1 do linetowrite=substr(linetowrite,0,data['depthdecr_in'][i])+';.kybldebuggerfdepth--;'+substr(linetowrite,data['depthdecr_in'][i]); end; end; if data['valid'] then linetowrite=substr(linetowrite,0,data['position'])+';KyblDebugInfo('+linenum+','+actualdepthbefore+','+fileposition+',['+data['activevariables_line']+']);'+substr(linetowrite,data['position']); end; if data['depthdecr'] # null then io.write(out,';.kybldebuggerfdepth--;'); end; io.writeln(out,linetowrite); if data['depthincr'] # null then io.write(out,';.kybldebuggerfdepth++;'); end; fileposition+=len(line)+1; //+1=enter linenum++; end; append(.kybldebuglines,line); end; if not quickprepare then io.writeln(out,';KyblDebugInfo(\'quit\','+.actualdepth+');'); if .iscomment or len(.line_ignoreuntil) # 0 then if not ui.confirm('Parsing error. Continue?') then return false; end; else io.writeln(out,'//KyblDebugEnd'); end; io.close(out); end; io.close(inp); return true; end; function DebugProgram(name,watchingvariables) .kybldebuglines=[]; .actualdepth=0; .insidefunction=false; .line_ignoreuntil=[]; .iscomment=false; .activevariables=[]; .debugprogram=name+'-debug'; try if proc.runs(.debugprogram) then proc.stop(.debugprogram); proc.close(.debugprogram); end; catch e by //ok end; if not CreateDebugProgram(name,watchingvariables) then return; end; pipein=proc.pipe('KyblDebuggerIn'); pipeout=proc.pipe('KyblDebuggerOut'); if io.avail(pipein) # 0 then //some waste in pipe io.read(pipein,io.avail(pipein)); end; print 'Debugging...'; proc.run(.debugprogram); stopdebug=false; goedit=false; wait=50; while not stopdebug do if io.avail(pipein) > 0 then wait=50; if .displayeddebugapp then proc.show(.thisprogram); .displayeddebugapp=false; end; msg=io.readm(pipein); case msg[0] in 'breakpoint': linenum=msg[1]; depth=msg[2]; fileposition=msg[3]; if io.avail(pipein) # 0 then print 'waste in pipe #1'; io.read(pipein,io.avail(pipein)); end; while true do response=Breakpoint(linenum,depth); if response = 'showoutput' then io.writem(pipeout,['showoutput']); proc.show(.debugprogram); reply=io.readm(pipein); if reply[0] # 'ok' then throw 'Unrecognized reply ('+reply+')'; end; proc.show(.thisprogram); elsif response = 'showvariables' then if not watchingvariables then ui.error('You don\'t allowed variables watching during start of debugging.'); else io.writem(pipeout,['showvariables']); reply=io.readm(pipein); if reply[0] # 'variablesstart' then throw 'Unrecognized reply ('+reply+')'; end; if reply[1] = 0 then ui.msg('No active variables yet.','Note'); else variables=[]; for count=reply[1]-1 to 0 by -1 do reply=io.readm(pipein); if reply[0] # 'variable' then throw 'Unrecognized reply '+reply; end; variables[reply[1]]= reply[2]; end; ShowVariables(variables,null); end; end; elsif response = 'editfile' then if ui.confirm('Exit debugger and start edit '+name+' at line '+(linenum+1)+'?','Go to editor') then stopdebug=true; goedit=fileposition; break; end; elsif response = 'quit' then stopdebug=true; break; else io.writem(pipeout,[response]); break; end; end; in 'quit': break; else print msg; throw msg; end; else if not proc.runs(.debugprogram) then ui.msg('Program terminated.'); stopdebug=true; else sleep(wait); wait=wait*1.5; if wait > 300 then wait=300; if not .displayeddebugapp then proc.show(.debugprogram); .displayeddebugapp=true; end; end; end; end; end; if io.avail(pipein) # 0 then //some waste in pipe print 'waste in pipe #2'; io.read(pipein,io.avail(pipein)); end; io.close(pipein); io.close(pipeout); try proc.stop(.debugprogram); catch e by end; try proc.close(.debugprogram); catch e by end; graph.hide(); .programstoremove[.debugprogram]=true; print 'Program finished.\n'; if .displayeddebugapp then proc.show(.thisprogram); .displayeddebugapp=false; end; return goedit; end; function ChooseProgramToDebug() apps=files.scan('*.m',0,0); newapps=[]; for app in apps do if len(app) < 8 or substr(app,len(app)-8) # '-debug.m' then append(newapps,substr(app,0,len(app)-2)); end; end; ret=ui.form(['Choose action or program to debug','Program to debug': array.concat(.lastused,['',''],newapps),'Variables watching (slower)': .watchingvariables],'KýblDebugger Menu'); if ret = null then return null; else return ['program': ret[0],'watchingvariables': ret[1]]; end; end; cls(); print 'Welcome in KýblDebugger v'+.debuggerversion+'\n'; .lastused=[]; if not LoadSettings() then print 'This is the first start of KýblDebugger. It\'s needed to set keys.\nPlease press any key to continue.\n'; ui.keys(false); ui.cmd(); SetKeys(); print 'Setting was saved to '+.thisprogram+'.cfg. Now you can choose program to debugging.\nPlease press any key to continue.\n'; SaveSettings(); ui.keys(false); ui.cmd(); end; while true do program=ChooseProgramToDebug(); if program = null then print 'Bye!'; .programstoremove=keys(.programstoremove); for program in .programstoremove do try files.delete(program+'.m'); catch e by end; end; break; elsif program['program'] = '' then SetKeys(); SaveSettings(); elsif program['program'] = '' then ui.msg('KýblDebugger v'+.debuggerversion+'\nThe first debugger for m-shell\n\nNote: Not all syntaxes are supported yet. If you have problems with executing, make sure that all each commands are just in one line.\n\n(C) Aleš Janda, KÝBLSoft 2008\nwww.kyblsoft.cz/mobil'); elsif not files.exists(program['program']+'.m') then ui.error('File '+program['program']+'.m not found!'); else .watchingvariables=program['watchingvariables']; index=array.index(.lastused,program['program']); if index # -1 then array.remove(.lastused,index); end; array.insert(.lastused,0,program['program']); if len(.lastused) > .maxlastused then array.remove(.lastused,.maxlastused,len(.lastused)-.maxlastused); end; SaveSettings(); goedit=DebugProgram(program['program'],program['watchingvariables']); if goedit # false then print 'Going to editor...'; files.edit(program['program']+'.m',goedit); break; end; end; sleep(100); //while parse error, next dialog isn't shown without this delay (probably m-shell bug) end; proc.close();