From 817cf5a1a90991f11e66b4c5fe7630c4e2cd5b83 Mon Sep 17 00:00:00 2001 From: Maxwell Jeffress Date: Thu, 30 Oct 2025 10:55:21 +0000 Subject: [PATCH] Basic math support --- src/lexer/lexer.cpp | 8 +++ src/lexer/lexer.h | 2 +- src/parser/parser.cpp | 108 +++++++++++++++++++++++++++++++++- src/parser/parser.h | 19 +++++- src/runner/runner.cpp | 134 +++++++++++++++++++++++++++++++++++++++++- src/runner/runner.h | 1 + 6 files changed, 264 insertions(+), 8 deletions(-) diff --git a/src/lexer/lexer.cpp b/src/lexer/lexer.cpp index b2eb4bb..0a859ad 100644 --- a/src/lexer/lexer.cpp +++ b/src/lexer/lexer.cpp @@ -83,6 +83,14 @@ Lexer::Lexer(std::string in) : file(std::move(in)) { content.emplace_back("<"); } break; + case '!': + if (peek() == '=') { + consume(); + content.emplace_back("!="); + } else { + content.emplace_back("!"); + } + break; case '+': case '-': case '*': diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h index dfc9fe6..1ddc4e6 100644 --- a/src/lexer/lexer.h +++ b/src/lexer/lexer.h @@ -15,7 +15,7 @@ class Lexer { private: std::vector delimiters = { - '(', ')', '{', '}', '[', ']', '.', '\n', '+', '-', '*', '/', '^', '>', '<', ' ', ',' + '(', ')', '{', '}', '[', ']', '.', '\n', '+', '-', '*', '/', '^', '>', '<', ' ', ',', '!' }; std::string file; size_t incrementor = -1; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index 74628c3..39a3cf8 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -8,6 +8,82 @@ #include #include +std::map ASTOperator::operatorMap = { + {"==", OperatorType::Equal}, + {"!=", OperatorType::Inequal}, + {">=", OperatorType::GreaterEqual}, + {"<=", OperatorType::LessEqual}, + {">", OperatorType::Greater}, + {"<", OperatorType::Less}, + {"+", OperatorType::Add}, + {"-", OperatorType::Subtract}, + {"*", OperatorType::Multiply}, + {"/", OperatorType::Divide}, + {"+=", OperatorType::AddBy}, + {"-=", OperatorType::SubtractBy}, + {"*=", OperatorType::MultiplyBy}, + {"/=", OperatorType::DivideBy} +}; + +ASTList::ASTList(std::vector in) : elements(std::move(in)) {} + +ASTOperator::ASTOperator(std::vector args) { + std::vector> outputQueue; + std::vector operatorStack; + + auto getPrecedence = [&](OperatorType op) { + switch (op) { + case OperatorType::Multiply: + case OperatorType::Divide: + return 2; + case OperatorType::Add: + case OperatorType::Subtract: + return 1; + case OperatorType::Equal: + case OperatorType::Inequal: + case OperatorType::Less: + case OperatorType::LessEqual: + case OperatorType::Greater: + case OperatorType::GreaterEqual: + return 0; + default: + return -1; + } + }; + + for (const std::string& token : args) { + if (operatorMap.find(token) != operatorMap.end()) { + // it's an operator + OperatorType o1 = operatorMap[token]; + while (!operatorStack.empty()) { + OperatorType o2 = operatorStack.back(); + if (getPrecedence(o2) >= getPrecedence(o1)) { + arguments.emplace_back(o2); + operatorStack.pop_back(); + } else { + break; + } + } + operatorStack.push_back(o1); + } else { + // it's an operand (Value or Identifier) + ASTCodeBlock tempBlock({token}); + if (!tempBlock.nodes.empty()) { + arguments.emplace_back(tempBlock.nodes[0]); + } else { + // This should not happen if token is valid + std::cout << "Invalid token in expression: " << token << std::endl; + exit(1); + } + } + } + + while (!operatorStack.empty()) { + arguments.emplace_back(operatorStack.back()); + operatorStack.pop_back(); + } +} + ASTValue::ASTValue() : type(ValueType::None) {} ASTValue::ASTValue(std::string in) : type(ValueType::String), value(in) {} ASTValue::ASTValue(bool in) : type(ValueType::Bool), value(in) {} @@ -15,8 +91,6 @@ ASTValue::ASTValue(long long in) : type(ValueType::Int), value(in) {} ASTValue::ASTValue(double in) : type(ValueType::Float), value(in) {} ASTValue::ASTValue(ASTList in) : type(ValueType::List), value(std::move(in)) {} -ASTList::ASTList(std::vector in) : elements(std::move(in)) {} - ValueType ASTValue::getValueType(std::string in) { if (in.size() < 1) { return ValueType::None; @@ -53,7 +127,7 @@ ValueType ASTValue::getValueType(std::string in) { } ASTFunction::ASTFunction(ASTCodeBlock body) : body(std::move(body)) {} -ASTFunction::ASTFunction() {} +ASTFunction::ASTFunction() = default; ASTFunctionCall::ASTFunctionCall(std::string func, std::vector args) : func(std::move(func)), args(std::move(args)) {} ASTIdentifier::ASTIdentifier(std::string in) : name(std::move(in)) {} @@ -98,6 +172,10 @@ std::optional ASTValue::getList() const { } } +std::vector> ASTOperator::getArguments() const { + return arguments; +} + std::vector ASTList::getElements() const { return elements; } @@ -128,6 +206,10 @@ TokenType ASTCodeBlock::getTokenType() { } else { return TokenType::None; } + + if (ASTOperator::operatorMap.find(token) != ASTOperator::operatorMap.end()) { + return TokenType::Operator; + } // Check for values first if (ASTValue().getValueType(token) != ValueType::None) { @@ -199,6 +281,26 @@ void ASTCodeBlock::parseBlock() { return; } TokenType tokenType = getTokenType(); + + // Expression parsing logic + if (tokenType == TokenType::Value || tokenType == TokenType::Identifier) { + if (peek(1).has_value() && ASTOperator::operatorMap.count(peek(1).value())) { + std::vector expressionTokens; + while (peek(0).has_value()) { + TokenType currentTokenType = getTokenType(); // use a different variable + if (currentTokenType == TokenType::Value || currentTokenType == TokenType::Identifier || currentTokenType == TokenType::Operator) { + expressionTokens.push_back(consume().value()); + } else { + break; + } + } + if (!expressionTokens.empty()) { + nodes.emplace_back(std::make_shared(expressionTokens)); + } + continue; + } + } + ValueType valueType = ASTValue().getValueType(token.value()); std::optional currentToken = consume(); diff --git a/src/parser/parser.h b/src/parser/parser.h index a23653c..a34ed37 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -11,8 +12,9 @@ class ASTFunction; class ASTFunctionCall; class ASTCodeBlock; class ASTIdentifier; +class ASTOperator; -typedef std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr> ASTNode; +typedef std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr, std::shared_ptr> ASTNode; class ASTList { private: @@ -30,7 +32,20 @@ enum class ValueType { }; enum class TokenType { - Identifier, Value, Function, FunctionCallStart, OpenParen, CloseParen, CodeBlockStart, CodeBlockEnd, NewLine, ListStart, ListEnd, None + Identifier, Value, Function, FunctionCallStart, OpenParen, CloseParen, CodeBlockStart, CodeBlockEnd, NewLine, ListStart, ListEnd, Operator, None +}; + +enum class OperatorType { + Add, Subtract, Multiply, Divide, Equal, Inequal, Less, LessEqual, Greater, GreaterEqual, AddBy, MultiplyBy, DivideBy, SubtractBy +}; + +class ASTOperator { + private: + std::vector> arguments; + public: + static std::map operatorMap; + explicit ASTOperator(std::vector args); + std::vector> getArguments() const; }; /** diff --git a/src/runner/runner.cpp b/src/runner/runner.cpp index 1a74fad..e3132eb 100644 --- a/src/runner/runner.cpp +++ b/src/runner/runner.cpp @@ -82,6 +82,116 @@ void Executor::printValue(const ASTValue& arg) { } } +ASTValue Executor::evaluateOperator(const std::shared_ptr& op) { + std::vector stack; + auto args = op->getArguments(); + + for (const auto& arg : args) { + if (std::holds_alternative(arg)) { + ASTNode node = std::get(arg); + if (std::holds_alternative>(node)) { + stack.push_back(*std::get>(node)); + } else if (std::holds_alternative>(node)) { + std::string varName = std::get>(node)->name; + if (variables.count(varName)) { + Object obj = variables[varName]; + if (!obj.isCustomObject && std::holds_alternative(obj.value)) { + stack.push_back(std::get(obj.value)); + } else { + std::cout << "Variable " << varName << " is not a value in expression" << std::endl; + exit(1); + } + } else { + std::cout << "Undefined variable " << varName << " in expression" << std::endl; + exit(1); + } + } + } else { // It's an OperatorType + OperatorType opType = std::get(arg); + if (stack.size() < 2) { + std::cout << "Syntax error in expression: not enough operands for operator" << std::endl; + exit(1); + } + ASTValue right = stack.back(); + stack.pop_back(); + ASTValue left = stack.back(); + stack.pop_back(); + + if ((left.type == ValueType::Int || left.type == ValueType::Float) && + (right.type == ValueType::Int || right.type == ValueType::Float)) { + + double l = left.type == ValueType::Int ? (double)left.getInt().value() : left.getFloat().value(); + double r = right.type == ValueType::Int ? (double)right.getInt().value() : right.getFloat().value(); + bool resultIsFloat = left.type == ValueType::Float || right.type == ValueType::Float; + + switch (opType) { + case OperatorType::Add: + resultIsFloat ? stack.emplace_back(l + r) : stack.emplace_back((long long)(l + r)); + break; + case OperatorType::Subtract: + resultIsFloat ? stack.emplace_back(l - r) : stack.emplace_back((long long)(l - r)); + break; + case OperatorType::Multiply: + resultIsFloat ? stack.emplace_back(l * r) : stack.emplace_back((long long)(l * r)); + break; + case OperatorType::Divide: + if (r == 0) { std::cout << "Division by zero" << std::endl; exit(1); } + stack.emplace_back(l / r); + break; + case OperatorType::Equal: + stack.emplace_back(l == r); + break; + case OperatorType::Inequal: + stack.emplace_back(l != r); + break; + case OperatorType::Greater: + stack.emplace_back(l > r); + break; + case OperatorType::GreaterEqual: + stack.emplace_back(l >= r); + break; + case OperatorType::Less: + stack.emplace_back(l < r); + break; + case OperatorType::LessEqual: + stack.emplace_back(l <= r); + break; + default: + std::cout << "Unsupported operator for numeric types" << std::endl; + exit(1); + } + } else if (left.type == ValueType::String && opType == OperatorType::Add) { + std::string r_str; + if (right.type == ValueType::String) r_str = right.getString().value(); + else if (right.type == ValueType::Int) r_str = std::to_string(right.getInt().value()); + else if (right.type == ValueType::Float) r_str = std::to_string(right.getFloat().value()); + else if (right.type == ValueType::Bool) r_str = right.getBool().value() ? "true" : "false"; + else { std::cout << "Unsupported type for string concatenation" << std::endl; exit(1); } + stack.emplace_back(left.getString().value() + r_str); + } else if (left.type == ValueType::Bool && right.type == ValueType::Bool) { + bool l = left.getBool().value(); + bool r = right.getBool().value(); + switch (opType) { + case OperatorType::Equal: stack.emplace_back(l == r); break; + case OperatorType::Inequal: stack.emplace_back(l != r); break; + default: std::cout << "Unsupported operator for booleans" << std::endl; exit(1); + } + } + else { + std::cout << "Unsupported operand types in expression" << std::endl; + exit(1); + } + } + } + + if (stack.size() != 1) { + std::cout << "Invalid expression" << std::endl; + exit(1); + } + + return stack.back(); +} + std::optional Executor::consume() { if (iterator < code.nodes.size()) { return code.nodes[iterator++]; @@ -141,6 +251,10 @@ Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map>(node.value())) { + if (std::holds_alternative>(node.value())) { + evaluateOperator(std::get>(node.value())); + continue; + } std::optional next = consume(); if (next.has_value()) { // function assignment @@ -159,6 +273,9 @@ Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map>(node.value())->name, *std::get>(valueNode.value())); } else if (std::holds_alternative>(valueNode.value())) { variables.emplace(std::get>(node.value())->name, *std::get>(valueNode.value())); + } else if (std::holds_alternative>(valueNode.value())) { + ASTValue result = evaluateOperator(std::get>(valueNode.value())); + variables.emplace(std::get>(node.value())->name, result); } else { std::cout << "Expected value or function after = sign" << std::endl; exit(1); @@ -188,6 +305,9 @@ Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map>(callArgNode)->name) != variables.end()) { callArgs.push_back(variables[std::get>(callArgNode)->name]); } + } else if (std::holds_alternative>(callArgNode)) { + ASTValue result = evaluateOperator(std::get>(callArgNode)); + callArgs.emplace_back(result); } } @@ -288,7 +408,12 @@ Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map>(block.value())) { - Executor(*std::get>(block.value()), false, variables); + Executor newExecutor(*std::get>(block.value()), false, variables); + for (const auto& [key, value] : newExecutor.getVariables()) { + if (variables.find(key) != variables.end()) { + variables[key] = newExecutor.getVariables()[key]; + } + } } } } else if (fnName == "while") { @@ -315,7 +440,12 @@ Executor::Executor(ASTCodeBlock in, bool isInitCall, std::map>(block.value())) { - Executor(*std::get>(block.value()), false, variables); + Executor newExecutor(*std::get>(block.value()), false, variables); + for (const auto& [key, value] : newExecutor.getVariables()) { + if (variables.find(key) != variables.end()) { + variables[key] = newExecutor.getVariables()[key]; + } + } } } } diff --git a/src/runner/runner.h b/src/runner/runner.h index 1e1b2fd..1574d30 100644 --- a/src/runner/runner.h +++ b/src/runner/runner.h @@ -36,6 +36,7 @@ class Executor { std::optional consume(); std::optional peek(int ahead = 1); void printValue(const ASTValue& arg); + ASTValue evaluateOperator(const std::shared_ptr& op); public: explicit Executor(ASTCodeBlock in, bool isInitCall = false, std::map scopeVals = {}, std::vector args = {}); std::map getVariables();