From cb3065c34756a70cb6006fc25777ce3e720ff1a8 Mon Sep 17 00:00:00 2001
From: Ferenc Szontágh <szf@fsociety.hu>
Date: Sun, 13 Apr 2025 18:16:19 +0000
Subject: [PATCH] implement variable contexts, add function body store and parsing
---
cli/main.cpp | 2
src/ScriptExceptionMacros.h | 3
src/ScriptInterpreterHelpers.hpp | 59 ++++---
src/Lexer.hpp | 13 +
src/Lexer.cpp | 164 ++++++++++++++---------
src/ScriptInterpreter.cpp | 106 ++++++++-------
CMakeLists.txt | 1
.clangd | 2
src/ScriptInterpreter.hpp | 55 +++++++
9 files changed, 260 insertions(+), 145 deletions(-)
diff --git a/.clangd b/.clangd
new file mode 100644
index 0000000..d96eb21
--- /dev/null
+++ b/.clangd
@@ -0,0 +1,2 @@
+CompileFlags:
+ Add: [-Wunused]
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d6d4bf2..a5af0a9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -57,6 +57,7 @@
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(DEBUG_BUILD ON)
+ set(COMPILE_WARNING_AS_ERROR ON)
endif()
configure_file("cmake/options.h.in" "include/options.h" @ONLY)
diff --git a/cli/main.cpp b/cli/main.cpp
index 1ec1928..cf15e33 100644
--- a/cli/main.cpp
+++ b/cli/main.cpp
@@ -53,7 +53,7 @@
ScriptInterpreter interp;
interp.registerModule("print", std::make_shared<PrintFunction>());
interp.registerModule("sleep", std::make_shared<SleepFunction>());
- interp.executeScript(content, filename, DEBUG);
+ interp.executeScript(content, filename, "_default_", false);
} catch (const std::exception & e) {
std::cerr << "Parser error: " << e.what() << "\n";
return 1;
diff --git a/src/Lexer.cpp b/src/Lexer.cpp
index c7067d7..6d003a3 100644
--- a/src/Lexer.cpp
+++ b/src/Lexer.cpp
@@ -35,31 +35,62 @@
return pos >= src.size();
}
-Token Lexer::string() {
+Token Lexer::createToken(TokenType type, const std::string & lexeme) const {
+ size_t startChar = charNumber - lexeme.length();
+ return {
+ type, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber }
+ };
+}
+
+Token Lexer::createSingleCharToken(TokenType type, const std::string & lexeme) {
+ size_t startCol = colNumber;
+ size_t startChar = charNumber;
+ advance();
+ return {
+ type, lexeme, filename, lineNumber, startCol, { startChar, charNumber }
+ };
+}
+
+Token Lexer::createUnknownToken(const std::string & lexeme) const {
+ size_t startChar = charNumber - lexeme.length();
+ return {
+ TokenType::Unknown, lexeme, filename, lineNumber, colNumber - lexeme.length(), { startChar, charNumber }
+ };
+}
+
+Token Lexer::stringToken() {
std::string result;
- size_t startCol = colNumber;
+ size_t startChar = charNumber;
+ size_t startCol = colNumber;
advance(); // Skip opening quote
while (!isAtEnd() && peek() != '"') {
result += advance();
}
if (isAtEnd() || peek() != '"') {
- return { TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "Unterminated string", filename, lineNumber, startCol, { startChar, pos }
+ };
}
advance(); // Skip closing quote
- return { TokenType::StringLiteral, result, filename, lineNumber, startCol };
+ return {
+ TokenType::StringLiteral, result, filename, lineNumber, startCol, { startChar, pos }
+ };
}
-Token Lexer::number() {
+Token Lexer::numberToken() {
std::string result;
std::string found;
TokenType type = TokenType::Unknown;
bool decimalPointSeen = false;
+ size_t startChar = charNumber;
size_t startCol = colNumber;
while (std::isdigit(peek()) || peek() == '.') {
if (peek() == '.') {
if (decimalPointSeen) {
- return { TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "Invalid number format", filename, lineNumber, startCol, { startChar, pos }
+ };
}
decimalPointSeen = true;
}
@@ -72,34 +103,46 @@
result = found;
type = TokenType::IntLiteral;
} else {
- return { TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "Invalid integer", filename, lineNumber, startCol, { startChar, pos }
+ };
}
} else {
if (is_number<double>(found)) {
result = found;
type = TokenType::DoubleLiteral;
} else {
- return { TokenType::Unknown, "Invalid double", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "Invalid double", filename, lineNumber, startCol, { startChar, pos }
+ };
}
}
} else {
- return { TokenType::Unknown, "Expected number", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "Expected number", filename, lineNumber, startCol, { startChar, pos }
+ };
}
- return { type, result, filename, lineNumber, startCol };
+ return {
+ type, result, filename, lineNumber, startCol, { startChar, pos }
+ };
}
-Token Lexer::identifier() {
+Token Lexer::identifierToken() {
std::string result;
- size_t startCol = colNumber;
+ size_t startChar = charNumber;
+ size_t startCol = colNumber;
while (isalnum(peek()) || peek() == '_') {
result += advance();
}
- return { TokenType::Identifier, result, filename, lineNumber, startCol };
+ return {
+ TokenType::Identifier, result, filename, lineNumber, startCol, { startChar, pos }
+ };
}
-Token Lexer::variable() {
- size_t startCol = colNumber;
+Token Lexer::variableToken() {
+ size_t startChar = charNumber;
+ size_t startCol = colNumber;
advance(); // Skip $
std::string varName;
if (isalpha(peek()) || peek() == '_') {
@@ -107,24 +150,30 @@
while (isalnum(peek()) || peek() == '_') {
varName += advance();
}
- return { TokenType::Variable, varName, filename, lineNumber, startCol };
+ return {
+ TokenType::Variable, varName, filename, lineNumber, startCol, { startChar, pos }
+ };
}
- return { TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol };
+ return {
+ TokenType::Unknown, "$ followed by invalid character", filename, lineNumber, startCol, { startChar, pos }
+ };
}
-Token Lexer::comment() {
- size_t startCol = colNumber;
+Token Lexer::commentToken() {
+ size_t startChar = charNumber;
+ size_t startCol = colNumber;
advance(); // Skip #
std::string commentText;
while (!isAtEnd() && peek() != '\n') {
commentText += advance();
}
- return { TokenType::Comment, commentText, filename, lineNumber, startCol };
+ return {
+ TokenType::Comment, commentText, filename, lineNumber, startCol, { startChar, pos }
+ };
}
-Token Lexer::keywordOrIdentifier() {
+Token Lexer::keywordOrIdentifierToken() {
std::string lexeme;
- size_t startCol = colNumber;
while (isalpha(peek())) {
lexeme += advance();
}
@@ -135,16 +184,14 @@
advance();
}
if (peek() == '$') {
- return this->variableDeclaration(type);
+ return this->variableDeclarationToken(type);
}
- return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+ return createToken(TokenType::Identifier, lexeme);
}
-
- return { TokenType::Identifier, lexeme, filename, lineNumber, startCol };
+ return createToken(TokenType::Identifier, lexeme);
}
-Token Lexer::variableDeclaration(Variables::Type type) {
- size_t startCol = colNumber;
+Token Lexer::variableDeclarationToken(Variables::Type type) {
advance(); // Skip $
std::string varName;
if (isalpha(peek()) || peek() == '_') {
@@ -154,40 +201,29 @@
}
for (auto it = Variables::StringToTypeMap.begin(); it != Variables::StringToTypeMap.end(); ++it) {
if (it->second == type) {
- return { getTokenTypeFromValueDeclaration(it->second), varName, filename, lineNumber, startCol };
+ return createToken(getTokenTypeFromValueDeclaration(it->second), varName);
}
}
-
- return { TokenType::Unknown, "Invalid variable type in declaration", filename, lineNumber, startCol };
+ return createUnknownToken("Invalid variable type in declaration");
}
- return { TokenType::Unknown, "$ followed by invalid character in declaration", filename, lineNumber, startCol };
-}
-
-Token Lexer::singleCharToken(TokenType type, const std::string & lexeme) {
- size_t startCol = colNumber;
- advance();
- return { type, lexeme, filename, lineNumber, startCol };
+ return createUnknownToken("$ followed by invalid character in declaration");
}
bool Lexer::matchSequence(const std::string & sequence, bool caseSensitive) const {
if (this->pos + sequence.size() > src.size()) {
return false;
}
-
for (size_t i = 0; i < sequence.size(); ++i) {
char srcChar = src[this->pos + i];
char seqChar = sequence[i];
-
if (!caseSensitive) {
srcChar = std::tolower(static_cast<unsigned char>(srcChar));
seqChar = std::tolower(static_cast<unsigned char>(seqChar));
}
-
if (srcChar != seqChar) {
return false;
}
}
-
return true;
}
@@ -205,84 +241,84 @@
while (pos < src.size()) {
char c = src[pos];
+
if (isspace(c)) {
advance();
continue;
}
if (c == '\n') {
- tokens.push_back(singleCharToken(TokenType::EndOfLine, "\n"));
+ tokens.push_back(createSingleCharToken(TokenType::EndOfLine, "\n"));
continue;
}
if (c == COMMENT_CHARACTER) {
- tokens.push_back(comment());
+ tokens.push_back(commentToken());
advance(); // Skip newline after comment
continue;
}
if (matchSequence(PARSER_OPEN_TAG)) {
- size_t startCol = colNumber;
matchAndConsume(PARSER_OPEN_TAG);
- tokens.push_back({ TokenType::ParserOpenTag, PARSER_OPEN_TAG, filename, lineNumber, startCol });
+ tokens.push_back(createToken(TokenType::ParserOpenTag, PARSER_OPEN_TAG));
continue;
}
if (matchSequence(PARSER_CLOSE_TAG)) {
- size_t startCol = colNumber;
matchAndConsume(PARSER_CLOSE_TAG);
- tokens.push_back({ TokenType::ParserCloseTag, PARSER_CLOSE_TAG, filename, lineNumber, startCol });
+ tokens.push_back(createToken(TokenType::ParserCloseTag, PARSER_CLOSE_TAG));
continue;
}
if (matchSequence("if")) {
- size_t startCol = colNumber;
matchAndConsume("if");
- tokens.push_back({ TokenType::ParserIfStatement, "if", filename, lineNumber, startCol });
+ tokens.push_back(createToken(TokenType::ParserIfStatement, "if"));
continue;
}
switch (c) {
case 'a' ... 'z':
case 'A' ... 'Z':
- tokens.push_back(keywordOrIdentifier());
+ tokens.push_back(keywordOrIdentifierToken());
break;
case '$':
- tokens.push_back(variable());
+ tokens.push_back(variableToken());
break;
case '0' ... '9':
- tokens.push_back(number());
+ tokens.push_back(numberToken());
break;
case '"':
case '\'':
- tokens.push_back(string());
+ tokens.push_back(stringToken());
break;
case '(':
- tokens.push_back(singleCharToken(TokenType::LeftParenthesis, "("));
+ tokens.push_back(createSingleCharToken(TokenType::LeftParenthesis, "("));
break;
case ')':
- tokens.push_back(singleCharToken(TokenType::RightParenthesis, ")"));
+ tokens.push_back(createSingleCharToken(TokenType::RightParenthesis, ")"));
break;
case ',':
- tokens.push_back(singleCharToken(TokenType::Comma, ","));
+ tokens.push_back(createSingleCharToken(TokenType::Comma, ","));
break;
case ';':
- tokens.push_back(singleCharToken(TokenType::Semicolon, ";"));
+ tokens.push_back(createSingleCharToken(TokenType::Semicolon, ";"));
break;
case '=':
- tokens.push_back(singleCharToken(TokenType::Equals, "="));
+ tokens.push_back(createSingleCharToken(TokenType::Equals, "="));
break;
case '+':
- tokens.push_back(singleCharToken(TokenType::Plus, "+"));
+ tokens.push_back(createSingleCharToken(TokenType::Plus, "+"));
break;
case '{':
- tokens.push_back(singleCharToken(TokenType::LeftCurlyBracket, "{"));
+ tokens.push_back(createSingleCharToken(TokenType::LeftCurlyBracket, "{"));
break;
case '}':
- tokens.push_back(singleCharToken(TokenType::RightCurlyBracket, "}"));
+ tokens.push_back(createSingleCharToken(TokenType::RightCurlyBracket, "}"));
break;
default:
- tokens.push_back({ TokenType::Unknown, std::string(1, c), filename, lineNumber, colNumber });
+ tokens.push_back(createUnknownToken(std::string(1, c)));
advance();
break;
}
}
- tokens.push_back({ TokenType::EndOfFile, "", filename, lineNumber, colNumber });
+ tokens.push_back({
+ TokenType::EndOfFile, "", filename, lineNumber, colNumber, { charNumber, charNumber }
+ });
return tokens;
}
diff --git a/src/Lexer.hpp b/src/Lexer.hpp
index d374fa1..5648ab7 100644
--- a/src/Lexer.hpp
+++ b/src/Lexer.hpp
@@ -38,6 +38,19 @@
Token variableDeclaration(Variables::Type type);
void matchAndConsume(const std::string & sequence, bool caseSensitive = true);
+ // create token methods
+ Token createToken(TokenType type, const std::string & lexeme) const;
+ Token createSingleCharToken(TokenType type, const std::string & lexeme);
+ Token createUnknownToken(const std::string & lexeme) const;
+ Token createErrorToken(const std::string & lexeme) const;
+ Token stringToken();
+ Token numberToken();
+ Token identifierToken();
+ Token variableToken();
+ Token commentToken();
+ Token keywordOrIdentifierToken();
+ Token variableDeclarationToken(Variables::Type type);
+
// validate number types from string
template <typename Numeric> static bool is_number(const std::string & s) {
Numeric n;
diff --git a/src/ScriptExceptionMacros.h b/src/ScriptExceptionMacros.h
index c2e754f..55dce1c 100644
--- a/src/ScriptExceptionMacros.h
+++ b/src/ScriptExceptionMacros.h
@@ -18,6 +18,9 @@
#define THROW_UNDEFINED_VARIABLE_ERROR(name, token) \
throw ScriptException::makeUndefinedVariableError(name, token, __FILE__, __LINE__)
+#define THROW_UNDEFINED_VARIABLE_ERROR_HELPER(name, token, file, line) \
+ throw ScriptException::makeUndefinedVariableError(name, token, file, line)
+
// Unknown (undefined) function call
#define THROW_UNDEFINED_FUNCTION_ERROR(name, token) \
throw ScriptException::makeUndefinedFunctionError(name, token, __FILE__, __LINE__)
diff --git a/src/ScriptInterpreter.cpp b/src/ScriptInterpreter.cpp
index 92e1da6..3735ba0 100644
--- a/src/ScriptInterpreter.cpp
+++ b/src/ScriptInterpreter.cpp
@@ -35,8 +35,14 @@
throw std::runtime_error("Double literal out of range: " + token.lexeme);
}
}
+ if (token.type == TokenType::BooleanLiteral || token.type == TokenType::Identifier) {
+ std::string lowered = token.lexeme;
+ std::transform(lowered.begin(), lowered.end(), lowered.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+ return Value::fromBoolean(token, (lowered == "true" ? true : false));
+ }
if (token.type == TokenType::Variable) {
- return this->getVariable(token);
+ return this->getVariable(token, this->contextPrefix, __FILE__, __LINE__);
}
THROW_UNEXPECTED_TOKEN_ERROR(token, "string, integer, double, or variable");
@@ -81,67 +87,79 @@
}
void ScriptInterpreter::handleBooleanDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
- const auto varName = tokens[i].lexeme;
- const auto varType = tokens[i].variableType;
+ const auto & varToken = tokens[i];
i++; // Skip variable name
if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
i++; // Skip '='
if (i < tokens.size() && tokens[i].type == TokenType::Variable) {
- const auto variable = this->getVariable(tokens[i]);
+ const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__);
if (variable.type != Variables::Type::VT_BOOLEAN) {
- THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_BOOLEAN),
+ THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme,
+ Variables::TypeToString(Variables::Type::VT_BOOLEAN),
tokens[i].lexeme, variable.TypeToString(), tokens[i]);
}
- this->setVariable(varName, variable);
+ this->setVariable(varToken.lexeme, variable, this->contextPrefix, true);
i++; // Skip variable name
EXPECT_SEMICOLON(tokens, i, "after bool variable declaration");
- } else if (i < tokens.size() && tokens[i].type == TokenType::Identifier) {
+ } else if (i < tokens.size() &&
+ (tokens[i].type == TokenType::Identifier || tokens[i].type == TokenType::StringLiteral)) {
std::string lowered = tokens[i].lexeme;
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
[](unsigned char c) { return std::tolower(c); });
if (lowered == "true") {
- this->setVariable(varName, Value::fromBoolean(tokens[i], true));
+ this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true);
} else if (lowered == "false") {
- this->setVariable(varName, Value::fromBoolean(tokens[i], false));
+ this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true);
} else {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "true or false after '='");
}
i++; // Skip boolean literal
EXPECT_SEMICOLON(tokens, i, "after bool declaration");
+ } else if (i < tokens.size() && tokens[i].type == TokenType::IntLiteral) {
+ const auto test = std::stoi(tokens[i].lexeme);
+ if (test == 0) {
+ this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], false), this->contextPrefix, true);
+ } else if (test > 0) {
+ this->setVariable(varToken.lexeme, Value::fromBoolean(tokens[i], true), this->contextPrefix, true);
+ } else {
+ THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='");
+ }
+ i++;
+ EXPECT_SEMICOLON(tokens, i, "after bool declaration");
} else {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "bool literal after '='");
}
+
} else {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after bool declaration");
}
};
void ScriptInterpreter::handleStringDeclaration(const std::vector<Token> & tokens, std::size_t & i) {
- const auto varName = tokens[i].lexeme;
- const auto varType = tokens[i].variableType;
+ const auto varToken = tokens[i];
i++; // Skip variable name
if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
i++; // Skip '='
if (i < tokens.size() && tokens[i].type == TokenType::Variable) {
- const auto variable = this->getVariable(tokens[i]);
+ const auto variable = this->getVariable(tokens[i], this->contextPrefix, __FILE__, __LINE__);
if (variable.type != Variables::Type::VT_STRING) {
- THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_STRING),
+ THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme,
+ Variables::TypeToString(Variables::Type::VT_STRING),
tokens[i].lexeme, variable.TypeToString(), tokens[i]);
}
- this->setVariable(varName, variable);
- //variables[varName] = variables[tokens[i].lexeme];
+ this->setVariable(varToken.lexeme, variable, this->contextPrefix, true);
i++; // Skip variable name
EXPECT_SEMICOLON(tokens, i, "after string variable declaration");
} else if (i < tokens.size() && tokens[i].type == TokenType::StringLiteral) {
- this->setVariable(varName, Value::fromString(tokens[i]));
+ this->setVariable(varToken.lexeme, Value::fromString(tokens[i]), this->contextPrefix, true);
i++; // Skip string literal
EXPECT_SEMICOLON(tokens, i, "after string declaration");
} else {
@@ -153,8 +171,7 @@
}
void ScriptInterpreter::handleNumberDeclaration(const std::vector<Token> & tokens, std::size_t & i, TokenType type) {
- const auto varName = tokens[i].lexeme;
- const auto varType = tokens[i].variableType;
+ const auto & varToken = tokens[i];
i++; // Skip variable name
if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
@@ -162,12 +179,7 @@
if (i < tokens.size()) {
if (type == TokenType::IntDeclaration && tokens[i].type == TokenType::IntLiteral) {
try {
- const auto variable = this->getVariable(tokens[i]);
- if (variable.type != Variables::Type::VT_INT) {
- THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, Variables::TypeToString(Variables::Type::VT_INT),
- tokens[i].lexeme, variable.TypeToString(), tokens[i]);
- }
- this->setVariable(varName, Value::fromInt(tokens[i]));
+ this->setVariable(varToken.lexeme, Value::fromInt(tokens[i]), this->contextPrefix, true);
i++; // Skip int literal
} catch (const std::invalid_argument & e) {
throw std::runtime_error("Invalid integer literal in declaration: " + tokens[i].lexeme);
@@ -176,13 +188,7 @@
}
} else if (type == TokenType::DoubleDeclaration && tokens[i].type == TokenType::DoubleLiteral) {
try {
- const auto variable = this->getVariable(tokens[i]);
- if (variable.type != Variables::Type::VT_DOUBLE) {
- THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName,
- Variables::TypeToString(Variables::Type::VT_DOUBLE),
- tokens[i].lexeme, variable.TypeToString(), tokens[i]);
- }
- this->setVariable(varName, Value::fromDouble(tokens[i]));
+ this->setVariable(varToken.lexeme, Value::fromDouble(tokens[i]), this->contextPrefix, true);
i++; // Skip double literal
} catch (const std::invalid_argument & e) {
throw std::runtime_error("Invalid double literal in declaration: " + tokens[i].lexeme);
@@ -191,7 +197,7 @@
}
} else {
const std::string expectedType = type == TokenType::IntDeclaration ? "int" : "double";
- THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varName, expectedType, "",
+ THROW_VARIABLE_TYPE_MISSMATCH_ERROR(varToken.lexeme, expectedType, "",
getVariableTypeFromTokenTypeAsString(tokens[i].type), tokens[i]);
}
EXPECT_SEMICOLON(tokens, i, "after variable declaration");
@@ -199,7 +205,7 @@
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i - 1], "literal after '='");
}
} else {
- THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varName);
+ THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "= after variable declaration, variable name: " + varToken.lexeme);
}
}
@@ -224,24 +230,26 @@
i++;
// parse arg definitions
- const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i);
+ const auto args = ScriptInterpreterHelpers::parseFunctionDeclarationArguments(tokens, i, __FILE__, __LINE__);
std::cout << "args: " << args.size() << std::endl;
for (const auto & arg : args) {
std::cout << "arg name: " << arg.GetToken().lexeme << " type: " << arg.TypeToString() << std::endl;
}
this->functionParameters[varName].assign(args.begin(), args.end());
- const std::string body = ScriptInterpreterHelpers::getFunctionBody(tokens, i);
- this->functionBodies[varName] = body;
+ size_t start;
+ size_t end;
+ ScriptInterpreterHelpers::getFunctionBody(tokens, i, start, end);
+ std::cout << "Body start: " << start << " end: " << end << std::endl;
+ const std::string function_body = ScriptInterpreterHelpers::extractSubstring(this->source, start, end);
+ this->functionBodies[varName] = function_body;
// recheck the close curly brace
if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}");
}
i++;
- //this->functionBodies[varName] = std::make_shared<ScriptInterpreter>();
- //this->functionBodies[varName]->executeScript(body, this->filename, true);
- //EXPECT_SEMICOLON(tokens, i, "after function declaration");
- // there is no need semicolon to the end of the function declaration
- std::cout << "function body: \n\"" << body << "\"" << std::endl;
+#if DEBUG_BUILD == 1
+ std::cout << "function body: \n\"" << function_body << "\"" << std::endl;
+#endif
}
void ScriptInterpreter::handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i) {
@@ -272,16 +280,13 @@
}
void ScriptInterpreter::handleVariableReference(const std::vector<Token> & tokens, std::size_t & i) {
- //THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "function call or variable assignment (not yet implemented)");
-// const auto varName = tokens[i].lexeme;
-// const auto varType = tokens[i].variableType;
- const auto& varToken = tokens[i];
+ const auto & varToken = tokens[i];
i++; // Skip variable token to avoid infinite loop
if (i < tokens.size() && tokens[i].type == TokenType::Equals) {
i++; // Skip '='
if (i < tokens.size()) {
- const auto variable = this->getVariable(varToken);
- this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]));
+ const auto variable = this->getVariable(varToken, this->contextPrefix, __FILE__, __LINE__);
+ this->setVariable(varToken.lexeme, evaluateExpression(tokens[i]), this->contextPrefix, false);
i++; // Skip value
EXPECT_SEMICOLON(tokens, i, "after variable assignment");
} else {
@@ -300,8 +305,11 @@
i++; // Skip semicolon token
}
-void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename, bool ignore_tags) {
- this->filename = filename;
+void ScriptInterpreter::executeScript(const std::string & source, const std::string & filename,
+ const std::string & _namespace, bool ignore_tags) {
+ this->filename = filename;
+ this->source = source;
+ this->contextPrefix = filename + _namespace;
Lexer lexer(source, filename);
auto tokens = lexer.tokenize();
diff --git a/src/ScriptInterpreter.hpp b/src/ScriptInterpreter.hpp
index 5ecc7c3..803ef66 100644
--- a/src/ScriptInterpreter.hpp
+++ b/src/ScriptInterpreter.hpp
@@ -1,7 +1,6 @@
#ifndef SSCRIPTINTERPRETER_HPP
#define SSCRIPTINTERPRETER_HPP
#include <functional>
-#include <map>
#include <memory>
#include <string>
#include <unordered_map>
@@ -18,7 +17,8 @@
class ScriptInterpreter {
public:
void registerModule(const std::string & name, std::shared_ptr<BaseFunction> fn);
- void executeScript(const std::string & source, const std::string & filenaneame, bool ignore_tags = false);
+ void executeScript(const std::string & source, const std::string & filename,
+ const std::string & _namespace = "_default_", bool ignore_tags = false);
private:
std::unordered_map<std::string, FunctionValidator> functionValidators;
@@ -29,13 +29,19 @@
std::unordered_map<std::string, std::vector<Value>> functionParameters;
std::unordered_map<std::string, std::string> functionBodies;
std::string filename;
+ std::string source;
+ std::string contextPrefix;
[[nodiscard]] std::vector<Value> parseFunctionArguments(const std::vector<Token> & tokens,
std::size_t & index) const;
[[nodiscard]] Value evaluateExpression(const Token & token) const;
// type handlers
- void setVariable(const std::string & name, const Value & value, const std::string & context = "default") {
+ void setVariable(const std::string & name, const Value & value, const std::string & context = "default",
+ bool exception_if_exists = false) {
+ if (exception_if_exists && variables[context].find(name) != variables[context].end()) {
+ THROW_VARIABLE_REDEFINITION_ERROR(name, value.token);
+ }
this->variables[context][name] = value;
}
@@ -51,7 +57,8 @@
throw std::runtime_error("Variable not found: " + name);
};
- [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default") const {
+ [[nodiscard]] Value getVariable(const Token & token, const std::string & context = "default",
+ const std::string & file = __FILE__, const int & line = __LINE__) const {
for (auto it = variables.begin(); it != variables.end(); ++it) {
if (it->first == context) {
const auto & innerMap = it->second.find(token.lexeme);
@@ -60,9 +67,47 @@
}
}
}
- THROW_UNDEFINED_VARIABLE_ERROR(token.lexeme, token);
+ THROW_UNDEFINED_VARIABLE_ERROR_HELPER(token.lexeme, token, file, line);
};
+ /**
+ * Checks if a variable exists within the specified context.
+ *
+ * @param name The name of the variable to check.
+ * @param context The context in which to search for the variable (defaults to "default").
+ * @param file The source file where the check is performed (for error reporting).
+ * @param line The line number where the check is performed (for error reporting).
+ * @return True if the variable exists in the specified context.
+ * @throws Throws an undefined variable error if the variable is not found.
+ */
+ [[nodiscard]] bool variableExists(const std::string & name, const std::string & context = "default",
+ const std::string & file = __FILE__, const int & line = __LINE__) const {
+ for (auto it = variables.begin(); it != variables.end(); ++it) {
+ if (it->first == context) {
+ const auto & innerMap = it->second.find(name);
+ if (innerMap != it->second.end()) {
+ return true;
+ }
+ }
+ }
+ THROW_UNDEFINED_VARIABLE_ERROR_HELPER(name, Token(TokenType::Variable, name, name, 0, 0), file, line);
+ }
+
+ /**
+ * Checks if a variable exists within the specified context using a Token.
+ *
+ * @param token The token representing the variable to check.
+ * @param context The context in which to search for the variable (defaults to "default").
+ * @param file The source file where the check is performed (for error reporting).
+ * @param line The line number where the check is performed (for error reporting).
+ * @return True if the variable exists in the specified context.
+ * @throws Throws an undefined variable error if the variable is not found.
+ */
+ [[nodiscard]] bool variableExists(const Token & token, const std::string & context = "default",
+ const std::string & file = __FILE__, const int & line = __LINE__) const {
+ return this->variableExists(token.lexeme, context, file, line);
+ }
+
void handleFunctionCall(const std::vector<Token> & tokens, std::size_t & i);
void handleVariableReference(const std::vector<Token> & tokens, std::size_t & i);
void handleComment(std::size_t & i);
diff --git a/src/ScriptInterpreterHelpers.hpp b/src/ScriptInterpreterHelpers.hpp
index 6b615c7..debd1bc 100644
--- a/src/ScriptInterpreterHelpers.hpp
+++ b/src/ScriptInterpreterHelpers.hpp
@@ -14,7 +14,7 @@
namespace ScriptInterpreterHelpers {
-static std::string extractSubstring(const std::string & str, size_t start, size_t end) {
+static std::string extractSubstring(const std::string & str, const size_t & start, const size_t & end) {
if (start >= 0 && start < str.length() && end >= start && end < str.length()) {
return str.substr(start, end - start + 1);
}
@@ -37,28 +37,31 @@
// check the arguments types
if (tokens[i].type != TokenType::StringDeclaration && tokens[i].type != TokenType::BooleanDeclaration &&
- tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration) {
+ tokens[i].type != TokenType::IntDeclaration && tokens[i].type != TokenType::DoubleDeclaration &&
+ tokens[i].type != TokenType::RightParenthesis) {
THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "variable declaration", file, line);
}
- const auto parameter_type = getVariableTypeFromTokenTypeDeclaration(tokens[i].type);
- if (parameter_type == Variables::Type::VT_NOT_DEFINED) {
- THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
+ if (tokens[i].type != TokenType::RightParenthesis) {
+ const auto parameter_type = getVariableTypeFromTokenTypeDeclaration(tokens[i].type);
+ if (parameter_type == Variables::Type::VT_NOT_DEFINED) {
+ THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
+ }
+
+ if (parameter_type == Variables::Type::VT_FUNCTION) {
+ THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
+ }
+
+ if (parameter_type == Variables::Type::VT_NULL) {
+ THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
+ }
+
+ Value val;
+ val.type = parameter_type;
+ val.token = tokens[i];
+
+ arguments.emplace_back(std::move(val));
+ i++; // Skip variable declaration
}
-
- if (parameter_type == Variables::Type::VT_FUNCTION) {
- THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
- }
-
- if (parameter_type == Variables::Type::VT_NULL) {
- THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], "valid type identifier", file, line);
- }
-
- Value val;
- val.type = parameter_type;
- val.token = tokens[i];
-
- arguments.emplace_back(std::move(val));
- i++; // Skip variable declaration
if (tokens[i].type != TokenType::RightParenthesis) {
THROW_UNEXPECTED_TOKEN_ERROR_HELPER(tokens[i], ") - Only one argument is allowed", file, line);
@@ -68,17 +71,19 @@
return arguments;
}
-[[nodiscard]] static std::string getFunctionBody(const std::vector<Token> & tokens, std::size_t & i) {
- const size_t first_index = i;
+static void getFunctionBody(const std::vector<Token> & tokens, std::size_t & i, std::size_t & start,
+ std::size_t & end) {
+ start = tokens[i].pos.end;
+ std::cout << "START Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
+ << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
+
if (i >= tokens.size() || tokens[i].type != TokenType::LeftCurlyBracket) {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "{");
}
i++; // Skip '{'
- std::string lines;
while (i < tokens.size() && tokens[i].type != TokenType::RightCurlyBracket) {
if (tokens[i].type == TokenType::EndOfLine) {
- lines += "\n";
i++;
continue;
}
@@ -86,14 +91,16 @@
throw std::runtime_error("Unexpected end of file");
break;
}
- lines += tokens[i].lexeme + " ";
i++;
}
+ end = tokens[i].pos.start - 1;
+
+ std::cout << "END Token: " << tokens[i].lexeme << " start pos: " << std::to_string(tokens[i].pos.start)
+ << " end pos: " << std::to_string(tokens[i].pos.end) << std::endl;
if (i >= tokens.size() || tokens[i].type != TokenType::RightCurlyBracket) {
THROW_UNEXPECTED_TOKEN_ERROR(tokens[i], "}");
}
- return lines;
};
}; // namespace ScriptInterpreterHelpers
--
Gitblit v1.9.3