-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb5.ps
654 lines (581 loc) · 19.5 KB
/
db5.ps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
%!
%db5.ps
/signalerror where{pop}{/signalerror/.error load def}ifelse
/comment{{currentfile token pop/end-comment eq{exit}if}loop}def
comment
Re-write 5 of postscript source-level debugger.
Previous versions: (https://groups.google.com/d/topic/comp.lang.postscript/PDn8CxfL1qc/discussion)
(http://code.google.com/p/xpost/downloads/detail?name=db4.ps) requires also:
(http://code.google.com/p/xpost/downloads/detail?name=ds.ps).
Accepts executable file, string, or procedure as argument.
'traceon' presents a concise 1-line summary of the current object and the stack picture.
Catches errors and issues an error prompt, maintaining the state and context of the
program at the point of error.
This file defines the following names
comment ... end-comment comment block notation for extended comments
%%Hacker Note that an extended comment (ie. ^^-all of this surrounding text-VV), unlike a %normal comment
%%must follow legal postscript syntax so all braces must match, eg. <hex-only-please>, {}.
proc|file|string debug - invoke the debugger
- resume - resume a session after 'db-error exit' or 'step: q'
- traceon - enable tracing
- traceoff - disable tracing
- stepon - enable single-stepping
- stepoff - disable single-stepping
- steponnext - enable single-stepping on the next object
- dumpstack - print the contents of the debugging stack
- skip - skip the current object
These names may be used to select options before invoking debug,
or to change options at the db-error prompt, which is a "for-real-reals" ps prompt
with errors suppressed. After executing your command line, db-error will try to
re-execute the failed operation, unless you 'skip'. Note that if you 'skip' from
the db-error prompt, you probably want to 'pop', too, because the object is on
the stack as well as in the next-object cache. In fact whenever trying to "fix"
an error on the fly, consider carefully how the stacks are left and whether you
need to 'pop' the last object.
The step: prompt accepts single-letter commands (followed by Return):
c continue disable single-stepping
n next execute the next object (or loop) atomically
b bypass skip next object
s step step into next object (or loop)
p prompt run executive right here, resume when it exits
q quit exit the debugger (may be 'resume'ed)
blank-line repeat previous command (initial "previous command" is 'n'ext)
To invoke from an external program:
%!
%my buggy code
(db5.ps)run traceon currentfile cvx debug
blah blah blah undefined undefined
Insert 'stepon' in your code to trigger a "breakpoint" and begin
single-stepping at that point. A more elaborate way of doing a breakpoint
would be to insert an undefined name which will trigger the db-error
prompt, from which you may select options (like 'stepon') or execute
some postscript code at that point.
Bugs.
When single-stepping with tracing on, there is some garbage trace of twiddling
the /step variable.
Debugging the debugger.
If you ever really need to debug the debugger (or are very very curious),
it is possible to extract the dbdict from one of the external procedures.
eg. /stepon load 0 get { exch =only ( )=only == } forall %using gs =only
end-comment
% prepare internal dict known as //dbdict
% this dict is //immediately-loaded into the procedures
% so it does not need to be on the dict stack
/dbdict <<
/nextobject null
/stack 500 array % internal stack array this is a typical level-1 limit
% it can be made larger for larger programs
/ptr -1 % stack cursor -1::empty
/trace false % are we producing a trace?
/step false % are we pausing at each command?
/delaystep false
/enddefbegin { currentdict 3 1 roll end def begin } % define an external function
>> dup begin def
% Portable implementation of gs marvelous =only and ==only
/=buffer 256 string def
/=nonldict
<<
/operatortype { pop (-oper-) print }
/filetype { pop (-file-) print }
/nametype { dup xcheck not {(/)print} if //=buffer cvs print }
/stringtype { { print } stopped { pop (-string-) print } if }
/integertype { //=buffer cvs print }
/realtype { //=buffer cvs print }
/arraytype { pop (-array-) print }
/packedarraytype 1 index
/dicttype { pop (-dict-) print }
/booleantype { {(true)}{(false)} ifelse print }
/nulltype { pop (null) print }
/marktype { pop (-mark-) print }
/savetype { pop (-save-) print }
>> def
/=nonl {
//=nonldict 1 index type get exec
} def
/==nonldict
<<
/operatortype { pop (-oper-) tprint }
/filetype { pop (-file-) tprint }
/nametype { dup xcheck not {(/)tprint} if //=buffer cvs tprint }
/stringtype { dup rcheck { dostring }{ (-string-) tprint } ifelse }
/integertype { //=buffer cvs tprint }
/realtype { //=buffer cvs tprint }
/arraytype { dup rcheck not { pop (-array-) tprint }{
dup xcheck { ({) tprint doarray (}) tprint }{
([) tprint doarray (]) tprint
} ifelse } ifelse }
/packedarraytype 1 index
/dicttype { pop (-dict-) tprint }
/booleantype { {(true)}{(false)} ifelse tprint }
/nulltype { pop (null) tprint }
/marktype { pop (-mark-) tprint }
/savetype { pop (-save-) tprint }
/tprint {
charcount 1 index length add 80 gt {
(\n) print
/charcount 1 index length def
}{
/charcount charcount 2 index length add def
} ifelse
print
}
/dostring {
(\() tprint
{
dup 32 lt 1 index 126 gt or {
(\\) tprint
1000 add
//=buffer cvs
1 1 index length 1 sub getinterval
tprint
}{
( ) dup 0 4 3 roll put tprint
} ifelse
} forall
(\)) tprint
}
/doarray {
dup length 0 eq {pop}{
dup 0 get dup type exec
1 1 index length 1 sub getinterval
{
( ) tprint
dup type exec
} forall
} ifelse
}
>> def
/==nonl {
//==nonldict begin
/charcount 0 def
dup type exec
end
} def
% push and pop the dbdict<stack>
/pushs { % proc|file|string|name pushs -
//dbdict /ptr 2 copy get 1 add put % ++dbdict<ptr>
//dbdict /stack get //dbdict /ptr get 3 -1 roll put % dbdict<stack>[dbdict<ptr>] = _
} def
/pops { % - pops -
//dbdict /ptr get 0 ge {
//dbdict /stack get //dbdict /ptr get get % dbdict<stack>[dbdict<ptr>]
dup type /filetype eq {closefile}{pop} ifelse % close if file
//dbdict /ptr 2 copy get 1 sub put % --dbdict<ptr>
} if
} def
% executable source type handlers
% these all attempt to present the same interface as `file token`
% nametype is not really a source, but is allowed on the stack to
% handle loop continuations which push the loop name
/nametype {
//pops exec % remove name from source stack
true
} def
/filetype {
token
} def
/stringtype {
token {
exch
//dbdict /stack get //dbdict /ptr get 3 -1 roll put %update source
true
}{
false
} ifelse
} def
/arraytype {
dup length 0 gt {
dup 0 get exch
dup length 1 gt {
//dbdict /stack get //dbdict /ptr get 3 -1 roll %update source
1 1 index length 1 sub getinterval put
}{
pop
//pops exec
} ifelse
true
}{ % length not >0, ie. ==0
//pops exec % remove array from source stack
false
} ifelse
} def
% fetch next object from top of source stack
% using typename procedures
/getnextobject {
//dbdict /nextobject get
dup null eq {
pop
//dbdict /ptr get 0 ge {
//dbdict /stack get //dbdict /ptr get get
//dbdict 1 index type get exec % call source handler by typename
}{
false
} ifelse
}{ true } ifelse
//dbdict /delaystep get {
//dbdict /delaystep false put
stepon
} if
} def
% addoper defines an override for the actual operator object, for bound procs
%/addoper { 1 index load dup type /operatortype eq { 1 index }{pop} ifelse } def
/addoper {} def
% list of operators that our override of `exit` searches for
/loopingops <<
/loop 1
/repeat 1
/for 1
/forall 1
/dict-forall 1
>> def
% operator overrides
% these all use dbdict<stack> like an exec stack
/operators <<
%for exec, just push onto our stack
/exec { //pushs exec } addoper
%for run, open file and push
/run { (r) file cvx //pushs exec } addoper
%for currentfile, we need to scan through the stack
% and fallback to calling the real currentfile
/currentfile {
{
//dbdict /stack get //dbdict /ptr get -1 0 {
2 copy get
type /filetype eq { % top-most executable file
get cvlit stop
} if
pop
} for
pop
} stopped not {
(currentfile: fallback)=
currentfile
} if
} addoper
%push the appropriate proc
/if { exch { //pushs exec }{ pop } ifelse } addoper
/ifelse { 3 2 roll not { exch } if pop //pushs exec } addoper
%to do loops,
% push continuation (the same looping operator)
% construct and push an argument array for the continuation
% push the first iteration
/loop {
/loop cvx //pushs exec
dup 1 array astore cvx //pushs exec
//pushs exec
} addoper
/repeat {
exch dup 0 eq { pop pop }{
/repeat cvx //pushs exec
1 sub 2 copy exch 2 array astore cvx //pushs exec
pop //pushs exec
} ifelse
} addoper
/for {
4 1 roll
3 copy exch 0 gt { le }{ ge } ifelse {
/for cvx //pushs exec % {} st inc fin
2 index 2 index add % {} st inc fin st+inc
3 1 roll 4 index 4 array astore cvx //pushs exec % {} st
exch //pushs exec % st
}{
pop pop pop pop
} ifelse
} addoper
% for forall,
% for strings and arrays, it's pretty straightforward:
% push continuation op,
% construct and push continuation argument array
% put element 0 the (real) stack and push the proc
% for dictionaries, first we spill the contents into an array
% then pass control (by continuation) to dict-forall,
% which handles cracking the array, calling proc, and continuing to itself
/dict-forall { % [[k v]*] proc
exch dup length 0 eq { pop pop }{ % proc array
/dict-forall cvx //pushs exec % proc array
dup 0 get 3 1 roll % array[0] proc array
1 1 index length 1 sub getinterval % array[0] proc array[1..n-1]
1 index 2 array astore cvx //pushs exec % array[0] proc
//pushs exec % [k v]
aload pop % key val
} ifelse
}
/forall {
exch dup type /dicttype eq { % dict proc
/dict-forall cvx //pushs exec % dict proc
[ exch [ exch % proc [ [ dict
{] [} forall pop ] % proc [[k v][k v]...[k v]]
exch 2 array astore cvx //pushs exec
}{
dup length 0 eq { pop pop }{
/forall cvx //pushs exec % comp proc
dup 0 get 3 1 roll % comp[0] proc comp
1 1 index length 1 sub getinterval % comp[0] proc comp[1..n-1]
1 index 2 array astore cvx //pushs exec % comp[0] proc
//pushs exec % comp[0]
} ifelse
} ifelse
} addoper
%for exit, scan through the stack for a looping operator
/exit {
%dumpstack
{
//dbdict /stack get //dbdict /ptr get -1 0 { % stack index
2 copy get % stack index object
//loopingops exch known { % found top-most looping op % stack index
//dbdict /ptr 3 2 roll 1 sub put % reset stack ptr to just below index % stack
stop
} if % not stopped: pop index
pop % stack
} for
} stopped not { % no looping op found: fallback (to error)
%/exit cvx /unregistered signalerror
pop exit
} if % stack
pop %
} addoper
>> def
% core of the debugloop when not stepping
/debugheart {
dup xcheck {
//operators 1 index known {
//operators exch get exec
}{
dup /nametype eq {
load
dup xcheck {
dup type /arraytype eq {
dup length 0 eq { pop } //pushs ifelse
}{
exec
} ifelse
} if
}{
exec
} ifelse
} ifelse
} if
} def
% single-char commands for step menu
/stepcommands <<
(n) 0 get { %next. execute next token atomically
/steponnext load //pushs exec
stepoff
//debugheart exec }
/ditto 1 index
/ditto2 1 index
(c) 0 get { stepoff //debugheart exec } %continue. stop single-stepping
(b) 0 get { pop skip } %bypass. pop and skip the current object
(s) 0 get //debugheart % :) %step. step into loop or procedure call
(p) 0 get { //pushs exec executive pop pop } %prompt. launch an executive session right here
(q) 0 get { pop exit } %quit. exit the debugger (can `resume`)
/default {
//dbdict /stepcommands get dup /ditto2 get /ditto exch put %reset ditto2 as ditto
(?) print flush
//dbdict /stepinput get exec
} % unrecognized command: print "?" and loop
>> def
/stepinput {
( step: (continue|next|bypass|step|prompt|quit)?) print flush % present prompt
(%lineedit)(r){
file % read line
}stopped{ %EOF
%pstack()= % <object> -file- false 0 -string-
pop pop pop pop pop ()= exit
}{
read { %something typed
//stepcommands dup /ditto get /ditto2 exch
put %save ditto as ditto2
//stepcommands exch
2 copy known not { pop /default } if
get //stepcommands /ditto 2 index put %set ditto as last command
exec
}{ %blank line
//stepcommands /ditto get exec
}ifelse
}ifelse
} def
% core of the debugloop when stepping
/stepheart {
dup type 20 string cvs print
( object )print dup //==nonl exec
%( defined as ) print
%dup { load } stopped { pop (!UNDEFINED!)= }{ == } ifelse
//stepinput exec
} def
%print error message
/errmsg {
(Error /) print
$error /errorname get dup length string cvs print
( occurred attempting to execute ) print
} def
%db-error> prompt
/handerr {
(db:)print //errmsg exec
$error /command get ==
(next: )print //dbdict /nextobject get ==
(Stack:\n)print pstack
(debug stack:\n)print dumpstack
{
(db-error>) print flush
(%lineedit)(r) file cvx exec
} stopped {
$error /errorname get dup /invalidexit eq
exch /undefinedfilename eq or {
$error /newerror false put % de-activate the error
exit
}{
//errmsg exec (user command\n) print
} ifelse
} if
} def
%get next object
% if tracing, print a trace
% if executable, but not an array, debug it
% else leave on stack
/debugloop {
//getnextobject exec
{
//dbdict /nextobject 3 -1 roll put
{
//dbdict /nextobject get
//dbdict /trace get {
//dbdict /cstack get exec % print stack comment
dup //==nonl exec % print current object
( )print
} if
dup xcheck {
dup type /arraytype ne {
//dbdict /step get //stepheart //debugheart ifelse
} if
} if % else leave on stack
//dbdict /nextobject null put
} stopped //handerr if
}{ % no objects
//pops exec
//dbdict /nextobject null put
//dbdict /ptr get 0 lt { exit } if %% <------ 'exit'
} ifelse
} def
% print a 1-line stack dump
%cf. https://groups.google.com/d/topic/comp.lang.postscript/Ptkxi3tAxkk/discussion
% & https://groups.google.com/d/topic/comp.lang.postscript/8NvOEccl_eg/discussion
% & http://tex.stackexchange.com/questions/160246/how-to-automatically-generate-a-stack-diagram-for-a-postscript-expression/160503#160503
% also see "The `count -1 1{-1 roll =}for` Idiom"
% https://groups.google.com/d/topic/comp.lang.postscript/Ptkxi3tAxkk/discussion
/cstack { ( %|- )print
count dup 1 add copy
exch pop 1 sub % ignore top-of-stack which is the current object
-1 1{ -1 roll //==nonl exec
( )print }for
pop ()= } def
% clear the pending object
/skip {
//dbdict /nextobject null put
} enddefbegin
/traceon {
//dbdict /trace true put
} enddefbegin
/traceoff {
//dbdict /trace false put
} enddefbegin
/steponnext {
//dbdict /delaystep true put
} enddefbegin
/stepon {
//dbdict /step true put
} enddefbegin
/stepoff {
//dbdict /step false put
} enddefbegin
% resume exit-ed or step:q-uit session
/resume {
//dbdict /ptr get 0 lt {
(db: nothing to resume\n) print
}{
(db: resuming) print
//dbdict /trace get { ( trace) print } if
(\n) print
//debugloop loop
} ifelse
} enddefbegin
% invoke the debugger on an executable file, string, or proc
/debug { % source debug -
//dbdict 1 index type known not
1 index xcheck not or
{ /debug cvx /typecheck signalerror } if
//pushs exec
//debugloop loop
} enddefbegin
/dumpstack {
//dbdict /stack get
0 1 //dbdict /ptr get { 2 copy get == pop } for
pop
} enddefbegin
end %discard internal names
% testing section.
% comment-out `currentfile flushfile` to run tests
currentfile flushfile
traceon stepon {
1 2 add 3 4 add 5 6 add 7 8 add 9 0 add
} debug
clear
traceon stepon {
/x [] def
/y /x load def
/y x def
} debug
traceon stepon {
1 2 add
} debug
traceon stepon {
/a { 27 } def
/b { 32 mul } def
a b b b
} debug
traceon {
blah
exit
3 6 9 mul mul
42
} debug
traceon stepon {
<< /k /v /k1 /v1 /k2 /v2 >> {
exch ==only ( )print ==
} forall
} debug
traceon stepon {
[0 1 2 3 4]{
=
} forall
} debug
traceon stepon
{
0 1 40 {
dup
} for
} debug
%traceon
%stepon
%(buggy.ps)(r)file cvx debug %testing currentfile
traceon
( 1 2 3 add add
6 eq { 8 9 add = } { 9 10 add = } ifelse
) cvx debug
()=
%stepon
{ 1 2 3 add add
6 eq { 7 } if
9 {
5
count 4 gt {
exit
} if
} repeat
{ 55 exit } loop
%undefin
(continuation) =
0 1 10 {
dup 5 eq {exit} if
} for
} debug