MXVM 1.8.1
Virtual Machine, Compiler, and Pascal Frontend
Loading...
Searching...
No Matches
valid.cpp
Go to the documentation of this file.
1
6#include "mxvm/valid.hpp"
7#include "mxvm/instruct.hpp"
9#include <algorithm>
10#include <sstream>
11#include <unordered_map>
12#include <unordered_set>
13
14namespace mxvm {
15
16 static inline bool isImmediate(const OpKind k) {
17 return k == OpKind::Num || k == OpKind::Hex || k == OpKind::Str;
18 }
19
20 static bool has_semicolon(const std::string &s) {
21 bool in_str = false, escaped = false;
22 for (size_t i = 0; i < s.size(); ++i) {
23 char c = s[i];
24 if (!in_str) {
25 if (c == '#')
26 break;
27 if (c == '/' && i + 1 < s.size() && s[i + 1] == '/')
28 break;
29 if (c == '"') {
30 in_str = true;
31 continue;
32 }
33 if (c == ';')
34 return true;
35 } else {
36 if (escaped) {
37 escaped = false;
38 continue;
39 }
40 if (c == '\\') {
41 escaped = true;
42 continue;
43 }
44 if (c == '"') {
45 in_str = false;
46 continue;
47 }
48 }
49 }
50 return false;
51 }
52
53 void Validator::collect_objects(std::unordered_set<std::string> &objects, size_t start_index, size_t end_index) {
54 bool in_object_section = false;
55 int brace_depth = 0;
56
57 for (size_t i = start_index; i <= end_index && i < scanner.size(); ++i) {
58 const auto &tok = scanner[i];
59
60 if (!in_object_section) {
61 // Look for "section object {"
62 if (tok.getTokenType() == types::TokenType::TT_ID &&
63 tok.getTokenValue() == "section" &&
64 i + 2 < scanner.size() &&
65 scanner[i + 1].getTokenValue() == "object" &&
66 scanner[i + 2].getTokenValue() == "{") {
67 in_object_section = true;
68 brace_depth = 1;
69 i += 2; // Skip "object" and "{"
70 continue;
71 }
72 } else {
73 // Inside object section
74 if (tok.getTokenValue() == "{") {
75 ++brace_depth;
76 } else if (tok.getTokenValue() == "}") {
77 if (brace_depth <= 0) {
78 throw mx::Exception("Syntax Error in '" + filename + "': unexpected '}' in object section");
79 }
80 --brace_depth;
81 if (brace_depth == 0) {
82 in_object_section = false;
83 }
84 } else if (tok.getTokenType() == types::TokenType::TT_ID && brace_depth == 1) {
85 // This is an object name at the top level of the object section
86 objects.insert(tok.getTokenValue());
87 }
88 }
89 }
90 }
91
92 static const std::unordered_map<std::string, OpSpec> kOpSpecs = {
93 {"mov", {"mov", {OpKind::Id, OpKind::Any}}},
94 {"load", {"load", {OpKind::Id, OpKind::Id, OpKind::Any}, VArity::None, 3, 4}},
95 {"store", {"store", {OpKind::Any, OpKind::Id, OpKind::Any}, VArity::None, 3, 4}},
96 {"add", {"add", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
97 {"sub", {"sub", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
98 {"mul", {"mul", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
99 {"div", {"div", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
100 {"or", {"or", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
101 {"and", {"and", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
102 {"xor", {"xor", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
103 {"not", {"not", {OpKind::Id}}},
104 {"neg", {"neg", {OpKind::Id}}},
105 {"mod", {"mod", {OpKind::Id, OpKind::Any}, VArity::AnyTail, 2, 3}},
106 {"cmp", {"cmp", {OpKind::Any, OpKind::Any}}},
107 {"fcmp", {"fcmp", {OpKind::Any, OpKind::Any}}},
108 {"jmp", {"jmp", {OpKind::Label}}},
109 {"je", {"je", {OpKind::Label}}},
110 {"jne", {"jne", {OpKind::Label}}},
111 {"jl", {"jl", {OpKind::Label}}},
112 {"jle", {"jle", {OpKind::Label}}},
113 {"jg", {"jg", {OpKind::Label}}},
114 {"jge", {"jge", {OpKind::Label}}},
115 {"jz", {"jz", {OpKind::Label}}},
116 {"jnz", {"jnz", {OpKind::Label}}},
117 {"ja", {"ja", {OpKind::Label}}},
118 {"jb", {"jb", {OpKind::Label}}},
119 {"print", {"print", {OpKind::Any}, VArity::AnyTail, 1, -1}},
120 {"string_print", {"string_print", {OpKind::Any}}},
121 {"exit", {"exit", {}, VArity::AnyTail, 0, 1}},
122 {"alloc", {"alloc", {OpKind::Id, OpKind::Any, OpKind::Any}}},
123 {"realloc", {"realloc", {OpKind::Id, OpKind::Any, OpKind::Any}}},
124 {"free", {"free", {OpKind::Id}}},
125 {"lea", {"lea", {OpKind::Id, OpKind::Id}}},
126 {"getline", {"getline", {OpKind::Id}}},
127 {"push", {"push", {OpKind::Any}}},
128 {"pop", {"pop", {OpKind::Id}}},
129 {"stack_load", {"stack_load", {OpKind::Id, OpKind::Any}}},
130 {"stack_store", {"stack_store", {OpKind::Any, OpKind::Any}}},
131 {"stack_sub", {"stack_sub", {OpKind::Any}}},
132 {"call", {"call", {OpKind::Label}}},
133 {"ret", {"ret", {}}},
134 {"done", {"done", {}}},
135 {"to_int", {"to_int", {OpKind::Id, OpKind::Any}}},
136 {"to_float", {"to_float", {OpKind::Id, OpKind::Any}}},
137 {"invoke", {"invoke", {OpKind::Id}, VArity::ArgsTail}},
138 {"return", {"return", {OpKind::Id}}}};
139
140 static bool isIdLike(OpKind k) { return k == OpKind::Id || k == OpKind::Member; }
141
143 if (!token)
144 throw mx::Exception("Syntax Error in '" + filename + "': Unexpected EOF parsing operand");
145 ParsedOp n;
147 std::string a = token->getTokenValue();
148 const scan::TToken *at = token;
149 next();
150 if (match(".")) {
151 next();
153 a += "." + token->getTokenValue();
155 n.text = a;
156 n.at = at;
157 next();
158 return n;
159 }
160 n.kind = OpKind::Id;
161 n.text = a;
162 n.at = at;
163 return n;
164 }
166 next();
167 }
169 std::string num_str = token->getTokenValue();
170 if (num_str.find('.') != std::string::npos) {
171 throw mx::Exception("Syntax Error in '" + filename + "': Floating point constants must be declared as variables, not used directly in instructions at line " + std::to_string(token->getLine()));
172 }
173 n.kind = OpKind::Num;
174 n.text = token->getTokenValue();
175 n.at = token;
176 next();
177 return n;
178 }
180 n.kind = OpKind::Hex;
181 n.text = token->getTokenValue();
182 n.at = token;
183 next();
184 return n;
185 }
187 n.kind = OpKind::Str;
188 n.text = token->getTokenValue();
189 n.at = token;
190 next();
191 return n;
192 }
193 throw mx::Exception("Syntax Error in '" + filename + "': invalid operand '" + token->getTokenValue() + "' at line " + std::to_string(token->getLine()));
194 }
195
196 std::vector<ParsedOp> Validator::parseOperandList() {
197 std::vector<ParsedOp> out;
198 while (token && !match("}")) {
199 size_t operand_line = token->getLine();
200 out.push_back(parseOperand());
201 if (match(",")) {
202 next();
203 if (token && token->getLine() != operand_line) {
204 throw mx::Exception("Syntax Error in file '" + filename + "': Operands must be on the same line");
205 }
206 continue;
207 }
208
209 if (token && token->getLine() == operand_line &&
210 !token->getTokenValue().empty()) {
211 throw mx::Exception("Syntax Error in file '" + filename + "': Multiple items or missing comma between operands on line " +
212 std::to_string(operand_line));
213 }
214 break;
215 }
216 return out;
217 }
218
219 void Validator::collect_labels(std::unordered_map<std::string, std::string> &labels) {
220 for (size_t i = 0; i < scanner.size(); ++i) {
221 const auto &tok = scanner[i];
222 if (tok.getTokenType() == types::TokenType::TT_ID) {
223 if (i + 1 < scanner.size() && scanner[i + 1].getTokenValue() == ":") {
224 labels[tok.getTokenValue()] = tok.getTokenValue();
225 }
226 }
227 }
228 }
229
231 const std::string &op,
232 const std::vector<ParsedOp> &ops,
233 const std::unordered_map<std::string, Variable> &vars,
234 const std::unordered_map<std::string, std::string> &labels,
235 const std::unordered_set<std::string> &objects,
236 std::vector<UseVar> &usedVarsRef,
237 std::vector<UseLabel> &usedLabelsRef) {
238 auto it = kOpSpecs.find(op);
239 if (it == kOpSpecs.end()) {
240 throw mx::Exception("Syntax Error in '" + filename + "': Unknown instruction '" + op + "'");
241 }
242 const OpSpec &spec = it->second;
243
244 int minArgs = (spec.minArgs >= 0) ? spec.minArgs : (int)spec.fixed.size();
245 int maxArgs = (spec.maxArgs >= 0) ? spec.maxArgs : (spec.varPolicy == VArity::None ? (int)spec.fixed.size() : INT32_MAX);
246
247 if ((int)ops.size() < minArgs || (int)ops.size() > maxArgs) {
248 throw mx::Exception("Syntax Error in '" + filename + "': '" + op + "' expects " + std::to_string(minArgs) + ((maxArgs == INT32_MAX) ? "..inf" : ".." + std::to_string(maxArgs)) + " operands; found " + std::to_string(ops.size()));
249 }
250
251 auto kindOk = [&](OpKind want, OpKind got) {
252 if (want == OpKind::Any)
253 return true;
254 if (want == OpKind::Label)
255 return got == OpKind::Id || got == OpKind::Member || got == OpKind::Label;
256 if (want == OpKind::Id)
257 return got == OpKind::Id || got == OpKind::Member;
258 return want == got;
259 };
260
261 for (size_t i = 0; i < spec.fixed.size() && i < ops.size(); ++i) {
262 if (!kindOk(spec.fixed[i], ops[i].kind)) {
263 throw mx::Exception("Syntax Error in '" + filename + "': '" + op + "' operand " + std::to_string((int)i + 1) + " has wrong kind");
264 }
265 }
266
267 if (spec.varPolicy == VArity::ArgsTail) {
268 for (size_t i = spec.fixed.size(); i < ops.size(); ++i) {
269 if (!(isImmediate(ops[i].kind) || isIdLike(ops[i].kind))) {
270 throw mx::Exception("Syntax Error in '" + filename + "': '" + op + "' extra args must be Id/Imm");
271 }
272 }
273 }
274
275 auto pushVar = [&](const ParsedOp &p) {
276 if (isIdLike(p.kind)) {
277 if (p.kind == OpKind::Id && p.text.find('.') == std::string::npos) {
278 if (!vars.count(p.text)) {
279 std::string msg = "Syntax Error in '" + filename + "': Undefined variable '" + p.text + "'";
280 if (p.at) {
281 msg += " at line " + std::to_string(p.at->getLine());
282 }
283 throw mx::Exception(msg);
284 }
285 }
286 // NEW: Validate object references
287 else if (p.kind == OpKind::Member && p.text.find('.') != std::string::npos) {
288 std::string objectName = p.text.substr(0, p.text.find('.'));
289 if (!objects.count(objectName)) {
290 std::string msg = "Syntax Error in '" + filename + "': Undefined object '" + objectName + "'";
291 if (p.at) {
292 msg += " at line " + std::to_string(p.at->getLine());
293 }
294 throw mx::Exception(msg);
295 }
296 }
297 usedVarsRef.push_back({p.text, p.at});
298 }
299 };
300 auto pushLabel = [&](const ParsedOp &p) {
301 if (p.text.find('.') == std::string::npos) {
302 usedLabelsRef.push_back({p.text, p.at});
303 }
304 };
305
306 if (op == "jmp" || op == "je" || op == "jne" || op == "jl" || op == "jle" || op == "jg" || op == "jge" || op == "jz" || op == "jnz" || op == "ja" || op == "jb") {
307 if (!ops.empty())
308 pushLabel(ops[0]);
309 }
310 if (op == "call") {
311 if (!ops.empty())
312 pushLabel(ops[0]);
313 }
314
315 if (op == "mov" || op == "pop" || op == "stack_load" || op == "alloc" || op == "getline" ||
316 op == "return" || op == "not" || op == "neg" || op == "to_int" || op == "to_float") {
317 if (!ops.empty() && isIdLike(ops[0].kind))
318 pushVar(ops[0]);
319 }
320
321 if (op == "add" || op == "sub" || op == "mul" || op == "div" || op == "or" || op == "and" ||
322 op == "xor" || op == "mod" || op == "cmp") {
323 if (ops.size() >= 1 && isIdLike(ops[0].kind))
324 pushVar(ops[0]);
325 if (ops.size() >= 2 && isIdLike(ops[1].kind))
326 pushVar(ops[1]);
327 if (ops.size() >= 3 && isIdLike(ops[2].kind))
328 pushVar(ops[2]);
329 }
330
331 if (op == "load") {
332 if (ops.size() >= 1 && isIdLike(ops[0].kind))
333 pushVar(ops[0]);
334 if (ops.size() >= 2 && isIdLike(ops[1].kind))
335 pushVar(ops[1]);
336 if (ops.size() >= 3 && isIdLike(ops[2].kind))
337 pushVar(ops[2]);
338 }
339
340 if (op == "store") {
341 if (ops.size() >= 1 && isIdLike(ops[0].kind))
342 pushVar(ops[0]);
343 if (ops.size() >= 2 && isIdLike(ops[1].kind))
344 pushVar(ops[1]);
345 if (ops.size() >= 3 && isIdLike(ops[2].kind))
346 pushVar(ops[2]);
347 }
348
349 if (op == "free") {
350 if (!ops.empty() && isIdLike(ops[0].kind))
351 pushVar(ops[0]);
352 }
353
354 if (op == "invoke") {
355 for (size_t i = 1; i < ops.size(); ++i) {
356 if (isIdLike(ops[i].kind))
357 pushVar(ops[i]);
358 }
359 }
360 if (op == "print") {
361 for (size_t i = 0; i < ops.size(); ++i) {
362 if (isIdLike(ops[i].kind))
363 pushVar(ops[i]);
364 }
365 }
366 }
367
368 bool Validator::validate(const std::string &name) {
369 filename = name;
370 scanner.scan();
371 next();
372
373 while (token) {
374 std::unordered_map<std::string, std::string> labels;
375 std::unordered_set<std::string> objects;
376 std::vector<UseVar> usedVars;
377 std::vector<UseLabel> usedLabels;
378
379 size_t block_start = index - 1;
380 size_t block_end = block_start;
381
382 int brace_count = 0;
383 bool found_opening = false;
384 for (size_t i = block_start; i < scanner.size(); ++i) {
385 if (scanner[i].getTokenValue() == "{") {
386 found_opening = true;
387 brace_count++;
388 } else if (scanner[i].getTokenValue() == "}") {
389 brace_count--;
390 if (found_opening && brace_count == 0) {
391 block_end = i;
392 break;
393 }
394 }
395 }
396
397 collect_labels(labels);
398 collect_objects(objects, block_start, block_end);
399
400 std::vector<std::string> lines;
401 {
402 std::istringstream code_info(source);
403 std::string l;
404 while (std::getline(code_info, l)) {
405 lines.push_back(l);
406 }
407 }
408
409 std::vector<std::pair<int, int>> code_ranges;
410 {
411 bool seen_section = false;
412 bool section_is_code = false;
413 bool in_code = false;
414 int brace_depth = 0;
415 int range_start = -1;
416 for (size_t i = 0; i < scanner.size(); ++i) {
417 const auto &t = scanner[i];
418 if (!in_code) {
419 if (t.getTokenType() == types::TokenType::TT_ID && t.getTokenValue() == "section") {
420 seen_section = true;
421 section_is_code = false;
422 continue;
423 }
424 if (seen_section && t.getTokenType() == types::TokenType::TT_ID) {
425 section_is_code = (t.getTokenValue() == "code");
426 continue;
427 }
428 if (section_is_code && t.getTokenValue() == "{") {
429 in_code = true;
430 brace_depth = 1;
431 range_start = t.getLine();
432 seen_section = false;
433 section_is_code = false;
434 continue;
435 }
436 } else {
437 if (t.getTokenValue() == "{") {
438 ++brace_depth;
439 } else if (t.getTokenValue() == "}") {
440 --brace_depth;
441 if (brace_depth == 0) {
442 int range_end = t.getLine();
443 code_ranges.emplace_back(range_start, range_end);
444 in_code = false;
445 }
446 }
447 }
448 }
449 }
450
451 for (const auto &pr : code_ranges) {
452 const int start = std::max(1, pr.first);
453 const int end = pr.second;
454 for (int line = start; line <= end; ++line) {
455 const int idx = line - 1;
456 if (idx < 0 || idx >= static_cast<int>(lines.size()))
457 continue;
458 const std::string &text = lines[idx];
459 if (has_semicolon(text)) {
460 throw mx::Exception(
461 "Syntax Error in file '" + filename +
462 "': Semicolons are not allowed in code section at line " +
463 std::to_string(line) + ": '" + text + "'");
464 }
465 }
466 }
467
468 std::unordered_map<int, int> line_instruction_count;
469 auto is_in_code = [&](int line) -> bool {
470 for (const auto &pr : code_ranges) {
471 if (line >= pr.first && line <= pr.second)
472 return true;
473 }
474 return false;
475 };
476 for (size_t i = 0; i < scanner.size(); ++i) {
477 const auto &tok = scanner[i];
478 const int line = tok.getLine();
479 if (!is_in_code(line))
480 continue;
481 if (tok.getTokenType() != types::TokenType::TT_ID)
482 continue;
483
484 if (i + 1 < scanner.size() && scanner[i + 1].getTokenValue() == ":") {
485 continue;
486 }
487 if (tok.getTokenValue() == "function") {
488 continue;
489 }
490 if (std::find(IncType.begin(), IncType.end(), tok.getTokenValue()) != IncType.end()) {
491 line_instruction_count[line]++;
492 }
493 }
494 for (const auto &kv : line_instruction_count) {
495 const int line_num = kv.first;
496 const int count = kv.second;
497 if (count > 1) {
498 const int idx = line_num - 1;
499 const std::string line_text =
500 (idx >= 0 && idx < static_cast<int>(lines.size())) ? lines[idx] : std::string("<unknown>");
501 throw mx::Exception("Syntax Error in file '" + filename +
502 "': Multiple instructions on same line at line " +
503 std::to_string(line_num) + ": '" + line_text + "'");
504 }
505 }
506
507 auto skipSeparators = [&]() {};
508
509 skipSeparators();
510 if (!token)
511 break;
512
513 if (match("program")) {
514 next();
515 } else {
516 require("object");
517 next();
518 }
519
520 skipSeparators();
522 next();
523 skipSeparators();
524 require("{");
525 next();
526
527 std::unordered_map<std::string, Variable> vars;
528 for (auto &n : {"stdout", "stdin", "stderr"}) {
529 vars[n] = Variable();
530 vars[n].var_name = n;
531 }
532
533 skipSeparators();
534 while (token && !match("}")) {
535 skipSeparators();
536 require("section");
537 next();
538 skipSeparators();
540 std::string sectionName = token->getTokenValue();
541 next();
542 skipSeparators();
543 require("{");
544 next();
545
546 if (sectionName == "module" || sectionName == "object") {
547 skipSeparators();
548 while (token && !match("}")) {
549 skipSeparators();
551 next();
552 skipSeparators();
553 if (match(",")) {
554 next();
555 continue;
556 }
557 continue;
558 }
559 break;
560 }
561 skipSeparators();
562 require("}");
563 next();
564 } else if (sectionName == "data") {
565 skipSeparators();
566 while (token && !match("}")) {
567 skipSeparators();
569 (token->getTokenValue() == "int" ||
570 token->getTokenValue() == "string" ||
571 token->getTokenValue() == "float" ||
572 token->getTokenValue() == "ptr" ||
573 token->getTokenValue() == "byte" ||
574 token->getTokenValue() == "export")) {
575 if (token->getTokenValue() == "export")
576 next();
577
578 std::string vtype = token->getTokenValue();
579 next();
580 skipSeparators();
581
583 std::string vname = token->getTokenValue();
584 vars[vname].var_name = vname;
585 next();
586 skipSeparators();
587
588 if (match(",") && vtype == "string") {
589 next();
590 skipSeparators();
592 next();
593 skipSeparators();
594 continue;
595 } else {
596 throw mx::Exception("Syntax Error in file '" + filename + "': string buffer requires number on line " + std::to_string(token->getLine()));
597 }
598 }
599
600 require("=");
601 next();
602 skipSeparators();
603 if (match("-") && match(types::TokenType::TT_SYM)) {
604 next();
605 skipSeparators();
606 }
607
608 if (vtype == "byte") {
610 throw mx::Exception("Syntax Error in file '" + filename + "': byte must be a valid byte value integer 0-255 on line " + std::to_string(token->getLine()));
611 }
612 int64_t value = std::stoll(token->getTokenValue(), nullptr, 0);
613 if (value < 0 || value > 0xFF) {
614 throw mx::Exception("Syntax Error in file '" + filename + "': byte out of range 0-255 on line: " + std::to_string(token->getLine()));
615 }
616 next();
617 skipSeparators();
618 } else if (vtype == "string") {
620 next();
621 skipSeparators();
622 } else if (token->getTokenValue() == "null" ||
626 next();
627 skipSeparators();
628 } else {
629 throw mx::Exception("Syntax Error in file '" + filename + "': Expected value for variable, found: " + token->getTokenValue() + " at line " + std::to_string(token->getLine()));
630 }
631 } else {
632 throw mx::Exception("Syntax Error in file '" + filename + "': Expected variable declaration, found: " + token->getTokenValue() + " at line " + std::to_string(token->getLine()));
633 }
634 }
635 skipSeparators();
636 require("}");
637 next();
638 } else if (sectionName == "code") {
639 skipSeparators();
640
641 while (token && !match("}")) {
642 size_t old_index = index;
643 skipSeparators();
644
645 if (token && token->getTokenValue() == ";") {
646 throw mx::Exception("Syntax Error in file '" + filename +
647 "': Semicolons not allowed in code section at line " +
648 std::to_string(token->getLine()));
649 }
650
651 if (match(types::TokenType::TT_ID) && token->getTokenValue() == "function") {
652 next();
653 skipSeparators();
655 next();
656 skipSeparators();
657 require(":");
658 next();
659 continue;
660 }
661
662 if (match(types::TokenType::TT_ID) && peekIs(":")) {
663 next();
664 next();
665 continue;
666 }
667
669 std::string op = token->getTokenValue();
670 if (std::find(IncType.begin(), IncType.end(), op) == IncType.end()) {
671 throw mx::Exception("Syntax Error in file '" + filename + "': Unknown instruction '" + op + "' at line " + std::to_string(token->getLine()));
672 }
673 next();
674 skipSeparators();
675
676 if (op == "ret" || op == "done") {
677 std::vector<ParsedOp> emptyOps;
678 validateAgainstSpec(op, emptyOps, vars, labels, objects, usedVars, usedLabels); // Add objects
679 continue;
680 }
681
682 auto ops = parseOperandList();
683 validateAgainstSpec(op, ops, vars, labels, objects, usedVars, usedLabels); // Add objects
684 continue;
685 } else {
686 throw mx::Exception("Syntax Error in file '" + filename + "': Unexpected token '" + token->getTokenValue() + "' in code section at line " + std::to_string(token->getLine()));
687 }
688
689 if (old_index == index) {
690 if (!next())
691 break;
692 }
693 }
694
695 skipSeparators();
696 require("}");
697 next();
698 } else {
699 throw mx::Exception("Syntax Error in file '" + filename + "': Unknown section: " + sectionName + " at line " + std::to_string(token->getLine()));
700 }
701 skipSeparators();
702 }
703
704 skipSeparators();
705 require("}");
706 next(); // Consume the closing brace of the object/program
707
708 for (auto &u : usedLabels) {
709 if (!labels.count(u.name)) {
710 throw mx::Exception("Syntax Error in '" + filename + "': Undefined label '" + u.name + "' at line " + std::to_string(u.at->getLine()));
711 }
712 }
713 skipSeparators(); // Prepare for the next object/program or EOF
714 }
715 return true;
716 }
717
718 bool Validator::match(const std::string &m) {
719 if (!token)
720 return false;
721 if (token->getTokenValue() != m)
722 return false;
723 return true;
724 }
725
726 void Validator::require(const std::string &r) {
727 if (!token) {
728 throw mx::Exception("Syntax Error in '" + filename + "': Required: " + r + " but reached end of file");
729 }
730 if (r != token->getTokenValue())
731 throw mx::Exception(
732 "Syntax Error in '" + filename + "': Required: " + r +
733 " Found: " + token->getTokenValue() +
734 " at line " + std::to_string(token->getLine()));
735 }
736
738 if (!token || index >= scanner.size())
739 return false;
740 if (t != token->getTokenType())
741 return false;
742 return true;
743 }
744
746 if (!token || index >= scanner.size()) {
747 throw mx::Exception("Syntax Error in '" + filename + "': unexpected EOF");
748 }
749 if (t != token->getTokenType())
750 throw mx::Exception(
751 "Syntax Error in '" + filename + "': Required: " + tokenTypeToString(t) +
752 " instead found: " + token->getTokenValue() +
753 ":" + tokenTypeToString(token->getTokenType()) +
754 " at line " + std::to_string(token->getLine()));
755 }
756
758 while (index < scanner.size() &&
759 scanner[index].getTokenValue() == "\n" &&
760 scanner[index].getTokenType() == types::TokenType::TT_SYM) {
761 index++;
762 }
763 if (index < scanner.size()) {
764 token = &scanner[index++];
765 return true;
766 }
767 token = nullptr;
768 return false;
769 }
770
771 bool Validator::peekIs(const std::string &s) {
772 return index < scanner.size() && scanner[index].getTokenValue() == s;
773 }
774
776 return index < scanner.size() && scanner[index].getTokenType() == t;
777 }
778
780 switch (t) {
782 return "Identifier";
784 return "Number";
786 return "Hex";
788 return "String";
790 return "Symbol";
791 default:
792 return "";
793 }
794 }
795
796 Validator::Validator(const std::string &source_) : scanner(source_), source(source_) {
797 }
798} // namespace mxvm
General-purpose exception with errno-aware factory method.
Definition exception.hpp:38
const scan::TToken * token
Definition valid.hpp:75
scan::Scanner scanner
Definition valid.hpp:72
std::vector< ParsedOp > parseOperandList()
Parse a comma-separated list of operands.
Definition valid.cpp:196
ParsedOp parseOperand()
Parse a single instruction operand from the token stream.
Definition valid.cpp:142
std::string source
Definition valid.hpp:73
bool match(const std::string &m)
Check if the current token value matches and advance.
Definition valid.cpp:718
std::string filename
Definition valid.hpp:71
size_t index
Definition valid.hpp:74
void require(const std::string &r)
Require the current token value to match, or report an error.
Definition valid.cpp:726
bool validate(const std::string &name)
Run full validation on the source program.
Definition valid.cpp:368
bool peekIs(const std::string &s)
Check if the current token value matches without consuming.
Definition valid.cpp:771
Validator(const std::string &source)
Construct a validator from MXVM source code.
Definition valid.cpp:796
bool next()
Advance to the next token; returns false at end.
Definition valid.cpp:757
std::string tokenTypeToString(types::TokenType t)
Convert a TokenType to its display string.
Definition valid.cpp:779
void collect_objects(std::unordered_set< std::string > &objects, size_t start_index, size_t end_index)
Collect all object names declared between token indices.
Definition valid.cpp:53
void collect_labels(std::unordered_map< std::string, std::string > &labels)
Pre-collect all label declarations and map them to function names.
Definition valid.cpp:219
void validateAgainstSpec(const std::string &op, const std::vector< ParsedOp > &ops, const std::unordered_map< std::string, Variable > &vars, const std::unordered_map< std::string, std::string > &labels, const std::unordered_set< std::string > &objects, std::vector< UseVar > &usedVarsRef, std::vector< UseLabel > &usedLabelsRef)
Validate an instruction's operands against the spec table.
Definition valid.cpp:230
Exception class, hex formatting utilities, and terminal color definitions.
Instruction set enum, operand/instruction structs, variable types, and Variable/Variable_Value defini...
std::vector< std::string > IncType
String representations of Inc opcodes, indexed by enum value.
Definition instruct.hpp:82
Definition ast.hpp:14
static const std::unordered_map< std::string, OpSpec > kOpSpecs
Definition valid.cpp:92
@ None
exact fixed operand count required
Definition valid.hpp:49
@ ArgsTail
fixed operands followed by argument-typed trailing operands
Definition valid.hpp:51
@ AnyTail
fixed operands followed by any number of trailing operands
Definition valid.hpp:50
static bool isIdLike(OpKind k)
Definition valid.cpp:140
static bool has_semicolon(const std::string &s)
Definition valid.cpp:20
static bool isImmediate(const OpKind k)
Definition valid.cpp:16
OpKind
Classification of operand kinds for validation.
Definition valid.hpp:19
token::Token< char > TToken
Default token type.
Definition scanner.hpp:19
TokenType
Classification of scanned tokens.
Definition types.hpp:16
@ TT_SYM
symbol / operator token
Definition types.hpp:19
@ TT_HEX
hexadecimal numeric literal
Definition types.hpp:22
@ TT_STR
string literal
Definition types.hpp:20
@ TT_ID
identifier or keyword
Definition types.hpp:17
@ TT_NUM
decimal numeric literal
Definition types.hpp:21
Specification of valid operand patterns for an instruction.
Definition valid.hpp:55
int minArgs
Definition valid.hpp:59
VArity varPolicy
Definition valid.hpp:58
std::vector< OpKind > fixed
required fixed operand kinds
Definition valid.hpp:57
int maxArgs
Definition valid.hpp:60
A parsed operand with its kind, text, and source location.
Definition valid.hpp:30
OpKind kind
Definition valid.hpp:31
std::string text
Definition valid.hpp:32
const scan::TToken * at
token at the operand's source position
Definition valid.hpp:33
A named variable with type, value, and optional object association.
Definition instruct.hpp:292
Validator for MXVM programs — checks variable/label usage and instruction operand specifications.