1
2
3
4
5
6 :(scenarios load)
7 :(scenario first_recipe)
8 def main [
9 1:number <- copy 23
10 ]
11 +parse: instruction: copy
12 +parse: ingredient: {23: "literal"}
13 +parse: product: {1: "number"}
14
15 :(code)
16 vector<recipe_ordinal> load(string form) {
17 istringstream in(form);
18 in >> std::noskipws;
19 return load(in);
20 }
21
22 vector<recipe_ordinal> load(istream& in) {
23 in >> std::noskipws;
24 vector<recipe_ordinal> result;
25 while (has_data(in)) {
26 skip_whitespace_and_comments(in);
27 if (!has_data(in)) break;
28 string command = next_word(in);
29 if (command.empty()) {
30 assert(!has_data(in));
31 break;
32 }
33
34 if (command == "recipe" || command == "def") {
35 recipe_ordinal r = slurp_recipe(in);
36 if (r > 0) result.push_back(r);
37 }
38 else if (command == "recipe!" || command == "def!") {
39 Disable_redefine_checks = true;
40 recipe_ordinal r = slurp_recipe(in);
41 if (r > 0) result.push_back(r);
42 Disable_redefine_checks = false;
43 }
44
45 else {
46 raise << "unknown top-level command: " << command << '\n' << end();
47 }
48 }
49 return result;
50 }
51
52
53 int slurp_recipe(istream& in) {
54 recipe result;
55 result.name = next_word(in);
56 if (result.name.empty()) {
57 assert(!has_data(in));
58 raise << "file ended with 'recipe'\n" << end();
59 return -1;
60 }
61
62 skip_whitespace_but_not_newline(in);
63
64 if (result.name.empty())
65 raise << "empty result.name\n" << end();
66 trace(9991, "parse") << "--- defining " << result.name << end();
67 if (!contains_key(Recipe_ordinal, result.name))
68 put(Recipe_ordinal, result.name, Next_recipe_ordinal++);
69 if (Recipe.find(get(Recipe_ordinal, result.name)) != Recipe.end()) {
70 trace(9991, "parse") << "already exists" << end();
71 if (should_check_for_redefine(result.name))
72 raise << "redefining recipe " << result.name << "\n" << end();
73 Recipe.erase(get(Recipe_ordinal, result.name));
74 }
75 slurp_body(in, result);
76
77 put(Recipe, get(Recipe_ordinal, result.name), result);
78 return get(Recipe_ordinal, result.name);
79 }
80
81 void slurp_body(istream& in, recipe& result) {
82 in >> std::noskipws;
83 skip_whitespace_but_not_newline(in);
84 if (in.get() != '[')
85 raise << result.name << ": recipe body must begin with '['\n" << end();
86 skip_whitespace_and_comments(in);
87 instruction curr;
88 while (next_instruction(in, &curr)) {
89 curr.original_string = to_original_string(curr);
90
91 trace(9992, "load") << "after rewriting: " << to_string(curr) << end();
92 if (!curr.is_empty()) result.steps.push_back(curr);
93 }
94 }
95
96 bool next_instruction(istream& in, instruction* curr) {
97 curr->clear();
98 skip_whitespace_and_comments(in);
99 if (!has_data(in)) {
100 raise << "incomplete recipe at end of file (0)\n" << end();
101 return false;
102 }
103
104 vector<string> words;
105 while (has_data(in) && in.peek() != '\n') {
106 skip_whitespace_but_not_newline(in);
107 if (!has_data(in)) {
108 raise << "incomplete recipe at end of file (1)\n" << end();
109 return false;
110 }
111 string word = next_word(in);
112 if (word.empty()) {
113 assert(!has_data(in));
114 raise << "incomplete recipe at end of file (2)\n" << end();
115 return false;
116 }
117 words.push_back(word);
118 skip_whitespace_but_not_newline(in);
119 }
120 skip_whitespace_and_comments(in);
121 if (SIZE(words) == 1 && words.at(0) == "]")
122 return false;
123
124 if (SIZE(words) == 1 && is_label_word(words.at(0))) {
125 curr->is_label = true;
126 curr->label = words.at(0);
127 trace(9993, "parse") << "label: " << curr->label << end();
128 if (!has_data(in)) {
129 raise << "incomplete recipe at end of file (3)\n" << end();
130 return false;
131 }
132 return true;
133 }
134
135 vector<string>::iterator p = words.begin();
136 if (find(words.begin(), words.end(), "<-") != words.end()) {
137 for (; *p != "<-"; ++p)
138 curr->products.push_back(reagent(*p));
139 ++p;
140 }
141
142 if (p == words.end()) {
143 raise << "instruction prematurely ended with '<-'\n" << end();
144 return false;
145 }
146 curr->old_name = curr->name = *p; ++p;
147
148
149 for (; p != words.end(); ++p)
150 curr->ingredients.push_back(reagent(*p));
151
152 trace(9993, "parse") << "instruction: " << curr->name << end();
153 trace(9993, "parse") << " number of ingredients: " << SIZE(curr->ingredients) << end();
154 for (vector<reagent>::iterator p = curr->ingredients.begin(); p != curr->ingredients.end(); ++p)
155 trace(9993, "parse") << " ingredient: " << to_string(*p) << end();
156 for (vector<reagent>::iterator p = curr->products.begin(); p != curr->products.end(); ++p)
157 trace(9993, "parse") << " product: " << to_string(*p) << end();
158 if (!has_data(in)) {
159 raise << "9: unbalanced '[' for recipe\n" << end();
160 return false;
161 }
162 return true;
163 }
164
165
166 string next_word(istream& in) {
167 skip_whitespace_but_not_newline(in);
168
169 ostringstream out;
170 slurp_word(in, out);
171 skip_whitespace_and_comments_but_not_newline(in);
172 string result = out.str();
173 if (result != "[" && ends_with(result, '['))
174 raise << "insert a space before '[' in '" << result << "'\n" << end();
175 return result;
176 }
177
178 bool is_label_word(const string& word) {
179 if (word.empty()) return false;
180 return !isalnum(word.at(0)) && string("$_*@&,=-[]()").find(word.at(0)) == string::npos;
181 }
182
183 bool ends_with(const string& s, const char c) {
184 if (s.empty()) return false;
185 return *s.rbegin() == c;
186 }
187
188 :(before "End Globals")
189
190 extern const string Terminators("(){}");
191 :(code)
192 void slurp_word(istream& in, ostream& out) {
193 char c;
194 if (has_data(in) && Terminators.find(in.peek()) != string::npos) {
195 in >> c;
196 out << c;
197 return;
198 }
199 while (in >> c) {
200 if (isspace(c) || Terminators.find(c) != string::npos || Ignore.find(c) != string::npos) {
201 in.putback(c);
202 break;
203 }
204 out << c;
205 }
206 }
207
208 void skip_whitespace_and_comments(istream& in) {
209 while (true) {
210 if (!has_data(in)) break;
211 if (isspace(in.peek())) in.get();
212 else if (Ignore.find(in.peek()) != string::npos) in.get();
213 else if (in.peek() == '#') skip_comment(in);
214 else break;
215 }
216 }
217
218
219 void skip_whitespace_and_comments_but_not_newline(istream& in) {
220 while (true) {
221 if (!has_data(in)) break;
222 if (in.peek() == '\n') break;
223 if (isspace(in.peek())) in.get();
224 else if (Ignore.find(in.peek()) != string::npos) in.get();
225 else if (in.peek() == '#') skip_comment(in);
226 else break;
227 }
228 }
229
230 void skip_comment(istream& in) {
231 if (has_data(in) && in.peek() == '#') {
232 in.get();
233 while (has_data(in) && in.peek() != '\n') in.get();
234 }
235 }
236
237 :(scenario recipe_instead_of_def)
238 recipe main [
239 1:number <- copy 23
240 ]
241 +parse: instruction: copy
242 +parse: ingredient: {23: "literal"}
243 +parse: product: {1: "number"}
244
245 :(scenario parse_comment_outside_recipe)
246
247 def f1 [
248 ]
249
250 def main [
251 1:number <- copy 23
252 ]
253 +parse: instruction: copy
254 +parse: ingredient: {23: "literal"}
255 +parse: product: {1: "number"}
256
257 :(scenario parse_comment_amongst_instruction)
258 def main [
259
260 1:number <- copy 23
261 ]
262 +parse: instruction: copy
263 +parse: ingredient: {23: "literal"}
264 +parse: product: {1: "number"}
265
266 :(scenario parse_comment_amongst_instruction_2)
267 def main [
268
269 1:number <- copy 23
270
271 ]
272 +parse: instruction: copy
273 +parse: ingredient: {23: "literal"}
274 +parse: product: {1: "number"}
275
276 :(scenario parse_comment_amongst_instruction_3)
277 def main [
278 1:number <- copy 23
279
280 2:number <- copy 23
281 ]
282 +parse: instruction: copy
283 +parse: ingredient: {23: "literal"}
284 +parse: product: {1: "number"}
285 +parse: instruction: copy
286 +parse: ingredient: {23: "literal"}
287 +parse: product: {2: "number"}
288
289 :(scenario parse_comment_after_instruction)
290 def main [
291 1:number <- copy 23
292 ]
293 +parse: instruction: copy
294 +parse: ingredient: {23: "literal"}
295 +parse: product: {1: "number"}
296
297 :(scenario parse_label)
298 def main [
299 +foo
300 ]
301 +parse: label: +foo
302
303 :(scenario parse_dollar_as_recipe_name)
304 def main [
305 $foo
306 ]
307 +parse: instruction: $foo
308
309 :(scenario parse_multiple_properties)
310 def main [
311 1:number <- copy 23/foo:bar:baz
312 ]
313 +parse: instruction: copy
314 +parse: ingredient: {23: "literal", "foo": ("bar" "baz")}
315 +parse: product: {1: "number"}
316
317 :(scenario parse_multiple_products)
318 def main [
319 1:number, 2:number <- copy 23
320 ]
321 +parse: instruction: copy
322 +parse: ingredient: {23: "literal"}
323 +parse: product: {1: "number"}
324 +parse: product: {2: "number"}
325
326 :(scenario parse_multiple_ingredients)
327 def main [
328 1:number, 2:number <- copy 23, 4:number
329 ]
330 +parse: instruction: copy
331 +parse: ingredient: {23: "literal"}
332 +parse: ingredient: {4: "number"}
333 +parse: product: {1: "number"}
334 +parse: product: {2: "number"}
335
336 :(scenario parse_multiple_types)
337 def main [
338 1:number, 2:address:number <- copy 23, 4:number
339 ]
340 +parse: instruction: copy
341 +parse: ingredient: {23: "literal"}
342 +parse: ingredient: {4: "number"}
343 +parse: product: {1: "number"}
344 +parse: product: {2: ("address" "number")}
345
346 :(scenario parse_properties)
347 def main [
348 1:address:number/lookup <- copy 23
349 ]
350 +parse: product: {1: ("address" "number"), "lookup": ()}
351
352
353 :(code)
354 void test_parse_comment_terminated_by_eof() {
355 load("recipe main [\n"
356 " a:number <- copy 34\n"
357 "]\n"
358 "# abc");
359 cerr << ".";
360 }
361
362 :(scenario warn_on_missing_space_before_bracket)
363 % Hide_errors = true;
364 def main[
365 1:number <- copy 23
366 ]
367 +error: insert a space before '[' in 'main['
368
369
370
371
372 :(before "End Globals")
373 bool Disable_redefine_checks = false;
374 :(before "End Setup")
375 Disable_redefine_checks = false;
376 :(code)
377 bool should_check_for_redefine(const string& recipe_name) {
378 if (Disable_redefine_checks) return false;
379 return true;
380 }
381
382 :(scenario forbid_redefining_recipes)
383 % Hide_errors = true;
384 def main [
385 1:number <- copy 23
386 ]
387 def main [
388 1:number <- copy 24
389 ]
390 +error: redefining recipe main
391
392 :(scenario permit_forcibly_redefining_recipes)
393 def main [
394 1:number <- copy 23
395 ]
396 def! main [
397 1:number <- copy 24
398 ]
399 -error: redefining recipe main
400 $error: 0
401
402 :(code)
403
404 void show_rest_of_stream(istream& in) {
405 cerr << '^';
406 char c;
407 while (in >> c)
408 cerr << c;
409 cerr << "$\n";
410 exit(0);
411 }