MXVM 1.8.1
Virtual Machine, Compiler, and Pascal Frontend
Loading...
Searching...
No Matches
validator.cpp
Go to the documentation of this file.
1
6#include "validator.hpp"
8#include <algorithm>
9#include <cctype>
10#include <fstream>
11#include <sstream>
12#include <unordered_set>
13
14namespace mxx {
15
16 TPValidator::TPValidator(const std::string &source_) : scanner(source_), source(source_) {}
17
18 bool TPValidator::validate(const std::string &name) {
19 filename = name;
20 scanner.scan();
21
22 { // strip { } comments
23 auto &toks = scanner.getTokens();
24 for (size_t i = 0; i < toks.size();) {
25 if (toks[i].getTokenValue() == "{") {
26 size_t start = i;
27 ++i;
28 while (i < toks.size() && toks[i].getTokenValue() != "}") {
29 ++i;
30 }
31 if (i < toks.size()) {
32 ++i;
33 }
34 toks.erase(toks.begin() + static_cast<int64_t>(start),
35 toks.begin() + static_cast<int64_t>(i));
36 i = start;
37 } else {
38 ++i;
39 }
40 }
41 }
42
43 index = 0;
44 token = nullptr;
45 scopeStack.clear();
46 declaredProcs.clear();
47 declaredFuncs.clear();
48 importedUnits.clear();
49 pushScope();
50 next();
51 if (isKW("unit"))
52 parseUnit();
53 else
55 if (token)
56 failHere("Unexpected tokens after end of program");
57 popScope();
58 return true;
59 }
60
67 static bool ciEquals(const std::string &a, const std::string &b) {
68 if (a.size() != b.size()) return false;
69 for (size_t i = 0; i < a.size(); ++i)
70 if (std::tolower(static_cast<unsigned char>(a[i])) !=
71 std::tolower(static_cast<unsigned char>(b[i])))
72 return false;
73 return true;
74 }
75
76 bool TPValidator::match(const std::string &s) const {
77 return token && ciEquals(token->getTokenValue(), s);
78 }
79
81 return token && token->getTokenType() == t;
82 }
83
85 while (index < scanner.size() &&
86 scanner[index].getTokenType() == types::TokenType::TT_SYM &&
87 scanner[index].getTokenValue() == "\n") {
88 ++index;
89 }
90 if (index < scanner.size()) {
91 token = &scanner[index++];
92 return true;
93 }
94 token = nullptr;
95 return false;
96 }
97
98 bool TPValidator::peekIs(const std::string &s) {
99 return index < scanner.size() && ciEquals(scanner[index].getTokenValue(), s);
100 }
101
102 void TPValidator::require(const std::string &s) {
103 if (!match(s)) {
104 if (token)
105 failAt(token, "Required: " + s + " Found: " + found());
106 fail("Required: " + s + " Found: EOF");
107 }
108 }
109
111 if (!match(t)) {
112 if (token)
113 failAt(token, "Required: " + tokenTypeToString(t) + " Found: " + found());
114 fail("Required: " + tokenTypeToString(t) + " Found: EOF");
115 }
116 }
117
118 void TPValidator::requireKW(const std::string &k) {
119 if (!isKW(k)) {
120 if (token)
121 failAt(token, "Required: " + k + " Found: " + found());
122 fail("Required: " + k + " Found: EOF");
123 }
124 }
125
126 void TPValidator::fail(const std::string &msg) {
127 throw mx::Exception("Syntax Error in '" + filename + "': " + msg);
128 }
129
130 void TPValidator::failHere(const std::string &msg) {
131 if (token)
132 throw mx::Exception("Syntax Error in '" + filename + "': " + msg + " at line " + std::to_string(token->getLine()));
133 throw mx::Exception("Syntax Error in '" + filename + "': " + msg);
134 }
135
136 void TPValidator::failAt(const scan::TToken *at, const std::string &msg) {
137 if (at)
138 throw mx::Exception("Syntax Error in '" + filename + "': " + msg + " at line " + std::to_string(at->getLine()));
139 throw mx::Exception("Syntax Error in '" + filename + "': " + msg);
140 }
141
143 switch (t) {
145 return "Identifier";
147 return "Number";
149 return "Hex";
151 return "String";
153 return "Symbol";
154 default:
155 return "Unknown";
156 }
157 }
158
159 std::string TPValidator::lower(std::string s) {
160 for (auto &c : s)
161 c = (char)std::tolower((unsigned char)c);
162 return s;
163 }
164
165 std::string TPValidator::found() const {
166 if (!token)
167 return "EOF";
168 return token->getTokenValue() + ":" + tokenTypeToString(token->getTokenType());
169 }
170
171 bool TPValidator::isKW(const std::string &k) const {
172 if (!token || !isPascalKeyword(k) || token->getTokenType() != types::TokenType::TT_ID)
173 return false;
174 return lower(token->getTokenValue()) == k;
175 }
176
178 scopeStack.emplace_back();
179 }
180
182 if (!scopeStack.empty()) {
183 scopeStack.pop_back();
184 }
185 }
186
187 void TPValidator::declareConst(const std::string &name, const scan::TToken *at) {
188 auto key = lower(name);
189 if (scopeStack.back().consts.count(key)) {
190 failAt(at, "Redeclaration of constant '" + name + "' in the same scope");
191 }
192 scopeStack.back().consts.insert(key);
193 }
194
195 bool TPValidator::isBuiltinConst(const std::string &name) const {
196 std::string key = lower(name);
197 return key == "true" || key == "false" || key == "nil";
198 }
199
200 void TPValidator::checkVar(const std::string &name, const scan::TToken *at) {
201 auto key = lower(name);
202
203 if (key == "result" && inFunctionDepth > 0)
204 return;
205
206 for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) {
207 if (it->vars.count(key))
208 return;
209 }
210
211 if (withDepth > 0)
212 return;
213
214 if (!declaredFuncs.count(key) && !declaredProcs.count(key) && !isBuiltinConst(name) && !isPascalKeyword(key) && !importedUnits.count(key)) {
215 failAt(at, "Use of undeclared identifier '" + name + "'");
216 }
217 }
218
219 void TPValidator::checkVarOrConst(const std::string &name, const scan::TToken *at) {
220 auto key = lower(name);
221
222 if (key == "result" && inFunctionDepth > 0)
223 return;
224
225 for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) {
226 if (it->vars.count(key) || it->consts.count(key))
227 return;
228 }
229
230 if (withDepth > 0)
231 return;
232
233 if (!declaredFuncs.count(key) && !declaredProcs.count(key) && !isBuiltinConst(name) && !isPascalKeyword(key) && !importedUnits.count(key)) {
234 failAt(at, "Use of undeclared identifier '" + name + "'");
235 }
236 }
237
238 void TPValidator::checkConstOnly(const std::string &name, const scan::TToken *at) {
239 auto key = lower(name);
240 for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) {
241 if (it->consts.count(key))
242 return;
243 }
244 if (!isBuiltinConst(name)) {
245 failAt(at, "Constant expression requires constant '" + name + "'");
246 }
247 }
248
250 requireKW("program");
251 next();
253 next();
254 require(";");
255 next();
256 if (isKW("uses"))
257 parseUses();
258 parseBlock();
259 require(".");
260 next();
261 }
262
264 requireKW("unit");
265 next();
267 next();
268 require(";");
269 next();
270
271 // interface section
272 requireKW("interface");
273 next();
274 if (isKW("uses"))
275 parseUses();
277
278 // implementation section
279 requireKW("implementation");
280 next();
281 if (isKW("uses"))
282 parseUses();
283
284 // parse implementation declarations (const, type, var, procedure, function)
285 while (isKW("const") || isKW("type") || isKW("var") || isKW("procedure") || isKW("function")) {
286 if (isKW("const"))
288 else if (isKW("type"))
290 else if (isKW("var"))
292 else if (isKW("procedure") || isKW("function"))
294 }
295
296 requireKW("end");
297 next();
298 require(".");
299 next();
300 }
301
303 while (isKW("procedure") || isKW("function") || isKW("type") || isKW("const") || isKW("var")) {
304 if (isKW("procedure")) {
305 next();
307 declaredProcs.insert(lower(token->getTokenValue()));
308 next();
309 pushScope();
310 if (match("("))
312 require(";");
313 next();
314 popScope();
315 } else if (isKW("function")) {
316 next();
318 declaredFuncs.insert(lower(token->getTokenValue()));
319 next();
320 pushScope();
321 if (match("("))
323 require(":");
324 next();
325 parseType("");
326 require(";");
327 next();
328 popScope();
329 } else if (isKW("type")) {
331 } else if (isKW("const")) {
333 } else if (isKW("var")) {
335 }
336 }
337 }
338
340 requireKW("uses");
341 next();
343 std::vector<std::string> usedUnits;
344 usedUnits.push_back(token->getTokenValue());
345 next();
346 while (match(",")) {
347 next();
349 usedUnits.push_back(token->getTokenValue());
350 next();
351 }
352 require(";");
353 next();
354
355 // Import interface declarations from used units
356 static const std::unordered_set<std::string> nativeModules = {"io", "std", "string", "sdl", "strlib"};
357 std::string inputDir;
358 {
359 auto pos = filename.find_last_of("/\\");
360 inputDir = (pos != std::string::npos) ? filename.substr(0, pos + 1) : "./";
361 }
362 for (const auto &unit : usedUnits) {
363 importedUnits.insert(lower(unit));
364 if (nativeModules.count(unit))
365 continue;
366 // Try to find the unit's .pas file
367 std::string unitFile = inputDir + unit + ".pas";
368 std::ifstream uf(unitFile);
369 if (!uf.is_open()) {
370 std::string lowerUnit = unit;
371 std::transform(lowerUnit.begin(), lowerUnit.end(), lowerUnit.begin(), ::tolower);
372 unitFile = inputDir + lowerUnit + ".pas";
373 uf.open(unitFile);
374 }
375 if (!uf.is_open())
376 continue;
377 std::ostringstream buf;
378 buf << uf.rdbuf();
379 uf.close();
380 try {
381 // Use a scanner to extract interface declarations
382 scan::Scanner unitScanner(buf.str());
383 unitScanner.scan();
384 auto &toks = unitScanner.getTokens();
385 // Strip brace comments
386 for (size_t i = 0; i < toks.size();) {
387 if (toks[i].getTokenValue() == "{") {
388 size_t start = i;
389 ++i;
390 while (i < toks.size() && toks[i].getTokenValue() != "}") ++i;
391 if (i < toks.size()) ++i;
392 toks.erase(toks.begin() + static_cast<int64_t>(start),
393 toks.begin() + static_cast<int64_t>(i));
394 i = start;
395 } else {
396 ++i;
397 }
398 }
399 // Find 'interface' keyword, then collect procedure/function names until 'implementation'
400 size_t ti = 0;
401 auto tlower = [](const std::string &s) {
402 std::string r = s;
403 std::transform(r.begin(), r.end(), r.begin(), ::tolower);
404 return r;
405 };
406 // Helper to skip newline tokens in the raw token stream
407 auto skipNL = [&]() {
408 while (ti < toks.size() && toks[ti].getTokenType() == types::TokenType::TT_SYM
409 && toks[ti].getTokenValue() == "\n") ++ti;
410 };
411 // Skip to 'interface'
412 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) != "interface") ++ti;
413 if (ti < toks.size()) ++ti; // move past 'interface'
414 skipNL();
415 // Skip optional uses in interface
416 if (ti < toks.size() && tlower(toks[ti].getTokenValue()) == "uses") {
417 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
418 if (ti < toks.size()) ++ti;
419 skipNL();
420 }
421 // Collect procedure/function/const/var/type names until 'implementation'
422 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) != "implementation") {
423 skipNL();
424 if (ti >= toks.size() || tlower(toks[ti].getTokenValue()) == "implementation") break;
425 std::string kw = tlower(toks[ti].getTokenValue());
426 if (kw == "procedure" && ti + 1 < toks.size()) {
427 ++ti; skipNL();
428 declaredProcs.insert(lower(toks[ti].getTokenValue()));
429 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
430 if (ti < toks.size()) ++ti;
431 skipNL();
432 } else if (kw == "function" && ti + 1 < toks.size()) {
433 ++ti; skipNL();
434 declaredFuncs.insert(lower(toks[ti].getTokenValue()));
435 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
436 if (ti < toks.size()) ++ti;
437 skipNL();
438 } else if (kw == "const") {
439 ++ti; skipNL(); // skip 'const'
440 // Collect constant names: name = expr ;
441 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) != "implementation"
442 && toks[ti].getTokenType() == types::TokenType::TT_ID
443 && tlower(toks[ti].getTokenValue()) != "var"
444 && tlower(toks[ti].getTokenValue()) != "type"
445 && tlower(toks[ti].getTokenValue()) != "procedure"
446 && tlower(toks[ti].getTokenValue()) != "function") {
447 scopeStack.back().consts.insert(lower(toks[ti].getTokenValue()));
448 // Skip past = expr ;
449 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
450 if (ti < toks.size()) ++ti;
451 skipNL();
452 }
453 } else if (kw == "var") {
454 ++ti; skipNL(); // skip 'var'
455 // Collect variable names: name [, name]* : type ;
456 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) != "implementation"
457 && toks[ti].getTokenType() == types::TokenType::TT_ID
458 && tlower(toks[ti].getTokenValue()) != "const"
459 && tlower(toks[ti].getTokenValue()) != "type"
460 && tlower(toks[ti].getTokenValue()) != "procedure"
461 && tlower(toks[ti].getTokenValue()) != "function") {
462 scopeStack.back().vars.insert(lower(toks[ti].getTokenValue()));
463 ++ti;
464 while (ti < toks.size() && toks[ti].getTokenValue() == ",") {
465 ++ti; // skip ','
466 if (ti < toks.size()) {
467 scopeStack.back().vars.insert(lower(toks[ti].getTokenValue()));
468 ++ti;
469 }
470 }
471 // Skip past : type ;
472 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
473 if (ti < toks.size()) ++ti;
474 skipNL();
475 }
476 } else if (kw == "type") {
477 ++ti; skipNL(); // skip 'type'
478 // Collect type names: name = typedecl ;
479 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) != "implementation"
480 && toks[ti].getTokenType() == types::TokenType::TT_ID
481 && tlower(toks[ti].getTokenValue()) != "const"
482 && tlower(toks[ti].getTokenValue()) != "var"
483 && tlower(toks[ti].getTokenValue()) != "procedure"
484 && tlower(toks[ti].getTokenValue()) != "function") {
485 scopeStack.back().types.insert(lower(toks[ti].getTokenValue()));
486 // Skip past = typedecl ;
487 while (ti < toks.size() && toks[ti].getTokenValue() != ";") ++ti;
488 if (ti < toks.size()) ++ti;
489 skipNL();
490 }
491 } else {
492 ++ti; skipNL();
493 }
494 }
495 } catch (...) {
496 // Unit parsing failed; skip importing
497 }
498 }
499 }
500
502 if (isKW("label"))
504 while (isKW("const") || isKW("type") || isKW("var") || isKW("procedure") || isKW("function") || isKW("label")) {
505 if (isKW("label"))
507 else if (isKW("const"))
509 else if (isKW("type"))
511 else if (isKW("var"))
513 else if (isKW("procedure") || isKW("function"))
515 }
517 }
518
520 requireKW("label");
521 next();
523 next();
524 while (match(",")) {
525 next();
527 next();
528 }
529 require(";");
530 next();
531 }
532
534 requireKW("const");
535 next();
537 if (isKW("type") || isKW("var") || isKW("label") ||
538 isKW("begin") || isKW("procedure") || isKW("function") ||
539 isKW("implementation") || isKW("end") || isKW("interface")) {
540 break;
541 }
542 const auto *nameTok = token;
543 std::string name = token->getTokenValue();
544 next();
545 require("=");
546 next();
547 parseConstExpr({";"}, true);
548 declareConst(name, nameTok);
549 require(";");
550 next();
551 }
552 }
553
555 requireKW("type");
556 next();
557
559 if (isKW("procedure") || isKW("function") || isKW("var") || isKW("begin") || isKW("const") ||
560 isKW("implementation") || isKW("end") || isKW("interface")) {
561 break;
562 }
563
564 const auto *nameTok = token;
565 std::string name = token->getTokenValue();
566 next();
567 require("=");
568 next();
569
570 parseType(name);
571
572 declareType(name, nameTok);
573 require(";");
574 next();
575 }
576 }
577
579 requireKW("var");
580 next();
582 if (isKW("procedure") || isKW("function") || isKW("begin") ||
583 isKW("type") || isKW("const") || isKW("label") ||
584 isKW("implementation") || isKW("end") || isKW("interface")) {
585 break;
586 }
587 declareVar(token->getTokenValue(), token);
588 next();
589 while (match(",")) {
590 next();
592 declareVar(token->getTokenValue(), token);
593 next();
594 }
595 require(":");
596 next();
597 parseType("");
598 if (match("=")) {
599 next();
600 parseConstExpr({";"}, false);
601 }
602 require(";");
603 next();
604 }
605 }
607 bool isProc = isKW("procedure");
608 if (isProc) {
609 requireKW("procedure");
610 next();
612 declaredProcs.insert(lower(token->getTokenValue()));
613 next();
614 } else {
615 requireKW("function");
616 next();
618 declaredFuncs.insert(lower(token->getTokenValue()));
619 next();
620 }
621
622 pushScope();
623
624 if (match("("))
626
627 if (isProc) {
628 require(";");
629 next();
630 } else {
631 require(":");
632 next();
633 parseType("");
634 require(";");
635 next();
636 }
637
638 if (isKW("forward")) {
639 next();
640 require(";");
641 next();
642 popScope();
643 return;
644 }
645
646 if (!isProc)
648 parseBlock();
649 if (!isProc)
651 require(";");
652 next();
653 popScope();
654 }
655
657 require("(");
658 next();
659 if (match(")")) {
660 next();
661 return;
662 }
663 while (true) {
664 bool byRef = isKW("var");
665 if (byRef)
666 next();
668 require(":");
669 next();
670 parseType("");
671 if (match(";")) {
672 next();
673 continue;
674 }
675 require(")");
676 next();
677 break;
678 }
679 }
680
683 declareVar(token->getTokenValue(), token);
684 next();
685 while (match(",")) {
686 next();
688 declareVar(token->getTokenValue(), token);
689 next();
690 }
691 }
692
693 void TPValidator::parseType(const std::string &typeName) {
694 if (isKW("packed")) {
695 next(); // consume 'packed' modifier (accepted but has no effect)
696 }
697 if (isKW("array")) {
698 next();
699 // Dynamic array: array of <type> (no bounds)
700 if (isKW("of")) {
701 next();
702 parseType("");
703 return;
704 }
705 // Static array: array[lo..hi] of <type>
706 require("[");
707 next();
709 while (match(",")) {
710 next();
712 }
713 require("]");
714 next();
715 requireKW("of");
716 next();
717 parseType("");
718 return;
719 }
720 if (isKW("record")) {
721 next();
722 pushRecordFieldScope(typeName);
723 while (!isKW("end") && !isKW("case")) {
725 require(":");
726 next();
727 parseType("");
728 require(";");
729 next();
730 }
731 if (isKW("case")) {
732 next();
734 next();
735 require(":");
736 next();
737 parseType("");
738 requireKW("of");
739 next();
740 while (!isKW("end")) {
741 parseConstExpr({":", ","}, false);
742 while (match(",")) {
743 next();
744 parseConstExpr({":", ","}, false);
745 }
746 require(":");
747 next();
748 require("(");
749 next();
750 while (!match(")")) {
752 next();
753 while (match(",")) {
754 next();
756 next();
757 }
758 require(":");
759 next();
760 parseType("");
761 if (match(";"))
762 next();
763 }
764 require(")");
765 next();
766 if (match(";"))
767 next();
768 }
769 }
770 requireKW("end");
771 next();
773 return;
774 }
775 if (isKW("set")) {
776 next();
777 requireKW("of");
778 next();
780 return;
781 }
782 if (isKW("file")) {
783 next();
784 if (isKW("of")) {
785 next();
787 }
788 return;
789 }
790 if (match("(")) {
791 next();
793 declareConst(token->getTokenValue(), token);
794 next();
795 if (match(","))
796 next();
797 }
798 require(")");
799 next();
800 return;
801 }
802 if (match("^")) {
803 next();
805 return;
806 }
807 if (isKW("string")) {
808 next();
809 if (match("[")) {
810 next();
811 parseConstExpr({"]"}, true);
812 require("]");
813 next();
814 }
815 return;
816 }
817 if (isBuiltinType()) {
818 next();
819 return;
820 }
822 }
823
826 std::string name = token->getTokenValue();
827 checkType(name, token);
828 next();
829 }
830
832 parseConstExpr({"..", "]", ","}, true);
833 if (match("..")) {
834 next();
835 parseConstExpr({"]", ","}, true);
836 }
837 }
838
840 if (match("-")) {
841 next();
843 return;
844 }
846 }
847
850 next();
851 return;
852 }
853 if (isKW("true") || isKW("false") || isKW("nil")) {
854 next();
855 return;
856 }
858 checkConstOnly(token->getTokenValue(), token);
859 next();
860 }
861
863 requireKW("begin");
864 next();
865
866 if (isKW("end")) {
867 requireKW("end");
868 next();
869 return;
870 }
871
872 while (!isKW("end")) {
873 if (token == nullptr) {
874 fail("Unexpected end of file inside compound statement");
875 }
876
878
879 if (match(";")) {
880 next();
881
882 if (isKW("end")) {
883 break;
884 }
885 } else if (isKW("end")) {
886
887 break;
888 } else {
889
890 failHere("Expected ';' or 'end'");
891 }
892 }
893
894 requireKW("end");
895 next();
896 }
897
899 if (!token)
900 failHere("Unexpected EOF in statement");
901 if (isKW("begin")) {
903 return;
904 }
905 if (isKW("if")) {
906 parseIf();
907 return;
908 }
909 if (isKW("while")) {
910 parseWhile();
911 return;
912 }
913 if (isKW("repeat")) {
914 parseRepeat();
915 return;
916 }
917 if (isKW("for")) {
918 parseFor();
919 return;
920 }
921 if (isKW("case")) {
922 parseCase();
923 return;
924 }
925 if (isKW("with")) {
926 parseWith();
927 return;
928 }
929 if (isKW("goto")) {
930 parseGoto();
931 return;
932 }
934 // Label prefix: 100: statement
935 next();
936 if (match(":")) {
937 next();
939 return;
940 }
941 failHere("Statement cannot start with number (expected ':' for label)");
942 }
945 return;
946 }
947 if (match(";") || match(")"))
948 return;
949 if (isKW("end"))
950 return;
951 failHere("Invalid statement start");
952 }
953
955 requireKW("if");
956 next();
957 parseExprStop({"then"});
958 requireKW("then");
959 next();
961 if (isKW("else")) {
962 next();
964 }
965 }
966
968 requireKW("while");
969 next();
970 parseExprStop({"do"});
971 requireKW("do");
972 next();
974 }
975
977 requireKW("repeat");
978 next();
979 while (true) {
980 if (isKW("until")) {
981 break;
982 }
984 if (match(";")) {
985 next();
986 if (isKW("until")) {
987 break;
988 }
989 } else if (isKW("until")) {
990 break;
991 } else {
992 failHere("Expected ';' or 'until'");
993 }
994 }
995 requireKW("until");
996 next();
997 parseExprStop({";", "end"});
998 }
999
1001 requireKW("for");
1002 next();
1004 checkVar(token->getTokenValue(), token);
1005 next();
1006 require(":=");
1007 next();
1008 parseExprStop({"to", "downto"});
1009 if (isKW("to"))
1010 next();
1011 else {
1012 requireKW("downto");
1013 next();
1014 }
1015 parseExprStop({"do"});
1016 requireKW("do");
1017 next();
1019 }
1020
1022 requireKW("case");
1023 next();
1024 parseExprStop({"of"});
1025 requireKW("of");
1026 next();
1027 while (!isKW("end")) {
1029 require(":");
1030 next();
1032 if (match(";"))
1033 next();
1034 if (isKW("else")) {
1035 next();
1037 if (match(";"))
1038 next();
1039 break;
1040 }
1041 if (isKW("end"))
1042 break;
1043 }
1044 requireKW("end");
1045 next();
1046 }
1047
1050 while (match(",")) {
1051 next();
1053 }
1054 }
1055
1057 if (match("-"))
1058 next();
1060 const auto *at = token;
1061 next();
1062 if (match("..")) {
1063 next();
1064 if (match("-"))
1065 next();
1067 next();
1068 return;
1069 }
1070 failAt(at, "Invalid case label range");
1071 }
1072 return;
1073 }
1074 failHere("Invalid case label");
1075 }
1076
1078 requireKW("with");
1079 next();
1081 while (match(",")) {
1082 next();
1084 }
1085 requireKW("do");
1086 next();
1087 ++withDepth;
1089 --withDepth;
1090 }
1091
1093 requireKW("goto");
1094 next();
1096 next();
1097 }
1098
1100 if (match(types::TokenType::TT_ID) && peekIs("(")) {
1101 next();
1103 return;
1104 }
1106 if (match(":=")) {
1107 next();
1108 parseExprStop({";", "end", "else", ")", "]", "until", "of", "do", "then"});
1109 return;
1110 }
1111 if (match("(")) {
1113 return;
1114 }
1115 }
1116
1119 checkVar(token->getTokenValue(), token);
1120 next();
1121 while (true) {
1122 if (match(".")) {
1123 next();
1125 next();
1126 continue;
1127 }
1128 if (match("[")) {
1129 next();
1130 parseExprStop({"]"});
1131 while (match(",")) {
1132 next();
1133 parseExprStop({"]"});
1134 }
1135 require("]");
1136 next();
1137 continue;
1138 }
1139 if (match("^")) {
1140 next();
1141 continue;
1142 }
1143 break;
1144 }
1145 }
1146
1148 require("(");
1149 next();
1150 if (match(")")) {
1151 next();
1152 return;
1153 }
1154 while (true) {
1155 if (isKW("var"))
1156 next();
1157 parseExprStop({")", ";", ","});
1158 if (match(",")) {
1159 next();
1160 continue;
1161 }
1162 require(")");
1163 next();
1164 break;
1165 }
1166 }
1167
1168 void TPValidator::parseExprStop(const std::unordered_set<std::string> &stops) {
1169 int paren = 0, bracket = 0;
1170 bool expectOperand = true;
1171 while (token) {
1172 if (paren == 0 && bracket == 0 && token->getTokenType() == types::TokenType::TT_ID) {
1173 if (stops.count(lower(token->getTokenValue())))
1174 return;
1175 }
1176 if (paren == 0 && bracket == 0 && token->getTokenType() == types::TokenType::TT_SYM) {
1177 if (stops.count(token->getTokenValue()))
1178 return;
1179 }
1180
1181 if (match("(")) {
1182 ++paren;
1183 next();
1184 expectOperand = true;
1185 continue;
1186 }
1187 if (match(")")) {
1188 if (paren <= 0)
1189 break;
1190 --paren;
1191 next();
1192 expectOperand = false;
1193 continue;
1194 }
1195 if (match("[")) {
1196 ++bracket;
1197 next();
1198 expectOperand = true;
1199 continue;
1200 }
1201 if (match("]")) {
1202 if (bracket <= 0)
1203 break;
1204 --bracket;
1205 next();
1206 expectOperand = false;
1207 continue;
1208 }
1209
1210 if (expectOperand) {
1211 if (match("+") || match("-") || isKW("not") || match("@")) {
1212 next();
1213 continue;
1214 }
1216 next();
1217 expectOperand = false;
1218 continue;
1219 }
1221 const auto *at = token;
1222 std::string name = token->getTokenValue();
1223 next();
1224 if (match("(")) {
1226 expectOperand = false;
1227
1228 } else {
1229 checkVarOrConst(name, at);
1230 expectOperand = false;
1231 }
1232
1233 while (true) {
1234 if (match("^")) {
1235 next();
1236 continue;
1237 }
1238 if (match(".")) {
1239 next();
1241 next();
1242 continue;
1243 }
1244 if (match("[")) {
1245 next();
1246 parseExprStop({"]"});
1247 while (match(",")) {
1248 next();
1249 parseExprStop({"]"});
1250 }
1251 require("]");
1252 next();
1253 continue;
1254 }
1255 if (match("(")) {
1257 continue;
1258 }
1259 break;
1260 }
1261 continue;
1262 }
1263 failHere("Invalid expression");
1264 } else {
1265 if (bracket > 0 && match(",")) {
1266 next();
1267 expectOperand = true;
1268 continue;
1269 }
1270 if (isRelOp() || isAddOp() || isMulOp() || isSetOp()) {
1271 next();
1272 expectOperand = true;
1273 continue;
1274 }
1275 return;
1276 }
1277 }
1278 }
1279
1280 void TPValidator::parseConstExpr(const std::unordered_set<std::string> &stops, bool constOnly) {
1281 int paren = 0;
1282 bool expectOperand = true;
1283 while (token) {
1284 if (paren == 0) {
1285 if (token->getTokenType() == types::TokenType::TT_ID && stops.count(lower(token->getTokenValue())))
1286 return;
1287 if (token->getTokenType() == types::TokenType::TT_SYM && stops.count(token->getTokenValue()))
1288 return;
1289 }
1290 if (match("(")) {
1291 ++paren;
1292 next();
1293 expectOperand = true;
1294 continue;
1295 }
1296 if (match(")")) {
1297 if (paren <= 0)
1298 break;
1299 --paren;
1300 next();
1301 expectOperand = false;
1302 continue;
1303 }
1304 if (expectOperand) {
1305 if (match("+") || match("-") || isKW("not")) {
1306 next();
1307 continue;
1308 }
1310 next();
1311 expectOperand = false;
1312 continue;
1313 }
1315 const auto *at = token;
1316 if (constOnly)
1317 checkConstOnly(token->getTokenValue(), token);
1318 next();
1319 if (match("("))
1320 failAt(at, "Function call not allowed in constant expression");
1321 expectOperand = false;
1322 continue;
1323 }
1324 failHere("Invalid constant expression");
1325 } else {
1326 if (match("+") || match("-") || match("*") || match("/") ||
1327 isKW("div") || isKW("mod") || isKW("and") || isKW("or") || isKW("xor") ||
1328 match("<") || match("<=") || match(">") || match(">=") || match("=") || match("<>")) {
1329 next();
1330 expectOperand = true;
1331 continue;
1332 }
1333 return;
1334 }
1335 }
1336 }
1337
1338 bool TPValidator::isRelOp() const { return isKW("in") || match("=") || match("<>") || match("<") || match("<=") || match(">") || match(">="); }
1339 bool TPValidator::isAddOp() const { return match("+") || match("-") || isKW("or") || isKW("xor"); }
1340 bool TPValidator::isMulOp() const { return match("*") || match("/") || isKW("div") || isKW("mod") || isKW("and") || isKW("shl") || isKW("shr"); }
1341 bool TPValidator::isSetOp() const { return isKW("union") || isKW("exclude") || isKW("symdiff"); }
1342
1344 return isKW("integer") || isKW("real") || isKW("boolean") || isKW("char") ||
1345 isKW("byte") || isKW("word") || isKW("longint") || isKW("shortint") ||
1346 isKW("smallint") || isKW("cardinal") || isKW("string") || isKW("text") ||
1347 isKW("pointer");
1348 }
1349
1350 void TPValidator::checkType(const std::string &name, const scan::TToken *at) {
1351 auto key = lower(name);
1352 for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) {
1353 if (it->types.count(key))
1354 return;
1355 }
1356 if (!isBuiltinType()) {
1357 failAt(at, "Unknown type identifier '" + name + "'");
1358 }
1359 }
1360
1362 if (scopeStack.empty())
1363 return nullptr;
1364 return &scopeStack.back();
1365 }
1366
1368 if (scopeStack.empty())
1369 return nullptr;
1370 return &scopeStack.back();
1371 }
1372
1373 static const std::unordered_set<std::string> pascal_keywords = {
1374 "program", "unit", "interface", "implementation",
1375 "uses", "var", "const", "type", "procedure", "function", "begin", "end",
1376 "if", "then", "else", "while", "do", "for", "to", "downto", "repeat", "until",
1377 "case", "of", "with", "goto", "label", "exit", "break", "continue",
1378 "nil", "new", "dispose", "setlength", "high", "low",
1379 "writeln", "write", "readln", "read", "seed_random", "rand_number",
1380
1381 "div", "mod", "and", "or", "not", "in",
1382 "integer", "real", "boolean", "char", "byte", "word", "longint", "shortint",
1383 "smallint", "cardinal", "string", "text", "double", "single", "extended",
1384 "comp", "currency", "ptr", "pointer", "array", "record", "set",
1385 "packed", "file", "text",
1386 "assign", "reset", "rewrite", "append", "close", "eof", "eoln",
1387 "include", "exclude"};
1388
1389 bool TPValidator::isPascalKeyword(const std::string &s) const {
1390 return pascal_keywords.count(lower(s));
1391 }
1392
1393 bool TPValidator::isVarDeclaredHere(const std::string &name) const {
1394 return currentScope() && currentScope()->vars.count(lower(name));
1395 }
1396 bool TPValidator::isTypeDeclaredHere(const std::string &name) const {
1397 return currentScope() && currentScope()->types.count(lower(name));
1398 }
1399 bool TPValidator::isFuncDeclaredHere(const std::string &name) const {
1400 return currentScope() && currentScope()->funcs.count(lower(name));
1401 }
1402 bool TPValidator::isProcDeclaredHere(const std::string &name) const {
1403 return currentScope() && currentScope()->procs.count(lower(name));
1404 }
1405 bool TPValidator::isParamDeclaredHere(const std::string &name) const {
1406 return currentScope() && currentScope()->params.count(lower(name));
1407 }
1408
1409 void TPValidator::declareVar(const std::string &name, const scan::TToken *at) {
1410 std::string key = lower(name);
1411 if (isVarDeclaredHere(key) || isParamDeclaredHere(key) ||
1413 failAt(at, "Redeclaration of variable '" + name + "' in this scope");
1414 }
1415 currentScope()->vars.insert(key);
1416 }
1417
1418 void TPValidator::declareFunc(const std::string &name, const scan::TToken *at) {
1419 std::string key = lower(name);
1420 if (isFuncDeclaredHere(key) || isVarDeclaredHere(key) ||
1422 failAt(at, "Redeclaration of function '" + name + "' in this scope");
1423 }
1424 currentScope()->funcs.insert(key);
1425 }
1426
1427 void TPValidator::declareProc(const std::string &name, const scan::TToken *at) {
1428 std::string key = lower(name);
1429 if (isProcDeclaredHere(key) || isVarDeclaredHere(key) ||
1431 failAt(at, "Redeclaration of procedure '" + name + "' in this scope");
1432 }
1433 currentScope()->procs.insert(key);
1434 }
1435
1436 void TPValidator::declareParam(const std::string &name, const scan::TToken *at) {
1437 std::string key = lower(name);
1438 if (isParamDeclaredHere(key) || isVarDeclaredHere(key) ||
1440 failAt(at, "Redeclaration of parameter '" + name + "' in this scope");
1441 }
1442 currentScope()->params.insert(key);
1443 }
1444
1445 void TPValidator::declareType(const std::string &name, const scan::TToken *at) {
1446 auto key = lower(name);
1447 if (isTypeDeclaredHere(key) || isVarDeclaredHere(key) ||
1449 failAt(at, "Redeclaration of type '" + name + "' in this scope");
1450 }
1451 currentScope()->types.insert(key);
1452 }
1453
1454 void TPValidator::pushRecordFieldScope(const std::string &recordTypeName) {
1455 currentRecordTypeName = recordTypeName;
1456
1457 if (recordFieldScopesByType.find(recordTypeName) == recordFieldScopesByType.end()) {
1458 recordFieldScopesByType[recordTypeName] = std::unordered_set<std::string>();
1459 }
1460 }
1461
1465
1467 return !currentRecordTypeName.empty();
1468 }
1469
1470 void TPValidator::declareRecordField(const std::string &name, const scan::TToken *at) {
1471 if (!inRecordFieldScope()) {
1472 declareVar(name, at);
1473 return;
1474 }
1475
1476 std::string key = lower(name);
1478
1479 if (fields.count(key)) {
1480 failAt(at, "Redeclaration of field '" + name + "' in this record");
1481 }
1482
1483 fields.insert(key);
1484 }
1485
1488 declareRecordField(token->getTokenValue(), token);
1489 next();
1490 while (match(",")) {
1491 next();
1493 declareRecordField(token->getTokenValue(), token);
1494 next();
1495 }
1496 }
1497
1498} // namespace mxx
General-purpose exception with errno-aware factory method.
Definition exception.hpp:38
void declareProc(const std::string &name, const scan::TToken *at)
Declare a procedure in the current scope.
void pushRecordFieldScope(const std::string &recordTypeName)
Push a record field scope for the given type.
void parseUnit()
Validate a Pascal unit.
void parseWith()
Validate a with statement.
bool isBuiltinConst(const std::string &name) const
Check if a name is a built-in constant (true, false, maxint).
void parseVarSection()
Validate a var section.
void pushScope()
Push a new scope onto the scope stack.
int withDepth
nesting depth of with statements (suppress undeclared-id errors)
std::string currentRecordTypeName
name of record type currently being parsed
Definition validator.hpp:51
void require(const std::string &s)
Require the current token to match a string, or fail.
bool inRecordFieldScope() const
Check if currently inside a record field scope.
void declareType(const std::string &name, const scan::TToken *at)
Declare a user-defined type in the current scope.
bool isAddOp() const
Check if current token is an additive operator.
void parseExprStop(const std::unordered_set< std::string > &stops)
Validate an expression up to a set of stop tokens.
std::vector< Scope > scopeStack
stack of lexical scopes
Definition validator.hpp:52
bool isVarDeclaredHere(const std::string &name) const
Check if a variable is declared in the innermost scope.
void parseIf()
Validate an if statement.
void failHere(const std::string &msg)
Report a validation error at the current token.
void parseActualParams()
Validate actual parameter list.
bool isParamDeclaredHere(const std::string &name) const
Check if a parameter is declared in the innermost scope.
void parseDesignator()
Validate a designator (variable with selectors).
scan::Scanner scanner
underlying scanner
Definition validator.hpp:53
void parseType(const std::string &)
Validate a type specification.
void requireKW(const std::string &k)
Require the current token to be a keyword, or fail.
void parseTypeName()
Validate a type name reference.
void parseConstSection()
Validate a const section.
void parseLabelSection()
Validate a label section.
const scan::TToken * token
current token pointer
Definition validator.hpp:54
void declareRecordField(const std::string &name, const scan::TToken *at)
Declare a record field in the current record scope.
void declareFunc(const std::string &name, const scan::TToken *at)
Declare a function in the current scope.
std::unordered_set< std::string > declaredProcs
globally declared procedures
Definition validator.hpp:66
void declareParam(const std::string &name, const scan::TToken *at)
Declare a parameter in the current scope.
static std::string lower(std::string s)
Convert a string to lowercase.
bool isProcDeclaredHere(const std::string &name) const
Check if a procedure is declared in the innermost scope.
std::unordered_set< std::string > declaredFuncs
globally declared functions
Definition validator.hpp:65
void parseCaseLabel()
Validate a single case label.
void checkConstOnly(const std::string &name, const scan::TToken *at)
Assert a name is a constant (used in const expressions).
void parseCase()
Validate a case statement.
std::unordered_map< std::string, std::unordered_set< std::string > > recordFieldScopesByType
field names per record type
Definition validator.hpp:50
void parseInterfaceSection()
Validate interface section declarations.
void declareConst(const std::string &name, const scan::TToken *at)
Declare a constant in the current scope.
bool match(const std::string &s) const
Check if the current token value matches a string.
Definition validator.cpp:76
void parseTypeSection()
Validate a type section.
void declareVar(const std::string &name, const scan::TToken *at)
Declare a variable in the current scope.
std::string filename
source filename for error messages
Definition validator.hpp:56
void parseSubprogram()
Validate a procedure or function declaration.
bool isSetOp() const
Check if current token is a set operator.
void parseGoto()
Validate a goto statement.
void parseUses()
Validate a uses clause.
void parseConstExpr(const std::unordered_set< std::string > &stops, bool constOnly)
Parse and validate a constant expression.
void parseConstSimple()
Validate a simple constant (number, string, or name).
void checkVarOrConst(const std::string &name, const scan::TToken *at)
Assert a name is a variable or constant in scope.
void parseRepeat()
Validate a repeat..until statement.
void parseCaseLabelList()
Validate a case label list.
void parseFor()
Validate a for statement.
std::unordered_set< std::string > importedUnits
unit names from uses clause
Definition validator.hpp:67
void popRecordFieldScope()
Pop the current record field scope.
static std::string tokenTypeToString(types::TokenType t)
Convert a TokenType to a display string.
void parseCompoundStatement()
Validate a compound statement (begin..end).
void parseIdentList()
Validate a comma-separated identifier list.
void popScope()
Pop the current scope from the stack.
bool peekIs(const std::string &s)
Check if the current token value matches (and optionally advance).
Definition validator.cpp:98
bool isRelOp() const
Check if current token is a relational operator.
void parseWhile()
Validate a while statement.
void checkType(const std::string &name, const scan::TToken *at)
Assert a type name is declared and accessible.
size_t index
current token index
Definition validator.hpp:57
bool isPascalKeyword(const std::string &s) const
Check if a string is a Pascal reserved keyword.
Scope * currentScope()
Get the current (top) scope.
void checkVar(const std::string &name, const scan::TToken *at)
Assert a variable is declared and accessible in scope.
std::string source
original source text
Definition validator.hpp:55
bool next()
Advance to the next token.
Definition validator.cpp:84
void parseFieldIdentList()
Parse a field identifier list within a record definition.
bool isMulOp() const
Check if current token is a multiplicative operator.
void parseFormalParams()
Validate formal parameter declarations.
void fail(const std::string &msg)
Report a validation error with a generic message.
TPValidator(const std::string &source_)
Construct a validator for the given source.
Definition validator.cpp:16
std::string found() const
Return a display string for the current token value.
bool isBuiltinType() const
Check if the current token is a built-in type name.
void parseSimpleOrCallOrAssign()
Validate a simple statement, procedure call, or assignment.
bool validate(const std::string &name)
Run semantic validation.
Definition validator.cpp:18
void parseStatement()
Validate a single statement.
void parseBlock()
Validate a block (declarations + compound statement).
void parseSubrange()
Validate a subrange type.
bool isTypeDeclaredHere(const std::string &name) const
Check if a type is declared in the innermost scope.
bool isKW(const std::string &k) const
Check if current token value (lowercased) matches a keyword.
void failAt(const scan::TToken *at, const std::string &msg)
Report a validation error at a specific token.
bool isFuncDeclaredHere(const std::string &name) const
Check if a function is declared in the innermost scope.
void parseConstant()
Validate a constant value.
void parseProgram()
Validate a complete Pascal program.
Lexical scanner that tokenizes source text.
Definition scanner.hpp:28
std::vector< TToken > & getTokens()
Get the underlying token vector.
Definition scanner.hpp:62
uint64_t scan()
Tokenize the entire source buffer.
Definition scanner.cpp:62
uint64_t getLine() const
Get the source line number.
int64_t pos(const char *substr, const char *s)
Definition cstring.c:51
Exception class, hex formatting utilities, and terminal color definitions.
Definition expr.cpp:8
static bool ciEquals(const std::string &a, const std::string &b)
Case-insensitive string equality comparison.
Definition validator.cpp:67
static const std::unordered_set< std::string > pascal_keywords
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
A single scope level, tracking declared identifiers by category.
Definition validator.hpp:18
std::unordered_set< std::string > procs
procedure names
Definition validator.hpp:23
std::unordered_set< std::string > funcs
function names
Definition validator.hpp:22
std::unordered_set< std::string > types
type names
Definition validator.hpp:21
std::unordered_set< std::string > params
parameter names
Definition validator.hpp:24
std::unordered_set< std::string > vars
variable names
Definition validator.hpp:19
Semantic validator for Pascal programs — scope, type, and declaration checking.