]> git.lyx.org Git - lyx.git/blob - lib/examples/Literate.lyx
insetquote fix; getStatus tweaks
[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 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 \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, err_line, "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 \newline 
318 char *noweb_msgs_mimic_gcc[] = {
319 \newline 
320   ": unescaped << in documentation chunk"
321 \newline 
322 };
323 \newline 
324 @
325 \layout Standard
326
327 A noweb error message can be any string that contains a matching pair of
328  < <\SpecialChar ~
329 \SpecialChar ~
330 \SpecialChar ~
331 > >, or any of the above strings
332 \layout Scrap
333
334 <<Function bodies>>=
335 \newline 
336 int
337 \newline 
338 noweb_try (int buf_line)
339 \newline 
340 {
341 \newline 
342   char    *s, *t, *b;
343 \newline 
344   int     i; 
345 \newline 
346
347 \newline 
348   b = buffer[buf_line];
349 \newline 
350   err_line = 0;
351 \newline 
352
353 \newline 
354   for (i=0; i<1; i++) {
355 \newline 
356       s = (char *)strstr (b, noweb_msgs_mimic_gcc[i]);
357 \newline 
358       if (s != NULL) {
359 \newline 
360         t = (char *)strchr(buffer[buf_line], ':');
361 \newline 
362         err_line = atoi(t+1);
363 \newline 
364         t = buffer[buf_line];
365 \newline 
366         ++s;
367 \newline 
368         while (*(t++) = *(s++));
369 \newline 
370         return 1;
371 \newline 
372       }
373 \newline 
374   }
375 \newline 
376   s = (char *)strstr(b, "<<");
377 \newline 
378   if (s != NULL) {
379 \newline 
380     s = (char *)strstr(s+2, ">>");
381 \newline 
382     if (s != NULL) {
383 \newline 
384       return 1;
385 \newline 
386     }
387 \newline 
388   } else { 
389 \newline 
390      for (i = 0; i < 12; ++i) {
391 \newline 
392         s = (char *)strstr (b, noweb_msgs[i]);
393 \newline 
394         if (s != NULL) {
395 \newline 
396            return 1;
397 \newline 
398         }
399 \newline 
400     }
401 \newline 
402   }
403 \newline 
404   return 0;
405 \newline 
406 }
407 \newline 
408 @
409 \layout Scrap
410
411 <<Function prototypes>>=
412 \newline 
413 int noweb_try (int buf_line);
414 \newline 
415 @
416 \layout Standard
417
418 The xlc compiler always outputs one single line for each error found, thus
419  to scan the buffer for xlc error messages it is enough to exam one input
420  line at a time.
421 \layout Scrap
422
423 <<Scan input for xlc error messages>>= 
424 \newline 
425 {
426 \newline 
427   last_buf_line = 0;
428 \newline 
429   while (fgets(buffer[last_buf_line], 200, stdin)) {
430 \newline 
431     if (xlc_try(0))
432 \newline 
433       output_error(1, err_line, "xlc");
434 \newline 
435   }
436 \newline 
437 }
438 \newline 
439 @
440 \layout Standard
441
442 A xlc error message is easy to identify.
443  Every error message starts with a quoted string with no spaces, a comma,
444  a space, the word 
445 \begin_inset Quotes eld
446 \end_inset 
447
448 line
449 \begin_inset Quotes erd
450 \end_inset 
451
452 , a space, and some variable text.
453  The following routine tests if a given buffer line matches this criteria:
454 \layout Scrap
455
456 <<Function bodies>>=
457 \newline 
458 int 
459 \newline 
460 xlc_try (int buf_line)
461 \newline 
462 {
463 \newline 
464   char    *s, *t;
465 \newline 
466  
467 \newline 
468   t = buffer[buf_line];
469 \newline 
470   s = t+1;
471 \newline 
472   while (*s != '"' && *s != ' ' && *s != '
473 \backslash 
474 0')
475 \newline 
476     s++;
477 \newline 
478   if (*t != '"' || *s != '"' || strncmp(s+1, ", line ", 7) != 0)
479 \newline 
480     return 0;
481 \newline 
482   s += 8;
483 \newline 
484   err_line = atoi(s);
485 \newline 
486   return 1;
487 \newline 
488 }
489 \newline 
490 @
491 \layout Scrap
492
493 <<Function prototypes>>=
494 \newline 
495 int xlc_try (int buf_line);
496 \newline 
497 @
498 \layout Standard
499
500 The gcc compiler error messages are more complicated to scan.
501  Each error can span more than one line in the buffer.
502  The good news is that every buffer line on each error has the same pattern,
503  and share the same line number.
504  Thus the strategy will be to accumulate lines in the buffer while the reported
505  line number is still the same.
506  At the time they differ, all the accumulated lines, except the last one,
507  will belong to one single error message, which now can be output-ed to
508  LyX.
509 \layout Standard
510
511 Every gcc error message contains a string with no space followed by a 
512 \begin_inset Quotes eld
513 \end_inset 
514
515 :
516 \begin_inset Quotes eld
517 \end_inset 
518
519 .
520  If the next character is a space, then this line is a header of a error
521  message and the next line will detail the line number of the source code
522  where the error was found.
523  Otherwise, the next thing is a integer number followed by another 
524 \begin_inset Quotes eld
525 \end_inset 
526
527 :
528 \begin_inset Quotes eld
529 \end_inset 
530
531 .
532 \layout Scrap
533
534 <<Scan input for gcc error messages>>=
535 \newline 
536 {
537 \newline 
538   char    *s, *t;
539 \newline 
540  
541 \newline 
542   last_buf_line = 0;
543 \newline 
544   while (fgets(buffer[last_buf_line], 200, stdin)) {
545 \newline 
546     /****** Skip lines until I find an error */
547 \newline 
548     s = (char *)strpbrk(buffer[last_buf_line], " :");
549 \newline 
550     if (s == NULL || *s == ' ')
551 \newline 
552       continue; /* No gcc error found here */
553 \newline 
554     do {
555 \newline 
556       <<gcc error message criteria is to find a "...:999:" or a "...: ">>
557 \newline 
558       /****** OK It is an error message, get line number */
559 \newline 
560       err_line = atoi(s+1);
561 \newline 
562       if (last_err_line == 0 || last_err_line == err_line) {
563 \newline 
564         last_err_line = err_line;
565 \newline 
566         continue; /* It's either a header or a continuation, don't output
567  yet */
568 \newline 
569       }
570 \newline 
571       /****** Completed the scan of one error message, output it to LyX
572  */
573 \newline 
574       discharge_buffer(1);
575 \newline 
576       break;
577 \newline 
578     } while (fgets(buffer[last_buf_line], 200, stdin));
579 \newline 
580   }
581 \newline 
582   /****** EOF completes the scan of whatever was being scanned */
583 \newline 
584   discharge_buffer(0);
585 \newline 
586 }
587 \newline 
588 @
589 \layout Scrap
590
591 <<gcc error message criteria is to find a "...:999:" or a "...: ">>=
592 \newline 
593 /****** Search first ":" in the error number */
594 \newline 
595 s = (char *)strpbrk(buffer[last_buf_line], " :");
596 \newline 
597 last_buf_line++;
598 \newline 
599 if (s == NULL || *s == ' ') 
600 \newline 
601   <<No gcc error found here, but it might terminate the scanning of a previous
602  one>>
603 \newline 
604 /****** Search second ":" in the error number */
605 \newline 
606 t = (char *)strpbrk(s+1, " :");
607 \newline 
608 if (t == NULL || *t == ' ')
609 \newline 
610   <<No gcc error found here, but it might terminate the scanning of a previous
611  one>>
612 \newline 
613 /****** Verify if is all digits between ":" */
614 \newline 
615 if (t != s+1+strspn(s+1, "0123456789")) 
616 \newline 
617   <<No gcc error found here, but it might terminate the scanning of a previous
618  one>>
619 \newline 
620 @
621 \layout Scrap
622
623 <<No gcc error found here, but it might terminate the scanning of a previous
624  one>>=
625 \newline 
626 {
627 \newline 
628   err_line = 0;
629 \newline 
630   discharge_buffer(1);
631 \newline 
632   continue;
633 \newline 
634 }
635 \newline 
636 @
637 \layout Standard
638
639 As we mentioned, when the scan of one gcc error message is completed everything
640  in the buffer except the last line is one single error message.
641  But if the scan terminates with a EOF or through finding one line that
642  does not match the gcc error message criteria, then there is no 
643 \begin_inset Quotes eld
644 \end_inset 
645
646 last line
647 \begin_inset Quotes erd
648 \end_inset 
649
650  in the buffer to be concerned with.
651  In those cases we empty the buffer completely.
652 \layout Scrap
653
654 <<Function bodies>>=
655 \newline 
656 void
657 \newline 
658 discharge_buffer (int save_last)
659 \newline 
660 {
661 \newline 
662  if (last_err_line != 0) { 
663 \newline 
664    clean_gcc_messages();
665 \newline 
666    if (save_last != 0) {
667 \newline 
668       output_error(last_buf_line-1, last_err_line, "gcc");
669 \newline 
670       strcpy (buffer[0], buffer[last_buf_line-1]);
671 \newline 
672       last_err_line = err_line;
673 \newline 
674       last_buf_line = 1;
675 \newline 
676     } else { 
677 \newline 
678       ++last_buf_line;
679 \newline 
680       clean_gcc_messages();
681 \newline 
682       output_error(last_buf_line-1, last_err_line, "gcc");
683 \newline 
684       last_err_line = 0;
685 \newline 
686       last_buf_line = 0;
687 \newline 
688     }
689 \newline 
690   }
691 \newline 
692 }
693 \newline 
694 @
695 \layout Scrap
696
697 <<Function prototypes>>=
698 \newline 
699 void discharge_buffer (int save_last);
700 \newline 
701 @
702 \layout Standard
703
704 The next function 
705 \begin_inset Quotes eld
706 \end_inset 
707
708 cleans
709 \begin_inset Quotes erd
710 \end_inset 
711
712  superfluous information from gcc messages, namely the name of the noweb
713  file and the line number of the Error.
714 \begin_float footnote 
715 \layout Standard
716
717 More could be done.
718  For instance, some way of distinguishing between gcc Errors and Warnings
719  should be devised.
720 \end_float 
721 \layout Scrap
722
723 <<Function bodies>>=
724 \newline 
725 void
726 \newline 
727 clean_gcc_messages ()
728 \newline 
729 {
730 \newline 
731   int index;
732 \newline 
733   char search [30]; 
734 \newline 
735   char *tail, *head; 
736 \newline 
737   int search_len = sprintf(search, ".nw:%d:", last_err_line);
738 \newline 
739   
740 \newline 
741   for (index = 0; index < last_buf_line-1; index++) {
742 \newline 
743     tail = (char *)strstr (buffer[index], search);
744 \newline 
745     if ( tail == NULL) {
746 \newline 
747        tail = (char *) strstr (buffer[index], ".nw:");
748 \newline 
749        if (tail) {
750 \newline 
751           tail += 4;
752 \newline 
753        }
754 \newline 
755     } else {
756 \newline 
757        tail += search_len;
758 \newline 
759     }
760 \newline 
761     if (tail != NULL) {
762 \newline 
763        head = buffer[index];
764 \newline 
765        while (*(head++) = *(tail++));
766 \newline 
767     }
768 \newline 
769   }
770 \newline 
771 }
772 \newline 
773 @
774 \layout Scrap
775
776 <<Function prototypes>>=
777 \newline 
778 void clean_gcc_messages ();
779 \newline 
780 @
781 \layout Standard
782
783 To combine the scan of noweb error messages and xlc error messages is very
784  simple.
785  We just try each one for every input line:
786 \layout Scrap
787
788 <<AIX system using both noweb and xlc>>=
789 \newline 
790 {
791 \newline 
792   last_buf_line = 0;
793 \newline 
794   while (fgets(buffer[0], 200, stdin)) {
795 \newline 
796     if (noweb_try(0))
797 \newline 
798       output_error(1, err_line, "noweb");
799 \newline 
800     else if (xlc_try(0))
801 \newline 
802       output_error(1, err_line, "xlc");
803 \newline 
804   }
805 \newline 
806 }
807 \newline 
808 @
809 \layout Standard
810
811 To combine the scan of noweb error messages and gcc error messages is simple
812  if we realize that it is not possible to find a noweb error message in
813  the middle of a gcc error message.
814  So we just repeat the gcc procedure and test for noweb error messages in
815  the beginning of the scan:
816 \layout Scrap
817
818 <<Solaris and Linux systems using both noweb and gcc>>=
819 \newline 
820 {
821 \newline 
822   char    *s, *t;
823 \newline 
824  
825 \newline 
826   last_buf_line = 0;
827 \newline 
828   while (fgets(buffer[last_buf_line], 200, stdin)) {
829 \newline 
830     /****** Skip lines until I find an error */
831 \newline 
832     if (last_buf_line == 0 && noweb_try(0)) {
833 \newline 
834       output_error(1, err_line, "noweb");
835 \newline 
836       continue;
837 \newline 
838     }
839 \newline 
840     s = (char *)strpbrk(buffer[last_buf_line], " :");
841 \newline 
842     if (s == NULL || *s == ' ')
843 \newline 
844       continue; /* No gcc error found here */
845 \newline 
846     do {
847 \newline 
848       <<gcc error message criteria is to find a "...:999:" or a "...: ">>
849 \newline 
850       /****** OK It is an error, get line number */
851 \newline 
852       err_line = atoi(s+1);
853 \newline 
854       if (last_err_line == 0 || last_err_line == err_line) {
855 \newline 
856         last_err_line = err_line;
857 \newline 
858         continue; /* It's either a header or a continuation, don't output
859  yet */
860 \newline 
861       }
862 \newline 
863       /****** Completed the scan of one error message, output it to LyX
864  */
865 \newline 
866       discharge_buffer(1);
867 \newline 
868       break;
869 \newline 
870     } while (fgets(buffer[last_buf_line], 200, stdin));
871 \newline 
872   }
873 \newline 
874   /****** EOF completes the scan of whatever was being scanned */
875 \newline 
876   discharge_buffer(0);
877 \newline 
878 }
879 \newline 
880 @
881 \layout Section
882
883 Wrapping the code into a file
884 \layout Scrap
885
886 <<listerrors.c>>=
887 \newline 
888 #include <stdio.h>
889 \newline 
890 #include <strings.h>       
891 \newline 
892  
893 \newline 
894 <<Global variables>>
895 \newline 
896 <<Function prototypes>>
897 \newline 
898 <<Function bodies>>
899 \newline 
900 @
901 \layout Standard
902
903 To build this program, we want to add the 
904 \begin_inset Quotes eld
905 \end_inset 
906
907 -L
908 \begin_inset Quotes erd
909 \end_inset 
910
911  option in the tangle command to force gdb to load the file 
912 \family typewriter 
913 Literate.nw
914 \family default 
915  instead of 
916 \family typewriter 
917 listerrors.c
918 \family default 
919 .
920  In accordance with this, we pass the 
921 \begin_inset Quotes eld
922 \end_inset 
923
924 -g
925 \begin_inset Quotes erd
926 \end_inset 
927
928  option to gcc.
929 \layout Scrap
930
931 <<build-script>>=
932 \newline 
933 #!/usr/local/bin/bash
934 \newline 
935 notangle -L -Rlisterrors.c Literate.nw > listerrors.c
936 \newline 
937 gcc -g -o listerrors listerrors.c
938 \newline 
939 @
940 \layout Standard
941
942 This project can be tangled and compiled from LyX if you set 
943 \family typewriter 
944
945 \backslash 
946 build_command
947 \family default 
948  to call a generic script that always extracts a scrap named 
949 \family typewriter 
950 build-script
951 \family default 
952  and executes it.
953  Here is a example of such generic script:
954 \layout LyX-Code
955
956 #!/bin/sh
957 \newline 
958 notangle -Rbuild-script $1 | sh
959 \layout LyX-Code
960
961 \the_end