summaryrefslogtreecommitdiff
path: root/web/noweb/src/c/markup.nw
blob: bce184083094517d5eab9e85554ec42738547642 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
% Copyright 1991 by Norman Ramsey.  All rights reserved.
% See file COPYRIGHT for more information.
\subsection{Identifying the special {\tt noweb} control sequences}
This file is supposed to contain all the definitions of how different
things are distinguished in the source text.
@
Documentation is introduced only by an at sign (@) on a line by itself
(optionally followed by spaces).
It continues up to new documentation or a module definition.
A module definition is a module name, followed by one equals sign (=),
possibly followed by white space, on a line by itself.
A module name is any test enclosed in double angle brackets ([[@<<...@>>]]).
The ``unnamed'' module is called * (that is, [[@<<*@>>]]).

Double angle brackets may be escaped in source by preceding them with
the at sign.
No other character, not even the at sign, needs to be escaped.
This makes for more readable source, but makes it impossible to put an 
at sign immediately before a module name.
If we liked we could try to hack this by creating a predefined module that 
expands to an at sign, but I won't bother.

<<*>>=
static char rcsid[] = "$Id: markup.nw,v 2.24 2008/10/06 01:03:05 nr Exp nr $";
static char rcsname[] = "$Name: v2_12 $";
static struct keepalive { char *s; struct keepalive *p; } keepalive[] =
  { {rcsid, keepalive}, {rcsname, keepalive} }; /* avoid warnings */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include "markup.h"
#include "strsave.h"
#include "errors.h"

<<data definitions>>

<<C functions>>
@ 
To quote from the man page:
\begin{quote} 
A 
{\em noweb }
file consists of a sequence of
{\em chunks,}
which may appear in any order.
{\em noweb}
supports two kinds of chunks: documentation chunks and code chunks.
Documentation chunks begin with an at sign (@) at the beginning of a line,
followed by a space 
(or with an at sign on a line by itself).
They have no names.
Code chunks begin with
$$\hbox{\tt@<<{\em chunk name\/}@>>=}$$
on a line by itself.
The left double angle bracket ({\tt @<<}) must be in the first column.
Chunks are terminated by the beginning of another chunk, or by end of file.
If the first line in the file does not signal the beginning of a documentation
or code chunk, the first chunk is assumed to be a documentation chunk.
\end{quote} 
@
<<data definitions>>=
char at_sign = '@';             /* should be the only place '@' is mentioned */
<<header>>=
extern char at_sign;                    /* the at sign */
@
Here's how we recognize the start of a documentation chunk, and
extract the first line of documentation from what follows the @ sign.
<<header>>=
int starts_doc(char *);
char *first_doc_line(char *);
<<C functions>>=
int starts_doc(char *line) {
    return (*line==at_sign && (line[1]=='\0' || isspace(line[1])));
}

char *first_doc_line(char *line) {
    if (line[1]!='\0' && line[1] !='\n') return line+2;
    else return line+1;
}
@
An index line is a special case of starting documentation.
<<header>>=
int is_def(char *);
char *remove_def_marker(char *);
<<data definitions>>=
static char def_marker[] = " %def ";
#define def_length (6)
<<C functions>>=
int is_def(char *line) {
    int answer;
    static int complained;
    <<first time through, assert we got the length right>>
    answer = (*line==at_sign && !strncmp(line+1, def_marker, def_length));
    <<if [[answer && !complained]], complain>>
    return answer;
}

char *remove_def_marker(char *line) {
    return line+1+def_length;
}
<<first time through, assert we got the length right>>=
{ static int checked = 0;
  if (!checked) {
    assert(strlen(def_marker) == def_length);
    checked = 1;
  }
}
@ 
Explicit deprecation is just too painful.  I think the users won't
stand for it.
<<if [[answer && !complained]], complain>>=
if (answer && !complained) {
    complained = 1;
/*
    fprintf(stderr, "Warning: @ %%def is deprecated.  Use `-filter autodefs.xxx'\n"
	            "         or `-filter btdefn'\n");
*/
}
@ 
Recognizing module names (which introduce code chunks)
and extracting them is the mission of the [[mod_start]] and [[mod_end]]
functions.

The basic idea is to find an instance of [[@<<]], while skipping every
instance of [[@@<<]].
That's the start of a module name.
We find the end the same way.
A function called [[find_escaped]] does the dirty work.
If it succeeds, it returns a pointer to the first character after
the special sequence, in this case the first character of the
module name.
(If it fails, it returns a null pointer.)
If its [[mark]] paramter is set, it will put a [['\0']] in place
of the special sequence, splitting the string into pieces.
These two properties make it perfect for dissecting lines with module names.

[[mod_start]] and [[mod_end]] just store the special sequences.
<<header>>=
char *mod_start (char *s, int mark);    /* find the first module name */
char *mod_end (char *s, int mark);              /* find the end of module name */
<<C functions>>=
char *mod_start(char *s, int mark) {
    return find_escaped(s,"@<<","@@<<", mark);
}
char *mod_end(char *s, int mark) {
    return find_escaped(s,"@>>","@@>>", mark);
}
@ 
Once we know how to [[mod_start]] and [[mod_end]], recognizing the
beginning of a code chunk is easy.  The only tricky bit is that if we
see an unclosed, unescaped [[<<]] in a code chunk, this should be acceptable.
Because in a documentation chunk, an unclosed, unescaped [[<<]] will
already be caught with a ``module name doesn't end'' message, the
right thing to do here is silently to let an unclosed [[<<]] pass.
<<header>>=
int starts_code (char *line, char *filename, int lineno);  
                                        /* does this line start module defn? */
void getmodname(char *dest, int size, char *source);
                                        /* extract module name and put in dest */
<<C functions>>=
int starts_code (char *line, char *filename, int lineno) {
    char *tail;
    if (mod_start(line,0) != line+2) return 0;
    tail = mod_end(line+2,0);
    if (tail == NULL || *tail++ != '=') return 0;
    while (isspace(*tail)) tail++;
    return (*tail == '\0');
}

void getmodname(char *dest, int size, char *source) {
    /* excess characters in the module name are ignored */
    char *p = strsave(source);
    char *q = mod_start(p,1);

    if (q==NULL) <<Error exit from getmodname>>
    if (mod_end(q,1)==NULL) <<Error exit from getmodname>>
    strncpy(dest,q,size-1);
    dest[size-1] = '\0';
    free(p);
}
<<Error exit from getmodname>>= 
{
    free(p);
    impossible
        ("I couldn't manage to extract a module name, but I'm sure I saw one");
}
@
Here we do the magic [[find_escaped]].
Find an arbitrary character seqence, skipping an escaped sequence (if any),
replacing the beginning with [['\0']] if [[mark]] is set.

 If no [[search]] sequence in [[s]], returns [[NULL]].
      Otherwise, returns a pointer to the first character after the
      [[search]] sequence, and sets the first character to [['\0']], so
      that [[s]] will point to the part of the string before the [[search]]
      sequence.  The [[escape]] sequence (if any) is always skipped,
      even if the [[search]] sequence is a substring of it.

[[escape]] will be ignored if it is either the [[NULL]] pointer
or the null string ([[""]]).
<<header>>=
char *find_escaped(register char *s, char *search, char *escape, int mark);
                /* find escaped strings. See markup.nw for details */
<<C functions>>=
char *find_escaped(register char *s, char *search, char *escape, int mark) {
    register char first = *search;
    register char first_escape = (escape != NULL ? *escape : '\0');
    int searchlen = strlen(search);
    int escapelen = (escape != NULL ? strlen(escape) : 0);

    do {
        while (*s && *s != first && *s != first_escape) s++;
        if (!*s) break;
        if (first_escape && !strncmp(s,escape,escapelen)) {
                s += escapelen;
                continue;
        }
        if (!strncmp(s,search,searchlen)) break;
        s++;
    } while (*s != '\0');
    /* here either s is empty or it points to the first unescaped [[search]] */
    if (*s == '\0') return NULL;
    if (mark) *s = '\0';
    return s+searchlen;
}