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->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
163 return true;
164 }
165
166
167 string next_word(istream& in) {
168 skip_whitespace_but_not_newline(in);
169
170 ostringstream out;
171 slurp_word(in, out);
172 skip_whitespace_and_comments_but_not_newline(in);
173 string result = out.str();
174 if (result != "[" && ends_with(result, '['))
175 raise << "insert a space before '[' in '" << result << "'\n" << end();
176 return result;
177 }
178
179 bool is_label_word(const string& word) {
180 if (word.empty()) return false;
181 return !isalnum(word.at(0)) && string("$_*@&,=-[]()").find(word.at(0)) == string::npos;
182 }
183
184 bool ends_with(const string& s, const char c) {
185 if (s.empty()) return false;
186 return *s.rbegin() == c;
187 }
188
189 :(before "End Globals")
190
191 extern const string Terminators("(){}");
192 :(code)
193 void slurp_word(istream& in, ostream& out) {
194 char c;
195 if (has_data(in) && Terminators.find(in.peek()) != string::npos) {
196 in >> c;
197 out << c;
198 return;
199 }
200 while (in >> c) {
201 if (isspace(c) || Terminators.find(c) != string::npos || Ignore.find(c) != string::npos) {
202 in.putback(c);
203 break;
204 }
205 out << c;
206 }
207 }
208
209 void skip_whitespace_and_comments(istream& in) {
210 while (true) {
211 if (!has_data(in)) break;
212 if (isspace(in.peek())) in.get();
213 else if (Ignore.find(in.peek()) != string::npos) in.get();
214 else if (in.peek() == '#') skip_comment(in);
215 else break;
216 }
217 }
218
219
220 void skip_whitespace_and_comments_but_not_newline(istream& in) {
221 while (true) {
222 if (!has_data(in)) break;
223 if (in.peek() == '\n') break;
224 if (isspace(in.peek())) in.get();
225 else if (Ignore.find(in.peek()) != string::npos) in.get();
226 else if (in.peek() == '#') skip_comment(in);
227 else break;
228 }
229 }
230
231 void skip_comment(istream& in) {
232 if (has_data(in) && in.peek() == '#') {
233 in.get();
234 while (has_data(in) && in.peek() != '\n') in.get();
235 }
236 }
237
238 :(scenario recipe_instead_of_def)
239 recipe main [
240 1:number <- copy 23
241 ]
242 +parse: instruction: copy
243 +parse: ingredient: {23: "literal"}
244 +parse: product: {1: "number"}
245
246 :(scenario parse_comment_outside_recipe)
247
248 def f1 [
249 ]
250
251 def main [
252 1:number <- copy 23
253 ]
254 +parse: instruction: copy
255 +parse: ingredient: {23: "literal"}
256 +parse: product: {1: "number"}
257
258 :(scenario parse_comment_amongst_instruction)
259 def main [
260
261 1:number <- copy 23
262 ]
263 +parse: instruction: copy
264 +parse: ingredient: {23: "literal"}
265 +parse: product: {1: "number"}
266
267 :(scenario parse_comment_amongst_instruction_2)
268 def main [
269
270 1:number <- copy 23
271
272 ]
273 +parse: instruction: copy
274 +parse: ingredient: {23: "literal"}
275 +parse: product: {1: "number"}
276
277 :(scenario parse_comment_amongst_instruction_3)
278 def main [
279 1:number <- copy 23
280
281 2:number <- copy 23
282 ]
283 +parse: instruction: copy
284 +parse: ingredient: {23: "literal"}
285 +parse: product: {1: "number"}
286 +parse: instruction: copy
287 +parse: ingredient: {23: "literal"}
288 +parse: product: {2: "number"}
289
290 :(scenario parse_comment_after_instruction)
291 def main [
292 1:number <- copy 23
293 ]
294 +parse: instruction: copy
295 +parse: ingredient: {23: "literal"}
296 +parse: product: {1: "number"}
297
298 :(scenario parse_label)
299 def main [
300 +foo
301 ]
302 +parse: label: +foo
303
304 :(scenario parse_dollar_as_recipe_name)
305 def main [
306 $foo
307 ]
308 +parse: instruction: $foo
309
310 :(scenario parse_multiple_properties)
311 def main [
312 1:number <- copy 23/foo:bar:baz
313 ]
314 +parse: instruction: copy
315 +parse: ingredient: {23: "literal", "foo": ("bar" "baz")}
316 +parse: product: {1: "number"}
317
318 :(scenario parse_multiple_products)
319 def main [
320 1:number, 2:number <- copy 23
321 ]
322 +parse: instruction: copy
323 +parse: ingredient: {23: "literal"}
324 +parse: product: {1: "number"}
325 +parse: product: {2: "number"}
326
327 :(scenario parse_multiple_ingredients)
328 def main [
329 1:number, 2:number <- copy 23, 4:number
330 ]
331 +parse: instruction: copy
332 +parse: ingredient: {23: "literal"}
333 +parse: ingredient: {4: "number"}
334 +parse: product: {1: "number"}
335 +parse: product: {2: "number"}
336
337 :(scenario parse_multiple_types)
338 def main [
339 1:number, 2:address:number <- copy 23, 4:number
340 ]
341 +parse: instruction: copy
342 +parse: ingredient: {23: "literal"}
343 +parse: ingredient: {4: "number"}
344 +parse: product: {1: "number"}
345 +parse: product: {2: ("address" "number")}
346
347 :(scenario parse_properties)
348 def main [
349 1:address:number/lookup <- copy 23
350 ]
351 +parse: product: {1: ("address" "number"), "lookup": ()}
352
353
354 :(code)
355 void test_parse_comment_terminated_by_eof() {
356 load("recipe main [\n"
357 " a:number <- copy 34\n"
358 "]\n"
359 "# abc");
360 cerr << ".";
361 }
362
363 :(scenario warn_on_missing_space_before_bracket)
364 % Hide_errors = true;
365 def main[
366 1:number <- copy 23
367 ]
368 +error: insert a space before '[' in 'main['
369
370
371
372
373 :(before "End Globals")
374 bool Disable_redefine_checks = false;
375 :(before "End Setup")
376 Disable_redefine_checks = false;
377 :(code)
378 bool should_check_for_redefine(const string& recipe_name) {
379 if (Disable_redefine_checks) return false;
380 return true;
381 }
382
383 :(scenario forbid_redefining_recipes)
384 % Hide_errors = true;
385 def main [
386 1:number <- copy 23
387 ]
388 def main [
389 1:number <- copy 24
390 ]
391 +error: redefining recipe main
392
393 :(scenario permit_forcibly_redefining_recipes)
394 def main [
395 1:number <- copy 23
396 ]
397 def! main [
398 1:number <- copy 24
399 ]
400 -error: redefining recipe main
401 $error: 0
402
403 :(code)
404
405 void show_rest_of_stream(istream& in) {
406 cerr << '^';
407 char c;
408 while (in >> c)
409 cerr << c;
410 cerr << "$\n";
411 exit(0);
412 }