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