]> git.lyx.org Git - lyx.git/blob - lib/examples/Literate.lyx
56723f8b9b92ffae8e19f3804ad57e0976729647
[lyx.git] / lib / examples / Literate.lyx
1 #LyX 1.2 created this file. For more info see http://www.lyx.org/
2 \lyxformat 220
3 \textclass literate-article
4 \language english
5 \inputencoding default
6 \fontscheme default
7 \graphics default
8 \paperfontsize default
9 \spacing single 
10 \papersize Default
11 \paperpackage a4
12 \use_geometry 0
13 \use_amsmath 0
14 \use_natbib 0
15 \use_numerical_citations 0
16 \paperorientation portrait
17 \secnumdepth 3
18 \tocdepth 3
19 \paragraph_separation indent
20 \defskip medskip
21 \quotes_language english
22 \quotes_times 2
23 \papercolumns 1
24 \papersides 1
25 \paperpagestyle default
26
27 \layout Title
28
29 LyX and Literate Programming
30 \newline 
31 An example program
32 \layout Author
33
34 Edmar Wienskoski Jr.
35 \newline 
36 edmar-w-jr@technologist.com
37 \begin_inset Foot
38 collapsed true
39
40 \layout Standard
41
42 Modified by Bernard Michael Hurley bernardh@westherts.ac.uk ---- Don't blame
43  Edmar for any errors that have crept in!
44 \end_inset 
45
46
47 \layout Date
48
49
50 \begin_inset ERT
51 status Collapsed
52
53 \layout Standard
54
55 \backslash 
56 today
57 \end_inset 
58
59
60 \layout Standard
61
62
63 \begin_inset LatexCommand \tableofcontents{}
64
65 \end_inset 
66
67
68 \layout Section
69
70 Introduction
71 \layout Standard
72
73 After typesetting a document, LyX scans the LaTeX log file looking for errors.
74  For each error found, the line number is obtained and a error box is displayed
75  in the LyX screen at that position.
76 \layout Standard
77
78 To use this feature to view compilation errors while working with literate
79  documents, we need a program that filters the compilation errors and puts
80  them in a format suitable for LyX reading it.
81  
82 \layout Standard
83
84 In this document we present a filter that recognizes compilation error messages
85  from noweb, gnu C, and the IBM C compiler (xlc).
86 \layout Standard
87
88 The filter is required to read from standard input, parse for error messages
89  and copy the error messages to the standard output.
90  During the output process, the filter must present the error messages in
91  a format that LyX can interpret, currently, the LaTeX error message format.
92  Of course, nothing will prevent future LyX releases from being able to
93  read other formats as well (like gcc error messages for example).
94  This mechanism is necessary to fully explore the literate programming tool's
95  capabilities.
96 \layout Section
97
98 Algorithm
99 \layout Scrap
100
101 <<Function bodies>>=
102 \newline 
103 int
104 \newline 
105 main (int argc, char **argv)
106 \newline 
107 {
108 \newline 
109   if (argc == 2) {
110 \newline 
111     switch (argv[1][0]) {
112 \newline 
113     case 'n':
114 \newline 
115       <<Scan input for noweb error messages>>
116 \newline 
117       break;
118 \newline 
119     case 'x':
120 \newline 
121       <<Scan input for xlc error messages>>
122 \newline 
123       break;
124 \newline 
125     case 'a':
126 \newline 
127       <<AIX system using both noweb and xlc>>
128 \newline 
129       break;
130 \newline 
131     case 's':
132 \newline 
133     case 'b':
134 \newline 
135       <<Solaris and Linux systems using both noweb and gcc>>
136 \newline 
137       break;
138 \newline 
139     case 'g':
140 \newline 
141     default:
142 \newline 
143       <<Scan input for gcc error messages>>
144 \newline 
145       break;
146 \newline 
147     }
148 \newline 
149   } else {
150 \newline 
151     <<Scan input for gcc error messages>>
152 \newline 
153   }
154 \newline 
155 }
156 \newline 
157 @
158 \layout Scrap
159
160 <<Function prototypes>>=
161 \newline 
162 int main (int argc, char **argv);
163 \newline 
164 @
165 \layout Section
166
167 Data Structures
168 \layout Standard
169
170 We resort to some global variables to allow access from several different
171  routines.
172  These are the buffer and related pointers used during the parse of the
173  input.
174 \layout Scrap
175
176 <<Global variables>>=
177 \newline 
178 char    buffer[200][200];
179 \newline 
180 int     last_buf_line;
181 \newline 
182 int     last_err_line;
183 \newline 
184 int     err_line;
185 \newline 
186
187 \layout Section
188
189 The output format
190 \layout Standard
191
192 The output format mimics the TeX error messages format.
193  This function prints a number of lines residing in the global variable
194  
195 \family typewriter 
196 buffer
197 \family default 
198 , a program name and line number.
199  There is no special requirement on the input strings, they can be anything.
200 \begin_inset Foot
201 collapsed true
202
203 \layout Standard
204
205 This function has been slightly changed from EW's original to make scanning
206  a bit easier with LaTeX::scanLogFile().
207  The test has been added because LyX can crash if empty lines are allowed
208  here --- I can't figure out why! --- BMH
209 \end_inset 
210
211
212 \layout Scrap
213
214 <<Function bodies>>=
215 \newline 
216 void
217 \newline 
218 output_error (int buf_size, int error_line, char *tool)
219 \newline 
220 {
221 \newline 
222   int     i;
223 \newline 
224  
225 \newline 
226   fprintf(stdout, "! Build Error: ==> %s ==>
227 \backslash 
228 n", tool);
229 \newline 
230   fprintf(stdout, " ...
231 \backslash 
232 n
233 \backslash 
234 nl.%d ...
235 \backslash 
236 n", error_line);
237 \newline 
238  
239 \newline 
240   for (i=0; i<buf_size; i++)
241 \newline 
242     if (strlen(buffer[i]) != 0)
243 \newline 
244       fprintf(stdout, "%s", buffer[i]);
245 \newline 
246  
247 \newline 
248   fprintf(stdout, "
249 \backslash 
250 n");
251 \newline 
252 }
253 \newline 
254 @
255 \layout Scrap
256
257 <<Function prototypes>>=
258 \newline 
259 void output_error (int buf_size, int error_line, char *tool);
260 \newline 
261 @
262 \layout Section
263
264 Functions Implementation
265 \layout Standard
266
267 Both noweave and notangle routines, always output one single line for each
268  error found, thus to scan the buffer for noweb error messages is enough
269  to exam one input line at a time.
270  Note that the noweb software does not provide a line error number, so all
271  errors boxes related to noweb messages will be displayed at the beginning
272  of the file.
273 \layout Scrap
274
275 <<Scan input for noweb error messages>>=
276 \newline 
277 {
278 \newline 
279   last_buf_line = 0;
280 \newline 
281   while (fgets(buffer[0], 200, stdin)) {
282 \newline 
283     if (noweb_try(0))
284 \newline 
285       output_error(1, err_line, "noweb");
286 \newline 
287   }
288 \newline 
289 }
290 \newline 
291 @
292 \layout Standard
293
294 The examination itself is very inefficient.
295  Unfortunately noweb doesn't have any characteristic that would help to
296  identify one of its error messages.
297  The solution is to collect all possible output messages in an array of
298  strings, and turn the examination process into a linear search in this
299  array.
300 \layout Scrap
301
302 <<Global variables>>=
303 \newline 
304 char *noweb_msgs[] = {
305 \newline 
306   "couldn't open file",
307 \newline 
308   "couldn't open temporary file",
309 \newline 
310   "error writing temporary file",
311 \newline 
312   "ill-formed option",
313 \newline 
314   "unknown option",
315 \newline 
316   "Bad format sequence",
317 \newline 
318   "Can't open output file",
319 \newline 
320   "Can't open temporary file",
321 \newline 
322   "Capacity exceeded:",
323 \newline 
324   "Ignoring unknown option -",
325 \newline 
326   "This can't happen:",
327 \newline 
328   "non-numeric line number in"
329 \newline 
330 };
331 \newline 
332
333 \newline 
334 char *noweb_msgs_mimic_gcc[] = {
335 \newline 
336   ": unescaped << in documentation chunk"
337 \newline 
338 };
339 \newline 
340 @
341 \layout Standard
342
343 A noweb error message can be any string that contains a matching pair of
344  < <\SpecialChar ~
345 \SpecialChar ~
346 \SpecialChar ~
347 > >, or any of the above strings
348 \layout Scrap
349
350 <<Function bodies>>=
351 \newline 
352 int
353 \newline 
354 noweb_try (int buf_line)
355 \newline 
356 {
357 \newline 
358   char    *s, *t, *b;
359 \newline 
360   int     i; 
361 \newline 
362
363 \newline 
364   b = buffer[buf_line];
365 \newline 
366   err_line = 0;
367 \newline 
368
369 \newline 
370   for (i=0; i<1; i++) {
371 \newline 
372       s = (char *)strstr (b, noweb_msgs_mimic_gcc[i]);
373 \newline 
374       if (s != NULL) {
375 \newline 
376         t = (char *)strchr(buffer[buf_line], ':');
377 \newline 
378         err_line = atoi(t+1);
379 \newline 
380         t = buffer[buf_line];
381 \newline 
382         ++s;
383 \newline 
384         while (*(t++) = *(s++));
385 \newline 
386         return 1;
387 \newline 
388       }
389 \newline 
390   }
391 \newline 
392   s = (char *)strstr(b, "<<");
393 \newline 
394   if (s != NULL) {
395 \newline 
396     s = (char *)strstr(s+2, ">>");
397 \newline 
398     if (s != NULL) {
399 \newline 
400       return 1;
401 \newline 
402     }
403 \newline 
404   } else { 
405 \newline 
406      for (i = 0; i < 12; ++i) {
407 \newline 
408         s = (char *)strstr (b, noweb_msgs[i]);
409 \newline 
410         if (s != NULL) {
411 \newline 
412            return 1;
413 \newline 
414         }
415 \newline 
416     }
417 \newline 
418   }
419 \newline 
420   return 0;
421 \newline 
422 }
423 \newline 
424 @
425 \layout Scrap
426
427 <<Function prototypes>>=
428 \newline 
429 int noweb_try (int buf_line);
430 \newline 
431 @
432 \layout Standard
433
434 The xlc compiler always outputs one single line for each error found, thus
435  to scan the buffer for xlc error messages it is enough to exam one input
436  line at a time.
437 \layout Scrap
438
439 <<Scan input for xlc error messages>>= 
440 \newline 
441 {
442 \newline 
443   last_buf_line = 0;
444 \newline 
445   while (fgets(buffer[last_buf_line], 200, stdin)) {
446 \newline 
447     if (xlc_try(0))
448 \newline 
449       output_error(1, err_line, "xlc");
450 \newline 
451   }
452 \newline 
453 }
454 \newline 
455 @
456 \layout Standard
457
458 A xlc error message is easy to identify.
459  Every error message starts with a quoted string with no spaces, a comma,
460  a space, the word 
461 \begin_inset Quotes eld
462 \end_inset 
463
464 line
465 \begin_inset Quotes erd
466 \end_inset 
467
468 , a space, and some variable text.
469  The following routine tests if a given buffer line matches this criteria:
470 \layout Scrap
471
472 <<Function bodies>>=
473 \newline 
474 int 
475 \newline 
476 xlc_try (int buf_line)
477 \newline 
478 {
479 \newline 
480   char    *s, *t;
481 \newline 
482  
483 \newline 
484   t = buffer[buf_line];
485 \newline 
486   s = t+1;
487 \newline 
488   while (*s != '"' && *s != ' ' && *s != '
489 \backslash 
490 0')
491 \newline 
492     s++;
493 \newline 
494   if (*t != '"' || *s != '"' || strncmp(s+1, ", line ", 7) != 0)
495 \newline 
496     return 0;
497 \newline 
498   s += 8;
499 \newline 
500   err_line = atoi(s);
501 \newline 
502   return 1;
503 \newline 
504 }
505 \newline 
506 @
507 \layout Scrap
508
509 <<Function prototypes>>=
510 \newline 
511 int xlc_try (int buf_line);
512 \newline 
513 @
514 \layout Standard
515
516 The gcc compiler error messages are more complicated to scan.
517  Each error can span more than one line in the buffer.
518  The good news is that every buffer line on each error has the same pattern,
519  and share the same line number.
520  Thus the strategy will be to accumulate lines in the buffer while the reported
521  line number is still the same.
522  At the time they differ, all the accumulated lines, except the last one,
523  will belong to one single error message, which now can be output-ed to
524  LyX.
525 \layout Standard
526
527 Every gcc error message contains a string with no space followed by a 
528 \begin_inset Quotes eld
529 \end_inset 
530
531 :
532 \begin_inset Quotes eld
533 \end_inset 
534
535 .
536  If the next character is a space, then this line is a header of a error
537  message and the next line will detail the line number of the source code
538  where the error was found.
539  Otherwise, the next thing is a integer number followed by another 
540 \begin_inset Quotes eld
541 \end_inset 
542
543 :
544 \begin_inset Quotes eld
545 \end_inset 
546
547 .
548 \layout Scrap
549
550 <<Scan input for gcc error messages>>=
551 \newline 
552 {
553 \newline 
554   char    *s, *t;
555 \newline 
556  
557 \newline 
558   last_buf_line = 0;
559 \newline 
560   while (fgets(buffer[last_buf_line], 200, stdin)) {
561 \newline 
562     /****** Skip lines until I find an error */
563 \newline 
564     s = (char *)strpbrk(buffer[last_buf_line], " :");
565 \newline 
566     if (s == NULL || *s == ' ')
567 \newline 
568       continue; /* No gcc error found here */
569 \newline 
570     do {
571 \newline 
572       <<gcc error message criteria is to find a "...:999:" or a "...: ">>
573 \newline 
574       /****** OK It is an error message, get line number */
575 \newline 
576       err_line = atoi(s+1);
577 \newline 
578       if (last_err_line == 0 || last_err_line == err_line) {
579 \newline 
580         last_err_line = err_line;
581 \newline 
582         continue; /* It's either a header or a continuation, don't output
583  yet */
584 \newline 
585       }
586 \newline 
587       /****** Completed the scan of one error message, output it to LyX
588  */
589 \newline 
590       discharge_buffer(1);
591 \newline 
592       break;
593 \newline 
594     } while (fgets(buffer[last_buf_line], 200, stdin));
595 \newline 
596   }
597 \newline 
598   /****** EOF completes the scan of whatever was being scanned */
599 \newline 
600   discharge_buffer(0);
601 \newline 
602 }
603 \newline 
604 @
605 \layout Scrap
606
607 <<gcc error message criteria is to find a "...:999:" or a "...: ">>=
608 \newline 
609 /****** Search first ":" in the error number */
610 \newline 
611 s = (char *)strpbrk(buffer[last_buf_line], " :");
612 \newline 
613 last_buf_line++;
614 \newline 
615 if (s == NULL || *s == ' ') 
616 \newline 
617   <<No gcc error found here, but it might terminate the scanning of a previous
618  one>>
619 \newline 
620 /****** Search second ":" in the error number */
621 \newline 
622 t = (char *)strpbrk(s+1, " :");
623 \newline 
624 if (t == NULL || *t == ' ')
625 \newline 
626   <<No gcc error found here, but it might terminate the scanning of a previous
627  one>>
628 \newline 
629 /****** Verify if is all digits between ":" */
630 \newline 
631 if (t != s+1+strspn(s+1, "0123456789")) 
632 \newline 
633   <<No gcc error found here, but it might terminate the scanning of a previous
634  one>>
635 \newline 
636 @
637 \layout Scrap
638
639 <<No gcc error found here, but it might terminate the scanning of a previous
640  one>>=
641 \newline 
642 {
643 \newline 
644   err_line = 0;
645 \newline 
646   discharge_buffer(1);
647 \newline 
648   continue;
649 \newline 
650 }
651 \newline 
652 @
653 \layout Standard
654
655 As we mentioned, when the scan of one gcc error message is completed everything
656  in the buffer except the last line is one single error message.
657  But if the scan terminates with a EOF or through finding one line that
658  does not match the gcc error message criteria, then there is no 
659 \begin_inset Quotes eld
660 \end_inset 
661
662 last line
663 \begin_inset Quotes erd
664 \end_inset 
665
666  in the buffer to be concerned with.
667  In those cases we empty the buffer completely.
668 \layout Scrap
669
670 <<Function bodies>>=
671 \newline 
672 void
673 \newline 
674 discharge_buffer (int save_last)
675 \newline 
676 {
677 \newline 
678  if (last_err_line != 0) { 
679 \newline 
680    clean_gcc_messages();
681 \newline 
682    if (save_last != 0) {
683 \newline 
684       output_error(last_buf_line-1, last_err_line, "gcc");
685 \newline 
686       strcpy (buffer[0], buffer[last_buf_line-1]);
687 \newline 
688       last_err_line = err_line;
689 \newline 
690       last_buf_line = 1;
691 \newline 
692     } else { 
693 \newline 
694       ++last_buf_line;
695 \newline 
696       clean_gcc_messages();
697 \newline 
698       output_error(last_buf_line-1, last_err_line, "gcc");
699 \newline 
700       last_err_line = 0;
701 \newline 
702       last_buf_line = 0;
703 \newline 
704     }
705 \newline 
706   }
707 \newline 
708 }
709 \newline 
710 @
711 \layout Scrap
712
713 <<Function prototypes>>=
714 \newline 
715 void discharge_buffer (int save_last);
716 \newline 
717 @
718 \layout Standard
719
720 The next function 
721 \begin_inset Quotes eld
722 \end_inset 
723
724 cleans
725 \begin_inset Quotes erd
726 \end_inset 
727
728  superfluous information from gcc messages, namely the name of the noweb
729  file and the line number of the Error.
730 \begin_inset Foot
731 collapsed true
732
733 \layout Standard
734
735 More could be done.
736  For instance, some way of distinguishing between gcc Errors and Warnings
737  should be devised.
738 \end_inset 
739
740
741 \layout Scrap
742
743 <<Function bodies>>=
744 \newline 
745 void
746 \newline 
747 clean_gcc_messages ()
748 \newline 
749 {
750 \newline 
751   int index;
752 \newline 
753   char search [30]; 
754 \newline 
755   char *tail, *head; 
756 \newline 
757   int search_len = sprintf(search, ".nw:%d:", last_err_line);
758 \newline 
759   
760 \newline 
761   for (index = 0; index < last_buf_line-1; index++) {
762 \newline 
763     tail = (char *)strstr (buffer[index], search);
764 \newline 
765     if ( tail == NULL) {
766 \newline 
767        tail = (char *) strstr (buffer[index], ".nw:");
768 \newline 
769        if (tail) {
770 \newline 
771           tail += 4;
772 \newline 
773        }
774 \newline 
775     } else {
776 \newline 
777        tail += search_len;
778 \newline 
779     }
780 \newline 
781     if (tail != NULL) {
782 \newline 
783        head = buffer[index];
784 \newline 
785        while (*(head++) = *(tail++));
786 \newline 
787     }
788 \newline 
789   }
790 \newline 
791 }
792 \newline 
793 @
794 \layout Scrap
795
796 <<Function prototypes>>=
797 \newline 
798 void clean_gcc_messages ();
799 \newline 
800 @
801 \layout Standard
802
803 To combine the scan of noweb error messages and xlc error messages is very
804  simple.
805  We just try each one for every input line:
806 \layout Scrap
807
808 <<AIX system using both noweb and xlc>>=
809 \newline 
810 {
811 \newline 
812   last_buf_line = 0;
813 \newline 
814   while (fgets(buffer[0], 200, stdin)) {
815 \newline 
816     if (noweb_try(0))
817 \newline 
818       output_error(1, err_line, "noweb");
819 \newline 
820     else if (xlc_try(0))
821 \newline 
822       output_error(1, err_line, "xlc");
823 \newline 
824   }
825 \newline 
826 }
827 \newline 
828 @
829 \layout Standard
830
831 To combine the scan of noweb error messages and gcc error messages is simple
832  if we realize that it is not possible to find a noweb error message in
833  the middle of a gcc error message.
834  So we just repeat the gcc procedure and test for noweb error messages in
835  the beginning of the scan:
836 \layout Scrap
837
838 <<Solaris and Linux systems using both noweb and gcc>>=
839 \newline 
840 {
841 \newline 
842   char    *s, *t;
843 \newline 
844  
845 \newline 
846   last_buf_line = 0;
847 \newline 
848   while (fgets(buffer[last_buf_line], 200, stdin)) {
849 \newline 
850     /****** Skip lines until I find an error */
851 \newline 
852     if (last_buf_line == 0 && noweb_try(0)) {
853 \newline 
854       output_error(1, err_line, "noweb");
855 \newline 
856       continue;
857 \newline 
858     }
859 \newline 
860     s = (char *)strpbrk(buffer[last_buf_line], " :");
861 \newline 
862     if (s == NULL || *s == ' ')
863 \newline 
864       continue; /* No gcc error found here */
865 \newline 
866     do {
867 \newline 
868       <<gcc error message criteria is to find a "...:999:" or a "...: ">>
869 \newline 
870       /****** OK It is an error, get line number */
871 \newline 
872       err_line = atoi(s+1);
873 \newline 
874       if (last_err_line == 0 || last_err_line == err_line) {
875 \newline 
876         last_err_line = err_line;
877 \newline 
878         continue; /* It's either a header or a continuation, don't output
879  yet */
880 \newline 
881       }
882 \newline 
883       /****** Completed the scan of one error message, output it to LyX
884  */
885 \newline 
886       discharge_buffer(1);
887 \newline 
888       break;
889 \newline 
890     } while (fgets(buffer[last_buf_line], 200, stdin));
891 \newline 
892   }
893 \newline 
894   /****** EOF completes the scan of whatever was being scanned */
895 \newline 
896   discharge_buffer(0);
897 \newline 
898 }
899 \newline 
900 @
901 \layout Section
902
903 Wrapping the code into a file
904 \layout Scrap
905
906 <<listerrors.c>>=
907 \newline 
908 #include <stdio.h>
909 \newline 
910 #include <strings.h>       
911 \newline 
912  
913 \newline 
914 <<Global variables>>
915 \newline 
916 <<Function prototypes>>
917 \newline 
918 <<Function bodies>>
919 \newline 
920 @
921 \layout Standard
922
923 To build this program, we want to add the 
924 \begin_inset Quotes eld
925 \end_inset 
926
927 -L
928 \begin_inset Quotes erd
929 \end_inset 
930
931  option in the tangle command to force gdb to load the file 
932 \family typewriter 
933 Literate.nw
934 \family default 
935  instead of 
936 \family typewriter 
937 listerrors.c
938 \family default 
939 .
940  In accordance with this, we pass the 
941 \begin_inset Quotes eld
942 \end_inset 
943
944 -g
945 \begin_inset Quotes erd
946 \end_inset 
947
948  option to gcc.
949 \layout Scrap
950
951 <<build-script>>=
952 \newline 
953 #!/bin/sh
954 \newline 
955 if [ -z "$NOWEB_SOURCE" ]; then NOWEB_SOURCE=Literate.nw; fi
956 \newline 
957 notangle -L -Rlisterrors.c ${NOWEB_SOURCE} > listerrors.c
958 \newline 
959 gcc -g -o listerrors listerrors.c
960 \newline 
961 @
962 \layout Standard
963
964 This project can be tangled and compiled from LyX if you set 
965 \family typewriter 
966
967 \backslash 
968 build_command
969 \family default 
970  to call a generic script that always extracts a scrap named 
971 \family typewriter 
972 build-script
973 \family default 
974  and executes it.
975  Here is a example of such generic script:
976 \layout LyX-Code
977
978 #!/bin/sh
979 \newline 
980 notangle -Rbuild-script $1 | env NOWEB_SOURCE=$1 sh
981 \layout LyX-Code
982
983 \the_end