#include "validator.hpp" #include <cctype> #include <unordered_set> namespace mxx { TPValidator::TPValidator(const std::string &source_) : scanner(source_), source(source_) {} bool TPValidator::validate(const std::string &name) { filename = name; scanner.scan(); index = 0; token = nullptr; scopeStack.clear(); declaredProcs.clear(); declaredFuncs.clear(); pushScope(); next(); parseProgram(); if (token) failHere("Unexpected tokens after end of program"); popScope(); return true; } bool TPValidator::match(const std::string &s) const { return token && token->getTokenValue() == s; } bool TPValidator::match(types::TokenType t) const { return token && token->getTokenType() == t; } bool TPValidator::next() { while (index < scanner.size() && scanner[index].getTokenType() == types::TokenType::TT_SYM && scanner[index].getTokenValue() == "\n") { ++index; } if (index < scanner.size()) { token = &scanner[index++]; return true; } token = nullptr; return false; } bool TPValidator::peekIs(const std::string &s) { return index < scanner.size() && scanner[index].getTokenValue() == s; } void TPValidator::require(const std::string &s) { if (!match(s)) { if (token) failAt(token, "Required: " + s + " Found: " + found()); fail("Required: " + s + " Found: EOF"); } } void TPValidator::require(types::TokenType t) { if (!match(t)) { if (token) failAt(token, "Required: " + tokenTypeToString(t) + " Found: " + found()); fail("Required: " + tokenTypeToString(t) + " Found: EOF"); } } void TPValidator::requireKW(const std::string &k) { if (!isKW(k)) { if (token) failAt(token, "Required: " + k + " Found: " + found()); fail("Required: " + k + " Found: EOF"); } } void TPValidator::fail(const std::string &msg) { throw mx::Exception("Syntax Error in '" + filename + "': " + msg); } void TPValidator::failHere(const std::string &msg) { if (token) throw mx::Exception("Syntax Error in '" + filename + "': " + msg + " at line " + std::to_string(token->getLine())); throw mx::Exception("Syntax Error in '" + filename + "': " + msg); } void TPValidator::failAt(const scan::TToken *at, const std::string &msg) { if (at) throw mx::Exception("Syntax Error in '" + filename + "': " + msg + " at line " + std::to_string(at->getLine())); throw mx::Exception("Syntax Error in '" + filename + "': " + msg); } std::string TPValidator::tokenTypeToString(types::TokenType t) { switch (t) { case types::TokenType::TT_ID: return "Identifier"; case types::TokenType::TT_NUM: return "Number"; case types::TokenType::TT_HEX: return "Hex"; case types::TokenType::TT_STR: return "String"; case types::TokenType::TT_SYM: return "Symbol"; default: return "Unknown"; } } std::string TPValidator::lower(std::string s) { for (auto &c : s) c = (char)std::tolower((unsigned char)c); return s; } std::string TPValidator::found() const { if (!token) return "EOF"; return token->getTokenValue() + ":" + tokenTypeToString(token->getTokenType()); } bool TPValidator::isKW(const std::string &k) const { if (!token || !isPascalKeyword(k) || token->getTokenType() != types::TokenType::TT_ID) return false; return lower(token->getTokenValue()) == k; } void TPValidator::pushScope() { scopeStack.emplace_back(); } void TPValidator::popScope() { if (!scopeStack.empty()) { scopeStack.pop_back(); } } void TPValidator::declareConst(const std::string &name, const scan::TToken *at) { auto key = lower(name); if (scopeStack.back().consts.count(key)) { failAt(at, "Redeclaration of constant '" + name + "' in the same scope"); } scopeStack.back().consts.insert(key); } bool TPValidator::isBuiltinConst(const std::string &name) const { std::string key = lower(name); return key == "true" || key == "false" || key == "nil"; } void TPValidator::checkVar(const std::string &name, const scan::TToken *at) { auto key = lower(name); for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) { if (it->vars.count(key)) return; } if (!declaredFuncs.count(key) && !declaredProcs.count(key) && !isBuiltinConst(name) && !isPascalKeyword(key)) { failAt(at, "Use of undeclared identifier '" + name + "'"); } } void TPValidator::checkVarOrConst(const std::string &name, const scan::TToken *at) { auto key = lower(name); for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) { if (it->vars.count(key) || it->consts.count(key)) return; } if (!declaredFuncs.count(key) && !declaredProcs.count(key) && !isBuiltinConst(name) && !isPascalKeyword(key)) { failAt(at, "Use of undeclared identifier '" + name + "'"); } } void TPValidator::checkConstOnly(const std::string &name, const scan::TToken *at) { auto key = lower(name); for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) { if (it->consts.count(key)) return; } if (!isBuiltinConst(name)) { failAt(at, "Constant expression requires constant '" + name + "'"); } } void TPValidator::parseProgram() { requireKW("program"); next(); require(types::TokenType::TT_ID); next(); require(";"); next(); if (isKW("uses")) parseUses(); parseBlock(); require("."); next(); } void TPValidator::parseUses() { requireKW("uses"); do { next(); require(types::TokenType::TT_ID); next(); if (match(",")) next(); else break; } while (true); require(";"); next(); } void TPValidator::parseBlock() { if (isKW("label")) parseLabelSection(); while (isKW("const") || isKW("type") || isKW("var") || isKW("procedure") || isKW("function")) { if (isKW("const")) parseConstSection(); else if (isKW("type")) parseTypeSection(); else if (isKW("var")) parseVarSection(); else if (isKW("procedure") || isKW("function")) parseSubprogram(); } parseCompoundStatement(); } void TPValidator::parseLabelSection() { requireKW("label"); do { next(); require(types::TokenType::TT_NUM); next(); if (match(",")) next(); else break; } while (true); require(";"); next(); } void TPValidator::parseConstSection() { requireKW("const"); next(); while (match(types::TokenType::TT_ID)) { if (isKW("type") || isKW("var") || isKW("label") || isKW("begin") || isKW("procedure") || isKW("function")) { break; } const auto *nameTok = token; std::string name = token->getTokenValue(); next(); require("="); next(); parseConstExpr({";"}, true); declareConst(name, nameTok); require(";"); next(); } } void TPValidator::parseTypeSection() { requireKW("type"); next(); while (match(types::TokenType::TT_ID)) { if (isKW("procedure") || isKW("function") || isKW("var") || isKW("begin") || isKW("const")) { break; } const auto *nameTok = token; std::string name = token->getTokenValue(); next(); require("="); next(); parseType(name); declareType(name, nameTok); require(";"); next(); } } void TPValidator::parseVarSection() { requireKW("var"); next(); while (match(types::TokenType::TT_ID)) { if (isKW("procedure") || isKW("function") || isKW("begin") || isKW("type") || isKW("const")) { break; } declareVar(token->getTokenValue(), token); next(); while (match(",")) { next(); require(types::TokenType::TT_ID); declareVar(token->getTokenValue(), token); next(); } require(":"); next(); parseType(""); if (match("=")) { next(); parseConstExpr({";"}, false); } require(";"); next(); } } void TPValidator::parseSubprogram() { bool isProc = isKW("procedure"); if (isProc) { requireKW("procedure"); next(); require(types::TokenType::TT_ID); declaredProcs.insert(lower(token->getTokenValue())); next(); } else { requireKW("function"); next(); require(types::TokenType::TT_ID); declaredFuncs.insert(lower(token->getTokenValue())); next(); } pushScope(); if (match("(")) parseFormalParams(); if (isProc) { require(";"); next(); } else { require(":"); next(); parseTypeName(); require(";"); next(); } if (isKW("forward")) { next(); require(";"); next(); popScope(); return; } parseBlock(); require(";"); next(); popScope(); } void TPValidator::parseFormalParams() { require("("); next(); if (match(")")) { next(); return; } while (true) { bool byRef = isKW("var"); if (byRef) next(); parseIdentList(); require(":"); next(); parseType(""); if (match(";")) { next(); continue; } require(")"); next(); break; } } void TPValidator::parseIdentList() { require(types::TokenType::TT_ID); declareVar(token->getTokenValue(), token); next(); while (match(",")) { next(); require(types::TokenType::TT_ID); declareVar(token->getTokenValue(), token); next(); } } void TPValidator::parseType(const std::string &typeName) { if (isKW("array")) { next(); require("["); next(); parseSubrange(); while (match(",")) { next(); parseSubrange(); } require("]"); next(); requireKW("of"); next(); parseType(""); return; } if (isKW("record")) { next(); pushRecordFieldScope(typeName); while (!isKW("end")) { parseFieldIdentList(); require(":"); next(); parseType(""); require(";"); next(); } requireKW("end"); next(); popRecordFieldScope(); return; } if (isKW("set")) { next(); requireKW("of"); next(); parseTypeName(); return; } if (match("^")) { next(); parseTypeName(); return; } if (isKW("string")) { next(); if (match("[")) { next(); parseConstExpr({"]"}, true); require("]"); next(); } return; } if (isBuiltinType()) { next(); return; } parseTypeName(); } void TPValidator::parseTypeName() { require(types::TokenType::TT_ID); std::string name = token->getTokenValue(); checkType(name, token); next(); } void TPValidator::parseSubrange() { parseConstExpr({"..", "]", ","}, true); if (match("..")) { next(); parseConstExpr({"]", ","}, true); } } void TPValidator::parseConstant() { if (match("-")) { next(); parseConstSimple(); return; } parseConstSimple(); } void TPValidator::parseConstSimple() { if (match(types::TokenType::TT_NUM) || match(types::TokenType::TT_HEX) || match(types::TokenType::TT_STR)) { next(); return; } if (isKW("true") || isKW("false") || isKW("nil")) { next(); return; } require(types::TokenType::TT_ID); checkConstOnly(token->getTokenValue(), token); next(); } void TPValidator::parseCompoundStatement() { requireKW("begin"); next(); if (isKW("end")) { requireKW("end"); next(); return; } while (!isKW("end")) { if (token == nullptr) { fail("Unexpected end of file inside compound statement"); } parseStatement(); if (match(";")) { next(); if (isKW("end")) { break; } } else if (isKW("end")) { break; } else { failHere("Expected ';' or 'end'"); } } requireKW("end"); next(); } void TPValidator::parseStatement() { if (!token) failHere("Unexpected EOF in statement"); if (isKW("begin")) { parseCompoundStatement(); return; } if (isKW("if")) { parseIf(); return; } if (isKW("while")) { parseWhile(); return; } if (isKW("repeat")) { parseRepeat(); return; } if (isKW("for")) { parseFor(); return; } if (isKW("case")) { parseCase(); return; } if (isKW("with")) { parseWith(); return; } if (isKW("goto")) { parseGoto(); return; } if (match(types::TokenType::TT_NUM)) failHere("Statement cannot start with number"); if (match(types::TokenType::TT_ID)) { parseSimpleOrCallOrAssign(); return; } if (match(";") || match(")")) return; if (isKW("end")) return; failHere("Invalid statement start"); } void TPValidator::parseIf() { requireKW("if"); next(); parseExprStop({"then"}); requireKW("then"); next(); parseStatement(); if (isKW("else")) { next(); parseStatement(); } } void TPValidator::parseWhile() { requireKW("while"); next(); parseExprStop({"do"}); requireKW("do"); next(); parseStatement(); } void TPValidator::parseRepeat() { requireKW("repeat"); next(); while (true) { if (isKW("until")) { break; } parseStatement(); if (match(";")) { next(); if (isKW("until")) { break; } } else if (isKW("until")) { break; } else { failHere("Expected ';' or 'until'"); } } requireKW("until"); next(); parseExprStop({";", "end"}); } void TPValidator::parseFor() { requireKW("for"); next(); require(types::TokenType::TT_ID); checkVar(token->getTokenValue(), token); next(); require(":="); next(); parseExprStop({"to", "downto"}); if (isKW("to")) next(); else { requireKW("downto"); next(); } parseExprStop({"do"}); requireKW("do"); next(); parseStatement(); } void TPValidator::parseCase() { requireKW("case"); next(); parseExprStop({"of"}); requireKW("of"); next(); while (!isKW("end")) { parseCaseLabelList(); require(":"); next(); parseStatement(); if (match(";")) next(); if (isKW("else")) { next(); parseStatement(); if (match(";")) next(); break; } if (isKW("end")) break; } requireKW("end"); next(); } void TPValidator::parseCaseLabelList() { parseCaseLabel(); while (match(",")) { next(); parseCaseLabel(); } } void TPValidator::parseCaseLabel() { if (match("-")) next(); if (match(types::TokenType::TT_NUM) || match(types::TokenType::TT_STR) || match(types::TokenType::TT_ID)) { const auto *at = token; next(); if (match("..")) { next(); if (match("-")) next(); if (match(types::TokenType::TT_NUM) || match(types::TokenType::TT_STR) || match(types::TokenType::TT_ID)) { next(); return; } failAt(at, "Invalid case label range"); } return; } failHere("Invalid case label"); } void TPValidator::parseWith() { requireKW("with"); next(); parseDesignator(); while (match(",")) { next(); parseDesignator(); } requireKW("do"); next(); parseStatement(); } void TPValidator::parseGoto() { requireKW("goto"); next(); require(types::TokenType::TT_NUM); next(); } void TPValidator::parseSimpleOrCallOrAssign() { if (match(types::TokenType::TT_ID) && peekIs("(")) { next(); parseActualParams(); return; } parseDesignator(); if (match(":=")) { next(); parseExprStop({";", "end", "else", ")", "]", "until", "of", "do", "then"}); return; } if (match("(")) { parseActualParams(); return; } } void TPValidator::parseDesignator() { require(types::TokenType::TT_ID); checkVar(token->getTokenValue(), token); next(); while (true) { if (match(".")) { next(); require(types::TokenType::TT_ID); next(); continue; } if (match("[")) { next(); parseExprStop({"]"}); while (match(",")) { next(); parseExprStop({"]"}); } require("]"); next(); continue; } if (match("^")) { next(); continue; } break; } } void TPValidator::parseActualParams() { require("("); next(); if (match(")")) { next(); return; } while (true) { if (isKW("var")) next(); parseExprStop({")", ";", ","}); if (match(",")) { next(); continue; } require(")"); next(); break; } } void TPValidator::parseExprStop(const std::unordered_set<std::string> &stops) { int paren = 0, bracket = 0; bool expectOperand = true; while (token) { if (paren == 0 && bracket == 0 && token->getTokenType() == types::TokenType::TT_ID) { if (stops.count(lower(token->getTokenValue()))) return; } if (paren == 0 && bracket == 0 && token->getTokenType() == types::TokenType::TT_SYM) { if (stops.count(token->getTokenValue())) return; } if (match("(")) { ++paren; next(); expectOperand = true; continue; } if (match(")")) { if (paren <= 0) break; --paren; next(); expectOperand = false; continue; } if (match("[")) { ++bracket; next(); expectOperand = true; continue; } if (match("]")) { if (bracket <= 0) break; --bracket; next(); expectOperand = false; continue; } if (expectOperand) { if (match("+") || match("-") || isKW("not") || match("@")) { next(); continue; } if (match(types::TokenType::TT_NUM) || match(types::TokenType::TT_HEX) || match(types::TokenType::TT_STR)) { next(); expectOperand = false; continue; } if (match(types::TokenType::TT_ID)) { const auto *at = token; std::string name = token->getTokenValue(); next(); if (match("(")) { parseActualParams(); expectOperand = false; } else { checkVarOrConst(name, at); expectOperand = false; } while (true) { if (match("^")) { next(); continue; } if (match(".")) { next(); require(types::TokenType::TT_ID); next(); continue; } if (match("[")) { next(); parseExprStop({"]"}); while (match(",")) { next(); parseExprStop({"]"}); } require("]"); next(); continue; } break; } continue; } failHere("Invalid expression"); } else { if (isRelOp() || isAddOp() || isMulOp() || isSetOp()) { next(); expectOperand = true; continue; } return; } } } void TPValidator::parseConstExpr(const std::unordered_set<std::string> &stops, bool constOnly) { int paren = 0; bool expectOperand = true; while (token) { if (paren == 0) { if (token->getTokenType() == types::TokenType::TT_ID && stops.count(lower(token->getTokenValue()))) return; if (token->getTokenType() == types::TokenType::TT_SYM && stops.count(token->getTokenValue())) return; } if (match("(")) { ++paren; next(); expectOperand = true; continue; } if (match(")")) { if (paren <= 0) break; --paren; next(); expectOperand = false; continue; } if (expectOperand) { if (match("+") || match("-") || isKW("not")) { next(); continue; } if (match(types::TokenType::TT_NUM) || match(types::TokenType::TT_HEX) || match(types::TokenType::TT_STR)) { next(); expectOperand = false; continue; } if (match(types::TokenType::TT_ID)) { const auto *at = token; if (constOnly) checkConstOnly(token->getTokenValue(), token); next(); if (match("(")) failAt(at, "Function call not allowed in constant expression"); expectOperand = false; continue; } failHere("Invalid constant expression"); } else { if (match("+") || match("-") || match("*") || match("/") || isKW("div") || isKW("mod") || isKW("and") || isKW("or") || isKW("xor") || match("<") || match("<=") || match(">") || match(">=") || match("=") || match("<>")) { next(); expectOperand = true; continue; } return; } } } bool TPValidator::isRelOp() const { return isKW("in") || match("=") || match("<>") || match("<") || match("<=") || match(">") || match(">="); } bool TPValidator::isAddOp() const { return match("+") || match("-") || isKW("or") || isKW("xor"); } bool TPValidator::isMulOp() const { return match("*") || match("/") || isKW("div") || isKW("mod") || isKW("and") || isKW("shl") || isKW("shr"); } bool TPValidator::isSetOp() const { return isKW("union") || isKW("exclude") || isKW("symdiff"); } bool TPValidator::isBuiltinType() const { return isKW("integer") || isKW("real") || isKW("boolean") || isKW("char") || isKW("byte") || isKW("word") || isKW("longint") || isKW("shortint") || isKW("smallint") || isKW("cardinal") || isKW("string") || isKW("text"); } void TPValidator::checkType(const std::string &name, const scan::TToken *at) { auto key = lower(name); for (auto it = scopeStack.rbegin(); it != scopeStack.rend(); ++it) { if (it->types.count(key)) return; } if (!isBuiltinType()) { failAt(at, "Unknown type identifier '" + name + "'"); } } mxx::Scope *TPValidator::currentScope() { if (scopeStack.empty()) return nullptr; return &scopeStack.back(); } const mxx::Scope *TPValidator::currentScope() const { if (scopeStack.empty()) return nullptr; return &scopeStack.back(); } static const std::unordered_set<std::string> pascal_keywords = { "program", "var", "const", "type", "procedure", "function", "begin", "end", "if", "then", "else", "while", "do", "for", "to", "downto", "repeat", "until", "case", "of", "with", "goto", "exit", "break", "continue", "div", "mod", "and", "or", "not", "in", "integer", "real", "boolean", "char", "byte", "word", "longint", "shortint", "smallint", "cardinal", "string", "text", "double", "single", "extended", "comp", "currency", "ptr", "array", "record", "set"}; bool TPValidator::isPascalKeyword(const std::string &s) const { return pascal_keywords.count(lower(s)); } bool TPValidator::isVarDeclaredHere(const std::string &name) const { return currentScope() && currentScope()->vars.count(lower(name)); } bool TPValidator::isTypeDeclaredHere(const std::string &name) const { return currentScope() && currentScope()->types.count(lower(name)); } bool TPValidator::isFuncDeclaredHere(const std::string &name) const { return currentScope() && currentScope()->funcs.count(lower(name)); } bool TPValidator::isProcDeclaredHere(const std::string &name) const { return currentScope() && currentScope()->procs.count(lower(name)); } bool TPValidator::isParamDeclaredHere(const std::string &name) const { return currentScope() && currentScope()->params.count(lower(name)); } void TPValidator::declareVar(const std::string &name, const scan::TToken *at) { std::string key = lower(name); if (isVarDeclaredHere(key) || isParamDeclaredHere(key) || isTypeDeclaredHere(key) || isFuncDeclaredHere(key) || isProcDeclaredHere(key)) { failAt(at, "Redeclaration of variable '" + name + "' in this scope"); } currentScope()->vars.insert(key); } void TPValidator::declareFunc(const std::string &name, const scan::TToken *at) { std::string key = lower(name); if (isFuncDeclaredHere(key) || isVarDeclaredHere(key) || isTypeDeclaredHere(key) || isProcDeclaredHere(key) || isParamDeclaredHere(key)) { failAt(at, "Redeclaration of function '" + name + "' in this scope"); } currentScope()->funcs.insert(key); } void TPValidator::declareProc(const std::string &name, const scan::TToken *at) { std::string key = lower(name); if (isProcDeclaredHere(key) || isVarDeclaredHere(key) || isTypeDeclaredHere(key) || isFuncDeclaredHere(key) || isParamDeclaredHere(key)) { failAt(at, "Redeclaration of procedure '" + name + "' in this scope"); } currentScope()->procs.insert(key); } void TPValidator::declareParam(const std::string &name, const scan::TToken *at) { std::string key = lower(name); if (isParamDeclaredHere(key) || isVarDeclaredHere(key) || isTypeDeclaredHere(key) || isFuncDeclaredHere(key) || isProcDeclaredHere(key)) { failAt(at, "Redeclaration of parameter '" + name + "' in this scope"); } currentScope()->params.insert(key); } void TPValidator::declareType(const std::string &name, const scan::TToken *at) { auto key = lower(name); if (isTypeDeclaredHere(key) || isVarDeclaredHere(key) || isFuncDeclaredHere(key) || isProcDeclaredHere(key) || isParamDeclaredHere(key)) { failAt(at, "Redeclaration of type '" + name + "' in this scope"); } currentScope()->types.insert(key); } void TPValidator::pushRecordFieldScope(const std::string &recordTypeName) { currentRecordTypeName = recordTypeName; if (recordFieldScopesByType.find(recordTypeName) == recordFieldScopesByType.end()) { recordFieldScopesByType[recordTypeName] = std::unordered_set<std::string>(); } } void TPValidator::popRecordFieldScope() { currentRecordTypeName.clear(); } bool TPValidator::inRecordFieldScope() const { return !currentRecordTypeName.empty(); } void TPValidator::declareRecordField(const std::string &name, const scan::TToken *at) { if (!inRecordFieldScope()) { declareVar(name, at); return; } std::string key = lower(name); auto &fields = recordFieldScopesByType[currentRecordTypeName]; if (fields.count(key)) { failAt(at, "Redeclaration of field '" + name + "' in this record"); } fields.insert(key); } void TPValidator::parseFieldIdentList() { require(types::TokenType::TT_ID); declareRecordField(token->getTokenValue(), token); next(); while (match(",")) { next(); require(types::TokenType::TT_ID); declareRecordField(token->getTokenValue(), token); next(); } } } // namespace mxx