1 #LyX 2.1 created this file. For more info see http://www.lyx.org/
5 \textclass literate-article
6 \use_default_options false
7 \maintain_unincluded_children false
9 \language_package default
10 \inputencoding default
14 \font_typewriter default
16 \font_default_family default
17 \use_non_tex_fonts false
23 \default_output_format default
25 \bibtex_command default
26 \index_command default
27 \paperfontsize default
32 \use_package amsmath 0
33 \use_package amssymb 0
35 \use_package mathdots 1
36 \use_package mathtools 0
38 \use_package undertilde 0
40 \cite_engine_type numerical
44 \paperorientation portrait
54 \paragraph_separation indent
55 \paragraph_indentation default
56 \quotes_language english
59 \paperpagestyle default
60 \tracking_changes false
70 LyX and Literate Programming
71 \begin_inset Newline newline
79 \begin_inset Newline newline
82 edmar-w-jr@technologist.com
86 \begin_layout Plain Layout
87 Modified by Bernard Michael Hurley bernardh@westherts.ac.uk ---- Don't blame
88 Edmar for any errors that have crept in!
96 \begin_layout Abstract
101 This example program is provided for educational use only.
102 The functionality in this C program has been superceded by the equivalent
105 examples/listerrors.lyx
107 which should be installed in the LyX scripts directory.
114 \begin_layout Plain Layout
126 \begin_layout Standard
127 \begin_inset CommandInset toc
128 LatexCommand tableofcontents
135 \begin_layout Section
139 \begin_layout Standard
140 After typesetting a document, LyX scans the LaTeX log file looking for errors.
141 For each error found, the line number is obtained and a error box is displayed
142 in the LyX screen at that position.
145 \begin_layout Standard
146 To use this feature to view compilation errors while working with literate
147 documents, we need a program that filters the compilation errors and puts
148 them in a format suitable for LyX reading it.
152 \begin_layout Standard
153 In this document we present a filter that recognizes compilation error messages
154 from noweb, gnu C, and the IBM C compiler (xlc).
157 \begin_layout Standard
158 The filter is required to read from standard input, parse for error messages
159 and copy the error messages to the standard output.
160 During the output process, the filter must present the error messages in
161 a format that LyX can interpret, currently, the LaTeX error message format.
162 Of course, nothing will prevent future LyX releases from being able to
163 read other formats as well (like gcc error messages for example).
164 This mechanism is necessary to fully explore the literate programming tool's
168 \begin_layout Section
181 main (int argc, char **argv)
193 switch (argv[1][0]) {
201 <<Scan input for noweb error messages>>
213 <<Scan input for xlc error messages>>
225 <<AIX system using both noweb and xlc>>
241 <<Solaris and Linux systems using both noweb and gcc>>
257 <<Scan input for gcc error messages>>
273 <<Scan input for gcc error messages>>
289 <<Function prototypes>>=
293 int main (int argc, char **argv);
300 \begin_layout Section
304 \begin_layout Standard
305 We resort to some global variables to allow access from several different
307 These are the buffer and related pointers used during the parse of the
312 <<Global variables>>=
316 char buffer[200][200];
317 \begin_inset Newline newline
321 \begin_inset Newline newline
325 \begin_inset Newline newline
329 \begin_inset Newline newline
335 \begin_layout Section
339 \begin_layout Standard
340 The output format mimics the TeX error messages format.
341 This function prints a number of lines residing in the global variable
346 , a program name and line number.
347 There is no special requirement on the input strings, they can be anything.
351 \begin_layout Plain Layout
352 This function has been slightly changed from EW's original to make scanning
353 a bit easier with LaTeX::scanLogFile().
354 The test has been added because LyX can crash if empty lines are allowed
355 here --- I can't figure out why! --- BMH
372 output_error (int buf_size, int error_line, char *tool)
388 fprintf(stdout, "! Build Error: ==> %s ==>
394 fprintf(stdout, " ...
408 for (i=0; i<buf_size; i++)
412 if (strlen(buffer[i]) != 0)
416 fprintf(stdout, "%s", buffer[i]);
438 <<Function prototypes>>=
442 void output_error (int buf_size, int error_line, char *tool);
449 \begin_layout Section
450 Functions Implementation
453 \begin_layout Standard
454 Both noweave and notangle routines, always output one single line for each
455 error found, thus to scan the buffer for noweb error messages is enough
456 to exam one input line at a time.
457 Note that the noweb software does not provide a line error number, so all
458 errors boxes related to noweb messages will be displayed at the beginning
463 <<Scan input for noweb error messages>>=
475 while (fgets(buffer[0], 200, stdin)) {
483 output_error(1, err_line, "noweb");
498 \begin_layout Standard
499 The examination itself is very inefficient.
500 Unfortunately noweb doesn't have any characteristic that would help to
501 identify one of its error messages.
502 The solution is to collect all possible output messages in an array of
503 strings, and turn the examination process into a linear search in this
512 <<Global variables>>=
516 char *noweb_msgs[] = {
520 "couldn't open file",
524 "couldn't open temporary file",
528 "error writing temporary file",
540 "Bad format sequence",
544 "Can't open output file",
548 "Can't open temporary file",
552 "Capacity exceeded:",
556 "Ignoring unknown option -",
560 "This can't happen:",
564 "non-numeric line number in"
576 char *noweb_msgs_mimic_gcc[] = {
580 ": unescaped << in documentation chunk"
591 \begin_layout Standard
592 A noweb error message can be any string that contains a matching pair of
605 > >, or any of the above strings
614 \begin_inset Newline newline
618 \begin_inset Newline newline
621 noweb_try (int buf_line)
622 \begin_inset Newline newline
626 \begin_inset Newline newline
630 \begin_inset Newline newline
634 \begin_inset Newline newline
638 \begin_inset Newline newline
641 b = buffer[buf_line];
642 \begin_inset Newline newline
646 \begin_inset Newline newline
650 \begin_inset Newline newline
653 for (i=0; i<1; i++) {
654 \begin_inset Newline newline
657 s = (char *)strstr (b, noweb_msgs_mimic_gcc[i]);
658 \begin_inset Newline newline
662 \begin_inset Newline newline
665 t = (char *)strchr(buffer[buf_line], ':');
666 \begin_inset Newline newline
669 err_line = atoi(t+1);
670 \begin_inset Newline newline
673 t = buffer[buf_line];
674 \begin_inset Newline newline
678 \begin_inset Newline newline
681 while (*(t++) = *(s++));
682 \begin_inset Newline newline
686 \begin_inset Newline newline
690 \begin_inset Newline newline
694 \begin_inset Newline newline
697 s = (char *)strstr(b, "<<");
698 \begin_inset Newline newline
702 \begin_inset Newline newline
705 s = (char *)strstr(s+2, ">>");
706 \begin_inset Newline newline
710 \begin_inset Newline newline
714 \begin_inset Newline newline
718 \begin_inset Newline newline
722 \begin_inset Newline newline
725 for (i = 0; i < 12; ++i) {
726 \begin_inset Newline newline
729 s = (char *)strstr (b, noweb_msgs[i]);
730 \begin_inset Newline newline
734 \begin_inset Newline newline
738 \begin_inset Newline newline
742 \begin_inset Newline newline
746 \begin_inset Newline newline
750 \begin_inset Newline newline
754 \begin_inset Newline newline
758 \begin_inset Newline newline
765 <<Function prototypes>>=
769 int noweb_try (int buf_line);
776 \begin_layout Standard
777 The xlc compiler always outputs one single line for each error found, thus
778 to scan the buffer for xlc error messages it is enough to exam one input
783 <<Scan input for xlc error messages>>=
795 while (fgets(buffer[last_buf_line], 200, stdin)) {
803 output_error(1, err_line, "xlc");
818 \begin_layout Standard
819 A xlc error message is easy to identify.
820 Every error message starts with a quoted string with no spaces, a comma,
822 \begin_inset Quotes eld
826 \begin_inset Quotes erd
829 , a space, and some variable text.
830 The following routine tests if a given buffer line matches this criteria:
850 xlc_try (int buf_line)
866 t = buffer[buf_line];
874 while (*s != '"' && *s != ' ' && *s != '
884 if (*t != '"' || *s != '"' || strncmp(s+1, ", line ", 7) != 0)
912 <<Function prototypes>>=
916 int xlc_try (int buf_line);
923 \begin_layout Standard
924 The gcc compiler error messages are more complicated to scan.
925 Each error can span more than one line in the buffer.
926 The good news is that every buffer line on each error has the same pattern,
927 and share the same line number.
928 Thus the strategy will be to accumulate lines in the buffer while the reported
929 line number is still the same.
930 At the time they differ, all the accumulated lines, except the last one,
931 will belong to one single error message, which now can be output-ed to
935 \begin_layout Standard
936 Every gcc error message contains a string with no space followed by a
937 \begin_inset Quotes eld
941 \begin_inset Quotes eld
945 If the next character is a space, then this line is a header of a error
946 message and the next line will detail the line number of the source code
947 where the error was found.
948 Otherwise, the next thing is a integer number followed by another
949 \begin_inset Quotes eld
953 \begin_inset Quotes eld
960 <<Scan input for gcc error messages>>=
980 while (fgets(buffer[last_buf_line], 200, stdin)) {
984 /****** Skip lines until I find an error */
988 s = (char *)strpbrk(buffer[last_buf_line], " :");
992 if (s == NULL || *s == ' ')
996 continue; /* No gcc error found here */
1004 <<gcc error message criteria is to find a "...:999:" or a "...: ">>
1008 /****** OK It is an error message, get line number */
1012 err_line = atoi(s+1);
1016 if (last_err_line == 0 || last_err_line == err_line) {
1020 last_err_line = err_line;
1024 continue; /* It's either a header or a continuation, don't output
1033 /****** Completed the scan of one error message, output it to LyX
1038 discharge_buffer(1);
1046 } while (fgets(buffer[last_buf_line], 200, stdin));
1054 /****** EOF completes the scan of whatever was being scanned */
1058 discharge_buffer(0);
1070 <<gcc error message criteria is to find a "...:999:" or a "...: ">>=
1074 /****** Search first ":" in the error number */
1078 s = (char *)strpbrk(buffer[last_buf_line], " :");
1086 if (s == NULL || *s == ' ')
1090 <<No gcc error found here, but it might terminate the scanning of a previous
1095 /****** Search second ":" in the error number */
1099 t = (char *)strpbrk(s+1, " :");
1100 \begin_inset Newline newline
1103 if (t == NULL || *t == ' ')
1104 \begin_inset Newline newline
1107 <<No gcc error found here, but it might terminate the scanning of a previous
1109 \begin_inset Newline newline
1112 /****** Verify if is all digits between ":" */
1113 \begin_inset Newline newline
1116 if (t != s+1+strspn(s+1, "0123456789"))
1117 \begin_inset Newline newline
1120 <<No gcc error found here, but it might terminate the scanning of a previous
1122 \begin_inset Newline newline
1137 <<No gcc error found here, but it might terminate the scanning of a previous
1150 discharge_buffer(1);
1165 \begin_layout Standard
1166 As we mentioned, when the scan of one gcc error message is completed everything
1167 in the buffer except the last line is one single error message.
1168 But if the scan terminates with a EOF or through finding one line that
1169 does not match the gcc error message criteria, then there is no
1170 \begin_inset Quotes eld
1174 \begin_inset Quotes erd
1177 in the buffer to be concerned with.
1178 In those cases we empty the buffer completely.
1190 <<Function bodies>>=
1198 discharge_buffer (int save_last)
1206 if (last_err_line != 0) {
1210 clean_gcc_messages();
1214 if (save_last != 0) {
1218 output_error(last_buf_line-1, last_err_line, "gcc");
1222 strcpy (buffer[0], buffer[last_buf_line-1]);
1226 last_err_line = err_line;
1242 clean_gcc_messages();
1246 output_error(last_buf_line-1, last_err_line, "gcc");
1274 <<Function prototypes>>=
1278 void discharge_buffer (int save_last);
1285 \begin_layout Standard
1287 \begin_inset Quotes eld
1291 \begin_inset Quotes erd
1294 superfluous information from gcc messages, namely the name of the noweb
1295 file and the line number of the Error.
1299 \begin_layout Plain Layout
1301 For instance, some way of distinguishing between gcc Errors and Warnings
1311 <<Function bodies>>=
1319 clean_gcc_messages ()
1339 int search_len = sprintf(search, ".nw:%d:", last_err_line);
1347 for (index = 0; index < last_buf_line-1; index++) {
1351 tail = (char *)strstr (buffer[index], search);
1355 if ( tail == NULL) {
1359 tail = (char *) strstr (buffer[index], ".nw:");
1391 head = buffer[index];
1395 while (*(head++) = *(tail++));
1415 <<Function prototypes>>=
1419 void clean_gcc_messages ();
1426 \begin_layout Standard
1427 To combine the scan of noweb error messages and xlc error messages is very
1429 We just try each one for every input line:
1433 <<AIX system using both noweb and xlc>>=
1442 \begin_inset Newline newline
1445 while (fgets(buffer[0], 200, stdin)) {
1446 \begin_inset Newline newline
1450 \begin_inset Newline newline
1453 output_error(1, err_line, "noweb");
1454 \begin_inset Newline newline
1457 else if (xlc_try(0))
1458 \begin_inset Newline newline
1461 output_error(1, err_line, "xlc");
1462 \begin_inset Newline newline
1466 \begin_inset Newline newline
1470 \begin_inset Newline newline
1476 \begin_layout Standard
1477 To combine the scan of noweb error messages and gcc error messages is simple
1478 if we realize that it is not possible to find a noweb error message in
1479 the middle of a gcc error message.
1480 So we just repeat the gcc procedure and test for noweb error messages in
1481 the beginning of the scan:
1485 <<Solaris and Linux systems using both noweb and gcc>>=
1505 while (fgets(buffer[last_buf_line], 200, stdin)) {
1509 /****** Skip lines until I find an error */
1513 if (last_buf_line == 0 && noweb_try(0)) {
1517 output_error(1, err_line, "noweb");
1529 s = (char *)strpbrk(buffer[last_buf_line], " :");
1533 if (s == NULL || *s == ' ')
1537 continue; /* No gcc error found here */
1545 <<gcc error message criteria is to find a "...:999:" or a "...: ">>
1549 /****** OK It is an error, get line number */
1553 err_line = atoi(s+1);
1557 if (last_err_line == 0 || last_err_line == err_line) {
1561 last_err_line = err_line;
1565 continue; /* It's either a header or a continuation, don't output
1574 /****** Completed the scan of one error message, output it to LyX
1579 discharge_buffer(1);
1587 } while (fgets(buffer[last_buf_line], 200, stdin));
1595 /****** EOF completes the scan of whatever was being scanned */
1599 discharge_buffer(0);
1610 \begin_layout Section
1611 Wrapping the code into a file
1623 #include <strings.h>
1631 <<Global variables>>
1635 <<Function prototypes>>
1646 \begin_layout Standard
1647 To build this program, we want to add the
1648 \begin_inset Quotes eld
1652 \begin_inset Quotes erd
1655 option in the tangle command to force gdb to load the file
1664 In accordance with this, we pass the
1665 \begin_inset Quotes eld
1669 \begin_inset Quotes erd
1684 if [ -z "$NOWEB_SOURCE" ]; then NOWEB_SOURCE=Literate.nw; fi
1688 if [ -z "$NOWEB_OUTPUT_DIR" ]; then NOWEB_OUTPUT_DIR=.; fi
1692 notangle -L -Rlisterrors.c ${NOWEB_SOURCE} > ${NOWEB_OUTPUT_DIR}/listerrors.c
1696 gcc -g -o listerrors listerrors.c
1703 \begin_layout Standard
1704 This project can be tangled and compiled from LyX if you set
1710 to call a generic script that always extracts a scrap named
1715 Here is a example of such generic script:
1718 \begin_layout LyX-Code
1720 \begin_inset Newline newline
1723 notangle -Rbuild-script $1 | env NOWEB_SOURCE=$1 NOWEB_OUTPUT_DIR=$r sh
1726 \begin_layout LyX-Code