diff --git a/nyan/CMakeLists.txt b/nyan/CMakeLists.txt index 1c14ed2..cb0bc31 100644 --- a/nyan/CMakeLists.txt +++ b/nyan/CMakeLists.txt @@ -65,9 +65,12 @@ add_library(nyan SHARED transaction.cpp type.cpp util.cpp + value_token.cpp value/boolean.cpp value/container.cpp + value/dict.cpp value/file.cpp + value/none.cpp value/number.cpp value/object.cpp value/orderedset.cpp diff --git a/nyan/api_error.cpp b/nyan/api_error.cpp index 5e9f281..a7338b4 100644 --- a/nyan/api_error.cpp +++ b/nyan/api_error.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "api_error.h" @@ -8,40 +8,40 @@ namespace nyan { APIError::APIError(const std::string &msg) - : - Error{msg} {} + : + Error{msg} {} InvalidObjectError::InvalidObjectError() - : - APIError("uninitialized object was used") {} + : + APIError("uninitialized object was used") {} MemberTypeError::MemberTypeError(const fqon_t &objname, const memberid_t &member, const std::string &real_type, const std::string &wrong_type) - : - APIError{ - (static_cast( - std::ostringstream{} << "type mismatch for member " << objname + "." << member - << ": tried to convert real type " << real_type << " to " << wrong_type) - ).str()}, - name{objname}, - member{member}, - real_type{real_type}, - wrong_type{wrong_type} {} + : + APIError{ + (static_cast( + std::ostringstream{} << "type mismatch for member " << objname + "." << member + << ": tried to convert real type " << real_type << " to " << wrong_type) + ).str()}, + objname{objname}, + member{member}, + real_type{real_type}, + wrong_type{wrong_type} {} ObjectNotFoundError::ObjectNotFoundError(const fqon_t &obj_name) - : - APIError{"object not found: " + obj_name}, - name{obj_name} {} + : + APIError{"object not found: " + obj_name}, + objname{obj_name} {} MemberNotFoundError::MemberNotFoundError(const fqon_t &obj_name, const memberid_t &member_name) - : - APIError{"Could not find member " + obj_name + "." + member_name}, - obj_name{obj_name}, - name{member_name} {} + : + APIError{"Could not find member " + obj_name + "." + member_name}, + objname{obj_name}, + name{member_name} {} } // namespace nyan diff --git a/nyan/api_error.h b/nyan/api_error.h index 2dcfeee..ac8a4b5 100644 --- a/nyan/api_error.h +++ b/nyan/api_error.h @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include "error.h" @@ -11,7 +11,7 @@ namespace nyan { */ class APIError : public Error { public: - APIError(const std::string &msg); + APIError(const std::string &msg); }; @@ -22,7 +22,7 @@ class APIError : public Error { */ class InvalidObjectError : public APIError { public: - InvalidObjectError(); + InvalidObjectError(); }; @@ -31,14 +31,29 @@ class InvalidObjectError : public APIError { */ class MemberTypeError : public APIError { public: - MemberTypeError(const fqon_t &objname, const memberid_t &member, - const std::string &real_type, const std::string &wrong_type); + MemberTypeError(const fqon_t &objname, const memberid_t &member, + const std::string &real_type, const std::string &wrong_type); protected: - fqon_t name; - memberid_t member; - std::string real_type; - std::string wrong_type; + /** + * Name (identifier) of the object the member is part of. + */ + fqon_t objname; + + /** + * Name (identifier) of the member. + */ + memberid_t member; + + /** + * Type that the member should have assigned. + */ + std::string real_type; + + /** + * Type that the member has actually assigned. + */ + std::string wrong_type; }; @@ -47,10 +62,13 @@ class MemberTypeError : public APIError { */ class ObjectNotFoundError : public APIError { public: - ObjectNotFoundError(const fqon_t &objname); + ObjectNotFoundError(const fqon_t &objname); protected: - fqon_t name; + /** + * Name (identifier) of the object. + */ + fqon_t objname; }; @@ -59,12 +77,19 @@ class ObjectNotFoundError : public APIError { */ class MemberNotFoundError : public APIError { public: - MemberNotFoundError(const fqon_t &objname, - const memberid_t &membername); + MemberNotFoundError(const fqon_t &objname, + const memberid_t &membername); protected: - fqon_t obj_name; - memberid_t name; + /** + * Name (identifier) of the object the member is part of. + */ + fqon_t objname; + + /** + * Name (identifier) of the member. + */ + memberid_t name; }; } // namespace nyan diff --git a/nyan/ast.cpp b/nyan/ast.cpp index eaaf5f1..3429287 100644 --- a/nyan/ast.cpp +++ b/nyan/ast.cpp @@ -1,8 +1,9 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "ast.h" #include +#include #include #include "compiler.h" @@ -12,691 +13,887 @@ namespace nyan { +unsigned int comma_list(token_type end, + TokenStream &tokens, + size_t limit, + const std::function &func) { + + auto token = tokens.next(); + bool comma_expected = false; + + // add identifiers until the expected end token is reached. + unsigned int index = 0; + while (index < limit) { + if (token->type == token_type::ENDLINE) { + token = tokens.next(); + continue; + } + else if (token->type == end) { + break; + } + else if (token->type == token_type::COMMA) { + if (comma_expected) { + token = tokens.next(); + comma_expected = false; + continue; + } + else { + throw ASTError{"expecting value, but got", *token}; + } + } + else if (comma_expected) { + throw ASTError{"expected comma, but got", *token}; + } + + // value encountered, now process it. + func(*token, tokens); + + comma_expected = true; + index++; + + // now the container is over, or a comma must follow + token = tokens.next(); + } + + return index; +} + + +unsigned int comma_list(token_type end, + TokenStream &tokens, + const std::function &func) { + return comma_list(end, + tokens, + SIZE_MAX, + func); +} -void comma_list(token_type end, - TokenStream &tokens, - const std::function &func) { - - auto token = tokens.next(); - bool comma_expected = false; - - // add identifiers until the expected end token is reached. - while (true) { - if (token->type == token_type::ENDLINE) { - token = tokens.next(); - continue; - } - else if (token->type == end) { - break; - } - else if (token->type == token_type::COMMA) { - if (comma_expected) { - token = tokens.next(); - comma_expected = false; - continue; - } - else { - throw ASTError{"expecting value, but got", *token}; - } - } - else if (comma_expected) { - throw ASTError{"expected comma, but got", *token}; - } - - // value encountered, now process it. - func(*token, tokens); - - comma_expected = true; - // now the container is over, or a comma must follow - token = tokens.next(); - } +std::string ASTBase::str() const { + std::ostringstream builder; + this->strb(builder); + return builder.str(); } -std::string ASTBase::str() const { - std::ostringstream builder; - this->strb(builder); - return builder.str(); +const std::vector &AST::get_args() const { + return this->args; } const std::vector &AST::get_objects() const { - return this->objects; + return this->objects; } const std::vector &AST::get_imports() const { - return this->imports; + return this->imports; } AST::AST(TokenStream &tokens) { - while (tokens.full()) { - auto token = tokens.next(); - if (token->type == token_type::IMPORT) { - this->imports.emplace_back(tokens); - } - else if (token->type == token_type::ID) { - this->objects.emplace_back(*token, tokens); - } - else if (token->type == token_type::ENDFILE) { - // we're done! - if (tokens.empty()) { - return; - } - else { - throw InternalError{"some token came after EOF."}; - } - } - else { - throw ASTError{"expected object name, but got", *token}; - } - } + auto token = tokens.next(); + + // Ensure that AST has version argument + if (token->type == token_type::BANG) { + this->args.emplace_back(tokens); + + if (this->args.front().get_arg().str() != "version") { + throw InternalError{"file must start with 'version' argument, not " + + this->args.front().get_arg().str()}; + } + } + else { + throw InternalError{"missing starting argument: version"}; + } + + while (tokens.full()) { + token = tokens.next(); + if (token->type == token_type::BANG) { + this->args.emplace_back(tokens); + } + else if (token->type == token_type::IMPORT) { + this->imports.emplace_back(tokens); + } + else if (token->type == token_type::ID) { + this->objects.emplace_back(*token, tokens); + } + else if (token->type == token_type::ENDFILE) { + // we're done! + if (tokens.empty()) { + return; + } + else { + throw InternalError{"some token came after EOF."}; + } + } + else { + throw ASTError{"expected object name, but got", *token}; + } + } +} + + +ASTArgument::ASTArgument(TokenStream &tokens) { + auto token = tokens.next(); + + if (token->type == token_type::ID) { + this->arg = IDToken{*token, tokens}; + token = tokens.next(); + } else { + throw ASTError("expected argument keyword, encountered", *token); + } + + while (not token->is_endmarker()) { + if (token->is_content()) { + this->params.emplace_back(*token, tokens); + token = tokens.next(); + } else { + throw ASTError("expected parameter value, encountered", *token); + } + } +} + + +const IDToken &ASTArgument::get_arg() const { + return this->arg; +} + + +const std::vector &ASTArgument::get_params() const { + return this->params; } ASTImport::ASTImport(TokenStream &tokens) { - auto token = tokens.next(); + auto token = tokens.next(); - if (token->type == token_type::ID) { - this->namespace_name = IDToken{*token, tokens}; - } else { - throw ASTError("expected namespace name to import, encountered", *token); - } + if (token->type == token_type::ID) { + this->namespace_name = IDToken{*token, tokens}; + } else { + throw ASTError("expected namespace name to import, encountered", *token); + } - token = tokens.next(); - if (token->type == token_type::AS) { - token = tokens.next(); + token = tokens.next(); + if (token->type == token_type::AS) { + token = tokens.next(); - if (token->type != token_type::ID) { - throw ASTError{"expected namespace alias identifier, but encountered", *token}; - } - this->alias = *token; - token = tokens.next(); - } + if (token->type != token_type::ID) { + throw ASTError{"expected namespace alias identifier, but encountered", *token}; + } + this->alias = *token; + token = tokens.next(); + } - if (token->type != token_type::ENDLINE) { - throw ASTError("newline expected after import, there is", *token); - } + if (token->type != token_type::ENDLINE) { + throw ASTError("newline expected after import, there is", *token); + } } const IDToken &ASTImport::get() const { - return this->namespace_name; + return this->namespace_name; } bool ASTImport::has_alias() const { - return (this->alias.get().size() > 0); + return (this->alias.get().size() > 0); } const Token &ASTImport::get_alias() const { - return this->alias; + return this->alias; } ASTObject::ASTObject(const Token &name, TokenStream &tokens) - : - name{name} { + : + name{name} { - auto token = tokens.next(); + auto token = tokens.next(); - if (token->type == token_type::LANGLE) { - this->ast_targets(tokens); - token = tokens.next(); - } + if (token->type == token_type::LANGLE) { + this->ast_targets(tokens); + token = tokens.next(); + } - if (token->type == token_type::LBRACKET) { - this->ast_inheritance_mod(tokens); - token = tokens.next(); - } + if (token->type == token_type::LBRACKET) { + this->ast_inheritance_mod(tokens); + token = tokens.next(); + } - if (token->type == token_type::LPAREN) { - this->ast_parents(tokens); - } else { - throw ASTError("create the object with (), i got", *token); - } + if (token->type == token_type::LPAREN) { + this->ast_parents(tokens); + } else { + throw ASTError("create the object with (), i got", *token); + } - token = tokens.next(); - if (token->type != token_type::COLON) { - throw ASTError("expected a ':' but instead encountered", *token); - } + token = tokens.next(); + if (token->type != token_type::COLON) { + throw ASTError("expected a ':' but instead encountered", *token); + } - token = tokens.next(); - if (token->type != token_type::ENDLINE) { - throw ASTError("expected a newline there is", *token); - } + token = tokens.next(); + if (token->type != token_type::ENDLINE) { + throw ASTError("expected a newline there is", *token); + } - token = tokens.next(); - if (token->type != token_type::INDENT) { - throw ASTError("expected indentation but instead there's", *token); - } + token = tokens.next(); + if (token->type != token_type::INDENT) { + throw ASTError("expected indentation but instead there's", *token); + } - this->ast_members(tokens); + this->ast_members(tokens); } void ASTObject::ast_targets(TokenStream &tokens) { - auto token = tokens.next(); - if (token->type == token_type::ID) { - this->target = IDToken{*token, tokens}; - } - else { - throw ASTError("expected identifier, encountered", *token); - } + auto token = tokens.next(); + if (token->type == token_type::ID) { + this->target = IDToken{*token, tokens}; + } + else { + throw ASTError("expected identifier, encountered", *token); + } - token = tokens.next(); + token = tokens.next(); - if (token->type != token_type::RANGLE) { - throw ASTError("expected > as patch target end, there is", *token); - } + if (token->type != token_type::RANGLE) { + throw ASTError("expected > as patch target end, there is", *token); + } } void ASTObject::ast_inheritance_mod(TokenStream &tokens) { - comma_list( - token_type::RBRACKET, - tokens, - [this] (const Token & /*token*/, TokenStream &stream) { - stream.reinsert_last(); - this->inheritance_change.emplace_back(stream); - } - ); + comma_list( + token_type::RBRACKET, + tokens, + [this] (const Token & /*token*/, TokenStream &stream) { + stream.reinsert_last(); + this->inheritance_change.emplace_back(stream); + } + ); } void ASTObject::ast_parents(TokenStream &tokens) { - comma_list( - token_type::RPAREN, - tokens, - [this] (const Token &token, TokenStream &stream) { + comma_list( + token_type::RPAREN, + tokens, + [this] (const Token &token, TokenStream &stream) { - if (token.type != token_type::ID) { - throw ASTError{ - "expected inheritance parent identifier, but there is", token - }; - } + if (token.type != token_type::ID) { + throw ASTError{ + "expected inheritance parent identifier, but there is", token + }; + } - this->parents.emplace_back(token, stream); - } - ); + this->parents.emplace_back(token, stream); + } + ); } void ASTObject::ast_members(TokenStream &tokens) { - auto token = tokens.next(); - - while (token->type != token_type::DEDENT and - token->type != token_type::ENDFILE) { - - // content entry of the object - if (token->type == token_type::ID) { - - // determine if this is a member or a nested object. - bool object_next = false; - auto lookahead = tokens.next(); - - if (lookahead->type == token_type::OPERATOR or - lookahead->type == token_type::COLON) { - - object_next = false; - } - else if (lookahead->type == token_type::LANGLE or - lookahead->type == token_type::LBRACKET or - lookahead->type == token_type::LPAREN) { - - object_next = true; - } - else { - // don't think this will ever happen, right? - throw ASTError("could not identify member or nested object defintion " - "after", *token); - } - - tokens.reinsert_last(); - - if (object_next) { - this->objects.emplace_back(*token, tokens); - } - else { - this->members.emplace_back(*token, tokens); - } - } - else if (token->type == token_type::PASS or - token->type == token_type::ELLIPSIS) { - // "empty" member entry. - token = tokens.next(); - if (token->type != token_type::ENDLINE) { - throw ASTError("expected newline after pass or '...', " - "but got", *token); - } - } - else { - throw ASTError("expected member or object identifier, " - "instead got", *token); - } - - token = tokens.next(); - } + auto token = tokens.next(); + + while (token->type != token_type::DEDENT and + token->type != token_type::ENDFILE) { + + // content entry of the object + if (token->type == token_type::ID) { + + // determine if this is a member or a nested object. + bool object_next = false; + auto lookahead = tokens.next(); + + if (lookahead->type == token_type::OPERATOR or + lookahead->type == token_type::COLON) { + + object_next = false; + } + else if (lookahead->type == token_type::LANGLE or + lookahead->type == token_type::LBRACKET or + lookahead->type == token_type::LPAREN) { + + object_next = true; + } + else { + // don't think this will ever happen, right? + throw ASTError("could not identify member or nested object defintion " + "after", *token); + } + + tokens.reinsert_last(); + + if (object_next) { + this->objects.emplace_back(*token, tokens); + } + else { + this->members.emplace_back(*token, tokens); + } + } + else if (token->type == token_type::PASS or + token->type == token_type::ELLIPSIS) { + // "empty" member entry. + token = tokens.next(); + if (token->type != token_type::ENDLINE) { + throw ASTError("expected newline after pass or '...', " + "but got", *token); + } + } + else { + throw ASTError("expected member or object identifier, " + "instead got", *token); + } + + token = tokens.next(); + } } const Token &ASTObject::get_name() const { - return this->name; + return this->name; } const std::vector &ASTObject::get_objects() const { - return this->objects; + return this->objects; } ASTInheritanceChange::ASTInheritanceChange(TokenStream &tokens) { - bool had_operator = false; - bool had_target = false; - auto token = tokens.next(); - - if (token->type == token_type::OPERATOR) { - had_operator = true; - nyan_op action = op_from_token(*token); - - switch (action) { - case nyan_op::ADD: - this->type = inher_change_t::ADD_BACK; - break; - default: - throw ASTError{"unsupported inheritance change operator", *token}; - } - token = tokens.next(); - } - - if (token->type == token_type::ID) { - this->target = IDToken{*token, tokens}; - had_target = true; - - token = tokens.next(); - } - - if (unlikely(not (had_operator or had_target))) { - throw ASTError{"expected inheritance operator or identifier, there is", *token}; - } - - if (token->type == token_type::OPERATOR) { - if (unlikely(had_operator)) { - throw ASTError{ - "inheritance modifier already had operator at front", *token, false - }; - } - - had_operator = true; - nyan_op action = op_from_token(*token); - - switch (action) { - case nyan_op::ADD: - this->type = inher_change_t::ADD_FRONT; - break; - default: - throw ASTError{"unsupported inheritance change operator", *token}; - } - token = tokens.next(); - } - - if (unlikely(not had_operator)) { - throw ASTError{"inheritance change is missing operator", *token, false}; - } - else { - tokens.reinsert_last(); - } + bool had_operator = false; + bool had_target = false; + auto token = tokens.next(); + + if (token->type == token_type::OPERATOR) { + had_operator = true; + nyan_op action = op_from_token(*token); + + switch (action) { + case nyan_op::ADD: + this->type = inher_change_t::ADD_BACK; + break; + default: + throw ASTError{"unsupported inheritance change operator", *token}; + } + token = tokens.next(); + } + + if (token->type == token_type::ID) { + this->target = IDToken{*token, tokens}; + had_target = true; + + token = tokens.next(); + } + + if (unlikely(not (had_operator or had_target))) { + throw ASTError{"expected inheritance operator or identifier, there is", *token}; + } + + if (token->type == token_type::OPERATOR) { + if (unlikely(had_operator)) { + throw ASTError{ + "inheritance modifier already had operator at front", *token, false + }; + } + + had_operator = true; + nyan_op action = op_from_token(*token); + + switch (action) { + case nyan_op::ADD: + this->type = inher_change_t::ADD_FRONT; + break; + default: + throw ASTError{"unsupported inheritance change operator", *token}; + } + token = tokens.next(); + } + + if (unlikely(not had_operator)) { + throw ASTError{"inheritance change is missing operator", *token, false}; + } + else { + tokens.reinsert_last(); + } } inher_change_t ASTInheritanceChange::get_type() const { - return this->type; + return this->type; } const IDToken &ASTInheritanceChange::get_target() const { - return this->target; + return this->target; } ASTMember::ASTMember(const Token &name, TokenStream &tokens) { - this->name = IDToken{name, tokens}; - - auto token = tokens.next(); - bool had_def_or_decl = false; - - // type specifier (the ": typename" part) - if (token->type == token_type::COLON) { - token = tokens.next(); - - if (token->type == token_type::ID) { - this->type = ASTMemberType{*token, tokens}; - had_def_or_decl = true; - } else { - throw ASTError{"expected type name, instead got", *token}; - } - - token = tokens.next(); - } + this->name = IDToken{name, tokens}; + + auto token = tokens.next(); + bool had_def_or_decl = false; + + // type specifier (the ": typename" part) + if (token->type == token_type::COLON) { + token = tokens.next(); + + if (token->type == token_type::ID) { + this->type = ASTMemberType{*token, tokens}; + had_def_or_decl = true; + } else { + throw ASTError{"expected type name, instead got", *token}; + } + + token = tokens.next(); + } + + // value assigning + if (token->type == token_type::OPERATOR) { + this->operation = op_from_token(*token); + + if (this->operation == nyan_op::INVALID) { + throw ASTError{"invalid operation", *token}; + } + + token = tokens.next(); + auto next_token = tokens.next(); + + // look ahead if this is a set type configuration + if (not token->is_endmarker() and + next_token->type == token_type::LBRACE) { + + composite_t ctype; + if (token->get() == "o") { + ctype = composite_t::ORDEREDSET; + } + else { + throw ASTError{"unhandled set type", *token}; + } + + this->value = ASTMemberValue{ctype, tokens}; + } + else { + tokens.reinsert_last(); + + if (token->type == token_type::LBRACE) { + // default => it's a standard set + composite_t ctype = composite_t::SET; + + // Look ahead to check if it's a dict + // TODO: This is really inconvenient + int look_ahead = 0; + while (not (token->type == token_type::RBRACE + or token->type == token_type::COMMA)) { + token = tokens.next(); + look_ahead++; + + if (token->type == token_type::COLON) { + ctype = composite_t::DICT; + break; + } + } + + // Go back to start of container + for (int i = look_ahead; i > 0; i--) { + tokens.reinsert_last(); + } + + this->value = ASTMemberValue{ctype, tokens}; + } + else { + // single-value + + if (unlikely(not token->is_content())) { + throw ASTError{"expected value, have", *token}; + } + + this->value = ASTMemberValue{IDToken{*token, tokens}}; + } + } + + had_def_or_decl = true; + + token = tokens.next(); + } + if (not had_def_or_decl) { + throw ASTError("expected type declaration ( : type ) " + "or value ( = something), instead got", + *token); + } + + if (not token->is_endmarker()) { + throw ASTError("expected newline after member entry, but got", + *token); + } +} - // value assigning - if (token->type == token_type::OPERATOR) { - this->operation = op_from_token(*token); - if (this->operation == nyan_op::INVALID) { - throw ASTError{"invalid operation", *token}; - } +ASTMemberType::ASTMemberType() + : + does_exist{false}, + has_args{false} {} - token = tokens.next(); - auto next_token = tokens.next(); - // look ahead if this is a set type configuration - if (not token->is_endmarker() and - next_token->type == token_type::LBRACE) { +ASTMemberType::ASTMemberType(const Token &name, + TokenStream &tokens) + : + does_exist{true}, + has_args{false} { + + this->name = IDToken{name, tokens}; + + // now there may follow type arguments, e.g. set(arg, key=val) + auto token = tokens.next(); + + // Check how many type arguments are required at minimum + BasicType member_type = BasicType::from_type_token(this->name); + auto num_expected_types = BasicType::expected_nested_types(member_type); + + if (token->type == token_type::LPAREN) { + auto num_read_types = comma_list( + token_type::RPAREN, + tokens, + num_expected_types, + [this] (const Token &token, TokenStream &stream) { + this->nested_types.emplace_back(token, stream); + } + ); + if (num_read_types < num_expected_types) { + throw ASTError( + std::string("expected at least ") + + std::to_string(num_expected_types) + + " arguments for " + + composite_type_to_string(member_type.composite_type) + + " declaration, but only " + + std::to_string(num_read_types) + + " could be found", + *token, false + ); + } + + if (this->args.size() > 0) { + this->has_args = true; + } + } + else if (num_expected_types > 0) { + throw ASTError( + std::string("expected at least ") + + std::to_string(num_expected_types) + + " arguments for " + + composite_type_to_string(member_type.composite_type) + + " declaration", + *token, false + ); + } + else { + tokens.reinsert_last(); + } +} - container_t ctype; - if (token->get() == "o") { - ctype = container_t::ORDEREDSET; - } - else { - throw ASTError{"unhandled set type", *token}; - } - this->value = ASTMemberValue{ctype, tokens}; - } - else { - tokens.reinsert_last(); +bool ASTMemberType::exists() const { + return this->does_exist; +} - if (token->type == token_type::LBRACE) { - // no set type defined => it's a standard set - this->value = ASTMemberValue{container_t::SET, tokens}; - } - else { - // single-value - if (unlikely(not token->is_content())) { - throw ASTError{"expected value, have", *token}; - } +ASTMemberTypeArgument::ASTMemberTypeArgument(TokenStream &tokens) + : + has_key{false} { + auto token = tokens.next(); + if (token->type != token_type::ID) { + throw ASTError("expected argument value or key, but got", *token); + } - this->value = ASTMemberValue{IDToken{*token, tokens}}; - } - } + auto next_token = tokens.next(); + // check if the argument is keyed + if (next_token->type == token_type::OPERATOR) { + if (unlikely(op_from_token(*next_token) != nyan_op::ASSIGN)) { + throw ASTError("expected argument keyed assignment, but got", *token); + } - had_def_or_decl = true; + this->has_key = true; + this->key = IDToken(*token, tokens); - token = tokens.next(); - } - if (not had_def_or_decl) { - throw ASTError("expected type declaration ( : type ) " - "or value ( = something), instead got", - *token); - } + token = tokens.next(); + if (unlikely(token->type != token_type::ID)) { + throw ASTError("expected argument value, but got", *token); + } + } else { + tokens.reinsert_last(); + } - if (not token->is_endmarker()) { - throw ASTError("expected newline after member entry, but got", - *token); - } + this->value = IDToken{*token, tokens}; } -ASTMemberType::ASTMemberType() - : - does_exist{false}, - has_payload{false} {} - - -ASTMemberType::ASTMemberType(const Token &name, - TokenStream &tokens) - : - does_exist{true}, - has_payload{false} { - - this->name = IDToken{name, tokens}; +ASTMemberValue::ASTMemberValue() + : + does_exist{false} {} - // now there may follow a type payload, e.g. set(payloadtype) - auto token = tokens.next(); - if (token->type == token_type::LPAREN) { - token = tokens.next(); - if (token->type == token_type::ID) { - this->payload = IDToken{*token, tokens}; - this->has_payload = true; - } - else { - throw ASTError("expected type identifier, but got", *token); - } - token = tokens.next(); +ASTMemberValue::ASTMemberValue(const IDToken &value) + : + does_exist{true}, + composite_type{composite_t::SINGLE} { - if (token->type != token_type::RPAREN) { - throw ASTError("expected closing parens, but encountered", *token); - } - } else { - tokens.reinsert_last(); - } + this->values.emplace_back(value); } -bool ASTMemberType::exists() const { - return this->does_exist; -} +ASTMemberValue::ASTMemberValue(composite_t type, + TokenStream &tokens) + : + does_exist{true}, + composite_type{type} { + token_type end_token; -ASTMemberValue::ASTMemberValue() - : - does_exist{false} {} + switch (this->composite_type) { + case composite_t::SET: + case composite_t::ORDEREDSET: { + end_token = token_type::RBRACE; + comma_list( + end_token, + tokens, + [this] (const Token &token, TokenStream &stream) { + const IDToken id_token = IDToken(token, stream); + this->values.emplace_back(id_token); + } + ); + } break; + case composite_t::DICT: { + end_token = token_type::RBRACE; -ASTMemberValue::ASTMemberValue(const IDToken &value) - : - does_exist{true}, - container_type{container_t::SINGLE} { + comma_list( + end_token, + tokens, + [this] (const Token &token, TokenStream &stream) { + std::vector id_tokens; - this->values.push_back(value); -} + // key + id_tokens.emplace_back(token, stream); + auto next_token = stream.next(); + if (next_token->type == token_type::COLON) { + next_token = stream.next(); + } + else { + throw ASTError{"expected colon, but got", *next_token}; + } -ASTMemberValue::ASTMemberValue(container_t type, - TokenStream &tokens) - : - does_exist{true}, - container_type{type} { + // value + id_tokens.emplace_back(*next_token, stream); - token_type end_token; + this->values.emplace_back(composite_type, id_tokens); + } + ); + } break; - switch (this->container_type) { - case container_t::SET: - case container_t::ORDEREDSET: - end_token = token_type::RBRACE; break; + default: + throw InternalError{"unknown container value type"}; + } - default: - throw InternalError{"unknown container value type"}; - } - comma_list( - end_token, - tokens, - [this] (const Token &token, TokenStream &stream) { - this->values.emplace_back(token, stream); - } - ); } bool ASTMemberValue::exists() const { - // the size of this->values doesn't matter, as the value could be an empty set. - return this->does_exist; + // the size of this->values doesn't matter, as the value could be an empty set. + return this->does_exist; } -const std::vector &ASTMemberValue::get_values() const { - return this->values; +const std::vector &ASTMemberValue::get_values() const { + return this->values; } -const container_t &ASTMemberValue::get_container_type() const { - return this->container_type; +const composite_t &ASTMemberValue::get_composite_type() const { + return this->composite_type; } static void indenter(std::ostringstream &builder, int indentlevel) { - builder << std::string(SPACES_PER_INDENT * indentlevel, ' '); + builder << std::string(SPACES_PER_INDENT * indentlevel, ' '); } void AST::strb(std::ostringstream &builder, int indentlevel) const { - size_t count = 0; - for (auto &obj : this->objects) { - builder << std::endl; - indenter(builder, indentlevel); - builder << "# [object " << count << "]" << std::endl; - obj.strb(builder, indentlevel); - count += 1; - } + size_t count = 0; + for (auto &obj : this->objects) { + builder << std::endl; + indenter(builder, indentlevel); + builder << "# [object " << count << "]" << std::endl; + obj.strb(builder, indentlevel); + count += 1; + } } +void ASTArgument::strb(std::ostringstream &builder, int /*indentlevel*/) const { + builder << "!" << this->arg.str(); + for (auto ¶m : this->params) { + builder << " " << param.str(); + } +} void ASTImport::strb(std::ostringstream &builder, int /*indentlevel*/) const { - builder << "import " << this->namespace_name.str(); - if (this->has_alias()) { - builder << " as " << this->get_alias().get(); - } + builder << "import " << this->namespace_name.str(); + if (this->has_alias()) { + builder << " as " << this->get_alias().get(); + } } void ASTObject::strb(std::ostringstream &builder, int indentlevel) const { - indenter(builder, indentlevel); - builder << this->name.get(); - - auto token_str = [](const auto &in) { - return in.str(); - }; - - // print - if (this->target.exists()) { - builder << "<" << this->target.str() << ">"; - } - - - if (this->inheritance_change.size() > 0) { - builder << "["; - for (auto &change : this->inheritance_change) { - change.strb(builder); - } - builder << "]"; - } - - builder << "(" - << util::strjoin(", ", this->parents, token_str) - << "):" - << std::endl; - - if (this->objects.size() > 0) { - for (auto &object : this->objects) { - object.strb(builder, indentlevel + 1); - } - } - if (this->members.size() > 0) { - for (auto &member : this->members) { - member.strb(builder, indentlevel + 1); - } - } - else { - indenter(builder, indentlevel + 1); - builder << "pass" << std::endl; - } + indenter(builder, indentlevel); + builder << this->name.get(); + + auto token_str = [](const auto &in) { + return in.str(); + }; + + // print + if (this->target.exists()) { + builder << "<" << this->target.str() << ">"; + } + + + if (this->inheritance_change.size() > 0) { + builder << "["; + for (auto &change : this->inheritance_change) { + change.strb(builder); + } + builder << "]"; + } + + builder << "(" + << util::strjoin(", ", this->parents, token_str) + << "):" + << std::endl; + + if (this->objects.size() > 0) { + for (auto &object : this->objects) { + object.strb(builder, indentlevel + 1); + } + } + if (this->members.size() > 0) { + for (auto &member : this->members) { + member.strb(builder, indentlevel + 1); + } + } + else { + indenter(builder, indentlevel + 1); + builder << "pass" << std::endl; + } } void ASTInheritanceChange::strb(std::ostringstream &builder, int /*indentlevel*/) const { - switch (this->type) { - case inher_change_t::ADD_BACK: - builder << "+"; - break; - default: - break; - } + switch (this->type) { + case inher_change_t::ADD_BACK: + builder << "+"; + break; + default: + break; + } - builder << this->target.str(); + builder << this->target.str(); - switch (this->type) { - case inher_change_t::ADD_FRONT: - builder << "+"; - break; - default: - break; - } + switch (this->type) { + case inher_change_t::ADD_FRONT: + builder << "+"; + break; + default: + break; + } } void ASTMember::strb(std::ostringstream &builder, int indentlevel) const { - indenter(builder, indentlevel); - builder << this->name.str(); + indenter(builder, indentlevel); + builder << this->name.str(); - if (this->type.exists()) { - builder << " : "; - this->type.strb(builder); - } + if (this->type.exists()) { + builder << " : "; + this->type.strb(builder); + } - if (this->value.exists()) { - builder << " " - << op_to_string(this->operation) - << " "; + if (this->value.exists()) { + builder << " " + << op_to_string(this->operation) + << " "; - this->value.strb(builder); - } + this->value.strb(builder); + } - builder << std::endl; + builder << std::endl; } void ASTMemberType::strb(std::ostringstream &builder, int /*indentlevel*/) const { - builder << this->name.str(); + builder << this->name.str(); - if (this->has_payload) { - builder << "(" << this->payload.str() << ")"; - } + if (this->has_args) { + builder << "("; + for (auto &arg : this->args) { + arg.strb(builder); + } + builder << ")"; + } } +void ASTMemberTypeArgument::strb(std::ostringstream &builder, int /*indentlevel*/) const { + if (this->has_key) { + builder << this->key.str() << "="; + } + + builder << this->value.str(); +} + void ASTMemberValue::strb(std::ostringstream &builder, int /*indentlevel*/) const { - switch (this->container_type) { - case container_t::SINGLE: - builder << this->values[0].str(); - return; + switch (this->composite_type) { + case composite_t::SINGLE: + builder << this->values[0].str(); + return; - case container_t::SET: - builder << "{"; break; + case composite_t::SET: + case composite_t::DICT: + builder << "{"; break; - case container_t::ORDEREDSET: - builder << "o{"; break; + case composite_t::ORDEREDSET: + builder << "o{"; break; - default: - throw InternalError{"unhandled container type"}; - } + default: + throw InternalError{"unhandled container type"}; + } - bool comma_active = false; - for (auto &value : this->values) { - if (comma_active) { - builder << ", "; - } - builder << value.str(); - comma_active = true; - } + bool comma_active = false; + for (auto &value : this->values) { + if (comma_active) { + builder << ", "; + } + builder << value.str(); + comma_active = true; + } - switch (this->container_type) { - case container_t::SET: - case container_t::ORDEREDSET: - builder << "}"; break; + switch (this->composite_type) { + case composite_t::SET: + case composite_t::ORDEREDSET: + case composite_t::DICT: + builder << "}"; break; - default: - throw InternalError{"unhandled container type"}; - } + default: + throw InternalError{"unhandled container type"}; + } } @@ -705,36 +902,36 @@ void ASTMemberValue::strb(std::ostringstream &builder, int /*indentlevel*/) cons ASTError::ASTError(const std::string &msg, const Token &token, bool add_token) - : - LangError{Location{token}, ""} { + : + LangError{Location{token}, ""} { - if (add_token) { - std::ostringstream builder; - builder << msg << ": " - << token_type_str(token.type); - this->msg = builder.str(); - } - else { - this->msg = msg; - } + if (add_token) { + std::ostringstream builder; + builder << msg << ": " + << token_type_str(token.type); + this->msg = builder.str(); + } + else { + this->msg = msg; + } } ASTError::ASTError(const std::string &msg, const IDToken &token, bool add_token) - : - LangError{Location{token}, ""} { - - if (add_token) { - std::ostringstream builder; - builder << msg << ": " - << token_type_str(token.get_type()); - this->msg = builder.str(); - } - else { - this->msg = msg; - } + : + LangError{Location{token}, ""} { + + if (add_token) { + std::ostringstream builder; + builder << msg << ": " + << token_type_str(token.get_type()); + this->msg = builder.str(); + } + else { + this->msg = msg; + } } diff --git a/nyan/ast.h b/nyan/ast.h index 218ba27..dadd16e 100644 --- a/nyan/ast.h +++ b/nyan/ast.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -16,57 +16,148 @@ #include "token_stream.h" #include "type.h" #include "util.h" +#include "value_token.h" namespace nyan { +/** + * Walks through a comma separated list and calls a user-provided + * function on a list item. Stops when either the specified end token + * is reached or the number of read items is equal to the set limit. + * + * @param end End delimiter of the comma seperated list. + * @param tokens TokenStream that is walked through. + * @param limit Maximum number of list items that should be processed. + * @param func Function called on the list item. + * + * @return Number of list items processed. + */ +unsigned int comma_list(token_type end, + TokenStream &tokens, + size_t limit, + const std::function &func); + /** - * Add tokens to the value list until the end token is reached. + * Walks through a comma separated list and calls a user-provided + * function on a list item. Stops when the specified end token + * is reached. + * + * @param end End delimiter of the comma seperated list. + * @param tokens TokenStream that is walked through. + * @param func Function called on the list item. + * + * @return Number of list items processed. */ -void comma_list(token_type end, - TokenStream &tokens, - const std::function &func); +unsigned int comma_list(token_type end, + TokenStream &tokens, + const std::function &func); /** * Base class for nyan AST classes. */ class ASTBase { - friend class Database; + friend class Database; public: - virtual ~ASTBase() = default; + virtual ~ASTBase() = default; - /** - * Return a string representation of this AST element - * and maybe its children. - */ - std::string str() const; + /** + * Return a string representation of this AST element + * and its children. + */ + std::string str() const; protected: - virtual void strb(std::ostringstream &builder, int indentlevel=0) const = 0; + + /** + * Append a string represantation of this AST element to a + * string stream. The element may indent the appended string. + * + * @param builder String stream that is appended to. + * @param indentlevel Indentation level (4 spaces per level). + */ + virtual void strb(std::ostringstream &builder, int indentlevel=0) const = 0; }; /** - * AST representation of a member type declaration. + * AST representation of a member type argument declaration. */ -class ASTMemberType : ASTBase { - friend class ASTMember; - friend class Database; - friend class Type; +class ASTMemberTypeArgument : public ASTBase { + friend class ASTMemberType; + friend class Database; + friend class Type; + public: - ASTMemberType(); - ASTMemberType(const Token &name, TokenStream &tokens); + ASTMemberTypeArgument(TokenStream &tokens); - bool exists() const; - void strb(std::ostringstream &builder, int indentlevel=0) const override; + void strb(std::ostringstream &builder, int indentlevel=0) const override; protected: - bool does_exist; - IDToken name; + /** + * true if the argument is a key-value pair. + */ + bool has_key; + + /** + * Argument key token. If the argument is not keyed, this is nullptr. + */ + IDToken key; + + /** + * Argument value token. + */ + IDToken value; +}; + + +/** + * AST representation of a member type declaration. + */ +class ASTMemberType : ASTBase { + friend class ASTMember; + friend class Database; + friend class Type; +public: + ASTMemberType(); + ASTMemberType(const Token &name, TokenStream &tokens); + + /** + * Checks if the member type was declared, i.e. it contains a type definition. + * + * @return true if the member type was declared, false if it + * wasn't. In the latter case the member type must be declared + * in a parent object. + */ + bool exists() const; + void strb(std::ostringstream &builder, int indentlevel=0) const override; - bool has_payload; - IDToken payload; +protected: + /** + * true if the member type was declared, i.e. it contains a type definition. + */ + bool does_exist; + + /** + * Typename of the member type. + */ + IDToken name; + + /** + * Nested types of a composite type member, e.g. int in set(int) + */ + std::vector nested_types; + + /** + * true if the member type has at least one argument. + */ + bool has_args; + + /** + * Type arguments. + */ + std::vector args; }; @@ -74,27 +165,54 @@ class ASTMemberType : ASTBase { * AST representation of a member value. */ class ASTMemberValue : public ASTBase { - friend class Database; - friend class ASTMember; + friend class Database; + friend class ASTMember; public: - ASTMemberValue(); - ASTMemberValue(container_t type, - TokenStream &tokens); - ASTMemberValue(const IDToken &value); - - bool exists() const; - - const std::vector &get_values() const; - const container_t &get_container_type() const; - - void strb(std::ostringstream &builder, int indentlevel=0) const override; + ASTMemberValue(); + ASTMemberValue(composite_t type, + TokenStream &tokens); + ASTMemberValue(const IDToken &value); + + /** + * Checks if the member value was defined, i.e. it contains values. + * + * @return true if the member value was defined, false if it + * wasn't. In the latter case the member is abstract. + */ + bool exists() const; + + /** + * Returns the values defined by this member value. + * + * @return A list of ValueToken objects that each contain a subvalue. + */ + const std::vector &get_values() const; + + /** + * Returns the composite type of the member value. + * + * @return A composite type. + */ + const composite_t &get_composite_type() const; + + void strb(std::ostringstream &builder, int indentlevel=0) const override; protected: - bool does_exist; - container_t container_type; - - std::vector values; + /** + * true if the member value was defined, i.e. it contains values. + */ + bool does_exist; + + /** + * Composite type of the member value. Defines how the value is formatted in the file. + */ + composite_t composite_type; + + /** + * Values defined in the member value. + */ + std::vector values; }; @@ -102,61 +220,144 @@ class ASTMemberValue : public ASTBase { * The abstract syntax tree representation of a member entry. */ class ASTMember : public ASTBase { - friend class Database; + friend class Database; public: - ASTMember(const Token &name, TokenStream &tokens); + ASTMember(const Token &name, TokenStream &tokens); - void strb(std::ostringstream &builder, int indentlevel=0) const override; + void strb(std::ostringstream &builder, int indentlevel=0) const override; protected: - IDToken name; - nyan_op operation; - ASTMemberType type; - ASTMemberValue value; + /** + * Name (identifier) of the member. + */ + IDToken name; + + /** + * Operation defined by the member. + */ + nyan_op operation; + + /** + * Member type definition. + */ + ASTMemberType type; + + /** + * Member value definition. + */ + ASTMemberValue value; }; /** - * An import in a nyan file is represented by this AST entry. - * Used for the `import ... (as ...)` statement. + * The abstract syntax tree representation of an argument. */ -class ASTImport : public ASTBase { +class ASTArgument : public ASTBase { public: - ASTImport(TokenStream &tokens); + ASTArgument(TokenStream &tokens); - void strb(std::ostringstream &builder, int indentlevel=0) const override; + void strb(std::ostringstream &builder, int indentlevel=0) const override; - /** return the imported namespace name */ - const IDToken &get() const; + /** + * Returns the argument type. + * + * @return IDToken with the argument type string. + */ + const IDToken &get_arg() const; - /** return true if this import has defined an alias */ - bool has_alias() const; + /** + * Returns the argument parameters. + * + * @return A list of IDTokens with argument parameter values. + */ + const std::vector &get_params() const; + +protected: + IDToken arg; + std::vector params; +}; - /** return the alias, if existing */ - const Token &get_alias() const; + +/** + * An import in a nyan file is represented by this AST entry. + * Used for the `import ... (as ...)` statement. + */ +class ASTImport : public ASTBase { +public: + ASTImport(TokenStream &tokens); + + void strb(std::ostringstream &builder, int indentlevel=0) const override; + + /** + * Returns the imported namespace name. + * + * @return IDToken with the namespace name. + */ + const IDToken &get() const; + + /** + * Checks if the import has defined an alias. + * + * @return true if an alias is defined, else false. + */ + bool has_alias() const; + + /** + * Returns the import alias. + * + * @return The Token with the alias name, if it exists, or nullptr + * if it doesn't. + */ + const Token &get_alias() const; protected: - IDToken namespace_name; - Token alias; + /** + * Name (identifier) of the namespace that is imported from. + */ + IDToken namespace_name; + + /** + * Import alias name. + */ + Token alias; }; /** - * Inheritance chang + * The abstract syntax tree representation of an inheritance change. */ class ASTInheritanceChange : public ASTBase { - friend class Database; + friend class Database; public: - ASTInheritanceChange(TokenStream &tokens); + ASTInheritanceChange(TokenStream &tokens); - inher_change_t get_type() const; - const IDToken &get_target() const; + /** + * Returns the inheritance change type. + * + * @return The inheritance change type. + */ + inher_change_t get_type() const; - void strb(std::ostringstream &builder, int indentlevel=0) const override; + /** + * Returns the name of the object add to the inheritance. + * + * @return Inheritance change type. + */ + const IDToken &get_target() const; + + void strb(std::ostringstream &builder, int indentlevel=0) const override; protected: - inher_change_t type; - IDToken target; + /** + * Determines at which end in the object's linearization the + * inheritance change is appended (front or back). + */ + inher_change_t type; + + /** + * Identifier of the object that is added to the inheritance. + */ + IDToken target; }; @@ -164,27 +365,86 @@ class ASTInheritanceChange : public ASTBase { * The abstract syntax tree representation of a nyan object. */ class ASTObject : public ASTBase { - friend class Database; + friend class Database; public: - ASTObject(const Token &name, TokenStream &tokens); - - void ast_targets(TokenStream &tokens); - void ast_inheritance_mod(TokenStream &tokens); - void ast_parents(TokenStream &tokens); - void ast_members(TokenStream &tokens); - - const Token &get_name() const; - const std::vector &get_objects() const; - - void strb(std::ostringstream &builder, int indentlevel=0) const override; + ASTObject(const Token &name, TokenStream &tokens); + + /** + * Reads a patch target and sets it as 'target'. + * + * @param tokens TokenStream that points to the Token *before* the target begins. + */ + void ast_targets(TokenStream &tokens); + + /** + * Reads inheritance changes target and adds them to 'inheritance_change'. + * + * @param tokens TokenStream that points to first Token in the list of + * added inheritance objects. + */ + void ast_inheritance_mod(TokenStream &tokens); + + /** + * Reads object parents and adds them to 'parents'. + * + * @param tokens TokenStream that points to first Token in the list of + * parent objects. + */ + void ast_parents(TokenStream &tokens); + + /** + * Reads object members and adds them to 'parents'. + * + * @param tokens TokenStream that points to the Token *before* the members begin. + */ + void ast_members(TokenStream &tokens); + + /** + * Returns the object name. + * + * @return The Token with the object's name. + */ + const Token &get_name() const; + + /** + * Returns the nested objects in this object. + * + * @return A list of nested ASTObjects. + */ + const std::vector &get_objects() const; + + void strb(std::ostringstream &builder, int indentlevel=0) const override; protected: - Token name; - IDToken target; - std::vector inheritance_change; - std::vector parents; - std::vector members; - std::vector objects; + /** + * Name of the object. + */ + Token name; + + /** + * Target if this is a patch. + */ + IDToken target; + + /** + * Inheritance changes to the target if this is a patch. + */ + std::vector inheritance_change; + + /** + * Parents of the object. + */ + std::vector parents; + + /** + * Members of the object. + */ + std::vector members; + + /** + * Nested objects in the object. + */ + std::vector objects; }; @@ -193,15 +453,46 @@ class ASTObject : public ASTBase { */ class AST : public ASTBase { public: - AST(TokenStream &tokens); - - void strb(std::ostringstream &builder, int indentlevel=0) const override; - const std::vector &get_objects() const; - const std::vector &get_imports() const; + AST(TokenStream &tokens); + + void strb(std::ostringstream &builder, int indentlevel=0) const override; + + /** + * Returns the arguments in the AST. + * + * @return A list of ASTArguments. + */ + const std::vector &get_args() const; + + /** + * Returns the imports in the AST. + * + * @return A list of ASTImports. + */ + const std::vector &get_imports() const; + + /** + * Returns the objects in the AST. + * + * @return A list of ASTObjects. + */ + const std::vector &get_objects() const; protected: - std::vector imports; - std::vector objects; + /** + * Arguments in the AST. + */ + std::vector args; + + /** + * Imports in the AST. + */ + std::vector imports; + + /** + * Objects in the AST. + */ + std::vector objects; }; @@ -210,11 +501,25 @@ class AST : public ASTBase { */ class ASTError : public LangError { public: - ASTError(const std::string &msg, const Token &token, - bool add_token=true); - - ASTError(const std::string &msg, const IDToken &token, - bool add_token=true); + /** + * Creates an error for a Token in the parsers token stream. + * + * @param msg Error message string. + * @param token Token that caused the error. + * @param add_token If true, the token type is displayed. + */ + ASTError(const std::string &msg, const Token &token, + bool add_token=true); + + /** + * Creates an error for an IDToken in the parsers token stream. + * + * @param msg Error message string. + * @param token IDToken that caused the error. + * @param add_token If true, the token type is displayed. + */ + ASTError(const std::string &msg, const IDToken &token, + bool add_token=true); }; } // namespace nyan diff --git a/nyan/basic_type.cpp b/nyan/basic_type.cpp index 668d575..ea61767 100644 --- a/nyan/basic_type.cpp +++ b/nyan/basic_type.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2018 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "basic_type.h" @@ -11,80 +11,129 @@ namespace nyan { bool BasicType::is_object() const { - return (this->primitive_type == primitive_t::OBJECT); + return (this->primitive_type == primitive_t::OBJECT); } bool BasicType::is_fundamental() const { - switch (this->primitive_type) { - case primitive_t::BOOLEAN: - case primitive_t::TEXT: - case primitive_t::FILENAME: - case primitive_t::INT: - case primitive_t::FLOAT: - return true; - case primitive_t::CONTAINER: - case primitive_t::OBJECT: - return false; - } - - throw InternalError{"unknown primitive type"}; + switch (this->primitive_type) { + case primitive_t::BOOLEAN: + case primitive_t::TEXT: + case primitive_t::FILENAME: + case primitive_t::INT: + case primitive_t::FLOAT: + case primitive_t::NONE: + return true; + case primitive_t::OBJECT: + case primitive_t::CONTAINER: + case primitive_t::MODIFIER: + return false; + } + + throw InternalError{"unknown primitive type"}; +} + + +bool BasicType::is_composite() const { + return (this->composite_type != composite_t::SINGLE); } bool BasicType::is_container() const { - return (this->container_type != container_t::SINGLE); + switch (this->composite_type) { + case composite_t::SET: + case composite_t::ORDEREDSET: + case composite_t::DICT: + return true; + case composite_t::SINGLE: + case composite_t::ABSTRACT: + case composite_t::CHILDREN: + case composite_t::OPTIONAL: + return false; + } + + throw InternalError{"unknown composite type"}; +} + + +bool BasicType::is_modifier() const { + switch (this->composite_type) { + case composite_t::ABSTRACT: + case composite_t::CHILDREN: + case composite_t::OPTIONAL: + return true; + case composite_t::SINGLE: + case composite_t::SET: + case composite_t::ORDEREDSET: + case composite_t::DICT: + return false; + } + + throw InternalError{"unknown composite type"}; } bool BasicType::operator ==(const BasicType &other) const { - return (this->primitive_type == other.primitive_type and - this->container_type == other.container_type); + return (this->primitive_type == other.primitive_type and + this->composite_type == other.composite_type); } // textual type conversion for the type definition in a member BasicType BasicType::from_type_token(const IDToken &tok) { - // primitive type name map - static const std::unordered_map primitive_types = { - {"bool", primitive_t::BOOLEAN}, - {"text", primitive_t::TEXT}, - {"file", primitive_t::FILENAME}, - {"int", primitive_t::INT}, - {"float", primitive_t::FLOAT} - }; - - // container type name map - static const std::unordered_map container_types = { - {"set", container_t::SET}, - {"orderedset", container_t::ORDEREDSET} - }; - - - primitive_t type = primitive_t::OBJECT; - container_t container_type = container_t::SINGLE; - - switch (tok.get_type()) { - // type names are always identifiers: - case token_type::ID: { - auto it0 = primitive_types.find(tok.get_first()); - if (it0 != std::end(primitive_types)) { - type = it0->second; - break; - } - - auto it1 = container_types.find(tok.get_first()); - if (it1 != std::end(container_types)) { - type = primitive_t::CONTAINER; - container_type = it1->second; - } - break; - } - default: - throw ASTError{"expected some type name but there is", tok}; - } - - return BasicType{type, container_type}; + // primitive type name map + static const std::unordered_map primitive_types = { + {"bool", primitive_t::BOOLEAN}, + {"text", primitive_t::TEXT}, + {"file", primitive_t::FILENAME}, + {"int", primitive_t::INT}, + {"float", primitive_t::FLOAT} + }; + + // container type name map + static const std::unordered_map container_types = { + {"set", composite_t::SET}, + {"orderedset", composite_t::ORDEREDSET}, + {"dict", composite_t::DICT} + }; + + // modifier type name map + static const std::unordered_map modifier_types = { + {"abstract", composite_t::ABSTRACT}, + {"children", composite_t::CHILDREN}, + {"optional", composite_t::OPTIONAL} + }; + + primitive_t type = primitive_t::OBJECT; + composite_t composite_type = composite_t::SINGLE; + + switch (tok.get_type()) { + // type names are always identifiers: + case token_type::ID: { + auto it0 = primitive_types.find(tok.get_first()); + if (it0 != std::end(primitive_types)) { + type = it0->second; + break; + } + + auto it1 = container_types.find(tok.get_first()); + if (it1 != std::end(container_types)) { + type = primitive_t::CONTAINER; + composite_type = it1->second; + break; + } + + auto it2 = modifier_types.find(tok.get_first()); + if (it2 != std::end(modifier_types)) { + type = primitive_t::MODIFIER; + composite_type = it2->second; + } + break; + } + default: + throw ASTError{"expected some type name but there is", tok}; + } + + return BasicType{type, composite_type}; } - } // namespace nyan diff --git a/nyan/basic_type.h b/nyan/basic_type.h index dba4fb9..f9dce04 100644 --- a/nyan/basic_type.h +++ b/nyan/basic_type.h @@ -1,4 +1,4 @@ -// Copyright 2016-2018 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include "config.h" @@ -12,119 +12,210 @@ class IDToken; /** * Member types available in nyan. * These are the primitive types. - * A CONTAINER packs multiple primitive values together. * The OBJECT type requires a payload as "target" name. + * A CONTAINER packs multiple primitive values together. + * A MODIFIER is an extension of a contained type. */ enum class primitive_t { - BOOLEAN, - TEXT, - FILENAME, - INT, - FLOAT, - CONTAINER, - OBJECT + BOOLEAN, + TEXT, + FILENAME, + INT, + FLOAT, + OBJECT, + NONE, + CONTAINER, + MODIFIER, }; /** - * Available member container types. - * Single means it's not a container. + * Available member composite types (containers or modifiers). + * SINGLE means it's not a composite. */ -enum class container_t { - SINGLE, - SET, - ORDEREDSET, +enum class composite_t { + // primitive value + SINGLE, + + // Containers + SET, + ORDEREDSET, + DICT, + + // Modifiers + ABSTRACT, + CHILDREN, + OPTIONAL, }; /** * Basic nyan type information. * Stores a combination of the primitive type - * and the container type. + * and the composite type. * * This is for storing and handling the built-in types. * Custom types are then added with the `Type` class. */ class BasicType { public: - /** - * Primitive type. - * Decides if this Type is primitive, a container, or an object. - */ - primitive_t primitive_type; - - /** - * Stores if this type is a container and if yes, which one. - */ - container_t container_type; - - /** - * Return whether the type is object. - */ - bool is_object() const; - - /** - * Return whether the given type is fundamental. - * Primitive types are int, float, text, etc. - * Non-primitive types are containers and objects. - */ - bool is_fundamental() const; - - - /** - * Test if this basic type is a container, - * that is, the container type is not SINGLE - * or primitive type is CONTAINER. - */ - bool is_container() const; - - - /** - * Equality comparison. - */ - bool operator ==(const BasicType &other) const; - - - /** - * Test if the given type token declares a valid primitive_t, - * returns it. Also returns the container type. - * A type token is e.g. "int" or "float" or "SomeObject". - * If it is e.g. "set", type will be CONTAINER and the container type SET. - * throws ASTError if it fails. - */ - static BasicType from_type_token(const IDToken &token); + /** + * Primitive type. + * Decides if this Type is primitive, an object, a container + * or a modifier. + */ + primitive_t primitive_type; + + /** + * Stores if this type is a composite if types and if yes, which one. + * Composite types can be containers or modifiers. + */ + composite_t composite_type; + + /** + * Return whether the type is object. + * + * @return true if the basic type is an object, else false. + */ + bool is_object() const; + + + /** + * Return whether the given type is fundamental. + * Primitive types are int, float, text, etc. + * Non-primitive types are objects, containers and modifiers. + * + * @return true if the basic type is fundamental, else false. + */ + bool is_fundamental() const; + + + /** + * Test if this basic type is a composite. + * that is, the composite type is not SINGLE. + * + * @return true if the basic type is a composite, else false. + */ + bool is_composite() const; + + + /** + * Test if this basic type is a container. + * that is, the composite type is one of the container + * types SET, ORDEREDSET or DICT. + * + * @return true if the basic type is a container, else false. + */ + bool is_container() const; + + + /** + * Test if this basic type is a modifier. + * that is, the composite type is one of the modifier + * types ABSTRACT, CHILDREN or OPTIONAL. + * + * @return true if the basic type is a modifier, else false. + */ + bool is_modifier() const; + + + /** + * Equality comparison. + * + * @return true if the basic types are exactly the same, else false. + */ + bool operator ==(const BasicType &other) const; + + + /** + * Test if the given type token declares a valid primitive + * and composite type. + * A type token is e.g. "int" or "float" or "SomeObject". + * If it is e.g. "set", type will be CONTAINER and the composite type SET. + * + * @param token An IDToken that contains a type identifier or a nyan object name. + * + * @return BasicType declared by the token. + * @throw ASTError if no typename could be found. + */ + static BasicType from_type_token(const IDToken &token); + + + /** + * Returns how many nested types are required when declaring a given + * type. + * + * @param type The tested type. + * + * @return Number of required nested types. + */ + static unsigned int expected_nested_types(const BasicType &type) { + if (type.is_fundamental()) { + return 0; + } + + switch (type.composite_type) { + // containers + case composite_t::SET: return 1; + case composite_t::ORDEREDSET: return 1; + case composite_t::DICT: return 2; + + // modifiers + case composite_t::ABSTRACT: return 1; + case composite_t::CHILDREN: return 1; + case composite_t::OPTIONAL: return 1; + + // else, must be an object + default: + return 0; + } + } }; /** * Get a string representation of a basic nyan type. + * + * @param type A primitive type. + * + * @return String representation of the type. */ constexpr const char *type_to_string(primitive_t type) { - switch (type) { - case primitive_t::BOOLEAN: return "bool"; - case primitive_t::TEXT: return "text"; - case primitive_t::FILENAME: return "file"; - case primitive_t::INT: return "int"; - case primitive_t::FLOAT: return "float"; - case primitive_t::CONTAINER: return "container"; - case primitive_t::OBJECT: return "object"; - } - - return "unhandled primitive_t"; + switch (type) { + case primitive_t::BOOLEAN: return "bool"; + case primitive_t::TEXT: return "text"; + case primitive_t::FILENAME: return "file"; + case primitive_t::INT: return "int"; + case primitive_t::FLOAT: return "float"; + case primitive_t::OBJECT: return "object"; + case primitive_t::NONE: return "none"; + case primitive_t::CONTAINER: return "container"; + case primitive_t::MODIFIER: return "modifier"; + } + + return "unhandled primitive_t"; } /** - * Get a string represenation for a nyan container type. + * Get a string represenation for a nyan composite type. + * + * @param type A composite type. + * + * @return String representation of the type. */ -constexpr const char *container_type_to_string(container_t type) { - switch (type) { - case container_t::SINGLE: return "single_value"; - case container_t::SET: return "set"; - case container_t::ORDEREDSET: return "orderedset"; - } - - return "unhandled container_t"; +constexpr const char *composite_type_to_string(composite_t type) { + switch (type) { + case composite_t::SINGLE: return "single_value"; + case composite_t::SET: return "set"; + case composite_t::ORDEREDSET: return "orderedset"; + case composite_t::DICT: return "dict"; + case composite_t::ABSTRACT: return "abstract"; + case composite_t::CHILDREN: return "children"; + case composite_t::OPTIONAL: return "optional"; + } + + return "unhandled composite_t"; } } // namespace nyan diff --git a/nyan/c3.cpp b/nyan/c3.cpp index e473418..3ccc453 100644 --- a/nyan/c3.cpp +++ b/nyan/c3.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "c3.h" @@ -11,8 +11,8 @@ namespace nyan { std::vector linearize(const fqon_t &name, const objstate_fetch_t &get_obj) { - std::unordered_set seen; - return linearize_recurse(name, get_obj, &seen); + std::unordered_set seen; + return linearize_recurse(name, get_obj, &seen); } @@ -34,155 +34,155 @@ linearize_recurse(const fqon_t &name, const objstate_fetch_t &get_obj, std::unordered_set *seen) { - using namespace std::string_literals; - - // test for inheritance loops - if (seen->find(name) != std::end(*seen)) { - throw C3Error{ - "recursive inheritance loop detected: '"s + name + "' already in {" - + util::strjoin(", ", *seen) - + "}" - }; - } else { - seen->insert(name); - } - - // get raw ObjectState of this object at the requested time - const ObjectState &obj_state = get_obj(name); - - // calculate a new linearization in this list - std::vector linearization; - - // The current object is always the first in the returned list - linearization.push_back(name); - - // Get parents of object. - const auto &parents = obj_state.get_parents(); - - // Calculate the parent linearization recursively - std::vector> par_linearizations; - par_linearizations.reserve(parents.size() + 1); - - for (auto &parent : parents) { - // Recursive call to get the linearization of the parent - par_linearizations.push_back( - linearize_recurse(parent, get_obj, seen) - ); - } - - // And at the end, add all parents of this object to the merge-list. - par_linearizations.push_back( - {std::begin(parents), std::end(parents)} - ); - - // remove current name from the seen set - // we only needed it for the recursive call above. - seen->erase(name); - - // Index to start with in each list - // On a side note, I used {} instead of () for some time. - // But that, unfortunately was buggy. - // What the bug was is left as a fun challenge for the reader. - std::vector sublists_heads(par_linearizations.size(), 0); - - // For each loop, find a candidate to add to the result. - while (true) { - const fqon_t *candidate; - bool candidate_ok = false; - size_t sublists_available = par_linearizations.size(); - - // Try to find a head that is not element of any tail - for (size_t i = 0; i < par_linearizations.size(); i++) { - const auto &par_linearization = par_linearizations[i]; - const size_t headpos = sublists_heads[i]; - - // The head position has reached the end (i.e. the list is "empty") - if (headpos >= par_linearization.size()) { - sublists_available -= 1; - continue; - } - - // Pick a head - candidate = &par_linearization[headpos]; - candidate_ok = true; - - // Test if the candidate is in any tail - for (size_t j = 0; j < par_linearizations.size(); j++) { - - // The current list will never contain the candidate again. - if (j == i) { - continue; - } - - const auto &tail = par_linearizations[j]; - const size_t headpos_try = sublists_heads[j]; - - // Start one slot after the head - // and check that the candidate is not in that tail. - for (size_t k = headpos_try + 1; k < tail.size(); k++) { - - // The head is in that tail, so we fail - if (unlikely(*candidate == tail[k])) { - candidate_ok = false; - break; - } - } - - // Don't try further tails as one already failed. - if (unlikely(not candidate_ok)) { - break; - } - } - - // The candidate was not in any tail - if (candidate_ok) { - break; - } else { - // Try the next candidate, - // this means to select the next par_lin list. - continue; - } - } - - // We found a candidate, add it to the return list - if (candidate_ok) { - linearization.push_back(*candidate); - - // Advance all the lists where the candidate was the head - for (size_t i = 0; i < par_linearizations.size(); i++) { - const auto &par_linearization = par_linearizations[i]; - const size_t headpos = sublists_heads[i]; - - if (headpos < par_linearization.size()) { - if (par_linearization[headpos] == *candidate) { - sublists_heads[i] += 1; - } - } - } - } - - // No more sublists have any entry - if (sublists_available == 0) { - // linearization is finished! - return linearization; - } - - if (not candidate_ok) { - throw C3Error{ - "Can't find consistent C3 resolution order for "s - + name + " for bases " + util::strjoin(", ", parents) - }; - } - } - - // should not be reached :) - throw InternalError{"C3 internal error"}; + using namespace std::string_literals; + + // test for inheritance loops + if (seen->find(name) != std::end(*seen)) { + throw C3Error{ + "recursive inheritance loop detected: '"s + name + "' already in {" + + util::strjoin(", ", *seen) + + "}" + }; + } else { + seen->insert(name); + } + + // get raw ObjectState of this object at the requested time + const ObjectState &obj_state = get_obj(name); + + // calculate a new linearization in this list + std::vector linearization; + + // The current object is always the first in the returned list + linearization.push_back(name); + + // Get parents of object. + const auto &parents = obj_state.get_parents(); + + // Calculate the parent linearization recursively + std::vector> par_linearizations; + par_linearizations.reserve(parents.size() + 1); + + for (auto &parent : parents) { + // Recursive call to get the linearization of the parent + par_linearizations.push_back( + linearize_recurse(parent, get_obj, seen) + ); + } + + // And at the end, add all parents of this object to the merge-list. + par_linearizations.push_back( + {std::begin(parents), std::end(parents)} + ); + + // remove current name from the seen set + // we only needed it for the recursive call above. + seen->erase(name); + + // Index to start with in each list + // On a side note, I used {} instead of () for some time. + // But that, unfortunately was buggy. + // What the bug was is left as a fun challenge for the reader. + std::vector sublists_heads(par_linearizations.size(), 0); + + // For each loop, find a candidate to add to the result. + while (true) { + const fqon_t *candidate; + bool candidate_ok = false; + size_t sublists_available = par_linearizations.size(); + + // Try to find a head that is not element of any tail + for (size_t i = 0; i < par_linearizations.size(); i++) { + const auto &par_linearization = par_linearizations[i]; + const size_t headpos = sublists_heads[i]; + + // The head position has reached the end (i.e. the list is "empty") + if (headpos >= par_linearization.size()) { + sublists_available -= 1; + continue; + } + + // Pick a head + candidate = &par_linearization[headpos]; + candidate_ok = true; + + // Test if the candidate is in any tail + for (size_t j = 0; j < par_linearizations.size(); j++) { + + // The current list will never contain the candidate again. + if (j == i) { + continue; + } + + const auto &tail = par_linearizations[j]; + const size_t headpos_try = sublists_heads[j]; + + // Start one slot after the head + // and check that the candidate is not in that tail. + for (size_t k = headpos_try + 1; k < tail.size(); k++) { + + // The head is in that tail, so we fail + if (unlikely(*candidate == tail[k])) { + candidate_ok = false; + break; + } + } + + // Don't try further tails as one already failed. + if (unlikely(not candidate_ok)) { + break; + } + } + + // The candidate was not in any tail + if (candidate_ok) { + break; + } else { + // Try the next candidate, + // this means to select the next par_lin list. + continue; + } + } + + // We found a candidate, add it to the return list + if (candidate_ok) { + linearization.push_back(*candidate); + + // Advance all the lists where the candidate was the head + for (size_t i = 0; i < par_linearizations.size(); i++) { + const auto &par_linearization = par_linearizations[i]; + const size_t headpos = sublists_heads[i]; + + if (headpos < par_linearization.size()) { + if (par_linearization[headpos] == *candidate) { + sublists_heads[i] += 1; + } + } + } + } + + // No more sublists have any entry + if (sublists_available == 0) { + // linearization is finished! + return linearization; + } + + if (not candidate_ok) { + throw C3Error{ + "Can't find consistent C3 resolution order for "s + + name + " for bases " + util::strjoin(", ", parents) + }; + } + } + + // should not be reached :) + throw InternalError{"C3 internal error"}; } C3Error::C3Error(const std::string &msg) - : - Error{msg} {} + : + Error{msg} {} } // namespace nyan diff --git a/nyan/c3.h b/nyan/c3.h index 72df7b6..0c79c6b 100644 --- a/nyan/c3.h +++ b/nyan/c3.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -23,12 +23,24 @@ using objstate_fetch_t = std::function; /** * Implements the C3 multi inheritance linearization algorithm * to bring the parents of an object into the "right" order. + * Calls linearize_recurse(). + * + * @param name Identifier of the object that is linearized. + * @param get_obj Function to retrive the ObjectState of the object. + * + * @return A C3 linearization of the object's parents. */ std::vector linearize(const fqon_t &name, const objstate_fetch_t &get_obj); /** * Recursive walk for the c3 linearization implememtation. + * + * @param name Identifier of the object that is linearized. + * @param get_obj Function to retrive the ObjectState of the object. + * @param seen Set of objects that have already been found. Should be empty on initial call. + * + * @return A C3 linearization of the object's parents. */ std::vector linearize_recurse(const fqon_t &name, @@ -41,7 +53,7 @@ linearize_recurse(const fqon_t &name, */ class C3Error : public Error { public: - C3Error(const std::string &msg); + C3Error(const std::string &msg); }; diff --git a/nyan/change_tracker.cpp b/nyan/change_tracker.cpp index a36d950..713a9f4 100644 --- a/nyan/change_tracker.cpp +++ b/nyan/change_tracker.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "change_tracker.h" @@ -6,50 +6,50 @@ namespace nyan { void ObjectChanges::add_parent(const fqon_t &obj) { - this->new_parents.push_back(obj); + this->new_parents.push_back(obj); } const std::vector &ObjectChanges::get_new_parents() const { - return this->new_parents; + return this->new_parents; } bool ObjectChanges::parents_update_required() const { - return this->new_parents.size() > 0; + return this->new_parents.size() > 0; } ObjectChanges &ChangeTracker::track_patch(const fqon_t &target_name) { - // if existing, return the object change tracker - // else: create a new one. - auto it = this->changes.find(target_name); - if (it == std::end(this->changes)) { - return this->changes.emplace( - target_name, - ObjectChanges{} - ).first->second; - } - else { - return it->second; - } + // if existing, return the object change tracker + // else: create a new one. + auto it = this->changes.find(target_name); + if (it == std::end(this->changes)) { + return this->changes.emplace( + target_name, + ObjectChanges{} + ).first->second; + } + else { + return it->second; + } } const std::unordered_map &ChangeTracker::get_object_changes() const { - return this->changes; + return this->changes; } std::unordered_set ChangeTracker::get_changed_objects() const { - std::unordered_set ret; - ret.reserve(this->changes.size()); + std::unordered_set ret; + ret.reserve(this->changes.size()); - for (auto &it : this->changes) { - ret.insert(it.first); - } + for (auto &it : this->changes) { + ret.insert(it.first); + } - return ret; + return ret; } diff --git a/nyan/change_tracker.h b/nyan/change_tracker.h index f2156f2..c2c164b 100644 --- a/nyan/change_tracker.h +++ b/nyan/change_tracker.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -16,13 +16,32 @@ namespace nyan { */ class ObjectChanges { public: - void add_parent(const fqon_t &obj); + /** + * Track an object as a new parent. + * + * @param obj Identifier of the object. + */ + void add_parent(const fqon_t &obj); - const std::vector &get_new_parents() const; - bool parents_update_required() const; + /** + * Retrieve the list of new parents. + * + * @return The list of new parents. + */ + const std::vector &get_new_parents() const; + + /** + * Check if the parents were updated by the change. + * + * @return true if size of new_parents is > 1, else false. + */ + bool parents_update_required() const; protected: - std::vector new_parents; + /** + * List of new parents. + */ + std::vector new_parents; }; @@ -32,14 +51,37 @@ class ObjectChanges { */ class ChangeTracker { public: - ObjectChanges &track_patch(const fqon_t &target_name); - const std::unordered_map &get_object_changes() const; + /** + * Get the ObjectChanges for an object targeted by a patch + * from the changes map or create a new one if there doesn't + * exist one yet. + * + * @param target_name Identifier of the target object. + * + * @return An ObjectChanges tracker. + */ + ObjectChanges &track_patch(const fqon_t &target_name); + + /** + * Retrieve the map with all ObjectChanges by object. + * + * @return A map of ObjectChanges by object. + */ + const std::unordered_map &get_object_changes() const; - std::unordered_set get_changed_objects() const; + /** + * Get all objects whose ObjectChanges are tracked in this tracker. + * + * @return A set of object identifiers. + */ + std::unordered_set get_changed_objects() const; protected: - std::unordered_map changes; + /** + * Map of ObjectChanges by object. + */ + std::unordered_map changes; }; } // namespace nyan diff --git a/nyan/compiler.h b/nyan/compiler.h index 4422ed9..73d9ec7 100644 --- a/nyan/compiler.h +++ b/nyan/compiler.h @@ -1,18 +1,18 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #if defined(__GNUC__) - /* - * Branch prediction tuning. - * The expression is expected to be true (=likely) or false (=unlikely). - */ - #define likely(x) __builtin_expect(!!(x), 1) - #define unlikely(x) __builtin_expect(!!(x), 0) + /* + * Branch prediction tuning. + * The expression is expected to be true (=likely) or false (=unlikely). + */ + #define likely(x) __builtin_expect(!!(x), 1) + #define unlikely(x) __builtin_expect(!!(x), 0) #else - #define likely(x) (x) - #define unlikely(x) (x) + #define likely(x) (x) + #define unlikely(x) (x) #endif @@ -20,10 +20,10 @@ * Software breakpoint for debugging. */ #ifdef _WIN32 - #define BREAKPOINT __debugbreak() + #define BREAKPOINT __debugbreak() #else - #include - #define BREAKPOINT raise(SIGTRAP) + #include + #define BREAKPOINT raise(SIGTRAP) #endif @@ -31,11 +31,11 @@ * shared library symbol export declarations */ #if defined(_WIN32) - #if defined(nyan_EXPORTS) - #define NYANAPI __declspec(dllexport) // library is built - #else - #define NYANAPI __declspec(dllimport) // library is used - #endif /* nyan_EXPORTS */ + #if defined(nyan_EXPORTS) + #define NYANAPI __declspec(dllexport) // library is built + #else + #define NYANAPI __declspec(dllimport) // library is used + #endif /* nyan_EXPORTS */ #else - #define NYANAPI __attribute__((visibility("default"))) + #define NYANAPI __attribute__((visibility("default"))) #endif diff --git a/nyan/config.h b/nyan/config.h index 3048d81..454415c 100644 --- a/nyan/config.h +++ b/nyan/config.h @@ -1,17 +1,16 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #ifdef _MSC_VER // Allow using alternative operator representation with non-conforming compiler - #include + #include #endif #include #include #include - namespace nyan { /** number of spaces per indent **/ @@ -41,5 +40,16 @@ using value_int_t = int64_t; /** type used for nyan::Float values */ using value_float_t = double; +/** positive infinity for nyan::Int values */ +constexpr const value_int_t INT_POS_INF = std::numeric_limits::max(); + +/** negative infinity for nyan::Int values */ +constexpr const value_int_t INT_NEG_INF = std::numeric_limits::min(); + +/** positive infinity for nyan::Float values */ +constexpr const value_float_t FLOAT_POS_INF = std::numeric_limits::infinity(); + +/** negative infinity for nyan::Float values */ +constexpr const value_float_t FLOAT_NEG_INF = -std::numeric_limits::infinity(); } // namespace nyan diff --git a/nyan/curve.cpp b/nyan/curve.cpp index 08d5cf0..a0a1089 100644 --- a/nyan/curve.cpp +++ b/nyan/curve.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "curve.h" diff --git a/nyan/curve.h b/nyan/curve.h index 2e0a1cd..8f99708 100644 --- a/nyan/curve.h +++ b/nyan/curve.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -17,120 +17,146 @@ template class Curve { public: - using container_t = std::map; + using container_t = std::map; - using fallback_t = std::function; + using fallback_t = std::function; - Curve() {} + Curve() {} #ifdef CURVE_FALLBACK_FUNCTION - Curve(const fallback_t &func) - : - fallback{func} {} - - /** - * Fallback function, used if a time before the first - * entry is used. - */ - fallback_t fallback; + Curve(const fallback_t &func) + : + fallback{func} {} + + /** + * Fallback function, used if a time before the first + * entry is used. + */ + fallback_t fallback; #endif - // TODO: maybe add variants of the functions below - // which also return the time the keyframe belongs to. - - /** - * Get the latest value at given time. - */ - const T &at(const order_t time) const { - // search for element which is greater than time - auto it = this->container.upper_bound(time); - if (it == std::begin(this->container)) { + // TODO: maybe add variants of the functions below + // which also return the time the keyframe belongs to. + + /** + * Get the first value after a given point in time. + * + * @param time The point in time after which the method searches for a value. + * + * @return The first value that can be found after 'time'. + */ + const T &at(const order_t time) const { + // search for element which is greater than time + auto it = this->container.upper_bound(time); + if (it == std::begin(this->container)) { #ifdef CURVE_FALLBACK_FUNCTION - if (likely(this->fallback)) { - return this->fallback(time); - } - else { + if (likely(this->fallback)) { + return this->fallback(time); + } + else { #endif - throw InternalError{ - "requested time lower than first curve entry" - }; + throw InternalError{ + "requested time lower than first curve entry" + }; #ifdef CURVE_FALLBACK_FUNCTION - } + } #endif - } - - // go one back, so it's less or equal the requested time. - --it; - return it->second; - } - - /** - * Like `at`, but returns nullptr if no keyframe was found. - */ - const T *at_find(const order_t time) const { - auto it = this->container.upper_bound(time); - if (it == std::begin(this->container)) { - return nullptr; - } - --it; - return &it->second; - } - - /** - * Get the value at the exact time. - */ - const T *at_exact(const order_t time) const { - auto it = this->container.find(time); - if (it == std::end(this->container)) { - return nullptr; - } - - return &it->second; - } - - /** - * Get the value which active earlier than given time. - */ - const T &before(const order_t time) const { - // search for element which is not less than the given time. - auto it = this->container.lower_bound(time); - if (it == std::begin(this->container)) { - throw InternalError{"curve has no previous keyframe"}; - } - - // go one back, so it's less than the requested time. - --it; - return it->second; - } - - /** - * No data is stored in the curve. - */ - bool empty() const { - return this->container.empty(); - } - - /** - * Add a new value at the given time. - */ - T &insert_drop(const order_t time, T &&value) { - auto it = this->container.lower_bound(time); - - // remove all elements greater or equal the requested time - this->container.erase(it, std::end(this->container)); - - // insert the new keyframe - auto ret = this->container.insert({time, std::move(value)}); - if (unlikely(ret.second == false)) { - throw InternalError{"did not insert value, it existed before"}; - } - - return ret.first->second; - } + } + + // go one back, so it's less or equal the requested time. + --it; + return it->second; + } + + /** + * Like `at`, but returns nullptr if no keyframe was found. + * + * @param time The point in time after which the method searches for a value. + * + * @return The first value that can be found after 'time' if one exists, else nullptr. + */ + const T *at_find(const order_t time) const { + auto it = this->container.upper_bound(time); + if (it == std::begin(this->container)) { + return nullptr; + } + --it; + return &it->second; + } + + /** + * Get the value at the exact time. + * + * @param time The point in time at which the value should be retrieved. + * + * @return Value at the given time if it exists, else nullptr. + */ + const T *at_exact(const order_t time) const { + auto it = this->container.find(time); + if (it == std::end(this->container)) { + return nullptr; + } + + return &it->second; + } + + /** + * Get the first value before a given point in time. + * + * @param time The point in time before which the method searches for a value. + * + * @return The first value that can be found before 'time'. + */ + const T &before(const order_t time) const { + // search for element which is not less than the given time. + auto it = this->container.lower_bound(time); + if (it == std::begin(this->container)) { + throw InternalError{"curve has no previous keyframe"}; + } + + // go one back, so it's less than the requested time. + --it; + return it->second; + } + + /** + * Check if no values are stored in the curve. + * + * @return true if the value container is empty, else false. + */ + bool empty() const { + return this->container.empty(); + } + + /** + * Insert a new keyframe with a value into the curve. + * + * @param time The point in time at which the value is inserted. + * @param value Value that is inserted. + * + * @return The inserted value. + */ + T &insert_drop(const order_t time, T &&value) { + auto it = this->container.lower_bound(time); + + // remove all elements greater or equal the requested time + this->container.erase(it, std::end(this->container)); + + // insert the new keyframe + auto ret = this->container.insert({time, std::move(value)}); + if (unlikely(ret.second == false)) { + throw InternalError{"did not insert value, it existed before"}; + } + + return ret.first->second; + } protected: - container_t container; + /** + * Keyframes of the curve, stored as a map of values by time. + */ + container_t container; }; } // namespace nyan diff --git a/nyan/database.cpp b/nyan/database.cpp index 788adc9..ecf9ca2 100644 --- a/nyan/database.cpp +++ b/nyan/database.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "database.h" @@ -22,13 +22,13 @@ namespace nyan { std::shared_ptr Database::create() { - return std::make_shared(); + return std::make_shared(); } Database::Database() - : - state{std::make_shared()} {} + : + state{std::make_shared()} {} Database::~Database() = default; @@ -50,171 +50,171 @@ static void ast_obj_walk_recurser(const ast_objwalk_cb_t &callback, const Namespace &ns, const std::vector &objs) { - // go over all objects - for (auto &astobj : objs) { - Namespace objname{ns, astobj.get_name().get()}; + // go over all objects + for (auto &astobj : objs) { + Namespace objname{ns, astobj.get_name().get()}; - // process nested objects first - ast_obj_walk_recurser(callback, scope, objname, astobj.get_objects()); + // process nested objects first + ast_obj_walk_recurser(callback, scope, objname, astobj.get_objects()); - // do whatever needed - callback(scope, ns, objname, astobj); - } + // do whatever needed + callback(scope, ns, objname, astobj); + } } static void ast_obj_walk(const namespace_lookup_t &imports, const ast_objwalk_cb_t &cb) { - // go over all the imported files - for (auto &it : imports) { - const Namespace &ns = it.first; - const NamespaceFinder ¤t_file = it.second; - const AST &ast = current_file.get_ast(); + // go over all the imported files + for (auto &it : imports) { + const Namespace &ns = it.first; + const NamespaceFinder ¤t_file = it.second; + const AST &ast = current_file.get_ast(); - // each file has many objects, which can be nested. - ast_obj_walk_recurser(cb, current_file, ns, ast.get_objects()); - } + // each file has many objects, which can be nested. + ast_obj_walk_recurser(cb, current_file, ns, ast.get_objects()); + } } void Database::load(const std::string &filename, const filefetcher_t &filefetcher) { - Parser parser; - - // tracking of imported namespaces (with aliases) - namespace_lookup_t imports; - - // namespaces to which were requested to be imported - // the location is the first request origin. - std::unordered_map to_import; - - // push the first namespace to import - to_import.insert( - { - Namespace::from_filename(filename), - Location{" -> requested by native call to Database::load()"} - } - ); - - while (to_import.size() > 0) { - auto cur_ns_it = to_import.begin(); - const Namespace &namespace_to_import = cur_ns_it->first; - const Location &req_location = cur_ns_it->second; - - auto it = imports.find(namespace_to_import); - if (it != std::end(imports)) { - // this namespace is already imported! - continue; - } - - std::shared_ptr current_file; - try { - // get the data and parse the file - current_file = filefetcher( - namespace_to_import.to_filename() - ); - } - catch (FileReadError &err) { - // the import request failed, - // so the nyan file structure or content is wrong. - throw LangError{req_location, err.str()}; - } - - // create import tracking entry for this file - // and parse the file contents! - NamespaceFinder &new_ns = imports.insert({ - namespace_to_import, // name of the import - NamespaceFinder{ - parser.parse(current_file) // read the ast! - } - }).first->second; - - // enqueue all new imports of this file - // and record import aliases - for (auto &import : new_ns.get_ast().get_imports()) { - Namespace request{import.get()}; - - // either register the alias - if (import.has_alias()) { - new_ns.add_alias(import.get_alias(), request); - } - // or the plain import - else { - new_ns.add_import(request); - } - - // check if this import was already requested or is known. - // todo: also check if that ns is already fully loaded in the db - auto was_imported = imports.find(request); - auto import_requested = to_import.find(request); - - if (was_imported == std::end(imports) and - import_requested == std::end(to_import)) { - - // add the request to the pending imports - to_import.insert({std::move(request), import.get()}); - } - } - - to_import.erase(cur_ns_it); - } - - - using namespace std::placeholders; - - size_t new_obj_count = 0; - - // first run: create empty object info objects - ast_obj_walk(imports, std::bind(&Database::create_obj_info, - this, &new_obj_count, - _1, _2, _3, _4)); - - std::vector new_objects; - new_objects.reserve(new_obj_count); - - // map object => new children. - std::unordered_map> obj_children; - - // now, all new object infos need to be filled with types - ast_obj_walk(imports, std::bind(&Database::create_obj_content, - this, - &new_objects, - &obj_children, - _1, _2, _3, _4)); - - // linearize the parents of all new objects - this->linearize_new(new_objects); - - // resolve the types of members to their definition - this->resolve_types(new_objects); - - // these objects were uses as values at some file location. - std::vector> objs_in_values; - - // state value creation - ast_obj_walk(imports, std::bind(&Database::create_obj_state, - this, &objs_in_values, - _1, _2, _3, _4)); - - // verify hierarchy consistency - this->check_hierarchy(new_objects, objs_in_values); - - // store the children mapping. - for (auto &it : obj_children) { - auto &obj = it.first; - auto &children = it.second; - - ObjectInfo *info = this->meta_info.get_object(obj); - if (unlikely(info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - - info->set_children(std::move(children)); - } - - // TODO: check pending objectvalues (probably not needed as they're all loaded) + Parser parser; + + // tracking of imported namespaces (with aliases) + namespace_lookup_t imports; + + // namespaces to which were requested to be imported + // the location is the first request origin. + std::unordered_map to_import; + + // push the first namespace to import + to_import.insert( + { + Namespace::from_filename(filename), + Location{" -> requested by native call to Database::load()"} + } + ); + + while (to_import.size() > 0) { + auto cur_ns_it = to_import.begin(); + const Namespace &namespace_to_import = cur_ns_it->first; + const Location &req_location = cur_ns_it->second; + + auto it = imports.find(namespace_to_import); + if (it != std::end(imports)) { + // this namespace is already imported! + continue; + } + + std::shared_ptr current_file; + try { + // get the data and parse the file + current_file = filefetcher( + namespace_to_import.to_filename() + ); + } + catch (FileReadError &err) { + // the import request failed, + // so the nyan file structure or content is wrong. + throw LangError{req_location, err.str()}; + } + + // create import tracking entry for this file + // and parse the file contents! + NamespaceFinder &new_ns = imports.insert({ + namespace_to_import, // name of the import + NamespaceFinder{ + parser.parse(current_file) // read the ast! + } + }).first->second; + + // enqueue all new imports of this file + // and record import aliases + for (auto &import : new_ns.get_ast().get_imports()) { + Namespace request{import.get()}; + + // either register the alias + if (import.has_alias()) { + new_ns.add_alias(import.get_alias(), request); + } + // or the plain import + else { + new_ns.add_import(request); + } + + // check if this import was already requested or is known. + // todo: also check if that ns is already fully loaded in the db + auto was_imported = imports.find(request); + auto import_requested = to_import.find(request); + + if (was_imported == std::end(imports) and + import_requested == std::end(to_import)) { + + // add the request to the pending imports + to_import.insert({std::move(request), import.get()}); + } + } + + to_import.erase(cur_ns_it); + } + + + using namespace std::placeholders; + + size_t new_obj_count = 0; + + // first run: create empty object info objects + ast_obj_walk(imports, std::bind(&Database::create_obj_info, + this, &new_obj_count, + _1, _2, _3, _4)); + + std::vector new_objects; + new_objects.reserve(new_obj_count); + + // map object => new children. + std::unordered_map> obj_children; + + // now, all new object infos need to be filled with types + ast_obj_walk(imports, std::bind(&Database::create_obj_content, + this, + &new_objects, + &obj_children, + _1, _2, _3, _4)); + + // linearize the parents of all new objects + this->linearize_new(new_objects); + + // resolve the types of members to their definition + this->resolve_types(new_objects); + + // these objects were uses as values at some file location. + std::vector> objs_in_values; + + // state value creation + ast_obj_walk(imports, std::bind(&Database::create_obj_state, + this, &objs_in_values, + _1, _2, _3, _4)); + + // verify hierarchy consistency + this->check_hierarchy(new_objects, objs_in_values); + + // store the children mapping. + for (auto &it : obj_children) { + auto &obj = it.first; + auto &children = it.second; + + ObjectInfo *info = this->meta_info.get_object(obj); + if (unlikely(info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + + info->set_children(std::move(children)); + } + + // TODO: check pending objectvalues (probably not needed as they're all loaded) } @@ -224,24 +224,24 @@ void Database::create_obj_info(size_t *counter, const Namespace &objname, const ASTObject &astobj) { - const std::string &name = astobj.name.get(); + const std::string &name = astobj.name.get(); - // object name must not be an alias - if (current_file.check_conflict(name)) { - // TODO: show conflict origin (the import) - throw NameError{ - astobj.name, - "object name conflicts with import", - name - }; - } + // object name must not be an alias + if (current_file.check_conflict(name)) { + // TODO: show conflict origin (the import) + throw NameError{ + astobj.name, + "object name conflicts with import", + name + }; + } - this->meta_info.add_object( - objname.to_fqon(), - ObjectInfo{astobj.name} - ); + this->meta_info.add_object( + objname.to_fqon(), + ObjectInfo{astobj.name} + ); - *counter += 1; + *counter += 1; } @@ -252,101 +252,101 @@ void Database::create_obj_content(std::vector *new_objs, const Namespace &objname, const ASTObject &astobj) { - fqon_t obj_fqon = objname.to_fqon(); - new_objs->push_back(obj_fqon); - - ObjectInfo *info = this->meta_info.get_object(obj_fqon); - if (unlikely(info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - - // save the patch target, has to be alias-expanded - const IDToken &target = astobj.target; - if (target.exists()) { - fqon_t target_id = scope.find(ns, target, this->meta_info); - info->add_patch(std::make_shared(std::move(target_id)), true); - } - - // a patch may add inheritance parents - for (auto &change : astobj.inheritance_change) { - inher_change_t change_type = change.get_type(); - fqon_t new_parent_id = scope.find(ns, change.get_target(), this->meta_info); - info->add_inheritance_change(InheritanceChange{change_type, std::move(new_parent_id)}); - } - - // parents are stored in the object data state - std::deque object_parents; - for (auto &parent : astobj.parents) { - fqon_t parent_id = scope.find(ns, parent, this->meta_info); - - // this object is therefore a child of the parent one. - auto ins = child_assignments->emplace( - parent_id, - std::unordered_set{} - ); - ins.first->second.insert(obj_fqon); - - object_parents.push_back(std::move(parent_id)); - } - - // fill initial state: - this->state->add_object( - obj_fqon, - std::make_shared( - std::move(object_parents) - ) - ); - - // create member types - for (auto &astmember : astobj.members) { - - // TODO: the member name requires advanced expansions - // for conflict resolving - - MemberInfo &member_info = info->add_member( - astmember.name.str(), - MemberInfo{astmember.name} - ); - - if (not astmember.type.exists()) { - continue; - } - - // if existing, create type information of member. - member_info.set_type( - std::make_shared( - astmember.type, - scope, - objname, - this->meta_info - ), - true // type was defined in the ast -> initial definition - ); - } + fqon_t obj_fqon = objname.to_fqon(); + new_objs->push_back(obj_fqon); + + ObjectInfo *info = this->meta_info.get_object(obj_fqon); + if (unlikely(info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + + // save the patch target, has to be alias-expanded + const IDToken &target = astobj.target; + if (target.exists()) { + fqon_t target_id = scope.find(ns, target, this->meta_info); + info->add_patch(std::make_shared(std::move(target_id)), true); + } + + // a patch may add inheritance parents + for (auto &change : astobj.inheritance_change) { + inher_change_t change_type = change.get_type(); + fqon_t new_parent_id = scope.find(ns, change.get_target(), this->meta_info); + info->add_inheritance_change(InheritanceChange{change_type, std::move(new_parent_id)}); + } + + // parents are stored in the object data state + std::deque object_parents; + for (auto &parent : astobj.parents) { + fqon_t parent_id = scope.find(ns, parent, this->meta_info); + + // this object is therefore a child of the parent one. + auto ins = child_assignments->emplace( + parent_id, + std::unordered_set{} + ); + ins.first->second.insert(obj_fqon); + + object_parents.push_back(std::move(parent_id)); + } + + // fill initial state: + this->state->add_object( + obj_fqon, + std::make_shared( + std::move(object_parents) + ) + ); + + // create member types + for (auto &astmember : astobj.members) { + + // TODO: the member name requires advanced expansions + // for conflict resolving + + MemberInfo &member_info = info->add_member( + astmember.name.str(), + MemberInfo{astmember.name} + ); + + if (not astmember.type.exists()) { + continue; + } + + // if existing, create type information of member. + member_info.set_type( + std::make_shared( + astmember.type, + scope, + objname, + this->meta_info + ), + true // type was defined in the ast -> initial definition + ); + } } void Database::linearize_new(const std::vector &new_objects) { - // linearize the parents of all newly created objects - - for (auto &obj : new_objects) { - std::unordered_set seen; - - ObjectInfo *obj_info = this->meta_info.get_object(obj); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object information not retrieved"}; - } - - obj_info->set_linearization( - linearize_recurse( - obj, - [this] (const fqon_t &name) -> const ObjectState & { - return **this->state->get(name); - }, - &seen - ) - ); - } + // linearize the parents of all newly created objects + + for (auto &obj : new_objects) { + std::unordered_set seen; + + ObjectInfo *obj_info = this->meta_info.get_object(obj); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object information not retrieved"}; + } + + obj_info->set_linearization( + linearize_recurse( + obj, + [this] (const fqon_t &name) -> const ObjectState & { + return **this->state->get(name); + }, + &seen + ) + ); + } } @@ -358,171 +358,171 @@ void Database::find_member(bool skip_first, const MemberInfo &, const Member *)> &member_found) { - bool finished = false; - - // member doesn't have type yet. find it. - for (auto &obj : search_objs) { - - // at the very beginning, we have to skip the object - // we want to find the type for. it's the first in the linearization. - if (skip_first) { - skip_first = false; - continue; - } - - ObjectInfo *obj_info = this->meta_info.get_object(obj); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object information not retrieved"}; - } - const MemberInfo *obj_member_info = obj_info->get_member(member_id); - - // obj doesn't have this member - if (not obj_member_info) { - continue; - } - - const ObjectState *par_state = this->state->get(obj)->get(); - if (unlikely(par_state == nullptr)) { - throw InternalError{"object state not retrieved"}; - } - const Member *member = par_state->get(member_id); - - finished = member_found(obj, *obj_member_info, member); - - if (finished) { - break; - } - } - - // recurse into the patch target - if (not finished and obj_info.is_patch()) { - const fqon_t &target = obj_info.get_patch()->get_target(); - const ObjectInfo *obj_info = this->meta_info.get_object(target); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"target not found in metainfo"}; - } - - // recurse into the target. - // check if the patch defines the member as well -> error. - // otherwise, infer type from patch. - this->find_member(false, member_id, - obj_info->get_linearization(), - *obj_info, member_found); - } + bool finished = false; + + // member doesn't have type yet. find it. + for (auto &obj : search_objs) { + + // at the very beginning, we have to skip the object + // we want to find the type for. it's the first in the linearization. + if (skip_first) { + skip_first = false; + continue; + } + + ObjectInfo *obj_info = this->meta_info.get_object(obj); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object information not retrieved"}; + } + const MemberInfo *obj_member_info = obj_info->get_member(member_id); + + // obj doesn't have this member + if (not obj_member_info) { + continue; + } + + const ObjectState *par_state = this->state->get(obj)->get(); + if (unlikely(par_state == nullptr)) { + throw InternalError{"object state not retrieved"}; + } + const Member *member = par_state->get(member_id); + + finished = member_found(obj, *obj_member_info, member); + + if (finished) { + break; + } + } + + // recurse into the patch target + if (not finished and obj_info.is_patch()) { + const fqon_t &target = obj_info.get_patch()->get_target(); + const ObjectInfo *obj_info = this->meta_info.get_object(target); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"target not found in metainfo"}; + } + + // recurse into the target. + // check if the patch defines the member as well -> error. + // otherwise, infer type from patch. + this->find_member(false, member_id, + obj_info->get_linearization(), + *obj_info, member_found); + } } void Database::resolve_types(const std::vector &new_objects) { - using namespace std::string_literals; - - // TODO: if inheritance parents are added, - // should a patch be able to modify the newly accessible members? - - // link patch information to the origin patch - // and check if there's not multiple patche targets per object hierarchy - for (auto &obj : new_objects) { - ObjectInfo *obj_info = this->meta_info.get_object(obj); - - const auto &linearization = obj_info->get_linearization(); - if (unlikely(linearization.size() < 1)) { - throw InternalError{ - "Linearization doesn't contain obj itself." - }; - } - - auto it = std::begin(linearization); - // skip first, it's the object itself. - ++it; - for (auto end = std::end(linearization); it != end; ++it) { - ObjectInfo *parent_info = this->meta_info.get_object(*it); - - if (parent_info->is_initial_patch()) { - if (unlikely(obj_info->is_initial_patch())) { - // TODO: show patch target instead of member - throw LangError{ - obj_info->get_location(), - "child patches can't declare a patch target", - {{parent_info->get_location(), "parent that declares the patch"}} - }; - } - else { - // this is patch because of inheritance. - // false => it wasn't initially a patch. - obj_info->add_patch(parent_info->get_patch(), false); - } - } - } - } - - // resolve member types: - // link member types to matching parent if not known yet. - // this required that patch targets are linked. - for (auto &obj : new_objects) { - ObjectInfo *obj_info = this->meta_info.get_object(obj); - - const auto &linearization = obj_info->get_linearization(); - - // resolve the type for each member - for (auto &it : obj_info->get_members()) { - const memberid_t &member_id = it.first; - MemberInfo &member_info = it.second; - - // if the member already defines it, we found it already. - // we still need to check for conflicts though. - bool type_found = member_info.is_initial_def(); - - // start the recursion into the inheritance tree, - // which includes the recursion into patch targets. - this->find_member( - true, // make sure the object we search the type for isn't checked with itself. - member_id, linearization, *obj_info, - [&member_info, &type_found, &member_id] - (const fqon_t &parent, - const MemberInfo &source_member_info, - const Member *) { - - if (source_member_info.is_initial_def()) { - const std::shared_ptr &new_type = source_member_info.get_type(); - - if (unlikely(not new_type.get())) { - throw InternalError{"initial type definition has no type"}; - } - - // check if the member we're looking for isn't already typed. - if (unlikely(member_info.is_initial_def())) { - // another parent defines this type, - // which is disallowed! - - // TODO: show location of infringing type instead of member - throw LangError{ - member_info.get_location(), - ("parent '"s + parent - + "' already defines type of '" + member_id + "'"), - {{source_member_info.get_location(), "parent that declares the member"}} - }; - } - - type_found = true; - member_info.set_type(new_type, false); - } - // else that member knows the type, - // but we're looking for the initial definition. - - // we need to traverse all members and never stop early. - return false; - } - ); - - if (unlikely(not type_found)) { - throw TypeError{ - member_info.get_location(), - "could not infer type of '"s + member_id - + "' from parents or patch target" - }; - } - } - } + using namespace std::string_literals; + + // TODO: if inheritance parents are added, + // should a patch be able to modify the newly accessible members? + + // link patch information to the origin patch + // and check if there's not multiple patche targets per object hierarchy + for (auto &obj : new_objects) { + ObjectInfo *obj_info = this->meta_info.get_object(obj); + + const auto &linearization = obj_info->get_linearization(); + if (unlikely(linearization.size() < 1)) { + throw InternalError{ + "Linearization doesn't contain obj itself." + }; + } + + auto it = std::begin(linearization); + // skip first, it's the object itself. + ++it; + for (auto end = std::end(linearization); it != end; ++it) { + ObjectInfo *parent_info = this->meta_info.get_object(*it); + + if (parent_info->is_initial_patch()) { + if (unlikely(obj_info->is_initial_patch())) { + // TODO: show patch target instead of member + throw LangError{ + obj_info->get_location(), + "child patches can't declare a patch target", + {{parent_info->get_location(), "parent that declares the patch"}} + }; + } + else { + // this is patch because of inheritance. + // false => it wasn't initially a patch. + obj_info->add_patch(parent_info->get_patch(), false); + } + } + } + } + + // resolve member types: + // link member types to matching parent if not known yet. + // this required that patch targets are linked. + for (auto &obj : new_objects) { + ObjectInfo *obj_info = this->meta_info.get_object(obj); + + const auto &linearization = obj_info->get_linearization(); + + // resolve the type for each member + for (auto &it : obj_info->get_members()) { + const memberid_t &member_id = it.first; + MemberInfo &member_info = it.second; + + // if the member already defines it, we found it already. + // we still need to check for conflicts though. + bool type_found = member_info.is_initial_def(); + + // start the recursion into the inheritance tree, + // which includes the recursion into patch targets. + this->find_member( + true, // make sure the object we search the type for isn't checked with itself. + member_id, linearization, *obj_info, + [&member_info, &type_found, &member_id] + (const fqon_t &parent, + const MemberInfo &source_member_info, + const Member *) { + + if (source_member_info.is_initial_def()) { + const std::shared_ptr &new_type = source_member_info.get_type(); + + if (unlikely(not new_type.get())) { + throw InternalError{"initial type definition has no type"}; + } + + // check if the member we're looking for isn't already typed. + if (unlikely(member_info.is_initial_def())) { + // another parent defines this type, + // which is disallowed! + + // TODO: show location of infringing type instead of member + throw LangError{ + member_info.get_location(), + ("parent '"s + parent + + "' already defines type of '" + member_id + "'"), + {{source_member_info.get_location(), "parent that declares the member"}} + }; + } + + type_found = true; + member_info.set_type(new_type, false); + } + // else that member knows the type, + // but we're looking for the initial definition. + + // we need to traverse all members and never stop early. + return false; + } + ); + + if (unlikely(not type_found)) { + throw TypeError{ + member_info.get_location(), + "could not infer type of '"s + member_id + + "' from parents or patch target" + }; + } + } + } } @@ -532,284 +532,282 @@ void Database::create_obj_state(std::vector> *objs_i const Namespace &objname, const ASTObject &astobj) { - using namespace std::string_literals; - - if (astobj.members.size() == 0) { - // no members, nothing to do. - return; - } - - ObjectInfo *info = this->meta_info.get_object(objname.to_fqon()); - if (unlikely(info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - - ObjectState &objstate = **this->state->get(objname.to_fqon()); - - std::unordered_map members; - - // create member values - for (auto &astmember : astobj.members) { - - // member has no value - if (not astmember.value.exists()) { - continue; - } - - // TODO: the member name may need some resolution for conflicts - const memberid_t &memberid = astmember.name.str(); - - const MemberInfo *member_info = info->get_member(memberid); - if (unlikely(member_info == nullptr)) { - throw InternalError{"member info could not be retrieved"}; - } - - const Type *member_type = member_info->get_type().get(); - if (unlikely(member_type == nullptr)) { - throw InternalError{"member type could not be retrieved"}; - } - - nyan_op operation = astmember.operation; - - if (unlikely(operation == nyan_op::INVALID)) { - // the ast buildup should have ensured this. - throw InternalError{"member has value but no operator"}; - } - - // create the member with operation and value - Member &new_member = members.emplace( - memberid, - Member{ - 0, // TODO: get override depth from AST (the @-count) - operation, - Value::from_ast( - *member_type, astmember.value, - // function to determine object names used in values: - [&scope, &objname, this, &objs_in_values] - (const Type &target_type, - const IDToken &token) -> fqon_t { - - // find the desired object in the scope of the object - fqon_t obj_id = scope.find(objname, token, this->meta_info); - - ObjectInfo *obj_info = this->meta_info.get_object(obj_id); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - - const auto &obj_lin = obj_info->get_linearization(); - - // check if the type of the value is okay - // (i.e. it's in the linearization) - if (unlikely(not util::contains(obj_lin, target_type.get_target()))) { - - throw TypeError{ - token, - "value (resolved as "s + obj_id - + ") does not match type " + target_type.get_target() - }; - } - - // remember to check if this object can be used as value - objs_in_values->push_back({obj_id, Location{token}}); - - return obj_id; - } - ) - } - ).first->second; - - // let the value determine if it can work together - // with the member type. - const Value &new_value = new_member.get_value(); - const std::unordered_set &allowed_ops = new_value.allowed_operations(*member_type); - - if (unlikely(allowed_ops.find(operation) == std::end(allowed_ops))) { - // TODO: show location of operation, not the member name - - // I'm really sorry for this flower meadow of an error message. - throw TypeError{ - astmember.name, - "invalid operator "s - + op_to_string(operation) - + ": member type "s - + member_type->str() - + - (allowed_ops.size() > 0 - ? - " only allows operations '"s - + util::strjoin( - ", ", allowed_ops, - [] (const nyan_op &op) { - return op_to_string(op); - } - ) - + "' " - : - " allows no operations " - ) - + "for value " - + new_value.str() - }; - } - } - - objstate.set_members(std::move(members)); + using namespace std::string_literals; + + if (astobj.members.size() == 0) { + // no members, nothing to do. + return; + } + + ObjectInfo *info = this->meta_info.get_object(objname.to_fqon()); + if (unlikely(info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + + ObjectState &objstate = **this->state->get(objname.to_fqon()); + + std::unordered_map members; + + // create member values + for (auto &astmember : astobj.members) { + + // member has no value + if (not astmember.value.exists()) { + continue; + } + + // TODO: the member name may need some resolution for conflicts + const memberid_t &memberid = astmember.name.str(); + + const MemberInfo *member_info = info->get_member(memberid); + if (unlikely(member_info == nullptr)) { + throw InternalError{"member info could not be retrieved"}; + } + + const Type *member_type = member_info->get_type().get(); + if (unlikely(member_type == nullptr)) { + throw InternalError{"member type could not be retrieved"}; + } + + nyan_op operation = astmember.operation; + + if (unlikely(operation == nyan_op::INVALID)) { + // the ast buildup should have ensured this. + throw InternalError{"member has value but no operator"}; + } + + // create the member with operation, type and value + Member &new_member = members.emplace( + memberid, + Member{ + 0, // TODO: get override depth from AST (the @-count) + operation, + *member_type, + Value::from_ast( + *member_type, astmember.value, objs_in_values, + // function to determine object names used in values: + [&scope, &objname, this, &objs_in_values] + (const IDToken &token) -> fqon_t { + // find the desired object in the scope of the object + fqon_t obj_id = scope.find(objname, token, this->meta_info); + + // remember to check if this object can be used as value + objs_in_values->push_back({obj_id, Location{token}}); + + return obj_id; + }, + // function to retrieve an object's linearization + [&objname, this] + (const fqon_t &fqon) -> std::vector { + const ObjectInfo *obj_info = meta_info.get_object(fqon); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + + return obj_info->get_linearization(); + } + ) + } + ).first->second; + + // let the value determine if it can work together + // with the member type. + const Value &new_value = new_member.get_value(); + + // Remove the outer modifiers of the type as they + // are not relevant for allowed operations + Type unpacked_type = *member_type; + while (unpacked_type.is_modifier()) { + unpacked_type = unpacked_type.get_element_type()->at(0); + } + const std::unordered_set &allowed_ops = new_value.allowed_operations(unpacked_type); + + if (unlikely(allowed_ops.find(operation) == std::end(allowed_ops))) { + // TODO: show location of operation, not the member name + + // I'm really sorry for this flower meadow of an error message. + throw TypeError{ + astmember.name, + "invalid operator "s + + op_to_string(operation) + + ": member type "s + + member_type->str() + + + (allowed_ops.size() > 0 + ? + " only allows operations '"s + + util::strjoin( + ", ", allowed_ops, + [] (const nyan_op &op) { + return op_to_string(op); + } + ) + + "' " + : + " allows no operations " + ) + + "for value " + + new_value.str() + }; + } + } + + objstate.set_members(std::move(members)); } void Database::check_hierarchy(const std::vector &new_objs, const std::vector> &objs_in_values) { - using namespace std::string_literals; - - for (auto &obj : new_objs) { - - ObjectInfo *obj_info = this->meta_info.get_object(obj); - ObjectState *obj_state = this->state->get(obj)->get(); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - if (unlikely(obj_state == nullptr)) { - throw InternalError{"initial object state could not be retrieved"}; - } - - // check if an object has inher parent adds, it must be a patch. - if (obj_info->get_inheritance_change().size() > 0) { - if (unlikely(not obj_info->is_patch())) { - throw LangError{ - obj_info->get_location(), - "Inheritance additions can only be done in patches." - }; - } - } - - const auto &linearization = obj_info->get_linearization(); - - // check that relative operators can't be performed when the parent has no value. - for (auto &it : obj_state->get_members()) { - bool assign_ok = false; - bool other_op = false; - - this->find_member( - false, it.first, linearization, *obj_info, - [&assign_ok, &other_op] - (const fqon_t &, - const MemberInfo &, - const Member *member) { - // member has no value - if (member == nullptr) { - return false; - } - - nyan_op op = member->get_operation(); - if (op == nyan_op::ASSIGN) { - assign_ok = true; - return true; - } - else if (likely(op != nyan_op::INVALID)) { - other_op = true; - } - else { - // op == INVALID - throw InternalError{"member has invalid operator"}; - } - return false; - } - ); - - if (unlikely(other_op and not assign_ok)) { - const MemberInfo *member_info = obj_info->get_member(it.first); - throw LangError{ - member_info->get_location(), - "this member was never assigned a value." - }; - } - } - - // TODO: check the @-propagation is type-compatible for each operator - // -> can we even know? yes, as the patch target depth must be >= @-count. - } - - - std::unordered_set obj_values_ok; - - for (auto &it : objs_in_values) { - const fqon_t &obj_id = it.first; - - if (obj_values_ok.find(obj_id) != std::end(obj_values_ok)) { - // the object is non-abstract. - continue; - } - - ObjectInfo *obj_info = this->meta_info.get_object(obj_id); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object info could not be retrieved"}; - } - - const auto &lin = obj_info->get_linearization(); - std::unordered_set pending_members; - - for (auto obj = std::rbegin(lin); obj != std::rend(lin); ++obj) { - const ObjectInfo *obj_info = this->meta_info.get_object(*obj); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object used as value has no metainfo"}; - } - const ObjectState *obj_state = this->state->get(*obj)->get(); - if (unlikely(obj_state == nullptr)) { - throw InternalError{"object in hierarchy has no state"}; - } - - const auto &state_members = obj_state->get_members(); - - // the member is undefined if it's stored in the info - // but not in the state. - - for (auto &it : obj_info->get_members()) { - const memberid_t &member_id = it.first; - - if (state_members.find(member_id) == std::end(state_members)) { - // member is not in the state. - pending_members.insert(member_id); - } - } - - for (auto &it : state_members) { - const memberid_t &member_id = it.first; - const Member &member = it.second; - nyan_op op = member.get_operation(); - - // member has = operation, so it's no longer pending. - if (op == nyan_op::ASSIGN) { - pending_members.erase(member_id); - } - } - } - - if (pending_members.size() == 0) { - obj_values_ok.insert(obj_id); - } - else { - const Location &loc = it.second; - - throw TypeError{ - loc, - "this object has members without values: "s - + util::strjoin(", ", pending_members) - }; - } - } - - // TODO: check if @-overrides change an = to something else - // without a = remaining somewhere in a parent - // TODO: check if the @-depth is <= the patch depth - - // TODO: check if adding parents would be cyclic - // TODO: check if adding parents leads to member name clashes + using namespace std::string_literals; + + for (auto &obj : new_objs) { + + ObjectInfo *obj_info = this->meta_info.get_object(obj); + ObjectState *obj_state = this->state->get(obj)->get(); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + if (unlikely(obj_state == nullptr)) { + throw InternalError{"initial object state could not be retrieved"}; + } + + // check if an object has inher parent adds, it must be a patch. + if (obj_info->get_inheritance_change().size() > 0) { + if (unlikely(not obj_info->is_patch())) { + throw LangError{ + obj_info->get_location(), + "Inheritance additions can only be done in patches." + }; + } + } + + const auto &linearization = obj_info->get_linearization(); + + // check that relative operators can't be performed when the parent has no value. + for (auto &it : obj_state->get_members()) { + bool assign_ok = false; + bool other_op = false; + + this->find_member( + false, it.first, linearization, *obj_info, + [&assign_ok, &other_op] + (const fqon_t &, + const MemberInfo &, + const Member *member) { + // member has no value + if (member == nullptr) { + return false; + } + + nyan_op op = member->get_operation(); + if (op == nyan_op::ASSIGN) { + assign_ok = true; + return true; + } + else if (likely(op != nyan_op::INVALID)) { + other_op = true; + } + else { + // op == INVALID + throw InternalError{"member has invalid operator"}; + } + return false; + } + ); + + if (unlikely(other_op and not assign_ok)) { + const MemberInfo *member_info = obj_info->get_member(it.first); + throw LangError{ + member_info->get_location(), + "this member was never assigned a value." + }; + } + } + + // TODO: check the @-propagation is type-compatible for each operator + // -> can we even know? yes, as the patch target depth must be >= @-count. + } + + + std::unordered_set obj_values_ok; + + for (auto &it : objs_in_values) { + const fqon_t &obj_id = it.first; + + if (obj_values_ok.find(obj_id) != std::end(obj_values_ok)) { + // the object is non-abstract. + continue; + } + + ObjectInfo *obj_info = this->meta_info.get_object(obj_id); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object info could not be retrieved"}; + } + + const auto &lin = obj_info->get_linearization(); + std::unordered_set pending_members; + + for (auto obj = std::rbegin(lin); obj != std::rend(lin); ++obj) { + const ObjectInfo *obj_info = this->meta_info.get_object(*obj); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object used as value has no metainfo"}; + } + const ObjectState *obj_state = this->state->get(*obj)->get(); + if (unlikely(obj_state == nullptr)) { + throw InternalError{"object in hierarchy has no state"}; + } + + const auto &state_members = obj_state->get_members(); + + // the member is undefined if it's stored in the info + // but not in the state. + + for (auto &it : obj_info->get_members()) { + const memberid_t &member_id = it.first; + + if (state_members.find(member_id) == std::end(state_members)) { + // member is not in the state. + pending_members.insert(member_id); + } + } + + for (auto &it : state_members) { + const memberid_t &member_id = it.first; + const Member &member = it.second; + nyan_op op = member.get_operation(); + + // member has = operation, so it's no longer pending. + if (op == nyan_op::ASSIGN) { + pending_members.erase(member_id); + } + } + } + + if (pending_members.size() == 0) { + obj_values_ok.insert(obj_id); + } + else { + const Location &loc = it.second; + + throw TypeError{ + loc, + "this object has members without values: "s + + util::strjoin(", ", pending_members) + }; + } + } + + // TODO: check if @-overrides change an = to something else + // without a = remaining somewhere in a parent + // TODO: check if the @-depth is <= the patch depth + + // TODO: check if adding parents would be cyclic + // TODO: check if adding parents leads to member name clashes } std::shared_ptr Database::new_view() { - return std::make_shared(shared_from_this()); + return std::make_shared(shared_from_this()); } diff --git a/nyan/database.h b/nyan/database.h index f731f38..8b8d77e 100644 --- a/nyan/database.h +++ b/nyan/database.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -37,103 +37,173 @@ using namespace_lookup_t = std::unordered_map; */ class Database : public std::enable_shared_from_this { public: - /** - * Create a new nyan database. - */ - static std::shared_ptr create(); - - /** - * Construct an empty nyan database. - * In order for views to work, the database has to be a std::shared_ptr. - */ - Database(); - - ~Database(); - - /** - * Function that provides a file for a given filename. - * Used to query and open imported files. - */ - using filefetcher_t = std::function(const std::string &filename)>; - - /** - * Load a nyan file. - * This loads imported files as well. - */ - void load(const std::string &filename, - const filefetcher_t &filefetcher); - - /** - * Return a new view to the database, it allows changes. - */ - std::shared_ptr new_view(); - - /** - * Return the initial database state. - */ - const std::shared_ptr &get_state() const { - return this->state; - }; - - /** - * Return the type info storage. - */ - const MetaInfo &get_info() const { - return this->meta_info; - } + /** + * Create a new nyan database. + */ + static std::shared_ptr create(); + + /** + * Construct an empty nyan database. + * In order for views to work, the database has to be a std::shared_ptr. + */ + Database(); + + ~Database(); + + /** + * Function that provides a file for a given filename. + * Used to query and open imported files. + */ + using filefetcher_t = std::function(const std::string &filename)>; + + /** + * Load a nyan file. + * This loads imported files as well. + * + * @param filename Filename of the to-be-loaded file. + * @param filefetcher Function to extract the data from the file. + */ + void load(const std::string &filename, + const filefetcher_t &filefetcher); + + /** + * Return a new view to the database, it allows changes. + * + * @return Shared pointer to View on the database. + */ + std::shared_ptr new_view(); + + /** + * Return the initial database state. + * + * @return Shared pointer to initial State of the database. + */ + const std::shared_ptr &get_state() const { + return this->state; + }; + + /** + * Return the type info storage. + * + * @return The MetaInfo object for the database content. + */ + const MetaInfo &get_info() const { + return this->meta_info; + } protected: - void create_obj_info( - size_t *counter, - const NamespaceFinder ¤t_file, - const Namespace &ns, - const Namespace &objname, - const ASTObject &astobj - ); - - void create_obj_content( - std::vector *new_objs, - std::unordered_map> *child_assignments, - const NamespaceFinder ¤t_file, - const Namespace &ns, - const Namespace &objname, - const ASTObject &astobj - ); - - void create_obj_state( - std::vector> *objs_in_values, - const NamespaceFinder ¤t_file, - const Namespace &ns, - const Namespace &objname, - const ASTObject &astobj - ); - - void linearize_new(const std::vector &new_objs); - - void find_member( - bool skip_first, - const memberid_t &member_id, - const std::vector &search_objs, - const ObjectInfo &obj_info, - const std::function &member_found - ); - - void resolve_types(const std::vector &new_objs); - - void check_hierarchy(const std::vector &new_objs, - const std::vector> &objs_in_values); - - /** - * Database start state. - * Used as base for the changes, those are tracked in a View. - */ - std::shared_ptr state; - - /** - * Tracks type information and locations of the database content etc. - */ - MetaInfo meta_info; + /** + * Create the metadata information ObjectInfo for an object. + * + * @param counter Increments after ObjectInfo has been created. Used to count the created objects. + * @param current_file NamespaceFinder to check object naming conflicts. + * @param ns Namespace the object is in? + * @param objname Namespace created by the object. + * @param astobj Parsed object data from the AST. + */ + void create_obj_info( + size_t *counter, + const NamespaceFinder ¤t_file, + const Namespace &ns, + const Namespace &objname, + const ASTObject &astobj + ); + + /** + * Add an object's content to the metadata information ObjectInfo of + * an object. This will also create metadata information for the + * object's members. + * + * @param new_objs The object's identifier is appended here. Used to track which objects have been processed. + * @param child_assignments If the object is a child, it is mapped to its parents' fqons. Used to track children of objects. + * @param current_file NamespaceFinder to check object naming conflicts. + * @param ns Namespace the object is in. + * @param objname Namespace created by the object. + * @param astobj Parsed object data from the AST. + */ + void create_obj_content( + std::vector *new_objs, + std::unordered_map> *child_assignments, + const NamespaceFinder ¤t_file, + const Namespace &ns, + const Namespace &objname, + const ASTObject &astobj + ); + + /** + * Create the initial ObjectState of an object. + * + * @param objs_in_values Object identifiers in the object's member values that must be non-abstract. + * @param current_file NamespaceFinder to check object naming conflicts. + * @param ns Namespace the object is in. + * @param objname Namespace created by the object. + * @param astobj Parsed object data from the AST. + */ + void create_obj_state( + std::vector> *objs_in_values, + const NamespaceFinder ¤t_file, + const Namespace &ns, + const Namespace &objname, + const ASTObject &astobj + ); + + /** + * Linearizes the parents of all given objects. + * + * @param new_objs Identifiers of the objects that should be linearized. + */ + void linearize_new(const std::vector &new_objs); + + /** + * Find a member in a given list of objects and perform an operation on it. + * + * @param skip_first If true, the first object in the list is skipped. + * @param member_id Identifier of the member. + * @param search_objs List of objects that is searched. + * @param obj_info ObjectInfo of the initial object spawning the function call. + * @param member_found Function performed on the member after it has been found. + */ + void find_member( + bool skip_first, + const memberid_t &member_id, + const std::vector &search_objs, + const ObjectInfo &obj_info, + const std::function &member_found + ); + + /** + * Resolves types that are inherited by objects and members. This will + * find the origin types and store them in the child objects' and child + * members' metadata. + * + * @param new_objs Identifiers of the objects which should be resolved. + */ + void resolve_types(const std::vector &new_objs); + + /** + * Sanity check after creating the database, e.g. + * - checks if patches or objects are malformed. + * - checks if relative operators of members can be performed. + * - checks if objects in values are non-abstract. + * - checks if all members who have metadata are in the database state. + * + * @param new_objs Identifiers of the objects which should be checked. + * @param objs_in_values Object identifiers in the object's member values that must be non-abstract. + */ + void check_hierarchy(const std::vector &new_objs, + const std::vector> &objs_in_values); + + /** + * Database start state. + * Used as base for the changes, those are tracked in a View. + */ + std::shared_ptr state; + + /** + * Tracks type information and locations of the database content etc. + */ + MetaInfo meta_info; }; } // namespace nyan diff --git a/nyan/datastructure/orderedset.cpp b/nyan/datastructure/orderedset.cpp index 5e4d6fa..6fe0c6f 100644 --- a/nyan/datastructure/orderedset.cpp +++ b/nyan/datastructure/orderedset.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "orderedset.h" diff --git a/nyan/datastructure/orderedset.h b/nyan/datastructure/orderedset.h index fe170df..b928121 100644 --- a/nyan/datastructure/orderedset.h +++ b/nyan/datastructure/orderedset.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -16,226 +16,226 @@ namespace nyan::datastructure { template class OrderedSet { public: - OrderedSet() = default; - ~OrderedSet() = default; + OrderedSet() = default; + ~OrderedSet() = default; - OrderedSet(const OrderedSet &other) { - for (auto &value : other) { - this->insert(value); - } - } + OrderedSet(const OrderedSet &other) { + for (auto &value : other) { + this->insert(value); + } + } - const OrderedSet &operator =(const OrderedSet &other) { - for (auto &value : other) { - this->insert(value); - } - } + const OrderedSet &operator =(const OrderedSet &other) { + for (auto &value : other) { + this->insert(value); + } + } - // no moves allowed because they invalidate - // the ordering iterators. - OrderedSet(OrderedSet &&other) = delete; - const OrderedSet &operator =(OrderedSet &&other) = delete; + // no moves allowed because they invalidate + // the ordering iterators. + OrderedSet(OrderedSet &&other) = delete; + const OrderedSet &operator =(OrderedSet &&other) = delete; - /** - * Type of value contained in the set. - */ - using value_type = T; + /** + * Type of value contained in the set. + */ + using value_type = T; - /** - * Type of the list that preserves the element order. - */ - using order_list_t = std::list; + /** + * Type of the list that preserves the element order. + */ + using order_list_t = std::list; - /** - * Iterator for list elements. - */ - using list_iter = typename order_list_t::const_iterator; + /** + * Iterator for list elements. + */ + using list_iter = typename order_list_t::const_iterator; - /** - * Type of the value set. - * Stores to the ordered list iterator so we can access the order. - */ - using value_storage_t = std::unordered_map; + /** + * Type of the value set. + * Stores to the ordered list iterator so we can access the order. + */ + using value_storage_t = std::unordered_map; protected: - /** - * OrderedSet const_iterator. - * - * Basically relays to the list iterator, but it returns - * a T& because of double-dereferencing the iterator. - * That way, you can iterate over the actual set contents - * in the right order. - * - * Thanks C++ for such a small and readable implementation. - */ - class ConstIterator - : public std::iterator { - public: - - ConstIterator(list_iter iter) - : - iter{iter} {} - - virtual ~ConstIterator() = default; - - /** - * Advance the inner iterator to the next element. - */ - ConstIterator &operator ++() { - ++this->iter; - return *this; - } - - /** - * Get the element the inner iterator points to. - * The first iterator is the order-iterator. - * Dereferencing it provides a pointer to the data. - * Dereferencing that pointer gets the data reference. - */ - const T &operator *() const { - return *(*this->iter); - } - - /** - * Check if this iterator points to the same element - * as the other iterator. - */ - bool operator ==(const ConstIterator& other) const { - return (this->iter == other.iter); - } - - /** - * Check if the iterator does not point to the same - * element as the other iterator. - */ - bool operator !=(const ConstIterator& other) const { - return not (*this == other); - } - - protected: - list_iter iter; - }; + /** + * OrderedSet const_iterator. + * + * Basically relays to the list iterator, but it returns + * a T& because of double-dereferencing the iterator. + * That way, you can iterate over the actual set contents + * in the right order. + * + * Thanks C++ for such a small and readable implementation. + */ + class ConstIterator + : public std::iterator { + public: + + ConstIterator(list_iter iter) + : + iter{iter} {} + + virtual ~ConstIterator() = default; + + /** + * Advance the inner iterator to the next element. + */ + ConstIterator &operator ++() { + ++this->iter; + return *this; + } + + /** + * Get the element the inner iterator points to. + * The first iterator is the order-iterator. + * Dereferencing it provides a pointer to the data. + * Dereferencing that pointer gets the data reference. + */ + const T &operator *() const { + return *(*this->iter); + } + + /** + * Check if this iterator points to the same element + * as the other iterator. + */ + bool operator ==(const ConstIterator& other) const { + return (this->iter == other.iter); + } + + /** + * Check if the iterator does not point to the same + * element as the other iterator. + */ + bool operator !=(const ConstIterator& other) const { + return not (*this == other); + } + + protected: + list_iter iter; + }; public: - // just have a const_iterator, because sets don't support - // changing values in them! - using const_iterator = ConstIterator; + // just have a const_iterator, because sets don't support + // changing values in them! + using const_iterator = ConstIterator; protected: - /** - * list to preserve the set order. - */ - order_list_t value_order; + /** + * list to preserve the set order. + */ + order_list_t value_order; - /** - * unordered entry storage. - */ - value_storage_t values; + /** + * unordered entry storage. + */ + value_storage_t values; public: - /** - * Add an entry to the orderedset. - * If already in the set, move entry to the end. - */ - bool insert(const T &value) { - // maybe it is even faster if we check existence with - // this->values.find(value) first, although then - // we need to hash value twice: once for the find - // and once for the insert. - // most of the time it won't be in the list, - // so i chose this approach. - - // try new insert, get the iterator to the insertion place - // as list position, use a dummy which gets replaced below - auto [value_pos, new_insert] = this->values.emplace( - value, list_iter{}); - - if (not new_insert) { - // inserted again -> move it to the back in the order list - // -> delete the current order list entry - this->value_order.erase(value_pos->second); - } - - // the pointer is only invalidated when the element is deleted - // so we can store it in the order list - // the pointer is const, as the hashmap key must not change - // => this orderedset can't have modifying iterators! - const T *value_ptr = &(value_pos->first); - - // add a ptr to the value to the element order list at the end - auto list_ins = this->value_order.insert( - std::end(this->value_order), value_ptr - ); - - // and store the list iterator to the map - value_pos->second = list_ins; - return new_insert; - } - - // TODO: add add(T &&value) function - - - /** - * Remove all entries from the set. - */ - void clear() { - this->values.clear(); - this->value_order.clear(); - } - - - /** - * Erase an element from the set. - */ - size_t erase(const T &value) { - auto it = this->values.find(value); - if (it == std::end(this->values)) { - return 0; - } - - // remove the order entry - this->value_order.erase(it->second); - - // and remove the value mapping - this->values.erase(it); - - return 1; - } - - - /** - * Is the specified value stored in this set? - */ - bool contains(const T &value) const { - return (this->values.find(value) != std::end(this->values)); - } - - - /** - * Return the number of elements stored. - */ - size_t size() const { - return this->value_order.size(); - } - - - /** provide the begin iterator of this set */ - const_iterator begin() const { - return {this->value_order.begin()}; - } - - - /** provide the end iterator of this set */ - const_iterator end() const { - return {this->value_order.end()}; - } + /** + * Add an entry to the orderedset. + * If already in the set, move entry to the end. + */ + bool insert(const T &value) { + // maybe it is even faster if we check existence with + // this->values.find(value) first, although then + // we need to hash value twice: once for the find + // and once for the insert. + // most of the time it won't be in the list, + // so i chose this approach. + + // try new insert, get the iterator to the insertion place + // as list position, use a dummy which gets replaced below + auto [value_pos, new_insert] = this->values.emplace( + value, list_iter{}); + + if (not new_insert) { + // inserted again -> move it to the back in the order list + // -> delete the current order list entry + this->value_order.erase(value_pos->second); + } + + // the pointer is only invalidated when the element is deleted + // so we can store it in the order list + // the pointer is const, as the hashmap key must not change + // => this orderedset can't have modifying iterators! + const T *value_ptr = &(value_pos->first); + + // add a ptr to the value to the element order list at the end + auto list_ins = this->value_order.insert( + std::end(this->value_order), value_ptr + ); + + // and store the list iterator to the map + value_pos->second = list_ins; + return new_insert; + } + + // TODO: add add(T &&value) function + + + /** + * Remove all entries from the set. + */ + void clear() { + this->values.clear(); + this->value_order.clear(); + } + + + /** + * Erase an element from the set. + */ + size_t erase(const T &value) { + auto it = this->values.find(value); + if (it == std::end(this->values)) { + return 0; + } + + // remove the order entry + this->value_order.erase(it->second); + + // and remove the value mapping + this->values.erase(it); + + return 1; + } + + + /** + * Is the specified value stored in this set? + */ + bool contains(const T &value) const { + return (this->values.find(value) != std::end(this->values)); + } + + + /** + * Return the number of elements stored. + */ + size_t size() const { + return this->value_order.size(); + } + + + /** provide the begin iterator of this set */ + const_iterator begin() const { + return {this->value_order.begin()}; + } + + + /** provide the end iterator of this set */ + const_iterator end() const { + return {this->value_order.end()}; + } }; } // namespace nyan::datastructure diff --git a/nyan/error.cpp b/nyan/error.cpp index 051d135..a17fc51 100644 --- a/nyan/error.cpp +++ b/nyan/error.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "error.h" @@ -24,59 +24,59 @@ using namespace std::string_literals; void Backtrace::analyze() { - std::vector buffer{32}; + std::vector buffer{32}; - // increase buffer size until it's enough - while (true) { - int elements = backtrace(buffer.data(), buffer.size()); - if (elements < static_cast(buffer.size())) { - buffer.resize(elements); - break; - } - buffer.resize(buffer.size() * 2); - } + // increase buffer size until it's enough + while (true) { + int elements = backtrace(buffer.data(), buffer.size()); + if (elements < static_cast(buffer.size())) { + buffer.resize(elements); + break; + } + buffer.resize(buffer.size() * 2); + } - for (void *element : buffer) { - this->stack_addrs.push_back(element); - } + for (void *element : buffer) { + this->stack_addrs.push_back(element); + } } void Backtrace::get_symbols(std::function cb, bool reversed) const { - backtrace_symbol symbol; + backtrace_symbol symbol; - if (reversed) { - for (size_t idx = this->stack_addrs.size(); idx-- > 0;) { - void *pc = this->stack_addrs[idx]; + if (reversed) { + for (size_t idx = this->stack_addrs.size(); idx-- > 0;) { + void *pc = this->stack_addrs[idx]; - symbol.functionname = util::symbol_name(pc, false, true); - symbol.pc = pc; + symbol.functionname = util::symbol_name(pc, false, true); + symbol.pc = pc; - cb(&symbol); - } - } else { - for (void *pc : this->stack_addrs) { - symbol.functionname = util::symbol_name(pc, false, true); - symbol.pc = pc; + cb(&symbol); + } + } else { + for (void *pc : this->stack_addrs) { + symbol.functionname = util::symbol_name(pc, false, true); + symbol.pc = pc; - cb(&symbol); - } - } + cb(&symbol); + } + } } void Backtrace::trim_to_current_stack_frame() { - Backtrace current; - current.analyze(); + Backtrace current; + current.analyze(); - while (not current.stack_addrs.empty() and not this->stack_addrs.empty()) { - if (this->stack_addrs.back() != current.stack_addrs.back()) { - break; - } + while (not current.stack_addrs.empty() and not this->stack_addrs.empty()) { + if (this->stack_addrs.back() != current.stack_addrs.back()) { + break; + } - this->stack_addrs.pop_back(); - current.stack_addrs.pop_back(); - } + this->stack_addrs.pop_back(); + current.stack_addrs.pop_back(); + } } @@ -88,117 +88,117 @@ bool Error::break_on_error = false; Error::Error(const std::string &msg, bool generate_backtrace, bool store_cause) - : - std::runtime_error{runtime_error_message}, - backtrace{nullptr}, - msg{msg} { + : + std::runtime_error{runtime_error_message}, + backtrace{nullptr}, + msg{msg} { - if (generate_backtrace) { - this->backtrace = std::make_shared(); - this->backtrace->analyze(); - } + if (generate_backtrace) { + this->backtrace = std::make_shared(); + this->backtrace->analyze(); + } - if (store_cause) { - this->store_cause(); - } + if (store_cause) { + this->store_cause(); + } - if (unlikely(this->break_on_error)) { - BREAKPOINT; - } + if (unlikely(this->break_on_error)) { + BREAKPOINT; + } } std::string Error::str() const { - return this->msg; + return this->msg; } const char *Error::what() const noexcept { - this->what_cache = this->str(); - return this->what_cache.c_str(); + this->what_cache = this->str(); + return this->what_cache.c_str(); } void Error::store_cause() { - if (not std::current_exception()) { - return; - } + if (not std::current_exception()) { + return; + } - try { - throw; - } catch (Error &cause) { - cause.trim_backtrace(); - this->cause = std::current_exception(); - } catch (...) { - this->cause = std::current_exception(); - } + try { + throw; + } catch (Error &cause) { + cause.trim_backtrace(); + this->cause = std::current_exception(); + } catch (...) { + this->cause = std::current_exception(); + } } void Error::trim_backtrace() { - if (this->backtrace) { - this->backtrace->trim_to_current_stack_frame(); - } + if (this->backtrace) { + this->backtrace->trim_to_current_stack_frame(); + } } void Error::rethrow_cause() const { - if (this->cause) { - std::rethrow_exception(this->cause); - } + if (this->cause) { + std::rethrow_exception(this->cause); + } } std::string Error::type_name() const { - return util::demangle(typeid(*this).name()); + return util::demangle(typeid(*this).name()); } Backtrace *Error::get_backtrace() const { - return this->backtrace.get(); + return this->backtrace.get(); } const std::string &Error::get_msg() const { - return this->msg; + return this->msg; } void Error::enable_break(bool enable) { - Error::break_on_error = enable; + Error::break_on_error = enable; } std::ostream &operator <<(std::ostream &os, const Error &e) { - // output the exception cause - bool had_a_cause = true; - try { - e.rethrow_cause(); - had_a_cause = false; - } catch (Error &cause) { - os << cause << std::endl; - } catch (std::exception &cause) { - os << util::demangle(typeid(cause).name()) << ": " << cause.what() << std::endl; - } - - if (had_a_cause) { - os << std::endl - << "The above exception was the direct cause " - "of the following exception:" - << std::endl << std::endl; - } - - // output the exception backtrace - if (e.get_backtrace()) { - os << *e.get_backtrace(); - } else { - os << "origin:" << std::endl; - } - - os << e.type_name() << ":" << std::endl; - os << e.str(); - - return os; + // output the exception cause + bool had_a_cause = true; + try { + e.rethrow_cause(); + had_a_cause = false; + } catch (Error &cause) { + os << cause << std::endl; + } catch (std::exception &cause) { + os << util::demangle(typeid(cause).name()) << ": " << cause.what() << std::endl; + } + + if (had_a_cause) { + os << std::endl + << "The above exception was the direct cause " + "of the following exception:" + << std::endl << std::endl; + } + + // output the exception backtrace + if (e.get_backtrace()) { + os << *e.get_backtrace(); + } else { + os << "origin:" << std::endl; + } + + os << e.type_name() << ":" << std::endl; + os << e.str(); + + return os; } @@ -206,20 +206,20 @@ std::ostream &operator <<(std::ostream &os, const Error &e) { * Prints a backtrace_symbol object. */ std::ostream &operator <<(std::ostream &os, const backtrace_symbol &bt_sym) { - // imitate the looks of a Python traceback. - os << " -> "; + // imitate the looks of a Python traceback. + os << " -> "; - if (bt_sym.functionname.empty()) { - os << '?'; - } else { - os << bt_sym.functionname; - } + if (bt_sym.functionname.empty()) { + os << '?'; + } else { + os << bt_sym.functionname; + } - if (bt_sym.pc != nullptr) { - os << " [" << bt_sym.pc << "]"; - } + if (bt_sym.pc != nullptr) { + os << " [" << bt_sym.pc << "]"; + } - return os; + return os; } @@ -227,24 +227,24 @@ std::ostream &operator <<(std::ostream &os, const backtrace_symbol &bt_sym) { * Prints an entire Backtrace object. */ std::ostream &operator <<(std::ostream &os, const Backtrace &bt) { - // imitate the looks of a Python traceback. - os << "Traceback (most recent call last):" << std::endl; + // imitate the looks of a Python traceback. + os << "Traceback (most recent call last):" << std::endl; - bt.get_symbols([&os](const backtrace_symbol *symbol) { - os << *symbol << std::endl; - }, true); + bt.get_symbols([&os](const backtrace_symbol *symbol) { + os << *symbol << std::endl; + }, true); - return os; + return os; } InternalError::InternalError(const std::string &msg) - : - Error{msg} {} + : + Error{msg} {} FileReadError::FileReadError(const std::string &msg) - : - Error{msg} {} + : + Error{msg} {} } // namespace nyan diff --git a/nyan/error.h b/nyan/error.h index 5b0fc0a..89f7bff 100644 --- a/nyan/error.h +++ b/nyan/error.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -19,8 +19,8 @@ namespace nyan { * Backtrace::get_symbols(). */ struct backtrace_symbol { - std::string functionname; // empty if unknown - void *pc; // nullptr if unknown + std::string functionname; // empty if unknown + void *pc; // nullptr if unknown }; @@ -29,45 +29,45 @@ struct backtrace_symbol { */ class Backtrace { public: - Backtrace() = default; - - virtual ~Backtrace() = default; - - /** - * Analyzes the current stack, and stores the program counter values in - * this->stack_addrs. - */ - void analyze(); - - /** - * Provide the names for all stack frames via the callback. - * - * The most recent call is returned last (alike Python). - * - * @param cb - * is called for every symbol in the backtrace, - * starting with the top-most frame. - * @param reversed - * if true, the most recent call is given last. - */ - void get_symbols(std::function cb, - bool reversed=true) const; - - /** - * Removes all the lower frames that are also present - * in the current stack. - * - * Designed to be used in catch clauses, - * to simulate stack trace collection - * from throw to catch, instead of from throw to the process entry point. - */ - void trim_to_current_stack_frame(); + Backtrace() = default; + + virtual ~Backtrace() = default; + + /** + * Analyzes the current stack, and stores the program counter values in + * this->stack_addrs. + */ + void analyze(); + + /** + * Provide the names for all stack frames via the callback. + * + * The most recent call is returned last (alike Python). + * + * @param cb + * is called for every symbol in the backtrace, + * starting with the top-most frame. + * @param reversed + * if true, the most recent call is given last. + */ + void get_symbols(std::function cb, + bool reversed=true) const; + + /** + * Removes all the lower frames that are also present + * in the current stack. + * + * Designed to be used in catch clauses, + * to simulate stack trace collection + * from throw to catch, instead of from throw to the process entry point. + */ + void trim_to_current_stack_frame(); protected: - /** - * All program counters of this backtrace. - */ - std::vector stack_addrs; + /** + * All program counters of this backtrace. + */ + std::vector stack_addrs; }; @@ -76,110 +76,131 @@ class Backtrace { */ class Error : public std::runtime_error { public: - Error(const std::string &msg, - bool generate_backtrace=true, - bool store_cause=true); - - virtual ~Error() = default; - - /** - * String representation of this exception, as - * specialized by a child exception. - */ - virtual std::string str() const; - - /** - * Returns the message's content. - */ - const char *what() const noexcept override; - - /** - * Stores a pointer to the currently-handled exception in this->cause. - */ - void store_cause(); - - /** - * Calls this->backtrace->trim_to_current_stack_frame(), - * if this->backtrace is not nullptr. - * - * Designed to be used in catch clauses, to strip away all those - * unneeded symbols from program init upwards. - */ - void trim_backtrace(); - - /** - * Re-throws the exception cause, if the exception has one. - * Otherwise, does nothing. - * - * Use this when handling the exception, to handle the cause. - */ - void rethrow_cause() const; - - /** - * Get the type name of of the exception. - * Use it to display the name of a child exception. - */ - virtual std::string type_name() const; - - /** - * Return the backtrace where the exception was thrown. - * nullptr if no backtrace was collected. - */ - Backtrace *get_backtrace() const; - - /** - * Directly return the message stored in the exception. - */ - const std::string &get_msg() const; - - /** - * Enable invocation of software breakpoint - * when this Error is constructed. - */ - static void enable_break(bool enable); + Error(const std::string &msg, + bool generate_backtrace=true, + bool store_cause=true); + + virtual ~Error() = default; + + /** + * String representation of this exception, as + * specialized by a child exception. + * + * @return String representation of this exception. + */ + virtual std::string str() const; + + /** + * Returns the message's content. + * + * @return Char array containing the error message. + */ + const char *what() const noexcept override; + + /** + * Stores a pointer to the currently-handled exception in this->cause. + */ + void store_cause(); + + /** + * Calls this->backtrace->trim_to_current_stack_frame(), + * if this->backtrace is not nullptr. + * + * Designed to be used in catch clauses, to strip away all those + * unneeded symbols from program init upwards. + */ + void trim_backtrace(); + + /** + * Re-throws the exception cause, if the exception has one. + * Otherwise, does nothing. + * + * Use this when handling the exception, to handle the cause. + */ + void rethrow_cause() const; + + /** + * Get the type name of of the exception. + * Use it to display the name of a child exception. + * + * @return String with the exception type. + */ + virtual std::string type_name() const; + + /** + * Return the backtrace where the exception was thrown. + * nullptr if no backtrace was collected. + * + * @return Backtrace to the exception's origin. + */ + Backtrace *get_backtrace() const; + + /** + * Directly return the message stored in the exception. + * + * @return String containing the error message. + */ + const std::string &get_msg() const; + + /** + * Enable invocation of software breakpoint + * when this Error is constructed. + * + * @param enable If true, enable a breakpoint, else disable it. + */ + static void enable_break(bool enable); protected: - /** - * The (optional) backtrace where the exception came from. - */ - std::shared_ptr backtrace; - - /** - * The error message text. - */ - std::string msg; - - /** - * Cached error message text for returning C string via what(). - */ - mutable std::string what_cache; - - /** - * Re-throw this with rethrow_cause(). - */ - std::exception_ptr cause; - - /** - * Issue software breakpoint when an error is constructed. - */ - static bool break_on_error; + /** + * The (optional) backtrace where the exception came from. + */ + std::shared_ptr backtrace; + + /** + * The error message text. + */ + std::string msg; + + /** + * Cached error message text for returning C string via what(). + */ + mutable std::string what_cache; + + /** + * Re-throw this with rethrow_cause(). + */ + std::exception_ptr cause; + + /** + * Issue software breakpoint when an error is constructed. + */ + static bool break_on_error; }; /** * Output stream concat for nyanerrors. + * + * @param os Output stream the error is appended to. + * @param e Error whose message is appended to the output stream. */ std::ostream &operator <<(std::ostream &os, const Error &e); /** * Output stream concat for backtrace symbols. + * + * @param os Output stream the backtrace symbol is appended to. + * @param bt_sym Backtrace symbol which is appended to the output stream. */ std::ostream &operator <<(std::ostream &os, const backtrace_symbol &bt_sym); /** * Output stream concat for backtraces. + * + * @param os Output stream the backtrace is appended to. + * @param bt Backtrace which is appended to the output stream. */ std::ostream &operator <<(std::ostream &os, const Backtrace &bt); @@ -189,7 +210,7 @@ std::ostream &operator <<(std::ostream &os, const Backtrace &bt); */ class InternalError : public Error { public: - InternalError(const std::string &msg); + InternalError(const std::string &msg); }; @@ -200,7 +221,7 @@ class InternalError : public Error { */ class FileReadError : public Error { public: - FileReadError(const std::string &msg); + FileReadError(const std::string &msg); }; } // namespace nyan diff --git a/nyan/file.cpp b/nyan/file.cpp index f7382ef..a422242 100644 --- a/nyan/file.cpp +++ b/nyan/file.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "file.h" @@ -10,58 +10,58 @@ namespace nyan { File::File(const std::string &virtual_name, std::string &&data) - : - name{virtual_name}, - data{std::move(data)} { + : + name{virtual_name}, + data{std::move(data)} { - this->extract_lines(); + this->extract_lines(); } File::File(const std::string &path) - : - File{path, util::read_file(path)} { + : + File{path, util::read_file(path)} { - // util::read_file throws a FileReadError if unsuccessful. + // util::read_file throws a FileReadError if unsuccessful. } void File::extract_lines() { - this->line_ends = { std::string::npos }; - - for (size_t i = 0; i < this->data.size(); i++) { - if (this->data[i] == '\n') { - this->line_ends.push_back(i); - } - } - this->line_ends.push_back(data.size()); + this->line_ends = { std::string::npos }; + + for (size_t i = 0; i < this->data.size(); i++) { + if (this->data[i] == '\n') { + this->line_ends.push_back(i); + } + } + this->line_ends.push_back(data.size()); } const std::string &File::get_name() const { - return this->name; + return this->name; } const std::string &File::get_content() const { - return this->data; + return this->data; } std::string File::get_line(size_t n) const { - size_t begin = this->line_ends[n - 1] + 1; - size_t len = this->line_ends[n] - begin; - return this->data.substr(begin, len); + size_t begin = this->line_ends[n - 1] + 1; + size_t len = this->line_ends[n] - begin; + return this->data.substr(begin, len); } const char *File::c_str() const { - return this->data.c_str(); + return this->data.c_str(); } size_t File::size() const { - return this->data.size(); + return this->data.size(); } diff --git a/nyan/file.h b/nyan/file.h index 521d72e..397360e 100644 --- a/nyan/file.h +++ b/nyan/file.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -14,65 +14,86 @@ namespace nyan { */ class File { public: - File(const std::string &path); - File(const std::string &virtual_name, std::string &&data); - - // moving allowed - File(File &&other) noexcept = default; - File& operator =(File &&other) noexcept = default; - - // no copies - File(const File &other) = delete; - File &operator =(const File &other) = delete; - - virtual ~File() = default; - - /** - * Return the file name. - */ - const std::string &get_name() const; - - /** - * Return the file content. - */ - const std::string &get_content() const; - - /** - * Return the given line number of the file. - * Starts at line 1. *buhuuuuu* *sob* *mrrrmmuu* *whimper* - */ - std::string get_line(size_t n) const; - - /** - * Return the number of lines in the file. - * It will always have at least one line. - * The empty file has one line of length 0. - */ - size_t get_line_count() const; - - /** - * Return a c string of the file content. - */ - const char *c_str() const; - - /** - * Return the size of the file content. - */ - size_t size() const; + File(const std::string &path); + File(const std::string &virtual_name, std::string &&data); + + // moving allowed + File(File &&other) noexcept = default; + File& operator =(File &&other) noexcept = default; + + // no copies + File(const File &other) = delete; + File &operator =(const File &other) = delete; + + virtual ~File() = default; + + /** + * Return the file name. + * + * @return String containing the filename. + */ + const std::string &get_name() const; + + /** + * Return the file content. + * + * @return String containing the file's content. + */ + const std::string &get_content() const; + + /** + * Return the given line number of the file. + * Starts at line 1. *buhuuuuu* *sob* *mrrrmmuu* *whimper* + * + * @param n Line number. + * + * @return String containing the content of line n. + */ + std::string get_line(size_t n) const; + + /** + * Return the number of lines in the file. + * It will always have at least one line. + * The empty file has one line of length 0. + * + * @return Number of lines in the file. + */ + size_t get_line_count() const; + + /** + * Return a c string of the file content. + * + * @return Char array containing the filename. + */ + const char *c_str() const; + + /** + * Return the size of the file content. + * + * @return Size of the file content string (number of characters). + */ + size_t size() const; protected: - /** - * Create line_ends entries from the file content. - */ - void extract_lines(); - - std::string name; - std::string data; - - /** - * Stores the offsets of line endings in the file content. - */ - std::vector line_ends; + /** + * Create line_ends entries from the file content. + */ + void extract_lines(); + + /** + * Name of the file. + */ + std::string name; + + /** + * Content of the file. + */ + std::string data; + + /** + * Stores the offsets of line endings in the file content. + */ + std::vector line_ends; }; } // namespace std diff --git a/nyan/id_token.cpp b/nyan/id_token.cpp index f826411..9764d47 100644 --- a/nyan/id_token.cpp +++ b/nyan/id_token.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "id_token.h" #include @@ -14,88 +14,88 @@ namespace nyan { IDToken::IDToken(const Token &first, TokenStream &tokens) { - this->ids.push_back(first); + this->ids.push_back(first); - auto token = tokens.next(); - while (token->type == token_type::DOT) { - token = tokens.next(); - if (unlikely(token->type != token_type::ID)) { - throw ASTError{"expected identifier after a dot, encountered", *token}; - } - this->ids.push_back(*token); - token = tokens.next(); - } + auto token = tokens.next(); + while (token->type == token_type::DOT) { + token = tokens.next(); + if (unlikely(token->type != token_type::ID)) { + throw ASTError{"expected identifier after a dot, encountered", *token}; + } + this->ids.push_back(*token); + token = tokens.next(); + } - tokens.reinsert_last(); + tokens.reinsert_last(); } std::string IDToken::str() const { - return util::strjoin( - ".", - this->ids, - [] (const auto &in) { - return in.get(); - } - ); + return util::strjoin( + ".", + this->ids, + [] (const auto &in) { + return in.get(); + } + ); } bool IDToken::exists() const { - return this->ids.size() > 0; + return this->ids.size() > 0; } token_type IDToken::get_type() const { - if (unlikely(not this->exists())) { - return token_type::INVALID; - } - else { - return this->ids.at(0).type; - } + if (unlikely(not this->exists())) { + return token_type::INVALID; + } + else { + return this->ids.at(0).type; + } } const Location &IDToken::get_start_location() const { - if (unlikely(not this->exists())) { - throw InternalError{ - "this IDToken doesn't exist, but you queried its location" - }; - } + if (unlikely(not this->exists())) { + throw InternalError{ + "this IDToken doesn't exist, but you queried its location" + }; + } - return this->ids.at(0).location; + return this->ids.at(0).location; } size_t IDToken::get_length() const { - if (not this->exists()) { - return 0; - } + if (not this->exists()) { + return 0; + } - size_t len = 0; - for (auto &tok : this->ids) { - // there's separating . in between each id - len += tok.location.get_length() + 1; - } + size_t len = 0; + for (auto &tok : this->ids) { + // there's separating . in between each id + len += tok.location.get_length() + 1; + } - // there's no trailing . in an id - len -= 1; + // there's no trailing . in an id + len -= 1; - return len; + return len; } const std::vector &IDToken::get_components() const { - return this->ids; + return this->ids; } const std::string &IDToken::get_first() const { - if (unlikely(not this->exists())) { - throw InternalError{"element of non-existing IDToken requested"}; - } + if (unlikely(not this->exists())) { + throw InternalError{"element of non-existing IDToken requested"}; + } - return this->ids[0].get(); + return this->ids[0].get(); } } // namespace nyan diff --git a/nyan/id_token.h b/nyan/id_token.h index 64c37a7..b763d58 100644 --- a/nyan/id_token.h +++ b/nyan/id_token.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -13,29 +13,69 @@ namespace nyan { /** - * Sometimes tokens, used as identifiers, can be concatenated by dots sometimes. + * String tokens, used as identifiers, can be concatenated by dots sometimes. * Used for e.g. namespace references, or ambiguous members of parent objects. * This multi-token groups those. */ class IDToken { public: - IDToken() = default; - IDToken(const Token &first, TokenStream &tokens); + IDToken() = default; + IDToken(const Token &first, TokenStream &tokens); - std::string str() const; + /** + * Get the string representation of this IDToken. + * + * @return String representation formatted in nyan language notation. + */ + std::string str() const; - bool exists() const; + /** + * Check if this IDToken is empty. + * + * @return true if the IDToken has more than one ID, else false. + */ + bool exists() const; - token_type get_type() const; + /** + * Get the type of the IDToken's content. + * + * @return Type of the first ID in this IDToken or token_type::INVALID if it doesn't exist. + */ + token_type get_type() const; - const Location &get_start_location() const; - size_t get_length() const; + /** + * Get the starting location of this IDToken in a file. + * + * @return Location of this IDToken. + */ + const Location &get_start_location() const; - const std::vector &get_components() const; - const std::string &get_first() const; + /** + * Get the character length of this IDToken. + * + * @return Length of this IDToken. + */ + size_t get_length() const; + + /** + * Get the list of IDs in this IDToken. + * + * @return A list of Tokens in this IDToken. + */ + const std::vector &get_components() const; + + /** + * Get the string representation of the first ID in this IDToken. + * + * @return String representation of the first ID formatted in nyan language notation. + */ + const std::string &get_first() const; protected: - std::vector ids; + /** + * List of IDs defining the IDToken. + */ + std::vector ids; }; diff --git a/nyan/inheritance_change.cpp b/nyan/inheritance_change.cpp index dedc374..c22808d 100644 --- a/nyan/inheritance_change.cpp +++ b/nyan/inheritance_change.cpp @@ -1,22 +1,22 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "inheritance_change.h" namespace nyan { InheritanceChange::InheritanceChange(inher_change_t type, fqon_t &&target) - : - type{type}, - target{std::move(target)} {} + : + type{type}, + target{std::move(target)} {} inher_change_t InheritanceChange::get_type() const { - return this->type; + return this->type; } const fqon_t &InheritanceChange::get_target() const { - return this->target; + return this->target; } } // namespace nyan diff --git a/nyan/inheritance_change.h b/nyan/inheritance_change.h index f0c28b3..ee07a9c 100644 --- a/nyan/inheritance_change.h +++ b/nyan/inheritance_change.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include "config.h" @@ -12,14 +12,34 @@ namespace nyan { */ class InheritanceChange { public: - InheritanceChange(inher_change_t type, fqon_t &&target); - - inher_change_t get_type() const; - const fqon_t &get_target() const; + InheritanceChange(inher_change_t type, fqon_t &&target); + + /** + * Get the type of inheritance change, i.e.whether the new parent is appended to + * the front or the back of the linearization. + * + * @return The inheritance change type. + */ + inher_change_t get_type() const; + + /** + * Get the object to which the inheritance change is applied. + * + * @return Identifier of the target object. + */ + const fqon_t &get_target() const; protected: - inher_change_t type; - fqon_t target; + + /** + * Inheritance change type. + */ + inher_change_t type; + + /** + * Identifier of the target object. + */ + fqon_t target; }; diff --git a/nyan/lang_error.cpp b/nyan/lang_error.cpp index 27381f7..96dce10 100644 --- a/nyan/lang_error.cpp +++ b/nyan/lang_error.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "lang_error.h" @@ -15,107 +15,107 @@ using namespace std::string_literals; LangError::LangError(const Location &location, const std::string &msg, std::vector> &&reasons) - : - Error{msg}, - location{location}, - reasons{std::move(reasons)} {} + : + Error{msg}, + location{location}, + reasons{std::move(reasons)} {} std::string LangError::str() const { - std::ostringstream builder; + std::ostringstream builder; - builder << "\x1b[1m"; + builder << "\x1b[1m"; - this->location.str(builder); + this->location.str(builder); - builder << "\x1b[31;1merror:\x1b[39;49m " << this->msg - << "\x1b[0m"; + builder << "\x1b[31;1merror:\x1b[39;49m " << this->msg + << "\x1b[0m"; - return builder.str(); + return builder.str(); } static void visualize_location(std::ostringstream &builder, const Location &location) { - size_t offset = location.get_line_offset(); - size_t length = location.get_length(); - - if (length > 0) { - length -= 1; - } - else { - length = 0; - } - - builder << location.get_line_content() << std::endl - << std::string(offset, ' ') << "\x1b[36;1m^" - << std::string(length, '~') << "\x1b[m"; + size_t offset = location.get_line_offset(); + size_t length = location.get_length(); + + if (length > 0) { + length -= 1; + } + else { + length = 0; + } + + builder << location.get_line_content() << std::endl + << std::string(offset, ' ') << "\x1b[36;1m^" + << std::string(length, '~') << "\x1b[m"; } std::string LangError::show_problem_origin() const { - std::ostringstream builder; + std::ostringstream builder; - if (this->location.is_builtin()) { - builder << this->location.get_msg(); - } - else { - visualize_location(builder, this->location); - } + if (this->location.is_builtin()) { + builder << this->location.get_msg(); + } + else { + visualize_location(builder, this->location); + } - for (const auto &reason : this->reasons) { - const Location &loc = reason.first; - const std::string &msg = reason.second; + for (const auto &reason : this->reasons) { + const Location &loc = reason.first; + const std::string &msg = reason.second; - builder << std::endl << "\x1b[1m"; + builder << std::endl << "\x1b[1m"; - loc.str(builder); + loc.str(builder); - builder << "\x1b[30;1mnote:\x1b[39;49m " - << msg - << "\x1b[0m" << std::endl; + builder << "\x1b[30;1mnote:\x1b[39;49m " + << msg + << "\x1b[0m" << std::endl; - visualize_location(builder, loc); - builder << std::endl; - } + visualize_location(builder, loc); + builder << std::endl; + } - return builder.str(); + return builder.str(); } TypeError::TypeError(const Location &location, const std::string &msg) - : - LangError{location, msg} {} + : + LangError{location, msg} {} NameError::NameError(const Location &location, const std::string &msg, const std::string &name) - : - LangError{location, msg}, - name{name} {} + : + LangError{location, msg}, + name{name} {} std::string NameError::str() const { - std::ostringstream builder; - builder << "\x1b[1m"; + std::ostringstream builder; + builder << "\x1b[1m"; - this->location.str(builder); + this->location.str(builder); - builder << "\x1b[31;1mname error:\x1b[39;49m "; - builder << this->msg; + builder << "\x1b[31;1mname error:\x1b[39;49m "; + builder << this->msg; - if (not this->name.empty()) { - builder << ": '" << this->name << "'"; - } + if (not this->name.empty()) { + builder << ": '" << this->name << "'"; + } - builder << "\x1b[0m"; + builder << "\x1b[0m"; - return builder.str(); + return builder.str(); } TokenizeError::TokenizeError(const Location &location, const std::string &msg): - LangError{location, msg} {} + LangError{location, msg} {} } // namespace nyan diff --git a/nyan/lang_error.h b/nyan/lang_error.h index e7f2551..dc7de7b 100644 --- a/nyan/lang_error.h +++ b/nyan/lang_error.h @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -15,16 +15,35 @@ namespace nyan { */ class LangError : public Error { public: - LangError(const Location &location, const std::string &msg, - std::vector> &&reasons={}); + LangError(const Location &location, const std::string &msg, + std::vector> &&reasons={}); + + /** + * String representation of this error. + * + * @return String representation of this error. + */ + std::string str() const override; + + /** + * Get a string that visualizes the error in the output using + * the location and reasons for the error. + * + * @return String containing a pretty error message. + */ + virtual std::string show_problem_origin() const; - std::string str() const override; +protected: - virtual std::string show_problem_origin() const; + /** + * Location of the error in a file. + */ + Location location; -protected: - Location location; - std::vector> reasons; + /** + * Map of reasons for the error by location in the file. + */ + std::vector> reasons; }; @@ -33,24 +52,33 @@ class LangError : public Error { */ class TypeError : public LangError { public: - TypeError(const Location &location, const std::string &msg); + TypeError(const Location &location, const std::string &msg); - virtual ~TypeError() = default; + virtual ~TypeError() = default; }; /** - * Exception for name access problems. + * Exception for name access problems and naming conflicts. */ class NameError : public LangError { public: - NameError(const Location &location, - const std::string &msg, const std::string &name=""); + NameError(const Location &location, + const std::string &msg, const std::string &name=""); - std::string str() const override; + /** + * String representation of this error. + * + * @return String representation of this error. + */ + std::string str() const override; protected: - std::string name; + + /** + * Name that the entitycausing this error conflicts with. + */ + std::string name; }; @@ -59,8 +87,8 @@ class NameError : public LangError { */ class TokenizeError : public LangError { public: - TokenizeError(const Location &location, - const std::string &msg); + TokenizeError(const Location &location, + const std::string &msg); }; } // namespace nyan diff --git a/nyan/lexer/bracket.cpp b/nyan/lexer/bracket.cpp index 8336e13..9c41797 100644 --- a/nyan/lexer/bracket.cpp +++ b/nyan/lexer/bracket.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "bracket.h" @@ -7,103 +7,103 @@ namespace nyan::lexer { Bracket::Bracket(token_type ttype, int indent) - : - indentation{indent}, - type{this->to_type(ttype)}, - hanging{true} {} + : + indentation{indent}, + type{this->to_type(ttype)}, + hanging{true} {} void Bracket::doesnt_hang(int new_indent) { - this->hanging = false; - this->indentation = new_indent; + this->hanging = false; + this->indentation = new_indent; } bool Bracket::is_hanging() const { - return this->hanging; + return this->hanging; } bool Bracket::matches(token_type type) const { - return type == this->expected_match(); + return type == this->expected_match(); } int Bracket::get_content_indent() const { - if (this->is_hanging()) { - // hanging brackets store their expected indent level - return this->indentation; - } - else { - // wrapped brackets store their base indent level - return this->indentation + SPACES_PER_INDENT; - } + if (this->is_hanging()) { + // hanging brackets store their expected indent level + return this->indentation; + } + else { + // wrapped brackets store their base indent level + return this->indentation + SPACES_PER_INDENT; + } } bool Bracket::closing_indent_ok(int indent) const { - if (this->is_hanging()) { - // hanging indent requires the closing bracket to be - // closed after the opening bracket column - return this->indentation <= indent; - } - else { - return this->indentation == indent; - } + if (this->is_hanging()) { + // hanging indent requires the closing bracket to be + // closed after the opening bracket column + return this->indentation <= indent; + } + else { + return this->indentation == indent; + } } std::string Bracket::get_closing_indent() const { - if (this->is_hanging()) { - std::ostringstream builder; - builder << "at least " - << this->indentation; - return builder.str(); - } - else { - return std::to_string(this->indentation); - } + if (this->is_hanging()) { + std::ostringstream builder; + builder << "at least " + << this->indentation; + return builder.str(); + } + else { + return std::to_string(this->indentation); + } } const char *Bracket::matching_type_str() const { - return token_type_str(this->expected_match()); + return token_type_str(this->expected_match()); } bracket_type Bracket::to_type(token_type token) { - switch (token) { - case token_type::LPAREN: - case token_type::RPAREN: - return bracket_type::PAREN; - case token_type::LANGLE: - case token_type::RANGLE: - return bracket_type::ANGLE; - case token_type::LBRACKET: - case token_type::RBRACKET: - return bracket_type::BRACKET; - case token_type::LBRACE: - case token_type::RBRACE: - return bracket_type::BRACE; - default: - throw InternalError{"tried to convert non-bracket token to bracket"}; - } + switch (token) { + case token_type::LPAREN: + case token_type::RPAREN: + return bracket_type::PAREN; + case token_type::LANGLE: + case token_type::RANGLE: + return bracket_type::ANGLE; + case token_type::LBRACKET: + case token_type::RBRACKET: + return bracket_type::BRACKET; + case token_type::LBRACE: + case token_type::RBRACE: + return bracket_type::BRACE; + default: + throw InternalError{"tried to convert non-bracket token to bracket"}; + } } token_type Bracket::expected_match() const { - switch (this->type) { - case bracket_type::PAREN: - return token_type::RPAREN; - case bracket_type::ANGLE: - return token_type::RANGLE; - case bracket_type::BRACKET: - return token_type::RBRACKET; - case bracket_type::BRACE: - return token_type::RBRACE; - default: - throw InternalError{"unknown bracket type"}; - } + switch (this->type) { + case bracket_type::PAREN: + return token_type::RPAREN; + case bracket_type::ANGLE: + return token_type::RANGLE; + case bracket_type::BRACKET: + return token_type::RBRACKET; + case bracket_type::BRACE: + return token_type::RBRACE; + default: + throw InternalError{"unknown bracket type"}; + } } diff --git a/nyan/lexer/bracket.h b/nyan/lexer/bracket.h index ed47d16..3a16692 100644 --- a/nyan/lexer/bracket.h +++ b/nyan/lexer/bracket.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once diff --git a/nyan/lexer/flex.lpp b/nyan/lexer/flex.lpp index a8c319c..4a65d1b 100644 --- a/nyan/lexer/flex.lpp +++ b/nyan/lexer/flex.lpp @@ -38,6 +38,7 @@ id [A-Za-z_][A-Za-z0-9_]* operator [-+*/|%&]=?|= int (-|0[xX])?{digit}+ float -?({digit}+\.{digit}*|{digit}*\.{digit}+) +inf -?inf %% @@ -65,6 +66,7 @@ float -?({digit}+\.{digit}*|{digit}*\.{digit}+) "{" { impl->token(nyan::token_type::LBRACE); } "}" { impl->token(nyan::token_type::RBRACE); } "@" { impl->token(nyan::token_type::AT); } +"!" { impl->token(nyan::token_type::BANG); } "pass" { impl->token(nyan::token_type::PASS); } "..." { impl->token(nyan::token_type::ELLIPSIS); } @@ -72,6 +74,7 @@ float -?({digit}+\.{digit}*|{digit}*\.{digit}+) "from" { impl->token(nyan::token_type::FROM); } "as" { impl->token(nyan::token_type::AS); } {operator} { impl->token(nyan::token_type::OPERATOR); } +{inf} { impl->token(nyan::token_type::INF); } {int} { impl->token(nyan::token_type::INT); } {float} { impl->token(nyan::token_type::FLOAT); } {id} { impl->token(nyan::token_type::ID); } diff --git a/nyan/lexer/impl.cpp b/nyan/lexer/impl.cpp index 9db7499..a411862 100644 --- a/nyan/lexer/impl.cpp +++ b/nyan/lexer/impl.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2018 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "impl.h" @@ -11,17 +11,17 @@ namespace nyan::lexer { Impl::Impl(const std::shared_ptr &file) - : - file{file}, - input{file->get_content()} { + : + file{file}, + input{file->get_content()} { - yylex_init_extra(this, &this->scanner); + yylex_init_extra(this, &this->scanner); } Impl::~Impl() { - if (this->scanner) { - yylex_destroy(this->scanner); - } + if (this->scanner) { + yylex_destroy(this->scanner); + } } /* @@ -29,18 +29,18 @@ Impl::~Impl() { * Return tokens from the queue until it's empty. */ Token Impl::generate_token() { - if (this->tokens.empty()) { - yylex(this->scanner); - } - - if (not this->tokens.empty()) { - auto ret = this->tokens.front(); - this->tokens.pop(); - return ret; - } - - // if generate_token did not generate a token: - throw this->error("internal error."); + if (this->tokens.empty()) { + yylex(this->scanner); + } + + if (not this->tokens.empty()) { + auto ret = this->tokens.front(); + this->tokens.pop(); + return ret; + } + + // if generate_token did not generate a token: + throw this->error("internal error."); } @@ -48,75 +48,75 @@ Token Impl::generate_token() { * Fetch the current lexer state and throw an error. */ TokenizeError Impl::error(const std::string &msg) { - return TokenizeError{ - Location{ - this->file, - yyget_lineno(this->scanner), - this->linepos - static_cast(yyget_leng(this->scanner)), - static_cast(yyget_leng(this->scanner)) - }, - msg - }; + return TokenizeError{ + Location{ + this->file, + yyget_lineno(this->scanner), + this->linepos - static_cast(yyget_leng(this->scanner)), + static_cast(yyget_leng(this->scanner)) + }, + msg + }; } void Impl::advance_linepos() { - this->linepos += yyget_leng(this->scanner); + this->linepos += yyget_leng(this->scanner); } int Impl::read_input(char *buffer, int max_size) { - if (unlikely(max_size <= 0)) { - return 0; - } + if (unlikely(max_size <= 0)) { + return 0; + } - this->input.read(buffer, static_cast(max_size)); - return static_cast(this->input.gcount()); + this->input.read(buffer, static_cast(max_size)); + return static_cast(this->input.gcount()); } void Impl::endline() { - // ENDLINE is not an acceptable first token. - // Optimize for consecutive ENDLINE tokens: keep only one. - if (not tokens.empty() and tokens.back().type != token_type::ENDLINE) { - this->token(token_type::ENDLINE); - } - // Reset the line position to the beginning. - this->linepos = linepos_start; + // ENDLINE is not an acceptable first token. + // Optimize for consecutive ENDLINE tokens: keep only one. + if (not tokens.empty() and tokens.back().type != token_type::ENDLINE) { + this->token(token_type::ENDLINE); + } + // Reset the line position to the beginning. + this->linepos = linepos_start; } /* * Fetch the current lexer state variables and create a token. */ void Impl::token(token_type type) { - int length = yyget_leng(this->scanner); - int token_start = this->linepos - length; - int lineno = yyget_lineno(this->scanner); - if (type == token_type::ENDLINE) { - /* don't assign the `\n` for the next line */ - --lineno; - } - - // to register open and close parenthesis - // for correct line-wrap-indentation. - this->track_brackets(type, token_start); - - if (token_needs_payload(type)) { - this->tokens.push(Token{ - this->file, - lineno, - token_start, - length, - type, - yyget_text(this->scanner) - }); - } - else { - this->tokens.push(Token{ - this->file, - lineno, - token_start, - length, - type - }); - } + int length = yyget_leng(this->scanner); + int token_start = this->linepos - length; + int lineno = yyget_lineno(this->scanner); + if (type == token_type::ENDLINE) { + /* don't assign the `\n` for the next line */ + --lineno; + } + + // to register open and close parenthesis + // for correct line-wrap-indentation. + this->track_brackets(type, token_start); + + if (token_needs_payload(type)) { + this->tokens.push(Token{ + this->file, + lineno, + token_start, + length, + type, + yyget_text(this->scanner) + }); + } + else { + this->tokens.push(Token{ + this->file, + lineno, + token_start, + length, + type + }); + } } /* @@ -125,81 +125,81 @@ void Impl::token(token_type type) { */ void Impl::track_brackets(token_type type, int token_start) { - // opening brackets - if (type == token_type::LPAREN or - type == token_type::LANGLE or - type == token_type::LBRACKET or - type == token_type::LBRACE) { - - // Track bracket type and indentation. - // The position after the ( is exactly the expected indent - // for hanging brackets. - this->brackets.emplace( - type, - token_start + 1 - ); - - this->possibly_hanging = true; - return; - } - // closing brackets - else if (type == token_type::RPAREN or - type == token_type::RANGLE or - type == token_type::RBRACKET or - type == token_type::RBRACE) { - - if (this->brackets.empty()) { - throw this->error("unexpected closing bracket, " - "as no opening one is known"); - } - - Bracket &matching_open_bracket = this->brackets.top(); - - // test if bracket actually matches - if (not matching_open_bracket.matches(type)) { - std::ostringstream builder; - builder << "non-matching bracket: expected '" - << matching_open_bracket.matching_type_str() - << "' but got '" << token_type_str(type) << "'"; - throw this->error(builder.str()); - } - - if (not matching_open_bracket.closing_indent_ok(token_start)) { - std::ostringstream builder; - builder << "wrong indentation of bracket: expected " - << matching_open_bracket.get_closing_indent() - << " indentation spaces (it is currently at " - << token_start << " spaces)"; - throw this->error(builder.str()); - } - - this->bracketcloseindent_expected = false; - this->brackets.pop(); - } - // newline directly after opening bracket - // means regular indentation has to follow - // and the bracket pair doesn't hang. - else if (not this->brackets.empty() and - this->possibly_hanging and - type == token_type::ENDLINE) { - - // the bracket is followed by a newline directly, - // thus is not hanging. - this->brackets.top().doesnt_hang( - this->previous_indent - ); - } - else if (not this->brackets.empty() and - this->bracketcloseindent_expected) { - std::ostringstream builder; - builder << ("expected closing bracket or content " - "at indentation with ") - << this->brackets.top().get_content_indent() - << " spaces (you start at " << token_start << " spaces)"; - throw this->error(builder.str()); - } - - this->possibly_hanging = false; + // opening brackets + if (type == token_type::LPAREN or + type == token_type::LANGLE or + type == token_type::LBRACKET or + type == token_type::LBRACE) { + + // Track bracket type and indentation. + // The position after the ( is exactly the expected indent + // for hanging brackets. + this->brackets.emplace( + type, + token_start + 1 + ); + + this->possibly_hanging = true; + return; + } + // closing brackets + else if (type == token_type::RPAREN or + type == token_type::RANGLE or + type == token_type::RBRACKET or + type == token_type::RBRACE) { + + if (this->brackets.empty()) { + throw this->error("unexpected closing bracket, " + "as no opening one is known"); + } + + Bracket &matching_open_bracket = this->brackets.top(); + + // test if bracket actually matches + if (not matching_open_bracket.matches(type)) { + std::ostringstream builder; + builder << "non-matching bracket: expected '" + << matching_open_bracket.matching_type_str() + << "' but got '" << token_type_str(type) << "'"; + throw this->error(builder.str()); + } + + if (not matching_open_bracket.closing_indent_ok(token_start)) { + std::ostringstream builder; + builder << "wrong indentation of bracket: expected " + << matching_open_bracket.get_closing_indent() + << " indentation spaces (it is currently at " + << token_start << " spaces)"; + throw this->error(builder.str()); + } + + this->bracketcloseindent_expected = false; + this->brackets.pop(); + } + // newline directly after opening bracket + // means regular indentation has to follow + // and the bracket pair doesn't hang. + else if (not this->brackets.empty() and + this->possibly_hanging and + type == token_type::ENDLINE) { + + // the bracket is followed by a newline directly, + // thus is not hanging. + this->brackets.top().doesnt_hang( + this->previous_indent + ); + } + else if (not this->brackets.empty() and + this->bracketcloseindent_expected) { + std::ostringstream builder; + builder << ("expected closing bracket or content " + "at indentation with ") + << this->brackets.top().get_content_indent() + << " spaces (you start at " << token_start << " spaces)"; + throw this->error(builder.str()); + } + + this->possibly_hanging = false; } /* @@ -207,58 +207,58 @@ void Impl::track_brackets(token_type type, int token_start) { */ void Impl::handle_indent(int depth) { - this->linepos -= yyget_leng(this->scanner) - depth; - - if (not this->brackets.empty()) { - // we're in a pair of brackets, - // there the indentation is way funnier. - - // check if the content indentation is correct. - int expected = this->brackets.top().get_content_indent(); - if (depth != expected) { - // if the expected depth is not correct, - // then the only thing that is allowed is - // the closing bracket. - // the check will be done for the next token in - // `track_brackets`. - this->bracketcloseindent_expected = true; - } - - // don't need to track the indent stack, - // this is done in the bracket tracking now. - return; - } - - // regular indent is enforced when not in a bracket pair - if ((depth % SPACES_PER_INDENT) > 0) { - std::ostringstream builder; - builder << "indentation requires exactly " - << SPACES_PER_INDENT - << " spaces per level"; - throw this->error(builder.str()); - } - - if (depth == this->previous_indent) { - // same indent level, ignore - return; - } - else if (depth < this->previous_indent) { - // current line is further left than the previous one - int delta = this->previous_indent - depth; - while (delta > 0) { - delta -= SPACES_PER_INDENT; - this->token(token_type::DEDENT); - } - } - else { - // current line has more depth than the previous one - int delta = depth - this->previous_indent; - while (delta > 0) { - delta -= SPACES_PER_INDENT; - this->token(token_type::INDENT); - } - } - this->previous_indent = depth; + this->linepos -= yyget_leng(this->scanner) - depth; + + if (not this->brackets.empty()) { + // we're in a pair of brackets, + // there the indentation is way funnier. + + // check if the content indentation is correct. + int expected = this->brackets.top().get_content_indent(); + if (depth != expected) { + // if the expected depth is not correct, + // then the only thing that is allowed is + // the closing bracket. + // the check will be done for the next token in + // `track_brackets`. + this->bracketcloseindent_expected = true; + } + + // don't need to track the indent stack, + // this is done in the bracket tracking now. + return; + } + + // regular indent is enforced when not in a bracket pair + if ((depth % SPACES_PER_INDENT) > 0) { + std::ostringstream builder; + builder << "indentation requires exactly " + << SPACES_PER_INDENT + << " spaces per level"; + throw this->error(builder.str()); + } + + if (depth == this->previous_indent) { + // same indent level, ignore + return; + } + else if (depth < this->previous_indent) { + // current line is further left than the previous one + int delta = this->previous_indent - depth; + while (delta > 0) { + delta -= SPACES_PER_INDENT; + this->token(token_type::DEDENT); + } + } + else { + // current line has more depth than the previous one + int delta = depth - this->previous_indent; + while (delta > 0) { + delta -= SPACES_PER_INDENT; + this->token(token_type::INDENT); + } + } + this->previous_indent = depth; } } // namespace nyan::lexer diff --git a/nyan/lexer/impl.h b/nyan/lexer/impl.h index a9c3404..100e888 100644 --- a/nyan/lexer/impl.h +++ b/nyan/lexer/impl.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -14,95 +14,95 @@ namespace nyan::lexer { class Impl { public: - explicit Impl(const std::shared_ptr &file); + explicit Impl(const std::shared_ptr &file); - ~Impl(); + ~Impl(); - /** No copies. No moves. */ - Impl(const Impl &other) = delete; - Impl(Impl&& other) = delete; - const Impl &operator =(const Impl &other) = delete; - Impl &&operator =(Impl &&other) = delete; + /** No copies. No moves. */ + Impl(const Impl &other) = delete; + Impl(Impl&& other) = delete; + const Impl &operator =(const Impl &other) = delete; + Impl &&operator =(Impl &&other) = delete; - /** Produce a token by reading the input. */ - Token generate_token(); + /** Produce a token by reading the input. */ + Token generate_token(); /** @name FlexInterfaceMethods * Methods used by the flex generated lexer. */ ///@{ - /** Advance the line position by match length. */ - void advance_linepos(); + /** Advance the line position by match length. */ + void advance_linepos(); - /** - * Try to read `max_size` bytes to `buffer`. - * Return number of bytes read. - */ - int read_input(char *buffer, int max_size); + /** + * Try to read `max_size` bytes to `buffer`. + * Return number of bytes read. + */ + int read_input(char *buffer, int max_size); - /** - * Create a token with correct text position and value. - * Add the token to the queue. - */ - void token(token_type type); + /** + * Create a token with correct text position and value. + * Add the token to the queue. + */ + void token(token_type type); - /** Tokenize error was encountered. */ - TokenizeError error(const std::string &msg); + /** Tokenize error was encountered. */ + TokenizeError error(const std::string &msg); - /** Emit line ending token for current position. */ - void endline(); + /** Emit line ending token for current position. */ + void endline(); - /** Generate indentation tokens based on given depth. */ - void handle_indent(int depth); + /** Generate indentation tokens based on given depth. */ + void handle_indent(int depth); ///@} protected: - /** - * Indentation enforcement in parens requires to track - * the open and closing parens `(<[{}]>)`. - */ - void track_brackets(token_type type, int token_start); + /** + * Indentation enforcement in parens requires to track + * the open and closing parens `(<[{}]>)`. + */ + void track_brackets(token_type type, int token_start); - /** Input file used for tokenization. */ - std::shared_ptr file; + /** Input file used for tokenization. */ + std::shared_ptr file; - /** String stream which is fed into the lexer. */ - std::istringstream input; + /** String stream which is fed into the lexer. */ + std::istringstream input; - /** Available tokens. */ - std::queue tokens; + /** Available tokens. */ + std::queue tokens; - /** The indentation level of the previous line. */ - int previous_indent = 0; + /** The indentation level of the previous line. */ + int previous_indent = 0; - /** The bracket stack remembers current open positions of `(<[{}]>)`. */ - std::stack brackets; + /** The bracket stack remembers current open positions of `(<[{}]>)`. */ + std::stack brackets; - /** - * Set to true when a opening bracket was encountered. - * If it is true and the next token is a newline, the bracket is hanging. - * It will be set to false when the token after a opening - * bracket was processed. - */ - bool possibly_hanging = false; + /** + * Set to true when a opening bracket was encountered. + * If it is true and the next token is a newline, the bracket is hanging. + * It will be set to false when the token after a opening + * bracket was processed. + */ + bool possibly_hanging = false; - /** - * True when the indentation in brackets doesn't match. - * This is only the case when for a closing bracket. - */ - bool bracketcloseindent_expected = false; + /** + * True when the indentation in brackets doesn't match. + * This is only the case when for a closing bracket. + */ + bool bracketcloseindent_expected = false; - /** The default line positon at the very beginning of one line. */ - static constexpr int linepos_start = 0; + /** The default line positon at the very beginning of one line. */ + static constexpr int linepos_start = 0; - /** Current position in a line. */ - int linepos = linepos_start; + /** Current position in a line. */ + int linepos = linepos_start; - /** yyscan_t object: pointer to flex generated lexer */ - void *scanner{nullptr}; + /** yyscan_t object: pointer to flex generated lexer */ + void *scanner{nullptr}; }; } // namespace nyan::lexer diff --git a/nyan/lexer/lexer.cpp b/nyan/lexer/lexer.cpp index 88defc4..069f169 100644 --- a/nyan/lexer/lexer.cpp +++ b/nyan/lexer/lexer.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "lexer.h" @@ -8,8 +8,8 @@ namespace nyan { Lexer::Lexer(const std::shared_ptr &file) - : - impl{std::make_unique(file)} { + : + impl{std::make_unique(file)} { } @@ -17,13 +17,13 @@ Lexer::~Lexer() = default; // Return the token generated from the implementation. Token Lexer::get_next_token() { - return this->impl->generate_token(); + return this->impl->generate_token(); } LexerError::LexerError(const Location &location, const std::string &msg) - : - LangError{location, msg} {} + : + LangError{location, msg} {} } // namespace nyan diff --git a/nyan/lexer/lexer.h b/nyan/lexer/lexer.h index f754edb..07f30ed 100644 --- a/nyan/lexer/lexer.h +++ b/nyan/lexer/lexer.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -19,26 +19,26 @@ class Impl; class Lexer { public: - /** - * Create a lexer for the given file. - */ - Lexer(const std::shared_ptr &file); - virtual ~Lexer(); - - // no moves and copies - Lexer(Lexer &&other) = delete; - Lexer(const Lexer &other) = delete; - Lexer &operator =(Lexer &&other) = delete; - Lexer &operator =(const Lexer &other) = delete; - - /** - * Return the next available token. - */ - Token get_next_token(); + /** + * Create a lexer for the given file. + */ + Lexer(const std::shared_ptr &file); + virtual ~Lexer(); + + // no moves and copies + Lexer(Lexer &&other) = delete; + Lexer(const Lexer &other) = delete; + Lexer &operator =(Lexer &&other) = delete; + Lexer &operator =(const Lexer &other) = delete; + + /** + * Return the next available token. + */ + Token get_next_token(); protected: - /** Lexer internal implementation */ - std::unique_ptr impl; + /** Lexer internal implementation */ + std::unique_ptr impl; }; @@ -47,7 +47,7 @@ class Lexer { */ class LexerError : public LangError { public: - LexerError(const Location &location, const std::string &msg); + LexerError(const Location &location, const std::string &msg); }; diff --git a/nyan/location.cpp b/nyan/location.cpp index 8183f10..2d3cece 100644 --- a/nyan/location.cpp +++ b/nyan/location.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "location.h" @@ -11,73 +11,73 @@ namespace nyan { Location::Location(const Token &token) - : - Location{token.location} {} + : + Location{token.location} {} Location::Location(const IDToken &token) - : - Location{token.get_start_location()} { + : + Location{token.get_start_location()} { - // use the full id length as location length - this->length = token.get_length(); + // use the full id length as location length + this->length = token.get_length(); } Location::Location(const std::shared_ptr &file, int line, int line_offset, int length) - : - file{file}, - line{line}, - line_offset{line_offset}, - length{length} {} + : + file{file}, + line{line}, + line_offset{line_offset}, + length{length} {} Location::Location(const std::string &custom) - : - _is_builtin{true}, - msg{custom} {} + : + _is_builtin{true}, + msg{custom} {} bool Location::is_builtin() const { - return this->_is_builtin; + return this->_is_builtin; } const std::string &Location::get_msg() const { - return this->msg; + return this->msg; } int Location::get_line() const { - return this->line; + return this->line; } int Location::get_line_offset() const { - return this->line_offset; + return this->line_offset; } int Location::get_length() const { - return this->length; + return this->length; } std::string Location::get_line_content() const { - if (this->_is_builtin) { - return this->msg; - } - return this->file->get_line(this->get_line()); + if (this->_is_builtin) { + return this->msg; + } + return this->file->get_line(this->get_line()); } void Location::str(std::ostringstream &builder) const { - if (this->_is_builtin) { - builder << "[native call]: "; - return; - } - - builder << this->file->get_name() << ":" - << this->line << ":" - << this->line_offset << ": "; + if (this->_is_builtin) { + builder << "[native call]: "; + return; + } + + builder << this->file->get_name() << ":" + << this->line << ":" + << this->line_offset << ": "; } } // namespace nyan diff --git a/nyan/location.h b/nyan/location.h index 530761c..b6a2d66 100644 --- a/nyan/location.h +++ b/nyan/location.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -18,39 +18,95 @@ class IDToken; */ class Location { public: - Location() = default; - Location(const Token &token); - Location(const IDToken &token); - Location(const std::shared_ptr &file, int line, - int line_offset, int length=0); - explicit Location(const std::string &custom); + Location() = default; + Location(const Token &token); + Location(const IDToken &token); + Location(const std::shared_ptr &file, int line, + int line_offset, int length=0); + explicit Location(const std::string &custom); - ~Location() = default; + ~Location() = default; - bool is_builtin() const; - const std::string &get_msg() const; - int get_line() const; - int get_line_offset() const; - int get_length() const; + /** + * Checks if the location is a built-in nyan location. + * + * @return true if the location is built-in, else false. + */ + bool is_builtin() const; - std::string get_line_content() const; + /** + * Get the message for the location. + * + * @return String containing the message. + */ + const std::string &get_msg() const; - void str(std::ostringstream &builder) const; + /** + * Get the line index of the location in the file. + * + * @return Line index in the file (starting from 1). + */ + int get_line() const; + + /** + * Get the line offset of the location in its line. + * + * @return Line offset in the line. + */ + int get_line_offset() const; + + /** + * Get the content length of the location in the file. + * + * @return Number of characters in the location content. + */ + int get_length() const; + + /** + * Get the line of the location in the file. + * + * @return String containing the contents of the line. + */ + std::string get_line_content() const; + + /** + * Append the string representation of the location to a given output stream. + * + * @param builder Output stream the string representation is appended to. + */ + void str(std::ostringstream &builder) const; protected: - /** - * if true, this location does point to a file, - * instead it describes some built-in location of nyan itself. - */ - bool _is_builtin = false; + /** + * if true, this location does not point to a file, + * instead it describes some built-in location of nyan itself. + */ + bool _is_builtin = false; + + /** + * Shared pointer to the file of the location. + */ + std::shared_ptr file; + + /** + * Line index in the file (starting from 1). + */ + int line; - std::shared_ptr file; + /** + * Line offset in its line. + */ + int line_offset; - int line; - int line_offset; - int length; + /** + * Length of the content the location associates with. + */ + int length; - std::string msg; + /** + * Message for built-in locations. + */ + std::string msg; }; } // namespace nyan diff --git a/nyan/member.cpp b/nyan/member.cpp index 9cd069b..65d4b37 100644 --- a/nyan/member.cpp +++ b/nyan/member.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "member.h" @@ -6,6 +6,7 @@ #include "compiler.h" #include "util.h" +#include "value/none.h" namespace nyan { @@ -13,81 +14,88 @@ namespace nyan { Member::Member(override_depth_t depth, nyan_op operation, + Type declared_type, ValueHolder &&value) - : - override_depth{depth}, - operation{operation}, - value{std::move(value)} {} + : + override_depth{depth}, + operation{operation}, + declared_type{declared_type}, + value{std::move(value)} {} Member::Member(const Member &other) - : - override_depth{other.override_depth}, - operation{other.operation}, - value{other.value->copy()} {} + : + override_depth{other.override_depth}, + operation{other.operation}, + declared_type{other.declared_type}, + value{other.value->copy()} {} Member::Member(Member &&other) noexcept - : - override_depth{std::move(other.override_depth)}, - operation{std::move(other.operation)}, - value{std::move(other.value)} {} + : + override_depth{std::move(other.override_depth)}, + operation{std::move(other.operation)}, + declared_type{std::move(other.declared_type)}, + value{std::move(other.value)} {} Member &Member::operator =(const Member &other) { - *this = Member{other}; - return *this; + *this = Member{other}; + return *this; } Member &Member::operator =(Member &&other) noexcept { - this->override_depth = std::move(other.override_depth); - this->operation = std::move(other.operation); - this->value = std::move(other.value); - return *this; + this->override_depth = std::move(other.override_depth); + this->operation = std::move(other.operation); + this->value = std::move(other.value); + return *this; } nyan_op Member::get_operation() const { - return this->operation; + return this->operation; } const Value &Member::get_value() const { - if (unlikely(not this->value.exists())) { - throw InternalError{"fetched nonexisting value of member"}; - } + if (unlikely(not this->value.exists())) { + throw InternalError{"fetched nonexisting value of member"}; + } - return *this->value; + return *this->value; } void Member::apply(const Member &change) { - // if the change requests an operator overwrite, apply it. - if (change.override_depth > 0) { - this->override_depth = change.override_depth - 1; - this->operation = change.get_operation(); - this->value = change.get_value().copy(); - } - // else, keep operator as-is and modify the value. - else { - this->value->apply(change); - } + // if the change requests an operator overwrite, apply it. + if (change.override_depth > 0) { + this->override_depth = change.override_depth - 1; + this->operation = change.get_operation(); + this->value = change.get_value().copy(); + } + else if (typeid(change.get_value()) == typeid(None&)) { + this->value = {std::make_shared(NYAN_NONE)}; + } + // else, keep operator as-is and modify the value. + else { + this->value->apply(change); + } } std::string Member::str() const { - std::ostringstream builder; + std::ostringstream builder; - if (this->operation != nyan_op::INVALID) { - builder << op_to_string(this->operation); - } + if (this->operation != nyan_op::INVALID) { + builder << op_to_string(this->operation); + } - if (this->value.exists()) { - builder << " " << this->value->repr(); - } + if (this->value.exists()) { + builder << " " << this->value->repr(); + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/member.h b/nyan/member.h index 15ab4ae..126b655 100644 --- a/nyan/member.h +++ b/nyan/member.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -20,59 +20,72 @@ namespace nyan { */ class Member { public: - /** - * Member with value. - */ - Member(override_depth_t depth, - nyan_op operation, - ValueHolder &&value); - - Member(const Member &other); - Member(Member &&other) noexcept; - Member &operator =(const Member &other); - Member &operator =(Member &&other) noexcept; - - ~Member() = default; - - /** - * Provide the operation stored in the member. - */ - nyan_op get_operation() const; - - /** - * Return the value stored in this member. - */ - const Value &get_value() const; - - /** - * Apply another member to this one. - * This applies the member with its operation - * to this member. - */ - void apply(const Member &change); - - /** - * String representation of this member. - */ - std::string str() const; + /** + * Member with value. + */ + Member(override_depth_t depth, + nyan_op operation, + Type declared_type, + ValueHolder &&value); + + Member(const Member &other); + Member(Member &&other) noexcept; + Member &operator =(const Member &other); + Member &operator =(Member &&other) noexcept; + + ~Member() = default; + + /** + * Get the operation performed by this member. + * + * @return Operation of the member. + */ + nyan_op get_operation() const; + + /** + * Get the value stored in this member. + * + * @return Value of the member. + */ + const Value &get_value() const; + + /** + * Apply another member, using its operation, to this member. + * + * @param change Member applied to this member. + */ + void apply(const Member &change); + + /** + * Get the string representation of this member's initialization part, + * i.e. operation and value. + * + * @return String containing the member initialization in nyan format. + */ + std::string str() const; protected: - /** - * Number of @ chars before the operation, - * those define the override depth when applying the patch. - */ - override_depth_t override_depth = 0; - - /** - * operation specified for this member. - */ - nyan_op operation = nyan_op::INVALID; - - /** - * Value stored in this member. - */ - ValueHolder value; + /** + * Number of @ chars before the operation, + * those define the override depth when applying the patch. + */ + override_depth_t override_depth = 0; + + /** + * Operation specified for this member. + */ + nyan_op operation = nyan_op::INVALID; + + /** + * Type from the member declaration. + */ + Type declared_type; + + /** + * Value stored in this member. + */ + ValueHolder value; }; diff --git a/nyan/member_info.cpp b/nyan/member_info.cpp index 2513b91..ae55d88 100644 --- a/nyan/member_info.cpp +++ b/nyan/member_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "member_info.h" #include @@ -9,51 +9,51 @@ namespace nyan { MemberInfo::MemberInfo(const Location &location) - : - location{location}, - initial_def{false} {} + : + location{location}, + initial_def{false} {} Type &MemberInfo::set_type(std::shared_ptr &&type, bool initial) { - this->initial_def = initial; - this->type = std::move(type); - return *this->type.get(); + this->initial_def = initial; + this->type = std::move(type); + return *this->type.get(); } Type &MemberInfo::set_type(const std::shared_ptr &type, bool initial) { - this->initial_def = initial; - this->type = type; - return *this->type.get(); + this->initial_def = initial; + this->type = type; + return *this->type.get(); } const std::shared_ptr &MemberInfo::get_type() const { - return this->type; + return this->type; } const Location &MemberInfo::get_location() const { - return this->location; + return this->location; } bool MemberInfo::is_initial_def() const { - return this->initial_def; + return this->initial_def; } std::string MemberInfo::str() const { - std::ostringstream builder; + std::ostringstream builder; - if (this->type) { - builder << this->type->str(); - } - else { - builder << "[no type]"; - } + if (this->type) { + builder << this->type->str(); + } + else { + builder << "[no type]"; + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/member_info.h b/nyan/member_info.h index 61db751..ba2391d 100644 --- a/nyan/member_info.h +++ b/nyan/member_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -13,38 +13,80 @@ class Type; /** * Stores information for a member of an Object. - * Also responsible for validating applied operators. */ class MemberInfo { public: - explicit MemberInfo(const Location &location); - ~MemberInfo() = default; - - Type &set_type(std::shared_ptr &&type, bool initial); - Type &set_type(const std::shared_ptr &type, bool initial); - const std::shared_ptr &get_type() const; - - const Location &get_location() const; - - bool is_initial_def() const; - - std::string str() const; + explicit MemberInfo(const Location &location); + ~MemberInfo() = default; + + /** + * Set the type of this member. Moves the shared pointer storing the + * type. + * + * @param type Shared pointer to the Type. + * @param initial Set to true if the member defined the type, false if the member is a + * patch member or inherited. + * + * @return Type of the member. + */ + Type &set_type(std::shared_ptr &&type, bool initial); + + /** + * Set the type of this member. Copies the shared pointer storing the + * type. + * + * @param type Shared pointer to the Type. + * @param initial Set to true if the member defined the type, false if the + * member is a patch member or inherited. + * + * @return Type of the member. + */ + Type &set_type(const std::shared_ptr &type, bool initial); + + /** + * Returns the type of this member. + * + * @return Type of the member. + */ + const std::shared_ptr &get_type() const; + + /** + * Get the position of this member in a file. + * + * @return Location of the member. + */ + const Location &get_location() const; + + /** + * Checks if this member contains the initial type definition, i.e. it + * is not a patch member or inherited. + * + * @return true if the member is the initial definition, else false. + */ + bool is_initial_def() const; + + /** + * Get the string representation of this member's declaration. + * + * @return String containing the member declaration in nyan format. + */ + std::string str() const; protected: - /** - * Location where the member was defined. - */ - Location location; - - /** - * is this member definition the initial one? - */ - bool initial_def; - - /** - * Type of the member. - */ - std::shared_ptr type; + /** + * Location where the member was defined. + */ + Location location; + + /** + * Determines whether this member definition is the initial one. + */ + bool initial_def; + + /** + * Type of the member. + */ + std::shared_ptr type; }; diff --git a/nyan/meta_info.cpp b/nyan/meta_info.cpp index 9055d78..a075e8c 100644 --- a/nyan/meta_info.cpp +++ b/nyan/meta_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "meta_info.h" @@ -9,60 +9,60 @@ namespace nyan { -ObjectInfo &MetaInfo::add_object(const fqon_t &name, ObjectInfo &&obj) { - // copy location so we can use it after obj was moved. - Location loc = obj.get_location(); +ObjectInfo &MetaInfo::add_object(const fqon_t &name, ObjectInfo &&obj_info) { + // copy location so we can use it after obj was moved. + Location loc = obj_info.get_location(); - auto ret = this->object_info.insert({name, std::move(obj)}); - if (ret.second == false) { - throw LangError{ - loc, - "object already defined", - {{ret.first->second.get_location(), "first defined here"}} - }; - } + auto ret = this->object_info.insert({name, std::move(obj_info)}); + if (ret.second == false) { + throw LangError{ + loc, + "object already defined", + {{ret.first->second.get_location(), "first defined here"}} + }; + } - return ret.first->second; + return ret.first->second; } const MetaInfo::obj_info_t &MetaInfo::get_objects() const { - return this->object_info; + return this->object_info; } ObjectInfo *MetaInfo::get_object(const fqon_t &name) { - auto it = this->object_info.find(name); - if (it == std::end(this->object_info)) { - return nullptr; - } - return &it->second; + auto it = this->object_info.find(name); + if (it == std::end(this->object_info)) { + return nullptr; + } + return &it->second; } // Thanks C++ for the beautiful duplication const ObjectInfo *MetaInfo::get_object(const fqon_t &name) const { - auto it = this->object_info.find(name); - if (it == std::end(this->object_info)) { - return nullptr; - } - return &it->second; + auto it = this->object_info.find(name); + if (it == std::end(this->object_info)) { + return nullptr; + } + return &it->second; } bool MetaInfo::has_object(const fqon_t &name) const { - return (this->object_info.find(name) != std::end(this->object_info)); + return (this->object_info.find(name) != std::end(this->object_info)); } std::string MetaInfo::str() const { - std::ostringstream builder; + std::ostringstream builder; - for (auto &it : this->get_objects()) { - builder << it.first << " -> " << it.second.str() << std::endl; - } + for (auto &it : this->get_objects()) { + builder << it.first << " -> " << it.second.str() << std::endl; + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/meta_info.h b/nyan/meta_info.h index 6bd2931..27c964a 100644 --- a/nyan/meta_info.h +++ b/nyan/meta_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -17,28 +17,71 @@ namespace nyan { */ class MetaInfo { public: - using obj_info_t = std::unordered_map; + using obj_info_t = std::unordered_map; - MetaInfo() = default; - ~MetaInfo() = default; + MetaInfo() = default; + ~MetaInfo() = default; - ObjectInfo &add_object(const fqon_t &name, ObjectInfo &&obj); + /** + * Add metadata information for an object. + * + * @param name Identifier of the object. + * @param obj_info ObjectInfo with metadata information. + * + * @return The stored metadata information object. + */ + ObjectInfo &add_object(const fqon_t &name, ObjectInfo &&obj_info); - const obj_info_t &get_objects() const; + /** + * Get the all metadata information objects for objects + * stored in the database. + * + * @return Map of metadata information objects by object identifier. + */ + const obj_info_t &get_objects() const; - ObjectInfo *get_object(const fqon_t &name); - const ObjectInfo *get_object(const fqon_t &name) const; + /** + * Get the the metadata information object for an object. + * + * @param name Identifier of the object. + * + * @return ObjectInfo with metadata information if the object is + * in the database, else nullptr. + */ + ObjectInfo *get_object(const fqon_t &name); - bool has_object(const fqon_t &name) const; + /** + * Get the the metadata information object for an object. + * + * @param name Identifier of the object. + * + * @return ObjectInfo with metadata information if the object is + * in the database, else nullptr. + */ + const ObjectInfo *get_object(const fqon_t &name) const; - std::string str() const; + /** + * Check if an object is in the database. + * + * @param name Identifier of the object. + * + * @return true if the object is in the database, else false. + */ + bool has_object(const fqon_t &name) const; + + /** + * Get a string representation of all metadata information objects. + * + * @return String representation of all metadata information objects. + */ + std::string str() const; protected: - /** - * Location and type information for the objects. - * This is for displaying error messages and line information. - */ - obj_info_t object_info; + /** + * Location and type information for the objects. + * This is for displaying error messages and line information. + */ + obj_info_t object_info; }; } // namespace nyan diff --git a/nyan/namespace.cpp b/nyan/namespace.cpp index 418580a..f9681e0 100644 --- a/nyan/namespace.cpp +++ b/nyan/namespace.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "namespace.h" @@ -15,106 +15,106 @@ static const std::string extension = ".nyan"; Namespace::Namespace(const fqon_t &name) - : - components{util::split(name, '.')} {} + : + components{util::split(name, '.')} {} Namespace::Namespace(const IDToken &token) { - this->components.reserve(token.get_components().size()); - for (auto &tok : token.get_components()) { - this->components.push_back(tok.get()); - } + this->components.reserve(token.get_components().size()); + for (auto &tok : token.get_components()) { + this->components.push_back(tok.get()); + } } Namespace::Namespace(const Namespace &other, const std::string &addend) - : - Namespace{other} { + : + Namespace{other} { - for (const auto &component : util::split(addend, '.')) { - this->components.push_back(component); - } + for (const auto &component : util::split(addend, '.')) { + this->components.push_back(component); + } } void Namespace::pop_last() { - if (this->empty()) { - throw InternalError{"popping from empty namespace"}; - } - this->components.pop_back(); + if (this->empty()) { + throw InternalError{"popping from empty namespace"}; + } + this->components.pop_back(); } bool Namespace::empty() const { - return this->components.empty(); + return this->components.empty(); } fqon_t Namespace::combine(const IDToken &name, size_t skip) const { - Namespace combined{*this}; - - // append all components, but skip the first n parts. - for (auto &part : name.get_components()) { - if (skip > 0) { - skip -= 1; - } else { - combined.components.push_back(part.get()); - } - } - - return combined.to_fqon(); + Namespace combined{*this}; + + // append all components, but skip the first n parts. + for (auto &part : name.get_components()) { + if (skip > 0) { + skip -= 1; + } else { + combined.components.push_back(part.get()); + } + } + + return combined.to_fqon(); } // TODO: nested objects have components within one file. // separating via / is wrong for them. std::string Namespace::to_filename() const { - std::string ret = util::strjoin( - "/", - this->components, - [] (const auto &in) -> const std::string& { - return in; - } - ); - - ret += extension; - return ret; + std::string ret = util::strjoin( + "/", + this->components, + [] (const auto &in) -> const std::string& { + return in; + } + ); + + ret += extension; + return ret; } Namespace Namespace::from_filename(const std::string &filename) { - if (not util::ends_with(filename, extension)) { - throw APIError{"invalid file extension"}; - } + if (not util::ends_with(filename, extension)) { + throw APIError{"invalid file extension"}; + } - size_t n = std::count(filename.begin(), filename.end(), '.'); + size_t n = std::count(filename.begin(), filename.end(), '.'); - // only the .nyan dot is allowed - if (n > 1) { - throw APIError{"there's too many dots in the path"}; - } + // only the .nyan dot is allowed + if (n > 1) { + throw APIError{"there's too many dots in the path"}; + } - // strip off the file extension - std::string namespace_name{filename, 0, filename.size() - extension.size()}; - std::replace(namespace_name.begin(), namespace_name.end(), '/', '.'); + // strip off the file extension + std::string namespace_name{filename, 0, filename.size() - extension.size()}; + std::replace(namespace_name.begin(), namespace_name.end(), '/', '.'); - // the fqon_t constructor. - return Namespace{namespace_name}; + // the fqon_t constructor. + return Namespace{namespace_name}; } fqon_t Namespace::to_fqon() const { - return util::strjoin(".", this->components); + return util::strjoin(".", this->components); } std::string Namespace::str() const { - return this->to_fqon(); + return this->to_fqon(); } bool Namespace::operator ==(const Namespace &other) const { - return this->components == other.components; + return this->components == other.components; } } // namespace nyan @@ -123,11 +123,11 @@ bool Namespace::operator ==(const Namespace &other) const { namespace std { size_t hash::operator ()(const nyan::Namespace &ns) const { - size_t ret = 0; - for (auto &component : ns.components) { - ret = nyan::util::hash_combine(ret, std::hash{}(component)); - } - return ret; + size_t ret = 0; + for (auto &component : ns.components) { + ret = nyan::util::hash_combine(ret, std::hash{}(component)); + } + return ret; } } // namespace std diff --git a/nyan/namespace.h b/nyan/namespace.h index efa0a53..bd1a36b 100644 --- a/nyan/namespace.h +++ b/nyan/namespace.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -13,36 +13,70 @@ class IDToken; class Namespace { - friend struct std::hash; + friend struct std::hash; public: - explicit Namespace(const fqon_t &token); - explicit Namespace(const IDToken &token); - Namespace(const Namespace &other, const std::string &addend); - - virtual ~Namespace() = default; - - void pop_last(); - bool empty() const; - - /** - * Append the given name to this namespace. - * Return the combined fqon. - * Skip n components of the given name before appending. - */ - fqon_t combine(const IDToken &name, size_t skip=0) const; - - std::string to_filename() const; - static Namespace from_filename(const std::string &filename); - - fqon_t to_fqon() const; - - std::string str() const; - - bool operator ==(const Namespace &other) const; + explicit Namespace(const fqon_t &token); + explicit Namespace(const IDToken &token); + Namespace(const Namespace &other, const std::string &addend); + + virtual ~Namespace() = default; + + void pop_last(); + bool empty() const; + + /** + * Append the given object/member reference to the namespace identifier to + * get its identifier. + * + * @param name IDToken with an object/member reference. + * @param skip Number of components at the start of @p name to be skipped. + * + * @return Identifier of the object/member. + */ + fqon_t combine(const IDToken &name, size_t skip=0) const; + + /** + * Get a (relative) path to a filename for the namespace. + * + * @return String representation of the path. Uses '/' as path + * component separator. + */ + std::string to_filename() const; + + /** + * Create a namespace from a given filename. Performs a sanity + * check on the filename. + * + * @param filename Name of a file, including the extension. + * + * @return Namespace for the filename. + */ + static Namespace from_filename(const std::string &filename); + + /** + * Get the identifier of the namespace. + * + * @return Identifier of the namespace. + */ + fqon_t to_fqon() const; + + /** + * Get a string representation of the namespace. + * + * @return String representation of the namespace. + */ + std::string str() const; + + /** + * Checks if this namespace is equal to a given namespace. + * + * @return true if the namespaces are equal, else false. + */ + bool operator ==(const Namespace &other) const; protected: - std::vector components; + std::vector components; }; } // namespace nyan @@ -51,6 +85,6 @@ class Namespace { namespace std { template <> struct hash { - size_t operator ()(const nyan::Namespace &ns) const; + size_t operator ()(const nyan::Namespace &ns) const; }; } // namespace std diff --git a/nyan/namespace_finder.cpp b/nyan/namespace_finder.cpp index b96b536..b0790a2 100644 --- a/nyan/namespace_finder.cpp +++ b/nyan/namespace_finder.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "namespace_finder.h" @@ -11,50 +11,50 @@ namespace nyan { NamespaceFinder::NamespaceFinder(AST &&ast) - : - ast{std::move(ast)} {} + : + ast{std::move(ast)} {} void NamespaceFinder::add_import(const Namespace &import) { - this->imports.insert(import); + this->imports.insert(import); } void NamespaceFinder::add_alias(const Token &alias, const Namespace &destination) { - const std::string &search = alias.get(); + const std::string &search = alias.get(); - if (this->aliases.find(search) != std::end(this->aliases)) { - throw NameError{alias, "redefinition of namespace alias", search}; - } + if (this->aliases.find(search) != std::end(this->aliases)) { + throw NameError{alias, "redefinition of namespace alias", search}; + } - this->aliases.insert({search, destination}); + this->aliases.insert({search, destination}); } bool NamespaceFinder::check_conflict(const std::string &name) const { - return (this->aliases.find(name) != std::end(this->aliases) or - this->imports.find(Namespace{name}) != std::end(this->imports)); + return (this->aliases.find(name) != std::end(this->aliases) or + this->imports.find(Namespace{name}) != std::end(this->imports)); } fqon_t NamespaceFinder::expand_alias(const IDToken &name) const { - if (unlikely(not name.exists())) { - throw InternalError{"tried expanding alias on empty id token"}; - } + if (unlikely(not name.exists())) { + throw InternalError{"tried expanding alias on empty id token"}; + } - // only the first component can be an alias. - const std::string &first = name.get_components()[0].get(); + // only the first component can be an alias. + const std::string &first = name.get_components()[0].get(); - auto it = this->aliases.find(first); - if (it != std::end(this->aliases)) { - // alias found. now expand it. - return it->second.combine(name, 1); - } + auto it = this->aliases.find(first); + if (it != std::end(this->aliases)) { + // alias found. now expand it. + return it->second.combine(name, 1); + } - // no alias found. basically return the input name. - return Namespace{name}.to_fqon(); + // no alias found. basically return the input name. + return Namespace{name}.to_fqon(); } @@ -62,59 +62,59 @@ fqon_t NamespaceFinder::find(const Namespace ¤t, const IDToken &search, const MetaInfo &typedb) const { - if (unlikely(not search.exists())) { - throw InternalError{"tried to find namespace for empty id"}; - } + if (unlikely(not search.exists())) { + throw InternalError{"tried to find namespace for empty id"}; + } - Namespace search_base{current}; + Namespace search_base{current}; - fqon_t result; - // go towards the root namespace - // to search for the matching object name - while (true) { - result = search_base.combine(search); - if (typedb.has_object(result)) { - return result; - } + fqon_t result; + // go towards the root namespace + // to search for the matching object name + while (true) { + result = search_base.combine(search); + if (typedb.has_object(result)) { + return result; + } - // if the search base is exhausted, do alias expansion. - if (search_base.empty()) { - result = this->expand_alias(search); + // if the search base is exhausted, do alias expansion. + if (search_base.empty()) { + result = this->expand_alias(search); - if (not typedb.has_object(result)) { - throw NameError{search, "unknown name", search.str()}; - } + if (not typedb.has_object(result)) { + throw NameError{search, "unknown name", search.str()}; + } - return result; - } + return result; + } - search_base.pop_last(); - } + search_base.pop_last(); + } } const AST &NamespaceFinder::get_ast() const { - return this->ast; + return this->ast; } std::string NamespaceFinder::str() const { - std::ostringstream builder; - builder << "NamespaceFinder knows:" << std::endl - << "= aliases:" << std::endl; + std::ostringstream builder; + builder << "NamespaceFinder knows:" << std::endl + << "= aliases:" << std::endl; - for (auto &it : this->aliases) { - builder << " * " << it.first - << " => " << it.second.str() << std::endl; - } + for (auto &it : this->aliases) { + builder << " * " << it.first + << " => " << it.second.str() << std::endl; + } - builder << "= imports:" << std::endl; + builder << "= imports:" << std::endl; - for (auto &it : this->imports) { - builder << " * " << it.str() << std::endl; - } + for (auto &it : this->imports) { + builder << " * " << it.str() << std::endl; + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/namespace_finder.h b/nyan/namespace_finder.h index caabd3d..4843c1d 100644 --- a/nyan/namespace_finder.h +++ b/nyan/namespace_finder.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -13,47 +13,92 @@ namespace nyan { class NamespaceFinder { - /** - * Lookup map to find the namespace for an alias. - */ - using namespace_alias_t = std::unordered_map; + /** + * Lookup map to find the namespace for an alias. + */ + using namespace_alias_t = std::unordered_map; - /** - * In the current namespace, namespaces in here were imported. - */ - using namespace_available_t = std::unordered_set; + /** + * In the current namespace, namespaces in here were imported. + */ + using namespace_available_t = std::unordered_set; public: - NamespaceFinder(AST &&ast); - - void add_import(const Namespace &ns); - void add_alias(const Token &alias, const Namespace &destination); - - /** - * check for naming conflict of an object. - * returns true if the name conflicts. - */ - bool check_conflict(const std::string &name) const; - - /** Expand the id for an alias. */ - fqon_t expand_alias(const IDToken &name) const; - - /** Search for a fqon in a given namespace and below */ - fqon_t find(const Namespace ¤t, - const IDToken &search, - const MetaInfo &typedb) const; - - /** Return the AST of this namespace */ - const AST &get_ast() const; - - std::string str() const; + NamespaceFinder(AST &&ast); + + /** + * Imports another namespace into the finder. + * + * @param ns Namespace that should be imported. + */ + void add_import(const Namespace &ns); + + /** + * Imports another namespace into the finder by using an alias. + * + * @param alias Token with the name of the alias. + * @param ns Namespace that should be imported. + */ + void add_alias(const Token &alias, const Namespace &destination); + + /** + * Check if a name conflicts wth other names in the namespace, i.e. the + * name is already used. + * + * @return true if the name is already used, else false. + */ + bool check_conflict(const std::string &name) const; + + /** + * Get the identifier of a namespace associated with an alias. + * + * @param name IDToken with an alias as its first component. + * + * @return Identifier of the namespace. + */ + fqon_t expand_alias(const IDToken &name) const; + + /** + * Search for the object/member identifier of an object/member reference + * in a given namespace. + * + * @param name IDToken with an object/member reference. + * + * @return Identifier of the object/member. + */ + fqon_t find(const Namespace ¤t, + const IDToken &search, + const MetaInfo &typedb) const; + + /** + * Get the AST (abstract syntax tree) of this namespace. + * + * @return AST of this namespace. + */ + const AST &get_ast() const; + + /** + * Get the string representation of this namespace. + * + * @return String representation of this namespace. + */ + std::string str() const; public: - AST ast; - - namespace_available_t imports; - - namespace_alias_t aliases; + /** + * Abstract syntax tree of the namespace of this finder. + */ + AST ast; + + /** + * Directly imported namespaces. + */ + namespace_available_t imports; + + /** + * Namespaces imported by alias. + */ + namespace_alias_t aliases; }; } // namespace nyan diff --git a/nyan/nyan_tool.cpp b/nyan/nyan_tool.cpp index 509df80..587d055 100644 --- a/nyan/nyan_tool.cpp +++ b/nyan/nyan_tool.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "nyan_tool.h" @@ -15,216 +15,216 @@ namespace nyan { int test_parser(const std::string &base_path, const std::string &filename) { - int ret = 0; - auto db = Database::create(); - - db->load( - filename, - [&base_path] (const std::string &filename) { - return std::make_shared(base_path + "/" + filename); - } - ); - - std::shared_ptr root = db->new_view(); - - Object second = root->get_object("test.Second"); - Object first = root->get_object("test.First"); - - std::cout << "after change: First.member == " - << *root->get_object("test.First").get("member") - << std::endl; - - Object patch = root->get_object("test.FirstPatch"); - for (int i = 0; i < 3; i++) { - Transaction tx = root->new_transaction(); - tx.add(patch); - if (not tx.commit()) { - std::cout << "fail @ " << i << std::endl; - } - } - - auto value = *root->get_object("test.First").get("member"); - - std::cout << "after change: First.member == " - << value.str() - << std::endl; - - if (value != 24) { - std::cout << "patch result is wrong" << std::endl; - return 1; - } - - - order_t trans_callback_time = -1; - bool got_child_callback = false; - auto cb_func = [&](order_t t, const fqon_t &fqon, const ObjectState &) { - trans_callback_time = t; - if (fqon == "test.TestChild") { - got_child_callback = true; - } - std::cout << "got transaction callback for object " << fqon << std::endl; - }; - - auto callback_hdl_test = root->get_object("test.Test").subscribe(cb_func); - auto callback_hdl_testchild = root->get_object("test.TestChild").subscribe(cb_func); - - bool success; - order_t trans_time = 1; - Transaction tx = root->new_transaction(trans_time); - tx.add(patch); - tx.add(root->get_object("test.Patch")); - tx.add(root->get_object("test.SetPatch")); - success = tx.commit(); - - if (trans_callback_time != trans_time) { - std::cout << "Transaction callback failed" << std::endl; - } - - if (not got_child_callback) { - std::cout << "got no callback for TestChild" << std::endl; - ret = 1; - } - - if (success) { - std::cout << "Transaction OK" << std::endl; - } - else { - std::cout << "Transaction FAILED" << std::endl; - ret = 1; - } - - std::cout << "after change: Second.member == " - << *second.get("member", 1) - << std::endl; - - std::cout << "SetTest.member = " - << root->get_object("test.SetTest").get_value("member")->str() - << std::endl - << "SetTest.orderedmember = " - << root->get_object("test.SetTest").get_value("orderedmember")->str() - << std::endl - << "Fifth.truth = " - << root->get_object("test.Fifth").get_value("truth")->str() - << std::endl - << "PATCH" - << std::endl - << "SetTest.member = " - << root->get_object("test.SetTest").get_value("member", 1)->str() - << std::endl - << "SetTest.orderedmember = " - << root->get_object("test.SetTest").get_value("orderedmember", 1)->str() - << std::endl << std::endl; - - std::cout << "test.gschicht parents = " << util::strjoin(", ", root->get_object("test.Test").get_parents()) - << std::endl << "PATCH" << std::endl - << "test.gschicht parents = " << util::strjoin(", ", root->get_object("test.Test").get_parents(1)) - << std::endl << "newvalue = " << root->get_object("test.Test").get_value("new_value", 1)->str() - << std::endl; - - return ret; + int ret = 0; + auto db = Database::create(); + + db->load( + filename, + [&base_path] (const std::string &filename) { + return std::make_shared(base_path + "/" + filename); + } + ); + + std::shared_ptr root = db->new_view(); + + Object second = root->get_object("test.Second"); + Object first = root->get_object("test.First"); + + std::cout << "after change: First.member == " + << *root->get_object("test.First").get("member") + << std::endl; + + Object patch = root->get_object("test.FirstPatch"); + for (int i = 0; i < 3; i++) { + Transaction tx = root->new_transaction(); + tx.add(patch); + if (not tx.commit()) { + std::cout << "fail @ " << i << std::endl; + } + } + + auto value = *root->get_object("test.First").get("member"); + + std::cout << "after change: First.member == " + << value.str() + << std::endl; + + if (value != 24) { + std::cout << "patch result is wrong" << std::endl; + return 1; + } + + + order_t trans_callback_time = -1; + bool got_child_callback = false; + auto cb_func = [&](order_t t, const fqon_t &fqon, const ObjectState &) { + trans_callback_time = t; + if (fqon == "test.TestChild") { + got_child_callback = true; + } + std::cout << "got transaction callback for object " << fqon << std::endl; + }; + + auto callback_hdl_test = root->get_object("test.Test").subscribe(cb_func); + auto callback_hdl_testchild = root->get_object("test.TestChild").subscribe(cb_func); + + bool success; + order_t trans_time = 1; + Transaction tx = root->new_transaction(trans_time); + tx.add(patch); + tx.add(root->get_object("test.Patch")); + tx.add(root->get_object("test.SetPatch")); + success = tx.commit(); + + if (trans_callback_time != trans_time) { + std::cout << "Transaction callback failed" << std::endl; + } + + if (not got_child_callback) { + std::cout << "got no callback for TestChild" << std::endl; + ret = 1; + } + + if (success) { + std::cout << "Transaction OK" << std::endl; + } + else { + std::cout << "Transaction FAILED" << std::endl; + ret = 1; + } + + std::cout << "after change: Second.member == " + << *second.get("member", 1) + << std::endl; + + std::cout << "SetTest.member = " + << root->get_object("test.SetTest").get_value("member")->str() + << std::endl + << "SetTest.orderedmember = " + << root->get_object("test.SetTest").get_value("orderedmember")->str() + << std::endl + << "Fifth.truth = " + << root->get_object("test.Fifth").get_value("truth")->str() + << std::endl + << "PATCH" + << std::endl + << "SetTest.member = " + << root->get_object("test.SetTest").get_value("member", 1)->str() + << std::endl + << "SetTest.orderedmember = " + << root->get_object("test.SetTest").get_value("orderedmember", 1)->str() + << std::endl << std::endl; + + std::cout << "test.gschicht parents = " << util::strjoin(", ", root->get_object("test.Test").get_parents()) + << std::endl << "PATCH" << std::endl + << "test.gschicht parents = " << util::strjoin(", ", root->get_object("test.Test").get_parents(1)) + << std::endl << "newvalue = " << root->get_object("test.Test").get_value("new_value", 1)->str() + << std::endl; + + return ret; } int run(flags_t flags, params_t params) { - try { - if (flags[option_flag::TEST_PARSER]) { - const std::string &filename = params[option_param::FILE]; - - if (filename.size() == 0) { - throw Error{"empty filename given"}; - } - - std::vector parts = util::split(filename, '/'); - - // first file is assumed to be in the root. - std::string first_file = parts[parts.size() - 1]; - - // everything else is the base path - parts.pop_back(); - std::string base_path = util::strjoin("/", parts); - - try { - return nyan::test_parser(base_path, first_file); - } - catch (LangError &err) { - std::cout << "\x1b[33;1mfile error:\x1b[m\n" - << err << std::endl - << err.show_problem_origin() - << std::endl << std::endl; - return 1; - } - } - else { - std::cout << "no action selected" << std::endl << std::endl; - help(); - } - } - catch (Error &err) { - std::cout << "\x1b[31;1merror:\x1b[m\n" - << err << std::endl; - return 1; - } - return 0; + try { + if (flags[option_flag::TEST_PARSER]) { + const std::string &filename = params[option_param::FILE]; + + if (filename.size() == 0) { + throw Error{"empty filename given"}; + } + + std::vector parts = util::split(filename, '/'); + + // first file is assumed to be in the root. + std::string first_file = parts[parts.size() - 1]; + + // everything else is the base path + parts.pop_back(); + std::string base_path = util::strjoin("/", parts); + + try { + return nyan::test_parser(base_path, first_file); + } + catch (LangError &err) { + std::cout << "\x1b[33;1mfile error:\x1b[m\n" + << err << std::endl + << err.show_problem_origin() + << std::endl << std::endl; + return 1; + } + } + else { + std::cout << "no action selected" << std::endl << std::endl; + help(); + } + } + catch (Error &err) { + std::cout << "\x1b[31;1merror:\x1b[m\n" + << err << std::endl; + return 1; + } + return 0; } void help() { - std::cout << "\x1b[32;1mnyan\x1b[m - " - "\x1b[32;1my\x1b[met " - "\x1b[32;1ma\x1b[mnother " - "\x1b[32;1mn\x1b[motation " - "-- tool" << std::endl - << std::endl - << "usage:" << std::endl - << "-h --help -- show this" << std::endl - << "-f --file -- file to load" << std::endl - << "-b --break -- debug-break on error" << std::endl - << " --test-parser -- test the parser" << std::endl - << " --echo -- print the ast" << std::endl - << "" << std::endl; + std::cout << "\x1b[32;1mnyan\x1b[m - " + "\x1b[32;1my\x1b[met " + "\x1b[32;1ma\x1b[mnother " + "\x1b[32;1mn\x1b[motation " + "-- tool" << std::endl + << std::endl + << "usage:" << std::endl + << "-h --help -- show this" << std::endl + << "-f --file -- file to load" << std::endl + << "-b --break -- debug-break on error" << std::endl + << " --test-parser -- test the parser" << std::endl + << " --echo -- print the ast" << std::endl + << "" << std::endl; } std::pair argparse(int argc, char** argv) { - flags_t flags{ - {option_flag::ECHO, false}, - {option_flag::TEST_PARSER, false} - }; - - params_t params{ - {option_param::FILE, ""} - }; - - for (int option_index = 1; option_index < argc; ++option_index) { - std::string arg = argv[option_index]; - if (arg == "-h" or arg == "--help") { - help(); - exit(0); - } - else if (arg == "-f" or arg == "--file") { - ++option_index; - if (option_index == argc) { - std::cerr << "Filename not specified" << std::endl; - help(); - exit(-1); - } - params[option_param::FILE] = argv[option_index]; - } - else if (arg == "-b" or arg == "--break") { - Error::enable_break(true); - } - else if (arg == "--echo") { - flags[option_flag::ECHO] = true; - } - else if (arg == "--test-parser") { - flags[option_flag::TEST_PARSER] = true; - } - else { - std::cerr << "Unused argument: " << arg << std::endl; - } - } - - return std::make_pair(flags, params); + flags_t flags{ + {option_flag::ECHO, false}, + {option_flag::TEST_PARSER, false} + }; + + params_t params{ + {option_param::FILE, ""} + }; + + for (int option_index = 1; option_index < argc; ++option_index) { + std::string arg = argv[option_index]; + if (arg == "-h" or arg == "--help") { + help(); + exit(0); + } + else if (arg == "-f" or arg == "--file") { + ++option_index; + if (option_index == argc) { + std::cerr << "Filename not specified" << std::endl; + help(); + exit(-1); + } + params[option_param::FILE] = argv[option_index]; + } + else if (arg == "-b" or arg == "--break") { + Error::enable_break(true); + } + else if (arg == "--echo") { + flags[option_flag::ECHO] = true; + } + else if (arg == "--test-parser") { + flags[option_flag::TEST_PARSER] = true; + } + else { + std::cerr << "Unused argument: " << arg << std::endl; + } + } + + return std::make_pair(flags, params); } } // namespace nyan @@ -232,22 +232,22 @@ std::pair argparse(int argc, char** argv) { int main(int argc, char **argv) { - auto args = nyan::argparse(argc, argv); - nyan::flags_t flags = args.first; - nyan::params_t params = args.second; + auto args = nyan::argparse(argc, argv); + nyan::flags_t flags = args.first; + nyan::params_t params = args.second; #define NYAN_CATCHALL #ifndef NYAN_CATCHALL - try { + try { #endif - return nyan::run(flags, params); + return nyan::run(flags, params); #ifndef NYAN_CATCHALL - } - catch (std::exception &exc) { - std::cout << "\x1b[31;1mfatal error:\x1b[m " - << exc.what() << std::endl; - return 1; - } + } + catch (std::exception &exc) { + std::cout << "\x1b[31;1mfatal error:\x1b[m " + << exc.what() << std::endl; + return 1; + } #endif } diff --git a/nyan/nyan_tool.h b/nyan/nyan_tool.h index 1422698..2e571d2 100644 --- a/nyan/nyan_tool.h +++ b/nyan/nyan_tool.h @@ -11,15 +11,15 @@ namespace nyan { * boolean flags to be set by cmdline options */ enum class option_flag { - ECHO, - TEST_PARSER + ECHO, + TEST_PARSER }; /** * string arguments to be set by cmdline options */ enum class option_param { - FILE + FILE }; using flags_t = std::unordered_map; @@ -46,9 +46,9 @@ namespace std { */ template<> struct hash { - size_t operator ()(const nyan::option_flag &x) const { - return static_cast(x); - } + size_t operator ()(const nyan::option_flag &x) const { + return static_cast(x); + } }; /** @@ -56,9 +56,9 @@ struct hash { */ template<> struct hash { - size_t operator ()(const nyan::option_param &x) const { - return static_cast(x); - } + size_t operator ()(const nyan::option_param &x) const { + return static_cast(x); + } }; } // namespace std diff --git a/nyan/object.cpp b/nyan/object.cpp index 43bfb8f..939a5ae 100644 --- a/nyan/object.cpp +++ b/nyan/object.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object.h" @@ -26,231 +26,231 @@ namespace nyan { Object::Object(const fqon_t &name, const std::shared_ptr &origin) - : - origin{origin}, - name{name} {} + : + origin{origin}, + name{name} {} Object::~Object() = default; const fqon_t &Object::get_name() const { - return this->name; + return this->name; } const std::shared_ptr &Object::get_view() const { - return this->origin; + return this->origin; } ValueHolder Object::get_value(const memberid_t &member, order_t t) const { - return this->calculate_value(member, t); + return this->calculate_value(member, t); } value_int_t Object::get_int(const memberid_t &member, order_t t) const { - return this->get_number(member, t); + return this->get_number(member, t); } value_float_t Object::get_float(const memberid_t &member, order_t t) const { - return this->get_number(member, t); + return this->get_number(member, t); } const std::string &Object::get_text(const memberid_t &member, order_t t) const { - return *this->get(member, t); + return *this->get(member, t); } bool Object::get_bool(const memberid_t &member, order_t t) const { - return *this->get(member, t); + return *this->get(member, t); } const set_t &Object::get_set(const memberid_t &member, order_t t) const { - return this->get(member, t)->get(); + return this->get(member, t)->get(); } const ordered_set_t &Object::get_orderedset(const memberid_t &member, order_t t) const { - return this->get(member, t)->get(); + return this->get(member, t)->get(); } const std::string &Object::get_file(const memberid_t &member, order_t t) const { - return this->get(member, t)->get(); + return this->get(member, t)->get(); } Object Object::get_object(const memberid_t &member, order_t t) const { - return *this->get(member, t); + return *this->get(member, t); } template <> std::shared_ptr Object::get(const memberid_t &member, order_t t) const { - auto obj_val = this->get(member, t); - fqon_t fqon = obj_val->get(); - std::shared_ptr ret = std::make_shared(Object::Restricted{}, - fqon, this->origin); - return ret; + auto obj_val = this->get(member, t); + fqon_t fqon = obj_val->get(); + std::shared_ptr ret = std::make_shared(Object::Restricted{}, + fqon, this->origin); + return ret; } ValueHolder Object::calculate_value(const memberid_t &member, order_t t) const { - using namespace std::string_literals; - - // TODO: don't allow calculating values for patches? - // it's impossible as they may have members without = - - // get references to all parentobject-states - std::vector> parents; - - const std::vector &linearization = this->get_linearized(t); - - // find the last value assigning with = - // it sets the base value where we apply the modifications then - size_t defined_by = 0; - - const Value *base_value = nullptr; - for (auto &obj : linearization) { - parents.push_back(this->origin->get_raw(obj, t)); - const ObjectState *obj_raw = parents.back().get(); - const Member *obj_member = obj_raw->get(member); - // if the object has the member, check if it's the = - if (obj_member != nullptr) { - if (obj_member->get_operation() == nyan_op::ASSIGN) { - base_value = &obj_member->get_value(); - break; - } - } - defined_by += 1; - } - - // no operator = was found for this member - // -> no parent assigned a value. - // errors in the data files are detected at load time already. - if (unlikely(defined_by >= linearization.size() or base_value == nullptr)) { - throw MemberNotFoundError{this->name, member}; - } - - // if this object defines the value, no aggregation is needed. - if (defined_by == 0) { - return base_value->copy(); - } - - // create a working copy of the value - ValueHolder result = base_value->copy(); - - // walk back and apply the value changes - while (true) { - const Member *change = parents[defined_by]->get(member); - if (change != nullptr) { - result->apply(*change); - } - if (defined_by == 0) { - break; - } - defined_by -= 1; - } - - return result; + using namespace std::string_literals; + + // TODO: don't allow calculating values for patches? + // it's impossible as they may have members without = + + // get references to all parentobject-states + std::vector> parents; + + const std::vector &linearization = this->get_linearized(t); + + // find the last value assigning with = + // it sets the base value where we apply the modifications then + size_t defined_by = 0; + + const Value *base_value = nullptr; + for (auto &obj : linearization) { + parents.push_back(this->origin->get_raw(obj, t)); + const ObjectState *obj_raw = parents.back().get(); + const Member *obj_member = obj_raw->get(member); + // if the object has the member, check if it's the = + if (obj_member != nullptr) { + if (obj_member->get_operation() == nyan_op::ASSIGN) { + base_value = &obj_member->get_value(); + break; + } + } + defined_by += 1; + } + + // no operator = was found for this member + // -> no parent assigned a value. + // errors in the data files are detected at load time already. + if (unlikely(defined_by >= linearization.size() or base_value == nullptr)) { + throw MemberNotFoundError{this->name, member}; + } + + // if this object defines the value, no aggregation is needed. + if (defined_by == 0) { + return base_value->copy(); + } + + // create a working copy of the value + ValueHolder result = base_value->copy(); + + // walk back and apply the value changes + while (true) { + const Member *change = parents[defined_by]->get(member); + if (change != nullptr) { + result->apply(*change); + } + if (defined_by == 0) { + break; + } + defined_by -= 1; + } + + return result; } const std::deque &Object::get_parents(order_t t) const { - return this->get_raw(t)->get_parents(); + return this->get_raw(t)->get_parents(); } bool Object::has(const memberid_t &member, order_t t) const { - // TODO: cache? + // TODO: cache? - const std::vector &lin = this->get_linearized(t); + const std::vector &lin = this->get_linearized(t); - for (auto &obj : lin) { - if (this->origin->get_raw(obj, t)->get(member) != nullptr) { - return true; - } - } + for (auto &obj : lin) { + if (this->origin->get_raw(obj, t)->get(member) != nullptr) { + return true; + } + } - return false; + return false; } bool Object::extends(fqon_t other_fqon, order_t t) const { - if (this->name == other_fqon) { - return true; - } + if (this->name == other_fqon) { + return true; + } - // TODO cache? + // TODO cache? - auto &linearization = this->get_linearized(t); + auto &linearization = this->get_linearized(t); - for (auto &obj : linearization) { - if (obj == other_fqon) { - return true; - } - } + for (auto &obj : linearization) { + if (obj == other_fqon) { + return true; + } + } - return false; + return false; } const ObjectInfo &Object::get_info() const { - if (unlikely(not this->name.size())) { - throw InvalidObjectError{}; - } - - const ObjectInfo *ret = this->origin->get_database().get_info().get_object(this->get_name()); - if (unlikely(ret == nullptr)) { - throw InternalError{"object info unavailable for object handle"}; - } - return *ret; + if (unlikely(not this->name.size())) { + throw InvalidObjectError{}; + } + + const ObjectInfo *ret = this->origin->get_database().get_info().get_object(this->get_name()); + if (unlikely(ret == nullptr)) { + throw InternalError{"object info unavailable for object handle"}; + } + return *ret; } bool Object::is_patch() const { - // must be a patch from the beginning of time! - return this->get_info().is_patch(); + // must be a patch from the beginning of time! + return this->get_info().is_patch(); } const fqon_t *Object::get_target() const { - const PatchInfo *patch_info = this->get_info().get_patch().get(); - if (unlikely(patch_info == nullptr)) { - return nullptr; - } - return &patch_info->get_target(); + const PatchInfo *patch_info = this->get_info().get_patch().get(); + if (unlikely(patch_info == nullptr)) { + return nullptr; + } + return &patch_info->get_target(); } const std::vector &Object::get_linearized(order_t t) const { - if (unlikely(not this->name.size())) { - throw InvalidObjectError{}; - } + if (unlikely(not this->name.size())) { + throw InvalidObjectError{}; + } - return this->origin->get_linearization(this->name, t); + return this->origin->get_linearization(this->name, t); } std::shared_ptr Object::subscribe(const update_cb_t &callback) { - if (unlikely(not this->name.size())) { - throw InvalidObjectError{}; - } + if (unlikely(not this->name.size())) { + throw InvalidObjectError{}; + } - return this->origin->create_notifier(this->name, callback); + return this->origin->create_notifier(this->name, callback); } const std::shared_ptr &Object::get_raw(order_t t) const { - if (unlikely(not this->name.size())) { - throw InvalidObjectError{}; - } + if (unlikely(not this->name.size())) { + throw InvalidObjectError{}; + } - return this->origin->get_raw(this->name, t); + return this->origin->get_raw(this->name, t); } } // namespace nyan diff --git a/nyan/object.h b/nyan/object.h index b9f09b6..ad1e139 100644 --- a/nyan/object.h +++ b/nyan/object.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -31,167 +31,314 @@ class View; * Handle for accessing a nyan object independent of time. */ class Object { - friend class View; + friend class View; protected: - /** - * Create a nyan-object handle. This is never invoked by the user, - * as handles are generated internally and then handed over. - */ - Object(const fqon_t &name, const std::shared_ptr &origin); - class Restricted {}; + /** + * Create a nyan-object handle. This is never invoked by the user, + * as handles are generated internally and then handed over. + */ + Object(const fqon_t &name, const std::shared_ptr &origin); + class Restricted {}; public: - /** - * Default constructor for an invalid nyan::Object. - */ - Object() = default; - - // This constructor is public, but can't be invoked since the Restricted - // class is not available. We use this to be able to invoke make_shared - // within this class, but not outside of it. - Object(Object::Restricted, const fqon_t &name, const std::shared_ptr &origin) - : Object(name, origin) {}; - ~Object(); - - /** - * Return the fully-qualified object name. - */ - const fqon_t &get_name() const; - - /** - * Return the view this object was retrieved from. - */ - const std::shared_ptr &get_view() const; - - /** - * Get a calculated member value. - */ - ValueHolder get_value(const memberid_t &member, order_t t=LATEST_T) const; - - /** - * Invokes the get_value function and then does a cast. - * There's a special variant for T=nyan::Object which creates - * an object handle. - * - * TODO: either return a stored variant reference or the shared ptr of the holder - */ - template - std::shared_ptr get(const memberid_t &member, order_t t=LATEST_T) const; - - template - ret get_number(const memberid_t &member, order_t t=LATEST_T) const; - - value_int_t get_int(const memberid_t &member, order_t t=LATEST_T) const; - - value_float_t get_float(const memberid_t &member, order_t t=LATEST_T) const; - - const std::string &get_text(const memberid_t &member, order_t t=LATEST_T) const; - - bool get_bool(const memberid_t &member, order_t t=LATEST_T) const; - - const set_t &get_set(const memberid_t &member, order_t t=LATEST_T) const; - - const ordered_set_t &get_orderedset(const memberid_t &member, order_t t=LATEST_T) const; - - const std::string &get_file(const memberid_t &member, order_t t=LATEST_T) const; - - Object get_object(const memberid_t &fqon, order_t t=LATEST_T) const; - - /** - * Return the parents of the object. - */ - const std::deque &get_parents(order_t t=LATEST_T) const; - - /** - * Test if this object has a member of given name. - */ - bool has(const memberid_t &member, order_t t=LATEST_T) const; - - /** - * Test if this object is a child of the given parent. - * Returns true if other_fqon equals this object or any - * of its (transitive) parents. - */ - bool extends(fqon_t other_fqon, order_t t=LATEST_T) const; - - /** - * Return the object metadata. - */ - const ObjectInfo &get_info() const; - - /** - * Check if this object is a patch. - * Currently, a non-patch can never become a new patch. - */ - bool is_patch() const; - - /** - * Get the patch target. Returns nullptr if the object is not a patch. - */ - const fqon_t *get_target() const; - - /** - * Return the linearization of this object and its parent objects. - */ - const std::vector &get_linearized(order_t t=LATEST_T) const; - - /** - * Register a function that will be called when this object changes in its current view. - * It is triggered when a patch is applied on this object - * or a parent object. - * The callback is registered in this object's view and will be fired as long - * as the returned ObjectNotifier was not deleted. - */ - std::shared_ptr subscribe(const update_cb_t &callback); + /** + * Default constructor for an invalid nyan::Object. + */ + Object() = default; + + // This constructor is public, but can't be invoked since the Restricted + // class is not available. We use this to be able to invoke make_shared + // within this class, but not outside of it. + Object(Object::Restricted, const fqon_t &name, const std::shared_ptr &origin) + : Object(name, origin) {}; + ~Object(); + + /** + * Get the identifier of this object (fully-qualified object name). + * + * @return Identifier of this object. + */ + const fqon_t &get_name() const; + + /** + * Get the view this object was created in. + * + * @return View of this object. + */ + const std::shared_ptr &get_view() const; + + /** + * Get the calculated member value for a given member at a given time. + * + * @param member Identifier of the member. + * @param t Time for which we want to calculate the value. + * + * @return ValueHolder with the value of the member. + */ + ValueHolder get_value(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Invokes the get_value function and then does a cast to type T. + * There's a special variant for T=nyan::Object which creates + * an object handle. + * + * TODO: either return a stored variant reference or the shared ptr of the holder + * + * @tparam Type the value is casted to. + * + * @param member Identifier of the member. + * @param t Time for which we want to calculate the value. + * + * @return Shared pointer with the value of the member. + */ + template + std::shared_ptr get(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to a number type T. + * + * @tparam Number type the value is casted to. + * @tparam Return type of the value. + * + * @param member Identifier of the member. + * @param t Time for which we want to calculate the value. + * + * @return Value of the member with type \p ret. + */ + template + ret get_number(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to int. + * + * @param member Identifier of the member. + * @param t Time for which we want to calculate the value. + * + * @return Value of the member. + */ + value_int_t get_int(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to float. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + value_float_t get_float(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to std::string. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + const std::string &get_text(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to bool. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + bool get_bool(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to std::unordered_set. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + const set_t &get_set(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to nyan::OrderedSet. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + const ordered_set_t &get_orderedset(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Casts to std::string. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + const std::string &get_file(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * Returns an Object. + * + * @param member Identifier of the member. + * @param t Time for which the value is calculated. + * + * @return Value of the member. + */ + Object get_object(const memberid_t &fqon, order_t t=LATEST_T) const; + + /** + * Get the parents of this object at a given time. + * + * @param t Time for which the parents are returned. + * + * @return Double-linked queue containing the parents of this object. + */ + const std::deque &get_parents(order_t t=LATEST_T) const; + + /** + * Test if this object has a member with a given name at a given time. + * + * @param member Identifier of the member. + * @param t Time for which the member existence is checked. + * + * @return true if the member exists for this object, else false. + */ + bool has(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * Check if this object is a child of the given parent at a given time. + * + * @param other_fqon Identifier of the suspected parent object. + * @param t Time for which the relationship is checked. + * + * @return true if the parent's identifier equals this object's + * identifier or that of any of its (transitive) parents, + * else false + */ + bool extends(fqon_t other_fqon, order_t t=LATEST_T) const; + + /** + * Get the metadata information object for this object. + * + * @return Metadata information object for this object. + */ + const ObjectInfo &get_info() const; + + /** + * Check if this object is a patch. + * + * @return true if this is a patch, else false. + */ + bool is_patch() const; + + /** + * Get the identifier of the patch target. + * + * @return Identifier of the patch target if this object is a + * patch, else nullptr. + */ + const fqon_t *get_target() const; + + /** + * Return the C3 linearization of this object at a given time. + * + * @param t Time for which the C3 linearization is calculated. + * + * @return C3 linearization of this object. + */ + const std::vector &get_linearized(order_t t=LATEST_T) const; + + /** + * Register a function that will be called when this object changes in its current view. + * It is triggered when a patch is applied on this object or a parent object. + * The callback is registered in this object's view and will be fired as long + * as the returned ObjectNotifier was not deleted. + * + * @param callback Callback function that is executed when a patch + * is applied to this object or a parent object. + * + * @return Shared pointer to the ObjectNotifier. + */ + std::shared_ptr subscribe(const update_cb_t &callback); protected: - /** - * Return the object state for a given time. - */ - const std::shared_ptr &get_raw(order_t t=LATEST_T) const; - - /** - * Calculate a member value of this object. - * This performs tree traversal for value calculations. - */ - ValueHolder calculate_value(const memberid_t &member, order_t t=LATEST_T) const; - - /** - * View the object was created from. - */ - std::shared_ptr origin; - - /** - * The name of this object. - */ - fqon_t name; + /** + * Get the object state at a given time. + * + * @param t Point in time for which the object state is retrieved. + * + * @return Shared pointer to the object state. + */ + const std::shared_ptr &get_raw(order_t t=LATEST_T) const; + + /** + * Get the calculated member value for a given member at a given time. + * + * @param member Identifier of the member. + * @param t Time for which we want to calculate the value. + * + * @return ValueHolder with the value of the member. + */ + ValueHolder calculate_value(const memberid_t &member, order_t t=LATEST_T) const; + + /** + * View the object was created from. + */ + std::shared_ptr origin; + + /** + * Identifier of the object. + */ + fqon_t name; }; // TODO: use concepts... template std::shared_ptr Object::get(const memberid_t &member, order_t t) const { - std::shared_ptr value = this->get_value(member, t).get_ptr(); - auto ret = std::dynamic_pointer_cast(value); - - if (not ret) { - throw MemberTypeError{ - this->name, - member, - util::typestring(value.get()), - util::typestring() - }; - } - - return ret; + std::shared_ptr value = this->get_value(member, t).get_ptr(); + auto ret = std::dynamic_pointer_cast(value); + + if (not ret) { + throw MemberTypeError{ + this->name, + member, + util::typestring(value.get()), + util::typestring() + }; + } + + return ret; } // TODO: use concepts... template ret Object::get_number(const memberid_t &member, order_t t) const { - return *this->get(member, t); + return *this->get(member, t); } diff --git a/nyan/object_history.cpp b/nyan/object_history.cpp index 6ce49f2..8d7d7f2 100644 --- a/nyan/object_history.cpp +++ b/nyan/object_history.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object_history.h" @@ -9,30 +9,30 @@ namespace nyan { void ObjectHistory::insert_change(const order_t time) { - auto it = this->changes.lower_bound(time); + auto it = this->changes.lower_bound(time); - // remove all newer entries - this->changes.erase(it, std::end(this->changes)); + // remove all newer entries + this->changes.erase(it, std::end(this->changes)); - auto ret = this->changes.insert(time); - if (unlikely(ret.second == false)) { - throw InternalError{"did not insert change point, it existed before"}; - } + auto ret = this->changes.insert(time); + if (unlikely(ret.second == false)) { + throw InternalError{"did not insert change point, it existed before"}; + } } std::optional ObjectHistory::last_change_before(order_t t) const { - // get the iterator to the first element greater than t - auto it = this->changes.upper_bound(t); - if (it == std::begin(this->changes)) { - // the requested ordering point is not in this history - return {}; - } + // get the iterator to the first element greater than t + auto it = this->changes.upper_bound(t); + if (it == std::begin(this->changes)) { + // the requested ordering point is not in this history + return {}; + } - // go one back, this is the item we're looking for - --it; + // go one back, this is the item we're looking for + --it; - return *it; + return *it; } } // namespace nyan diff --git a/nyan/object_history.h b/nyan/object_history.h index 5e94939..7d52933 100644 --- a/nyan/object_history.h +++ b/nyan/object_history.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -20,42 +20,43 @@ namespace nyan { */ class ObjectHistory { public: - /** - * Insert a new change record for this object. - * This only updates the change history, - * not the linearizations or the child tracking. - */ - void insert_change(const order_t t); - - /** - * Return (in_history, exact_order) for a given order. - * in_history: true if the timestamp can be used to query change - * curves at exact points of order - * exact_order: the ordering timestamp for the last matching change. - * will be 0, but invalid, if in_history is false. - * - */ - std::optional last_change_before(order_t t) const; - - // TODO: curve for value cache: memberid_t => curve - - /** - * Stores the parent linearization of this object over time. - */ - Curve> linearizations; - - /** - * Stores the direct children an object has over time. - */ - Curve> children; + /** + * Insert a new change record for this object. + * This only updates the change history, + * not the linearizations or the child tracking. + * + * @param t Time of insertion. + */ + void insert_change(const order_t t); + + /** + * Get the time of the last change before a given time. + * + * @param t Search changes before this point in time + * + * @return Time of the last change if there is one, else empty std::optional. + */ + std::optional last_change_before(order_t t) const; + + // TODO: curve for value cache: memberid_t => curve + + /** + * Stores the parent linearization of this object over time. + */ + Curve> linearizations; + + /** + * Stores the direct children an object has over time. + */ + Curve> children; protected: - /** - * History of order points where this object was modified. - * This is used to quickly find the matching order for an - * object state in the state history. - */ - std::set changes; + /** + * History of order points where this object was modified. + * This is used to quickly find the matching order for an + * object state in the state history. + */ + std::set changes; }; diff --git a/nyan/object_info.cpp b/nyan/object_info.cpp index 91834dd..14eb0eb 100644 --- a/nyan/object_info.cpp +++ b/nyan/object_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object_info.h" @@ -13,159 +13,159 @@ namespace nyan { ObjectInfo::ObjectInfo(const Location &location) - : - location{location}, - initial_patch{false} {} + : + location{location}, + initial_patch{false} {} const Location &ObjectInfo::get_location() const { - return this->location; + return this->location; } MemberInfo &ObjectInfo::add_member(const memberid_t &name, MemberInfo &&member) { - // copy the location so it's still valid if the insert fails. - Location loc = member.get_location(); + // copy the location so it's still valid if the insert fails. + Location loc = member.get_location(); - auto ret = this->member_info.insert({name, std::move(member)}); - if (ret.second == false) { - throw LangError{ - loc, - "member already in this object", - {{ret.first->second.get_location(), "first defined here"}} - }; - } + auto ret = this->member_info.insert({name, std::move(member)}); + if (ret.second == false) { + throw LangError{ + loc, + "member already in this object", + {{ret.first->second.get_location(), "first defined here"}} + }; + } - return ret.first->second; + return ret.first->second; } ObjectInfo::member_info_t &ObjectInfo::get_members() { - return this->member_info; + return this->member_info; } const ObjectInfo::member_info_t &ObjectInfo::get_members() const { - return this->member_info; + return this->member_info; } const MemberInfo *ObjectInfo::get_member(const memberid_t &name) const { - auto it = this->member_info.find(name); + auto it = this->member_info.find(name); - if (it == std::end(this->member_info)) { - return nullptr; - } + if (it == std::end(this->member_info)) { + return nullptr; + } - return &it->second; + return &it->second; } bool ObjectInfo::is_patch() const { - return this->patch_info.get() != nullptr; + return this->patch_info.get() != nullptr; } bool ObjectInfo::is_initial_patch() const { - return this->initial_patch; + return this->initial_patch; } PatchInfo &ObjectInfo::add_patch(const std::shared_ptr &info, bool initial) { - this->initial_patch = initial; - this->patch_info = info; - return *this->patch_info.get(); + this->initial_patch = initial; + this->patch_info = info; + return *this->patch_info.get(); } const std::shared_ptr &ObjectInfo::get_patch() const { - return this->patch_info; + return this->patch_info; } void ObjectInfo::add_inheritance_change(InheritanceChange &&change) { - this->inheritance_change.push_back(std::move(change)); + this->inheritance_change.push_back(std::move(change)); } const std::vector &ObjectInfo::get_inheritance_change() const { - return this->inheritance_change; + return this->inheritance_change; } void ObjectInfo::set_linearization(std::vector &&lin) { - this->initial_linearization = std::move(lin); + this->initial_linearization = std::move(lin); } const std::vector &ObjectInfo::get_linearization() const { - return this->initial_linearization; + return this->initial_linearization; } void ObjectInfo::set_children(std::unordered_set &&children) { - this->initial_children = std::move(children); + this->initial_children = std::move(children); } const std::unordered_set &ObjectInfo::get_children() const { - return this->initial_children; + return this->initial_children; } std::string ObjectInfo::str() const { - std::ostringstream builder; + std::ostringstream builder; - builder << "ObjectInfo"; + builder << "ObjectInfo"; - if (this->is_patch()) { - builder << " " << this->patch_info->str(); - } + if (this->is_patch()) { + builder << " " << this->patch_info->str(); + } - if (this->inheritance_change.size() > 0) { - builder << " ["; + if (this->inheritance_change.size() > 0) { + builder << " ["; - bool liststart = true; - for (auto &change : this->inheritance_change) { - if (not liststart) { - builder << ", "; - } else { - liststart = false; - } + bool liststart = true; + for (auto &change : this->inheritance_change) { + if (not liststart) { + builder << ", "; + } else { + liststart = false; + } - switch (change.get_type()) { - case inher_change_t::ADD_FRONT: - builder << change.get_target() << "+"; - break; + switch (change.get_type()) { + case inher_change_t::ADD_FRONT: + builder << change.get_target() << "+"; + break; - case inher_change_t::ADD_BACK: - builder << "+" << change.get_target(); - break; + case inher_change_t::ADD_BACK: + builder << "+" << change.get_target(); + break; - default: - throw InternalError{"unknown inheritance change type"}; - } - } + default: + throw InternalError{"unknown inheritance change type"}; + } + } - builder << "]"; - } + builder << "]"; + } - builder << ":" << std::endl; + builder << ":" << std::endl; - if (this->member_info.size() == 0) { - builder << " [no members]" << std::endl; - } + if (this->member_info.size() == 0) { + builder << " [no members]" << std::endl; + } - for (auto &it : this->member_info) { - const auto &memberid = it.first; - const auto &memberinfo = it.second; + for (auto &it : this->member_info) { + const auto &memberid = it.first; + const auto &memberinfo = it.second; - builder << " -> " << memberid; - builder << " : " << memberinfo.str() << std::endl; - } + builder << " -> " << memberid; + builder << " : " << memberinfo.str() << std::endl; + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/object_info.h b/nyan/object_info.h index 65e9672..1ada171 100644 --- a/nyan/object_info.h +++ b/nyan/object_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -24,75 +24,174 @@ class State; */ class ObjectInfo { public: - using member_info_t = std::unordered_map; - - explicit ObjectInfo(const Location &location); - ~ObjectInfo() = default; - - const Location &get_location() const; - - MemberInfo &add_member(const memberid_t &name, - MemberInfo &&member); - - member_info_t &get_members(); - const member_info_t &get_members() const; - - const MemberInfo *get_member(const memberid_t &name) const; - - PatchInfo &add_patch(const std::shared_ptr &info, bool initial); - const std::shared_ptr &get_patch() const; - - void add_inheritance_change(InheritanceChange &&change); - const std::vector &get_inheritance_change() const; - - void set_linearization(std::vector &&lin); - const std::vector &get_linearization() const; - - void set_children(std::unordered_set &&children); - const std::unordered_set &get_children() const; - - bool is_patch() const; - bool is_initial_patch() const; - - std::string str() const; + using member_info_t = std::unordered_map; + + explicit ObjectInfo(const Location &location); + ~ObjectInfo() = default; + + /** + * Get the position of this object in a file. + * + * @return Location of the object. + */ + const Location &get_location() const; + + /** + * Add metadata information for a member. + * + * @param name Identifier of the member. + * @param obj_info MemberInfo with metadata information. + * + * @return The stored metadata information object. + */ + MemberInfo &add_member(const memberid_t &name, + MemberInfo &&member); + + /** + * Get the all metadata information objects for members + * stored in this object. + * + * @return Map of metadata information objects by member identifier. + */ + member_info_t &get_members(); + + /** + * Get the all metadata information objects for members + * stored in this object. + * + * @return Map of metadata information objects by member identifier. + */ + const member_info_t &get_members() const; + + /** + * Get the the metadata information object for a member. + * + * @param name Identifier of the member. + * + * @return MemberInfo with metadata information if the member is + * in the object, else nullptr. + */ + const MemberInfo *get_member(const memberid_t &name) const; + + /** + * Add metadata information for a patch if this object is one. + * + * @param info Shared pointer to the metadata information object for the patch. + * @param initial Set to true if this object is the initial patch definition, false if the + * object inherits from a patch. + * + * @return The stored metadata information object. + */ + PatchInfo &add_patch(const std::shared_ptr &info, bool initial); + + /** + * Get the the patch metadata information object for this object. + * + * @return Shared pointer to the metadata information object for the patch. + */ + const std::shared_ptr &get_patch() const; + + /** + * Add an inheritance change to the patch target if this is a patch. + * + * @param change Inheritance change. + */ + void add_inheritance_change(InheritanceChange &&change); + + /** + * Get the list of inheritance changes to the patch target if this is a patch. + * + * @return List of inheritance changes made by this patch. + */ + const std::vector &get_inheritance_change() const; + + /** + * Set the initial initialization of the object at load time. + * + * @param lin C3 linearization of the object as a list of identifiers. + */ + void set_linearization(std::vector &&lin); + + /** + * Get the initial initialization of the object at load time. + * + * @return C3 linearization of the object as a list of identifiers. + */ + const std::vector &get_linearization() const; + + /** + * Set the initial children of the object at load time. + * + * @param children List of initial children of the object. + */ + void set_children(std::unordered_set &&children); + + /** + * Get the initial direct children of the object at load time. + * + * @return List of initial children of the object. + */ + const std::unordered_set &get_children() const; + + /** + * Check if the object is a patch. + * + * @return true if the object is a patch, else false. + */ + bool is_patch() const; + + /** + * Check if the object is an initial patch, i.e. it is not + * a patch by inheritance. + * + * @return true if the object is a initial patch, else false. + */ + bool is_initial_patch() const; + + /** + * Get the string representation of the metadata information. + * + * @return String representation of the metadata information. + */ + std::string str() const; protected: - /** - * Location where the object was defined. - */ - Location location; - - /** - * Is this object an initial patch? - * It is one when it was declared with . - * Otherwise we just link to the parent that does. - */ - bool initial_patch; - - /** - * Patch target and modification information. - */ - std::shared_ptr patch_info; - - /** - * List of objects to add to the patch target. - */ - std::vector inheritance_change; - - /** - * Maps members to their information. - */ - member_info_t member_info; - - /** - * Linearizations for the object when it was initially loaded. - */ - std::vector initial_linearization; - - /** - * Direct children of the object at load time. - */ - std::unordered_set initial_children; + /** + * Location where the object was defined. + */ + Location location; + + /** + * Determines whether this object was defined as a patch. + * It is one when it was declared with . + * Otherwise we just link to the parent that does. + */ + bool initial_patch; + + /** + * Patch target and modification information. + */ + std::shared_ptr patch_info; + + /** + * List of objects to add to the patch target. + */ + std::vector inheritance_change; + + /** + * Map of member metadata information by their identifier. + */ + member_info_t member_info; + + /** + * Linearizations for the object at load time. + */ + std::vector initial_linearization; + + /** + * Direct children of the object at load time. + */ + std::unordered_set initial_children; }; diff --git a/nyan/object_notifier.cpp b/nyan/object_notifier.cpp index e2b7c68..c29c674 100644 --- a/nyan/object_notifier.cpp +++ b/nyan/object_notifier.cpp @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object_notifier.h" @@ -8,31 +8,31 @@ namespace nyan { ObjectNotifierHandle::ObjectNotifierHandle(const update_cb_t &func) - : - func{func} {} + : + func{func} {} void ObjectNotifierHandle::fire(order_t t, const fqon_t &fqon, const ObjectState &state) const { - this->func(t, fqon, state); + this->func(t, fqon, state); } ObjectNotifier::ObjectNotifier(const fqon_t &fqon, const update_cb_t &func, const std::shared_ptr &view) - : - fqon{fqon}, - view{view}, - handle{std::make_shared(func)} {} + : + fqon{fqon}, + view{view}, + handle{std::make_shared(func)} {} ObjectNotifier::~ObjectNotifier() { - this->view->deregister_notifier(this->fqon, this->handle); + this->view->deregister_notifier(this->fqon, this->handle); } const std::shared_ptr &ObjectNotifier::get_handle() const { - return this->handle; + return this->handle; } diff --git a/nyan/object_notifier.h b/nyan/object_notifier.h index 34efe57..84df290 100644 --- a/nyan/object_notifier.h +++ b/nyan/object_notifier.h @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -17,43 +17,55 @@ class View; */ class ObjectNotifierHandle { public: - ObjectNotifierHandle(const update_cb_t &func); + ObjectNotifierHandle(const update_cb_t &func); - void fire(order_t t, const fqon_t &fqon, const ObjectState &state) const; + /** + * Calls the user provided function of the notifier. + * + * @param t Time of update. + * @param fqon Identifier of the updated object. + * @param state New object state. + */ + void fire(order_t t, const fqon_t &fqon, const ObjectState &state) const; protected: - /** - * The user function which is called when the object is changed. - */ - update_cb_t func; + /** + * The user function which is called when the object is changed. + */ + update_cb_t func; }; class ObjectNotifier { public: - ObjectNotifier(const fqon_t &fqon, - const update_cb_t &func, - const std::shared_ptr &view); - ~ObjectNotifier(); + ObjectNotifier(const fqon_t &fqon, + const update_cb_t &func, + const std::shared_ptr &view); + ~ObjectNotifier(); - const std::shared_ptr &get_handle() const; + /** + * Get the callback handle for the object notifier. + * + * @return Shared pointer to the ObjectNotifierHandle. + */ + const std::shared_ptr &get_handle() const; protected: - /** - * Which object the notifier is for. - */ - fqon_t fqon; - - /** - * View this notifier is active in. - */ - std::shared_ptr view; - - /** - * Stores the actual callback handle. - */ - std::shared_ptr handle; + /** + * Which object the notifier is for. + */ + fqon_t fqon; + + /** + * View this notifier is active in. + */ + std::shared_ptr view; + + /** + * Stores the actual callback handle. + */ + std::shared_ptr handle; }; diff --git a/nyan/object_notifier_types.h b/nyan/object_notifier_types.h index 753ac4b..8dafc2e 100644 --- a/nyan/object_notifier_types.h +++ b/nyan/object_notifier_types.h @@ -17,6 +17,10 @@ class ObjectState; * Is called with the change timestamp, * the updated object name, and the new nyan object state. * TODO: report the changed members (better as a separate type) + * + * @param t Time of update. + * @param fqon Identifier of the updated object. + * @param state New object state. */ using update_cb_t = std::function; diff --git a/nyan/object_state.cpp b/nyan/object_state.cpp index ab7e16b..3926885 100644 --- a/nyan/object_state.cpp +++ b/nyan/object_state.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object_state.h" @@ -15,135 +15,135 @@ namespace nyan { ObjectState::ObjectState(std::deque &&parents) - : - parents{std::move(parents)} {} + : + parents{std::move(parents)} {} void ObjectState::apply(const std::shared_ptr &mod, const ObjectInfo &mod_info, ObjectChanges &tracker) { - const auto &inher_changes = mod_info.get_inheritance_change(); - if (inher_changes.size() > 0) { - for (auto &change : inher_changes) { - - bool parent_exists = ( - std::find( - std::begin(this->parents), - std::end(this->parents), - change.get_target() - ) != std::end(this->parents) - ); - - // only add the parent if it does not exist. - // maybe we may want to relocate it in the future? - if (not parent_exists) { - switch (change.get_type()) { - case inher_change_t::ADD_FRONT: - this->parents.push_front(change.get_target()); - tracker.add_parent(change.get_target()); - break; - case inher_change_t::ADD_BACK: - this->parents.push_back(change.get_target()); - tracker.add_parent(change.get_target()); - break; - default: - throw InternalError{"unsupported inheritance change type"}; - } - } - } - } - - // change each member in this object by the member of the patch. - // other->members: maps memberid_t name => Member - for (auto &it : mod->members) { - auto search = this->members.find(it.first); - if (search == std::end(this->members)) { - // copy the member from the modification object, - // if it is a patch. - // that way a child object without the member - // can get the modifications. - if (likely(mod_info.is_patch())) { - this->members.emplace(it.first, it.second); - } - else { - throw InternalError{ - "a non-patch tried to change a nonexisting member" - }; - } - } - else { - search->second.apply(it.second); - } - - // TODO optimization: we could now calculate the resulting value! - // TODO: invalidate value cache with the change tracker - } + const auto &inher_changes = mod_info.get_inheritance_change(); + if (inher_changes.size() > 0) { + for (auto &change : inher_changes) { + + bool parent_exists = ( + std::find( + std::begin(this->parents), + std::end(this->parents), + change.get_target() + ) != std::end(this->parents) + ); + + // only add the parent if it does not exist. + // maybe we may want to relocate it in the future? + if (not parent_exists) { + switch (change.get_type()) { + case inher_change_t::ADD_FRONT: + this->parents.push_front(change.get_target()); + tracker.add_parent(change.get_target()); + break; + case inher_change_t::ADD_BACK: + this->parents.push_back(change.get_target()); + tracker.add_parent(change.get_target()); + break; + default: + throw InternalError{"unsupported inheritance change type"}; + } + } + } + } + + // change each member in this object by the member of the patch. + // other->members: maps memberid_t name => Member + for (auto &it : mod->members) { + auto search = this->members.find(it.first); + if (search == std::end(this->members)) { + // copy the member from the modification object, + // if it is a patch. + // that way a child object without the member + // can get the modifications. + if (likely(mod_info.is_patch())) { + this->members.emplace(it.first, it.second); + } + else { + throw InternalError{ + "a non-patch tried to change a nonexisting member" + }; + } + } + else { + search->second.apply(it.second); + } + + // TODO optimization: we could now calculate the resulting value! + // TODO: invalidate value cache with the change tracker + } } std::shared_ptr ObjectState::copy() const { - return std::make_shared(*this); + return std::make_shared(*this); } const std::deque &ObjectState::get_parents() const { - return this->parents; + return this->parents; } bool ObjectState::has(const memberid_t &name) const { - return this->members.find(name) != std::end(this->members); + return this->members.find(name) != std::end(this->members); } Member *ObjectState::get(const memberid_t &name) { - auto it = this->members.find(name); - if (it == std::end(this->members)) { - return nullptr; - } - return &it->second; + auto it = this->members.find(name); + if (it == std::end(this->members)) { + return nullptr; + } + return &it->second; } // Thanks C++, always redundancy free! const Member *ObjectState::get(const memberid_t &name) const { - auto it = this->members.find(name); - if (it == std::end(this->members)) { - return nullptr; - } - return &it->second; + auto it = this->members.find(name); + if (it == std::end(this->members)) { + return nullptr; + } + return &it->second; } const std::unordered_map &ObjectState::get_members() const { - return this->members; + return this->members; } std::string ObjectState::str() const { - std::ostringstream builder; + std::ostringstream builder; - builder << "ObjectState(" - << util::strjoin(", ", this->parents) - << ")" - << std::endl; + builder << "ObjectState(" + << util::strjoin(", ", this->parents) + << ")" + << std::endl; - if (this->members.size() == 0) { - builder << " [no members]" << std::endl; - } + if (this->members.size() == 0) { + builder << " [no members]" << std::endl; + } - for (auto &it : this->members) { - builder << " " << it.first - << " -> " << it.second.str() << std::endl; - } + for (auto &it : this->members) { + builder << " " << it.first + << " -> " << it.second.str() << std::endl; + } - return builder.str(); + return builder.str(); } void ObjectState::set_members(std::unordered_map &&members) { - this->members = std::move(members); + this->members = std::move(members); } } // namespace nyan diff --git a/nyan/object_state.h b/nyan/object_state.h index d0521d2..08bb379 100644 --- a/nyan/object_state.h +++ b/nyan/object_state.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -18,49 +18,98 @@ class ObjectInfo; * Single object state storage. */ class ObjectState { - friend class Database; + friend class Database; public: - /** - * Creation of an initial object state. - */ - ObjectState(std::deque &&parents); - - /** - * Patch application. - */ - void apply(const std::shared_ptr &other, - const ObjectInfo &mod_info, - ObjectChanges &tracker); - - std::shared_ptr copy() const; - - const std::deque &get_parents() const; - - bool has(const memberid_t &name) const; - Member *get(const memberid_t &name); - const Member *get(const memberid_t &name) const; - const std::unordered_map &get_members() const; - - std::string str() const; + /** + * Creation of an initial object state. + */ + ObjectState(std::deque &&parents); + + /** + * Apply changes to this object with a patch. + * + * @param mod Shared pointer to the patch modifying this object. + * @param mod_info Metadata information object of the patch. + * @param tracker Tracker for changes to this object. + */ + void apply(const std::shared_ptr &mod, + const ObjectInfo &mod_info, + ObjectChanges &tracker); + + /** + * Copy this object state. + * + * @return Shared pointer to the copy of the object state. + */ + std::shared_ptr copy() const; + + /** + * Get the parents of this object. + * + * @return Double-ended queue with identifiers for the parent objects. + */ + const std::deque &get_parents() const; + + /** + * Check if the object has a member with a given identifier. + * + * @param name Identifier of the member. + * + * @return true if the object has a member with the identifier, else false. + */ + bool has(const memberid_t &name) const; + + /** + * Get the pointer to a member with a given identifier. + * + * @param name Identifier of the member. + * + * @return Pointer to the member if the object has this member, else nullptr. + */ + Member *get(const memberid_t &name); + + /** + * Get the pointer to a member with a given identifier. + * + * @param name Identifier of the member. + * + * @return Pointer to the member object if the object has this member, else nullptr. + */ + const Member *get(const memberid_t &name) const; + + /** + * Get the members of this object. + * + * @return Map of the member objects by member identifer. + */ + const std::unordered_map &get_members() const; + + /** + * Get the string representation of this object state. + * + * @return String representation of this object state. + */ + std::string str() const; private: - /** - * Replace the member map. - * Used for creating initial object states. - */ - void set_members(std::unordered_map &&members); - - /** - * Parent objects. - */ - std::deque parents; - - /** - * Member storage. - */ - std::unordered_map members; - - // The object location is stored in the metainfo-database. + /** + * Replace the member map. Used for creating initial object states. + * + * @param members Map of the member objects by member identifer. + */ + void set_members(std::unordered_map &&members); + + /** + * Parent objects. + */ + std::deque parents; + + /** + * Member objects storage. + */ + std::unordered_map members; + + // The object location is stored in the metainfo-database. }; } // namespace nyan diff --git a/nyan/ops.cpp b/nyan/ops.cpp index b8cf8fc..7ee31c7 100644 --- a/nyan/ops.cpp +++ b/nyan/ops.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "ops.h" @@ -12,47 +12,47 @@ const std::unordered_set no_nyan_ops; nyan_op op_from_string(const std::string &str) { - static const std::unordered_map str_to_op{ - {"=", nyan_op::ASSIGN}, - {"+", nyan_op::ADD}, - {"-", nyan_op::SUBTRACT}, - {"*", nyan_op::MULTIPLY}, - {"/", nyan_op::DIVIDE}, - {"+=", nyan_op::ADD_ASSIGN}, - {"-=", nyan_op::SUBTRACT_ASSIGN}, - {"*=", nyan_op::MULTIPLY_ASSIGN}, - {"/=", nyan_op::DIVIDE_ASSIGN}, - {"|=", nyan_op::UNION_ASSIGN}, - {"&=", nyan_op::INTERSECT_ASSIGN}, - }; - - auto it = str_to_op.find(str); - - if (it == std::end(str_to_op)) { - return nyan_op::INVALID; - } else { - return it->second; - } + static const std::unordered_map str_to_op{ + {"=", nyan_op::ASSIGN}, + {"+", nyan_op::ADD}, + {"-", nyan_op::SUBTRACT}, + {"*", nyan_op::MULTIPLY}, + {"/", nyan_op::DIVIDE}, + {"+=", nyan_op::ADD_ASSIGN}, + {"-=", nyan_op::SUBTRACT_ASSIGN}, + {"*=", nyan_op::MULTIPLY_ASSIGN}, + {"/=", nyan_op::DIVIDE_ASSIGN}, + {"|=", nyan_op::UNION_ASSIGN}, + {"&=", nyan_op::INTERSECT_ASSIGN}, + }; + + auto it = str_to_op.find(str); + + if (it == std::end(str_to_op)) { + return nyan_op::INVALID; + } else { + return it->second; + } } nyan_op op_from_token(const Token &token) { - if (token.type == token_type::OPERATOR) { - return op_from_string(token.get()); - } - else { - throw ASTError("expected operator, but got", token); - } + if (token.type == token_type::OPERATOR) { + return op_from_string(token.get()); + } + else { + throw ASTError("expected operator, but got", token); + } } Operator::Operator(const Token &token) - : - op{op_from_token(token)} {} + : + op{op_from_token(token)} {} const nyan_op &Operator::get() { - return this->op; + return this->op; } diff --git a/nyan/ops.h b/nyan/ops.h index ea981b4..a390004 100644 --- a/nyan/ops.h +++ b/nyan/ops.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -15,18 +15,18 @@ class Token; * Operation identifiers for all builtin member types */ enum class nyan_op { - INVALID, - ADD, - ADD_ASSIGN, - ASSIGN, - DIVIDE, - DIVIDE_ASSIGN, - INTERSECT_ASSIGN, - MULTIPLY, - MULTIPLY_ASSIGN, - SUBTRACT, - SUBTRACT_ASSIGN, - UNION_ASSIGN, + INVALID, + ADD, + ADD_ASSIGN, + ASSIGN, + DIVIDE, + DIVIDE_ASSIGN, + INTERSECT_ASSIGN, + MULTIPLY, + MULTIPLY_ASSIGN, + SUBTRACT, + SUBTRACT_ASSIGN, + UNION_ASSIGN, }; @@ -34,12 +34,11 @@ enum class nyan_op { * Inheritance modification operations. */ enum class inher_change_t { - ADD_FRONT, - ADD_BACK + ADD_FRONT, + ADD_BACK }; - /** * Convenience variable to be used whenever no operation is allowed. * It comes in handy as the allowance sets are const static function @@ -51,51 +50,65 @@ extern const std::unordered_set no_nyan_ops; /** - * Return the operator + * Get a string representation of a nyan operation. + * + * @param op Nyan operation. + * + * @return Char array with the string representation of the nyan operation. */ -nyan_op op_from_string(const std::string &str); +constexpr const char *op_to_string(nyan_op op) { + switch (op) { + case nyan_op::ADD: return "+"; + case nyan_op::ADD_ASSIGN: return "+="; + case nyan_op::ASSIGN: return "="; + case nyan_op::DIVIDE: return "/"; + case nyan_op::DIVIDE_ASSIGN: return "/="; + case nyan_op::INTERSECT_ASSIGN: return "&="; + case nyan_op::INVALID: return "=INVALID="; + case nyan_op::MULTIPLY: return "*"; + case nyan_op::MULTIPLY_ASSIGN: return "*="; + case nyan_op::SUBTRACT: return "-"; + case nyan_op::SUBTRACT_ASSIGN: return "-="; + case nyan_op::UNION_ASSIGN: return "|="; + } + return "unhandled nyan_op"; +} /** - * Return the string representation of a nyan operator. + * Get a nyan operation from a string of an operator. + * + * @param str String with an operator. + * + * @return A nyan operation. */ -constexpr const char *op_to_string(nyan_op op) { - switch (op) { - case nyan_op::ADD: return "+"; - case nyan_op::ADD_ASSIGN: return "+="; - case nyan_op::ASSIGN: return "="; - case nyan_op::DIVIDE: return "/"; - case nyan_op::DIVIDE_ASSIGN: return "/="; - case nyan_op::INTERSECT_ASSIGN: return "&="; - case nyan_op::INVALID: return "=INVALID="; - case nyan_op::MULTIPLY: return "*"; - case nyan_op::MULTIPLY_ASSIGN: return "*="; - case nyan_op::SUBTRACT: return "-"; - case nyan_op::SUBTRACT_ASSIGN: return "-="; - case nyan_op::UNION_ASSIGN: return "|="; - } - return "unhandled nyan_op"; -} +nyan_op op_from_string(const std::string &str); /** - * Create the nyan_op from a token. + * Get a nyan operation from a token of an operator. + * + * @param str Token with an operator string. + * + * @return A nyan operation. */ nyan_op op_from_token(const Token &token); /** * A nyan operator, to be created from either a token + * + * TODO: What is this? */ class Operator { public: - Operator(const Token &token); - virtual ~Operator() = default; + Operator(const Token &token); + virtual ~Operator() = default; - const nyan_op &get(); + const nyan_op &get(); protected: - const nyan_op op; + const nyan_op op; }; @@ -108,9 +121,9 @@ namespace std { */ template<> struct hash { - size_t operator ()(const nyan::nyan_op &x) const { - return static_cast(x); - } + size_t operator ()(const nyan::nyan_op &x) const { + return static_cast(x); + } }; } // namespace std diff --git a/nyan/parser.cpp b/nyan/parser.cpp index ccafe17..0071fdb 100644 --- a/nyan/parser.cpp +++ b/nyan/parser.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "parser.h" @@ -25,43 +25,43 @@ Parser::Parser() = default; AST Parser::parse(const std::shared_ptr &file) { - // If you are some parser junkie and I trigger your rage mode now, - // feel free to rewrite the parser or use a tool like bison. + // If you are some parser junkie and I trigger your rage mode now, + // feel free to rewrite the parser or use a tool like bison. - // tokenize input - std::vector tokens = this->tokenize(file); + // tokenize input + std::vector tokens = this->tokenize(file); - // create ast from tokens - AST ast = this->create_ast(tokens); + // create ast from tokens + AST ast = this->create_ast(tokens); - return ast; + return ast; } std::vector Parser::tokenize(const std::shared_ptr &file) const { - Lexer lexer{file}; + Lexer lexer{file}; - std::vector ret; + std::vector ret; - while (true) { - Token token = lexer.get_next_token(); - bool end = (token.type == token_type::ENDFILE); + while (true) { + Token token = lexer.get_next_token(); + bool end = (token.type == token_type::ENDFILE); - ret.push_back(std::move(token)); + ret.push_back(std::move(token)); - if (end) { - break; - } - } + if (end) { + break; + } + } - return ret; + return ret; } AST Parser::create_ast(const std::vector &tokens) const { - TokenStream token_iter{tokens}; - AST root{token_iter}; - return root; + TokenStream token_iter{tokens}; + AST root{token_iter}; + return root; } } // namespace nyan diff --git a/nyan/parser.h b/nyan/parser.h index fad586b..5f6c601 100644 --- a/nyan/parser.h +++ b/nyan/parser.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -20,70 +20,82 @@ class Object; */ class Parser { public: - Parser(); - virtual ~Parser() = default; - - /** - * Parse a file and return its AST. - */ - AST parse(const std::shared_ptr &file); + Parser(); + virtual ~Parser() = default; + + /** + * Parse a nyan file and return its AST (abstact syntax tree). + * + * @param file Shared pointer to a nyan data file. + * + * @return AST of the nyan file. + */ + AST parse(const std::shared_ptr &file); protected: - /** - * Create the token stream from a file. - */ - std::vector tokenize(const std::shared_ptr &file) const; - - /** - * Create the abstact syntax tree from a token stream. - */ - AST create_ast(const std::vector &tokens) const; + /** + * Get the token stream of a nyan file. + * + * @param file Shared pointer to a nyan data file. + * + * @return List of tokens in the file. + */ + std::vector tokenize(const std::shared_ptr &file) const; + + /** + * Create an AST (abstact syntax tree) from a token list. + * + * @param tokens List of tokens. + * + * @return AST of the token list. + */ + AST create_ast(const std::vector &tokens) const; #if 0 - /** - * Create nyan objects and place them in the database - * (which was specified in the constructor) - */ - std::vector create_objects(const AST &ast); - - /** - * Add the object inheritance to an object to be constructed - * from that AST part. - */ - void add_inheritance(Object *obj, const ASTObject &astobj) const; - - /** - * Add the patch target objects from the AST to the object. - */ - void add_patch_targets(Object *obj, const ASTObject &astobj) const ; - - /** - * Determine the types of members, optionally consult parent objects - * in the database to get the type. - */ - std::unordered_map member_type_creation(Object *obj, const ASTObject &astobj) const; - - /** - * Create member entries which can then be stored in an object. - */ - std::vector> create_members(Object *obj, const ASTObject &astobj, std::unordered_map &member_types) const; - - /** - * Create a Value from an AST member value. - * Check if the ast value can be assigned (with the given operation) - * to the member type determined already. - */ - ValueContainer create_member_value(const Type *member_type, const ASTMemberValue &astmembervalue) const; - - /** - * Create a Value from a single value token. - */ - ValueContainer value_from_value_token(const Token &value_token) const; - - /** - * Store the inheritance modifications of a patch. - */ - void inheritance_mod(Object *obj, const ASTObject &astobj) const ; + /** + * Create nyan objects and place them in the database + * (which was specified in the constructor) + */ + std::vector create_objects(const AST &ast); + + /** + * Add the object inheritance to an object to be constructed + * from that AST part. + */ + void add_inheritance(Object *obj, const ASTObject &astobj) const; + + /** + * Add the patch target objects from the AST to the object. + */ + void add_patch_targets(Object *obj, const ASTObject &astobj) const ; + + /** + * Determine the types of members, optionally consult parent objects + * in the database to get the type. + */ + std::unordered_map member_type_creation(Object *obj, const ASTObject &astobj) const; + + /** + * Create member entries which can then be stored in an object. + */ + std::vector> create_members(Object *obj, const ASTObject &astobj, std::unordered_map &member_types) const; + + /** + * Create a Value from an AST member value. + * Check if the ast value can be assigned (with the given operation) + * to the member type determined already. + */ + ValueContainer create_member_value(const Type *member_type, const ASTMemberValue &astmembervalue) const; + + /** + * Create a Value from a single value token. + */ + ValueContainer value_from_value_token(const Token &value_token) const; + + /** + * Store the inheritance modifications of a patch. + */ + void inheritance_mod(Object *obj, const ASTObject &astobj) const ; #endif }; diff --git a/nyan/patch_info.cpp b/nyan/patch_info.cpp index c3eb132..18d5f9f 100644 --- a/nyan/patch_info.cpp +++ b/nyan/patch_info.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "patch_info.h" @@ -11,20 +11,20 @@ namespace nyan { PatchInfo::PatchInfo(fqon_t &&target) - : - target{std::move(target)} {} + : + target{std::move(target)} {} const fqon_t &PatchInfo::get_target() const { - return this->target; + return this->target; } std::string PatchInfo::str() const { - std::ostringstream builder; + std::ostringstream builder; - builder << "<" << this->target << ">"; - return builder.str(); + builder << "<" << this->target << ">"; + return builder.str(); } } // namespace nyan diff --git a/nyan/patch_info.h b/nyan/patch_info.h index 86e49c2..b2ab7dc 100644 --- a/nyan/patch_info.h +++ b/nyan/patch_info.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -12,18 +12,28 @@ namespace nyan { */ class PatchInfo { public: - explicit PatchInfo(fqon_t &&target); - ~PatchInfo() = default; - - const fqon_t &get_target() const; - - std::string str() const; + explicit PatchInfo(fqon_t &&target); + ~PatchInfo() = default; + + /** + * Get the identifier of the patch target. + * + * @return Identifier of the patch target. + */ + const fqon_t &get_target() const; + + /** + * Get the string representation of the metadata information. + * + * @return String representation of the metadata information. + */ + std::string str() const; protected: - /** - * Patch target name. - */ - fqon_t target; + /** + * Identifier of the patch target. + */ + fqon_t target; }; diff --git a/nyan/state.cpp b/nyan/state.cpp index 936f0c5..c85422f 100644 --- a/nyan/state.cpp +++ b/nyan/state.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "state.h" @@ -14,55 +14,55 @@ namespace nyan { State::State(const std::shared_ptr &previous_state) - : - previous_state{previous_state} {} + : + previous_state{previous_state} {} State::State() - : - previous_state{nullptr} {} + : + previous_state{nullptr} {} const std::shared_ptr *State::get(const fqon_t &fqon) const { - auto it = this->objects.find(fqon); - if (it != std::end(this->objects)) { - return &it->second; - } - else { - return nullptr; - } + auto it = this->objects.find(fqon); + if (it != std::end(this->objects)) { + return &it->second; + } + else { + return nullptr; + } } ObjectState &State::add_object(const fqon_t &name, std::shared_ptr &&obj) { - if (unlikely(this->previous_state != nullptr)) { - throw InternalError{"can't add new object in state that is not initial."}; - } + if (unlikely(this->previous_state != nullptr)) { + throw InternalError{"can't add new object in state that is not initial."}; + } - auto ins = this->objects.insert({name, std::move(obj)}); + auto ins = this->objects.insert({name, std::move(obj)}); - if (not ins.second) { - throw InternalError{"added an already-known object to the state!"}; - } + if (not ins.second) { + throw InternalError{"added an already-known object to the state!"}; + } - return *ins.first->second; + return *ins.first->second; } void State::update(std::shared_ptr &&source_state) { - // update this state with all objects from the source state - // -> move all objects from the sourcestate into this one. - for (auto &it : source_state.get()->objects) { - auto search = this->objects.find(it.first); - if (search != std::end(this->objects)) { - search->second = std::move(it.second); - } - else { - this->objects.insert( - {std::move(it.first), std::move(it.second)} - ); - } - } + // update this state with all objects from the source state + // -> move all objects from the sourcestate into this one. + for (auto &it : source_state.get()->objects) { + auto search = this->objects.find(it.first); + if (search != std::end(this->objects)) { + search->second = std::move(it.second); + } + else { + this->objects.insert( + {std::move(it.first), std::move(it.second)} + ); + } + } } @@ -70,53 +70,53 @@ const std::shared_ptr &State::copy_object(const fqon_t &name, order_t t, std::shared_ptr &origin) { - // last known state of the object - const std::shared_ptr &source = origin->get_raw(name, t); - - if (not source) { - throw InternalError{"object copy source not found"}; - } - - // check if the object already is in this state - auto it = this->objects.find(name); - if (it == std::end(this->objects)) { - // if not, copy the source object into this state - return this->objects.emplace( - name, - source->copy() - ).first->second; - } - else { - // else, no need to copy, the object is already in this state - return it->second; - } + // last known state of the object + const std::shared_ptr &source = origin->get_raw(name, t); + + if (not source) { + throw InternalError{"object copy source not found"}; + } + + // check if the object already is in this state + auto it = this->objects.find(name); + if (it == std::end(this->objects)) { + // if not, copy the source object into this state + return this->objects.emplace( + name, + source->copy() + ).first->second; + } + else { + // else, no need to copy, the object is already in this state + return it->second; + } } const std::shared_ptr &State::get_previous_state() const { - return this->previous_state; + return this->previous_state; } const std::unordered_map> & State::get_objects() const { - return this->objects; + return this->objects; } std::string State::str() const { - std::ostringstream builder; + std::ostringstream builder; - builder << "State:" << std::endl; + builder << "State:" << std::endl; - size_t i = 0; - for (auto &it : this->objects) { - builder << "object " << i << ":" << std::endl - << it.first << " => " << it.second->str() << std::endl; - i += 1; - } + size_t i = 0; + for (auto &it : this->objects) { + builder << "object " << i << ":" << std::endl + << it.first << " => " << it.second->str() << std::endl; + i += 1; + } - return builder.str(); + return builder.str(); } } // namespace nyan diff --git a/nyan/state.h b/nyan/state.h index 87afc1a..165d8a7 100644 --- a/nyan/state.h +++ b/nyan/state.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -21,55 +21,86 @@ class View; */ class State { public: - State(const std::shared_ptr &previous_state); - - State(); - - /** - * Get the object with given name in this state only. - */ - const std::shared_ptr *get(const fqon_t &fqon) const; - - /** - * Add an object to the state. - * This can only be done for the initial state, i.e. there's no previous state. - * Why? The database must be filled at some point. - */ - ObjectState &add_object(const fqon_t &name, std::shared_ptr &&obj); - - /** - * Add and potentially replace the objects in the storage from the other state. - */ - void update(std::shared_ptr &&obj); - - /** - * Copy an object from origin to this state. - * If it is in this state already, don't copy it. - */ - const std::shared_ptr ©_object(const fqon_t &name, - order_t t, - std::shared_ptr &origin); - - /** - * Return the previous state. - * The ptr contains nullptr if this is an initial state. - */ - const std::shared_ptr &get_previous_state() const; - - /** - * Return the objects stored in this state. - */ - const std::unordered_map> & - get_objects() const; - - /** - * String representation of this state. - */ - std::string str() const; + State(const std::shared_ptr &previous_state); + + State(); + + /** + * Get an object state for a given object identifier name. + * + * @param fqon Identifier of the object. + * + * @return Shared pointer to the object state if it exists, else nullptr. + */ + const std::shared_ptr *get(const fqon_t &fqon) const; + + /** + * Add an object state to the database state. This can only be done for the initial + * state, i.e. there's no previous state. Why? The database must be filled + * at some point. + * + * @param fqon Identifier of the object. + * @param obj Shared pointer to the state of the object. + * + * @return ObjectState that was added to the state. + */ + ObjectState &add_object(const fqon_t &name, std::shared_ptr &&obj); + + /** + * Add and potentially replace the object states of this state by + * object states from another database state. + * + * @param Shared pointer to the other database state. + */ + void update(std::shared_ptr &&obj); + + /** + * Copy an object state from an origin view to this state. + * If it is in this state already, don't copy it. + * + * @param name Identifier of the object. + * @param t Time for the object state is retrieved. + * @param origin View from which the object state is copied. + * + * @return Shared pointer to the object state. + */ + const std::shared_ptr ©_object(const fqon_t &name, + order_t t, + std::shared_ptr &origin); + + /** + * Get the previous database state. + * + * @return Shared pointer to the previous database state, + * nullptr if this is the initial state. + */ + const std::shared_ptr &get_previous_state() const; + + /** + * Return the object states stored in this state. + * + * @return Map of shared pointers to object states by object identifier. + */ + const std::unordered_map> & + get_objects() const; + + /** + * Get the string representation of this state. + * + * @return String representation of this state. + */ + std::string str() const; private: - std::unordered_map> objects; - std::shared_ptr previous_state; + /** + * Object states in the database state. + */ + std::unordered_map> objects; + + /** + * Previous state. + */ + std::shared_ptr previous_state; }; } // namespace nyan diff --git a/nyan/state_history.cpp b/nyan/state_history.cpp index a7e5465..f49213f 100644 --- a/nyan/state_history.cpp +++ b/nyan/state_history.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "state_history.h" @@ -12,76 +12,76 @@ namespace nyan { StateHistory::StateHistory(const std::shared_ptr &base) { - // create new empty state to work on at the beginning. - this->insert( - std::make_shared(base->get_state()), - DEFAULT_T - ); + // create new empty state to work on at the beginning. + this->insert( + std::make_shared(base->get_state()), + DEFAULT_T + ); } const std::shared_ptr &StateHistory::get_state(order_t t) const { - return this->history.at(t); + return this->history.at(t); } const std::shared_ptr &StateHistory::get_state_before(order_t t) const { - return this->history.at(t)->get_previous_state(); + return this->history.at(t)->get_previous_state(); } const std::shared_ptr *StateHistory::get_state_exact(order_t t) const { - return this->history.at_exact(t); + return this->history.at_exact(t); } const std::shared_ptr *StateHistory::get_obj_state(const fqon_t &fqon, order_t t) const { - // get the object history - const ObjectHistory *obj_history = this->get_obj_history(fqon); + // get the object history + const ObjectHistory *obj_history = this->get_obj_history(fqon); - // object isn't recorded in this state history - if (obj_history == nullptr) { - return nullptr; - } + // object isn't recorded in this state history + if (obj_history == nullptr) { + return nullptr; + } - std::optional order = obj_history->last_change_before(t); + std::optional order = obj_history->last_change_before(t); - if (not order) { - // the change is earlier than what is recorded in this history. - return nullptr; - } + if (not order) { + // the change is earlier than what is recorded in this history. + return nullptr; + } - // now we know the time in history the object was changed - const std::shared_ptr *state = this->history.at_exact(*order); - if (unlikely(state == nullptr)) { - throw InternalError{"no history record at change point"}; - } + // now we know the time in history the object was changed + const std::shared_ptr *state = this->history.at_exact(*order); + if (unlikely(state == nullptr)) { + throw InternalError{"no history record at change point"}; + } - const std::shared_ptr *obj_state = (*state)->get(fqon); - if (unlikely(state == nullptr)) { - throw InternalError{"object state not found at change point"}; - } + const std::shared_ptr *obj_state = (*state)->get(fqon); + if (unlikely(state == nullptr)) { + throw InternalError{"object state not found at change point"}; + } - return obj_state; + return obj_state; } void StateHistory::insert(std::shared_ptr &&new_state, order_t t) { - // record the changes. - for (const auto &it : new_state->get_objects()) { - ObjectHistory &obj_history = this->get_create_obj_history(it.first); - obj_history.insert_change(t); - } + // record the changes. + for (const auto &it : new_state->get_objects()) { + ObjectHistory &obj_history = this->get_create_obj_history(it.first); + obj_history.insert_change(t); + } - // drop all later changes - this->history.insert_drop(t, std::move(new_state)); + // drop all later changes + this->history.insert_drop(t, std::move(new_state)); } void StateHistory::insert_linearization(std::vector &&ins, order_t t) { - const auto &obj = ins.at(0); + const auto &obj = ins.at(0); - this->get_create_obj_history(obj).linearizations.insert_drop(t, std::move(ins)); + this->get_create_obj_history(obj).linearizations.insert_drop(t, std::move(ins)); } @@ -89,24 +89,24 @@ const std::vector & StateHistory::get_linearization(const fqon_t &obj, order_t t, const MetaInfo &meta_info) const { - const ObjectHistory *obj_hist = this->get_obj_history(obj); - if (obj_hist != nullptr) { - if (not obj_hist->linearizations.empty()) { - auto ret = obj_hist->linearizations.at_find(t); + const ObjectHistory *obj_hist = this->get_obj_history(obj); + if (obj_hist != nullptr) { + if (not obj_hist->linearizations.empty()) { + auto ret = obj_hist->linearizations.at_find(t); - if (ret != nullptr) { - return *ret; - } - } - } + if (ret != nullptr) { + return *ret; + } + } + } - // otherwise, the lin is only stored in the database. - const ObjectInfo *obj_info = meta_info.get_object(obj); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object not found in metainfo"}; - } + // otherwise, the lin is only stored in the database. + const ObjectInfo *obj_info = meta_info.get_object(obj); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object not found in metainfo"}; + } - return obj_info->get_linearization(); + return obj_info->get_linearization(); } @@ -114,7 +114,7 @@ void StateHistory::insert_children(const fqon_t &obj, std::unordered_set &&ins, order_t t) { - this->get_create_obj_history(obj).children.insert_drop(t, std::move(ins)); + this->get_create_obj_history(obj).children.insert_drop(t, std::move(ins)); } @@ -122,61 +122,61 @@ const std::unordered_set & StateHistory::get_children(const fqon_t &obj, order_t t, const MetaInfo &meta_info) const { - // first try the obj_history - const ObjectHistory *obj_hist = this->get_obj_history(obj); - if (obj_hist != nullptr) { - if (not obj_hist->children.empty()) { - auto ret = obj_hist->children.at_find(t); - - if (ret != nullptr) { - return *ret; - } - } - } - - // otherwise, the lin is only stored in the database. - const ObjectInfo *obj_info = meta_info.get_object(obj); - if (unlikely(obj_info == nullptr)) { - throw InternalError{"object not found in metainfo"}; - } - - return obj_info->get_children(); + // first try the obj_history + const ObjectHistory *obj_hist = this->get_obj_history(obj); + if (obj_hist != nullptr) { + if (not obj_hist->children.empty()) { + auto ret = obj_hist->children.at_find(t); + + if (ret != nullptr) { + return *ret; + } + } + } + + // otherwise, the lin is only stored in the database. + const ObjectInfo *obj_info = meta_info.get_object(obj); + if (unlikely(obj_info == nullptr)) { + throw InternalError{"object not found in metainfo"}; + } + + return obj_info->get_children(); } ObjectHistory *StateHistory::get_obj_history(const fqon_t &obj) { - auto it = this->object_obj_hists.find(obj); - if (it != std::end(this->object_obj_hists)) { - return &it->second; - } - else { - return nullptr; - } + auto it = this->object_obj_hists.find(obj); + if (it != std::end(this->object_obj_hists)) { + return &it->second; + } + else { + return nullptr; + } } const ObjectHistory *StateHistory::get_obj_history(const fqon_t &obj) const { - auto it = this->object_obj_hists.find(obj); - if (it != std::end(this->object_obj_hists)) { - return &it->second; - } - else { - return nullptr; - } + auto it = this->object_obj_hists.find(obj); + if (it != std::end(this->object_obj_hists)) { + return &it->second; + } + else { + return nullptr; + } } ObjectHistory &StateHistory::get_create_obj_history(const fqon_t &obj) { - auto it = this->object_obj_hists.find(obj); - if (it != std::end(this->object_obj_hists)) { - return it->second; - } - else { - // create new obj_history entry. - return this->object_obj_hists.emplace( - obj, ObjectHistory{} - ).first->second; - } + auto it = this->object_obj_hists.find(obj); + if (it != std::end(this->object_obj_hists)) { + return it->second; + } + else { + // create new obj_history entry. + return this->object_obj_hists.emplace( + obj, ObjectHistory{} + ).first->second; + } } } // namespace nyan diff --git a/nyan/state_history.h b/nyan/state_history.h index 81e3704..407fde3 100644 --- a/nyan/state_history.h +++ b/nyan/state_history.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -23,48 +23,136 @@ class State; */ class StateHistory { public: - StateHistory(const std::shared_ptr &base); - - /** return the state for t or if t doesn't exist, the latest state before that */ - const std::shared_ptr &get_state(order_t t) const; - - /** return the latest state before t, that is if t exists, the previous one. */ - const std::shared_ptr &get_state_before(order_t t) const; - - /** return the state at exactly t, which is nullptr if there's no state at t */ - const std::shared_ptr *get_state_exact(order_t t) const; - - /** - * find the latest object state for a given object at t. - * if there's no object state at t, take the latest state before t. - */ - const std::shared_ptr *get_obj_state(const fqon_t &fqon, order_t t) const; - - void insert(std::shared_ptr &&new_state, order_t t); - - void insert_linearization(std::vector &&ins, order_t t); - const std::vector &get_linearization(const fqon_t &obj, order_t t, - const MetaInfo &meta_info) const; - - void insert_children(const fqon_t &obj, std::unordered_set &&ins, order_t t); - const std::unordered_set &get_children(const fqon_t &obj, order_t t, - const MetaInfo &meta_info) const; + StateHistory(const std::shared_ptr &base); + + /** + * Get the latest database state at or after a given time. + * + * @param t Time for which the state is retrieved. + * + * @return Shared pointer to State at time \p t if it exists, else the next state after that. + */ + const std::shared_ptr &get_state(order_t t) const; + + /** + * Get the latest database state before a given time. + * + * @param t Time for which the state is retrieved. + * + * @return Shared pointer to State before time \p t . + */ + const std::shared_ptr &get_state_before(order_t t) const; + + /** + * Get the database state at a given time. + * + * @param t Time for which the state is retrieved. + * + * @return Shared pointer to State at time \p t if it exists, else nullptr.. + */ + const std::shared_ptr *get_state_exact(order_t t) const; + + /** + * Get an object state at a given time. + * + * @param fqon Identifier of the object. + * @param t Time for which the object state is retrieved. + * + * @return Shared pointer to the ObjectState at time \p t if + * it exists, else the next state before that. + */ + const std::shared_ptr *get_obj_state(const fqon_t &fqon, order_t t) const; + + /** + * Record all changes of a new state in the history. + * + * @param new_state New state in the database. + * @param t Time of insertion. + */ + void insert(std::shared_ptr &&new_state, order_t t); + + /** + * Record a change to the linearization of an object in its history. + * + * @param ins New linearization of the object. The first element in + * the list is also the identifier of the object. + * @param t Time of insertion. + */ + void insert_linearization(std::vector &&ins, order_t t); + + /** + * Get the linearization of an object at a given time. + * + * @param obj Identifier of the object. + * @param t Time for which the object linearization is retrieved. + * @param meta_info Metadata information of the database. + * + * @return C3 linearization of the object. + */ + const std::vector &get_linearization(const fqon_t &obj, order_t t, + const MetaInfo &meta_info) const; + + /** + * Record a change to the children of an object in its history. + * + * @param obj Identifier of the object. + * @param ins New children of the object. + * @param t Time of insertion. + */ + void insert_children(const fqon_t &obj, std::unordered_set &&ins, order_t t); + + /** + * Get the children of an object at a given time. + * + * @param obj Identifier of the object. + * @param t Time for which the object children are retrieved. + * @param meta_info Metadata information of the database. + * + * @return List of children of the object. + */ + const std::unordered_set &get_children(const fqon_t &obj, order_t t, + const MetaInfo &meta_info) const; protected: - ObjectHistory *get_obj_history(const fqon_t &obj); - const ObjectHistory *get_obj_history(const fqon_t &obj) const; - ObjectHistory &get_create_obj_history(const fqon_t &obj); - - /** - * Storage of states over time. - */ - Curve> history; - - /** - * Information history for each object. - * Optimizes searches in the history. - */ - std::unordered_map object_obj_hists; + + /** + * Get the object history an an object in the database. + * + * @param obj Identifier of the object. + * + * @return Pointer to the ObjectHistory of the object if it exists, else nullptr. + */ + ObjectHistory *get_obj_history(const fqon_t &obj); + + /** + * Get the object history an an object in the database. + * + * @param obj Identifier of the object. + * + * @return Pointer to the ObjectHistory of the object if it exists, else nullptr. + */ + const ObjectHistory *get_obj_history(const fqon_t &obj) const; + + /** + * Get the object history an an object in the database or create it if + * it doesn't exist. + * + * @param obj Identifier of the object. + * + * @return Pointer to the ObjectHistory of the object. + */ + ObjectHistory &get_create_obj_history(const fqon_t &obj); + + /** + * Storage of states over time. + */ + Curve> history; + + /** + * Information history for each object. + * Optimizes searches in the history. + */ + std::unordered_map object_obj_hists; }; diff --git a/nyan/token.cpp b/nyan/token.cpp index f02a138..24e2378 100644 --- a/nyan/token.cpp +++ b/nyan/token.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "token.h" @@ -14,77 +14,78 @@ namespace nyan { Token::Token(const std::shared_ptr &file, int line, int line_offset, int length, token_type type) - : - location{file, line, line_offset, length}, - type{type} {} + : + location{file, line, line_offset, length}, + type{type} {} Token::Token(const std::shared_ptr &file, int line, int line_offset, int length, token_type type, const std::string &value) - : - location{file, line, line_offset, length}, - type{type}, - value{value} {} + : + location{file, line, line_offset, length}, + type{type}, + value{value} {} Token::Token() - : - type{token_type::INVALID} {} + : + type{token_type::INVALID} {} const std::string &Token::get() const { - return this->value; + return this->value; } std::string Token::str() const { - std::ostringstream builder; - builder << "(" << this->location.get_line() << ":" - << this->location.get_line_offset() << ": " - << token_type_str(this->type); - if (this->value.size() > 0) { - builder << " '" << this->value << "'"; - } - builder << ")"; - return builder.str(); + std::ostringstream builder; + builder << "(" << this->location.get_line() << ":" + << this->location.get_line_offset() << ": " + << token_type_str(this->type); + if (this->value.size() > 0) { + builder << " '" << this->value << "'"; + } + builder << ")"; + return builder.str(); } bool Token::exists() const { - return this->get().size() > 0; + return this->get().size() > 0; } bool Token::is_endmarker() const { - switch (this->type) { - case token_type::ENDFILE: - case token_type::ENDLINE: - return true; + switch (this->type) { + case token_type::ENDFILE: + case token_type::ENDLINE: + return true; - case token_type::INVALID: - throw InternalError{"invalid token used"}; + case token_type::INVALID: + throw InternalError{"invalid token used"}; - default: - return false; - } + default: + return false; + } } bool Token::is_content() const { - switch (this->type) { - case token_type::FLOAT: - case token_type::ID: - case token_type::INT: - case token_type::STRING: - return true; - - case token_type::INVALID: - throw InternalError{"invalid token used"}; - - default: - return false; - } + switch (this->type) { + case token_type::FLOAT: + case token_type::ID: + case token_type::INF: + case token_type::INT: + case token_type::STRING: + return true; + + case token_type::INVALID: + throw InternalError{"invalid token used"}; + + default: + return false; + } } } // namespace nyan diff --git a/nyan/token.h b/nyan/token.h index f5e2454..162435c 100644 --- a/nyan/token.h +++ b/nyan/token.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -15,137 +15,188 @@ class File; * Available token types. */ enum class token_type { - AS, - AT, - COLON, - COMMA, - DEDENT, - DOT, - ENDFILE, - ENDLINE, - ELLIPSIS, - FLOAT, - FROM, - ID, - IMPORT, - INDENT, - INVALID, - INT, - LANGLE, - LBRACE, - LBRACKET, - LPAREN, - OPERATOR, - PASS, - RANGLE, - RBRACE, - RBRACKET, - RPAREN, - STRING, + AS, + AT, + BANG, + COLON, + COMMA, + DEDENT, + DOT, + ENDFILE, + ENDLINE, + ELLIPSIS, + FLOAT, + FROM, + ID, + IMPORT, + INDENT, + INF, + INT, + INVALID, + LANGLE, + LBRACE, + LBRACKET, + LPAREN, + OPERATOR, + PASS, + RANGLE, + RBRACE, + RBRACKET, + RPAREN, + STRING, }; /** * Known bracket types. */ enum class bracket_type { - PAREN, - ANGLE, - BRACKET, - BRACE, + PAREN, + ANGLE, + BRACKET, + BRACE, }; /** - * Text representations of the token types. - * Oh you, c++, such a good and comfort language. + * Get the string representation of a given token type. + * + * @param type Token type. + * + * @return String representation of the token type. */ constexpr const char *token_type_str(token_type type) { - using namespace std::string_literals; - - switch (type) { - case token_type::AS: return "as"; - case token_type::AT: return "@"; - case token_type::COLON: return "colon"; - case token_type::COMMA: return "comma"; - case token_type::DEDENT: return "dedentation"; - case token_type::DOT: return "dot"; - case token_type::ELLIPSIS: return "ellipsis"; - case token_type::ENDFILE: return "end of file"; - case token_type::ENDLINE: return "end of line"; - case token_type::FLOAT: return "float"; - case token_type::FROM: return "from"; - case token_type::ID: return "identifier"; - case token_type::IMPORT: return "import"; - case token_type::INDENT: return "indentation"; - case token_type::INT: return "int"; - case token_type::INVALID: return "invalid"; - case token_type::LANGLE: return "'<'"; - case token_type::LBRACE: return "'{'"; - case token_type::LBRACKET: return "'['"; - case token_type::LPAREN: return "'('"; - case token_type::OPERATOR: return "operator"; - case token_type::PASS: return "pass"; - case token_type::RANGLE: return "'>'"; - case token_type::RBRACE: return "'}'"; - case token_type::RBRACKET: return "']'"; - case token_type::RPAREN: return "')'"; - case token_type::STRING: return "string"; - } - - return "unhandled token_type"; + using namespace std::string_literals; + + switch (type) { + case token_type::AS: return "as"; + case token_type::AT: return "@"; + case token_type::BANG: return "!"; + case token_type::COLON: return "colon"; + case token_type::COMMA: return "comma"; + case token_type::DEDENT: return "dedentation"; + case token_type::DOT: return "dot"; + case token_type::ELLIPSIS: return "ellipsis"; + case token_type::ENDFILE: return "end of file"; + case token_type::ENDLINE: return "end of line"; + case token_type::FLOAT: return "float"; + case token_type::FROM: return "from"; + case token_type::ID: return "identifier"; + case token_type::IMPORT: return "import"; + case token_type::INDENT: return "indentation"; + case token_type::INF: return "inf"; + case token_type::INT: return "int"; + case token_type::INVALID: return "invalid"; + case token_type::LANGLE: return "'<'"; + case token_type::LBRACE: return "'{'"; + case token_type::LBRACKET: return "'['"; + case token_type::LPAREN: return "'('"; + case token_type::OPERATOR: return "operator"; + case token_type::PASS: return "pass"; + case token_type::RANGLE: return "'>'"; + case token_type::RBRACE: return "'}'"; + case token_type::RBRACKET: return "']'"; + case token_type::RPAREN: return "')'"; + case token_type::STRING: return "string"; + } + + return "unhandled token_type"; } /** - * Return if the given token type requires a payload storage. + * Check if the given token type requires a payload storage. * If not, then the token type is already enough information. + * + * @param type Token type. + * + * @return true if the token type requires a payload, else false. */ constexpr bool token_needs_payload(token_type type) { - switch (type) { - case token_type::FLOAT: - case token_type::ID: - case token_type::INT: - case token_type::OPERATOR: - case token_type::STRING: - return true; - default: - return false; - } + switch (type) { + case token_type::FLOAT: + case token_type::ID: + case token_type::INF: + case token_type::INT: + case token_type::OPERATOR: + case token_type::STRING: + return true; + default: + return false; + } } /** - * These are spit out by the nyan lexer. + * Tokens are generated by the nyan lexer. */ class Token { public: - Token(); - Token(const std::shared_ptr &file, - int line, - int line_offset, - int length, - token_type type); - Token(const std::shared_ptr &file, - int line, - int line_offset, - int length, - token_type type, - const std::string &value); - ~Token() = default; - - std::string str() const; - bool exists() const; - bool is_endmarker() const; - bool is_content() const; - - const std::string &get() const; - - Location location; - token_type type; + Token(); + Token(const std::shared_ptr &file, + int line, + int line_offset, + int length, + token_type type); + Token(const std::shared_ptr &file, + int line, + int line_offset, + int length, + token_type type, + const std::string &value); + ~Token() = default; + + /** + * Check if the token has a payload. + * + * @return true if a payload exists, else false. + */ + bool exists() const; + + /** + * Check if the token is an endmarker (EOL or EOF). + * + * @return true if the token is an endmarker, else false. + */ + bool is_endmarker() const; + + /** + * Check if the token type is used for storing content. + * This can be member values or object references. + * + * @return true if the token type is used for content, else false. + */ + bool is_content() const; + + /** + * Get the token payload. + * + * @return String of the token payload. + */ + const std::string &get() const; + + /** + * Get the string representation of the token. + * + * @return String representation of the token. + */ + std::string str() const; + + /** + * Location of the token in a file. + */ + Location location; + + /** + * Token type. + */ + token_type type; protected: - std::string value; + /** + * Token payload. + */ + std::string value; }; } // namespace nyan diff --git a/nyan/token_stream.cpp b/nyan/token_stream.cpp index b489b04..2b8451d 100644 --- a/nyan/token_stream.cpp +++ b/nyan/token_stream.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "token_stream.h" @@ -10,46 +10,46 @@ namespace nyan { TokenStream::TokenStream(const TokenStream::container_t &container) - : - container{container}, - iter{std::begin(container)} {} + : + container{container}, + iter{std::begin(container)} {} TokenStream::~TokenStream() = default; const TokenStream::tok_t *TokenStream::next() { - const tok_t *ret; + const tok_t *ret; - if (not this->full()) { - throw InternalError{"requested item from empty list"}; - } + if (not this->full()) { + throw InternalError{"requested item from empty list"}; + } - ret = &(*this->iter); + ret = &(*this->iter); - //std::cout << "tok: " << ret->str() << std::endl; + //std::cout << "tok: " << ret->str() << std::endl; - this->iter = std::next(this->iter); - return ret; + this->iter = std::next(this->iter); + return ret; } bool TokenStream::full() const { - return this->iter != std::end(this->container); + return this->iter != std::end(this->container); } bool TokenStream::empty() const { - return not this->full(); + return not this->full(); } void TokenStream::reinsert_last() { - if (this->iter == std::begin(this->container)) { - throw InternalError{"requested reinsert of unavailable token"}; - } + if (this->iter == std::begin(this->container)) { + throw InternalError{"requested reinsert of unavailable token"}; + } - this->iter = std::prev(this->iter); + this->iter = std::prev(this->iter); } } // namespace nyan diff --git a/nyan/token_stream.h b/nyan/token_stream.h index 22812de..eac1d12 100644 --- a/nyan/token_stream.h +++ b/nyan/token_stream.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -18,27 +18,51 @@ class Token; */ class TokenStream { public: - using tok_t = Token; - using container_t = std::vector; + using tok_t = Token; + using container_t = std::vector; - TokenStream(const container_t &container); + TokenStream(const container_t &container); - ~TokenStream(); + ~TokenStream(); - const tok_t *next(); + /** + * Advance one step in the stream and get the pointer to + * the token the streamnow points to. + * + * @return Next token in the stream. + */ + const tok_t *next(); - bool full() const; + /** + * Check if the end of the stream has been reached, i.e. there + * are no more tokens after the current token. + * + * @return true if there are no more tokens in the stream, else false. + */ + bool full() const; - bool empty() const; + /** + * Check if there are tokens left in the stream. + * + * @return true if there are more tokens in the stream, else false. + */ + bool empty() const; - /** - * Reinserts the tokens previously returned by next in reverse order. - */ - void reinsert_last(); + /** + * Reinserts the token previously returned by next(). + */ + void reinsert_last(); protected: - const container_t &container; - container_t::const_iterator iter; + /** + * List of tokens in the stream. + */ + const container_t &container; + + /** + * Iterator used for advancing/regressing in the stream. + */ + container_t::const_iterator iter; }; } // namespace nyan diff --git a/nyan/transaction.cpp b/nyan/transaction.cpp index d319e2a..a8e7a7b 100644 --- a/nyan/transaction.cpp +++ b/nyan/transaction.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "transaction.h" @@ -16,230 +16,230 @@ namespace nyan { Transaction::Transaction(order_t at, std::shared_ptr &&origin) - : - valid{true}, - at{at} { + : + valid{true}, + at{at} { - auto create_state_mod = [this] (std::shared_ptr &&view) { - StateHistory &view_history = view->get_state_history(); + auto create_state_mod = [this] (std::shared_ptr &&view) { + StateHistory &view_history = view->get_state_history(); - // use this as parent state - // might return the database initial state. - const std::shared_ptr &base_view_state = view_history.get_state_before(this->at); + // use this as parent state + // might return the database initial state. + const std::shared_ptr &base_view_state = view_history.get_state_before(this->at); - if (unlikely(base_view_state.get() == nullptr)) { - throw InternalError{"transaction base state is nullptr"}; - } + if (unlikely(base_view_state.get() == nullptr)) { + throw InternalError{"transaction base state is nullptr"}; + } - // create a state that follows the current view state. - auto new_view_state = std::make_shared(base_view_state); + // create a state that follows the current view state. + auto new_view_state = std::make_shared(base_view_state); - this->states.push_back({ - std::move(view), - std::move(new_view_state), - {} - }); - }; + this->states.push_back({ + std::move(view), + std::move(new_view_state), + {} + }); + }; - // first, perform transaction on the requested view - create_state_mod(std::move(origin)); + // first, perform transaction on the requested view + create_state_mod(std::move(origin)); - // this is actually the `origin` view, but we've moved it there. - auto &main_view = this->states.at(0).view; + // this is actually the `origin` view, but we've moved it there. + auto &main_view = this->states.at(0).view; - // recursively visit all of the view's children and their children - // lol C++ - std::function&)> recurse = - [&create_state_mod, &recurse] (const std::shared_ptr &view) { - bool view_has_stale_children = false; + // recursively visit all of the view's children and their children + // lol C++ + std::function&)> recurse = + [&create_state_mod, &recurse] (const std::shared_ptr &view) { + bool view_has_stale_children = false; - // also apply the transaction in all childs of the view. - for (auto &target_child_view_weakptr : - view->get_children()) { + // also apply the transaction in all childs of the view. + for (auto &target_child_view_weakptr : + view->get_children()) { - auto target_child_view = target_child_view_weakptr.lock(); - if (not target_child_view) { - // child view no longer there, so we skip it. - view_has_stale_children = true; - continue; - } + auto target_child_view = target_child_view_weakptr.lock(); + if (not target_child_view) { + // child view no longer there, so we skip it. + view_has_stale_children = true; + continue; + } - recurse(target_child_view); + recurse(target_child_view); - create_state_mod(std::move(target_child_view)); - } + create_state_mod(std::move(target_child_view)); + } - if (view_has_stale_children) { - view->cleanup_stale_children(); - } - }; + if (view_has_stale_children) { + view->cleanup_stale_children(); + } + }; - recurse(main_view); + recurse(main_view); } bool Transaction::add(const Object &patch) { - if (unlikely(not this->valid)) { - // TODO: throw some error? - return false; - } + if (unlikely(not this->valid)) { + // TODO: throw some error? + return false; + } - if (unlikely(not patch.is_patch())) { - return false; - } + if (unlikely(not patch.is_patch())) { + return false; + } - const auto target_ptr = patch.get_target(); - // TODO: recheck if target exists? - if (target_ptr == nullptr) { - throw InternalError{"patch somehow has no target"}; - } - const auto &target = *target_ptr; + const auto target_ptr = patch.get_target(); + // TODO: recheck if target exists? + if (target_ptr == nullptr) { + throw InternalError{"patch somehow has no target"}; + } + const auto &target = *target_ptr; - // apply the patch in each view's state - for (auto &view_state : this->states) { + // apply the patch in each view's state + for (auto &view_state : this->states) { - auto &view = view_state.view; - auto &new_state = view_state.state; - auto &tracker = view_state.changes; + auto &view = view_state.view; + auto &new_state = view_state.state; + auto &tracker = view_state.changes; - // TODO: speed up the state backtracking for finding the object + // TODO: speed up the state backtracking for finding the object - // This does not copy the object if the new state already has it. - auto &target_obj = new_state->copy_object(target, this->at, view); + // This does not copy the object if the new state already has it. + auto &target_obj = new_state->copy_object(target, this->at, view); - // apply each patch component (i.e. all the parents of the patch) - for (auto &patch_name : patch.get_linearized(this->at)) { + // apply each patch component (i.e. all the parents of the patch) + for (auto &patch_name : patch.get_linearized(this->at)) { - auto &patch_tracker = tracker.track_patch(target); + auto &patch_tracker = tracker.track_patch(target); - // apply all patch parents in order (last the patch itself) - target_obj->apply( - // TODO: use the same mechanism as above to get only parent - // obj states of base_state - view->get_raw(patch_name, this->at), - view->get_info(patch_name), - patch_tracker - ); - } + // apply all patch parents in order (last the patch itself) + target_obj->apply( + // TODO: use the same mechanism as above to get only parent + // obj states of base_state + view->get_raw(patch_name, this->at), + view->get_info(patch_name), + patch_tracker + ); + } - // TODO: linearize here so other patches can depend on that? - } + // TODO: linearize here so other patches can depend on that? + } - return true; + return true; } bool Transaction::add(const Object &/*patch*/, const Object &/*target*/) { - // TODO check if the target is a child of the original target - throw InternalError{"TODO custom patch target"}; + // TODO check if the target is a child of the original target + throw InternalError{"TODO custom patch target"}; } bool Transaction::commit() { - if (unlikely(not this->valid)) { - // TODO: throw some error? - return false; - } + if (unlikely(not this->valid)) { + // TODO: throw some error? + return false; + } - // TODO check if no other transaction was before this one. + // TODO check if no other transaction was before this one. - // merge a new state with an already existing base state - // this must be done for a transaction at a time - // where data is already stored. - this->merge_changed_states(); + // merge a new state with an already existing base state + // this must be done for a transaction at a time + // where data is already stored. + this->merge_changed_states(); - // for each view: those updates have to be performed - std::vector updates = this->generate_updates(); + // for each view: those updates have to be performed + std::vector updates = this->generate_updates(); - // now, all sanity checks are done and we can update the view! - this->update_views(std::move(updates)); + // now, all sanity checks are done and we can update the view! + this->update_views(std::move(updates)); - // TODO mark value caches dirty + // TODO mark value caches dirty - bool ret = this->valid; - this->valid = false; - return ret; + bool ret = this->valid; + this->valid = false; + return ret; } void Transaction::merge_changed_states() { - for (auto &view_state : this->states) { - auto &view = view_state.view; + for (auto &view_state : this->states) { + auto &view = view_state.view; - StateHistory &view_history = view->get_state_history(); + StateHistory &view_history = view->get_state_history(); - // new_state contains all modified objects for this view. - // base_state probably unused + // new_state contains all modified objects for this view. + // base_state probably unused - // TODO: this is not robust against other parallel transactions - const std::shared_ptr *existing = view_history.get_state_exact(this->at); + // TODO: this is not robust against other parallel transactions + const std::shared_ptr *existing = view_history.get_state_exact(this->at); - if (existing != nullptr) { - // state did exist: we have to merge it with the new state: - // the old state (which described the same time) must be updated - // with changed objects from the new state. + if (existing != nullptr) { + // state did exist: we have to merge it with the new state: + // the old state (which described the same time) must be updated + // with changed objects from the new state. - // create a copy of the base state which retains its pointers - // to object states. - auto merge_base = std::make_shared(*existing->get()); + // create a copy of the base state which retains its pointers + // to object states. + auto merge_base = std::make_shared(*existing->get()); - // replace all objects of the old state with objects from the new state - merge_base->update(std::move(view_state.state)); + // replace all objects of the old state with objects from the new state + merge_base->update(std::move(view_state.state)); - // so we now have a combined new state - view_state.state = std::move(merge_base); - } - } + // so we now have a combined new state + view_state.state = std::move(merge_base); + } + } } std::vector Transaction::generate_updates() { - std::vector updates; - - // try linearizing objects which have changed parents - // and their children - for (auto &view_state : this->states) { - auto &view = view_state.view; - auto &new_state = view_state.state; - auto &tracker = view_state.changes; - - // update to perform for this view. - view_update update; - - // from the known parent changes, find all affected children - // affected are: children of those objects which the child cache knows. - - // contains all objects whose parents changed. - std::unordered_set objs_to_linearize; - - // take a look at all the needed inheritance updates - // and generate the child tracking update from it. - update.children = this->inheritance_updates( - tracker, - view, - objs_to_linearize - ); - - try { - update.linearizations = this->relinearize_objects( - objs_to_linearize, - view, - new_state - ); - } - catch (C3Error &) { - // this error is non-fatal but aborts the transaction - this->set_error(std::current_exception()); - break; - } - - updates.push_back(std::move(update)); - } - - return updates; + std::vector updates; + + // try linearizing objects which have changed parents + // and their children + for (auto &view_state : this->states) { + auto &view = view_state.view; + auto &new_state = view_state.state; + auto &tracker = view_state.changes; + + // update to perform for this view. + view_update update; + + // from the known parent changes, find all affected children + // affected are: children of those objects which the child cache knows. + + // contains all objects whose parents changed. + std::unordered_set objs_to_linearize; + + // take a look at all the needed inheritance updates + // and generate the child tracking update from it. + update.children = this->inheritance_updates( + tracker, + view, + objs_to_linearize + ); + + try { + update.linearizations = this->relinearize_objects( + objs_to_linearize, + view, + new_state + ); + } + catch (C3Error &) { + // this error is non-fatal but aborts the transaction + this->set_error(std::current_exception()); + break; + } + + updates.push_back(std::move(update)); + } + + return updates; } @@ -248,40 +248,40 @@ Transaction::inheritance_updates(const ChangeTracker &tracker, const std::shared_ptr &view, std::unordered_set &objs_to_linearize) const { - // maps fqon => set of children fqons - view_update::child_map_t children; + // maps fqon => set of children fqons + view_update::child_map_t children; - // those objects were changed and require handling. - for (auto &it : tracker.get_object_changes()) { - auto &obj = it.first; - auto &obj_changes = it.second; + // those objects were changed and require handling. + for (auto &it : tracker.get_object_changes()) { + auto &obj = it.first; + auto &obj_changes = it.second; - // the object has new parents. - if (obj_changes.parents_update_required()) { + // the object has new parents. + if (obj_changes.parents_update_required()) { - // so we register the object at each parent as new child. - // the previous children are merged with that set later. - for (auto &parent : obj_changes.get_new_parents()) { - auto ins = children.emplace( - parent, - std::unordered_set{} - ); - ins.first->second.insert(obj); - } + // so we register the object at each parent as new child. + // the previous children are merged with that set later. + for (auto &parent : obj_changes.get_new_parents()) { + auto ins = children.emplace( + parent, + std::unordered_set{} + ); + ins.first->second.insert(obj); + } - // as this object's parents changed, - // it requires relinearization. - objs_to_linearize.insert(obj); + // as this object's parents changed, + // it requires relinearization. + objs_to_linearize.insert(obj); - // children of this object need to be relinearized as well. - const auto &obj_children = view->get_obj_children_all(obj, this->at); + // children of this object need to be relinearized as well. + const auto &obj_children = view->get_obj_children_all(obj, this->at); - objs_to_linearize.insert(std::begin(obj_children), - std::end(obj_children)); - } - } + objs_to_linearize.insert(std::begin(obj_children), + std::end(obj_children)); + } + } - return children; + return children; } @@ -290,97 +290,97 @@ Transaction::relinearize_objects(const std::unordered_set &objs_to_linea const std::shared_ptr &view, const std::shared_ptr &new_state) { - view_update::linearizations_t linearizations; - - for (auto &obj : objs_to_linearize) { - auto lin = linearize( - obj, - [this, &view, &new_state] - (const fqon_t &name) -> const ObjectState & { - - // try to use the object in the new state if it's in there - const auto &new_obj_state = new_state->get(name); - if (new_obj_state != nullptr) { - return *new_obj_state->get(); - } - - // else, get it from the already existing view. - const ObjectState *view_obj_state = view->get_raw(name, this->at).get(); - if (unlikely(view_obj_state == nullptr)) { - throw InternalError{"could not find parent object"}; - } - return *view_obj_state; - } - ); - - linearizations.push_back(std::move(lin)); - } - - return linearizations; + view_update::linearizations_t linearizations; + + for (auto &obj : objs_to_linearize) { + auto lin = linearize( + obj, + [this, &view, &new_state] + (const fqon_t &name) -> const ObjectState & { + + // try to use the object in the new state if it's in there + const auto &new_obj_state = new_state->get(name); + if (new_obj_state != nullptr) { + return *new_obj_state->get(); + } + + // else, get it from the already existing view. + const ObjectState *view_obj_state = view->get_raw(name, this->at).get(); + if (unlikely(view_obj_state == nullptr)) { + throw InternalError{"could not find parent object"}; + } + return *view_obj_state; + } + ); + + linearizations.push_back(std::move(lin)); + } + + return linearizations; } void Transaction::update_views(std::vector &&updates) { - size_t idx = 0; - for (auto &view_state : this->states) { - auto &view = view_state.view; - auto &new_state = view_state.state; - - // record the state updates in each view's history - StateHistory &view_history = view->get_state_history(); - - // insert the new state and drop later ones. - view_history.insert(std::move(new_state), this->at); - - // insert all newly calculated linearizations. - for (auto &lin : updates[idx].linearizations) { - view_history.insert_linearization(std::move(lin), this->at); - } - - // inheritance updates can generate new children for existing objects - for (auto &it : updates[idx].children) { - auto &obj = it.first; - auto &new_children = it.second; - - // merge the previous children with tne new child set - const auto &previous_children = view->get_obj_children(obj, this->at); - new_children.insert(std::begin(previous_children), - std::end(previous_children)); - - view_history.insert_children(obj, std::move(new_children), this->at); - } - - idx += 1; - } - - // now that the views were updated, we can fire the event notifications. - for (auto &view_state : this->states) { - auto &view = view_state.view; - auto &tracker = view_state.changes; - - std::unordered_set updated_objects = tracker.get_changed_objects(); - std::unordered_set affected_children; - // all children of the patched objects are also affected. - for (auto &obj : updated_objects) { - std::unordered_set children = view->get_obj_children_all(obj, this->at); - affected_children.merge(children); - } - - updated_objects.merge(affected_children); - - // TODO: if we don't want to fire for every object, but only - // for those with some members changed, we have to - // extend the ChangeTracker and track individual member updates - // (maybe only if the object is of interest). - // then we pass only the relevant objects here. - view->fire_notifications(updated_objects, this->at); - } + size_t idx = 0; + for (auto &view_state : this->states) { + auto &view = view_state.view; + auto &new_state = view_state.state; + + // record the state updates in each view's history + StateHistory &view_history = view->get_state_history(); + + // insert the new state and drop later ones. + view_history.insert(std::move(new_state), this->at); + + // insert all newly calculated linearizations. + for (auto &lin : updates[idx].linearizations) { + view_history.insert_linearization(std::move(lin), this->at); + } + + // inheritance updates can generate new children for existing objects + for (auto &it : updates[idx].children) { + auto &obj = it.first; + auto &new_children = it.second; + + // merge the previous children with tne new child set + const auto &previous_children = view->get_obj_children(obj, this->at); + new_children.insert(std::begin(previous_children), + std::end(previous_children)); + + view_history.insert_children(obj, std::move(new_children), this->at); + } + + idx += 1; + } + + // now that the views were updated, we can fire the event notifications. + for (auto &view_state : this->states) { + auto &view = view_state.view; + auto &tracker = view_state.changes; + + std::unordered_set updated_objects = tracker.get_changed_objects(); + std::unordered_set affected_children; + // all children of the patched objects are also affected. + for (auto &obj : updated_objects) { + std::unordered_set children = view->get_obj_children_all(obj, this->at); + affected_children.merge(children); + } + + updated_objects.merge(affected_children); + + // TODO: if we don't want to fire for every object, but only + // for those with some members changed, we have to + // extend the ChangeTracker and track individual member updates + // (maybe only if the object is of interest). + // then we pass only the relevant objects here. + view->fire_notifications(updated_objects, this->at); + } } void Transaction::set_error(std::exception_ptr &&exc) { - this->valid = false; - this->error = std::move(exc); + this->valid = false; + this->error = std::move(exc); } } // namespace nyan diff --git a/nyan/transaction.h b/nyan/transaction.h index fe0d28e..1b8ae14 100644 --- a/nyan/transaction.h +++ b/nyan/transaction.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -25,31 +25,40 @@ class View; */ struct view_update { - using linearizations_t = std::vector>; + using linearizations_t = std::vector>; - using child_map_t = std::unordered_map>; + using child_map_t = std::unordered_map>; - /** - * All linearizations to update because of the patches. - */ - linearizations_t linearizations; + /** + * All linearizations to update because of the patches. + */ + linearizations_t linearizations; - /** - * Maps objects to their new children. - */ - child_map_t children; + /** + * Maps objects to their new children. + */ + child_map_t children; }; /** * New information for a view. - * * The state that we're gonna build with this transaction - * * Changes done in the transaction to invalidate caches. */ struct view_state { - std::shared_ptr view; - std::shared_ptr state; - ChangeTracker changes; + /** + * View where the changes happen. + */ + std::shared_ptr view; + + /** + * State that we're gonna build with this transaction. + */ + std::shared_ptr state; + + /** + * Changes done in the transaction to invalidate caches. + */ + ChangeTracker changes; }; @@ -59,94 +68,125 @@ struct view_state { class Transaction { public: - Transaction(order_t at, std::shared_ptr &&origin); - - /** - * Add a patch to the transaction. - * Apply the patch to the target stored in the patch. - */ - bool add(const Object &obj); - - /** - * Add a patch to the transaction. - * Apply the patch to a custom target, which must be a - * child of the target stored in the patch. - */ - bool add(const Object &obj, const Object &target); - - /** - * Returns true if the transaction was successful. - */ - bool commit(); - - /** - * Return the exception that caused a transaction failure. - */ - const std::exception_ptr &get_exception() const; + Transaction(order_t at, std::shared_ptr &&origin); + + /** + * Add a patch to the transaction. Apply the patch to the target + * stored in the patch. + * + * @param obj Patch to be applied. + * + * @return true if the patch is successfully applied, else false. + */ + bool add(const Object &obj); + + /** + * Add a patch to the transaction. Apply the patch to a custom target, + * which must be a descendant of the target stored in the patch. + * + * @param obj Patch to be applied. + * @param target Target object. Must be a descendant of the target stored in the patch. + * + * @return true if the patch is successfully applied, else false. + */ + bool add(const Object &obj, const Object &target); + + /** + * Commit the transaction, i.e. update the views. + * + * @return true if the transaction was successful, else false. + */ + bool commit(); + + /** + * Get the pointer to the exception that caused a transaction failure. + * + * @return Pointer to the exception that caused a transaction failure. + */ + const std::exception_ptr &get_exception() const; protected: - /** - * Merge the new states with an existing one from the view. - */ - void merge_changed_states(); - - /** - * Generate all needed updates. - * The vector indices are mapped to the views of the - * `states` member at the bottom. - */ - std::vector generate_updates(); - - /** - * Track which parents need to be notified - * of new childs. - * Also builds up a list of objects to relinearize - * because its parents changed. - */ - view_update::child_map_t - inheritance_updates(const ChangeTracker &tracker, - const std::shared_ptr &view, - std::unordered_set &objs_to_linearize) const; - - /** - * Generate new linearizations for objects that changed. - */ - view_update::linearizations_t - relinearize_objects(const std::unordered_set &objs_to_linearize, - const std::shared_ptr &view, - const std::shared_ptr &new_state); - - - /** - * Apply the gathered state updates in all views. - * The update list is destroyed. - */ - void update_views(std::vector &&updates); - - /** - * A non-fatal exception occured, so let the transaction fail. - */ - void set_error(std::exception_ptr &&exc); - - /** - * Exception holder if something failed during the transaction. - */ - std::exception_ptr error; - - /** - * True if this transaction can be committed. - */ - bool valid; - - /** - * Time the transaction will take place. - */ - order_t at; - - /** - * The views where the transaction will be applied in. - */ - std::vector states; + /** + * Merge the new states with an existing one from the view. + */ + void merge_changed_states(); + + /** + * Generate all needed updates for a commit. The vector indices + * are mapped to the views of the \p states member of the transaction. + * + * @return List of updates for the views. + */ + std::vector generate_updates(); + + /** + * Track which parents need to be notified of new children. + * Also builds up a list of objects to relinearize because their + * parents changed. + * + * @param[in] tracker ChangeTracker contaiing the object inheritance changes. + * @param[in] view View for which the objects are changed. + * @param[out] objs_to_linearize Identifiers of objects that are relinearized. + * + * @return Map of sets of children identifiers that need to be notified + * by their parent. + */ + view_update::child_map_t + inheritance_updates(const ChangeTracker &tracker, + const std::shared_ptr &view, + std::unordered_set &objs_to_linearize) const; + + /** + * Generate new linearizations for objects that changed. + * + * @param objs_to_linearize Identifiers of objects that are relinearized. + * @param view View for which the objects are changed. + * @param new_state New state of the database after the transaction. + * + * @return List of list of new object linearizations. + */ + view_update::linearizations_t + relinearize_objects(const std::unordered_set &objs_to_linearize, + const std::shared_ptr &view, + const std::shared_ptr &new_state); + + + /** + * Apply the gathered state updates in all views. + * The update list is destroyed. + * + * @param updates List of updates for the views. + */ + void update_views(std::vector &&updates); + + /** + * Set the transaction to invalid and store the error. This can + * be used to make the transaction silently fail on non-fatal + * errors. + * + * @param exc Pointer to the error that occured. + */ + void set_error(std::exception_ptr &&exc); + + /** + * Exception holder if something failed during the transaction. + */ + std::exception_ptr error; + + /** + * True if this transaction can be committed. + */ + bool valid; + + /** + * Time at which the transaction will be commited. + */ + order_t at; + + /** + * The views to which the transaction will be applied in. + */ + std::vector states; }; } // namespace nyan diff --git a/nyan/type.cpp b/nyan/type.cpp index f9c40ea..de08561 100644 --- a/nyan/type.cpp +++ b/nyan/type.cpp @@ -1,8 +1,9 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "type.h" #include "ast.h" +#include "compiler.h" #include "error.h" #include "id_token.h" #include "meta_info.h" @@ -20,56 +21,53 @@ Type::Type(const ASTMemberType &ast_type, const NamespaceFinder &scope, const Namespace &ns, const MetaInfo &type_info) - : - element_type{nullptr} { - - this->basic_type = BasicType::from_type_token(ast_type.name); - - // test if the type is primitive (int, float, text, ...) - if (this->basic_type.is_fundamental()) { - if (ast_type.has_payload) { - throw ASTError{ - "fundamental type can't have a type payload", - ast_type.payload, false - }; - } - return; - } - - // container type like set(something) - if (this->basic_type.is_container()) { - if (not ast_type.has_payload) { - throw ASTError{ - "container content type not specified", - ast_type.name, false - }; - } - - this->element_type = std::make_unique( - ast_type.payload, - scope, - ns, - type_info - ); - return; - } - - // here, type must be a OBJECT. - - // type is not builtin, but has a payload - if (ast_type.has_payload) { - throw ASTError{ - "you can't assign a target to an object type", - ast_type.payload, false - }; - } - - this->basic_type = { - primitive_t::OBJECT, - container_t::SINGLE - }; - - this->target = scope.find(ns, ast_type.name, type_info); + : + element_type{nullptr} { + + this->basic_type = BasicType::from_type_token(ast_type.name); + + // test if the type is primitive (int, float, text, ...) + if (this->basic_type.is_fundamental()) { + return; + } + + // composite type like set(something) + if (this->basic_type.is_composite()) { + auto expected_element_types = BasicType::expected_nested_types(basic_type); + if (not (expected_element_types == ast_type.nested_types.size())) { + throw ASTError{ + std::string("only ") + + std::to_string(ast_type.nested_types.size()) + + " container element types specified, expected " + + std::to_string(expected_element_types), + ast_type.name, false + }; + } + + std::vector types; + + for (unsigned int i = 0; i < expected_element_types; i++) { + types.emplace_back( + ast_type.nested_types.at(i), + scope, + ns, + type_info + ); + } + + this->element_type = std::make_unique>(types); + + return; + } + + // here, type must be a OBJECT. + + this->basic_type = { + primitive_t::OBJECT, + composite_t::SINGLE + }; + + this->obj_ref = scope.find(ns, ast_type.name, type_info); } @@ -81,97 +79,167 @@ Type::Type(const IDToken &token, const NamespaceFinder &scope, const Namespace &ns, const MetaInfo &type_info) - : - element_type{nullptr} { + : + element_type{nullptr} { + + this->basic_type = BasicType::from_type_token(token); + + switch (this->get_primitive_type()) { + case primitive_t::OBJECT: + this->obj_ref = scope.find(ns, token, type_info); + break; + + case primitive_t::INT: + case primitive_t::FLOAT: + case primitive_t::TEXT: + case primitive_t::BOOLEAN: + case primitive_t::NONE: + // no obj_ref needs to be saved + break; + + default: + throw InternalError{"unhandled type from token"}; + } +} - this->basic_type = BasicType::from_type_token(token); - switch (this->get_primitive_type()) { - case primitive_t::OBJECT: - this->target = scope.find(ns, token, type_info); - break; +bool Type::is_object() const { + return this->basic_type.is_object(); +} - case primitive_t::INT: - case primitive_t::FLOAT: - case primitive_t::TEXT: - // no target needs to be saved - break; - default: - throw InternalError{"unhandled type from token"}; - } +bool Type::is_fundamental() const { + return this->basic_type.is_fundamental(); } -bool Type::is_fundamental() const { - return this->basic_type.is_fundamental(); +bool Type::is_composite() const { + return this->basic_type.is_composite(); } bool Type::is_container() const { - return this->basic_type.is_container(); + return this->basic_type.is_container(); +} + + +bool Type::is_container(composite_t type) const { + return (this->basic_type.is_container() and + this->get_composite_type() == type); +} + +bool Type::is_modifier() const { + return this->basic_type.is_modifier(); +} + + +bool Type::is_modifier(composite_t type) const { + return (this->basic_type.is_modifier() and + this->get_composite_type() == type); } -bool Type::is_container(container_t type) const { - return this->get_container_type() == type; +bool Type::is_hashable() const { + if (this->is_fundamental()) { + return true; + } + else if (this->basic_type.is_object()) { + return true; + } + else if (this->is_modifier()) { + // check the subtype + // TODO: This could be wrong if other modifiers are added + return std::all_of(this->element_type.get()->cbegin(), + this->element_type.get()->cend(), + [] (const Type &type) { + return type.is_hashable(); + }); + } + + // containers are non-hashable + return false; } -bool Type::is_basic_compatible(const BasicType &type) const { - return (this->basic_type == type); +bool Type::is_basic_type_match(const BasicType &type) const { + return (this->basic_type == type); } -const fqon_t &Type::get_target() const { - return this->target; +const fqon_t &Type::get_fqon() const { + return this->obj_ref; } const BasicType &Type::get_basic_type() const { - return this->basic_type; + return this->basic_type; } -const container_t &Type::get_container_type() const { - return this->basic_type.container_type; +const composite_t &Type::get_composite_type() const { + return this->basic_type.composite_type; } const primitive_t &Type::get_primitive_type() const { - return this->basic_type.primitive_type; + return this->basic_type.primitive_type; } -const Type *Type::get_element_type() const { - return this->element_type.get(); +const std::vector *Type::get_element_type() const { + return this->element_type.get(); } std::string Type::str() const { - if (this->is_fundamental()) { - return type_to_string(this->get_primitive_type()); - } - else { - if (this->get_primitive_type() == primitive_t::OBJECT) { - return this->target; - } - - if (this->get_container_type() == container_t::SINGLE) { - throw InternalError{ - "single value encountered when expecting container" - }; - } - - std::ostringstream builder; - - builder << container_type_to_string(this->get_container_type()) - << "(" - << this->element_type->str() - << ")"; - - return builder.str(); - } + if (this->is_fundamental()) { + return type_to_string(this->get_primitive_type()); + } + else { + if (this->get_primitive_type() == primitive_t::OBJECT) { + return this->obj_ref; + } + + if (this->get_composite_type() == composite_t::SINGLE) { + throw InternalError{ + "single value encountered when expecting composite" + }; + } + + std::ostringstream builder; + + builder << composite_type_to_string(this->get_composite_type()) + << "("; + + for (auto &elem_type : *this->get_element_type()) { + builder << elem_type.str(); + } + + builder << ")"; + + return builder.str(); + } +} + +bool Type::operator ==(const Type &other) const { + if (not this->is_basic_type_match(other.get_basic_type())) { + return false; + } + + for (unsigned int i = 0; i < other.get_element_type()->size(); i++) { + if (not (this->get_element_type()->at(i) == other.get_element_type()->at(i))) { + return false; + } + } + + if (this->is_object()) { + // For objects the fqons must be equal + if (this->get_fqon() != other.get_fqon()) { + return false; + } + } + + return true; } } // namespace nyan diff --git a/nyan/type.h b/nyan/type.h index 86da927..0dcb1de 100644 --- a/nyan/type.h +++ b/nyan/type.h @@ -1,8 +1,9 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include +#include #include #include @@ -31,105 +32,170 @@ class Token; class Type { public: - /** - * Construct type from the AST. - * - * The definition happens in a rechable name scope, - * where the type object is done in a specific namespace. - * - * The target name is searched in the type info database. - */ - Type(const ASTMemberType &ast_type, - const NamespaceFinder &scope, - const Namespace &ns, - const MetaInfo &type_info); - - /** - * Called when a container type is created from AST. - */ - Type(const IDToken &token, - const NamespaceFinder &scope, - const Namespace &ns, - const MetaInfo &type_info); + /** + * Construct type from the AST. + * + * The definition happens in a rechable name scope, + * where the type object is done in a specific namespace. + * + * The target name is searched in the type info database. + */ + Type(const ASTMemberType &ast_type, + const NamespaceFinder &scope, + const Namespace &ns, + const MetaInfo &type_info); + + /** + * Called when a composite type is created from AST. + */ + Type(const IDToken &token, + const NamespaceFinder &scope, + const Namespace &ns, + const MetaInfo &type_info); public: - virtual ~Type() = default; - - /** - * Return if this type is primitive (simple non-pointer value). - */ - bool is_fundamental() const; - - /** - * Return if this type is a container that stores multiple values. - */ - bool is_container() const; - - /** - * Test if is a container of the given type. - */ - bool is_container(container_t type) const; - - /** - * Test if the basic type is compatbile, i. e. the same. - */ - bool is_basic_compatible(const BasicType &type) const; - - /** - * Check if this type can be in the given other type. - * This will of course only suceed if other is a container. - */ - bool can_be_in(const Type &other) const; - - /** - * Return the object target name. - */ - const fqon_t &get_target() const; - - /** - * Return the basic type, namely the primitive and container type. - */ - const BasicType &get_basic_type() const; - - /** - * Return the container variant of this type. - */ - const container_t &get_container_type() const; - - /** - * Return the basic type of this Type. - */ - const primitive_t &get_primitive_type() const; - - /** - * Get the container element type, i. e. the inner type - * that specifies the type of each element. - */ - const Type *get_element_type() const; - - /** - * Return a string representation of this type. - */ - std::string str() const; + virtual ~Type() = default; + + /** + * Check if this type is an object. + * + * @return true if the basic type is an object, else false. + */ + bool is_object() const; + + /** + * Check if this type is fundamental (simple non-pointer value). + * + * @return true if the basic type is fundamental, else false. + */ + bool is_fundamental() const; + + /** + * Check if this type is a composite of multiple types. + * + * @return true if the basic type is a composite, else false. + */ + bool is_composite() const; + + /** + * Check if this type is a container that stores multiple values. + * + * @return true if the basic type is a container, else false. + */ + bool is_container() const; + + /** + * Check if this type is a container of a given type. + * + * @param type Composite type that is compared to this type's basic type. + * + * @return true if the composite types matches, else false. + */ + bool is_container(composite_t type) const; + + /** + * Check if this type is a modifier. + * + * @return true if the basic type is a modifier, else false. + */ + bool is_modifier() const; + + /** + * Check if this type is a modifier of a given type. + * + * @param type Composite type that is compared to this type's basic type. + * + * @return true if the composite types matches, else false. + */ + bool is_modifier(composite_t type) const; + + /** + * Check if a value of this type is hashable. + * + * @return true if values are hashable, else false. + */ + bool is_hashable() const; + + /** + * Check if the basic type matches the given basic type, i.e. it's the same. + * + * @param type Basic type that is compared to this type's basic type. + * + * @return true if the basic types match, else false. + */ + bool is_basic_type_match(const BasicType &type) const; + + /** + * Get the object fqon of the type. + * + * @return Identifier of the object if this type is an object, else nullptr. + */ + const fqon_t &get_fqon() const; + + /** + * Get the basic type of this type, namely the primitive and composite type. + * + * @return Basic type of this type. + */ + const BasicType &get_basic_type() const; + + /** + * Get the composite type of this type. + * + * @return Composite type of this type. + */ + const composite_t &get_composite_type() const; + + /** + * Get the primitive type of this type. + * + * @return Primitive type of this type. + */ + const primitive_t &get_primitive_type() const; + + /** + * Get the composite element type of this type, i.e. the inner type + * that specifies the type of each item in a value. + * + * @return Pointer to the list with the element types of this type. + */ + const std::vector *get_element_type() const; + + /** + * Get the string representation of this type. + * + * @return String representation of this type. + */ + std::string str() const; + + /** + * Checks if two types are equal. Their basic type, element types + * and fqon must match. + * + * @param other Type that is compared with this type. + * + * @return true if the types are equal, else false. + */ + bool operator ==(const Type &other) const; protected: - /** - * The basic type of this Type. - * Stores the primitive type and the container type. - */ - BasicType basic_type; - - /** - * If this type is a container, the element type is stored here. - */ - std::shared_ptr element_type; - - /** - * If this type is an object, store the target here. - * If it is nullptr, any object is covered by this type. - */ - fqon_t target; + /** + * The basic type of this Type. + * Stores the primitive type and the composite type. + */ + BasicType basic_type; + + /** + * If this type is a composite, the element type is stored here. + */ + std::shared_ptr> element_type; + + /** + * If this type is an object, store the reference here. + * If it is nullptr, any object is covered by this type. + */ + fqon_t obj_ref; }; } // namespace nyan diff --git a/nyan/util.cpp b/nyan/util.cpp index 6aef832..b52908f 100644 --- a/nyan/util.cpp +++ b/nyan/util.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "util.h" @@ -26,46 +26,46 @@ namespace { std::string symbol_name_win(const void *addr, bool require_exact_addr) { - // Handle to the current process - static HANDLE process_handle = INVALID_HANDLE_VALUE; - static bool initialized_symbol_handler = false; - static bool initialized_symbol_handler_successfully = false; - - // SymInitialize & SymFromAddr are, according to MSDN, not thread-safe. - static std::mutex sym_mutex; - std::lock_guard sym_lock_guard{sym_mutex}; - - // Initialize symbol handler for process, if it has not yet been initialized - // If we are not succesful on the first try, leave it, since MSDN says that searching for symbol files is very time consuming - if (!initialized_symbol_handler) { - initialized_symbol_handler = true; - - process_handle = GetCurrentProcess(); - initialized_symbol_handler_successfully = SymInitialize(process_handle, nullptr, TRUE); - } - - if (!initialized_symbol_handler_successfully) { - return {}; - } - - // The magic of winapi - constexpr int name_buffer_size = 1024; - constexpr int buffer_size = sizeof(SYMBOL_INFO) + name_buffer_size * sizeof(char); - std::array buffer; - - SYMBOL_INFO *symbol_info = reinterpret_cast(buffer.data()); - - symbol_info->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol_info->MaxNameLen = name_buffer_size; - - DWORD64 symbol_offset = 0; - if (not SymFromAddr(process_handle, reinterpret_cast(addr), - &symbol_offset, symbol_info) - or (require_exact_addr and symbol_offset != 0)) { - return {}; - } - - return std::string(symbol_info->Name); + // Handle to the current process + static HANDLE process_handle = INVALID_HANDLE_VALUE; + static bool initialized_symbol_handler = false; + static bool initialized_symbol_handler_successfully = false; + + // SymInitialize & SymFromAddr are, according to MSDN, not thread-safe. + static std::mutex sym_mutex; + std::lock_guard sym_lock_guard{sym_mutex}; + + // Initialize symbol handler for process, if it has not yet been initialized + // If we are not succesful on the first try, leave it, since MSDN says that searching for symbol files is very time consuming + if (!initialized_symbol_handler) { + initialized_symbol_handler = true; + + process_handle = GetCurrentProcess(); + initialized_symbol_handler_successfully = SymInitialize(process_handle, nullptr, TRUE); + } + + if (!initialized_symbol_handler_successfully) { + return {}; + } + + // The magic of winapi + constexpr int name_buffer_size = 1024; + constexpr int buffer_size = sizeof(SYMBOL_INFO) + name_buffer_size * sizeof(char); + std::array buffer; + + SYMBOL_INFO *symbol_info = reinterpret_cast(buffer.data()); + + symbol_info->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol_info->MaxNameLen = name_buffer_size; + + DWORD64 symbol_offset = 0; + if (not SymFromAddr(process_handle, reinterpret_cast(addr), + &symbol_offset, symbol_info) + or (require_exact_addr and symbol_offset != 0)) { + return {}; + } + + return std::string(symbol_info->Name); } @@ -77,121 +77,121 @@ namespace nyan::util { std::string read_file(const std::string &filename, bool binary) { - std::ifstream::openmode mode = std::ifstream::in | std::ifstream::ate; - if (binary) { - mode |= std::ifstream::binary; - } - - std::ifstream input{filename, mode}; - - if (input) { - std::string ret; - - // we're at the file end already because of ifstream::ate - ret.resize(input.tellg()); - input.seekg(0, std::ios::beg); - input.read(&ret[0], ret.size()); - input.close(); - - return ret; - } - else { - std::ostringstream builder; - builder << "failed reading file '" - << filename << "': " - << strerror(errno); - throw FileReadError{builder.str()}; - } + std::ifstream::openmode mode = std::ifstream::in | std::ifstream::ate; + if (binary) { + mode |= std::ifstream::binary; + } + + std::ifstream input{filename, mode}; + + if (input) { + std::string ret; + + // we're at the file end already because of ifstream::ate + ret.resize(input.tellg()); + input.seekg(0, std::ios::beg); + input.read(&ret[0], ret.size()); + input.close(); + + return ret; + } + else { + std::ostringstream builder; + builder << "failed reading file '" + << filename << "': " + << strerror(errno); + throw FileReadError{builder.str()}; + } } std::string demangle(const char *symbol) { #ifdef _MSC_VER - // TODO: demangle names for MSVC; Possibly using UnDecorateSymbolName - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681400(v=vs.85).aspx - return symbol; + // TODO: demangle names for MSVC; Possibly using UnDecorateSymbolName + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681400(v=vs.85).aspx + return symbol; #else - int status; - char *buf = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); - - if (status != 0) { - return symbol; - } else { - std::string result{buf}; - free(buf); - return result; - } + int status; + char *buf = abi::__cxa_demangle(symbol, nullptr, nullptr, &status); + + if (status != 0) { + return symbol; + } else { + std::string result{buf}; + free(buf); + return result; + } #endif } std::string addr_to_string(const void *addr) { - std::ostringstream out; - out << "[" << addr << "]"; - return out.str(); + std::ostringstream out; + out << "[" << addr << "]"; + return out.str(); } std::string symbol_name(const void *addr, bool require_exact_addr, bool no_pure_addrs) { #if defined(_WIN32) || defined(__CYGWIN__) - auto symbol_name_result = symbol_name_win(addr, require_exact_addr); + auto symbol_name_result = symbol_name_win(addr, require_exact_addr); - if (!symbol_name_result.empty()) { - return symbol_name_result; - } + if (!symbol_name_result.empty()) { + return symbol_name_result; + } - return no_pure_addrs ? "" : addr_to_string(addr); + return no_pure_addrs ? "" : addr_to_string(addr); #else - Dl_info addr_info; - - if (dladdr(addr, &addr_info) == 0) { - // dladdr has... failed. - return no_pure_addrs ? "" : addr_to_string(addr); - } else { - size_t symbol_offset = reinterpret_cast(addr) - - reinterpret_cast(addr_info.dli_saddr); - - if (addr_info.dli_sname == nullptr or - (symbol_offset != 0 and require_exact_addr)) { - - return no_pure_addrs ? "" : addr_to_string(addr); - } - - if (symbol_offset == 0) { - // this is our symbol name. - return demangle(addr_info.dli_sname); - } else { - std::ostringstream out; - out << demangle(addr_info.dli_sname) - << "+0x" << std::hex - << symbol_offset << std::dec; - return out.str(); - } - } + Dl_info addr_info; + + if (dladdr(addr, &addr_info) == 0) { + // dladdr has... failed. + return no_pure_addrs ? "" : addr_to_string(addr); + } else { + size_t symbol_offset = reinterpret_cast(addr) - + reinterpret_cast(addr_info.dli_saddr); + + if (addr_info.dli_sname == nullptr or + (symbol_offset != 0 and require_exact_addr)) { + + return no_pure_addrs ? "" : addr_to_string(addr); + } + + if (symbol_offset == 0) { + // this is our symbol name. + return demangle(addr_info.dli_sname); + } else { + std::ostringstream out; + out << demangle(addr_info.dli_sname) + << "+0x" << std::hex + << symbol_offset << std::dec; + return out.str(); + } + } #endif } std::vector split(const std::string &txt, char delimiter) { - std::vector items; - // use the back inserter iterator and the templated split function. - split(txt, delimiter, std::back_inserter(items)); - return items; + std::vector items; + // use the back inserter iterator and the templated split function. + split(txt, delimiter, std::back_inserter(items)); + return items; } bool ends_with(const std::string &txt, const std::string &end) { - if (end.size() > txt.size()) { - return false; - } - return std::equal(end.rbegin(), end.rend(), txt.rbegin()); + if (end.size() > txt.size()) { + return false; + } + return std::equal(end.rbegin(), end.rend(), txt.rbegin()); } size_t hash_combine(size_t hash1, size_t hash2) { - // Taken from http://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine - return hash1 ^ (hash2 + 0x9e3779b9 + ((hash1 << 6) + (hash1 >> 2))); + // Taken from http://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine + return hash1 ^ (hash2 + 0x9e3779b9 + ((hash1 << 6) + (hash1 >> 2))); } } // namespace nyan::util diff --git a/nyan/util.h b/nyan/util.h index 1fba1d5..ace501e 100644 --- a/nyan/util.h +++ b/nyan/util.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -16,7 +16,12 @@ namespace nyan::util { /** * Read a file from the filesystem and return the contents. - * Optionally, open it in binary mode, which will leave newlines untouched. + * + * @param filename Name of the file. + * @param binary If true, open the file in binary mode, which + * will leave newlines untouched. + * + * @return String with the file content. */ std::string read_file(const std::string &filename, bool binary=false); @@ -39,7 +44,7 @@ std::string symbol_name(const void *addr, bool require_exact_addr=true, bool no_ */ template std::string typestring() { - return demangle(typeid(T).name()); + return demangle(typeid(T).name()); } /** @@ -47,13 +52,13 @@ std::string typestring() { */ template std::string typestring(const T *ptr) { - return demangle(typeid(*ptr).name()); + return demangle(typeid(*ptr).name()); } template std::string get_container_elem(const T &in) { - return in; + return in; } @@ -65,103 +70,147 @@ std::string get_container_elem(const T &in) { * Now, this function also features a compiler bug: * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59949 * Fak u C++. + * + * @param delim Delimeter placed between the joined strings. + * @param container Iterable container holding the strings. + * @param func Function for retrieving a string from a container item. + * + * @return String containing the joined strings. */ template std::string strjoin(const std::string &delim, const T &container, const std::function func=&get_container_elem) { - std::ostringstream builder; + std::ostringstream builder; - size_t cnt = 0; - for (auto &entry : container) { - if (cnt > 0) { - builder << delim; - } + size_t cnt = 0; + for (auto &entry : container) { + if (cnt > 0) { + builder << delim; + } - builder << func(entry); - cnt += 1; - } + builder << func(entry); + cnt += 1; + } - return builder.str(); + return builder.str(); } /** * Split a string at a delimiter, push the result back in an iterator. * Why doesn't the fucking standard library have std::string::split(delimiter)? + * + * @tparam ret_t Return type. + * + * @param[in] txt String that is split. + * @param[in] delimiter Delimiter char at which the string is split. + * @param[out] result Splitted string with type \p ret_t. */ template void split(const std::string &txt, char delimiter, ret_t result) { - std::stringstream splitter; - splitter.str(txt); - std::string part; - - while (std::getline(splitter, part, delimiter)) { - *result = part; - result++; - } + std::stringstream splitter; + splitter.str(txt); + std::string part; + + while (std::getline(splitter, part, delimiter)) { + *result = part; + result++; + } } /** * Split a string at a delimiter into a vector. - * Internally, uses the above iterator splitter. + * Internally, uses the iterator splitter. + * + * @param txt String that is split. + * @param delim Delimiter char at which the string is split. + * + * @return List of string components from the splitting. */ std::vector split(const std::string &txt, char delim); /** - * Check if the given string ends with the ending. + * Check if a string ends with another given string. + * + * @param txt String that is checked. + * @param end String that is compared with the end of \p txt. + * + * @return true if \p txt ends with \p end, else false. */ bool ends_with(const std::string &txt, const std::string &end); /** - * Extend a vector with elements, without destroying source one. + * Extend a vector with elements, without destroying the + * source of elements. + * + * @tparam T Element type. + * + * @param vec Vector that is extended. + * @param ext Vector used as a source of elements. */ template void vector_extend(std::vector &vec, const std::vector &ext) { - vec.reserve(vec.size() + ext.size()); - vec.insert(std::end(vec), std::begin(ext), std::end(ext)); + vec.reserve(vec.size() + ext.size()); + vec.insert(std::end(vec), std::begin(ext), std::end(ext)); } /** - * Extend a vector with elements with move semantics. + * Extend a vector with elements using move semantics. + * + * @tparam T Element type. + * + * @param vec Vector that is extended. + * @param ext Vector used as a source of elements. */ template void vector_extend(std::vector &vec, std::vector &&ext) { - if (vec.empty()) { - vec = std::move(ext); - } - else { - vec.reserve(vec.size() + ext.size()); - std::move(std::begin(ext), std::end(ext), std::back_inserter(vec)); - ext.clear(); - } + if (vec.empty()) { + vec = std::move(ext); + } + else { + vec.reserve(vec.size() + ext.size()); + std::move(std::begin(ext), std::end(ext), std::back_inserter(vec)); + ext.clear(); + } } /** * Membership check for some container. + * + * @tparam T Container type. + * @tparam V Type of checked value. + * + * @param container Container that is searched. + * @param value Value that is searched for. + * + * @return true if the value is in the container, else false. */ template bool contains(const T &container, const V &value) { - if (std::find(std::begin(container), - std::end(container), - value) == std::end(container)) { - return false; - } - else { - return true; - } + if (std::find(std::begin(container), + std::end(container), + value) == std::end(container)) { + return false; + } + else { + return true; + } } /** * Creates a hash value as a combination of two other hashes. Can be called incrementally to create * hash value from several variables. + * + * @param hash1 First hash. + * @param hash2 Second hash. */ size_t hash_combine(size_t hash1, size_t hash2); diff --git a/nyan/value/boolean.cpp b/nyan/value/boolean.cpp index 843f001..ae13ec3 100644 --- a/nyan/value/boolean.cpp +++ b/nyan/value/boolean.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "boolean.h" @@ -13,111 +13,120 @@ namespace nyan { Boolean::Boolean(const bool &value) - : - value{value} {} + : + value{value} {} Boolean::Boolean(const IDToken &token) { - if (unlikely(token.get_type() != token_type::ID)) { - throw LangError{ - token, - "invalid value for boolean" - }; - } - - const std::string &token_value = token.get_first(); - - if (token_value == "true") { - this->value = true; - } - else if (token_value == "false") { - this->value = false; - } - else { - throw LangError{ - token, - "unknown boolean value (did you use 'true' and 'false'?)" - }; - } + if (unlikely(token.get_type() != token_type::ID)) { + throw LangError{ + token, + "invalid value for boolean" + }; + } + + const std::string &token_value = token.get_first(); + + if (token_value == "True") { + this->value = true; + } + else if (token_value == "False") { + this->value = false; + } + else { + throw LangError{ + token, + "unknown boolean value (did you use 'True' and 'False'?)" + }; + } } ValueHolder Boolean::copy() const { - return ValueHolder{ - std::make_shared(dynamic_cast(*this)) - }; + return ValueHolder{ + std::make_shared(dynamic_cast(*this)) + }; } void Boolean::apply_value(const Value &value, nyan_op operation) { - const Boolean &change = dynamic_cast(value); + const Boolean &change = dynamic_cast(value); - switch (operation) { - case nyan_op::ASSIGN: - this->value = change.value; break; + switch (operation) { + case nyan_op::ASSIGN: + this->value = change.value; break; - case nyan_op::UNION_ASSIGN: - this->value |= change.value; break; + case nyan_op::UNION_ASSIGN: + this->value |= change.value; break; - case nyan_op::INTERSECT_ASSIGN: - this->value &= change.value; break; + case nyan_op::INTERSECT_ASSIGN: + this->value &= change.value; break; - default: - throw Error{"unknown operation requested"}; - } + default: + throw Error{"unknown operation requested"}; + } } std::string Boolean::str() const { - if (this->value) { - return "true"; - } - else { - return "false"; - }; + if (this->value) { + return "True"; + } + else { + return "False"; + }; } std::string Boolean::repr() const { - return this->str(); + return this->str(); } size_t Boolean::hash() const { - return std::hash{}(this->value); + return std::hash{}(this->value); } bool Boolean::equals(const Value &other) const { - auto &other_val = dynamic_cast(other); - return this->value == other_val.value; + auto &other_val = dynamic_cast(other); + return this->value == other_val.value; } const std::unordered_set &Boolean::allowed_operations(const Type &with_type) const { - const static std::unordered_set ops{ - nyan_op::ASSIGN, - nyan_op::UNION_ASSIGN, - nyan_op::INTERSECT_ASSIGN, - }; - - if (with_type.get_primitive_type() == primitive_t::BOOLEAN) { - return ops; - } - else { - return no_nyan_ops; - } + + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + const static std::unordered_set ops{ + nyan_op::ASSIGN, + nyan_op::UNION_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + switch (with_type.get_primitive_type()) { + case primitive_t::BOOLEAN: + return ops; + + case primitive_t::NONE: + return none_ops; + + default: + return no_nyan_ops; + } } const BasicType &Boolean::get_type() const { - constexpr static BasicType type{ - primitive_t::BOOLEAN, - container_t::SINGLE, - }; + constexpr static BasicType type{ + primitive_t::BOOLEAN, + composite_t::SINGLE, + }; - return type; + return type; } diff --git a/nyan/value/boolean.h b/nyan/value/boolean.h index 0560b28..d2e5949 100644 --- a/nyan/value/boolean.h +++ b/nyan/value/boolean.h @@ -1,4 +1,4 @@ -// Copyright 2018-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2018-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -13,30 +13,30 @@ namespace nyan { */ class Boolean : public Value { public: - Boolean(const bool &value); - Boolean(const IDToken &token); + Boolean(const bool &value); + Boolean(const IDToken &token); - ValueHolder copy() const override; - std::string str() const override; - std::string repr() const override; - size_t hash() const override; + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + size_t hash() const override; - bool get() const { - return *this; - } + bool get() const { + return *this; + } - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; - operator bool() const { - return this->value; - } + operator bool() const { + return this->value; + } protected: - void apply_value(const Value &value, nyan_op operation) override; - bool equals(const Value &other) const override; + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override; - bool value; + bool value; }; } // namespace nyan diff --git a/nyan/value/container.h b/nyan/value/container.h index 43f0059..03e8b71 100644 --- a/nyan/value/container.h +++ b/nyan/value/container.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -25,35 +25,35 @@ template class ContainerIterBase : public std::iterator { public: - using this_type = ContainerIterBase; + using this_type = ContainerIterBase; - ContainerIterBase() = default; - virtual ~ContainerIterBase() = default; + ContainerIterBase() = default; + virtual ~ContainerIterBase() = default; - /** - * Advance the iterator to the next element. - */ - virtual this_type &operator ++() = 0; + /** + * Advance the iterator to the next element. + */ + virtual this_type &operator ++() = 0; - /** - * Get the element the iterator is currently pointing to. - */ - virtual elem_type &operator *() const = 0; + /** + * Get the element the iterator is currently pointing to. + */ + virtual elem_type &operator *() const = 0; - /** - * Compare if both iterators are pointing - * to the same container position. - */ - bool operator ==(const ContainerIterBase &other) const { - return (typeid(*this) == typeid(other)) and this->equals(other); - } + /** + * Compare if both iterators are pointing + * to the same container position. + */ + bool operator ==(const ContainerIterBase &other) const { + return (typeid(*this) == typeid(other)) and this->equals(other); + } protected: - /** - * Actually perform the comparison if both iterators - * point to the same element. - */ - virtual bool equals(const ContainerIterBase &other) const = 0; + /** + * Actually perform the comparison if both iterators + * point to the same element. + */ + virtual bool equals(const ContainerIterBase &other) const = 0; }; @@ -66,70 +66,70 @@ class ContainerIterBase : public std::iterator class ContainerIterator : public std::iterator { public: - using elem_type = T; - using real_iterator = ContainerIterBase; - - - ContainerIterator() = default; - ContainerIterator(std::unique_ptr> &&real) noexcept - : - iter{std::move(real)} {} - - ContainerIterator(const ContainerIterator &other) - : - iter{std::make_unique(other)} {} - - ContainerIterator(ContainerIterator &&other) noexcept - : - iter{std::move(other.iter)} {} - - ContainerIterator &operator =(const ContainerIterator &other) { - this->iter = std::make_unique(other); - } - - ContainerIterator &operator =(ContainerIterator &&other) noexcept { - this->iter = std::move(other); - } - - virtual ~ContainerIterator() = default; - - /** - * Advance the inner iterator to the next element. - */ - ContainerIterator &operator ++() { - ++(*this->iter); - return *this; - } - - /** - * Get the element the inner iterator points to. - */ - elem_type &operator *() const { - return *(*this->iter); - } - - /** - * Check if this iterator points to the same container element - * as the other iterator. - */ - bool operator ==(const ContainerIterator& other) const { - return (this->iter == other.iter) or (*this->iter == *other.iter); - } - - /** - * Check if the iterator does not point to the same container element - * as the other iterator. - */ - bool operator !=(const ContainerIterator& other) const { - return not (*this == other); - } + using elem_type = T; + using real_iterator = ContainerIterBase; + + + ContainerIterator() = default; + ContainerIterator(std::unique_ptr> &&real) noexcept + : + iter{std::move(real)} {} + + ContainerIterator(const ContainerIterator &other) + : + iter{std::make_unique(other)} {} + + ContainerIterator(ContainerIterator &&other) noexcept + : + iter{std::move(other.iter)} {} + + ContainerIterator &operator =(const ContainerIterator &other) { + this->iter = std::make_unique(other); + } + + ContainerIterator &operator =(ContainerIterator &&other) noexcept { + this->iter = std::move(other); + } + + virtual ~ContainerIterator() = default; + + /** + * Advance the inner iterator to the next element. + */ + ContainerIterator &operator ++() { + ++(*this->iter); + return *this; + } + + /** + * Get the element the inner iterator points to. + */ + elem_type &operator *() const { + return *(*this->iter); + } + + /** + * Check if this iterator points to the same container element + * as the other iterator. + */ + bool operator ==(const ContainerIterator& other) const { + return (this->iter == other.iter) or (*this->iter == *other.iter); + } + + /** + * Check if the iterator does not point to the same container element + * as the other iterator. + */ + bool operator !=(const ContainerIterator& other) const { + return not (*this == other); + } protected: - /** - * The real iterator. - * Just wrapped here to enable virtual function calls. - */ - std::unique_ptr> iter; + /** + * The real iterator. + * Just wrapped here to enable virtual function calls. + */ + std::unique_ptr> iter; }; @@ -139,41 +139,41 @@ class ContainerIterator : public std::iterator { template class DefaultIterator : public ContainerIterBase { public: - using this_type = DefaultIterator; - using base_type = ContainerIterBase; - - explicit DefaultIterator(iter_type &&iter) - : - iterator{std::move(iter)} {} - - /** - * Advance the iterator to the next element in the set. - */ - base_type &operator ++() override { - ++this->iterator; - return *this; - } - - /** - * Return the iterator value. - */ - elem_type &operator *() const override { - return *this->iterator; - } + using this_type = DefaultIterator; + using base_type = ContainerIterBase; + + explicit DefaultIterator(iter_type &&iter) + : + iterator{std::move(iter)} {} + + /** + * Advance the iterator to the next element in the set. + */ + base_type &operator ++() override { + ++this->iterator; + return *this; + } + + /** + * Return the iterator value. + */ + elem_type &operator *() const override { + return *this->iterator; + } protected: - /** - * Compare two iterators for pointing at the same element. - */ - bool equals(const base_type &other) const override { - auto other_me = dynamic_cast(other); - return (this->iterator == other_me.iterator); - } - - /** - * The wrapped std::iterator. - */ - iter_type iterator; + /** + * Compare two iterators for pointing at the same element. + */ + bool equals(const base_type &other) const override { + auto other_me = dynamic_cast(other); + return (this->iterator == other_me.iterator); + } + + /** + * The wrapped std::iterator. + */ + iter_type iterator; }; @@ -183,100 +183,100 @@ class DefaultIterator : public ContainerIterBase { */ class Container : public Value { public: - using iterator = ContainerIterator; - using const_iterator = ContainerIterator; - - using holder_iterator = ContainerIterator; - using holder_const_iterator = ContainerIterator; - - Container() = default; - virtual ~Container() = default; - - /** - * Return the number of elements in this container. - */ - virtual size_t size() const = 0; - - /** - * Add the given value to this container. - * @returns if the value was added successfully, - * false if it was already in there. - */ - virtual bool add(const ValueHolder &value) = 0; - - /** - * Test if this value is in the container. - */ - virtual bool contains(const ValueHolder &value) const = 0; - - /** - * Remove the given value from the container if it is in there. - * @returns if if was removed successfully. - */ - virtual bool remove(const ValueHolder &value) = 0; - - /** - * Get an iterator to the first element in that container. - */ - virtual iterator begin() = 0; - - /** - * Get an iterator to the slot beyond the last element in the container. - */ - virtual iterator end() = 0; - - /** - * Get a constant iterator to the first element in that container. - */ - virtual const_iterator begin() const = 0; - - /** - * Get a constant iterator to the slot beyond the last element in the - * container. - */ - virtual const_iterator end() const = 0; - - /** - * Guarantee a const_iterator beginning. - */ - const_iterator cbegin() const { return this->begin(); }; - - /** - * Guarantee a const_iterator end. - */ - const_iterator cend() const { return this->end(); }; - - /** - * Get an iterator to the first value holder in that container. - */ - virtual holder_iterator values_begin() = 0; - - /** - * Get an iterator to the slot beyond the last value holder - * in the container. - */ - virtual holder_iterator values_end() = 0; - - /** - * Get a constant iterator to the first value holder in the container. - */ - virtual holder_const_iterator values_begin() const = 0; - - /** - * Get a constant iterator to the slot beyond the last value holder - * in the container. - */ - virtual holder_const_iterator values_end() const = 0; - - /** - * Guarantee a const_iterator to the value iterator beginning. - */ - holder_const_iterator values_cbegin() const { return this->values_begin(); }; - - /** - * Guarantee a const_iterator to the value iterator end. - */ - holder_const_iterator values_cend() const { return this->values_end(); }; + using iterator = ContainerIterator; + using const_iterator = ContainerIterator; + + using holder_iterator = ContainerIterator; + using holder_const_iterator = ContainerIterator; + + Container() = default; + virtual ~Container() = default; + + /** + * Return the number of elements in this container. + */ + virtual size_t size() const = 0; + + /** + * Add the given value to this container. + * @returns if the value was added successfully, + * false if it was already in there. + */ + virtual bool add(const ValueHolder &value) = 0; + + /** + * Test if this value is in the container. + */ + virtual bool contains(const ValueHolder &value) const = 0; + + /** + * Remove the given value from the container if it is in there. + * @returns if if was removed successfully. + */ + virtual bool remove(const ValueHolder &value) = 0; + + /** + * Get an iterator to the first element in that container. + */ + virtual iterator begin() = 0; + + /** + * Get an iterator to the slot beyond the last element in the container. + */ + virtual iterator end() = 0; + + /** + * Get a constant iterator to the first element in that container. + */ + virtual const_iterator begin() const = 0; + + /** + * Get a constant iterator to the slot beyond the last element in the + * container. + */ + virtual const_iterator end() const = 0; + + /** + * Guarantee a const_iterator beginning. + */ + const_iterator cbegin() const { return this->begin(); }; + + /** + * Guarantee a const_iterator end. + */ + const_iterator cend() const { return this->end(); }; + + /** + * Get an iterator to the first value holder in that container. + */ + virtual holder_iterator values_begin() = 0; + + /** + * Get an iterator to the slot beyond the last value holder + * in the container. + */ + virtual holder_iterator values_end() = 0; + + /** + * Get a constant iterator to the first value holder in the container. + */ + virtual holder_const_iterator values_begin() const = 0; + + /** + * Get a constant iterator to the slot beyond the last value holder + * in the container. + */ + virtual holder_const_iterator values_end() const = 0; + + /** + * Guarantee a const_iterator to the value iterator beginning. + */ + holder_const_iterator values_cbegin() const { return this->values_begin(); }; + + /** + * Guarantee a const_iterator to the value iterator end. + */ + holder_const_iterator values_cend() const { return this->values_end(); }; }; } // namespace nyan diff --git a/nyan/value/dict.cpp b/nyan/value/dict.cpp new file mode 100644 index 0000000..2a7bbce --- /dev/null +++ b/nyan/value/dict.cpp @@ -0,0 +1,268 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. + +#include "dict.h" + +#include "orderedset.h" +#include "set.h" +#include "../error.h" +#include "../util.h" + + +namespace nyan { + +Dict::Dict() = default; + +Dict::Dict(std::unordered_map &&values) { + for (auto &value : values) { + this->values.insert(std::move(value)); + } +} + + +ValueHolder Dict::copy() const { + return {std::make_shared(*this)}; +} + + +std::string Dict::str() const { + // same as repr(), except we use str(). + + std::ostringstream builder; + builder << "{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val.first->str() + ": " + val.second->str(); + } + ); + builder << "}"; + + return builder.str(); +} + + +std::string Dict::repr() const { + // same as str(), except we use repr(). + + std::ostringstream builder; + builder << "{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val.first->repr() + ": " + val.second->repr(); + } + ); + builder << "}"; + + return builder.str(); +} + + +const BasicType &Dict::get_type() const { + constexpr static BasicType type{ + primitive_t::CONTAINER, + composite_t::DICT, + }; + + return type; +} + + +bool Dict::add(const element_type &value) { + return std::get<1>(this->values.insert(value)); +} + + +bool Dict::contains(const key_type &value) const { + return (this->values.find(value) != std::end(this->values)); +} + + +bool Dict::remove(const key_type &value) { + return (1 == this->values.erase(value)); +} + + +void Dict::apply_value(const Value &value, nyan_op operation) { + auto dict_applier = [](auto &member_value, auto operand, nyan_op operation) { + switch (operation) { + case nyan_op::ASSIGN: + member_value.clear(); + // fall through + + case nyan_op::UNION_ASSIGN: + case nyan_op::ADD_ASSIGN: { + for (auto &val : operand) { + member_value.insert(val); + } + break; + } + + case nyan_op::INTERSECT_ASSIGN:{ + // only keep items that are in both. Both key + // and value must match. + + std::unordered_map keep; + keep.reserve(member_value.size()); + + // iterate over the dict + for (auto &it : operand) { + // Check if key exists + auto search = member_value.find(it.first); + if (search != std::end(member_value)) { + // Check if values are equal + auto item_value = member_value[it.first]; + if (item_value == it.second) { + keep.insert(it); + } + } + } + + member_value.clear(); + + // Reinsert matching values + for (auto &value : keep) { + member_value.insert(value); + } + + break; + } + + default: + throw InternalError{"unknown dict value application"}; + } + }; + + auto set_applier = [](auto &member_value, auto operand, nyan_op operation) { + switch (operation) { + case nyan_op::SUBTRACT_ASSIGN: { + for (auto &val : operand) { + member_value.erase(val); + } + break; + } + + case nyan_op::INTERSECT_ASSIGN: { + // only keep items that are in both. Both key + // and value must match. + + std::unordered_map keep; + keep.reserve(member_value.size()); + + // iterate over the dict + for (auto &it : operand) { + // Check if key exists + auto search = member_value.find(it); + if (search != std::end(member_value)) { + keep.insert(std::make_pair(it, member_value[it])); + } + } + + member_value.clear(); + + // Reinsert matching values + for (auto &value : keep) { + member_value.insert(value); + } + + break; + } + + default: + throw InternalError{"unknown dict value application"}; + } + }; + + + if (typeid(Set&) == typeid(value)) { + const Set *change = dynamic_cast(&value); + + if (unlikely(change == nullptr)) { + using namespace std::string_literals; + throw InternalError{ + "set value application was not a container, it was: "s + + util::demangle(typeid(value).name()) + + " and couldn't cast to " + + util::demangle(typeid(change).name())}; + } + + set_applier(this->values, change->get(), operation); + + } + else if (typeid(OrderedSet&) == typeid(value)) { + const OrderedSet *change = dynamic_cast(&value); + + if (unlikely(change == nullptr)) { + using namespace std::string_literals; + throw InternalError{ + "set value application was not a container, it was: "s + + util::demangle(typeid(value).name()) + + " and couldn't cast to " + + util::demangle(typeid(change).name())}; + } + + set_applier(this->values, change->get(), operation); + + } + else if (typeid(Dict&) == typeid(value)) { + const Dict *change = dynamic_cast(&value); + + if (unlikely(change == nullptr)) { + using namespace std::string_literals; + throw InternalError{ + "set value application was not a container, it was: "s + + util::demangle(typeid(value).name()) + + " and couldn't cast to " + + util::demangle(typeid(change).name())}; + } + + dict_applier(this->values, change->get(), operation); + } + else { + throw InternalError("expected Container instance for operation, but got" + + std::string(typeid(value).name())); + } + + +} + +const std::unordered_set &Dict::allowed_operations(const Type &with_type) const { + + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + const static std::unordered_set set_ops{ + nyan_op::SUBTRACT_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + const static std::unordered_set dict_ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + nyan_op::UNION_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + if (with_type.get_primitive_type() == primitive_t::NONE) { + return none_ops; + } + + if (not with_type.is_container()) { + return no_nyan_ops; + } + + switch (with_type.get_composite_type()) { + case composite_t::SET: + case composite_t::ORDEREDSET: + return set_ops; + + case composite_t::DICT: + return dict_ops; + + default: + return no_nyan_ops; + } +} + +} // namespace nyan diff --git a/nyan/value/dict.h b/nyan/value/dict.h new file mode 100644 index 0000000..44e3a5c --- /dev/null +++ b/nyan/value/dict.h @@ -0,0 +1,141 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. +#pragma once + + +#include + +#include "../api_error.h" +#include "../compiler.h" +#include "../util.h" +#include "container.h" +#include "value.h" + + +namespace nyan { + + +/** + * Nyan value to store a dict/map/assocated array of things. + */ +class Dict : public Value { +public: + using value_storage = std::unordered_map; + using key_type = typename value_storage::key_type; + using value_type = typename value_storage::mapped_type; + using element_type = typename value_storage::value_type; + using value_const_iterator = typename value_storage::const_iterator; + + using iterator = ContainerIterator>; + using const_iterator = ContainerIterator>; + + using holder_iterator = ContainerIterator; + using holder_const_iterator = ContainerIterator; + + Dict(); + Dict(std::unordered_map &&values); + + + size_t hash() const override { + throw APIError{"Dicts are not hashable."}; + } + + + size_t size() const { + return this->values.size(); + } + + + void clear() { + this->values.clear(); + } + + + const value_storage &get() const { + return this->values; + } + + iterator begin() { + throw Error{ + "Dicts are only const-iterable. " + "make it const by using e.g. " + "for (auto &it = std::as_const(dict))" + }; + } + + + iterator end() { + // also throw the error above. + return this->begin(); + } + + + holder_iterator values_begin() { + throw Error{ + "Dict values holders are not non-const-iterable." + }; + } + + + holder_iterator values_end() { + // also throw the error above. + return this->values_begin(); + } + + + /** + * Get an iterator to the underlying dict storage. + * Contrary to the above, this will allow to get the + * ValueHolders. + */ + holder_const_iterator values_begin() const { + auto real_iterator = std::make_unique< + DefaultIterator>( + std::begin(this->values) + ); + + return holder_const_iterator{std::move(real_iterator)}; + } + + /** + * Iterator to end of the underlying storage. + */ + holder_const_iterator values_end() const { + auto real_iterator = std::make_unique< + DefaultIterator>( + std::end(this->values) + ); + + return holder_const_iterator{std::move(real_iterator)}; + } + + + bool add(const element_type &value); + bool contains(const key_type &value) const; + bool remove(const key_type &value); + + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; + +protected: + void apply_value(const Value &value, nyan_op operation) override; + + + bool equals(const Value &other) const override { + auto &other_val = dynamic_cast(other); + + return values == other_val.values; + } + + /** + * Dict value storage (this is an unordered map). + */ + value_storage values; +}; + +} // namespace nyan diff --git a/nyan/value/file.cpp b/nyan/value/file.cpp index 8854fa1..b78f468 100644 --- a/nyan/value/file.cpp +++ b/nyan/value/file.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "file.h" @@ -13,93 +13,95 @@ namespace nyan { Filename::Filename(const std::string &path) - : - path{path} { + : + path{path} { - // TODO relative path resolution + // TODO relative path resolution } Filename::Filename(const IDToken &token) - : - Filename{token.get_first()} { - - if (unlikely(token.get_type() != token_type::STRING)) { - throw LangError{ - token, - "invalid value for filename" - }; - } + : + Filename{token.get_first()} { + + if (unlikely(token.get_type() != token_type::STRING)) { + throw LangError{ + token, + "invalid value for filename" + }; + } } const std::string &Filename::get() const { - return this->path; + return this->path; } ValueHolder Filename::copy() const { - return {std::make_shared(*this)}; + return {std::make_shared(*this)}; } void Filename::apply_value(const Value &value, nyan_op operation) { - const Filename &change = dynamic_cast(value); + const Filename &change = dynamic_cast(value); - // TODO: relative path resolution + // TODO: relative path resolution - switch (operation) { - case nyan_op::ASSIGN: - this->path = change.path; break; + switch (operation) { + case nyan_op::ASSIGN: + this->path = change.path; break; - default: - throw Error{"unknown operation requested"}; - } + default: + throw Error{"unknown operation requested"}; + } } std::string Filename::str() const { - return this->path; + return this->path; } std::string Filename::repr() const { - return this->str(); + return this->str(); } size_t Filename::hash() const { - return std::hash{}(this->path); + return std::hash{}(this->path); } bool Filename::equals(const Value &other) const { - auto &other_val = dynamic_cast(other); - return this->path == other_val.path; + auto &other_val = dynamic_cast(other); + return this->path == other_val.path; } const std::unordered_set &Filename::allowed_operations(const Type &with_type) const { - const static std::unordered_set ops{ - nyan_op::ASSIGN, - }; - - if (with_type.get_primitive_type() == primitive_t::FILENAME) { - return ops; - } - else { - return no_nyan_ops; - } + const static std::unordered_set ops{ + nyan_op::ASSIGN, + }; + + switch (with_type.get_primitive_type()) { + case primitive_t::FILENAME: + case primitive_t::NONE: + return ops; + + default: + return no_nyan_ops; + } } const BasicType &Filename::get_type() const { - constexpr static BasicType type{ - primitive_t::FILENAME, - container_t::SINGLE, - }; + constexpr static BasicType type{ + primitive_t::FILENAME, + composite_t::SINGLE, + }; - return type; + return type; } diff --git a/nyan/value/file.h b/nyan/value/file.h index ddbf8b4..8b7e89c 100644 --- a/nyan/value/file.h +++ b/nyan/value/file.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -17,24 +17,24 @@ class IDToken; */ class Filename : public Value { public: - Filename(const std::string &path); - Filename(const IDToken &token); + Filename(const std::string &path); + Filename(const IDToken &token); - const std::string &get() const; + const std::string &get() const; - ValueHolder copy() const override; - std::string str() const override; - std::string repr() const override; - size_t hash() const override; + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + size_t hash() const override; - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; protected: - void apply_value(const Value &value, nyan_op operation) override; - bool equals(const Value &other) const override; + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override; - std::string path; + std::string path; }; } // namespace nyan diff --git a/nyan/value/none.cpp b/nyan/value/none.cpp new file mode 100644 index 0000000..b618ef6 --- /dev/null +++ b/nyan/value/none.cpp @@ -0,0 +1,76 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. + +#include + +#include "none.h" + + +namespace nyan { + +None::None() = default; + + +ValueHolder None::copy() const { + throw InternalError("cannot copy None, hardcoded value should be referenced instead"); +} + + +void None::apply_value(const Value &/**value*/, nyan_op /**operation*/) { + throw InternalError("cannot apply to None: assign Value directly to member instead"); +} + + +std::string None::str() const { + return "None"; +} + + +std::string None::repr() const { + return this->str(); +} + + +size_t None::hash() const { + return std::hash{}("None"); +} + + +bool None::equals(const Value &other) const { + // Check address equality because we only want identity + return std::addressof(*this) == std::addressof(other); +} + + +const std::unordered_set &None::allowed_operations(const Type &with_type) const { + const static std::unordered_set ops{ + nyan_op::ASSIGN, + }; + + switch (with_type.get_primitive_type()) + { + case primitive_t::BOOLEAN: + case primitive_t::INT: + case primitive_t::FLOAT: + case primitive_t::TEXT: + case primitive_t::FILENAME: + case primitive_t::NONE: + case primitive_t::CONTAINER: + return ops; + + default: + return no_nyan_ops; + } +} + + +const BasicType &None::get_type() const { + constexpr static BasicType type{ + primitive_t::NONE, + composite_t::SINGLE, + }; + + return type; +} + + +} // namespace nyan diff --git a/nyan/value/none.h b/nyan/value/none.h new file mode 100644 index 0000000..cefe59c --- /dev/null +++ b/nyan/value/none.h @@ -0,0 +1,35 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. +#pragma once + +#include "value.h" + + +namespace nyan { + +class IDToken; + +/** + * Nyan value to store None (basically a "no value" placeholder). + */ +class None : public Value { +public: + None(); + + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + size_t hash() const override; + + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; + +protected: + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override; + +}; + +/** None value */ +const None NYAN_NONE = None(); + +} // namespace nyan diff --git a/nyan/value/number.cpp b/nyan/value/number.cpp index 5d73d11..1a406ac 100644 --- a/nyan/value/number.cpp +++ b/nyan/value/number.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "number.h" @@ -15,77 +15,374 @@ namespace nyan { -static void check_token(const IDToken &token, token_type expected) { - - using namespace std::string_literals; - - if (unlikely(token.get_type() != expected)) { - throw LangError{ - token, - "invalid value for number, expected "s - + token_type_str(expected) - }; - } +static void check_token(const IDToken &token, std::vector expected) { + + using namespace std::string_literals; + + if (unlikely(not (std::find(expected.begin(), + expected.end(), + token.get_type()) != expected.end()))) { + throw LangError{ + token, + "invalid value for number, expected "s + + util::strjoin( + " or ", + expected, + [] (const auto &token_type) { + return token_type_str(token_type); + } + ) + }; + } } template<> Int::Number(const IDToken &token) { - check_token(token, token_type::INT); - - try { - this->value = std::stoll(token.get_first(), nullptr, 0); - } - catch (std::invalid_argument &) { - throw InternalError{"int token was not an int"}; - } - catch (std::out_of_range &) { - throw LangError{token, "number out of range"}; - } + const static std::vector expected{ + token_type::INT, + token_type::INF, + }; + + check_token(token, expected); + + try { + if (token.get_type() == token_type::INF) { + if (token.str() == "inf") { + this->value = INT_POS_INF; + } + else { + this->value = INT_NEG_INF; + } + } + else { + this->value = std::stoll(token.get_first(), nullptr, 0); + } + } + catch (std::invalid_argument &) { + throw InternalError{"int token was not an int"}; + } + catch (std::out_of_range &) { + throw LangError{token, "number out of range"}; + } } template<> Float::Number(const IDToken &token) { - check_token(token, token_type::FLOAT); - - try { - this->value = std::stod(token.get_first()); - } - catch (std::invalid_argument &) { - throw InternalError{"float token was not a float"}; - } - catch (std::out_of_range &) { - throw LangError{token, "number out of range"}; - } + const static std::vector expected{ + token_type::FLOAT, + token_type::INF, + }; + + check_token(token, expected); + + try { + if (token.get_type() == token_type::INF) { + if (token.str() == "inf") { + this->value = FLOAT_POS_INF; + } + else { + this->value = FLOAT_NEG_INF; + } + } + else { + this->value = std::stod(token.get_first()); + } + } + catch (std::invalid_argument &) { + throw InternalError{"float token was not a float"}; + } + catch (std::out_of_range &) { + throw LangError{token, "number out of range"}; + } } -template -void Number::apply_value(const Value &value, nyan_op operation) { - const Number &change = dynamic_cast(value); +template<> +bool Number::is_infinite() const { + return (this->value == INT_POS_INF or this->value == INT_NEG_INF); +} + + +template<> +bool Number::is_infinite() const { + return (this->value == FLOAT_POS_INF or this->value == FLOAT_NEG_INF); +} - switch (operation) { - case nyan_op::ASSIGN: - this->value = change.value; break; - case nyan_op::ADD_ASSIGN: - this->value += change.value; break; +template +void Number::apply_value(const Value &value, nyan_op operation) { + auto applier = [](auto &member_value, auto operand, nyan_op operation) { + switch (operation) { + case nyan_op::ASSIGN: + member_value = operand; break; + + case nyan_op::ADD_ASSIGN: + member_value += operand; break; + + case nyan_op::SUBTRACT_ASSIGN: + member_value -= operand; break; + + case nyan_op::MULTIPLY_ASSIGN: + member_value *= operand; break; + + case nyan_op::DIVIDE_ASSIGN: + member_value /= operand; break; + + default: + throw Error{"unknown operation requested"}; + } + }; + + if (typeid(Float&) == typeid(value)) { + const Float &change = dynamic_cast(value); + + if (not (this->is_infinite() or change.is_infinite())) { + applier(this->value, change.get(), operation); + } + else { + Float left = static_cast(*this); + auto change_value = left.handle_infinity(change, operation); + applier(this->value, change_value, nyan_op::ASSIGN); + } + } + else if (typeid(Int&) == typeid(value)) { + const Int &change = dynamic_cast(value); + + if (not (this->is_infinite() or change.is_infinite())) { + applier(this->value, change.get(), operation); + } + else { + Int left = static_cast(*this); + auto change_value = left.handle_infinity(change, operation); + applier(this->value, change_value, nyan_op::ASSIGN); + } + } + else { + throw InternalError("expected Number instance for operation, but got" + + std::string(typeid(value).name())); + } +} - case nyan_op::SUBTRACT_ASSIGN: - this->value -= change.value; break; - case nyan_op::MULTIPLY_ASSIGN: - this->value *= change.value; break; +template<> +Int::storage_type Int::handle_infinity(const Int &other, nyan_op operation) { + + // Both values are infinite + if (this->is_infinite() and other.is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + return other.get(); + + case nyan_op::ADD_ASSIGN: { + if (this->value == other.get()) { + return this->value; + } + throw Error{"adding two inf values with different sign not permitted"}; + } + + case nyan_op::SUBTRACT_ASSIGN: { + if (this->value != other.get()) { + return this->value; + } + throw Error{"subtracting two inf values with different sign not permitted"}; + } + + case nyan_op::MULTIPLY_ASSIGN:{ + if (this->value == other.get()) { + return INT_POS_INF; + } + return INT_NEG_INF; + } + + case nyan_op::DIVIDE_ASSIGN: + throw Error{"dividing two inf values not permitted"}; + + default: + throw Error{"unknown operation requested"}; + } + } + // Only left operand is infinite + else if (this->is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + return other.get(); + + case nyan_op::ADD_ASSIGN: + case nyan_op::SUBTRACT_ASSIGN: + return this->value; + + case nyan_op::MULTIPLY_ASSIGN: { + if (other.get() == 0) { + throw Error{"multiplying inf with 0 not permitted"}; + } + else if (other.get() > 0) { + return this->value; + } + else if (this->value > 0) { + return INT_NEG_INF; + } + return INT_POS_INF; + } + + case nyan_op::DIVIDE_ASSIGN:{ + if (other.get() == 0) { + throw Error{"dividing inf by 0 not permitted"}; + } + return this->value; + } + + default: + throw Error{"unknown operation requested"}; + } + } + // Only right operand is infinite + else if (other.is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + case nyan_op::ADD_ASSIGN: + return other.get(); + + case nyan_op::SUBTRACT_ASSIGN: { + if (other.get() > 0) { + return INT_NEG_INF; + } + return INT_POS_INF; + } + + case nyan_op::MULTIPLY_ASSIGN:{ + if (this->value == 0) { + throw Error{"multiplying inf with 0 not permitted"}; + } + else if (this->value > 0) { + return other.get(); + } + else if (other.get() > 0) { + return INT_NEG_INF; + } + return INT_POS_INF; + } + + case nyan_op::DIVIDE_ASSIGN: + return 0; + + default: + throw Error{"unknown operation requested"}; + } + } + throw InternalError("expected at least one infinite operand"); +} - case nyan_op::DIVIDE_ASSIGN: - this->value /= change.value; break; - default: - throw Error{"unknown operation requested"}; - } +template<> +Float::storage_type Float::handle_infinity(const Float &other, nyan_op operation) { + + // Both values are infinite + if (this->is_infinite() and other.is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + return other.get(); + + case nyan_op::ADD_ASSIGN: { + if (this->value == other.get()) { + return this->value; + } + throw Error{"adding two inf values with different sign not permitted"}; + } + + case nyan_op::SUBTRACT_ASSIGN: { + if (this->value != other.get()) { + return this->value; + } + throw Error{"subtracting two inf values with different sign not permitted"}; + } + + case nyan_op::MULTIPLY_ASSIGN:{ + if (this->value == other.get()) { + return FLOAT_POS_INF; + } + return INT_NEG_INF; + } + + case nyan_op::DIVIDE_ASSIGN: + throw Error{"dividing two inf values not permitted"}; + + default: + throw Error{"unknown operation requested"}; + } + } + // Only left operand is infinite + else if (this->is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + return other.get(); + + case nyan_op::ADD_ASSIGN: + case nyan_op::SUBTRACT_ASSIGN: + return this->value; + + case nyan_op::MULTIPLY_ASSIGN: { + if (other.get() == 0) { + throw Error{"multiplying inf with 0 not permitted"}; + } + else if (other.get() > 0) { + return this->value; + } + else if (this->value > 0) { + return FLOAT_NEG_INF; + } + return FLOAT_POS_INF; + } + + case nyan_op::DIVIDE_ASSIGN:{ + if (other.get() == 0) { + throw Error{"dividing inf by 0 not permitted"}; + } + return this->value; + } + + default: + throw Error{"unknown operation requested"}; + } + } + // Only right operand is infinite + else if (other.is_infinite()) { + switch (operation) { + case nyan_op::ASSIGN: + case nyan_op::ADD_ASSIGN: + return other.get(); + + case nyan_op::SUBTRACT_ASSIGN: { + if (other.get() > 0) { + return FLOAT_NEG_INF; + } + return FLOAT_POS_INF; + } + + case nyan_op::MULTIPLY_ASSIGN:{ + if (this->value == 0) { + throw Error{"multiplying inf with 0 not permitted"}; + } + else if (this->value > 0) { + return other.get(); + } + else if (other.get() > 0) { + return FLOAT_NEG_INF; + } + return FLOAT_POS_INF; + } + + case nyan_op::DIVIDE_ASSIGN: + return 0; + + default: + throw Error{"unknown operation requested"}; + } + } + throw InternalError("expected at least one infinite operand"); } @@ -93,62 +390,70 @@ template const std::unordered_set & Number::allowed_operations(const Type &with_type) const { - const static std::unordered_set ops{ - nyan_op::ASSIGN, - nyan_op::ADD_ASSIGN, - nyan_op::MULTIPLY_ASSIGN, - nyan_op::SUBTRACT_ASSIGN, - nyan_op::DIVIDE_ASSIGN, - }; - - // all allowed number types - switch (with_type.get_primitive_type()) { - case primitive_t::FLOAT: - case primitive_t::INT: - return ops; - default: - return no_nyan_ops; - } + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + const static std::unordered_set ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + nyan_op::MULTIPLY_ASSIGN, + nyan_op::SUBTRACT_ASSIGN, + nyan_op::DIVIDE_ASSIGN, + }; + + // all allowed number types + switch (with_type.get_primitive_type()) { + case primitive_t::FLOAT: + case primitive_t::INT: + return ops; + + case primitive_t::NONE: + return none_ops; + + default: + return no_nyan_ops; + } } template<> const BasicType &Int::get_type() const { - constexpr static BasicType type{ - primitive_t::INT, - container_t::SINGLE - }; - return type; + constexpr static BasicType type{ + primitive_t::INT, + composite_t::SINGLE + }; + return type; } template<> const BasicType &Float::get_type() const { - constexpr static BasicType type{ - primitive_t::FLOAT, - container_t::SINGLE - }; + constexpr static BasicType type{ + primitive_t::FLOAT, + composite_t::SINGLE + }; - return type; + return type; } // explicit instantiation of member functions template void Number::apply_value( - const Value &value, nyan_op operation); + const Value &value, nyan_op operation); template void Number::apply_value( - const Value &value, nyan_op operation); + const Value &value, nyan_op operation); template const std::unordered_set & Number::allowed_operations( - const Type &with_type) const; + const Type &with_type) const; template const std::unordered_set & Number::allowed_operations( - const Type &with_type) const; + const Type &with_type) const; } // namespace nyan diff --git a/nyan/value/number.h b/nyan/value/number.h index d9ed15b..5719db5 100644 --- a/nyan/value/number.h +++ b/nyan/value/number.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -19,50 +19,62 @@ class IDToken; template class Number : public Value { public: - Number(const IDToken &token); - Number(T value) - : - value{value} {} - - ValueHolder copy() const override { - return {std::make_shared(*this)}; - } - - std::string str() const override { - return std::to_string(this->value); - } - - std::string repr() const override { - return this->str(); - } - - size_t hash() const override { - return std::hash{}(this->value); - } - - T get() const { - return *this; - } - - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; - - operator T() const { - return this->value; - } - - using storage_type = T; + Number(const IDToken &token); + Number(T value) + : + value{value} {} + + ValueHolder copy() const override { + return {std::make_shared(*this)}; + } + + std::string str() const override { + return std::to_string(this->value); + } + + std::string repr() const override { + return this->str(); + } + + size_t hash() const override { + return std::hash{}(this->value); + } + + T get() const { + return *this; + } + + /** + * Checks if the value is positive or negative infinity. + */ + bool is_infinite() const; + + /** + * Calculates the value that is assigned when one of the operands + * of apply_value() is infinity. This can be either -inf, inf or 0. + * Throws an InternalError if the value would be NaN. + */ + T handle_infinity(const Number &other, nyan_op operation); + + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; + + operator T() const { + return this->value; + } + + using storage_type = T; protected: - void apply_value(const Value &value, nyan_op operation) override; - bool equals(const Value &other) const override { - auto &other_val = dynamic_cast(other); - return this->value == other_val.value; - } - - /** - * Actual numerical value. - */ - T value; + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override { + auto &other_val = dynamic_cast(other); + return this->value == other_val.value; + } + + /** + * Actual numerical value. + */ + T value; }; diff --git a/nyan/value/object.cpp b/nyan/value/object.cpp index 5bd7dad..101d896 100644 --- a/nyan/value/object.cpp +++ b/nyan/value/object.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "object.h" @@ -11,75 +11,77 @@ namespace nyan { ObjectValue::ObjectValue(const fqon_t &name) - : - name{name} {} + : + name{name} {} ValueHolder ObjectValue::copy() const { - return {std::make_shared(*this)}; + return {std::make_shared(*this)}; } void ObjectValue::apply_value(const Value &value, nyan_op operation) { - const ObjectValue &change = dynamic_cast(value); + const ObjectValue &change = dynamic_cast(value); - switch (operation) { - case nyan_op::ASSIGN: - this->name = change.name; break; + switch (operation) { + case nyan_op::ASSIGN: + this->name = change.name; break; - default: - throw Error{"unknown operation requested"}; - } + default: + throw Error{"unknown operation requested"}; + } } std::string ObjectValue::str() const { - return this->name; + return this->name; } std::string ObjectValue::repr() const { - return this->str(); + return this->str(); } size_t ObjectValue::hash() const { - return std::hash{}(this->name); + return std::hash{}(this->name); } const fqon_t &ObjectValue::get() const { - return this->name; + return this->name; } bool ObjectValue::equals(const Value &other) const { - auto &other_val = dynamic_cast(other); - return this->name == other_val.name; + auto &other_val = dynamic_cast(other); + return this->name == other_val.name; } const std::unordered_set &ObjectValue::allowed_operations(const Type &with_type) const { - const static std::unordered_set ops{ - nyan_op::ASSIGN, - }; - - if (with_type.get_primitive_type() == primitive_t::OBJECT) { - return ops; - } - else { - return no_nyan_ops; - } + const static std::unordered_set ops{ + nyan_op::ASSIGN, + }; + + switch (with_type.get_primitive_type()) { + case primitive_t::OBJECT: + case primitive_t::NONE: + return ops; + + default: + return no_nyan_ops; + } } const BasicType &ObjectValue::get_type() const { - constexpr static BasicType type{ - primitive_t::OBJECT, - container_t::SINGLE, - }; + constexpr static BasicType type{ + primitive_t::OBJECT, + composite_t::SINGLE, + }; - return type; + return type; } diff --git a/nyan/value/object.h b/nyan/value/object.h index 59c0478..fdbe061 100644 --- a/nyan/value/object.h +++ b/nyan/value/object.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include "../config.h" @@ -13,24 +13,24 @@ namespace nyan { */ class ObjectValue : public Value { public: - ObjectValue(const fqon_t &name); + ObjectValue(const fqon_t &name); - ValueHolder copy() const override; - std::string str() const override; - std::string repr() const override; - size_t hash() const override; + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + size_t hash() const override; - /** return the stored fqon */ - const fqon_t &get() const; + /** return the stored fqon */ + const fqon_t &get() const; - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; protected: - void apply_value(const Value &value, nyan_op operation) override; - bool equals(const Value &other) const override; + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override; - fqon_t name; + fqon_t name; }; } // namespace nyan diff --git a/nyan/value/orderedset.cpp b/nyan/value/orderedset.cpp index b23534f..9374417 100644 --- a/nyan/value/orderedset.cpp +++ b/nyan/value/orderedset.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "orderedset.h" @@ -12,103 +12,110 @@ OrderedSet::OrderedSet() = default; OrderedSet::OrderedSet(std::vector &&values) { - for (auto &value : values) { - this->values.insert(std::move(value)); - } + for (auto &value : values) { + this->values.insert(std::move(value)); + } } ValueHolder OrderedSet::copy() const { - return {std::make_shared(*this)}; + return {std::make_shared(*this)}; } bool OrderedSet::add(const ValueHolder &value) { - return this->values.insert(value); + return this->values.insert(value); } bool OrderedSet::contains(const ValueHolder &value) const { - return this->values.contains(value); + return this->values.contains(value); } bool OrderedSet::remove(const ValueHolder &value) { - return (this->values.erase(value) == 1); + return (this->values.erase(value) == 1); } std::string OrderedSet::str() const { - std::ostringstream builder; - builder << "o{"; - builder << util::strjoin( - ", ", this->values, - [] (const auto &val) { - return val->str(); - } - ); - builder << "}"; - - return builder.str(); + std::ostringstream builder; + builder << "o{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val->str(); + } + ); + builder << "}"; + + return builder.str(); } std::string OrderedSet::repr() const { - std::ostringstream builder; - builder << "o{"; - builder << util::strjoin( - ", ", this->values, - [] (const auto &val) { - return val->repr(); - } - ); - builder << "}"; - return builder.str(); + std::ostringstream builder; + builder << "o{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val->repr(); + } + ); + builder << "}"; + return builder.str(); } const std::unordered_set &OrderedSet::allowed_operations(const Type &with_type) const { - if (not with_type.is_container()) { - return no_nyan_ops; - } - - const static std::unordered_set orderedset_ops{ - nyan_op::ASSIGN, - nyan_op::ADD_ASSIGN, - nyan_op::SUBTRACT_ASSIGN, - nyan_op::INTERSECT_ASSIGN, - }; - - const static std::unordered_set set_ops{ - nyan_op::ASSIGN, - nyan_op::ADD_ASSIGN, - nyan_op::UNION_ASSIGN, - nyan_op::SUBTRACT_ASSIGN, - nyan_op::INTERSECT_ASSIGN, - }; - - - switch (with_type.get_container_type()) { - case container_t::ORDEREDSET: - return orderedset_ops; - - case container_t::SET: - return set_ops; - - default: - return no_nyan_ops; - } + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + const static std::unordered_set orderedset_ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + nyan_op::SUBTRACT_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + const static std::unordered_set set_ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + nyan_op::UNION_ASSIGN, + nyan_op::SUBTRACT_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + if (with_type.get_primitive_type() == primitive_t::NONE) { + return none_ops; + } + + if (not with_type.is_container()) { + return no_nyan_ops; + } + + switch (with_type.get_composite_type()) { + case composite_t::ORDEREDSET: + return orderedset_ops; + + case composite_t::SET: + return set_ops; + + default: + return no_nyan_ops; + } } const BasicType &OrderedSet::get_type() const { - constexpr static BasicType type{ - primitive_t::CONTAINER, - container_t::ORDEREDSET, - }; + constexpr static BasicType type{ + primitive_t::CONTAINER, + composite_t::ORDEREDSET, + }; - return type; + return type; } } // namespace nyan diff --git a/nyan/value/orderedset.h b/nyan/value/orderedset.h index d3f3169..de4c07c 100644 --- a/nyan/value/orderedset.h +++ b/nyan/value/orderedset.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -18,26 +18,26 @@ class Value; * Nyan value to store an ordered set of things. */ class OrderedSet - : public SetBase { + : public SetBase { - // fetch the constructors - using SetBase::SetBase; + // fetch the constructors + using SetBase::SetBase; public: - OrderedSet(); - OrderedSet(std::vector &&values); + OrderedSet(); + OrderedSet(std::vector &&values); - std::string str() const override; - std::string repr() const override; + std::string str() const override; + std::string repr() const override; - ValueHolder copy() const override; + ValueHolder copy() const override; - bool add(const ValueHolder &value) override; - bool contains(const ValueHolder &value) const override; - bool remove(const ValueHolder &value) override; + bool add(const ValueHolder &value) override; + bool contains(const ValueHolder &value) const override; + bool remove(const ValueHolder &value) override; - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; }; } // namespace nyan diff --git a/nyan/value/set.cpp b/nyan/value/set.cpp index ea9bed4..432a481 100644 --- a/nyan/value/set.cpp +++ b/nyan/value/set.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "set.h" @@ -12,104 +12,112 @@ Set::Set() = default; Set::Set(std::vector &&values) { - for (auto &value : values) { - this->values.insert(std::move(value)); - } + for (auto &value : values) { + this->values.insert(std::move(value)); + } } ValueHolder Set::copy() const { - return {std::make_shared(*this)}; + return {std::make_shared(*this)}; } bool Set::add(const ValueHolder &value) { - return std::get<1>(this->values.insert(value)); + return std::get<1>(this->values.insert(value)); } bool Set::contains(const ValueHolder &value) const { - return (this->values.find(value) != std::end(this->values)); + return (this->values.find(value) != std::end(this->values)); } bool Set::remove(const ValueHolder &value) { - return (1 == this->values.erase(value)); + return (1 == this->values.erase(value)); } std::string Set::str() const { - // same as repr(), except we use str(). - - std::ostringstream builder; - builder << "{"; - builder << util::strjoin( - ", ", this->values, - [] (const auto &val) { - return val->str(); - } - ); - builder << "}"; - - return builder.str(); + // same as repr(), except we use str(). + + std::ostringstream builder; + builder << "{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val->str(); + } + ); + builder << "}"; + + return builder.str(); } std::string Set::repr() const { - // same as str(), except we use repr(). - - std::ostringstream builder; - builder << "{"; - builder << util::strjoin( - ", ", this->values, - [] (const auto &val) { - return val->repr(); - } - ); - builder << "}"; - return builder.str(); + // same as str(), except we use repr(). + + std::ostringstream builder; + builder << "{"; + builder << util::strjoin( + ", ", this->values, + [] (const auto &val) { + return val->repr(); + } + ); + builder << "}"; + return builder.str(); } const std::unordered_set &Set::allowed_operations(const Type &with_type) const { - if (not with_type.is_container()) { - return no_nyan_ops; - } - - const static std::unordered_set set_ops{ - nyan_op::ASSIGN, - nyan_op::ADD_ASSIGN, - nyan_op::UNION_ASSIGN, - nyan_op::SUBTRACT_ASSIGN, - nyan_op::INTERSECT_ASSIGN, - }; - - const static std::unordered_set orderedset_ops{ - nyan_op::SUBTRACT_ASSIGN, - nyan_op::INTERSECT_ASSIGN, - }; - - switch (with_type.get_container_type()) { - case container_t::SET: - return set_ops; - - case container_t::ORDEREDSET: - return orderedset_ops; - - default: - return no_nyan_ops; - } + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + const static std::unordered_set set_ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + nyan_op::UNION_ASSIGN, + nyan_op::SUBTRACT_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + const static std::unordered_set orderedset_ops{ + nyan_op::SUBTRACT_ASSIGN, + nyan_op::INTERSECT_ASSIGN, + }; + + if (with_type.get_primitive_type() == primitive_t::NONE) { + return none_ops; + } + + if (not with_type.is_container()) { + return no_nyan_ops; + } + + switch (with_type.get_composite_type()) { + case composite_t::SET: + return set_ops; + + case composite_t::ORDEREDSET: + return orderedset_ops; + + default: + return no_nyan_ops; + } } const BasicType &Set::get_type() const { - constexpr static BasicType type{ - primitive_t::CONTAINER, - container_t::SET, - }; + constexpr static BasicType type{ + primitive_t::CONTAINER, + composite_t::SET, + }; - return type; + return type; } } // namespace nyan diff --git a/nyan/value/set.h b/nyan/value/set.h index bf52fe3..2dc1eff 100644 --- a/nyan/value/set.h +++ b/nyan/value/set.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -11,34 +11,31 @@ namespace nyan { -/** datatype used for ordered set storage */ -using set_t = std::unordered_set; - /** - * Value to store a unordered set of things. + * Nyan value to store a unordered set of things. */ class Set - : public SetBase { + : public SetBase { - // fetch the constructors - using SetBase::SetBase; + // fetch the constructors + using SetBase::SetBase; public: - Set(); - Set(std::vector &&values); + Set(); + Set(std::vector &&values); - std::string str() const override; - std::string repr() const override; + std::string str() const override; + std::string repr() const override; - ValueHolder copy() const override; + ValueHolder copy() const override; - bool add(const ValueHolder &value) override; - bool contains(const ValueHolder &value) const override; - bool remove(const ValueHolder &value) override; + bool add(const ValueHolder &value) override; + bool contains(const ValueHolder &value) const override; + bool remove(const ValueHolder &value) override; - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; }; } // namespace nyan diff --git a/nyan/value/set_base.h b/nyan/value/set_base.h index 0763d15..fe845a0 100644 --- a/nyan/value/set_base.h +++ b/nyan/value/set_base.h @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -25,42 +25,42 @@ class Set; template class SetIterator : public ContainerIterBase { public: - using this_type = SetIterator; - using base_type = ContainerIterBase; - - explicit SetIterator(iter_type &&iter) - : - iterator{std::move(iter)} {} - - /** - * Advance the iterator to the next element in the set. - */ - base_type &operator ++() override { - ++this->iterator; - return *this; - } - - /** - * Get the element the iterator is currently pointing to. - */ - elem_type &operator *() const override { - // unpack the ValueHolder! - return *(*this->iterator); - } + using this_type = SetIterator; + using base_type = ContainerIterBase; + + explicit SetIterator(iter_type &&iter) + : + iterator{std::move(iter)} {} + + /** + * Advance the iterator to the next element in the set. + */ + base_type &operator ++() override { + ++this->iterator; + return *this; + } + + /** + * Get the element the iterator is currently pointing to. + */ + elem_type &operator *() const override { + // unpack the ValueHolder! + return *(*this->iterator); + } protected: - /** - * compare two iterators - */ - bool equals(const base_type &other) const override { - auto other_me = dynamic_cast(other); - return (this->iterator == other_me.iterator); - } - - /** - * The wrapped std::iterator, from the Set std::unordered_set. - */ - iter_type iterator; + /** + * compare two iterators + */ + bool equals(const base_type &other) const override { + auto other_me = dynamic_cast(other); + return (this->iterator == other_me.iterator); + } + + /** + * The wrapped std::iterator, from the Set std::unordered_set. + */ + iter_type iterator; }; @@ -72,228 +72,228 @@ class SetIterator : public ContainerIterBase { template class SetBase : public Container { public: - using Container::iterator; - using Container::const_iterator; - using Container::holder_iterator; - using Container::holder_const_iterator; + using Container::iterator; + using Container::const_iterator; + using Container::holder_iterator; + using Container::holder_const_iterator; - using value_storage = T; - using element_type = typename value_storage::value_type; - using value_const_iterator = typename value_storage::const_iterator; + using value_storage = T; + using element_type = typename value_storage::value_type; + using value_const_iterator = typename value_storage::const_iterator; - SetBase() = default; - virtual ~SetBase() = default; + SetBase() = default; + virtual ~SetBase() = default; - size_t hash() const override { - throw APIError{"Sets are not hashable."}; - } + size_t hash() const override { + throw APIError{"Sets are not hashable."}; + } - size_t size() const override { - return this->values.size(); - } + size_t size() const override { + return this->values.size(); + } - - void clear() { - this->values.clear(); - } + + void clear() { + this->values.clear(); + } - const value_storage &get() const { - return this->values; - } + const value_storage &get() const { + return this->values; + } - iterator begin() override { - throw Error{ - "Sets are not non-const-iterable. " - "make it const by using e.g. " - "for (auto &it = std::as_const(container))" - }; - } + iterator begin() override { + throw Error{ + "Sets are only const-iterable. " + "make it const by using e.g. " + "for (auto &it = std::as_const(container))" + }; + } - iterator end() override { - // also throw the error above. - return this->begin(); - } + iterator end() override { + // also throw the error above. + return this->begin(); + } - const_iterator begin() const override { - // uuuh yes. this creates an iterator to the contained elements. - // the iterator itself is a stack object, which relays the calls - // to the actual iterator. - // - // does semi-magic: - // We create a heap-allocated iterator and then wrap it - // to do the virtual calls. - // It is designed to be callable by a generic interface - // that all Containers support. - // - // iterator::elem_type = the single element type of the iteration. - // Set = the target set class, - // which is non-const in this begin() - // implementation, - // but not in the begin() below. - // (this, true) = use this set as target, use the beginning. - auto real_iterator = std::make_unique< - SetIterator>(std::begin(this->values)); + const_iterator begin() const override { + // uuuh yes. this creates an iterator to the contained elements. + // the iterator itself is a stack object, which relays the calls + // to the actual iterator. + // + // does semi-magic: + // We create a heap-allocated iterator and then wrap it + // to do the virtual calls. + // It is designed to be callable by a generic interface + // that all Containers support. + // + // iterator::elem_type = the single element type of the iteration. + // Set = the target set class, + // which is non-const in this begin() + // implementation, + // but not in the begin() below. + // (this, true) = use this set as target, use the beginning. + auto real_iterator = std::make_unique< + SetIterator>(std::begin(this->values)); - return const_iterator{std::move(real_iterator)}; - } + return const_iterator{std::move(real_iterator)}; + } - const_iterator end() const override { - // see explanation in the begin() above - auto real_iterator = std::make_unique< - SetIterator>(std::end(this->values)); + const_iterator end() const override { + // see explanation in the begin() above + auto real_iterator = std::make_unique< + SetIterator>(std::end(this->values)); - return const_iterator{std::move(real_iterator)}; - } + return const_iterator{std::move(real_iterator)}; + } - holder_iterator values_begin() override { - throw Error{ - "Set values holders are not non-const-iterable." - }; - } + holder_iterator values_begin() override { + throw Error{ + "Set values holders are not non-const-iterable." + }; + } - holder_iterator values_end() override { - // also throw the error above. - return this->values_begin(); - } + holder_iterator values_end() override { + // also throw the error above. + return this->values_begin(); + } - /** - * Get an iterator to the underlying set storage. - * Contrary to the above, this will allow to get the - * ValueHolders. - */ - holder_const_iterator values_begin() const override { - auto real_iterator = std::make_unique< - DefaultIterator>( - std::begin(this->values) - ); + /** + * Get an iterator to the underlying set storage. + * Contrary to the above, this will allow to get the + * ValueHolders. + */ + holder_const_iterator values_begin() const override { + auto real_iterator = std::make_unique< + DefaultIterator>( + std::begin(this->values) + ); - return holder_const_iterator{std::move(real_iterator)}; - } + return holder_const_iterator{std::move(real_iterator)}; + } - /** - * Iterator to end of the underlying storage. - */ - holder_const_iterator values_end() const override { - auto real_iterator = std::make_unique< - DefaultIterator>( - std::end(this->values) - ); - - return holder_const_iterator{std::move(real_iterator)}; - } + /** + * Iterator to end of the underlying storage. + */ + holder_const_iterator values_end() const override { + auto real_iterator = std::make_unique< + DefaultIterator>( + std::end(this->values) + ); + + return holder_const_iterator{std::move(real_iterator)}; + } protected: - /** - * Update this set with another set with the given operation. - */ - void apply_value(const Value &value, nyan_op operation) override { - const Container *change = dynamic_cast(&value); - - if (unlikely(change == nullptr)) { - using namespace std::string_literals; - throw InternalError{ - "set value application was not a container, it was: "s - + util::demangle(typeid(value).name()) - + " and couldn't cast to " - + util::demangle(typeid(change).name())}; - } - - switch (operation) { - case nyan_op::ASSIGN: - this->values.clear(); - // fall through - - case nyan_op::UNION_ASSIGN: - case nyan_op::ADD_ASSIGN: { - auto it = change->values_begin(); - for (auto end = change->values_end(); it != end; ++it) { - this->values.insert(*it); - } - break; - } - - case nyan_op::SUBTRACT_ASSIGN: { - auto it = change->values_begin(); - for (auto end = change->values_end(); it != end; ++it) { - this->values.erase(*it); - } - break; - } - - case nyan_op::INTERSECT_ASSIGN: { - // only keep the values that are in both. - - std::vector keep; - keep.reserve(this->values.size()); - - // as an intersection with a set is allowed, - // we have to walk over the ordered set - // instead of the set value to keep the order. - auto it = this->values_begin(); - for (auto end = this->values_end(); it != end; ++it) { - if (change->contains(*it)) { - keep.push_back(*it); - } - } - - this->values.clear(); - - for (auto &value : keep) { - this->values.insert(value); - } - - break; - } - - default: - throw InternalError{"unknown set value application"}; - } - } - - - /** - * test if the same values are in those sets - */ - bool equals(const Value &other) const override { - auto &other_val = dynamic_cast(other); - - // TODO: this only compares for set values, - // but for the orderedset, the order might matter! - - if (this->size() != other_val.size()) { - return false; - } - - auto it = this->values_begin(); - for (auto end = this->values_end(); it != end; ++it) { - if (not other_val.contains(*it)) { - return false; - } - } - - return true; - } - - /** - * Set value storage. - * Type is determined by the set specialization. - */ - value_storage values; + /** + * Update this set with another set with the given operation. + */ + void apply_value(const Value &value, nyan_op operation) override { + const Container *change = dynamic_cast(&value); + + if (unlikely(change == nullptr)) { + using namespace std::string_literals; + throw InternalError{ + "set value application was not a container, it was: "s + + util::demangle(typeid(value).name()) + + " and couldn't cast to " + + util::demangle(typeid(change).name())}; + } + + switch (operation) { + case nyan_op::ASSIGN: + this->values.clear(); + // fall through + + case nyan_op::UNION_ASSIGN: + case nyan_op::ADD_ASSIGN: { + auto it = change->values_begin(); + for (auto end = change->values_end(); it != end; ++it) { + this->values.insert(*it); + } + break; + } + + case nyan_op::SUBTRACT_ASSIGN: { + auto it = change->values_begin(); + for (auto end = change->values_end(); it != end; ++it) { + this->values.erase(*it); + } + break; + } + + case nyan_op::INTERSECT_ASSIGN: { + // only keep the values that are in both. + + std::vector keep; + keep.reserve(this->values.size()); + + // as an intersection with a set is allowed, + // we have to walk over the ordered set + // instead of the set value to keep the order. + auto it = this->values_begin(); + for (auto end = this->values_end(); it != end; ++it) { + if (change->contains(*it)) { + keep.push_back(*it); + } + } + + this->values.clear(); + + for (auto &value : keep) { + this->values.insert(value); + } + + break; + } + + default: + throw InternalError{"unknown set value application"}; + } + } + + + /** + * test if the same values are in those sets + */ + bool equals(const Value &other) const override { + auto &other_val = dynamic_cast(other); + + // TODO: this only compares for set values, + // but for the orderedset, the order might matter! + + if (this->size() != other_val.size()) { + return false; + } + + auto it = this->values_begin(); + for (auto end = this->values_end(); it != end; ++it) { + if (not other_val.contains(*it)) { + return false; + } + } + + return true; + } + + /** + * Set value storage. + * Type is determined by the set specialization. + */ + value_storage values; }; } // namespace nyan diff --git a/nyan/value/set_types.h b/nyan/value/set_types.h index cd12262..7a42c4a 100644 --- a/nyan/value/set_types.h +++ b/nyan/value/set_types.h @@ -1,4 +1,4 @@ -// Copyright 2019-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2019-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -9,7 +9,7 @@ namespace nyan { -/** datatype used for ordered set storage */ +/** datatype used for (unordered) set storage */ using set_t = std::unordered_set; diff --git a/nyan/value/text.cpp b/nyan/value/text.cpp index df0c3ac..6770848 100644 --- a/nyan/value/text.cpp +++ b/nyan/value/text.cpp @@ -1,4 +1,4 @@ -// Copyright 2016-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "text.h" @@ -14,87 +14,95 @@ namespace nyan { Text::Text(const std::string &value) - : - value{value} {} + : + value{value} {} Text::Text(const IDToken &token) - : - Text{token.get_first()} { - - if (unlikely(token.get_type() != token_type::STRING)) { - throw LangError{ - token, - "invalid value for text" - }; - } + : + Text{token.get_first()} { + + if (unlikely(token.get_type() != token_type::STRING)) { + throw LangError{ + token, + "invalid value for text" + }; + } } ValueHolder Text::copy() const { - return {std::make_shared(*this)}; + return {std::make_shared(*this)}; } void Text::apply_value(const Value &value, nyan_op operation) { - const Text &change = dynamic_cast(value); + const Text &change = dynamic_cast(value); - switch (operation) { - case nyan_op::ASSIGN: - this->value = change.value; break; + switch (operation) { + case nyan_op::ASSIGN: + this->value = change.value; break; - case nyan_op::ADD_ASSIGN: - this->value += change.value; break; + case nyan_op::ADD_ASSIGN: + this->value += change.value; break; - default: - throw Error{"unknown operation requested"}; - } + default: + throw Error{"unknown operation requested"}; + } } std::string Text::str() const { - return this->value; + return this->value; } std::string Text::repr() const { - return this->str(); + return this->str(); } size_t Text::hash() const { - return std::hash{}(this->value); + return std::hash{}(this->value); } bool Text::equals(const Value &other) const { - auto &other_val = dynamic_cast(other); - return this->value == other_val.value; + auto &other_val = dynamic_cast(other); + return this->value == other_val.value; } const std::unordered_set &Text::allowed_operations(const Type &with_type) const { - const static std::unordered_set ops{ - nyan_op::ASSIGN, - nyan_op::ADD_ASSIGN, - }; - - if (with_type.get_primitive_type() == primitive_t::TEXT) { - return ops; - } - else { - return no_nyan_ops; - } + const static std::unordered_set ops{ + nyan_op::ASSIGN, + nyan_op::ADD_ASSIGN, + }; + + const static std::unordered_set none_ops{ + nyan_op::ASSIGN, + }; + + switch (with_type.get_primitive_type()) { + case primitive_t::TEXT: + return ops; + + case primitive_t::NONE: + return none_ops; + + default: + return no_nyan_ops; + } } const BasicType &Text::get_type() const { - constexpr static BasicType type{ - primitive_t::TEXT, - container_t::SINGLE, - }; + constexpr static BasicType type{ + primitive_t::TEXT, + composite_t::SINGLE, + }; - return type; + return type; } diff --git a/nyan/value/text.h b/nyan/value/text.h index b353e29..73153e6 100644 --- a/nyan/value/text.h +++ b/nyan/value/text.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once @@ -16,34 +16,34 @@ class IDToken; */ class Text : public Value { public: - Text(const std::string &value); - Text(const IDToken &token); + Text(const std::string &value); + Text(const IDToken &token); - ValueHolder copy() const override; - std::string str() const override; - std::string repr() const override; - size_t hash() const override; + ValueHolder copy() const override; + std::string str() const override; + std::string repr() const override; + size_t hash() const override; - const std::string &get() const { - return *this; - } + const std::string &get() const { + return *this; + } - const std::unordered_set &allowed_operations(const Type &with_type) const override; - const BasicType &get_type() const override; + const std::unordered_set &allowed_operations(const Type &with_type) const override; + const BasicType &get_type() const override; - operator const std::string&() const { - return this->value; - } + operator const std::string&() const { + return this->value; + } - operator const char *() const { - return this->value.c_str(); - } + operator const char *() const { + return this->value.c_str(); + } protected: - void apply_value(const Value &value, nyan_op operation) override; - bool equals(const Value &other) const override; + void apply_value(const Value &value, nyan_op operation) override; + bool equals(const Value &other) const override; - std::string value; + std::string value; }; } // namespace nyan diff --git a/nyan/value/value.cpp b/nyan/value/value.cpp index 6e18b10..3219bba 100644 --- a/nyan/value/value.cpp +++ b/nyan/value/value.cpp @@ -1,9 +1,11 @@ -// Copyright 2016-2018 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "value.h" #include "boolean.h" +#include "dict.h" #include "file.h" +#include "none.h" #include "number.h" #include "object.h" #include "orderedset.h" @@ -18,124 +20,364 @@ namespace nyan { -static ValueHolder value_from_value_token(const Type &target_type, - const IDToken &value_token, - const std::function &get_obj_value) { - - switch (target_type.get_primitive_type()) { - case primitive_t::BOOLEAN: - return {std::make_shared(value_token)}; - - case primitive_t::TEXT: - return {std::make_shared(value_token)}; - - case primitive_t::INT: - return {std::make_shared(value_token)}; - - case primitive_t::FLOAT: - return {std::make_shared(value_token)}; - - case primitive_t::FILENAME: { - // TODO: make relative to current namespace - return {std::make_shared(value_token)}; - } - case primitive_t::OBJECT: { +/** + * Create a ValueHolder from an IDToken. + * + * @param target_types Target type of the value. + * @param id_token IDToken from which values are extracted. + * @param get_fqon Function for retrieving an object identifier from + * an IDToken. + * + * @return A ValueHolder with the created value. + */ +static ValueHolder value_from_id_token(const Type &target_type, + const IDToken &id_token, + const std::function &get_fqon) { + + switch (target_type.get_primitive_type()) { + case primitive_t::BOOLEAN: + return {std::make_shared(id_token)}; + + case primitive_t::TEXT: + return {std::make_shared(id_token)}; + + case primitive_t::INT: { + if (id_token.get_type() == token_type::INF) { + return {std::make_shared(id_token)}; + } + else if (id_token.get_type() == token_type::INT) { + return {std::make_shared(id_token)}; + } + else if (id_token.get_type() == token_type::FLOAT) { + return {std::make_shared(id_token)}; + } + throw LangError{ + id_token, + "invalid token for int, expected int or inf" + }; + } + case primitive_t::FLOAT: { + if (id_token.get_type() == token_type::INF) { + return {std::make_shared(id_token)}; + } + else if (id_token.get_type() == token_type::INT) { + return {std::make_shared(id_token)}; + } + else if (id_token.get_type() == token_type::FLOAT) { + return {std::make_shared(id_token)}; + } + throw LangError{ + id_token, + "invalid token for float, expected float or inf" + }; + } + case primitive_t::FILENAME: { + // TODO: make relative to current namespace + return {std::make_shared(id_token)}; + } + case primitive_t::OBJECT: { + if (unlikely(id_token.get_type() != token_type::ID)) { + throw LangError{ + id_token, + "invalid value for object, expecting object id" + }; + } + + fqon_t obj_id = get_fqon(id_token); + + return {std::make_shared(std::move(obj_id))}; + } + default: + throw InternalError{"non-implemented primitive value type"}; + } + + return {nullptr}; +} - if (unlikely(value_token.get_type() != token_type::ID)) { - throw LangError{ - value_token, - "invalid value for object, expecting object id" - }; - } - fqon_t obj_id = get_obj_value(target_type, value_token); +/** + * Create ValueHolders from a ValueToken. + * + * @param target_types List of target types of the values in the token. Must have + * the same size as the value token. + * @param value_token Value token from which values are extracted. + * @param get_fqon Function for retrieving an object identifier from + * an IDToken. + * + * @return A list of ValueHolders with the created values. + */ +static std::vector value_from_value_token(const std::vector &target_types, + const ValueToken &value_token, + const std::function &get_fqon) { + if (unlikely(target_types.size() != value_token.get_value().size())) { + throw TypeError( + value_token.get_start_location(), + std::string("ValueToken has ") + + std::to_string(value_token.get_value().size()) + + " elements, but only " + + std::to_string(target_types.size()) + + " have been requested" + ); + } + + std::vector values; + values.reserve(value_token.get_value().size()); + + if (value_token.is_none()) { + values.emplace_back(std::make_shared(NYAN_NONE)); + return values; + } + + for (unsigned int i = 0; i < value_token.get_value().size(); i++) { + values.push_back(value_from_id_token( + target_types.at(i), + value_token.get_value().at(i), + get_fqon + ) + ); + } + + return values; +} - return {std::make_shared(std::move(obj_id))}; - } - default: - throw InternalError{"non-implemented value type"}; - } - return {nullptr}; +/** + * Handle modifiers of the member type, i.e. perform necessary modifications + * and checks on the created value. + * + * @param modifiers List of modifier ordered by inner mod --> outer mod. + * @param value_holder Value that is modified/checked. + * @param objs_in_values Pointer to the list of object identifiers in + * values at load time. + */ +static void handle_modifiers(const std::vector &modifiers, + const ValueHolder &value_holder, + std::vector> *objs_in_values) { + bool contains_optional = false; + for (auto &mod: modifiers) { + auto modifier_type = mod.get_composite_type(); + + if (modifier_type == composite_t::OPTIONAL) { + contains_optional = true; + } + } + + if (typeid(*value_holder.get_value()) == typeid(None&) and not contains_optional) { + throw InternalError{"NoneValue is content, but no optional modifier was found"}; + } + + for (auto &mod: modifiers) { + auto modifier_type = mod.get_composite_type(); + + switch (modifier_type) { + case composite_t::OPTIONAL: { + contains_optional = true; + break; + } + + case composite_t::CHILDREN: { + if (unlikely(typeid(*value_holder.get_value()) != typeid(ObjectValue&))) { + if (not contains_optional) { + throw InternalError{"children type requires ObjectValue as content"}; + } + } + + // Check if object fqon is a child (i.e. not the same fqon as the member type) + ObjectValue obj = dynamic_cast(*value_holder.get_value()); + fqon_t member_type_fqon = mod.get_element_type()->at(0).get_fqon(); + + if (obj.get() == member_type_fqon) { + throw InternalError{"children type does not allow an ObjectValue with same fqon as the member type"}; + } + break; + } + + case composite_t::ABSTRACT: { + if (unlikely(typeid(*value_holder.get_value()) != typeid(ObjectValue&))) { + if (not contains_optional) { + throw InternalError{"abstract type requires ObjectValue as content"}; + } + } + + // Remove last element here, so the object is not checked + // for non-abstractness later + objs_in_values->pop_back(); + break; + } + + default: + break; + } + } } ValueHolder Value::from_ast(const Type &target_type, const ASTMemberValue &astmembervalue, - const std::function &get_obj_value) { - - // TODO: someday values may be nested more than one level. - // then this function must be boosted a bit. - - if (not target_type.is_container()) { - // don't allow more than one value for a single-value type - if (astmembervalue.get_values().size() > 1) { - throw TypeError{ - astmembervalue.get_values()[1], - "storing multiple values in non-container member" - }; - } - - return value_from_value_token(target_type, - astmembervalue.get_values()[0], - get_obj_value); - } - - // process multi-value values (orderedsets etc) - std::vector values; - values.reserve(astmembervalue.get_values().size()); - - // convert all tokens to values - const Type *element_type = target_type.get_element_type(); - if (unlikely(element_type == nullptr)) { - throw InternalError{"container element type is nonexisting"}; - } - - for (auto &value_token : astmembervalue.get_values()) { - values.push_back( - value_from_value_token(*element_type, value_token, - get_obj_value) - ); - } - - // switch by container type determined in the ast, - // which can be different than the target_type. - switch (astmembervalue.get_container_type()) { - case container_t::SET: - // create a set from the value list - return {std::make_shared(std::move(values))}; - - case container_t::ORDEREDSET: - return {std::make_shared(std::move(values))}; - - default: - throw InternalError{"value creation for unhandled container type"}; - } + std::vector> *objs_in_values, + const std::function &get_fqon, + const std::function(const fqon_t &)> &get_obj_lin) { + // Save the modifiers in reverse order + // we will check them after we receive a value + Type current_type = target_type; + std::vector modifiers; + while (current_type.is_modifier()) { + modifiers.insert(modifiers.begin(), current_type); + current_type = current_type.get_element_type()->at(0); + } + + if (not current_type.is_container()) { + // don't allow more than one value for a single-value type + if (astmembervalue.get_values().size() > 1) { + throw TypeError{ + astmembervalue.get_values()[1].get_start_location(), + "storing multiple values in non-container" + }; + } + + std::vector target_types{current_type}; + ValueHolder value = value_from_value_token(target_types, + astmembervalue.get_values()[0], + get_fqon)[0]; + + if (target_type.is_object()) { + // Check if member type is in object linearization of the value + ObjectValue obj = dynamic_cast(*value.get_value()); + std::vector obj_lin = get_obj_lin(obj.get()); + + if (unlikely(not util::contains(obj_lin, target_type.get_fqon()))) { + throw InternalError{"object is not in linearization of member type"}; + } + } + + // Checks if value adheres to applied modifiers + handle_modifiers(modifiers, value, objs_in_values); + + return value; + } + + composite_t composite_type = current_type.get_composite_type(); + + switch (composite_type) + { + case composite_t::ORDEREDSET: + case composite_t::SET: { + std::vector values; + + // process multi-value values (orderedsets etc) + values.reserve(astmembervalue.get_values().size()); + + // convert all tokens to values + const std::vector *element_type = current_type.get_element_type(); + if (unlikely(element_type == nullptr)) { + throw InternalError{"container element type is nonexisting"}; + } + + Type cur_elem_type = element_type->at(0); + std::vector elem_modifiers; + while (cur_elem_type.is_modifier()) { + elem_modifiers.insert(elem_modifiers.begin(), cur_elem_type); + cur_elem_type = cur_elem_type.get_element_type()->at(0); + } + + std::vector target_types{cur_elem_type}; + for (auto &value_token : astmembervalue.get_values()) { + ValueHolder value = value_from_value_token( + target_types, + value_token, + get_fqon + )[0]; + + handle_modifiers(elem_modifiers, value, objs_in_values); + + values.push_back(value); + + } + + switch (composite_type) { + case composite_t::SET: + // create a set from the value list + return {std::make_shared(std::move(values))}; + + case composite_t::ORDEREDSET: + return {std::make_shared(std::move(values))}; + + default: + throw InternalError{"value creation for unhandled container type"}; + } + } + + case composite_t::DICT: { + std::unordered_map items; + + items.reserve(astmembervalue.get_values().size()); + + // convert all tokens to values + const std::vector *element_type = target_type.get_element_type(); + if (unlikely(element_type == nullptr)) { + throw InternalError{"container element type is nonexisting"}; + } + + Type cur_elem_type_key = element_type->at(0); + std::vector elem_modifiers_key; + while (cur_elem_type_key.is_modifier()) { + elem_modifiers_key.insert(elem_modifiers_key.begin(), cur_elem_type_key); + cur_elem_type_key = cur_elem_type_key.get_element_type()->at(0); + } + + Type cur_elem_type_value = element_type->at(1); + std::vector elem_modifiers_value; + while (cur_elem_type_value.is_modifier()) { + elem_modifiers_value.insert(elem_modifiers_value.begin(), cur_elem_type_value); + cur_elem_type_value = cur_elem_type_value.get_element_type()->at(0); + } + + std::vector target_types{cur_elem_type_key, cur_elem_type_value}; + + for (auto &value_token : astmembervalue.get_values()) { + std::vector keyval = value_from_value_token( + target_types, + value_token, + get_fqon + ); + + handle_modifiers(elem_modifiers_key, keyval[0], objs_in_values); + handle_modifiers(elem_modifiers_value, keyval[1], objs_in_values); + + items.insert(std::make_pair(keyval[0], keyval[1])); + } + + return {std::make_shared(std::move(items))}; + } + + default: + throw InternalError{"value creation for unhandled container type"}; + } } void Value::apply(const Member &change) { - // extract the member's value, - // this is just the data of the member, - // no parent data is included. - const Value &value = change.get_value(); + // extract the member's value, + // this is just the data of the member, + // no parent data is included. + const Value &value = change.get_value(); - // TODO: cache usage: if the value has a cached value, - // stop the patch loop and just use this value. - // BUT this will fail if one is in a diamond? + // TODO: cache usage: if the value has a cached value, + // stop the patch loop and just use this value. + // BUT this will fail if one is in a diamond? - this->apply_value(value, change.get_operation()); + this->apply_value(value, change.get_operation()); } bool Value::operator ==(const Value &other) const { - if (typeid(*this) != typeid(other)) { - return false; - } - return this->equals(other); + if (typeid(*this) != typeid(other)) { + return false; + } + return this->equals(other); } bool Value::operator !=(const Value &other) const { - return not (*this == other); + return not (*this == other); } } // namespace nyan diff --git a/nyan/value/value.h b/nyan/value/value.h index 31b38fe..68f4a22 100644 --- a/nyan/value/value.h +++ b/nyan/value/value.h @@ -1,4 +1,4 @@ -// Copyright 2016-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2016-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -21,84 +21,123 @@ class Object; */ class Value { public: - Value() = default; - virtual ~Value() = default; - - /** - * Create a value of this type from the AST. - */ - static ValueHolder from_ast(const Type &target_type, - const ASTMemberValue &val, - const std::function &get_obj_value); - - /** - * Return a copy of this Value. - */ - virtual ValueHolder copy() const = 0; - - /** - * Apply the given change to this value. - * This value will be modified by the change-member's - * operation and value. - */ - void apply(const Member &change); - - /** - * String representation of the value. - */ - virtual std::string str() const = 0; - - /** - * Smaller string representation of the value. - */ - virtual std::string repr() const = 0; - - /** - * Hash function for the value. - */ - virtual size_t hash() const = 0; - - /** - * Return the basic type of this value. - * This means the primitive and the container type are provided, - * but not container element types and object targets, because - * types are not inferred for values. - */ - virtual const BasicType &get_type() const = 0; - - /** - * Return a set of allowed operations - * that are allowed for "entry_type.$operation(this)". - * - * This means the allowed operations can be used to assign/manipulate the - * member entry with this value. - */ - virtual const std::unordered_set &allowed_operations(const Type &with_type) const = 0; - - /** - * Comparison for Values. - * Performs the typeid comparison, then calls this->equals(other). - */ - bool operator ==(const Value &other) const; - - /** - * Inequality operator, it's just "not ==". - */ - bool operator !=(const Value &other) const; + Value() = default; + virtual ~Value() = default; + + /** + * Create a value of this type from the AST. + * + * @param[in] target_type Type of the value's member. + * @param[in] astmembervalue Value representation as the ASTMemberValue. + * @param[out] objs_in_values Pointer to the list of object identifiers in + * values at load time. + * @param[in] get_fqon Function for retrieving an object identifier from + * an IDToken. + * @param[in] get_obj_lin Function for retrieving the object linearization + * for an object. + * + * @return ValueHolder with shared pointer to the value. + */ + static ValueHolder from_ast(const Type &target_type, + const ASTMemberValue &astmembervalue, + std::vector> *objs_in_values, + const std::function &get_fqon, + const std::function(const fqon_t &)> &get_obj_lin); + + /** + * Get a copy of this Value. + * + * @return ValueHolder with shared pointer to the value. + */ + virtual ValueHolder copy() const = 0; + + /** + * Apply a given change to this value. It will be modified + * by the change member's operation and value. + * + * @param change Member that is applied. + */ + void apply(const Member &change); + + /** + * Get the string representation of the value. + * + * @return String representation of the value. + */ + virtual std::string str() const = 0; + + /** + * Get the short string representation of the value. + * + * @return Short string representation of the value. + */ + virtual std::string repr() const = 0; + + /** + * Get the hash of the value. + * + * @return Hash of the value. + */ + virtual size_t hash() const = 0; + + /** + * Get the basic type of this value. This means the primitive + * and the composite type are provided, but not composite element + * types and object targets, because types are not inferred for values. + * + * @return Basic type of the value. + */ + virtual const BasicType &get_type() const = 0; + + /** + * Get the set of allowed operations with a given type. This means + * the allowed operations can be used to assign/manipulate the + * member entry with this value. + * + * @param with_type Type for which allowed operations are retrieved. + * + * @return Set of allowed operation with the type. + */ + virtual const std::unordered_set &allowed_operations(const Type &with_type) const = 0; + + /** + * Equality comparison for Values. Performs the typeid comparison, + * then calls this->equals(other). + * + * @param other Value that is compared with. + * + * @return true if the values are equal, else false. + */ + bool operator ==(const Value &other) const; + + /** + * Inequality comparison for Values. + * + * @param other Value that is compared with. + * + * @return true if the values are not equal, else false. + */ + bool operator !=(const Value &other) const; protected: - /** - * Value-specific comparison function. - * It casts the value dynamically, but no type check is performed! - */ - virtual bool equals(const Value &other) const = 0; - - /** - * Apply the given change to the value. - * Internally casts the value dynamically, - * the type check must be done before calling this function. - */ - virtual void apply_value(const Value &value, nyan_op operation) = 0; + /** + * Value-specific comparison function. + * It casts the value dynamically, but no type check is performed! + * + * @param other Value that is compared with. + * + * @return true if the values are equal, else false. + */ + virtual bool equals(const Value &other) const = 0; + + /** + * Apply the given change to the value. Internally casts the value dynamically. + * A type check for compatibility must be done before calling this function. + * + * @param value Value that is applied. + * @param operation Operation used in the application. + */ + virtual void apply_value(const Value &value, nyan_op operation) = 0; }; } // namespace nyan @@ -111,9 +150,9 @@ namespace std { */ template<> struct hash { - size_t operator ()(const nyan::Value *val) const { - return val->hash(); - } + size_t operator ()(const nyan::Value *val) const { + return val->hash(); + } }; } // namespace std diff --git a/nyan/value/value_holder.cpp b/nyan/value/value_holder.cpp index a268f84..48e2ca7 100644 --- a/nyan/value/value_holder.cpp +++ b/nyan/value/value_holder.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "value_holder.h" @@ -11,52 +11,52 @@ ValueHolder::ValueHolder() = default; ValueHolder::ValueHolder(std::shared_ptr &&value) - : - value{std::move(value)} {} + : + value{std::move(value)} {} ValueHolder::ValueHolder(const std::shared_ptr &value) - : - value{value} {} + : + value{value} {} Value *ValueHolder::get_value() const { - return this->value.get(); + return this->value.get(); } const std::shared_ptr &ValueHolder::get_ptr() const { - return this->value; + return this->value; } void ValueHolder::clear() { - this->value = nullptr; + this->value = nullptr; } bool ValueHolder::exists() const { - return this->get_value() != nullptr; + return this->get_value() != nullptr; } Value &ValueHolder::operator *() const { - return *this->get_value(); + return *this->get_value(); } Value *ValueHolder::operator ->() const { - return this->get_value(); + return this->get_value(); } bool ValueHolder::operator ==(const ValueHolder &other) const { - return (*this->get_value() == *other.get_value()); + return (*this->get_value() == *other.get_value()); } bool ValueHolder::operator !=(const ValueHolder &other) const { - return (*this->get_value() != *other.get_value()); + return (*this->get_value() != *other.get_value()); } @@ -66,7 +66,7 @@ bool ValueHolder::operator !=(const ValueHolder &other) const { namespace std { size_t hash::operator ()(const nyan::ValueHolder &val) const { - return hash{}(val.get_value()); + return hash{}(val.get_value()); } } // namespace std diff --git a/nyan/value/value_holder.h b/nyan/value/value_holder.h index 38c57f8..40df9a8 100644 --- a/nyan/value/value_holder.h +++ b/nyan/value/value_holder.h @@ -1,4 +1,4 @@ -// Copyright 2017-2017 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include @@ -15,22 +15,69 @@ class Value; */ class ValueHolder { public: - ValueHolder(); - ValueHolder(std::shared_ptr &&value); - ValueHolder(const std::shared_ptr &value); + ValueHolder(); + ValueHolder(std::shared_ptr &&value); + ValueHolder(const std::shared_ptr &value); - Value *get_value() const; - const std::shared_ptr &get_ptr() const; - void clear(); - bool exists() const; + /** + * Get the value stored at this holder's pointer. + * + * @return Value stored at this holder's pointer. + */ + Value *get_value() const; - Value &operator *() const; - Value *operator ->() const; - bool operator ==(const ValueHolder &other) const; - bool operator !=(const ValueHolder &other) const; + /** + * Get the shared pointer of this ValueHolder. + * + * @return Shared pointer to this holder's value. + */ + const std::shared_ptr &get_ptr() const; + + /** + * Set the shared pointer to the value to nullptr. + */ + void clear(); + + /** + * Check if this holder points to a value. + * + * @return true if \p this->value is not nullptr, else false. + */ + bool exists() const; + + /** + * Get the value stored at this holder's pointer. + * + * @return Value stored at this holder's pointer. + */ + Value &operator *() const; + + /** + * Get the shared pointer of this ValueHolder. + * + * @return Shared pointer to this holder's value. + */ + Value *operator ->() const; + + /** + * Compare two ValueHolders for equality. + * + * @return true if the values stored by the holders are equal, else false. + */ + bool operator ==(const ValueHolder &other) const; + + /** + * Compare two ValueHolders for inequality. + * + * @return true if the values stored by the holders are not equal, else false. + */ + bool operator !=(const ValueHolder &other) const; protected: - std::shared_ptr value; + /** + * Shared pointer to the wrapped value. + */ + std::shared_ptr value; }; } // namespace nyan @@ -44,7 +91,7 @@ namespace std { */ template <> struct hash { - size_t operator ()(const nyan::ValueHolder &val) const; + size_t operator ()(const nyan::ValueHolder &val) const; }; } // namespace std diff --git a/nyan/value_token.cpp b/nyan/value_token.cpp new file mode 100644 index 0000000..86fdbe7 --- /dev/null +++ b/nyan/value_token.cpp @@ -0,0 +1,119 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. +#include "value_token.h" + +#include + +#include "ast.h" +#include "compiler.h" +#include "error.h" +#include "location.h" + +namespace nyan { + + +ValueToken::ValueToken(const IDToken &token) + : + container_type{composite_t::SINGLE} { + + this->tokens.push_back(token); +} + + +ValueToken::ValueToken(composite_t type, + std::vector &tokens) + : + tokens{tokens} { + + const static std::unordered_set container_types{ + composite_t::SET, + composite_t::ORDEREDSET, + composite_t::DICT + }; + + if (container_types.find(type) == container_types.end()) { + throw InternalError{"unknown container value type"}; + } + + this->container_type = type; +} + + +std::string ValueToken::str() const { + switch (this->container_type) { + case composite_t::SINGLE: + case composite_t::SET: + case composite_t::ORDEREDSET: + return this->tokens.at(0).str(); + + case composite_t::DICT: + return this->tokens.at(0).str() + ": " + this->tokens.at(1).str(); + + default: + throw InternalError{"unknown container value type"}; + }; +} + + +bool ValueToken::exists() const { + return this->tokens.size() > 0; +} + + +bool ValueToken::is_none() const { + if (this->tokens.size() != 1) { + return false; + } + + IDToken id_token = this->tokens[0]; + if (id_token.get_components().size() != 1) { + return false; + } + + return id_token.get_components()[0].get() == "None"; +} + + +const Location &ValueToken::get_start_location() const { + if (unlikely(not this->exists())) { + throw InternalError{ + "this ValueToken doesn't exist, but you queried its location" + }; + } + + return this->tokens.at(0).get_start_location(); +} + + +size_t ValueToken::get_length() const { + if (not this->exists()) { + return 0; + } + + switch (this->container_type) { + case composite_t::SINGLE: + case composite_t::SET: + case composite_t::ORDEREDSET: + return this->tokens.at(0).get_length(); + + case composite_t::DICT: + // key token length + value token length + separating ": " length + return this->tokens.at(0).get_length() + + this->tokens.at(1).get_length() + 2; + + default: + throw InternalError{"unknown container value type"}; + }; +} + + +const std::vector &ValueToken::get_value() const { + return this->tokens; +} + + +const composite_t &ValueToken::get_container_type() const { + return this->container_type; +} + + +} // namespace nyan diff --git a/nyan/value_token.h b/nyan/value_token.h new file mode 100644 index 0000000..3fc3b10 --- /dev/null +++ b/nyan/value_token.h @@ -0,0 +1,95 @@ +// Copyright 2020-2020 the nyan authors, LGPLv3+. See copying.md for legal info. +#pragma once + + +#include +#include + +#include "token.h" +#include "type.h" + + +namespace nyan { + + +/** + * Stores a value. Values can consist of multiple IDTokens. + */ +class ValueToken { +public: + ValueToken() = default; + + /** + * Simple constructor for a single value that is not in a container. + */ + ValueToken(const IDToken &token); + + /** + * Constructor for value tokens in a container. + */ + ValueToken(composite_t type, + std::vector &tokens); + + /** + * Get the string representation of this ValueToken. + * + * @return String representation formatted in nyan language notation. + */ + std::string str() const; + + /** + * Check if this ValueToken is empty. + * + * @return true if the ValueToken has more than one IDToken, else false. + */ + bool exists() const; + + /** + * Checks if this ValueToken contains the None value. + * + * @return true if this ValueToken stores None, else false. + */ + bool is_none() const; + + /** + * Get the starting location of this ValueToken in a file. + * + * @return Location of this ValueToken. + */ + const Location &get_start_location() const; + + /** + * Get the character length of this ValueToken. + * + * @return Length of this ValueToken. + */ + size_t get_length() const; + + /** + * Get the list of IDTokens in this ValueToken. + * + * @return List of IDTokens in this ValueToken. + */ + const std::vector &get_value() const; + +protected: + /** + * Get the type of container that this ValueToken is stored in. + * + * @return Container type of this ValueToken. + */ + const composite_t &get_container_type() const; + + /** + * Container type where the value is tored in. + */ + composite_t container_type; + + /** + * Components in the token. + */ + std::vector tokens; +}; + + +} // namespace nyan diff --git a/nyan/view.cpp b/nyan/view.cpp index 26ddd4c..bcd74a8 100644 --- a/nyan/view.cpp +++ b/nyan/view.cpp @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #include "view.h" diff --git a/nyan/view.h b/nyan/view.h index 531764f..c98174d 100644 --- a/nyan/view.h +++ b/nyan/view.h @@ -1,4 +1,4 @@ -// Copyright 2017-2019 the nyan authors, LGPLv3+. See copying.md for legal info. +// Copyright 2017-2020 the nyan authors, LGPLv3+. See copying.md for legal info. #pragma once #include diff --git a/test/diamond.nyan b/test/diamond.nyan index e3e9277..28b640f 100644 --- a/test/diamond.nyan +++ b/test/diamond.nyan @@ -1,3 +1,5 @@ +!version 1 + a(): member : int = 0 diff --git a/test/imported.nyan b/test/imported.nyan index db10973..6e2ad7d 100644 --- a/test/imported.nyan +++ b/test/imported.nyan @@ -1,2 +1,4 @@ +!version 1 + ImportedObject(): it_works : int = 42 diff --git a/test/stuff/file.nyan b/test/stuff/file.nyan index e757ef6..2ffc426 100644 --- a/test/stuff/file.nyan +++ b/test/stuff/file.nyan @@ -1,3 +1,5 @@ +!version 1 + MoreData(): pass diff --git a/test/test.nyan b/test/test.nyan index 419b3a1..5412a73 100644 --- a/test/test.nyan +++ b/test/test.nyan @@ -1,25 +1,38 @@ # test nyan file +!version 1 +!arg 1 2 3 "Test" Dummy(): ... First(): # a first object! + wat : abstract(First) = Second + wat2 : abstract(children(First)) = Second + wat3 : optional(First) = Second + wat4 : set(children(First)) = {Second} member : int = 15 test : text = "rofl lol" bla : file = "wtf.h4x" + blub : int = inf + blob : float = -inf FirstPatch(): member += 3 + blub += 3 + blob -= 3 Second(First): - member *= 5 + member *= 5.5 + # nap : int NestingBase(First): setmember : set(int) = {1,2,3,4, 5,6,7,} + dictmember : dict(int, text) = {2: "two", 4: "four"} + member = 2 SomeChild(Dummy): @@ -36,11 +49,11 @@ NestingBase(First): Fourth(First): guenther : int = 1337 - truth : bool = true + truth : bool = True Fifth(Fourth): guenther /= 100 - truth &= false + truth &= False Third(First): rolf : int = 42