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