Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

open-axiom repository from github

24005 views
1
// Copyright (C) 2013, Gabriel Dos Reis.
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are
6
// met:
7
//
8
// - Redistributions of source code must retain the above copyright
9
// notice, this list of conditions and the following disclaimer.
10
//
11
// - Redistributions in binary form must reproduce the above copyright
12
// notice, this list of conditions and the following disclaimer in
13
// the documentation and/or other materials provided with the
14
// distribution.
15
//
16
// - Neither the name of The Numerical Algorithms Group Ltd. nor the
17
// names of its contributors may be used to endorse or promote products
18
// derived from this software without specific prior written permission.
19
//
20
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
21
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
24
// OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32
// --% Author: Gabriel Dos Reis.
33
// --% Description:
34
// --% This program implements basic functionalities for untangling
35
// --% algebra source code from the pamphlets. The syntax is that
36
// --% of `noweb'. A chunk definition starts with a pattern
37
// --% <<name>>= on a line by itself, and ends with `@' by itself
38
// --% on a line. A chunk can refer to another chunk through
39
// --% a pattern of the form `<<name>>'.
40
41
#include <string.h>
42
#include <stdlib.h>
43
#include <utility>
44
#include <string>
45
#include <iostream>
46
#include <fstream>
47
#include <iterator>
48
#include <list>
49
#include <vector>
50
#include <map>
51
#include <open-axiom/storage>
52
#include <open-axiom/FileMapping>
53
54
namespace OpenAxiom {
55
namespace Hammer {
56
// -------------
57
// -- Element --
58
// -------------
59
// Base class of document elements.
60
struct Element {
61
virtual ~Element() { }
62
};
63
64
// ---------------
65
// -- BasicText --
66
// ---------------
67
// Plain text, with no reference to any chunk.
68
struct BasicText : Element {
69
BasicText(const Byte* f, const Byte* l) : span(f, l) { }
70
// Pointer to the start of this basic text element
71
const Byte* begin() const { return span.first; }
72
// One-past-the-end of the this basic text element.
73
const Byte* end() const { return span.second; }
74
private:
75
std::pair<const Byte*, const Byte*> span;
76
};
77
78
// ---------------
79
// -- Reference --
80
// ---------------
81
// Reference to a a chunk by name.
82
struct Reference : Element {
83
explicit Reference(const std::string& s) : label(s) { }
84
// Naame of the chunk referenced.
85
const std::string& name() const { return label; }
86
private:
87
const std::string label;
88
};
89
90
// -------------------
91
// -- CompositeText --
92
// -------------------
93
// Sequence of basic elements and reference to chunks.
94
struct CompositeText: private std::vector<const Element*> {
95
typedef std::vector<const Element*> base;
96
using base::iterator;
97
using base::begin;
98
using base::end;
99
using base::size;
100
using base::operator[];
101
102
// Augment this chunk with a basic text in the open interval
103
// [f,l).
104
CompositeText& add_text(const Byte* f, const Byte* l) {
105
texts.push_back(BasicText(f, l));
106
push_back(&texts.back());
107
return *this;
108
}
109
110
// Augment this chunk with a reference to another chunk
111
// named `n'. Note that we don't attempt to check for
112
// possible circularities.
113
CompositeText reference_chunk(const Byte* f, const Byte* l) {
114
refs.push_back(Reference(std::string(f, l)));
115
push_back(&refs.back());
116
return *this;
117
}
118
119
private:
120
std::list<BasicText> texts;
121
std::list<Reference> refs;
122
};
123
124
// --------------
125
// -- Document --
126
// --------------
127
// A whole document; a sequence of chunks.
128
struct Document : std::list<CompositeText> {
129
Document(const Memory::FileMapping& file)
130
: active_chunk(&prose), text_start(file.begin()) {
131
parse(file);
132
}
133
134
// Return a pointer to a document chunk name `n'.
135
// Otherwise, return null.
136
CompositeText* lookup_chunk(const std::string& n) const {
137
ChunkTable::const_iterator i = defs.find(n);
138
return i == defs.end() ? 0 : i->second;
139
}
140
141
private:
142
typedef std::map<std::string, CompositeText*> ChunkTable;
143
CompositeText prose; // the prose around the chunks.
144
ChunkTable defs; // chunk definition table.
145
CompositeText* active_chunk; // chunk under construction.
146
const Byte* text_start; // begining of current basic text.
147
148
// Append basic text in the range `[text_start,last)'
149
// to the current chunk.
150
void finish_chunk(const Byte* last) {
151
if (text_start != last)
152
active_chunk->add_text(text_start, last);
153
active_chunk = &prose;
154
text_start = last;
155
}
156
157
// Start a new chunk or extend an existing chunk.
158
void begin_chunk(const std::string& name, const Byte* start) {
159
if (CompositeText* chunk = lookup_chunk(name))
160
active_chunk = chunk;
161
else {
162
push_back(CompositeText());
163
defs[name] = active_chunk = &back();
164
}
165
text_start = start;
166
}
167
168
// Parse a file mapping into this document.
169
void parse(const Memory::FileMapping&);
170
};
171
172
// Return true if the character `c' introduces a newline.
173
static inline bool
174
looking_at_newline(char c) {
175
return c == '\n' or c == '\r';
176
}
177
178
// Attempt to advance the cursor past newline marker.
179
// Return true on sucess.
180
static bool
181
saw_newline(const Byte*& cur, const Byte* end) {
182
if (*cur == '\n') {
183
++cur;
184
return true;
185
}
186
else if (*cur == '\r') {
187
if (++cur < end and *cur == '\n')
188
++cur;
189
return true;
190
}
191
return false;
192
}
193
194
// Move `cur' to end of line or `end', whichever comes first.
195
// Return true if the area swept consisted only of blank characters.
196
static inline bool
197
trailing_blank(const Byte*& cur, const Byte* end) {
198
bool result = true;
199
for (; cur < end and not saw_newline(cur, end); ++cur)
200
result = isspace(*cur);
201
return result;
202
}
203
204
// Attempt to advance `cur' past the double left angle brackets
205
// starting a chunk name. Returm true on success.
206
static bool
207
chunk_name_began(const Byte*& cur, const Byte* end) {
208
if (cur[0] == '<' and cur + 1 < end and cur[1] == '<') {
209
cur += 2;
210
return true;
211
}
212
return false;
213
}
214
215
// Attempt to move `cur' past the double right angle brackets
216
// terminating a chunk name. Returm true on success.
217
static bool
218
chunk_name_ended(const Byte*& cur, const Byte* end) {
219
if (cur[0] == '>' and cur + 1 < end and cur[1] == '>') {
220
cur += 2;
221
return true;
222
}
223
return false;
224
}
225
226
// We've just seen the start of a chunk reference; skip
227
// characters till we seen of the chunk's name.
228
static void
229
skip_to_end_of_chunk_name(const Byte*& cur, const Byte* end) {
230
while (cur < end) {
231
if (looking_at_newline(*cur)
232
or (cur + 1 < end and cur[0] == '>' and cur[1] == '>'))
233
return;
234
++cur;
235
}
236
}
237
238
// Move the cursor until end of line.
239
static void
240
skip_to_end_of_line(const Byte*& cur, const Byte* end) {
241
while (cur < end) {
242
if (saw_newline(cur, end))
243
break;
244
++cur;
245
}
246
}
247
248
void
249
Document::parse(const Memory::FileMapping& file) {
250
auto cur = text_start;
251
auto last = file.end();
252
// Process one line at a time.
253
while (cur < last) {
254
// 1. `@' ends previous chunk
255
if (*cur == '@') {
256
auto p = cur;
257
if (trailing_blank(++cur, last))
258
finish_chunk(p);
259
}
260
// 2. `<<' introduces a chunk reference or a chunk definition.
261
else if (chunk_name_began(cur, last)) {
262
auto label_start = cur;
263
skip_to_end_of_chunk_name(cur, last);
264
if (chunk_name_ended(cur, last)) {
265
auto label_end = cur - 2;
266
if (cur < last and *cur == '=') {
267
if (trailing_blank(++cur, last)) {
268
// chunk definition or extension
269
finish_chunk(label_start - 2);
270
begin_chunk(std::string(label_start, label_end), cur);
271
}
272
}
273
else if (trailing_blank(cur, last)) {
274
// This is just a reference to a chunk.
275
active_chunk->add_text(text_start, label_start - 2);
276
active_chunk->reference_chunk(label_start, label_end);
277
text_start = cur;
278
}
279
else
280
skip_to_end_of_line(cur, last);
281
}
282
}
283
else
284
skip_to_end_of_line(cur, last);
285
}
286
finish_chunk(cur);
287
}
288
289
// Capture chunk resolution in a document.
290
struct resolve_chunk {
291
resolve_chunk(const std::string& s, const Document& f)
292
: name(s), doc(f) { }
293
const std::string name; // name of the chunk
294
const Document& doc; // document containing the chunk.
295
};
296
297
// Print the resolution of a chunk name onto an output stream.
298
std::ostream&
299
operator<<(std::ostream& os, const resolve_chunk& rc) {
300
// FIXME: no attempt at detecting circularities.
301
const CompositeText* doc = rc.doc.lookup_chunk(rc.name);
302
if (doc == 0) {
303
std::cerr << "chunk " << rc.name << " is undefined" << std::endl;
304
exit(1);
305
}
306
for (std::size_t i = 0; i < doc->size(); ++i) {
307
const Element* elt = (*doc)[i];
308
if (const BasicText* t = dynamic_cast<const BasicText*>(elt))
309
std::copy(t->begin(), t->end(),
310
std::ostream_iterator<char>(os));
311
else if (const Reference* r = dynamic_cast<const Reference*>(elt))
312
os << resolve_chunk(r->name(), rc.doc);
313
else {
314
std::cerr << "unknown document element" << std::endl;
315
exit(1);
316
}
317
}
318
319
return os;
320
}
321
322
// Return true if the `arg' is the option named`opt'.
323
static inline bool
324
is_option(const char* arg, const char* opt) {
325
return strcmp(arg, opt) == 0;
326
}
327
328
// `arg' is a argument on the command line. If `arg'
329
// does not match option name `opt', return null. Otherwise,
330
// return a pointer to the terminating NUL character if there
331
// is no specified value for that option, or a pointer to the
332
// start of the value.
333
static const char*
334
is_named_arg(const char* arg, const char* opt) {
335
const int n = strlen(opt);
336
int i = 0;
337
// Get out if argion name does not match.
338
// Note: Ideally, we could use strncmp(). However, that
339
// function is not available in C++98, so we cannot depend on it.
340
for (; i < n ; ++i)
341
if (arg[i] != opt[i])
342
return 0;
343
344
if (arg[i] == '\0')
345
return arg + i; // no value for the option.
346
return arg + n + 1; // being of the value.
347
}
348
}
349
}
350
351
352
int
353
main(int argc, char* argv[]) {
354
using namespace OpenAxiom::Hammer;
355
int error_count = 0;
356
const char* chunk = 0; // chunck to tangle
357
const char* output_path = 0; // path to the output file
358
const char* input_path = 0; // path to the input file.
359
// 1. Process command line arguments.
360
for (int pos = 1; error_count == 0 and pos < argc; ++pos) {
361
if (const char* val = is_named_arg(argv[pos], "--tangle")) {
362
if (chunk != 0) {
363
std::cerr << "cannot tangle more than one chunk";
364
++error_count;
365
}
366
else
367
chunk = *val == 0 ? "*" : val;
368
}
369
else if (const char* val = is_named_arg(argv[pos], "--output")) {
370
if (*val == 0) {
371
std::cerr << "missing output file name" << std::endl;
372
++error_count;
373
}
374
else
375
output_path = val;
376
}
377
else if (argv[pos][0] == '-' and argv[pos][1] == '-') {
378
std::cerr << "unknown option " << argv[pos] << std::endl;
379
++error_count;
380
}
381
else if (input_path != 0) {
382
std::cerr << "there must be exactly one input file" << std::endl;
383
++error_count;
384
}
385
else
386
input_path = argv[pos];
387
}
388
389
// 2. Basic sanity check.
390
if (input_path == 0) {
391
std::cerr << "missing input file" << std::endl;
392
return 1;
393
}
394
if (output_path == 0) {
395
std::cerr << "missing output file" << std::endl;
396
return 1;
397
}
398
if (chunk == 0) {
399
std::cerr << "missing chunk name" << std::endl;
400
return 1;
401
}
402
403
if (error_count != 0)
404
return 1;
405
406
// 3. Attempt to extract the chunk.
407
try {
408
OpenAxiom::Memory::FileMapping file(input_path);
409
std::ofstream os(output_path);
410
os << resolve_chunk(chunk, Document(file));
411
}
412
catch(const OpenAxiom::SystemError& e) {
413
std::cerr << e.message() << std::endl;
414
exit(1);
415
}
416
return 0;
417
}
418
419