-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Transitional changes to define a common type for nullable values (aka undefined symbols/labels) #42
Conversation
Can you review with special focus on |
So I browsed through the code just now (on vacation so not always around my computer) -- and Inheriting from AnyValueContainer is a bit strange, you would normally just keep it as a member. Also in this case it seems you wouldn't even need In fact, couldn't you just replace |
This is actually the implementation ("val" is a protected member of the base class - moved originally from
Funny thing: All of those types can represent optional value(s). What is strange is that STL representation is not consistent. None values for those types are:
(The std::optional type has a slight advantage vs std::any, that the type cannot change by assignment.) Thinking this a little bit further we can use same technique on "complex" semantic values containing multiple Finally our question concerning the |
But an Instruction is not a value, it is an aggregate of an opcode, addressing mode and a value, so it should not inherit from the value representation. So Changing the number type to a variant is not really feasible, type conversions would be a nightmare. Just using double covers all the cases we need since it can represent integers up to 2^48, and the math is at least consistent with how languages like JavaScript works. (And even if Javascripts type conversions are bad, C/C++ is hardly better with its weird integer promotion rules). I am still not sure exactly what the benefits are here. If you could add some tests that shows the errors it catches? Then I could check that the simpler std::optional would also solve the same problem. |
My interpretation of std::variant was wrong, sorry! Let me correct that… Actually std::variant is a non-optional(!) type: std::variant<double> vd;
std::cout << std::get<0>(vd) << '\n'; // outputs "0.0" Generically spoken: Let's invalidate this… 🙂 std::variant<std::optional<double>> nullable_v1;
// or…
std::optional<std::variant<double>> nullable_v2; |
Yes. Only currently I am still a little physically slowed down… 🚑 I like to generalize (streamline) our implementation logic - e.g. by using a "dictionary-like" (nullable) type as a "transitional" implementation. Just added // NOTE: This is a WIP.
// In current implementation T has to be of type std::variant to satisfy the interface.
// (defaults to `MultiValueProvider<AsmValue>` aka `std::variant<int, double, std::string>`)
// This is subject to change. (actually can be an list/set of `ValueProviders`)
class Section : public MultiValueProvider {
public:
const std::string_view identifier;
const std::string_view name;
Section(std::string_view section_name)
: identifier(persist(fmt::format("section.{}", section_name)),
, name(persist(section_name))
{
reserveValue(fmt::format("{}.start", identifier)); // will initialize with std::optional<AsmValue> == nullopt
}
// members can be used for "non-serializable" values (like the above `identifier` and `name`).
// ValueView start(fmt::format("{}.start")); // convenience (not implemented -> ValueView is meant as a std::string_view equivalent). Effecticely results in a "binding"… :)
}; (This method also is applicable to However speaking about it… Let me make this more "independent" and move the (unfinished) concept out of the way until we can use |
The value provider implementation is slowly taking shape… If you like to check it out please modify this line for feature toggle. Concerning the tests running Currently the commmon data type is The most prominent corner case I see is, that it is possible to accidentally re-assign the "typed" value: ValueProvider<int> vp;
vp.setValue(42); // just like it was a std::optional<int>
vp.getAny() = "i will produce a bad_any_cast_error"; // changes type…
std::cout << vp.getValue() << '\n'; // whooops… However since it is type-safe I think this is acceptable and easy to find. |
But you have not added any tests ? |
Sorry should be there now… TBH those tests are pretty trivial and What I really like is that it provides more information about internal variable reassignments. Really can help with debugging the status quo… 💡 FYI: I also experimented a little with template <typename T>
void SymbolTable::set_opt(std::string const& name, std::optional<T> const& opt_val) {
if (opt_val.has_value()) {
set(name, opt_val.value());
} else {
undefined.insert(name);
}
} Got it to run through all the passes, but it messes up PC really badly… Are you okay with using those "transitional" functions? Because this PR is actually mergable (holds quite some goodies…). Or shall I "split" this and revert those |
It doesn't seem like you have read my comments... this PR adds a lot of complexity with no proven upside and I said already in the first comment that it needed do be done differently. That's why I wanted to see some test that shows what it helps with, so I could find an alternative way of supporting it without the Provider classes. |
So if it is shown that handling "unset" values like this is useful, and it actually needs a custom class, I would not expect you to inherit from it, but rather used it in place of the actual value. |
Actually I do. Sorry if it doesn't always show… --- FYI --- Apart from that here the underlying problem: std::set(typeinfo) optional_types;
bool is_optional(typeinfo a_type) { return optionals.contains(typeid(a_type)); }
template <typename T>
class Optional : public std::optional<T> {
public:
Optional<T>() : std::optional<T>() {
optionals.insert(typeid(T));
}
// … minimal implementation …
}; Since |
dc43a9d
to
2c24463
Compare
Moved value provider into feature branch. This PR is ready for review/merge! |
NOTE: Does not change current parsing logic! - Throw on invalid conversion of an empty std::any value to non-optional (c++) type (int,Number,string,…) - Remove (unused) SemanticValues array value cast function - Push the v_default AsmValue instead of creating another instance
🎱 Closed -> collecting the good parts into a new (reviewable) PR #44.
For reference
TBH I don't know how this will turn out, but I think we can replace/enhance our current
using Number = double;
type…One goal is to simplify bass's state logic and enable it to support more complex features (such as declarative stuff where code blocks can be placed independent of others of the same kind. One concrete example for this is the infamous "move section" use case).
The (obvious) advantage of having a "nullable" type (at runtime) is that we gain an additional state for values during parsing/serialization (
std::any a; if (typeid(a) == typeid(void)) { /* yay, we have found a 'None' value */ }
).As a rule we can safely say that a label can be accessed before its definition:
As discussed in issue #19, #37, #39 symbols are of constant nature and cannot be (re)defined once they hold a value. (NOTE: As the only exception to this, ACME supports the meta keyword
!set negative_8bit_value = -124
to explicitly set a defined value - rarely used anyway…)💡
Additionally I suggest adding explicit tests on our parsing rules (expecially "MetaBlock" etc.) to improve the debugging situation and find "unexpected" states of the AST more quickly. At least part of this can be done in this PR (still a hobby project…).