diff --git a/src/Binder/Binder.cpp b/src/Binder/Binder.cpp index 7ef8132..c368635 100644 --- a/src/Binder/Binder.cpp +++ b/src/Binder/Binder.cpp @@ -4,6 +4,7 @@ #include "../ScriptTypes/TableScriptType.hpp" #include "BoundExpressions/BoundTableExpression.hpp" #include "BoundExpressions/BoundFunctionCallExpression.hpp" +#include "BoundExpressions/BoundRequireExpression.hpp" #include "../UserData/UserDataScriptType.hpp" using namespace Porygon::Parser; @@ -16,7 +17,7 @@ namespace Porygon::Binder { binder._scope = scriptScope; auto statements = s->GetStatements(); vector boundStatements(statements->size()); - for (int i = 0; i < statements->size(); i++) { + for (size_t i = 0; i < statements->size(); i++) { boundStatements[i] = binder.BindStatement(statements->at(i)); } return new BoundScriptStatement(boundStatements, scriptScope->GetLocalVariableCount()); @@ -63,7 +64,7 @@ namespace Porygon::Binder { auto statements = ((ParsedBlockStatement *) statement)->GetStatements(); vector boundStatements(statements->size()); this->_scope->GoInnerScope(); - for (int i = 0; i < statements->size(); i++) { + for (size_t i = 0; i < statements->size(); i++) { boundStatements[i] = this->BindStatement(statements->at(i)); } this->_scope->GoOuterScope(); @@ -137,7 +138,7 @@ namespace Porygon::Binder { auto parameterKeys = vector>(parameters->size()); this->_scope->GoInnerScope(); - for (long i = 0; i < parameters->size(); i++) { + for (size_t i = 0; i < parameters->size(); i++) { auto var = parameters->at(i); auto parsedType = ParseTypeIdentifier(var->GetType()); if (parsedType == nullptr) { @@ -567,7 +568,14 @@ namespace Porygon::Binder { } BoundExpression *Binder::BindFunctionCall(const FunctionCallExpression *expression) { - auto functionExpression = BindExpression(expression->GetFunction()); + auto func = expression->GetFunction(); + if (func->GetKind() == ParsedExpressionKind::Variable){ + auto variable = dynamic_cast(func); + if (variable->GetValue().GetHash() == HashedString::ConstHash("require")){ + return this->BindRequire(expression); + } + } + auto functionExpression = BindExpression(func); auto type = functionExpression->GetType(); if (type->GetClass() != TypeClass::Function) { this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::ExpressionIsNotAFunction, @@ -579,7 +587,7 @@ namespace Porygon::Binder { auto givenParameters = expression->GetParameters(); auto givenParameterTypes = vector>(givenParameters->size()); vector boundParameters = vector(givenParameters->size()); - for (long i = 0; i < givenParameters->size(); i++){ + for (size_t i = 0; i < givenParameters->size(); i++){ boundParameters[i] = this -> BindExpression(givenParameters->at(i)); givenParameterTypes[i] = boundParameters[i]->GetType(); } @@ -596,6 +604,44 @@ namespace Porygon::Binder { expression->GetStartPosition(), expression->GetLength()); } + BoundExpression *Binder::BindRequire(const FunctionCallExpression* exp){ + auto parameters = exp->GetParameters(); + if (parameters->size() != 1){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::InvalidFunctionParameters, + exp->GetStartPosition(), + exp->GetLength()); + return new BoundBadExpression(exp->GetStartPosition(), exp ->GetLength()); + } + auto parameter = parameters->at(0); + auto boundParameter = this -> BindExpression(parameter); + if (boundParameter->GetKind() != BoundExpressionKind::LiteralString){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::InvalidFunctionParameters, + exp->GetStartPosition(), + exp->GetLength()); + return new BoundBadExpression(exp->GetStartPosition(), exp ->GetLength()); + } + auto key = *dynamic_cast(boundParameter)->GetValue(); + auto opt = this->_scriptData->GetScriptOptions(); + auto transformedKey = Utilities::StringUtils::FromUTF8(key); + delete boundParameter; + if (!opt->DoesModuleExist(transformedKey)){ + this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::ModuleDoesntExist, + exp->GetStartPosition(), + exp->GetLength()); + return new BoundBadExpression(exp->GetStartPosition(), exp ->GetLength()); + } + auto module = opt->ResolveModule(transformedKey); + if (module -> GetReturnType() == nullptr){ + for (const auto& v: *module->GetScriptVariables()){ + //TODO: Currently a hack, will always make all variables nil + auto type = make_shared(TypeClass::Nil); + this -> _scope -> AssignVariable(v.first, type); + } + + } + return new BoundRequireExpression(module, exp->GetStartPosition(), exp ->GetLength()); + } + BoundExpression *Binder::BindIndexExpression(const IndexExpression *expression, bool setter) { auto indexer = this->BindExpression(expression->GetIndexer()); auto index = this->BindExpression(expression->GetIndex()); @@ -672,7 +718,7 @@ namespace Porygon::Binder { if (!boundExpressions.empty()) { boundExpressions[0] = this->BindExpression(expressions->at(0)); valueType = boundExpressions[0]->GetType(); - for (long i = 1; i < expressions->size(); i++) { + for (size_t i = 1; i < expressions->size(); i++) { boundExpressions[i] = this->BindExpression(expressions->at(i)); if (boundExpressions[i]->GetType().get()->operator!=(valueType.get())) { this->_scriptData->Diagnostics->LogError(Diagnostics::DiagnosticCode::InvalidTableValueType, diff --git a/src/Binder/Binder.hpp b/src/Binder/Binder.hpp index 14e37b6..b9adb11 100644 --- a/src/Binder/Binder.hpp +++ b/src/Binder/Binder.hpp @@ -7,6 +7,7 @@ #include "BoundVariables/BoundScope.hpp" #include "../Parser/ParsedExpressions/ParsedTableExpression.hpp" #include "../ScriptTypes/FunctionScriptType.hpp" +#include "BoundExpressions/BoundFunctionCallExpression.hpp" using namespace std; using namespace Porygon::Parser; @@ -40,6 +41,7 @@ namespace Porygon::Binder { BoundExpression *BindBinaryOperator(const BinaryExpression *expression); BoundExpression *BindUnaryOperator(const UnaryExpression *expression); BoundExpression *BindFunctionCall(const FunctionCallExpression *expression); + BoundExpression *BindRequire(const FunctionCallExpression *exp); BoundExpression *BindIndexExpression(const IndexExpression *expression, bool setter); BoundExpression *BindNumericalTableExpression(const ParsedNumericalTableExpression *expression); BoundExpression *BindTableExpression(const ParsedTableExpression *expression); diff --git a/src/Binder/BoundExpressions/BoundExpression.hpp b/src/Binder/BoundExpressions/BoundExpression.hpp index aa496fc..aed00cc 100644 --- a/src/Binder/BoundExpressions/BoundExpression.hpp +++ b/src/Binder/BoundExpressions/BoundExpression.hpp @@ -28,6 +28,7 @@ namespace Porygon::Binder { PeriodIndex, NumericalTable, Table, + Require, }; class BoundExpression { @@ -331,6 +332,7 @@ namespace Porygon::Binder { return &_expressions; } }; + } #endif //PORYGONLANG_BOUNDEXPRESSION_HPP diff --git a/src/Binder/BoundExpressions/BoundRequireExpression.hpp b/src/Binder/BoundExpressions/BoundRequireExpression.hpp new file mode 100644 index 0000000..0f7702f --- /dev/null +++ b/src/Binder/BoundExpressions/BoundRequireExpression.hpp @@ -0,0 +1,32 @@ +#ifndef PORYGONLANG_BOUNDREQUIREEXPRESSION_HPP +#define PORYGONLANG_BOUNDREQUIREEXPRESSION_HPP + +#include "BoundExpression.hpp" +#include "../../Script.hpp" + +namespace Porygon::Binder { + class BoundRequireExpression : public BoundExpression { + Script* _module; + public: + BoundRequireExpression(Script* script, unsigned int start, + unsigned int length) + : BoundExpression(start, length, script->GetReturnType()), + _module(script){} + + ~BoundRequireExpression() final{ + delete _module; + } + + [[nodiscard]] + inline Script* GetModule() const{ + return _module; + } + + [[nodiscard]] + inline BoundExpressionKind GetKind() const final { + return BoundExpressionKind::Require; + } + }; +} + +#endif //PORYGONLANG_BOUNDREQUIREEXPRESSION_HPP diff --git a/src/Diagnostics/DiagnosticCode.hpp b/src/Diagnostics/DiagnosticCode.hpp index f859e26..38ee2cb 100644 --- a/src/Diagnostics/DiagnosticCode.hpp +++ b/src/Diagnostics/DiagnosticCode.hpp @@ -26,7 +26,8 @@ namespace Porygon::Diagnostics { UserDataFieldNoSetter, NumericalForArgumentNotANumber, CantIterateExpression, - InvalidFunctionParameters + InvalidFunctionParameters, + ModuleDoesntExist }; } #endif //PORYGONLANG_DIAGNOSTICCODE_HPP diff --git a/src/Evaluator/Evaluator.cpp b/src/Evaluator/Evaluator.cpp index 4a78626..dfbaf99 100644 --- a/src/Evaluator/Evaluator.cpp +++ b/src/Evaluator/Evaluator.cpp @@ -7,6 +7,7 @@ #include "EvalValues/TableEvalValue.hpp" #include "../Binder/BoundExpressions/BoundTableExpression.hpp" #include "../Binder/BoundExpressions/BoundFunctionCallExpression.hpp" +#include "../Binder/BoundExpressions/BoundRequireExpression.hpp" #include "../ScriptTypes/TableScriptType.hpp" #include "../UserData/UserDataFunction.hpp" #include "EvalValues/NumericalTableEvalValue.hpp" @@ -254,6 +255,8 @@ namespace Porygon::Evaluation { return this->EvaluateNumericTableExpression(expression); case BoundExpressionKind::Table: return this->EvaluateComplexTableExpression(expression); + case BoundExpressionKind::Require: + return this -> EvaluateRequireExpression(expression); } } @@ -396,4 +399,16 @@ namespace Porygon::Evaluation { this->_evaluationScope = currentEvaluator; return new TableEvalValue(variables); } + + EvalValuePointer Evaluator::EvaluateRequireExpression(const BoundExpression* expression) { + auto module = dynamic_cast(expression)->GetModule(); + if (module ->GetReturnType() == nullptr){ + for (const auto& v: *module->GetScriptVariables()){ + this->_scriptVariables->insert({v.first, v.second.Clone()}); + } + return nullptr; + } else{ + return module -> Evaluate().Take(); + } + } } \ No newline at end of file diff --git a/src/Evaluator/Evaluator.hpp b/src/Evaluator/Evaluator.hpp index 0ac3392..3010de0 100644 --- a/src/Evaluator/Evaluator.hpp +++ b/src/Evaluator/Evaluator.hpp @@ -47,6 +47,8 @@ namespace Porygon::Evaluation{ EvalValuePointer EvaluateNumericTableExpression(const BoundExpression *expression); EvalValuePointer EvaluateComplexTableExpression(const BoundExpression *expression); + EvalValuePointer EvaluateRequireExpression(const BoundExpression* expression); + EvalValuePointer GetVariable(const BoundVariableExpression *expression); public: explicit Evaluator(map* scriptVariables, const ScriptOptions* options) diff --git a/src/Script.hpp b/src/Script.hpp index 7a0eea0..b1fffc4 100644 --- a/src/Script.hpp +++ b/src/Script.hpp @@ -20,7 +20,7 @@ namespace Porygon{ Evaluator* _evaluator; map* _scriptVariables; shared_ptr _boundScript; - shared_ptr _returnType; + shared_ptr _returnType = nullptr; ScriptOptions* _scriptOptions; explicit Script(const u16string&); @@ -57,6 +57,10 @@ namespace Porygon{ const EvalValue* CallFunction(const u16string& key, const vector& variables); bool HasFunction(const u16string& key); + + const map* GetScriptVariables(){ + return _scriptVariables; + } }; } diff --git a/src/ScriptOptions.cpp b/src/ScriptOptions.cpp index a8630db..1d3987c 100644 --- a/src/ScriptOptions.cpp +++ b/src/ScriptOptions.cpp @@ -1,18 +1,30 @@ #include +#include +#include #include "ScriptOptions.hpp" #include "Utilities/StringUtils.hpp" +#include "Script.hpp" Porygon::ScriptOptions Porygon::ScriptOptions::DefaultScriptOptions; std::streambuf* Porygon::ScriptOptions::_printBuffer = std::cout.rdbuf(); std::ostream* Porygon::ScriptOptions::_printStream = new std::ostream(Porygon::ScriptOptions::_printBuffer); -static void DefaultPrint(const char16_t* s){ +void Porygon::ScriptOptions::DefaultPrint(const char16_t *s) { Porygon::ScriptOptions::GetDefaultScriptOptions()->GetPrintStream() << Porygon::Utilities::StringUtils::FromUTF8(s) << std::endl; } -void (*Porygon::ScriptOptions::_print)(const char16_t*) = DefaultPrint; +bool Porygon::ScriptOptions::DefaultModuleExists(const std::string& moduleName) { + return std::filesystem::exists(moduleName); +} +Porygon::Script *Porygon::ScriptOptions::DefaultResolveModule(const std::string& moduleName) { + auto stream = std::ifstream(moduleName); + std::basic_stringstream stringStream; + stringStream << stream.rdbuf(); + auto str = std::u16string(stringStream.str()); + return Porygon::Script::Create(str); +} extern "C"{ void SetDefaultPrintFunc(void (*func)(const char16_t*)){ diff --git a/src/ScriptOptions.hpp b/src/ScriptOptions.hpp index 95e1525..240b17d 100644 --- a/src/ScriptOptions.hpp +++ b/src/ScriptOptions.hpp @@ -2,28 +2,54 @@ #define PORYGONLANG_SCRIPTOPTIONS_HPP #include - +#include namespace Porygon{ + class Script; + class ScriptOptions{ static Porygon::ScriptOptions DefaultScriptOptions; - static void (*_print)(const char16_t* s); + static void DefaultPrint(const char16_t* s); + static bool DefaultModuleExists(const std::string& moduleName); + static Script* DefaultResolveModule(const std::string& moduleName); + + void (*_print)(const char16_t* s) = DefaultPrint; + bool (*_doesModuleExist)(const std::string& moduleName) = DefaultModuleExists; + Script* (*_resolveModule)(const std::string& moduleName) = DefaultResolveModule; static std::streambuf* _printBuffer; static std::ostream* _printStream; + public: static Porygon::ScriptOptions* GetDefaultScriptOptions(){ return &DefaultScriptOptions; } + inline void Print(const char16_t* s) const{ - ScriptOptions::_print(s); + this -> _print(s); + } + + inline bool DoesModuleExist(std::string moduleName) const{ + return _doesModuleExist(std::move(moduleName)); + } + + inline Script* ResolveModule(std::string moduleName) const{ + return _resolveModule(std::move(moduleName)); } void SetPrintFunc(void (*print)(const char16_t *)){ - ScriptOptions::_print = print; + this -> _print = print; + } + + void SetModuleExistsFunc(bool (*doesModuleExist)(const std::string& moduleName)){ + this ->_doesModuleExist = doesModuleExist; + } + + void SetResolveModuleFunc(Script* (*resolveModule)(const std::string& moduleName)){ + this ->_resolveModule = resolveModule; } std::ostream& GetPrintStream(){ - return *ScriptOptions::_printStream; + return *Porygon::ScriptOptions::_printStream; } void SetPrintStream(std::ostream* stream){ diff --git a/tests/integration/ModuleTests.cpp b/tests/integration/ModuleTests.cpp new file mode 100644 index 0000000..522f454 --- /dev/null +++ b/tests/integration/ModuleTests.cpp @@ -0,0 +1,45 @@ +#ifdef TESTS_BUILD +#include +#include "../src/Script.hpp" +using namespace Porygon; + +class ModuleHandler{ + static unordered_map MODULES; + + ~ModuleHandler(){ + for (auto v: MODULES){ + delete v.second; + } + } + + inline static bool DoesModuleExist(const string& moduleName){ + return MODULES.find(moduleName) != MODULES.end(); + } + + inline static Script* ResolveModule(const string& moduleName){ + return MODULES[moduleName]; + } + +public: + static void Initialize(){ + ScriptOptions::GetDefaultScriptOptions()->SetModuleExistsFunc(DoesModuleExist); + ScriptOptions::GetDefaultScriptOptions()->SetResolveModuleFunc(ResolveModule); + } +}; + +unordered_map ModuleHandler::MODULES = unordered_map{ + {"simple_return", Script::Create(u"return 500")} +}; + +TEST_CASE( "Require simple return script", "[integration]" ) { + ModuleHandler::Initialize(); + auto script = Script::Create(uR"( +return require("simple_return") +)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + auto var = script->Evaluate(); + REQUIRE(var->EvaluateInteger() == 500); + delete script; +} + +#endif