diff --git a/src/Binder/BoundVariables/BoundScope.cpp b/src/Binder/BoundVariables/BoundScope.cpp index 9e0a5a9..d82394f 100644 --- a/src/Binder/BoundVariables/BoundScope.cpp +++ b/src/Binder/BoundVariables/BoundScope.cpp @@ -2,6 +2,7 @@ #include "BoundScope.hpp" +#include "../../StandardLibraries/StaticScope.hpp" namespace Porygon::Binder { BoundScope::BoundScope(map *tableScope) { @@ -40,16 +41,21 @@ namespace Porygon::Binder { } int BoundScope::Exists(const Utilities::HashedString& key) { + for (int i = _currentScope - 1; i >= 0; i--) { + auto scope = _localScope.at(i); + auto found = scope->find(key); + if (found != scope->end()) { + return i + 1; + } + } + auto found = this->_tableScope->find(key); if (found != _tableScope->end()) { return 0; } - for (int i = _currentScope - 1; i >= 0; i--) { - auto scope = _localScope.at(i); - found = scope->find(key); - if (found != scope->end()) { - return i + 1; - } + auto result = StandardLibraries::StaticScope::GetBoundVariable(key); + if (result != nullptr){ + return -2; } return -1; } @@ -61,6 +67,8 @@ namespace Porygon::Binder { return find->second; } return nullptr; + } else if (scope == -2){ + return StandardLibraries::StaticScope::GetBoundVariable(identifier); } else { auto s = this->_localScope.at(scope - 1); auto find = s->find(identifier); @@ -83,7 +91,7 @@ namespace Porygon::Binder { VariableAssignment BoundScope::AssignVariable(const Utilities::HashedString& identifier, const std::shared_ptr &type) { int exists = this->Exists(identifier); - if (exists == -1) { + if (exists < 0) { // Creation _tableScope->insert({identifier, new BoundVariable(type)}); return VariableAssignment(VariableAssignmentResult::Ok, new BoundVariableKey(identifier, 0, true)); diff --git a/src/Evaluator/EvalValues/EvalValueHelper.hpp b/src/Evaluator/EvalValues/EvalValueHelper.hpp index 5e3bca7..770a48b 100644 --- a/src/Evaluator/EvalValues/EvalValueHelper.hpp +++ b/src/Evaluator/EvalValues/EvalValueHelper.hpp @@ -49,7 +49,7 @@ namespace Porygon::Evaluation{ return new BooleanEvalValue(b); } static EvalValue* Create(const string& s){ - return new StringEvalValue(Utilities::StringUtils::StringToU16String(s)); + return new StringEvalValue(Utilities::StringUtils::ToUTF8(s)); } static EvalValue* Create(u16string s){ return new StringEvalValue(std::move(s)); diff --git a/src/Evaluator/EvalValues/NilEvalValue.hpp b/src/Evaluator/EvalValues/NilEvalValue.hpp new file mode 100644 index 0000000..b681d81 --- /dev/null +++ b/src/Evaluator/EvalValues/NilEvalValue.hpp @@ -0,0 +1,25 @@ +#ifndef PORYGONLANG_NILEVALVALUE_HPP +#define PORYGONLANG_NILEVALVALUE_HPP + +#include "EvalValue.hpp" + +namespace Porygon::Evaluation{ + class NilEvalValue : public EvalValue{ + const TypeClass GetTypeClass() const final{ + return TypeClass ::Nil; + } + const bool operator==(EvalValue *b) const final{ + return b->GetTypeClass() == TypeClass ::Nil; + } + + const shared_ptr Clone() const final{ + return make_shared(); + } + + const std::size_t GetHashCode() const final{ + return 0; + } + }; +} + +#endif //PORYGONLANG_NILEVALVALUE_HPP diff --git a/src/Evaluator/EvaluationException.hpp b/src/Evaluator/EvaluationException.hpp index 30431df..e2c0a16 100644 --- a/src/Evaluator/EvaluationException.hpp +++ b/src/Evaluator/EvaluationException.hpp @@ -11,14 +11,14 @@ namespace Porygon::Evaluation { class EvaluationException : public std::exception { string _message; public: - explicit EvaluationException(string message) { - _message = std::move(message); + explicit EvaluationException(const string& message) { + _message = defaultErrorText +message; } const string defaultErrorText = "An evaluation exception occurred: "; const char *what() const noexcept final { - return (defaultErrorText + _message).c_str(); + return _message.c_str(); } }; } diff --git a/src/Evaluator/EvaluationScope/EvaluationScope.cpp b/src/Evaluator/EvaluationScope/EvaluationScope.cpp index d4476ba..5e188e1 100644 --- a/src/Evaluator/EvaluationScope/EvaluationScope.cpp +++ b/src/Evaluator/EvaluationScope/EvaluationScope.cpp @@ -1,5 +1,6 @@ #include "EvaluationScope.hpp" +#include "../../StandardLibraries/StaticScope.hpp" #include namespace Porygon::Evaluation { @@ -29,8 +30,11 @@ namespace Porygon::Evaluation { } shared_ptr EvaluationScope::GetVariable(const BoundVariableKey *key) { - if (key->GetScopeId() == 0) { + auto scopeId = key -> GetScopeId(); + if (scopeId== 0) { return _scriptScope->at(key->GetIdentifier()); + } else if(scopeId == -2){ + return StandardLibraries::StaticScope::GetVariable(key->GetIdentifier()); } else { return _localScope[key->GetHash()]; } diff --git a/src/StandardLibraries/BasicLibrary.hpp b/src/StandardLibraries/BasicLibrary.hpp new file mode 100644 index 0000000..5a6bb7b --- /dev/null +++ b/src/StandardLibraries/BasicLibrary.hpp @@ -0,0 +1,56 @@ +#ifndef PORYGONLANG_BASICLIBRARY_HPP +#define PORYGONLANG_BASICLIBRARY_HPP + +#include +#include +#include "../Evaluator/EvaluationException.hpp" +#include "../Evaluator/EvalValues/EvalValue.hpp" +#include "../Evaluator/EvalValues/NilEvalValue.hpp" +#include "../Utilities/StringUtils.hpp" +#include "../Binder/BoundVariables/BoundVariable.hpp" +#include "../UserData/UserDataFunction.hpp" +#include "../UserData/UserDataFunctionType.hpp" + +namespace Porygon::StandardLibraries{ + class BasicLibrary{ + static Evaluation::EvalValue* _error(void*, Evaluation::EvalValue* parameters[], int parameterCount){ + auto message = parameters[0]->EvaluateString(); + auto conv = Utilities::StringUtils::FromUTF8(message); + throw Evaluation::EvaluationException(conv); + } + + static Evaluation::EvalValue* _assert(void*, Evaluation::EvalValue* parameters[], int parameterCount){ + auto assertion = parameters[0]->EvaluateBool(); + if (!assertion){ + throw Evaluation::EvaluationException("assertion failed!"); + } + return new Evaluation::BooleanEvalValue(true); + } + + + public: + static void RegisterVariables(std::map* bound, + std::map>* values){ + // Register error function + auto errorFuncType = make_shared( + make_shared(TypeClass::Nil), + vector>{make_shared(false, 0)}); + auto errorFunc = make_shared(_error, nullptr); + auto errorLookup = Utilities::HashedString::CreateLookup(u"error"); + bound->insert({errorLookup, new Binder::BoundVariable(errorFuncType)}); + values->insert({errorLookup, errorFunc}); + + // Register assert function + auto assertFuncType = make_shared( + make_shared(TypeClass::Bool), + vector>{make_shared(TypeClass::Bool)}); + auto assertFunc = make_shared(_assert, nullptr); + auto assertLookup = Utilities::HashedString::CreateLookup(u"assert"); + bound->insert({assertLookup, new Binder::BoundVariable(assertFuncType)}); + values->insert({assertLookup, assertFunc}); + + } + }; +} + +#endif //PORYGONLANG_BASICLIBRARY_HPP diff --git a/src/StandardLibraries/StaticScope.cpp b/src/StandardLibraries/StaticScope.cpp new file mode 100644 index 0000000..b882c6b --- /dev/null +++ b/src/StandardLibraries/StaticScope.cpp @@ -0,0 +1,3 @@ +#include "StaticScope.hpp" + +Porygon::StandardLibraries::StaticScope::InternalScope Porygon::StandardLibraries::StaticScope::_internal; \ No newline at end of file diff --git a/src/StandardLibraries/StaticScope.hpp b/src/StandardLibraries/StaticScope.hpp new file mode 100644 index 0000000..de2cf87 --- /dev/null +++ b/src/StandardLibraries/StaticScope.hpp @@ -0,0 +1,51 @@ +#include + +#ifndef PORYGONLANG_STATICSCOPE_HPP +#define PORYGONLANG_STATICSCOPE_HPP + +#include +#include "../Utilities/HashedString.hpp" +#include "../Binder/BoundVariables/BoundVariable.hpp" +#include "../Evaluator/EvalValues/EvalValue.hpp" +#include "BasicLibrary.hpp" + +using namespace std; +namespace Porygon::StandardLibraries{ + /*! + \class StaticScope + \brief The static shared scope for all scripts. Variables registered in here should be stateless. + */ + class StaticScope { + class InternalScope{ + public: + map _boundVariables; + map> _variables; + + InternalScope(){ + BasicLibrary::RegisterVariables(&_boundVariables, &_variables); + } + }; + + static InternalScope _internal; + public: + static void RegisterVariable(const Utilities::HashedString& identifier, shared_ptr type, Evaluation::EvalValue* value){ + _internal._boundVariables.insert({identifier, new Binder::BoundVariable(std::move(type))}); + _internal._variables.insert({identifier, shared_ptr(value)}); + } + + static Binder::BoundVariable* GetBoundVariable(const Utilities::HashedString &identifier){ + auto found = _internal._boundVariables.find(identifier); + if (found != _internal._boundVariables.end()) { + return found->second; + } + return nullptr; + } + + static shared_ptr GetVariable(const Utilities::HashedString &identifier){ + return _internal._variables[identifier]; + } + }; +} + + +#endif //PORYGONLANG_STATICSCOPE_HPP diff --git a/src/UserData/UserDataFunction.hpp b/src/UserData/UserDataFunction.hpp index 9081269..0ba8f64 100644 --- a/src/UserData/UserDataFunction.hpp +++ b/src/UserData/UserDataFunction.hpp @@ -23,11 +23,7 @@ namespace Porygon::UserData{ } EvalValue* Call(EvalValue* parameters[], int parameterCount){ - try{ - return _call(_obj, parameters, parameterCount); - } catch (...){ - throw Evaluation::EvaluationException("An error occurred while executing a userdata function."); - } + return _call(_obj, parameters, parameterCount); } const shared_ptr Clone() const final { diff --git a/src/Utilities/StringUtils.cpp b/src/Utilities/StringUtils.cpp index 9429e84..542e6c7 100644 --- a/src/Utilities/StringUtils.cpp +++ b/src/Utilities/StringUtils.cpp @@ -1,5 +1,5 @@ #include "StringUtils.hpp" namespace Porygon::Utilities{ - std::wstring_convert, char16_t> StringUtils::conv; + std::wstring_convert, char16_t> StringUtils::to_16; } diff --git a/src/Utilities/StringUtils.hpp b/src/Utilities/StringUtils.hpp index 6fcfce3..533c7dd 100644 --- a/src/Utilities/StringUtils.hpp +++ b/src/Utilities/StringUtils.hpp @@ -10,13 +10,17 @@ namespace Porygon::Utilities{ class StringUtils{ - static std::wstring_convert, char16_t> conv; + private: + static std::wstring_convert, char16_t> to_16; public: static std::u16string IntToString(long const &i) { - return conv.from_bytes(std::to_string(i)); + return to_16.from_bytes(std::to_string(i)); } - static std::u16string StringToU16String(const std::string& s) { - return conv.from_bytes(s); + static std::u16string ToUTF8(const std::string &s) { + return to_16.from_bytes(s); + } + static std::string FromUTF8(const std::u16string &s) { + return to_16.to_bytes(s); } }; diff --git a/tests/standardLibraries/BasicLibrary.cpp b/tests/standardLibraries/BasicLibrary.cpp new file mode 100644 index 0000000..c4c2fc0 --- /dev/null +++ b/tests/standardLibraries/BasicLibrary.cpp @@ -0,0 +1,44 @@ +#ifdef TESTS_BUILD +#include +#include "../src/Script.hpp" +#include + +using namespace Porygon; + +TEST_CASE( "Error func throws error", "[integration]" ) { + Script* script = Script::Create(u"error('foo bar')"); + REQUIRE(!script->Diagnostics -> HasErrors()); + try{ + script -> Evaluate(); + throw; + } catch (const EvaluationException& e){ + auto err = e.what(); + REQUIRE(std::strcmp(err, "An evaluation exception occurred: foo bar") == 0); + } + delete script; +} + +TEST_CASE( "Assert func throws error if argument is false", "[integration]" ) { + Script* script = Script::Create(u"assert(false)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + try{ + script -> Evaluate(); + throw; + } catch (const EvaluationException& e){ + auto err = e.what(); + REQUIRE(std::strcmp(err, "An evaluation exception occurred: assertion failed!") == 0); + } + delete script; +} + +TEST_CASE( "Assert func does not throw if argument is true", "[integration]" ) { + Script* script = Script::Create(u"assert(true)"); + REQUIRE(!script->Diagnostics -> HasErrors()); + script -> Evaluate(); + delete script; +} + + + +#endif +