diff --git a/src/Binder/Binder.cpp b/src/Binder/Binder.cpp index c54c840..c9274a4 100644 --- a/src/Binder/Binder.cpp +++ b/src/Binder/Binder.cpp @@ -44,7 +44,8 @@ namespace Porygon::Binder { return this->BindReturnStatement(statement); case ParsedStatementKind::Conditional: return this->BindConditionalStatement(statement); - + case ParsedStatementKind::NumericalFor: + return this->BindNumericalForStatement(statement); case ParsedStatementKind::Bad: return new BoundBadStatement(); } @@ -214,6 +215,48 @@ namespace Porygon::Binder { return new BoundConditionalStatement(boundCondition, boundBlock, elseStatement); } + BoundStatement *Binder::BindNumericalForStatement(const ParsedStatement *statement) { + auto forStatement = (ParsedNumericalForStatement*) statement; + auto identifier = forStatement->GetIdentifier(); + auto start = this -> BindExpression(forStatement->GetStart()); + auto end = this -> BindExpression(forStatement->GetEnd()); + auto parsedStep = forStatement -> GetStep(); + BoundExpression* step = nullptr; + if (parsedStep != nullptr){ + step = this -> BindExpression(parsedStep); + } + if (start -> GetType()->GetClass() != TypeClass::Number){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::NumericalForArgumentNotANumber, start->GetStartPosition(), + start->GetLength()); + return new BoundBadStatement(); + } + if (end -> GetType()->GetClass() != TypeClass::Number){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::NumericalForArgumentNotANumber, end->GetStartPosition(), + end->GetLength()); + return new BoundBadStatement(); + } + if (step != nullptr && step -> GetType()->GetClass() != TypeClass::Number){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::NumericalForArgumentNotANumber, step->GetStartPosition(), + step->GetLength()); + return new BoundBadStatement(); + } + + this -> _scope ->GoInnerScope(); + auto variableKey = this -> _scope ->CreateExplicitLocal(identifier.GetHash(), make_shared(true, false)); + if (variableKey.GetResult() != VariableAssignmentResult::Ok){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::CantAssignVariable, statement->GetStartPosition(), + statement->GetLength()); + return new BoundBadStatement(); + } + auto block = this -> BindBlockStatement(forStatement->GetBlock()); + this -> _scope ->GoOuterScope(); + return new BoundNumericalForStatement(variableKey.GetKey(), start, end, step, block); + } + + ///////////////// + // Expressions // + ///////////////// + BoundExpression *Binder::BindExpression(const ParsedExpression *expression) { switch (expression->GetKind()) { case ParsedExpressionKind::LiteralInteger: @@ -586,4 +629,5 @@ namespace Porygon::Binder { return new BoundTableExpression((BoundBlockStatement *) block, tableType, expression->GetStartPosition(), expression->GetLength()); } + } diff --git a/src/Binder/Binder.hpp b/src/Binder/Binder.hpp index 945f26b..b7335bb 100644 --- a/src/Binder/Binder.hpp +++ b/src/Binder/Binder.hpp @@ -18,21 +18,19 @@ namespace Porygon::Binder { ~Binder(); + // Statements + BoundStatement *BindStatement(const ParsedStatement *statement); - BoundStatement *BindBlockStatement(const ParsedStatement *statement); - BoundStatement *BindExpressionStatement(const ParsedStatement *statement); - BoundStatement *BindAssignmentStatement(const ParsedStatement *statement); - BoundStatement *BindIndexAssignmentStatement(const ParsedStatement *statement); - BoundStatement *BindFunctionDeclarationStatement(const ParsedStatement *statement); - BoundStatement *BindReturnStatement(const ParsedStatement *statement); - BoundStatement *BindConditionalStatement(const ParsedStatement *statement); + BoundStatement *BindNumericalForStatement(const ParsedStatement *statement); + + // Expressions BoundExpression *BindExpression(const ParsedExpression *expression); diff --git a/src/Binder/BoundStatements/BoundStatement.hpp b/src/Binder/BoundStatements/BoundStatement.hpp index 3594382..8ed9f54 100644 --- a/src/Binder/BoundStatements/BoundStatement.hpp +++ b/src/Binder/BoundStatements/BoundStatement.hpp @@ -1,6 +1,3 @@ -#include - - #ifndef PORYGONLANG_BOUNDSTATEMENT_HPP #define PORYGONLANG_BOUNDSTATEMENT_HPP @@ -23,6 +20,7 @@ namespace Porygon::Binder { FunctionDeclaration, Return, Conditional, + NumericalFor, }; class BoundStatement { @@ -202,6 +200,53 @@ namespace Porygon::Binder { return _elseStatement; } }; + + class BoundNumericalForStatement : public BoundStatement { + const BoundVariableKey* _identifier; + const BoundExpression *_start; + const BoundExpression *_end; + const BoundExpression *_step; + + const BoundStatement *_block; + public: + explicit BoundNumericalForStatement(const BoundVariableKey* identifier, const BoundExpression *start, + const BoundExpression *end, const BoundExpression *step, + const BoundStatement *block) + : _identifier(identifier), _start(start), _end(end), _step(step), _block(block) { + } + + ~BoundNumericalForStatement() final { + delete _identifier; + delete _start; + delete _end; + delete _step; + delete _block; + } + + const BoundStatementKind GetKind() const final { + return BoundStatementKind::NumericalFor; + } + + const BoundVariableKey* GetIdentifier() const{ + return _identifier; + } + + const BoundExpression* GetStart() const{ + return _start; + } + + const BoundExpression* GetEnd() const{ + return _end; + } + + const BoundExpression* GetStep() const{ + return _step; + } + + const BoundStatement* GetBlock() const{ + return _block; + } + }; } diff --git a/src/Diagnostics/DiagnosticCode.hpp b/src/Diagnostics/DiagnosticCode.hpp index dcc86da..b3cad74 100644 --- a/src/Diagnostics/DiagnosticCode.hpp +++ b/src/Diagnostics/DiagnosticCode.hpp @@ -25,7 +25,8 @@ namespace Porygon::Diagnostics { InvalidTableValueType, InvalidTypeName, UserDataFieldNoGetter, - UserDataFieldNoSetter + UserDataFieldNoSetter, + NumericalForArgumentNotANumber }; } #endif //PORYGONLANG_DIAGNOSTICCODE_HPP diff --git a/src/Evaluator/Evaluator.cpp b/src/Evaluator/Evaluator.cpp index ed058ad..6d30c41 100644 --- a/src/Evaluator/Evaluator.cpp +++ b/src/Evaluator/Evaluator.cpp @@ -42,6 +42,8 @@ namespace Porygon::Evaluation { return this->EvaluateReturnStatement((BoundReturnStatement *) statement); case BoundStatementKind::Conditional: return this->EvaluateConditionalStatement((BoundConditionalStatement *) statement); + case BoundStatementKind::NumericalFor: + return this->EvaluateNumericalForStatement((BoundNumericalForStatement*)statement); case BoundStatementKind::Bad: throw; @@ -116,6 +118,38 @@ namespace Porygon::Evaluation { } } + void Evaluator::EvaluateNumericalForStatement(const BoundNumericalForStatement *statement) { + long start = this->EvaluateIntegerExpression(statement -> GetStart()) -> EvaluateInteger(); + long end = this->EvaluateIntegerExpression(statement -> GetEnd()) -> EvaluateInteger(); + long step = 1; + auto stepExp = statement -> GetStep(); + if (stepExp != nullptr){ + step = this -> EvaluateIntegerExpression(stepExp) -> EvaluateInteger(); + } + auto identifier = statement -> GetIdentifier(); + this -> _evaluationScope -> CreateVariable(identifier, nullptr); + auto block = (BoundBlockStatement*)statement -> GetBlock(); + if (step > 0){ + for (long i = start; i <= end; i += step){ + this -> _evaluationScope -> SetVariable(identifier, make_shared(i)); + for (auto s: *block->GetStatements()) { + this->EvaluateStatement(s); + if (this->_hasReturned) + break; + } + } + } else{ + for (long i = start; i >= end; i += step){ + this -> _evaluationScope -> SetVariable(identifier, make_shared(i)); + for (auto s: *block->GetStatements()) { + this->EvaluateStatement(s); + if (this->_hasReturned) + break; + } + } + } + } + const shared_ptr Evaluator::EvaluateExpression(const BoundExpression *expression) { auto type = expression->GetType(); switch (type->GetClass()) { @@ -389,4 +423,5 @@ namespace Porygon::Evaluation { throw; } } + } \ No newline at end of file diff --git a/src/Evaluator/Evaluator.hpp b/src/Evaluator/Evaluator.hpp index dfe1a81..bdfb8ee 100644 --- a/src/Evaluator/Evaluator.hpp +++ b/src/Evaluator/Evaluator.hpp @@ -31,6 +31,7 @@ namespace Porygon::Evaluation{ void EvaluateFunctionDeclarationStatement(const BoundFunctionDeclarationStatement *statement); void EvaluateReturnStatement(const BoundReturnStatement *statement); void EvaluateConditionalStatement(const BoundConditionalStatement *statement); + void EvaluateNumericalForStatement(const BoundNumericalForStatement *statement); const shared_ptr EvaluateExpression(const BoundExpression *expression); const shared_ptr EvaluateIntegerExpression(const BoundExpression *expression); diff --git a/src/Parser/ParsedExpressions/ParsedExpression.hpp b/src/Parser/ParsedExpressions/ParsedExpression.hpp index 3c2e5db..a467400 100644 --- a/src/Parser/ParsedExpressions/ParsedExpression.hpp +++ b/src/Parser/ParsedExpressions/ParsedExpression.hpp @@ -1,4 +1,3 @@ -#include #ifndef PORYGONLANG_PARSEDEXPRESSION_HPP #define PORYGONLANG_PARSEDEXPRESSION_HPP @@ -12,7 +11,7 @@ #include "../../Utilities/HashedString.hpp" namespace Porygon::Parser { - enum class ParsedExpressionKind { + enum class ParsedExpressionKind : uint8_t { Bad, LiteralInteger, diff --git a/src/Parser/ParsedStatements/ParsedStatement.hpp b/src/Parser/ParsedStatements/ParsedStatement.hpp index 1691930..eb60b1b 100644 --- a/src/Parser/ParsedStatements/ParsedStatement.hpp +++ b/src/Parser/ParsedStatements/ParsedStatement.hpp @@ -1,6 +1,3 @@ -#include - -#include #ifndef PORYGONLANG_PARSEDSTATEMENT_HPP #define PORYGONLANG_PARSEDSTATEMENT_HPP @@ -14,7 +11,7 @@ #include "../TypedVariableIdentifier.hpp" namespace Porygon::Parser { - enum class ParsedStatementKind { + enum class ParsedStatementKind : uint8_t { Bad, Script, Block, @@ -23,7 +20,8 @@ namespace Porygon::Parser { IndexAssignment, FunctionDeclaration, Return, - Conditional + Conditional, + NumericalFor }; class ParsedStatement { @@ -278,5 +276,51 @@ namespace Porygon::Parser { return _elseStatement; } }; + + class ParsedNumericalForStatement : public ParsedStatement { + const HashedString _identifier; + const ParsedExpression *_start; + const ParsedExpression *_end; + const ParsedExpression *_step; + const ParsedStatement *_block; + public: + ParsedNumericalForStatement(const HashedString identifier, const ParsedExpression *start, + const ParsedExpression *end, const ParsedExpression *step, const ParsedStatement *block, + unsigned int startPos, unsigned int length) + : ParsedStatement(startPos, length), _identifier(identifier), _start(start), _end(end), _step(step), _block(block) { + } + + ~ParsedNumericalForStatement() final { + delete _start; + delete _end; + delete _step; + delete _block; + } + + const ParsedStatementKind GetKind() const final { + return ParsedStatementKind::NumericalFor; + } + + const HashedString GetIdentifier() const{ + return _identifier; + } + + const ParsedExpression *GetStart() const{ + return _start; + } + + const ParsedExpression *GetEnd() const{ + return _end; + } + + const ParsedExpression *GetStep() const{ + return _step; + } + + const ParsedStatement *GetBlock() const{ + return _block; + } + }; + } #endif //PORYGONLANG_PARSEDSTATEMENT_HPP diff --git a/src/Parser/Parser.cpp b/src/Parser/Parser.cpp index 8a6daf2..fbb28a6 100644 --- a/src/Parser/Parser.cpp +++ b/src/Parser/Parser.cpp @@ -44,6 +44,8 @@ namespace Porygon::Parser { return this->ParseReturnStatement(current); case TokenKind::IfKeyword: return this->ParseIfStatement(current); + case TokenKind ::ForKeyword: + return this->ParseForStatement(current); default: break; } @@ -191,7 +193,6 @@ namespace Porygon::Parser { } ParsedStatement *Parser::ParseReturnStatement(const IToken *current) { - //TODO: if next token is on a different line, don't parse it as return expression. auto start = current->GetStartPosition(); auto startLine = this -> ScriptData -> Diagnostics ->GetLineFromPosition(start); if (startLine != this -> ScriptData -> Diagnostics -> GetLineFromPosition(this -> Peek() -> GetStartPosition())){ @@ -224,6 +225,55 @@ namespace Porygon::Parser { return new ParsedConditionalStatement(condition, block, start, block->GetEndPosition() - start); } + ParsedStatement *Parser::ParseForStatement(const IToken *current) { + auto identifier = this -> Next(); + if (this -> Peek()->GetKind() == TokenKind::AssignmentToken){ + return ParseNumericForStatement(identifier); + } else { + return ParseGenericForStatement(identifier); + } + } + + ParsedStatement *Parser::ParseNumericForStatement(const IToken *current) { + auto identifier = (IdentifierToken*)current; + this->Next(); // consume assignment token + bool hasErrors = false; + auto start = this ->ParseExpression(this ->Next()); + auto comma = this -> Next(); // consume comma token + if (comma->GetKind() != TokenKind::CommaToken){ + hasErrors = true; + this->ScriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::UnexpectedToken, comma->GetStartPosition(), + comma->GetLength()); + } + auto end = this -> ParseExpression(this -> Next()); + ParsedExpression *step = nullptr; + if (this -> Peek()->GetKind() == TokenKind::CommaToken){ + this -> Next(); + step = this -> ParseExpression(this -> Next()); + } + auto doToken = this ->Next(); + if (doToken->GetKind() != TokenKind::DoKeyword && !hasErrors){ + hasErrors = true; + this->ScriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::UnexpectedToken, doToken->GetStartPosition(), + doToken->GetLength()); + } + auto block = this -> ParseBlock({TokenKind ::EndKeyword}); + auto startPos = current->GetStartPosition(); + if (hasErrors){ + return new ParsedBadStatement(startPos, block -> GetEndPosition() - startPos); + } + return new ParsedNumericalForStatement(identifier->GetValue(), start, end, step, block, startPos, block->GetEndPosition() - startPos); + + } + + ParsedStatement *Parser::ParseGenericForStatement(const IToken *current) { + return nullptr; + } + + ///////////////// + // Expressions // + ///////////////// + ParsedExpression *Parser::ParseExpression(const IToken *current) { auto expression = this->ParseBinaryExpression(current, OperatorPrecedence::No); auto peekKind = this->Peek()->GetKind(); diff --git a/src/Parser/Parser.hpp b/src/Parser/Parser.hpp index 81c0951..e3ab72e 100644 --- a/src/Parser/Parser.hpp +++ b/src/Parser/Parser.hpp @@ -29,14 +29,15 @@ namespace Porygon::Parser { const IToken *Next(); + // Statements + ParsedStatement *ParseStatement(const IToken *current); ParsedStatement *ParseVariableAssignment(const IToken *current); ParsedStatement *ParseIndexAssignment(ParsedExpression *indexer); - ParsedStatement * - ParseBlock(const vector &endTokens, const vector &openStatements = {}); + ParsedStatement *ParseBlock(const vector &endTokens, const vector &openStatements = {}); ParsedStatement *ParseFunctionDeclaration(const IToken *current); @@ -44,6 +45,12 @@ namespace Porygon::Parser { ParsedStatement *ParseIfStatement(const IToken *current); + ParsedStatement *ParseForStatement(const IToken *current); + ParsedStatement *ParseNumericForStatement(const IToken *current); + ParsedStatement *ParseGenericForStatement(const IToken *current); + + // Expressions + ParsedExpression *ParseExpression(const IToken *current); ParsedExpression *ParseBinaryExpression(const IToken *current, OperatorPrecedence parentPrecedence); diff --git a/src/Parser/TokenKind.hpp b/src/Parser/TokenKind.hpp index 2b12964..dd2e0f2 100644 --- a/src/Parser/TokenKind.hpp +++ b/src/Parser/TokenKind.hpp @@ -2,7 +2,7 @@ #define PORYGONLANG_TOKENKIND_HPP namespace Porygon::Parser { - enum class TokenKind { + enum class TokenKind : uint8_t { EndOfFile, BadToken, WhiteSpace, diff --git a/tests/integration/Functions.cpp b/tests/integration/FunctionsTests.cpp similarity index 100% rename from tests/integration/Functions.cpp rename to tests/integration/FunctionsTests.cpp diff --git a/tests/integration/LoopTests.cpp b/tests/integration/LoopTests.cpp new file mode 100644 index 0000000..c056952 --- /dev/null +++ b/tests/integration/LoopTests.cpp @@ -0,0 +1,64 @@ +#ifdef TESTS_BUILD +#include +#include "../src/Script.hpp" +using namespace Porygon; + +TEST_CASE( "Numerical for loop without step", "[integration]" ) { + auto script = Script::Create(uR"( +result = 0 +for i = 0,10 do + result = result + 3 +end +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + script->Evaluate(); + auto var = script->GetVariable(u"result"); + REQUIRE(var->EvaluateInteger() == 33); + delete script; +} + +TEST_CASE( "Numerical for loop with step", "[integration]" ) { + auto script = Script::Create(uR"( +result = 0 +for i = 0,10,3 do + result = result + 3 +end +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + script->Evaluate(); + auto var = script->GetVariable(u"result"); + REQUIRE(var->EvaluateInteger() == 12); + delete script; +} + +TEST_CASE( "Numerical for loop with negative step", "[integration]" ) { + auto script = Script::Create(uR"( +result = 0 +for i = 10,0,-1 do + result = result + 3 +end +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + script->Evaluate(); + auto var = script->GetVariable(u"result"); + REQUIRE(var->EvaluateInteger() == 33); + delete script; +} + + +TEST_CASE( "Numerical for loop creates variable", "[integration]" ) { + auto script = Script::Create(uR"( +result = 0 +for i = 0,5 do + result = result + i +end +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + script->Evaluate(); + auto var = script->GetVariable(u"result"); + REQUIRE(var->EvaluateInteger() == 15); + delete script; +} + + +#endif diff --git a/tests/integration/Tables.cpp b/tests/integration/TablesTests.cpp similarity index 100% rename from tests/integration/Tables.cpp rename to tests/integration/TablesTests.cpp diff --git a/tests/integration/UserData.cpp b/tests/integration/UserDataTests.cpp similarity index 100% rename from tests/integration/UserData.cpp rename to tests/integration/UserDataTests.cpp diff --git a/tests/integration/Variables.cpp b/tests/integration/VariablesTests.cpp similarity index 100% rename from tests/integration/Variables.cpp rename to tests/integration/VariablesTests.cpp