LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
srvn_reader.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_LANG_LQN_SRVN_READER_H
6#define LINE_LANG_LQN_SRVN_READER_H
7
8/**
9 * @file
10 * @ingroup line_lang
11 * SRVN input format (the LQNS `.in` / `.lqn` text grammar) for layered models,
12 * the C++ twin of python/line_solver/io/srvn_io.py, MATLAB srvn2lqnx.m and the
13 * JAR's jline.io.SRVN2LQNX.
14 *
15 * The reader TRANSLATES an SRVN document into the LQN XML interchange and hands
16 * that to `read_lqnx_model`, so loading `model.in` builds exactly the model that
17 * loading the `.lqnx` written by `lqn2xml -Oxml model.in` builds. The
18 * translation is deterministic and BYTE-IDENTICAL across the four codebases:
19 * test/testsLQNS/srvn/expected in line-test.git is the shared golden.
20 *
21 * Grammar covered (LQNS 6): the `G`, `P`, `U`, `T`, `E`, `A` and `R` sections;
22 * processor, task, group, entry and activity declarations with every option
23 * LQNS accepts; fan-in/fan-out (`I`/`O`); semaphore and rwlock entries; activity
24 * precedence (sequence, AND/OR fork and join, quorum join, loop, reply lists);
25 * `#pragma` lines; and SPEX scalar parameters (`$x = expr`), which are
26 * substituted. A SPEX sweep (`$x = [ ... ]`) used by the model is refused
27 * (`LINE:unsupported:Format`), since one layered model carries one value per
28 * parameter. SPEX observations (`%u $U`), histogram requests (`H`) and the `R`
29 * result list are requests for output, not model content, and are skipped.
30 *
31 * NUMBERS ARE COPIED AS WRITTEN: an attribute carries the source token, leading
32 * '+' stripped, so the document never depends on how a codebase prints a
33 * double. Only a COMPUTED parameter (`$x = 2 * $y`) is printed, by
34 * `format_number`, whose rule the four codebases share.
35 */
36
37#include <cctype>
38#include <cmath>
39#include <cstdio>
40#include <cstdlib>
41#include <fstream>
42#include <map>
43#include <sstream>
44#include <string>
45#include <utility>
46#include <vector>
47
49#include "line/util/error.h"
50
51namespace line {
52namespace lqn {
53
54namespace srvn_detail {
55
56/** Python's `str.isspace` over ASCII, which also counts the four separators 0x1c-0x1f. */
57inline bool is_space(char c) {
58 return c == ' ' || (c >= '\t' && c <= '\r') || (c >= '\x1c' && c <= '\x1f');
59}
60
61inline bool is_digit(char c) { return c >= '0' && c <= '9'; }
62
63inline bool is_alnum(char c) {
64 return is_digit(c) || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
65}
66
67/** `[A-Za-z0-9_.$@]`, the characters of a name. */
68inline bool is_ident(char c) { return is_alnum(c) || c == '_' || c == '.' || c == '$' || c == '@'; }
69
70inline std::string strip(const std::string& s) {
71 std::size_t a = 0, b = s.size();
72 while (a < b && is_space(s[a])) ++a;
73 while (b > a && is_space(s[b - 1])) --b;
74 return s.substr(a, b - a);
75}
76
77/** Python's `repr` of an ASCII string, so messages read as the reference's do. */
78inline std::string py_repr(const std::string& s) {
79 const bool dq = s.find('\'') != std::string::npos && s.find('"') == std::string::npos;
80 const char q = dq ? '"' : '\'';
81 std::string r(1, q);
82 for (unsigned char c : s) {
83 if (c == '\\') r += "\\\\";
84 else if (c == static_cast<unsigned char>(q)) { r += '\\'; r += static_cast<char>(c); }
85 else if (c == '\n') r += "\\n";
86 else if (c == '\r') r += "\\r";
87 else if (c == '\t') r += "\\t";
88 else if (c < 0x20 || c == 0x7f) {
89 char buf[8];
90 std::snprintf(buf, sizeof(buf), "\\x%02x", c);
91 r += buf;
92 } else r += static_cast<char>(c);
93 }
94 return r + q;
95}
96
97inline InputError err(const std::string& msg, int line = -1) {
98 std::string where = line < 0 ? std::string() : " (line " + std::to_string(line) + ")";
99 return InputError(ErrorKind{"Format"}, "SRVN" + where + ": " + msg);
100}
101
102inline std::string esc(const std::string& s) {
103 std::string r;
104 r.reserve(s.size());
105 for (char c : s) {
106 if (c == '&') r += "&amp;";
107 else if (c == '<') r += "&lt;";
108 else if (c == '>') r += "&gt;";
109 else if (c == '"') r += "&quot;";
110 else r += c;
111 }
112 return r;
113}
114
115enum class TK { STR, NUM, ID, VAR, OBS, P };
116
117struct Tok {
119 std::string text;
120 int line;
121};
122
123/** A number as written: its source spelling and its value. `has` false is Python's None. */
124struct Val {
125 bool has = false;
126 std::string text;
127 double value = 0.0;
128 Val() {}
129 Val(std::string t, double v) : has(true), text(std::move(t)), value(v) {}
130};
131
132inline bool nz(const Val& v) { return v.has && v.value != 0; }
133
134inline std::string lstrip_plus(const std::string& s) {
135 std::size_t a = 0;
136 while (a < s.size() && s[a] == '+') ++a;
137 return s.substr(a);
138}
139
140inline double to_double(const std::string& s) { return std::strtod(s.c_str(), NULL); }
141
142/** End of `[-+]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][-+]?[0-9]+)?` matched at `i`, or npos. */
143inline std::size_t match_number(const std::string& src, std::size_t i) {
144 const std::size_t n = src.size();
145 std::size_t k = i;
146 if (k < n && (src[k] == '-' || src[k] == '+')) ++k;
147 if (k < n && is_digit(src[k])) {
148 while (k < n && is_digit(src[k])) ++k;
149 if (k < n && src[k] == '.') ++k;
150 while (k < n && is_digit(src[k])) ++k;
151 } else if (k + 1 < n && src[k] == '.' && is_digit(src[k + 1])) {
152 ++k;
153 while (k < n && is_digit(src[k])) ++k;
154 } else {
155 return std::string::npos;
156 }
157 if (k < n && (src[k] == 'e' || src[k] == 'E')) {
158 std::size_t e = k + 1;
159 if (e < n && (src[e] == '-' || src[e] == '+')) ++e;
160 if (e < n && is_digit(src[e])) {
161 while (e < n && is_digit(src[e])) ++e;
162 k = e;
163 }
164 }
165 return k;
166}
167
168/** `#\s*pragma\s+(.*)$` on a stripped comment line: the body, or false. */
169inline bool pragma_body(const std::string& line, std::string& body) {
170 if (line.empty() || line[0] != '#') return false;
171 std::size_t k = 1;
172 while (k < line.size() && is_space(line[k])) ++k;
173 if (line.compare(k, 6, "pragma") != 0) return false;
174 k += 6;
175 if (k >= line.size() || !is_space(line[k])) return false;
176 while (k < line.size() && is_space(line[k])) ++k;
177 body = strip(line.substr(k));
178 return true;
179}
180
181inline void tokenize(const std::string& src, std::vector<Tok>& toks,
182 std::vector<std::pair<std::string, std::string>>& pragmas) {
183 std::size_t i = 0;
184 const std::size_t n = src.size();
185 int line = 1;
186 while (i < n) {
187 const char c = src[i];
188 if (c == '\n') {
189 ++line;
190 ++i;
191 } else if (is_space(c)) {
192 ++i;
193 } else if (c == '#') {
194 std::size_t j = src.find('\n', i);
195 if (j == std::string::npos) j = n;
196 std::string body;
197 if (pragma_body(strip(src.substr(i, j - i)), body)) {
198 std::size_t k = 0;
199 while (k < body.size()) {
200 while (k < body.size() && is_space(body[k])) ++k;
201 if (k >= body.size()) break;
202 std::size_t e = k;
203 while (e < body.size() && !is_space(body[e])) ++e;
204 const std::string item = body.substr(k, e - k);
205 const std::size_t eq = item.find('=');
206 if (eq == std::string::npos) pragmas.emplace_back(item, std::string());
207 else pragmas.emplace_back(item.substr(0, eq), item.substr(eq + 1));
208 k = e;
209 }
210 }
211 i = j;
212 } else if (c == '"') {
213 const std::size_t j = src.find('"', i + 1);
214 if (j == std::string::npos) throw err("unterminated string", line);
215 toks.push_back(Tok{TK::STR, src.substr(i + 1, j - i - 1), line});
216 for (std::size_t k = i; k < j; ++k)
217 if (src[k] == '\n') ++line;
218 i = j + 1;
219 } else if (src.compare(i, 2, "->") == 0) {
220 toks.push_back(Tok{TK::P, "->", line});
221 i += 2;
222 } else if (c == '%') {
223 std::size_t j = i + 1;
224 while (j < n && (is_alnum(src[j]) || src[j] == '_')) ++j;
225 toks.push_back(Tok{TK::OBS, src.substr(i, j - i), line});
226 i = j;
227 } else if (is_digit(c) || (c == '.' && i + 1 < n && is_digit(src[i + 1])) ||
228 ((c == '-' || c == '+') && i + 1 < n &&
229 (is_digit(src[i + 1]) || (src[i + 1] == '.' && i + 2 < n && is_digit(src[i + 2]))))) {
230 std::size_t j = match_number(src, i);
231 if (j < n && is_ident(src[j]) && c != '-' && c != '+') {
232 while (j < n && is_ident(src[j])) ++j;
233 toks.push_back(Tok{TK::ID, src.substr(i, j - i), line});
234 } else {
235 toks.push_back(Tok{TK::NUM, src.substr(i, j - i), line});
236 }
237 i = j;
238 } else if (std::string("&;:()[],*=/^{}+-").find(c) != std::string::npos) {
239 toks.push_back(Tok{TK::P, std::string(1, c), line});
240 ++i;
241 } else if (is_ident(c)) {
242 std::size_t j = i;
243 while (j < n && is_ident(src[j])) ++j;
244 std::string text = src.substr(i, j - i);
245 const TK k = text[0] == '$' ? TK::VAR : TK::ID;
246 toks.push_back(Tok{k, std::move(text), line});
247 i = j;
248 } else {
249 throw err("unexpected character " + py_repr(std::string(1, c)), line);
250 }
251 }
252}
253
254struct Call {
255 std::string kind; ///< synch-call | asynch-call
256 std::string dst;
258};
259
260/** An entry phase or an activity: the s, c, f, Z, M values and the calls. */
261struct Work {
262 std::string name;
263 Val s, c, f, Z, M;
264 std::vector<Call> calls;
265 Val& field(char k) {
266 switch (k) {
267 case 's': return s;
268 case 'c': return c;
269 case 'f': return f;
270 case 'Z': return Z;
271 default: return M;
272 }
273 }
274};
275
276struct Proc {
277 std::string name, sched;
279 std::vector<std::size_t> groups; ///< into Model::groups
280 std::vector<std::size_t> tasks; ///< into Model::tasks
281};
282
283struct Group {
284 std::string name;
286 bool cap = false;
287 std::vector<std::size_t> tasks;
288};
289
290struct Task {
291 std::string name, sched;
292 std::vector<std::string> entries;
293 std::string proc;
295 bool has_group = false;
296 std::string group;
297 std::vector<std::pair<std::string, Val>> fanin, fanout;
298};
299
300struct Entry {
301 std::string name, task;
302 std::map<int, Work> phases;
303 bool has_start = false;
304 std::string start;
306 std::string sem, rw; ///< empty when not declared
307 std::vector<std::pair<std::string, Val>> fwd;
308};
309
310struct Conn {
311 std::string pre_kind;
312 std::vector<std::string> pre;
314 bool has_post = false;
315 std::string post_kind;
316 std::vector<std::pair<std::string, Val>> items;
317 bool has_end = false;
318 std::string end;
319};
320
321struct Acts {
322 std::vector<Work> defs;
323 std::map<std::string, std::size_t> by;
324 std::vector<std::vector<std::string>> prec;
325 std::vector<std::pair<std::string, std::string>> replies; ///< (entry, activity)
326 std::vector<Conn> conns;
327};
328
329struct Model {
330 std::string comment;
331 std::vector<std::pair<std::string, Val>> solver;
332 std::vector<Proc> procs;
333 std::map<std::string, std::size_t> proc_by;
334 std::vector<Group> groups;
335 std::vector<Task> tasks;
336 std::map<std::string, std::size_t> task_by;
337 std::map<std::string, Entry> entries;
338 std::vector<std::string> entry_order;
339 std::map<std::string, Acts> acts;
340};
341
342inline const char* proc_sched(const std::string& k) {
343 static const std::map<std::string, const char*> t = {
344 {"f", "fcfs"}, {"h", "hol"}, {"p", "pri"}, {"r", "rand"}, {"i", "inf"}, {"c", "cfs"}, {"s", "ps"}};
345 std::map<std::string, const char*>::const_iterator it = t.find(k);
346 return it == t.end() ? NULL : it->second;
347}
348
349inline const char* task_sched(const std::string& k) {
350 static const std::map<std::string, const char*> t = {
351 {"r", "ref"}, {"n", "fcfs"}, {"f", "fcfs"}, {"h", "hol"}, {"p", "pri"}, {"i", "inf"},
352 {"P", "poll"}, {"b", "burst"}, {"u", "uniform"}, {"S", "semaphore"}, {"W", "rwlock"}};
353 std::map<std::string, const char*>::const_iterator it = t.find(k);
354 return it == t.end() ? NULL : it->second;
355}
356
357inline const char* rwlock_kind(const std::string& k) {
358 if (k == "R") return "r-lock";
359 if (k == "U") return "r-unlock";
360 if (k == "W") return "w-lock";
361 if (k == "X") return "w-unlock";
362 return NULL;
363}
364
365} // namespace srvn_detail
366
367/**
368 * Shortest C `%g` spelling that round-trips `x`; the four codebases share this rule.
369 *
370 * An integer below 1e15 in magnitude prints as `%d`; anything else as the
371 * shortest `%.{p}g`, p = 1..17, that `strtod` reads back as `x`.
372 *
373 * @throws InputError (`LINE:input:Format`) when `x` is not finite (every caller checks first)
374 */
375inline std::string format_number(double x) {
376 if (!std::isfinite(x)) throw InputError(ErrorKind{"Format"}, "format_number: the value is not finite");
377 char buf[40];
378 if (x == std::trunc(x) && std::fabs(x) < 1e15) {
379 std::snprintf(buf, sizeof(buf), "%lld", static_cast<long long>(x));
380 return buf;
381 }
382 for (int p = 1; p < 18; ++p) {
383 std::snprintf(buf, sizeof(buf), "%.*g", p, x);
384 if (std::strtod(buf, NULL) == x) return buf;
385 }
386 std::snprintf(buf, sizeof(buf), "%.17g", x);
387 return buf;
388}
389
390namespace srvn_detail {
391
392class Parser {
393public:
394 explicit Parser(const std::string& src) { tokenize(src, toks, pragmas); }
395
396 std::vector<Tok> toks;
397 std::vector<std::pair<std::string, std::string>> pragmas;
398
400 Model m;
401 parameters();
402 expect("G");
403 general(m);
404 if (!at("P")) throw err("expected the processor section \"P\"", line());
405 next();
406 next(); // declared count, informational
407 processors(m);
408 if (at("U")) {
409 next();
410 next();
411 groups(m);
412 }
413 expect("T");
414 next();
415 tasks(m);
416 expect("E");
417 next();
418 entries(m);
419 while (at("A")) {
420 next();
421 activities(m);
422 }
423 if (at("R")) {
424 next();
425 int depth = 0;
426 while (true) {
427 const Tok& t = next();
428 if (t.text == "[" || t.text == "(") ++depth;
429 else if (t.text == "]" || t.text == ")") --depth;
430 else if (depth == 0 && t.kind == TK::NUM && t.text == "-1") break;
431 }
432 }
433 // observation pairs may trail the document
434 peek();
435 if (pos < toks.size()) {
436 const Tok& t = toks[pos];
437 throw err("unexpected " + py_repr(t.text) + " after the last section", t.line);
438 }
439 if (!used_sweeps.empty()) {
440 std::string names;
441 for (std::size_t i = 0; i < used_sweeps.size(); ++i) names += (i ? ", " : "") + used_sweeps[i];
442 throw UnsupportedError(ErrorKind{"Format"},
443 "SRVN: the model uses the SPEX sweep parameter(s) " + names +
444 "; LINE reads one value per parameter, so give each a scalar value (e.g. \"" +
445 used_sweeps[0] + " = 1\")");
446 }
447 return m;
448 }
449
450private:
451 std::size_t pos = 0;
452 std::map<std::string, Val> params;
453 std::map<std::string, int> sweeps;
454 std::vector<std::string> used_sweeps;
455
456 // -- token stream -----------------------------------------------------
457 void skip_obs() {
458 while (pos < toks.size() && toks[pos].kind == TK::OBS) {
459 ++pos;
460 if (pos < toks.size() && toks[pos].kind == TK::VAR) ++pos;
461 }
462 }
463
464 const Tok* peek(int k = 0) {
465 skip_obs();
466 std::size_t p = pos;
467 while (k > 0) {
468 ++p;
469 while (p < toks.size() && toks[p].kind == TK::OBS) {
470 ++p;
471 if (p < toks.size() && toks[p].kind == TK::VAR) ++p;
472 }
473 --k;
474 }
475 return p < toks.size() ? &toks[p] : NULL;
476 }
477
478 const Tok& next() {
479 const Tok* t = peek();
480 if (!t) throw err("unexpected end of input");
481 ++pos;
482 return *t;
483 }
484
485 int line() {
486 const Tok* t = peek();
487 return t ? t->line : (toks.empty() ? 1 : toks.back().line);
488 }
489
490 bool at(const char* text) {
491 const Tok* t = peek();
492 return t && t->text == text;
493 }
494
495 bool at_end_list() {
496 const Tok* t = peek();
497 return t && t->kind == TK::NUM && t->text == "-1";
498 }
499
500 void expect(const char* text) {
501 const Tok& t = next();
502 if (t.text != text) throw err("expected " + py_repr(text) + ", found " + py_repr(t.text), t.line);
503 }
504
505 void expect_end_list() {
506 const Tok& t = next();
507 if (!(t.kind == TK::NUM && t.text == "-1"))
508 throw err("expected end of list \"-1\", found " + py_repr(t.text), t.line);
509 }
510
511 std::string ident(const std::string& what) {
512 const Tok& t = next();
513 if (t.kind != TK::ID && t.kind != TK::NUM)
514 throw err("expected " + what + " name, found " + py_repr(t.text), t.line);
515 return t.text;
516 }
517
518 // -- values -----------------------------------------------------------
519 Val value(const std::string& what = "value") {
520 const Tok& t = next();
521 if (t.kind == TK::NUM) return Val(lstrip_plus(t.text), to_double(t.text));
522 if (t.kind == TK::VAR) return lookup(t);
523 throw err("expected a number for " + what + ", found " + py_repr(t.text), t.line);
524 }
525
526 bool is_value(int k = 0) {
527 const Tok* t = peek(k);
528 return t && (t->kind == TK::NUM || t->kind == TK::VAR) && !(t->kind == TK::NUM && t->text == "-1");
529 }
530
531 Val lookup(const Tok& t) {
532 const std::string& name = t.text;
533 if (sweeps.count(name)) {
534 bool seen = false;
535 for (const std::string& u : used_sweeps) seen = seen || u == name;
536 if (!seen) used_sweeps.push_back(name);
537 return Val("0", 0.0);
538 }
539 std::map<std::string, Val>::const_iterator it = params.find(name);
540 if (it == params.end()) throw err("parameter " + name + " is used but not defined", t.line);
541 return it->second;
542 }
543
544 std::vector<Val> value_list(const std::string& what) {
545 std::vector<Val> vals;
546 while (!at_end_list()) vals.push_back(value(what));
547 expect_end_list();
548 return vals;
549 }
550
551 // -- SPEX parameters ---------------------------------------------------
552 void parameters() {
553 while (true) {
554 const Tok* a = peek();
555 if (!a || a->kind != TK::VAR) break;
556 const Tok* b = peek(1);
557 if (!b || b->text != "=") break;
558 const Tok vt = next();
559 next();
560 if (at("[")) {
561 sweeps[vt.text] = vt.line;
562 int depth = 0;
563 while (true) {
564 const Tok& t = next();
565 if (t.text == "[") ++depth;
566 else if (t.text == "]") {
567 --depth;
568 if (depth == 0) break;
569 }
570 }
571 continue;
572 }
573 const std::size_t start = pos;
574 const double v = expr();
575 // pow overflow and domain errors, and overflowing products, all land here
576 if (!std::isfinite(v)) throw err("parameter " + vt.text + " evaluates to a non-finite value", vt.line);
577 const bool single = pos - start == 1 && toks[start].kind == TK::NUM;
578 params[vt.text] = Val(single ? lstrip_plus(toks[start].text) : format_number(v), v);
579 }
580 }
581
582 double expr() {
583 double v = term();
584 while (true) {
585 const Tok* t = peek();
586 if (t && t->kind == TK::P && (t->text == "+" || t->text == "-")) {
587 const bool plus = next().text == "+";
588 const double r = term();
589 v = plus ? v + r : v - r;
590 } else if (t && t->kind == TK::NUM && (t->text[0] == '+' || t->text[0] == '-')) {
591 // "$y -1" tokenizes the operator into the number; parameter definitions are followed by
592 // another "$x =" or by "G", never by a number, so a signed number here is a subtraction
593 const double r = to_double(t->text);
594 next();
595 v = v + r;
596 } else {
597 return v;
598 }
599 }
600 }
601
602 double term() {
603 double v = factor();
604 while (at("*") || at("/")) {
605 const Tok& ot = next();
606 const bool mul = ot.text == "*";
607 const double r = factor();
608 if (!mul && r == 0) throw err("division by zero in a parameter expression", ot.line);
609 v = mul ? v * r : v / r;
610 }
611 return v;
612 }
613
614 double factor() {
615 const double b = unary();
616 if (at("^")) {
617 next();
618 return std::pow(b, factor());
619 }
620 return b;
621 }
622
623 double unary() {
624 if (at("-")) {
625 next();
626 return -unary();
627 }
628 if (at("+")) {
629 next();
630 return unary();
631 }
632 if (at("(")) {
633 next();
634 const double v = expr();
635 expect(")");
636 return v;
637 }
638 const Tok& t = next();
639 if (t.kind == TK::NUM) return to_double(t.text);
640 if (t.kind == TK::VAR) {
641 if (sweeps.count(t.text))
642 throw UnsupportedError(ErrorKind{"Format"},
643 "SRVN (line " + std::to_string(t.line) + "): a parameter expression uses the SPEX sweep " +
644 t.text + "; LINE reads one value per parameter, so give it a scalar value");
645 return lookup(t).value;
646 }
647 throw err("malformed parameter expression at " + py_repr(t.text), t.line);
648 }
649
650 // -- document ---------------------------------------------------------
651 void general(Model& m) {
652 const Tok* t = peek();
653 if (t && t->kind == TK::STR) m.comment = next().text;
654 static const char* names[] = {"conv_val", "it_limit", "print_int", "underrelax_coeff"};
655 std::size_t k = 0;
656 while (!at_end_list()) {
657 if (k >= 4) throw err("too many values in the general section", line());
658 m.solver.emplace_back(names[k], value(names[k]));
659 ++k;
660 }
661 expect_end_list();
662 }
663
664 void processors(Model& m) {
665 while (!at_end_list()) {
666 const int ln = line();
667 expect("p");
668 const std::string name = ident("processor");
669 if (m.proc_by.count(name)) throw err("processor \"" + name + "\" is declared twice", ln);
670 const Tok& t = next();
671 const char* sched = proc_sched(t.text);
672 if (!sched)
673 throw err("unknown processor scheduling flag " + py_repr(t.text) + " for \"" + name + "\"", t.line);
674 Proc p;
675 p.name = name;
676 p.sched = sched;
677 if (t.text == "c" || t.text == "s") p.quantum = value("quantum");
678 while (true) {
679 if (at("m")) {
680 next();
681 p.mult = value("multiplicity");
682 } else if (at("i")) {
683 next();
684 p.sched = "inf";
685 } else if (at("r")) {
686 next();
687 p.repl = value("replication");
688 } else if (at("R")) {
689 next();
690 p.speed = value("speed factor");
691 } else {
692 break;
693 }
694 }
695 m.proc_by[name] = m.procs.size();
696 m.procs.push_back(p);
697 }
698 expect_end_list();
699 }
700
701 void groups(Model& m) {
702 while (!at_end_list()) {
703 const int ln = line();
704 expect("g");
705 const std::string name = ident("group");
706 const Val share = value("share");
707 bool cap = false;
708 if (at("c")) {
709 next();
710 cap = true;
711 }
712 const std::string proc = ident("processor");
713 if (!m.proc_by.count(proc))
714 throw err("group \"" + name + "\" names the undeclared processor \"" + proc + "\"", ln);
715 Group g;
716 g.name = name;
717 g.share = share;
718 g.cap = cap;
719 m.procs[m.proc_by[proc]].groups.push_back(m.groups.size());
720 m.groups.push_back(g);
721 }
722 expect_end_list();
723 }
724
725 struct Fan {
726 std::string kind, src, dst;
727 Val v;
728 int line;
729 };
730
731 void tasks(Model& m) {
732 std::vector<Fan> fans;
733 while (!at_end_list()) {
734 const int ln = line();
735 const Tok& t = next();
736 if (t.text == "I" || t.text == "O") {
737 Fan f;
738 f.kind = t.text;
739 f.line = t.line;
740 f.src = ident("task");
741 f.dst = ident("task");
742 f.v = value("fan value");
743 fans.push_back(f);
744 continue;
745 }
746 if (t.text != "t") throw err("expected a task declaration \"t\", found " + py_repr(t.text), t.line);
747 const std::string name = ident("task");
748 if (m.task_by.count(name)) throw err("task \"" + name + "\" is declared twice", ln);
749 const Tok& st = next();
750 const char* sched = task_sched(st.text);
751 if (!sched)
752 throw err("unknown task scheduling flag " + py_repr(st.text) + " for \"" + name + "\"", st.line);
753 Task task;
754 task.name = name;
755 task.sched = sched;
756 while (!at_end_list()) task.entries.push_back(ident("entry"));
757 expect_end_list();
758 task.proc = ident("processor");
759 if (!m.proc_by.count(task.proc))
760 throw err("task \"" + name + "\" runs on the undeclared processor \"" + task.proc + "\"", ln);
761 while (true) {
762 if (is_value()) {
763 task.prio = value("priority");
764 } else if (at("z")) {
765 next();
766 task.think = value("think time");
767 } else if (at("m")) {
768 next();
769 task.mult = value("multiplicity");
770 } else if (at("i")) {
771 next();
772 task.sched = "inf";
773 } else if (at("r")) {
774 next();
775 task.repl = value("replication");
776 } else if (at("g")) {
777 next();
778 task.has_group = true;
779 task.group = ident("group");
780 } else if (at("q")) {
781 next();
782 task.qlen = value("queue length");
783 } else {
784 break;
785 }
786 }
787 for (const std::string& e : task.entries) {
788 if (m.entries.count(e)) throw err("entry \"" + e + "\" is declared twice", ln);
789 Entry en;
790 en.name = e;
791 en.task = name;
792 m.entries[e] = en;
793 m.entry_order.push_back(e);
794 }
795 const std::size_t ti = m.tasks.size();
796 m.tasks.push_back(task);
797 m.task_by[name] = ti;
798 Proc& proc = m.procs[m.proc_by[task.proc]];
799 if (task.has_group) {
800 bool found = false;
801 for (std::size_t gi : proc.groups) {
802 if (m.groups[gi].name == task.group) {
803 m.groups[gi].tasks.push_back(ti);
804 found = true;
805 break;
806 }
807 }
808 if (!found)
809 throw err("task \"" + name + "\" names group \"" + task.group + "\", which processor \"" +
810 proc.name + "\" does not declare", ln);
811 } else {
812 proc.tasks.push_back(ti);
813 }
814 }
815 expect_end_list();
816 for (const Fan& f : fans) {
817 for (const std::string* nm : {&f.src, &f.dst})
818 if (!m.task_by.count(*nm))
819 throw err(std::string("fan-") + (f.kind == "I" ? "in" : "out") + " names the undeclared task \"" +
820 *nm + "\"", f.line);
821 if (f.kind == "O") m.tasks[m.task_by[f.src]].fanout.emplace_back(f.dst, f.v);
822 else m.tasks[m.task_by[f.dst]].fanin.emplace_back(f.src, f.v);
823 }
824 }
825
826 Entry& entry(Model& m, const std::string& name, int ln) {
827 std::map<std::string, Entry>::iterator it = m.entries.find(name);
828 if (it == m.entries.end()) throw err("entry \"" + name + "\" is not declared by any task", ln);
829 return it->second;
830 }
831
832 static Work& phase(Entry& e, int k) { return e.phases[k]; }
833
834 void entries(Model& m) {
835 while (!at_end_list()) {
836 const Tok t = next();
837 const int ln = t.line;
838 const std::string& k = t.text;
839 if (k == "s" || k == "c" || k == "f" || k == "Z" || k == "M") {
840 Entry& e = entry(m, ident("entry"), ln);
841 const std::vector<Val> vals = value_list("phase value");
842 for (std::size_t ph = 0; ph < vals.size(); ++ph) phase(e, static_cast<int>(ph + 1)).field(k[0]) = vals[ph];
843 } else if (k == "y" || k == "z") {
844 Entry& e = entry(m, ident("entry"), ln);
845 const std::string dst = ident("entry");
846 entry(m, dst, ln);
847 const std::vector<Val> vals = value_list("call count");
848 for (std::size_t ph = 0; ph < vals.size(); ++ph)
849 phase(e, static_cast<int>(ph + 1))
850 .calls.push_back(Call{k == "y" ? "synch-call" : "asynch-call", dst, vals[ph]});
851 } else if (k == "F") {
852 Entry& e = entry(m, ident("entry"), ln);
853 const std::string dst = ident("entry");
854 entry(m, dst, ln);
855 e.fwd.emplace_back(dst, value("forwarding probability"));
856 if (at_end_list()) next();
857 } else if (k == "a") {
858 Entry& e = entry(m, ident("entry"), ln);
859 e.arrival = value("arrival rate");
860 } else if (k == "A") {
861 Entry& e = entry(m, ident("entry"), ln);
862 e.has_start = true;
863 e.start = ident("activity");
864 } else if (k == "p") {
865 Entry& e = entry(m, ident("entry"), ln);
866 e.prio = value("priority");
867 } else if (k == "P" || k == "V") {
868 Entry& e = entry(m, ident("entry"), ln);
869 e.sem = k == "P" ? "signal" : "wait";
870 } else if (rwlock_kind(k)) {
871 Entry& e = entry(m, ident("entry"), ln);
872 e.rw = rwlock_kind(k);
873 } else if (k == "H") {
874 entry(m, ident("entry"), ln);
875 value("phase");
876 histogram();
877 } else {
878 throw err("unknown entry declaration " + py_repr(k), ln);
879 }
880 }
881 expect_end_list();
882 }
883
884 /** `min : max nbins [type]`; a request for output, so it is read and dropped. */
885 void histogram() {
886 value("histogram minimum");
887 expect(":");
888 value("histogram maximum");
889 value("histogram bins");
890 if (at("log") || at("sqrt") || at("linear")) next();
891 }
892
893 void activities(Model& m) {
894 const int ln = line();
895 const std::string tname = ident("task");
896 if (!m.task_by.count(tname)) throw err("activity section for the undeclared task \"" + tname + "\"", ln);
897 if (m.acts.count(tname)) throw err("task \"" + tname + "\" has two activity sections", ln);
898 Acts& A = m.acts[tname];
899 while (!(at_end_list() || at(":"))) {
900 const Tok t = next();
901 const std::string& k = t.text;
902 if (!(k == "s" || k == "c" || k == "f" || k == "Z" || k == "M" || k == "y" || k == "z" || k == "H"))
903 throw err("unknown activity declaration " + py_repr(k) + " in task \"" + tname + "\"", t.line);
904 const std::size_t ai = act(A, ident("activity"));
905 if (k == "y" || k == "z") {
906 const std::string dst = ident("entry");
907 entry(m, dst, t.line);
908 const Val v = value("call count");
909 A.defs[ai].calls.push_back(Call{k == "y" ? "synch-call" : "asynch-call", dst, v});
910 } else if (k == "H") {
911 histogram();
912 } else {
913 const Val v = value("activity value");
914 A.defs[ai].field(k[0]) = v;
915 }
916 }
917 if (at(":")) {
918 next();
919 while (true) {
920 connection(A, m, tname);
921 if (at(";")) {
922 next();
923 continue;
924 }
925 break;
926 }
927 }
928 expect_end_list();
929 for (const std::vector<std::string>& names : A.prec)
930 for (const std::string& nm : names)
931 if (!A.by.count(nm))
932 throw err("activity \"" + nm + "\" of task \"" + tname +
933 "\" is used in the precedence graph but not defined", ln);
934 }
935
936 static std::size_t act(Acts& A, const std::string& name) {
937 std::map<std::string, std::size_t>::const_iterator it = A.by.find(name);
938 if (it != A.by.end()) return it->second;
939 Work a;
940 a.name = name;
941 A.by[name] = A.defs.size();
942 A.defs.push_back(a);
943 return A.defs.size() - 1;
944 }
945
946 std::string actref(Acts& A, Model& m) {
947 const std::string name = ident("activity");
948 if (at("[")) {
949 next();
950 while (true) {
951 const std::string e = ident("entry");
952 entry(m, e, line());
953 A.replies.emplace_back(e, name);
954 if (at(",")) {
955 next();
956 continue;
957 }
958 break;
959 }
960 expect("]");
961 }
962 return name;
963 }
964
965 void connection(Acts& A, Model& m, const std::string& tname) {
966 Conn rec;
967 rec.pre.push_back(actref(A, m));
968 rec.pre_kind = "pre";
969 if (at("&")) {
970 rec.pre_kind = "pre-AND";
971 while (at("&")) {
972 next();
973 rec.pre.push_back(actref(A, m));
974 }
975 if (at("(")) {
976 next();
977 rec.quorum = value("quorum");
978 expect(")");
979 }
980 } else if (at("+")) {
981 rec.pre_kind = "pre-OR";
982 while (at("+")) {
983 next();
984 rec.pre.push_back(actref(A, m));
985 }
986 }
987 A.prec.push_back(rec.pre);
988 if (at("->")) {
989 next();
990 rec.has_post = true;
991 if (at("(")) {
992 while (true) {
993 expect("(");
994 const Val p = value("branch probability");
995 expect(")");
996 const std::string a = actref(A, m);
997 rec.items.emplace_back(a, p);
998 if (at("+")) {
999 next();
1000 continue;
1001 }
1002 break;
1003 }
1004 rec.post_kind = "post-OR";
1005 } else if (is_value() && peek(1) && peek(1)->text == "*") {
1006 const Val count = value("loop count");
1007 expect("*");
1008 const std::string body = actref(A, m);
1009 if (at(",")) {
1010 next();
1011 rec.has_end = true;
1012 rec.end = actref(A, m);
1013 }
1014 rec.post_kind = "post-LOOP";
1015 rec.items.emplace_back(body, count);
1016 } else {
1017 std::vector<std::string> post;
1018 post.push_back(actref(A, m));
1019 std::string kind = "post";
1020 if (at("&")) {
1021 kind = "post-AND";
1022 while (at("&")) {
1023 next();
1024 post.push_back(actref(A, m));
1025 }
1026 } else if (at("+")) {
1027 throw err("an OR fork in task \"" + tname + "\" needs a probability on each branch, \"(p) act\"",
1028 line());
1029 }
1030 rec.post_kind = kind;
1031 for (const std::string& x : post) rec.items.emplace_back(x, Val());
1032 }
1033 std::vector<std::string> names;
1034 for (const std::pair<std::string, Val>& it : rec.items) names.push_back(it.first);
1035 if (rec.has_end && !rec.end.empty()) names.push_back(rec.end);
1036 A.prec.push_back(names);
1037 }
1038 A.conns.push_back(rec);
1039 }
1040};
1041
1042inline std::string attr(const char* name, const Val& v) {
1043 return std::string(" ") + name + "=\"" + esc(v.text) + "\"";
1044}
1045
1046inline std::string activity_attrs(const Work& a, std::vector<const Call*>& calls) {
1047 // lqn-core.xsd requires host-demand-mean, and parseXML reads an absent one as NaN, not 0, so it is always written
1048 std::string s = a.s.has ? attr("host-demand-mean", a.s) : std::string(" host-demand-mean=\"0\"");
1049 if (a.c.has && nz(a.s)) s += attr("host-demand-cvsq", a.c);
1050 if (nz(a.Z)) s += attr("think-time", a.Z);
1051 if (nz(a.M)) s += attr("max-service-time", a.M);
1052 for (const Call& c : a.calls)
1053 if (c.v.value != 0) calls.push_back(&c);
1054 if (nz(a.f) && !calls.empty()) s += " call-order=\"DETERMINISTIC\"";
1055 return s;
1056}
1057
1058inline void emit_activity(std::vector<std::string>& out, const std::string& ind, const std::string& head,
1059 const Work& a) {
1060 std::vector<const Call*> calls;
1061 const std::string attrs = activity_attrs(a, calls);
1062 if (calls.empty()) {
1063 out.push_back(ind + "<activity" + head + attrs + "/>");
1064 return;
1065 }
1066 out.push_back(ind + "<activity" + head + attrs + ">");
1067 for (const Call* c : calls)
1068 out.push_back(ind + " <" + c->kind + " dest=\"" + esc(c->dst) + "\" calls-mean=\"" + esc(c->v.text) + "\"/>");
1069 out.push_back(ind + "</activity>");
1070}
1071
1072inline void emit_task(std::vector<std::string>& out, const std::string& ind, const Task& t, const Model& m) {
1073 std::string s = ind + "<task name=\"" + esc(t.name) + "\" scheduling=\"" + t.sched + "\"";
1074 if (t.mult.has && t.sched != "inf" && t.mult.value != 1) s += attr("multiplicity", t.mult);
1075 if (t.repl.has && t.repl.value != 1) s += attr("replication", t.repl);
1076 if (nz(t.think)) s += attr("think-time", t.think);
1077 if (t.prio.has) s += attr("priority", t.prio);
1078 if (t.qlen.has) s += attr("queue-length", t.qlen);
1079 out.push_back(s + ">");
1080 const std::string i2 = ind + " ";
1081 for (const std::pair<std::string, Val>& f : t.fanin)
1082 out.push_back(i2 + "<fan-in source=\"" + esc(f.first) + "\" value=\"" + esc(f.second.text) + "\"/>");
1083 for (const std::pair<std::string, Val>& f : t.fanout)
1084 out.push_back(i2 + "<fan-out dest=\"" + esc(f.first) + "\" value=\"" + esc(f.second.text) + "\"/>");
1085 for (const std::string& en : t.entries) {
1086 const Entry& e = m.entries.at(en);
1087 if (e.has_start && !e.phases.empty())
1088 throw err("entry \"" + en + "\" declares both a start activity and phase parameters");
1089 s = i2 + "<entry name=\"" + esc(en) + "\" type=\"" + (e.has_start ? "NONE" : "PH1PH2") + "\"";
1090 if (nz(e.arrival)) s += attr("open-arrival-rate", e.arrival);
1091 if (e.prio.has) s += attr("priority", e.prio);
1092 if (!e.sem.empty()) s += " semaphore=\"" + e.sem + "\"";
1093 if (!e.rw.empty()) s += " rwlock=\"" + e.rw + "\"";
1094 std::vector<std::string> body;
1095 const std::string i3 = i2 + " ";
1096 for (const std::pair<std::string, Val>& f : e.fwd)
1097 body.push_back(i3 + "<forwarding dest=\"" + esc(f.first) + "\" prob=\"" + esc(f.second.text) + "\"/>");
1098 if (!e.has_start) {
1099 std::vector<std::pair<int, const Work*>> phases;
1100 for (const std::pair<const int, Work>& kv : e.phases) {
1101 const Work& ph = kv.second;
1102 bool call = false;
1103 for (const Call& c : ph.calls) call = call || c.v.value != 0;
1104 if (nz(ph.s) || nz(ph.Z) || call) phases.emplace_back(kv.first, &ph);
1105 }
1106 if (!phases.empty()) {
1107 body.push_back(i3 + "<entry-phase-activities>");
1108 for (const std::pair<int, const Work*>& ph : phases) {
1109 const std::string k = std::to_string(ph.first);
1110 const std::string head = " name=\"" + esc(en + "_" + k) + "\" phase=\"" + k + "\"";
1111 emit_activity(body, i3 + " ", head, *ph.second);
1112 }
1113 body.push_back(i3 + "</entry-phase-activities>");
1114 }
1115 }
1116 if (!body.empty()) {
1117 out.push_back(s + ">");
1118 out.insert(out.end(), body.begin(), body.end());
1119 out.push_back(i2 + "</entry>");
1120 } else {
1121 out.push_back(s + "/>");
1122 }
1123 }
1124 std::map<std::string, Acts>::const_iterator ait = m.acts.find(t.name);
1125 if (ait != m.acts.end()) {
1126 const Acts& A = ait->second;
1127 std::map<std::string, std::string> bound;
1128 for (const std::string& en : t.entries) {
1129 const Entry& e = m.entries.at(en);
1130 if (e.has_start && !bound.count(e.start)) bound[e.start] = en;
1131 }
1132 out.push_back(i2 + "<task-activities>");
1133 const std::string i3 = i2 + " ";
1134 for (const Work& a : A.defs) {
1135 std::string head = " name=\"" + esc(a.name) + "\"";
1136 std::map<std::string, std::string>::const_iterator b = bound.find(a.name);
1137 if (b != bound.end()) head += " bound-to-entry=\"" + esc(b->second) + "\"";
1138 emit_activity(out, i3, head, a);
1139 }
1140 const std::string i4 = i3 + " ", i5 = i3 + " ";
1141 for (const Conn& rec : A.conns) {
1142 out.push_back(i3 + "<precedence>");
1143 std::string q;
1144 if (rec.quorum.has && rec.quorum.value != static_cast<double>(rec.pre.size())) q = attr("quorum", rec.quorum);
1145 out.push_back(i4 + "<" + rec.pre_kind + q + ">");
1146 for (const std::string& nm : rec.pre) out.push_back(i5 + "<activity name=\"" + esc(nm) + "\"/>");
1147 out.push_back(i4 + "</" + rec.pre_kind + ">");
1148 if (rec.has_post) {
1149 const std::string e = rec.has_end ? " end=\"" + esc(rec.end) + "\"" : std::string();
1150 out.push_back(i4 + "<" + rec.post_kind + e + ">");
1151 for (const std::pair<std::string, Val>& it : rec.items) {
1152 if (rec.post_kind == "post-OR")
1153 out.push_back(i5 + "<activity name=\"" + esc(it.first) + "\"" + attr("prob", it.second) + "/>");
1154 else if (rec.post_kind == "post-LOOP")
1155 out.push_back(i5 + "<activity name=\"" + esc(it.first) + "\"" + attr("count", it.second) + "/>");
1156 else
1157 out.push_back(i5 + "<activity name=\"" + esc(it.first) + "\"/>");
1158 }
1159 out.push_back(i4 + "</" + rec.post_kind + ">");
1160 }
1161 out.push_back(i3 + "</precedence>");
1162 }
1163 std::vector<std::string> order;
1164 for (const std::pair<std::string, std::string>& r : A.replies) {
1165 bool seen = false;
1166 for (const std::string& o : order) seen = seen || o == r.first;
1167 if (!seen) order.push_back(r.first);
1168 }
1169 for (const std::string& en : order) {
1170 const Entry& e = m.entries.at(en);
1171 if (e.task != t.name)
1172 throw err("activity of task \"" + t.name + "\" replies to entry \"" + en + "\" of task \"" + e.task +
1173 "\"");
1174 out.push_back(i3 + "<reply-entry name=\"" + esc(en) + "\">");
1175 std::vector<std::string> seen;
1176 for (const std::pair<std::string, std::string>& r : A.replies) {
1177 if (r.first != en) continue;
1178 bool dup = false;
1179 for (const std::string& x : seen) dup = dup || x == r.second;
1180 if (dup) continue;
1181 seen.push_back(r.second);
1182 out.push_back(i4 + "<reply-activity name=\"" + esc(r.second) + "\"/>");
1183 }
1184 out.push_back(i3 + "</reply-entry>");
1185 }
1186 out.push_back(i2 + "</task-activities>");
1187 } else {
1188 for (const std::string& en : t.entries) {
1189 const Entry& e = m.entries.at(en);
1190 if (e.has_start)
1191 throw err("entry \"" + en + "\" starts at activity \"" + e.start + "\", but task \"" + t.name +
1192 "\" has no activity section");
1193 }
1194 }
1195 out.push_back(ind + "</task>");
1196}
1197
1198/** Python's `os.path.splitext(os.path.basename(path))[0]`. */
1199inline std::string stem(const std::string& path) {
1200 const std::size_t slash = path.find_last_of('/');
1201 const std::string base = slash == std::string::npos ? path : path.substr(slash + 1);
1202 const std::size_t dot = base.rfind('.');
1203 const std::size_t first = base.find_first_not_of('.');
1204 if (dot == std::string::npos || first == std::string::npos || dot < first) return base;
1205 return base.substr(0, dot);
1206}
1207
1208/** The file as Python's text mode reads it: universal newlines, so `\r\n` and `\r` become `\n`. */
1209inline std::string read_text(const std::string& path) {
1210 std::ifstream f(path.c_str(), std::ios::binary);
1211 if (!f) throw InputError(ErrorKind{"File"}, "File cannot be found: " + path);
1212 std::ostringstream ss;
1213 ss << f.rdbuf();
1214 const std::string raw = ss.str();
1215 std::string s;
1216 s.reserve(raw.size());
1217 for (std::size_t i = 0; i < raw.size(); ++i) {
1218 if (raw[i] == '\r') {
1219 s += '\n';
1220 if (i + 1 < raw.size() && raw[i + 1] == '\n') ++i;
1221 } else {
1222 s += raw[i];
1223 }
1224 }
1225 return s;
1226}
1227
1228} // namespace srvn_detail
1229
1230/**
1231 * Translate SRVN text into an LQN XML document.
1232 *
1233 * @param src the SRVN document text
1234 * @param name the model name written to `<lqn-model name=...>`
1235 * @return the `.lqnx` document, newline-terminated
1236 * @throws InputError (`LINE:input:Format`) on a syntax or reference error
1237 * @throws UnsupportedError (`LINE:unsupported:Format`) when the model uses a SPEX sweep
1238 */
1239inline std::string srvn2lqnx(const std::string& src, const std::string& name = std::string("model")) {
1240 using namespace srvn_detail;
1241 Parser p(src);
1242 const Model m = p.parse();
1243 std::vector<std::string> out;
1244 out.push_back("<?xml version=\"1.0\"?>");
1245 out.push_back("<lqn-model name=\"" + esc(name) + "\" description=\"srvn2lqnx\">");
1246 std::string s = " <solver-params comment=\"" + esc(m.comment) + "\"";
1247 for (const std::pair<std::string, Val>& kv : m.solver) s += attr(kv.first.c_str(), kv.second);
1248 if (!p.pragmas.empty()) {
1249 out.push_back(s + ">");
1250 for (const std::pair<std::string, std::string>& kv : p.pragmas)
1251 out.push_back(" <pragma param=\"" + esc(kv.first) + "\" value=\"" + esc(kv.second) + "\"/>");
1252 out.push_back(" </solver-params>");
1253 } else {
1254 out.push_back(s + "/>");
1255 }
1256 for (const Proc& proc : m.procs) {
1257 s = " <processor name=\"" + esc(proc.name) + "\" scheduling=\"" + proc.sched + "\"";
1258 if (proc.quantum.has) s += attr("quantum", proc.quantum);
1259 if (proc.mult.has && proc.sched != "inf" && proc.mult.value != 1) s += attr("multiplicity", proc.mult);
1260 if (proc.repl.has && proc.repl.value != 1) s += attr("replication", proc.repl);
1261 if (proc.speed.has && proc.speed.value != 1) s += attr("speed-factor", proc.speed);
1262 out.push_back(s + ">");
1263 for (std::size_t gi : proc.groups) {
1264 const Group& g = m.groups[gi];
1265 out.push_back(" <group name=\"" + esc(g.name) + "\"" + attr("share", g.share) +
1266 (g.cap ? " cap=\"true\"" : "") + ">");
1267 for (std::size_t ti : g.tasks) emit_task(out, " ", m.tasks[ti], m);
1268 out.push_back(" </group>");
1269 }
1270 for (std::size_t ti : proc.tasks) emit_task(out, " ", m.tasks[ti], m);
1271 out.push_back(" </processor>");
1272 }
1273 out.push_back("</lqn-model>");
1274 std::string doc;
1275 for (const std::string& l : out) {
1276 doc += l;
1277 doc += '\n';
1278 }
1279 return doc;
1280}
1281
1282/**
1283 * True when `path` holds SRVN text rather than XML: its first non-blank
1284 * character is not '<'. An empty file counts as SRVN, as in the reference.
1285 *
1286 * @throws InputError (`LINE:input:File`) when the file cannot be opened
1287 */
1288inline bool is_srvn_file(const std::string& path) {
1289 std::ifstream f(path.c_str(), std::ios::binary);
1290 if (!f) throw InputError(ErrorKind{"File"}, "File cannot be found: " + path);
1291 char buf[4096];
1292 f.read(buf, sizeof(buf));
1293 const std::streamsize got = f.gcount();
1294 for (std::streamsize i = 0; i < got; ++i)
1295 if (!srvn_detail::is_space(buf[i])) return buf[i] != '<';
1296 return true;
1297}
1298
1299/**
1300 * Whether `path` should be read as SRVN: a `.in` file, or a `.lqn` file that is
1301 * not XML. This is the line-cli and loader rule; `.lqnx` and `.xml` are never SRVN.
1302 */
1303inline bool is_srvn_path(const std::string& path) {
1304 const std::size_t dot = path.find_last_of('.');
1305 const std::size_t slash = path.find_last_of('/');
1306 if (dot == std::string::npos || (slash != std::string::npos && dot < slash)) return false;
1307 std::string ext = path.substr(dot + 1);
1308 for (char& c : ext) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
1309 if (ext == "in") return true;
1310 if (ext != "lqn") return false;
1311 std::ifstream f(path.c_str(), std::ios::binary);
1312 return !f || is_srvn_file(path);
1313}
1314
1315/**
1316 * Translate the SRVN file `path` to `.lqnx` text, model name = its basename
1317 * without extension. Errors name the file: "SRVN <path> (line n): ...".
1318 */
1319inline std::string srvn_file_to_lqnx(const std::string& path) {
1320 const std::string src = srvn_detail::read_text(path);
1321 const std::string name = srvn_detail::stem(path);
1322 try {
1323 return srvn2lqnx(src, name);
1324 } catch (const UnsupportedError& e) {
1325 std::string msg = e.what();
1326 if (msg.compare(0, 4, "SRVN") == 0) msg = "SRVN " + path + msg.substr(4);
1327 throw UnsupportedError(ErrorKind{e.kind()}, msg);
1328 } catch (const InputError& e) {
1329 std::string msg = e.what();
1330 if (msg.compare(0, 4, "SRVN") == 0) msg = "SRVN " + path + msg.substr(4);
1331 throw InputError(ErrorKind{e.kind()}, msg);
1332 }
1333}
1334
1335/**
1336 * Read an SRVN file (`.in`, or a `.lqn` that is not XML) into the intermediate
1337 * `LqnModel`, via its `.lqnx` translation.
1338 */
1339template <class T>
1340LqnModel<T> read_srvn_model(const std::string& path) {
1342}
1343
1344/** `read_srvn_model` followed by `lqn_finalize`: the struct SolverLN consumes, as `read_lqnx` is. */
1345template <class T>
1346LqnStruct<T> read_srvn(const std::string& path) {
1347 return lqn_finalize(read_srvn_model<T>(path));
1348}
1349
1350} // namespace lqn
1351} // namespace line
1352
1353#endif // LINE_LANG_LQN_SRVN_READER_H
std::string kind() const
The <kind> part, or empty for LINE:Error.
Definition error.h:88
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
A refusal: the feature, method or analysis is not covered: LINE:unsupported:<kind>.
Definition error.h:117
UnsupportedError(const std::string &what)
Definition error.h:119
std::vector< std::pair< std::string, std::string > > pragmas
Parser(const std::string &src)
The exception types the port throws.
.lqnx -> LqnStruct, a port of matlab/src/lang/layered/@LayeredNetwork/parseXML.m followed by ....
const char * task_sched(const std::string &k)
std::size_t match_number(const std::string &src, std::size_t i)
End of [-+]?(?
void tokenize(const std::string &src, std::vector< Tok > &toks, std::vector< std::pair< std::string, std::string > > &pragmas)
std::string lstrip_plus(const std::string &s)
void emit_activity(std::vector< std::string > &out, const std::string &ind, const std::string &head, const Work &a)
bool is_digit(char c)
Definition srvn_reader.h:61
bool is_space(char c)
Python's str.isspace over ASCII, which also counts the four separators 0x1c-0x1f.
Definition srvn_reader.h:57
const char * proc_sched(const std::string &k)
const char * rwlock_kind(const std::string &k)
std::string stem(const std::string &path)
Python's os.path.splitext(os.path.basename(path))[0].
std::string esc(const std::string &s)
double to_double(const std::string &s)
std::string attr(const char *name, const Val &v)
bool pragma_body(const std::string &line, std::string &body)
#\s*pragma\s+(.
InputError err(const std::string &msg, int line=-1)
Definition srvn_reader.h:97
std::string py_repr(const std::string &s)
Python's repr of an ASCII string, so messages read as the reference's do.
Definition srvn_reader.h:78
std::string read_text(const std::string &path)
The file as Python's text mode reads it: universal newlines, so \r\n and \r become \n.
void emit_task(std::vector< std::string > &out, const std::string &ind, const Task &t, const Model &m)
std::string strip(const std::string &s)
Definition srvn_reader.h:70
bool nz(const Val &v)
bool is_alnum(char c)
Definition srvn_reader.h:63
bool is_ident(char c)
[A-Za-z0-9_.
Definition srvn_reader.h:68
std::string activity_attrs(const Work &a, std::vector< const Call * > &calls)
std::string name(const Element *el, const std::string &key="name")
std::string srvn_file_to_lqnx(const std::string &path)
Translate the SRVN file path to .lqnx text, model name = its basename without extension.
LqnStruct< T > lqn_finalize(const LqnModel< T > &m)
Port of @LayeredNetwork/getStruct.m: flatten the model into its struct.
Definition lqn_reader.h:501
LqnModel< T > read_lqnx_model_text(const std::string &text)
read_lqnx_model on a .lqnx document held in a string (the SRVN reader's translation,...
LqnModel< T > read_srvn_model(const std::string &path)
Read an SRVN file (.in, or a .lqn that is not XML) into the intermediate LqnModel,...
std::string format_number(double x)
Shortest C g spelling that round-trips x; the four codebases share this rule.
LqnStruct< T > read_srvn(const std::string &path)
read_srvn_model followed by lqn_finalize: the struct SolverLN consumes, as read_lqnx is.
std::string srvn2lqnx(const std::string &src, const std::string &name=std::string("model"))
Translate SRVN text into an LQN XML document.
bool is_srvn_file(const std::string &path)
True when path holds SRVN text rather than XML: its first non-blank character is not '<'.
bool is_srvn_path(const std::string &path)
Whether path should be read as SRVN: a .in file, or a .lqn file that is not XML.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The intermediate model, and the second stage that flattens it.
Definition lqn_reader.h:411
std::vector< Conn > conns
std::vector< std::pair< std::string, std::string > > replies
(entry, activity)
std::vector< Work > defs
std::map< std::string, std::size_t > by
std::vector< std::vector< std::string > > prec
std::string kind
synch-call | asynch-call
std::vector< std::string > pre
std::vector< std::pair< std::string, Val > > items
std::vector< std::pair< std::string, Val > > fwd
std::map< int, Work > phases
std::string rw
empty when not declared
std::vector< std::size_t > tasks
std::vector< std::string > entry_order
std::map< std::string, std::size_t > task_by
std::map< std::string, Entry > entries
std::vector< Task > tasks
std::map< std::string, std::size_t > proc_by
std::map< std::string, Acts > acts
std::vector< Group > groups
std::vector< Proc > procs
std::vector< std::pair< std::string, Val > > solver
std::vector< std::size_t > groups
into Model::groups
std::vector< std::size_t > tasks
into Model::tasks
std::vector< std::string > entries
std::vector< std::pair< std::string, Val > > fanin
std::vector< std::pair< std::string, Val > > fanout
A number as written: its source spelling and its value.
Val(std::string t, double v)
An entry phase or an activity: the s, c, f, Z, M values and the calls.
std::vector< Call > calls