12#include <unordered_set>
23 auto &toks =
scanner.getTokens();
24 for (
size_t i = 0; i < toks.size();) {
25 if (toks[i].getTokenValue() ==
"{") {
28 while (i < toks.size() && toks[i].getTokenValue() !=
"}") {
31 if (i < toks.size()) {
34 toks.erase(toks.begin() +
static_cast<int64_t
>(start),
35 toks.begin() +
static_cast<int64_t
>(i));
56 failHere(
"Unexpected tokens after end of program");
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])))
106 fail(
"Required: " + s +
" Found: EOF");
122 fail(
"Required: " + k +
" Found: EOF");
161 c = (char)std::tolower((
unsigned char)c);
188 auto key =
lower(name);
190 failAt(at,
"Redeclaration of constant '" + name +
"' in the same scope");
196 std::string key =
lower(name);
197 return key ==
"true" || key ==
"false" || key ==
"nil";
201 auto key =
lower(name);
207 if (it->vars.count(key))
215 failAt(at,
"Use of undeclared identifier '" + name +
"'");
220 auto key =
lower(name);
226 if (it->vars.count(key) || it->consts.count(key))
234 failAt(at,
"Use of undeclared identifier '" + name +
"'");
239 auto key =
lower(name);
241 if (it->consts.count(key))
245 failAt(at,
"Constant expression requires constant '" + name +
"'");
288 else if (
isKW(
"type"))
290 else if (
isKW(
"var"))
292 else if (
isKW(
"procedure") ||
isKW(
"function"))
304 if (
isKW(
"procedure")) {
315 }
else if (
isKW(
"function")) {
329 }
else if (
isKW(
"type")) {
331 }
else if (
isKW(
"const")) {
333 }
else if (
isKW(
"var")) {
343 std::vector<std::string> usedUnits;
344 usedUnits.push_back(
token->getTokenValue());
349 usedUnits.push_back(
token->getTokenValue());
356 static const std::unordered_set<std::string> nativeModules = {
"io",
"std",
"string",
"sdl",
"strlib"};
357 std::string inputDir;
360 inputDir = (
pos != std::string::npos) ?
filename.substr(0,
pos + 1) :
"./";
362 for (
const auto &unit : usedUnits) {
364 if (nativeModules.count(unit))
367 std::string unitFile = inputDir + unit +
".pas";
368 std::ifstream uf(unitFile);
370 std::string lowerUnit = unit;
371 std::transform(lowerUnit.begin(), lowerUnit.end(), lowerUnit.begin(), ::tolower);
372 unitFile = inputDir + lowerUnit +
".pas";
377 std::ostringstream buf;
386 for (
size_t i = 0; i < toks.size();) {
387 if (toks[i].getTokenValue() ==
"{") {
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));
401 auto tlower = [](
const std::string &s) {
403 std::transform(r.begin(), r.end(), r.begin(), ::tolower);
407 auto skipNL = [&]() {
409 && toks[ti].getTokenValue() ==
"\n") ++ti;
412 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) !=
"interface") ++ti;
413 if (ti < toks.size()) ++ti;
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;
422 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) !=
"implementation") {
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()) {
429 while (ti < toks.size() && toks[ti].getTokenValue() !=
";") ++ti;
430 if (ti < toks.size()) ++ti;
432 }
else if (kw ==
"function" && ti + 1 < toks.size()) {
435 while (ti < toks.size() && toks[ti].getTokenValue() !=
";") ++ti;
436 if (ti < toks.size()) ++ti;
438 }
else if (kw ==
"const") {
441 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) !=
"implementation"
443 && tlower(toks[ti].getTokenValue()) !=
"var"
444 && tlower(toks[ti].getTokenValue()) !=
"type"
445 && tlower(toks[ti].getTokenValue()) !=
"procedure"
446 && tlower(toks[ti].getTokenValue()) !=
"function") {
449 while (ti < toks.size() && toks[ti].getTokenValue() !=
";") ++ti;
450 if (ti < toks.size()) ++ti;
453 }
else if (kw ==
"var") {
456 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) !=
"implementation"
458 && tlower(toks[ti].getTokenValue()) !=
"const"
459 && tlower(toks[ti].getTokenValue()) !=
"type"
460 && tlower(toks[ti].getTokenValue()) !=
"procedure"
461 && tlower(toks[ti].getTokenValue()) !=
"function") {
464 while (ti < toks.size() && toks[ti].getTokenValue() ==
",") {
466 if (ti < toks.size()) {
472 while (ti < toks.size() && toks[ti].getTokenValue() !=
";") ++ti;
473 if (ti < toks.size()) ++ti;
476 }
else if (kw ==
"type") {
479 while (ti < toks.size() && tlower(toks[ti].getTokenValue()) !=
"implementation"
481 && tlower(toks[ti].getTokenValue()) !=
"const"
482 && tlower(toks[ti].getTokenValue()) !=
"var"
483 && tlower(toks[ti].getTokenValue()) !=
"procedure"
484 && tlower(toks[ti].getTokenValue()) !=
"function") {
487 while (ti < toks.size() && toks[ti].getTokenValue() !=
";") ++ti;
488 if (ti < toks.size()) ++ti;
507 else if (
isKW(
"const"))
509 else if (
isKW(
"type"))
511 else if (
isKW(
"var"))
513 else if (
isKW(
"procedure") ||
isKW(
"function"))
538 isKW(
"begin") ||
isKW(
"procedure") ||
isKW(
"function") ||
539 isKW(
"implementation") ||
isKW(
"end") ||
isKW(
"interface")) {
542 const auto *nameTok =
token;
543 std::string name =
token->getTokenValue();
560 isKW(
"implementation") ||
isKW(
"end") ||
isKW(
"interface")) {
564 const auto *nameTok =
token;
565 std::string name =
token->getTokenValue();
582 if (
isKW(
"procedure") ||
isKW(
"function") ||
isKW(
"begin") ||
584 isKW(
"implementation") ||
isKW(
"end") ||
isKW(
"interface")) {
607 bool isProc =
isKW(
"procedure");
638 if (
isKW(
"forward")) {
664 bool byRef =
isKW(
"var");
694 if (
isKW(
"packed")) {
720 if (
isKW(
"record")) {
723 while (!
isKW(
"end") && !
isKW(
"case")) {
740 while (!
isKW(
"end")) {
750 while (!
match(
")")) {
807 if (
isKW(
"string")) {
826 std::string name =
token->getTokenValue();
872 while (!
isKW(
"end")) {
873 if (
token ==
nullptr) {
874 fail(
"Unexpected end of file inside compound statement");
885 }
else if (
isKW(
"end")) {
900 failHere(
"Unexpected EOF in statement");
913 if (
isKW(
"repeat")) {
941 failHere(
"Statement cannot start with number (expected ':' for label)");
951 failHere(
"Invalid statement start");
989 }
else if (
isKW(
"until")) {
992 failHere(
"Expected ';' or 'until'");
1027 while (!
isKW(
"end")) {
1050 while (
match(
",")) {
1060 const auto *at =
token;
1070 failAt(at,
"Invalid case label range");
1081 while (
match(
",")) {
1108 parseExprStop({
";",
"end",
"else",
")",
"]",
"until",
"of",
"do",
"then"});
1131 while (
match(
",")) {
1169 int paren = 0, bracket = 0;
1170 bool expectOperand =
true;
1173 if (stops.count(
lower(
token->getTokenValue())))
1177 if (stops.count(
token->getTokenValue()))
1184 expectOperand =
true;
1192 expectOperand =
false;
1198 expectOperand =
true;
1206 expectOperand =
false;
1210 if (expectOperand) {
1217 expectOperand =
false;
1221 const auto *at =
token;
1222 std::string name =
token->getTokenValue();
1226 expectOperand =
false;
1230 expectOperand =
false;
1247 while (
match(
",")) {
1265 if (bracket > 0 &&
match(
",")) {
1267 expectOperand =
true;
1272 expectOperand =
true;
1282 bool expectOperand =
true;
1293 expectOperand =
true;
1301 expectOperand =
false;
1304 if (expectOperand) {
1311 expectOperand =
false;
1315 const auto *at =
token;
1320 failAt(at,
"Function call not allowed in constant expression");
1321 expectOperand =
false;
1324 failHere(
"Invalid constant expression");
1330 expectOperand =
true;
1344 return isKW(
"integer") ||
isKW(
"real") ||
isKW(
"boolean") ||
isKW(
"char") ||
1351 auto key =
lower(name);
1353 if (it->types.count(key))
1357 failAt(at,
"Unknown type identifier '" + name +
"'");
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",
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"};
1410 std::string key =
lower(name);
1413 failAt(at,
"Redeclaration of variable '" + name +
"' in this scope");
1419 std::string key =
lower(name);
1422 failAt(at,
"Redeclaration of function '" + name +
"' in this scope");
1428 std::string key =
lower(name);
1431 failAt(at,
"Redeclaration of procedure '" + name +
"' in this scope");
1437 std::string key =
lower(name);
1440 failAt(at,
"Redeclaration of parameter '" + name +
"' in this scope");
1446 auto key =
lower(name);
1449 failAt(at,
"Redeclaration of type '" + name +
"' in this scope");
1476 std::string key =
lower(name);
1479 if (fields.count(key)) {
1480 failAt(at,
"Redeclaration of field '" + name +
"' in this record");
1490 while (
match(
",")) {
General-purpose exception with errno-aware factory method.
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
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
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
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
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
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
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
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.
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
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
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).
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
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
bool next()
Advance to the next token.
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.
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.
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.
std::vector< TToken > & getTokens()
Get the underlying token vector.
uint64_t scan()
Tokenize the entire source buffer.
uint64_t getLine() const
Get the source line number.
int64_t pos(const char *substr, const char *s)
Exception class, hex formatting utilities, and terminal color definitions.
static bool ciEquals(const std::string &a, const std::string &b)
Case-insensitive string equality comparison.
static const std::unordered_set< std::string > pascal_keywords
token::Token< char > TToken
Default token type.
TokenType
Classification of scanned tokens.
@ TT_SYM
symbol / operator token
@ TT_HEX
hexadecimal numeric literal
@ TT_ID
identifier or keyword
@ TT_NUM
decimal numeric literal
A single scope level, tracking declared identifiers by category.
std::unordered_set< std::string > procs
procedure names
std::unordered_set< std::string > funcs
function names
std::unordered_set< std::string > types
type names
std::unordered_set< std::string > params
parameter names
std::unordered_set< std::string > vars
variable names
Semantic validator for Pascal programs — scope, type, and declaration checking.