aboutsummaryrefslogtreecommitdiff
path: root/src/tt.output.c
blob: b1d8e34339268bff69f0e12d97874d93abc73bd6 (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
// Outputting the results
// ============================================================================

// At this point, we have collected all references and accompanying insertinos
// in the source input. Two tasks remain:

// 1. We need to parse the destination files, identifying <<destinations>>.
// 2. We need to copy the destination files to the tangled files, overwriting
//    all <<destinations>> with the corresponding insertions.

// Both of these tasks will be performed in the same loop: -> main.output

  for (k = offset; k < argc; k++) {

// -> main.declarations

  int k;

// The counter k is set to the offset defined in the options section, which
// should be equal to the position of the first destination file in argv.
// We loop as long as we haven't reached the end of argv.

// On each iteration of the loop, we can obtain from argv the name of the
// destination file and copy it to a new string, adding the out_prefix. We'll
// call this string tangledfilename: -> main.declarations

  char *tangledfilename;

// -> main.output

    tangledfilename = malloc(1 + (strlen(out_prefix) + strlen(argv[k]) + 50) * sizeof(char));
    if (tangledfilename == NULL) err(1, "malloc");

    if (sprintf(tangledfilename, "%s%s", out_prefix, argv[k]) == -1)
      err(1, "sprintf");

// Now, we can open the tangled file for writing and the original destination
// file for reading. We'll call the handle for tangledfile f and the handle for
// argv[k] fo, the o standing for "original": -> main.declarations

  FILE *f;
  FILE *fo;

// -> main.output

    f = fopen(tangledfilename, "w");
    if (f == NULL) err(1, "fopen");
    fo = fopen(argv[k], "r");
    if (fo == NULL) err(1, "fopen");

// Having successfully opened the files, we have no need for tangledfilename:
// -> main.output

    free(tangledfilename);


// Parsing the current destination file and writing the tangled file
// ----------------------------------------------------------------------------

// The destination file will be parsed in a manner similar to the way in which
// the source input was parsed. The same structure will be used: -> main.output

    line_l = 0;
    /* line_s is remembered */
    
    while ((b = fgetc(fo)) != EOF) {
      c = b;
      if (c != '\n') {
        if (line_l + 1 > line_s) {
          line_s += 20;
          tmp = realloc(line, 1 + line_s * sizeof(char));
          if (tmp == NULL) err(1, "malloc");
          line = tmp;
        }
        line[line_l++] = c;
        continue;
      }

// Again, characters will be added to the line variable until a newline is
// encountered, at which point the collected line will be finished:
// -> main.output

finish2:
      line[line_l] = '\0';
      line_l = 0; /* reset line length count */

// From here on, however, the loop will look a bit different. First, tt takes
// note of the line's indentation, saving it to the indent variable:
// -> main.declarations

  int indent;

// Only spaces are currently supported: -> main.output

      ref = line;
      for (indent = 0; *ref == ' '; ref++) indent++;

// Also, as you can see, we re-use the ref variable that was used by the input
// parsing, but which is now unused.

// Parsing the <<destination identifier>> is simple: -> main.output

      if (strncmp(ref, "<<", 2) != 0
        || strncmp(ref + strlen(ref) - 2, ">>", 2) != 0) {
        fprintf(f, "%s\n", line);
        continue;
      }

// If no potential destination is found, then the line will be written as-is to
// the tangled file, and the loop continues parsing the next line of the file.
// If a potential destination is found, however, we store it in the ref
// variable, removing the << and >> markers: -> main.output

      ref += 2;
      ref[strlen(ref) - 2] = '\0';

// There is still one thing to check, before we know that the destination is
// valid -- it must not contain any whitespace: -> main.output

      for (i = 0; i < strlen(ref); i++)
        if (isspace(ref[i])) {
          fprintf(f, "%s\n", line);
          continue;
        }

// Again, if there is whitespace, then the line does not signify a destination
// and should be printed as-is to the resulting tangled file.

// As when parsing the input, long identifiers are truncated: -> main.output

      if (strlen(ref) > REFMAX)
        fprintf(stderr,
          "Warning: Truncating identifier exceeding %d characters\n", REFMAX);

// Finally, we check whether the destination actually has been referenced by
// the source input, warning the user otherwise: -> main.output

      for (i = 0; i < refs_c; i++)
        if (strncmp(refs[i], ref, REFMAX) == 0) goto found;
      fprintf(stderr, "Unreferenced destination: %s\n", ref);
      continue;
found:

// Having established that the identified destination is referenced by the
// source input, and having stored in the local i variable the reference's
// position in the refs variable, we can retrieve the insertion for the
// reference by looking at the same position in the ins variable.

// Our first order of business is to make sure that the insertion is not empty
// -- in that case, the user is warned, and the loop goes on to the next line:
// -> main.output

      if (ins[i] == NULL) {
        fprintf(stderr, "Warning: Insertion for %s is empty\n", ref);
        continue;
      }

// Now, we are ready to write the insertion for the destination to the tangled
// file. Because each insertion is stored as an array of strings, each string
// containing a single line of the insertion, we use yet another loop:
// -> main.output

      for (j = 0; ins[i][j] != NULL; j++) {
        if (ins[i][j + 1] == NULL) {
          if (strlen(ins[i][j]) == 0)
            break; /* remove extra newline */
        }
        for (m = indent; m > 0; m--) putc(' ', f);
        fprintf(f, "%s\n", ins[i][j]);
      }
    }

// -> main.declarations

int j;
int m;

// Apart from simply printing the inserted line to the tangled file, the code
// above also skips any empty line at the end of the insertion and adds the
// indentation identified when parsing the line in the destination file
// containing the destination identifier.

// Now, we have almost finished parsing the current destination file and
// writing to the corresponding tangled file, but -- as before -- we still
// haven't processed the final line of the file, if that line ends without
// a newline. To fix that, we just run the finishing code again:
// -> main.output

    if (c != '\n') { c = '\n'; goto finish2; }

// Finally, we close the handles to the destination file and tangled file:
// -> main.output

    fclose(f);
    fclose(fo);
  }

// And that is the end of the loop. The loop continues for every destination
// file given as an argument, and when it is done, so is the program.