]> git.lyx.org Git - lyx.git/blob - lib/examples/listerrors.lyx
63edb0512d855879e68dc0ea2004e273ea1e0029
[lyx.git] / lib / examples / listerrors.lyx
1 #LyX 1.2 created this file. For more info see http://www.lyx.org/
2 \lyxformat 220
3 \textclass literate-article
4 \begin_preamble
5 %
6 % ps2pdf stuff
7 %
8 \usepackage[ps2pdf,pdftitle={LyX listerrors re-implemented},urlcolor=blue,linktocpage,letterpaper,colorlinks=true]{hyperref}
9 \@savsf=1% This is to get around a hyperref+noweb interaction problem
10 \hyphenpenalty 10000
11
12 %
13 % This (from the noweb FAQ) relaxes the constraint that chunks are never broken across pages.
14 %
15 \def\nwendcode{\endtrivlist \endgroup \vfil\penalty10\vfilneg}
16 \let\nwdocspar=\smallbreak
17 \end_preamble
18 \language english
19 \inputencoding auto
20 \fontscheme pslatex
21 \graphics default
22 \paperfontsize default
23 \spacing single 
24 \papersize Default
25 \paperpackage a4
26 \use_geometry 0
27 \use_amsmath 0
28 \use_natbib 0
29 \use_numerical_citations 0
30 \paperorientation portrait
31 \secnumdepth 3
32 \tocdepth 3
33 \paragraph_separation indent
34 \defskip medskip
35 \quotes_language english
36 \quotes_times 2
37 \papercolumns 1
38 \papersides 1
39 \paperpagestyle default
40
41 \layout Title
42
43 LyX listerrors:
44 \newline 
45 rewritten in Python
46 \layout Author
47
48 Kayvan A.
49  Sylvan
50 \newline 
51
52 \begin_inset LatexCommand \url{mailto:kayvan@sylvan.com}
53
54 \end_inset 
55
56
57 \layout Date
58
59 3/15/2002
60 \layout Abstract
61
62 The listerrors program used to be compiled as a C program and installed
63  as 
64 \emph on 
65 BINDIR/listerrors
66 \emph default 
67  along with LyX in order to perform some simple re-formatting of noweb and
68  GCC error messages.
69  This document describes and implements the Python version of the same program.
70 \layout Standard
71
72
73 \begin_inset LatexCommand \tableofcontents{}
74
75 \end_inset 
76
77
78 \layout Section
79
80 Introduction
81 \layout Standard
82
83 The motivation for this program was Bugzilla bug 190
84 \begin_inset Foot
85 collapsed true
86
87 \layout Standard
88
89 Visit 
90 \begin_inset LatexCommand \url{http://bugzilla.lyx.org/show_bug.cgi?id=190}
91
92 \end_inset 
93
94  for the details.
95 \end_inset 
96
97  dealing with the 
98 \begin_inset Quotes eld
99 \end_inset 
100
101 listerrors
102 \begin_inset Quotes erd
103 \end_inset 
104
105  executable.
106 \layout Standard
107
108 What is 
109 \begin_inset Quotes eld
110 \end_inset 
111
112 listerrors
113 \begin_inset Quotes erd
114 \end_inset 
115
116 ? Usually, LyX has great support for parsing of error messages.
117  For each error in the log file, LyX pops up an error box at that location
118  in the LyX window.
119  The error scanning routines expect these errors to be in a certain format
120  (similar to LaTeX errors).
121  When dealing with Literate Programs, you have 
122 \begin_inset Quotes eld
123 \end_inset 
124
125 noweb
126 \begin_inset Foot
127 collapsed true
128
129 \layout Standard
130
131 See 
132 \begin_inset LatexCommand \url{http://www.eecs.harvard.edu/~nr/noweb}
133
134 \end_inset 
135
136  for more information about noweb.
137 \end_inset 
138
139
140 \begin_inset Quotes erd
141 \end_inset 
142
143  as well as gcc error messages (and potentially others).
144  The listerrors program attempts to standardize these error messages to
145  a format that LyX can parse and react to.
146 \layout Standard
147
148 In a nutshell, the problems with the old implementation of listerrors that
149  bug 190 refers to were::
150 \layout Enumerate
151
152 It was a C program and it was installed in the user path in the same directory
153  as LyX.
154  Having such a generically named binary in, for example, 
155 \emph on 
156 /usr/bin
157 \emph default 
158 , was potentially confusing.
159 \layout Enumerate
160
161 It required that noweb be installed on the compiling machine (the source
162  was extracted by noweb from 
163 \emph on 
164 SRCDIR/examples/Literate.lyx
165 \emph default 
166 , compiled and installed by 
167 \begin_inset Quotes eld
168 \end_inset 
169
170 make install
171 \begin_inset Quotes erd
172 \end_inset 
173
174 ).
175 \layout Standard
176
177 The new version deals with these problems in the following fashion:
178 \layout Enumerate
179
180 Both the example file (this document) and the program are to be added to
181  the LyX CVS repository.
182 \layout Enumerate
183
184 The program itself will be installed in 
185 \emph on 
186 SHAREDIR/lyx/scripts
187 \emph default 
188 , along with other LyX-specific helper scripts.
189 \layout Standard
190
191 In the design and implementation of this new 
192 \begin_inset Quotes eld
193 \end_inset 
194
195 listerrors
196 \begin_inset Quotes erd
197 \end_inset 
198
199 , the Python
200 \begin_inset Foot
201 collapsed true
202
203 \layout Standard
204
205 See the Python home page (
206 \begin_inset LatexCommand \url{http://www.python.org}
207
208 \end_inset 
209
210 ) for more information.
211 \end_inset 
212
213  language was chosen since it is fully multi-platform and provides a very
214  uniform and easy to read syntax.
215  This re-write also simplifies the code for 
216 \begin_inset Quotes eld
217 \end_inset 
218
219 listerrors
220 \begin_inset Quotes erd
221 \end_inset 
222
223  greatly.
224  Python is installed by default on all modern Linux systems and is freely
225  available for all other platforms.
226 \layout Scrap
227
228 <<listerrors>>=
229 \newline 
230 #!/usr/bin/python
231 \newline 
232 """reformat noweb and compiler errors for LyX.
233 \newline 
234
235 \newline 
236 Expects to read from stdin and output to stdout.
237 \newline 
238 """
239 \newline 
240
241 \newline 
242 __author__ = "Kayvan A.
243  Sylvan <kayvan@sylvan.com>"
244 \newline 
245 __date__ = "$Date: 2002/03/19 21:42:48 $"
246 \newline 
247 __version__ = "$Revision: 1.1 $"
248 \newline 
249 __credits__ = """Edmar Wienskoski Jr.
250  <edmar-w-jr@technologist.com>
251 \newline 
252     original Literate support for LyX.
253 \newline 
254 Bernard Michael Hurley <berhardh@westherts.ac.uk>
255 \newline 
256     modifications to original listerrors."""
257 \newline 
258 __copyright__ = "Copyright 2002 - The LyX team."
259 \newline 
260
261 \newline 
262 import sys
263 \newline 
264
265 \newline 
266 <<Function Bodies>>
267 \newline 
268
269 \newline 
270 if __name__ == "__main__":
271 \newline 
272   main()
273 \newline 
274 @
275 \layout Section
276
277 LaTeX style error message
278 \layout Standard
279
280 The following function mimics the TeX error message format.
281 \layout Scrap
282
283 <<Function Bodies>>=
284 \newline 
285 def write_error(msg, tool = "noweb", line_number = 1):
286 \newline 
287   """Write out the given message in TeX error style.
288 \newline 
289
290 \newline 
291   called like: write_error(msg, tool, line_number)."""
292 \newline 
293   print "! Build Error: ==> %s ==>
294 \backslash 
295 n" % (tool),
296 \newline 
297   print " ...
298 \backslash 
299 n
300 \backslash 
301 nl.%d ...
302 \backslash 
303 n" % (line_number),
304 \newline 
305   if type(msg) == type("str"): # simple string
306 \newline 
307     print msg
308 \newline 
309   else: # some kind of list (sequence or tuple)
310 \newline 
311     for m in msg:
312 \newline 
313         if m != "": print m,
314 \newline 
315     print
316 \newline 
317
318 \newline 
319 @ %def write_error
320 \layout Section
321
322 Filtering errors
323 \layout Standard
324
325 The only complication in our filtering code is that some parsers might need
326  to push back lines that are read in to be read again later.
327  We solve this problem by implementing a 
328 \begin_inset Quotes eld
329 \end_inset 
330
331 getline
332 \begin_inset Quotes erd
333 \end_inset 
334
335  and 
336 \begin_inset Quotes eld
337 \end_inset 
338
339 pushline
340 \begin_inset Quotes erd
341 \end_inset 
342
343  set of functions:
344 \layout Scrap
345
346 <<Function Bodies>>=
347 \newline 
348 __lines = [] # lines pushed back
349 \newline 
350
351 \newline 
352 def getline(file = sys.stdin):
353 \newline 
354   """read a line from internal stack or from file.
355 \newline 
356
357 \newline 
358   optional file argument defaults to sys.stdin."""
359 \newline 
360   global __lines
361 \newline 
362   lines = __lines
363 \newline 
364   if lines:
365 \newline 
366     line = lines[-1]
367 \newline 
368     lines = lines[:-1]
369 \newline 
370   else:
371 \newline 
372     line = file.readline()
373 \newline 
374   return line
375 \newline 
376
377 \newline 
378 @ %def getline
379 \layout Standard
380
381 And now for the corresponding pushline function:
382 \layout Scrap
383
384 <<Function Bodies>>=
385 \newline 
386 def pushline(line):
387 \newline 
388   "push a line onto the pushback stack."
389 \newline 
390   global __lines
391 \newline 
392   lines = __lines
393 \newline 
394   lines += (line,) # push a list onto the stack, not individual letters
395 \newline 
396
397 \newline 
398 @ %def pushline
399 \layout Standard
400
401 The main() entry point function is extremely simple.
402  Note that this version of 
403 \begin_inset Quotes eld
404 \end_inset 
405
406 listerrors
407 \begin_inset Quotes erd
408 \end_inset 
409
410  takes no options and simply filters, attempting simply to match against
411  the known error message patterns.
412  The listerrors C program handled a single-character command-line argument
413  that the current code no longer needs.
414  
415 \layout Scrap
416
417 <<Function Bodies>>=
418 \newline 
419 def main():
420 \newline 
421   """Entry point for listerrors.
422  Takes no options.
423 \newline 
424
425 \newline 
426   Reads stdin and writes to stdout.
427  Filter errors"""
428 \newline 
429
430 \newline 
431   while 1:
432 \newline 
433     line = getline()
434 \newline 
435     if line == "": break
436 \newline 
437     <<Check line against patterns and take action>>
438 \newline 
439 @ %def main
440 \layout Standard
441
442 For each line read in, we need to find out if it matches any of our tools
443  (noweb, gcc, etc.) and act accordingly.
444 \layout Scrap
445
446 <<Check line against patterns and take action>>=
447 \newline 
448 try_patterns_dispatch = [ noweb_try, gcc_try, xlc_try ]
449 \newline 
450 for predicate in try_patterns_dispatch:
451 \newline 
452   if predicate(line): break
453 \newline 
454 @
455 \layout Section
456
457 Different Error Formats
458 \layout Standard
459
460 The following sections handle the various error message formats that we
461  recognize in this program.
462  
463 \layout Subsection
464
465 noweb errors
466 \layout Standard
467
468 Noweb errors are output on a single line, so examining just the current
469  line is enough.
470 \layout Scrap
471
472 <<Function Bodies>>=
473 \newline 
474 def noweb_try(line):
475 \newline 
476   """see if line is a noweb error.
477 \newline 
478
479 \newline 
480   Returns 1 on success, 0 otherwise.
481  Outputs on stdout."""
482 \newline 
483   retval = 0
484 \newline 
485   <<Look for the unescaped angle-brackets in documentation>>
486 \newline 
487   <<Look for anything with double angle brackets>>
488 \newline 
489   <<Last ditch effort scan for specific strings>>
490 \newline 
491   return retval
492 \newline 
493
494 \newline 
495 @ %def noweb_try
496 \layout Standard
497
498 First, we look for the 
499 \begin_inset Quotes eld
500 \end_inset 
501
502 unescaped < < in documentation chunk
503 \begin_inset Quotes erd
504 \end_inset 
505
506  message.
507  This is the only message with an associated line number from noweb.
508 \layout Scrap
509
510 <<Look for the unescaped angle-brackets in documentation>>=
511 \newline 
512 if line.find(": unescaped << in documentation chunk") != -1:
513 \newline 
514   line_parts = line.split(':')
515 \newline 
516   num_str = line_parts[1]
517 \newline 
518   num_len = len(num_str)
519 \newline 
520   i = 0
521 \newline 
522   while i < num_len and num_str[i].isdigit(): i += 1
523 \newline 
524   if i == num_len:
525 \newline 
526     write_error(":" + line_parts[2], "noweb", int(num_str))
527 \newline 
528     retval = 1
529 \newline 
530 @
531 \layout Standard
532
533 Some noweb messages are simply about undefined scraps.
534  These can be seen by looking for matching double-angle-brackets.
535 \layout Scrap
536
537 <<Look for anything with double angle brackets>>=
538 \newline 
539 if (not retval):
540 \newline 
541   left = line.find("<<")
542 \newline 
543   if (left != -1) and ((left + 2) < len(line)) and 
544 \backslash 
545
546 \newline 
547      (line[left+2:].find(">>") != -1):
548 \newline 
549     write_error(line, "noweb");
550 \newline 
551     retval = 1;
552 \newline 
553 @
554 \layout Standard
555
556 Finally, here is an additional list of explicit strings to check for.
557 \layout Scrap
558
559 <<Last ditch effort scan for specific strings>>=
560 \newline 
561 if (not retval):
562 \newline 
563   msgs_to_try = ("couldn't open file",
564 \newline 
565     "couldn't open temporary file",
566 \newline 
567     "error writing temporary file",
568 \newline 
569     "ill-formed option",
570 \newline 
571     "unknown option",
572 \newline 
573     "Bad format sequence",
574 \newline 
575     "Can't open output file",
576 \newline 
577     "Can't open temporary file",
578 \newline 
579     "Capacity exceeded:",
580 \newline 
581     "Ignoring unknown option -",
582 \newline 
583     "This can't happen:",
584 \newline 
585     "non-numeric line number in")
586 \newline 
587   for msg in msgs_to_try:
588 \newline 
589     if line.find(msg) != -1:
590 \newline 
591       write_error(line, "noweb")
592 \newline 
593       retval = 1
594 \newline 
595       break
596 \newline 
597 @
598 \layout Subsection
599
600 gcc errors
601 \layout Standard
602
603 The gcc errors can be multi-line, with the following format:
604 \layout LyX-Code
605
606 foo.c: In function `main': 
607 \newline 
608 foo.c:3: `bar' undeclared (first use in this function) 
609 \newline 
610 foo.c:3: (Each undeclared identifier is reported only once 
611 \newline 
612 foo.c:3: for each function it appears in.) 
613 \newline 
614 foo.c:3: parse error before `x'
615 \layout Standard
616
617 In order to parse this, the gcc error handler has to look ahead and return
618  any and all lines that do not match the expected pattern.
619 \layout Scrap
620
621 <<Function Bodies>>=
622 \newline 
623 def gcc_try(line):
624 \newline 
625   """See if line is a gcc error.
626  Read ahead to handle all the lines.
627 \newline 
628
629 \newline 
630   Returns 1 on success, 0 otherwise.
631  Outputs on stdout."""
632 \newline 
633   retval = 0
634 \newline 
635   <<Handle the gcc error message>>
636 \newline 
637   return retval
638 \newline 
639
640 \newline 
641 @ %def gcc_try
642 \layout Standard
643
644 The error message starts with a gcc header (as above) without an associated
645  line number.
646 \layout Scrap
647
648 <<Handle the gcc error message>>= 
649 \newline 
650 first_space = line.find(' ')
651 \newline 
652 if first_space > 1: # The smallest would be "X: "
653 \newline 
654   if line[first_space - 1] == ':':
655 \newline 
656     header_to_see = line[:first_space - 1]
657 \newline 
658     next_line = getline()
659 \newline 
660     if next_line and next_line[:first_space - 1] == header_to_see:
661 \newline 
662       num_end = first_space
663 \newline 
664       while next_line[num_end].isdigit(): num_end += 1
665 \newline 
666       if num_end > first_space: # good!
667 \newline 
668         <<Accumulate gcc error lines and print it>>
669 \newline 
670       else: # oops! Not a gcc error.
671 \newline 
672         pushline(next_line)
673 \newline 
674     elif next_line:
675 \newline 
676       pushline(next_line) # return this line to input stream
677 \newline 
678 @
679 \layout Standard
680
681 At the point in the code that we know that we are in the middle of an error
682  message, we do the following:
683 \layout Scrap
684
685 <<Accumulate gcc error lines and print it>>=
686 \newline 
687 num_str = next_line[first_space:num_end]
688 \newline 
689 msgs = []
690 \newline 
691 msgs += (line[first_space:],)
692 \newline 
693 msgs += (next_line[num_end + 1:],)
694 \newline 
695 header_to_see = next_line[:num_end]
696 \newline 
697 next_line = getline()
698 \newline 
699 while next_line and next_line[:num_end] == header_to_see:
700 \newline 
701   msgs += (next_line[num_end + 1:],)
702 \newline 
703   next_line = getline()
704 \newline 
705 if next_line: pushline(next_line)
706 \newline 
707 write_error(msgs, "gcc", int(num_str))
708 \newline 
709 retval = 1
710 \newline 
711 @
712 \layout Subsection
713
714 xlc errors
715 \layout Standard
716
717 A xlc error message is easy to identify.
718  Every error message starts with a quoted string with no spaces, a comma,
719  a space, the word 
720 \begin_inset Quotes eld
721 \end_inset 
722
723 line
724 \begin_inset Quotes erd
725 \end_inset 
726
727 , a space, and some variable text.
728  The following routine tests if a given buffer line matches this criteria
729  (this code would probably be simpler if I used the 
730 \begin_inset Quotes eld
731 \end_inset 
732
733 re
734 \begin_inset Quotes erd
735 \end_inset 
736
737  regexp module, but we don't really need the full regular expression engine
738  here).
739  
740 \layout Scrap
741
742 <<Function Bodies>>=
743 \newline 
744 def xlc_try(line):
745 \newline 
746   """see if line is an xlc error.
747 \newline 
748
749 \newline 
750   Returns 1 on success, 0 otherwise.
751  Outputs on stdout."""
752 \newline 
753   retval = 0
754 \newline 
755   if line[0] == '"': # This is the first character of all xlc errors
756 \newline 
757     next_quote = line.find('"', 1)
758 \newline 
759     first_space = line.find(' ')
760 \newline 
761     if (next_quote != -1) and (first_space > next_quote): # no space inisde
762  quotes
763 \newline 
764       if line[first_space - 1:first_space + 6] == ", line ":
765 \newline 
766         num_start = num_end = first_space + 6
767 \newline 
768         while line[num_end].isdigit(): num_end += 1
769 \newline 
770         if num_end > num_start:
771 \newline 
772           write_error(line, "xlc", int(line[num_start : num_end]))
773 \newline 
774           retval = 1
775 \newline 
776   return retval
777 \newline 
778   
779 \newline 
780 @ %def xlc_try
781 \layout Section
782
783 Extracting the code
784 \layout Standard
785
786 This project can be tangled from LyX if you set your 
787 \begin_inset Quotes eld
788 \end_inset 
789
790 Program
791 \begin_inset Quotes erd
792 \end_inset 
793
794  convertor to call a generic script that always extracts a scrap named 
795 \family typewriter 
796 build-script
797 \family default 
798  and executes it.
799  Here is an example of such a generic script:
800 \layout LyX-Code
801
802 #!/bin/sh
803 \newline 
804 notangle -Rbuild-script $1 | env NOWEB_SOURCE=$1 sh
805 \layout Standard
806
807 This section defines our build-script, which extracts the code.
808 \layout Scrap
809
810 <<build-script>>=
811 \newline 
812 #!/bin/sh
813 \newline 
814 if [ -z "$NOWEB_SOURCE" ]; then NOWEB_SOURCE=listerrors.nw; fi
815 \newline 
816 notangle -Rlisterrors ${NOWEB_SOURCE} > listerrors
817 \newline 
818 chmod +x listerrors
819 \newline 
820 @
821 \layout Section
822
823 Indices
824 \layout Standard
825
826 This section provides cross-references into the rest of the program.
827 \layout Subsection
828
829 Macros
830 \layout Standard
831
832
833 \begin_inset ERT
834 status Collapsed
835
836 \layout Standard
837
838 \backslash 
839 nowebchunks
840 \end_inset 
841
842
843 \layout Subsection
844
845 Identifiers
846 \layout Standard
847
848
849 \begin_inset ERT
850 status Collapsed
851
852 \layout Standard
853
854 \backslash 
855 nowebindex
856 \end_inset 
857
858
859 \the_end