diff --git a/include/MiniLua/sourcechange.hpp b/include/MiniLua/sourcechange.hpp index 13e00221..7793f63e 100644 --- a/include/MiniLua/sourcechange.hpp +++ b/include/MiniLua/sourcechange.hpp @@ -110,6 +110,49 @@ inline eval_result_t operator<< (const eval_result_t& lhs, const source_change_t return eval_success(get_val(lhs), get_sc(lhs) & rhs); } +// helper functions to name and choose the source change variants + +/* + * returns a label for the source change that is automatically chosen when v is + * changed. This can be used e.g. for a mouseover to show the user, what will be + * changed if the value is manipulated. As an additional hint, the name of the + * first variable binding of the source value is also encoded. The actual output + * format might be subject to change. + * + * Example (not the exact output): + * + * radius = 7.3 + * v = radius + 7 + * default_source_change_label(v) => "location 10,3 [hint: radius]" + */ +optional default_source_change_label(const val& v); + +/* + * returns a list of labels of all possible variants of source changes. The + * alternatives are named after the location, that is changed when the changes + * are applied. As an additional hint, the name of the first variable binding of + * the source value is also encoded. + */ +vector source_change_labels(const val& v); + +/* + * get the source change, so that modifications to v cause a change of the + * source identified by hint. This is done by inserting $ operator to direct the + * changes to the desired source and not other alternatives. + * + * Example: + * a = 5 + * b = 3 + * x = a+b + * + * get_sc_for_hint(x, "b") => change 5 -> $5 + * + * The hint is an attempt to enumerate the + * different alternatives for source changes. A more sophisticated structure + * than a string is probably necessary in the future. + */ +optional> get_sc_for_hint(const val& v, const string& hint); + } } diff --git a/src/core/sourcechange.cpp b/src/core/sourcechange.cpp index 9cc3cf54..4886a901 100644 --- a/src/core/sourcechange.cpp +++ b/src/core/sourcechange.cpp @@ -72,5 +72,148 @@ vector ApplySCVisitor::apply_changes(const vector& tokens) { return new_tokens; } +optional default_source_change_label(const val& v) { + if (!v.source) + return nullopt; + + auto possible_changes = v.forceValue(v); + + if (!possible_changes) + return nullopt; + + ApplySCVisitor visitor; + (*possible_changes)->accept(visitor); + for (const auto& c : visitor.changes) { + if (c.hint != "" && c.hint != "?") + return c.to_string(); + } + + return visitor.changes.front().to_string(); +} + +vector source_change_labels(const val& v) { + if (!v.source) + return vector(); + + auto possible_changes = v.forceValue(v); + + if (!possible_changes) + return vector(); + + vector result; + + struct SCLabelVisior : ApplySCVisitor { + virtual void visit(const SourceChangeOr& sc_or) override { + for (const auto& c : sc_or.alternatives) { + c->accept(*this); + } + } + + virtual void visit(const SourceChangeAnd& sc_and) override { + if (!sc_and.changes.empty()) + sc_and.changes.back()->accept(*this); + } + + } visitor; + + (*possible_changes)->accept(visitor); + for (const auto& c : visitor.changes) { + result.push_back(c.to_string()); + } + + return result; +} + +/* + * removes the first alternative from a source change recursively. It does a + * depth first search for a source assignment and removes it, removing empty + * or/and nodes in the process. + * + * Precondition: the source changes must result from a call to x.forceValue(x) + * as it compares the match and replacement in the SourceAssignment to discard + * any SourceAssignments that do not influence the current source change. + * (probably not needed?) + * + * It returns true if a matching assignment was found, false otherwise. + */ + +static bool remove_alternative(shared_ptr& changes) { + if (auto node = dynamic_pointer_cast(changes)) { + if (node->token.match == node->replacement) { + // the node is a possibility to change the source -> return true + return true; + } + } else if (auto node = dynamic_pointer_cast(changes)) { + for (unsigned i = 0; i < node->changes.size(); ++i) { + auto& c = node->changes[i]; + if (remove_alternative(c)) { + if (auto child_node = dynamic_pointer_cast(c); child_node && child_node->alternatives.empty()) { + node->changes.erase(begin(node->changes) + i); + } else if (auto child_node = dynamic_pointer_cast(c); child_node && child_node->changes.empty()) { + node->changes.erase(begin(node->changes) + i); + } else if (auto child_node = dynamic_pointer_cast(c); child_node) { + node->changes.erase(begin(node->changes) + i); + } + return true; + } + } + return false; + } else if (auto node = dynamic_pointer_cast(changes)) { + for (unsigned i = 0; i < node->alternatives.size(); ++i) { + auto& c = node->alternatives[i]; + if (remove_alternative(c)) { + if (auto child_node = dynamic_pointer_cast(c); child_node && child_node->alternatives.empty()) { + node->alternatives.erase(begin(node->alternatives) + i); + } else if (auto child_node = dynamic_pointer_cast(c); child_node && child_node->changes.empty()) { + node->alternatives.erase(begin(node->alternatives) + i); + } else if (auto child_node = dynamic_pointer_cast(c); child_node) { + node->alternatives.erase(begin(node->alternatives) + i); + } + return true; + } + } + return false; + } + return false; +} + +optional> get_sc_for_hint(const val& v, const string& hint) { + if (!v.source) + return nullopt; + + auto possible_changes = v.forceValue(v); + + if (!possible_changes) + return nullopt; + + auto source_changes = make_shared(); + + for(;;) { + ApplySCVisitor visitor; + (*possible_changes)->accept(visitor); + for (const auto& c : visitor.changes) { + std::cout << c.hint << std::endl; + if (c.to_string() == hint) { + // we don't have to do more! + return source_changes; + } + } + + if (visitor.changes.empty()) + break; + + // remove current alternative and add it as a source change with $ to source_changes + remove_alternative(*possible_changes); + for (const auto& c : visitor.changes) { + if (c.token.match == c.replacement) { + source_changes->changes.push_back(SourceAssignment::create(c.token, "$" + c.replacement)); + } + } + } + + // We could not change the source to modify hint :-( + return nullopt; +} + } } diff --git a/src/core/sourceexp.cpp b/src/core/sourceexp.cpp index cfdbf2fd..fdf890c4 100644 --- a/src/core/sourceexp.cpp +++ b/src/core/sourceexp.cpp @@ -238,6 +238,7 @@ source_change_t sourceunop::forceValue(const val& new_v) const { auto res_and = make_shared(); res_and->changes.push_back(*result); res_and->changes.push_back(SourceAssignment::create(op, "")); + res_and->changes.back()->hint = identifier; res_or->alternatives.push_back(res_and); }