17#include <unordered_map>
18#include <unordered_set>
22#include "../attributes.hpp"
44namespace morph::units::detail {
49[[nodiscard]]
inline std::string formatOptional(
const std::optional<morph::math::Rational>& value) {
50 return formatOptionalDecimal(value);
56[[nodiscard]]
inline bool isLeafNode(
const ASTNode& node) {
return node.current.operation.empty(); }
61[[nodiscard]]
inline bool isConversionNode(
const ASTNode& node) {
62 return node.current.operation.starts_with(
"convert");
68[[nodiscard]]
inline bool isAtomNode(
const ASTNode& node) {
return isLeafNode(node) || isConversionNode(node); }
74[[nodiscard]]
inline const std::optional<morph::math::Rational>& nodeValue(
const ASTNode& node MORPH_LIFETIMEBOUND) {
75 return isLeafNode(node) ? node.current.lhs : node.current.result;
88enum class RenderMode : std::uint8_t {
102 const ASTNode* node{
nullptr};
105 bool expandThis{
false};
117 const ASTNode* node{
nullptr};
120 bool expandThis{
false};
124struct EquationRenderer {
127 explicit EquationRenderer(std::size_t steps) : maxSteps(
steps), stepBudget(
steps) {}
130 std::size_t maxSteps;
133 std::size_t stepBudget;
136 std::unordered_map<const ASTNode*, std::size_t> labelIndex;
139 std::unordered_map<const ASTNode*, int> refCount;
142 std::unordered_set<const ASTNode*> seen;
145 std::vector<const ASTNode*> placeholderOrder;
150 std::unordered_set<const ASTNode*> settled;
153 std::unordered_map<const ASTNode*, std::size_t> elisionIndex;
156 std::vector<const ASTNode*> elisionOrder;
163 [[nodiscard]]
bool isPlaceholder(
const ASTNode* node)
const {
return refCount.at(node) >= 2; }
173 void countRefs(
const ASTNode* root) {
174 std::vector<const ASTNode*> pending;
175 pending.push_back(root);
176 while (!pending.empty()) {
177 const ASTNode* node = pending.back();
182 if (node ==
nullptr || seen.contains(node)) {
186 if (node->name.has_value() || isAtomNode(*node)) {
190 ++refCount[node->left.get()];
193 ++refCount[node->right.get()];
195 pending.push_back(node->left.get());
196 pending.push_back(node->right.get());
223 void assignLabels(
const ASTNode* root,
bool expandRoot) {
224 std::vector<LabelFrame> pending;
225 pending.push_back(LabelFrame{.node = root, .expandThis = expandRoot});
226 while (!pending.empty()) {
227 LabelFrame
const frame = pending.back();
229 const ASTNode* node = frame.node;
230 if (node ==
nullptr || node->name.has_value()) {
238 if (!settled.insert(node).second) {
241 bool const labelIt = !frame.expandThis && isPlaceholder(node);
244 if (isAtomNode(*node)) {
246 labelIndex.emplace(node, placeholderOrder.size() + 1);
247 placeholderOrder.push_back(node);
251 if (stepBudget == 0) {
252 elisionIndex.emplace(node, elisionOrder.size() + 1);
253 elisionOrder.push_back(node);
258 labelIndex.emplace(node, placeholderOrder.size() + 1);
259 placeholderOrder.push_back(node);
261 pending.push_back(LabelFrame{.node = node->right.get(), .expandThis =
false});
262 pending.push_back(LabelFrame{.node = node->left.get(), .expandThis =
false});
283 [[nodiscard]]
static Rendered combine(
const std::string& op, Rendered left,
const Rendered& right) {
284 int const precedence = (op ==
"*" || op ==
"/") ? 2 : 1;
285 std::string
text = std::move(left.text);
286 if (left.precedence < precedence) {
287 text.insert(0, 1,
'(');
293 bool const rightNeedsParens =
294 (right.precedence < precedence) || (right.precedence == precedence && (op ==
"-" || op ==
"/"));
295 if (rightNeedsParens) {
302 return Rendered{.text = std::move(text), .precedence = precedence};
308 [[nodiscard]]
static Rendered combineUnary(
const Rendered& operand) {
309 std::string
const text = (operand.precedence <= 1) ?
"-(" + operand.text +
")" :
"-" + operand.
text;
310 return Rendered{.text =
text, .precedence = 3};
320 [[nodiscard]] std::optional<Rendered> atomRendering(
const ASTNode* node,
bool expandThis, RenderMode mode)
const {
326 if (
auto const elided = elisionIndex.find(node); elided != elisionIndex.end()) {
327 if (mode == RenderMode::symbolic) {
328 return Rendered{.text =
"e" + std::to_string(elided->second), .precedence = 100};
330 return Rendered{.text = formatOptional(nodeValue(*node)), .precedence = 100};
332 if (mode == RenderMode::symbolic) {
333 if (node->name.has_value()) {
334 return Rendered{.text =
"\"" + *node->name +
"\"", .precedence = 100};
336 if (!expandThis && isPlaceholder(node)) {
337 return Rendered{.text =
"c" + std::to_string(labelIndex.at(node)), .precedence = 100};
339 }
else if (!expandThis && (node->name.has_value() || isPlaceholder(node))) {
340 return Rendered{.text = formatOptional(nodeValue(*node)), .precedence = 100};
342 if (isLeafNode(*node)) {
343 return Rendered{.text = formatOptional(node->current.lhs), .precedence = 100};
345 if (isConversionNode(*node)) {
346 return Rendered{.text = formatOptional(node->current.result), .precedence = 100};
372 [[nodiscard]] Rendered render(
const ASTNode* root,
bool expandRoot, RenderMode mode)
const {
373 std::vector<RenderFrame> stack;
374 stack.push_back(RenderFrame{.node = root, .expandThis = expandRoot});
376 while (!stack.empty()) {
377 RenderFrame& top = stack.back();
378 if (top.stage == 0) {
379 if (std::optional<Rendered> atom = atomRendering(top.node, top.expandThis, mode)) {
380 finished = *std::move(atom);
385 if (top.node->left) {
386 const ASTNode* child = top.node->left.get();
387 stack.push_back(RenderFrame{.node = child, .expandThis =
false});
390 finished = Rendered{.text = formatOptional(top.node->current.lhs), .precedence = 100};
393 if (top.stage == 1) {
394 top.left = std::move(finished);
397 finished = Rendered{};
398 bool const hasRight = top.node->right || top.node->current.rhs.has_value();
400 finished = combineUnary(top.left);
405 if (top.node->right) {
406 const ASTNode* child = top.node->right.get();
407 stack.push_back(RenderFrame{.node = child, .expandThis =
false});
410 finished = Rendered{.text = formatOptional(top.node->current.rhs), .precedence = 100};
413 finished = combine(top.node->current.operation, std::move(top.left), finished);
423 [[nodiscard]] Rendered renderSymbolic(
const ASTNode* node,
bool expandThis)
const {
424 return render(node, expandThis, RenderMode::symbolic);
431 [[nodiscard]] Rendered renderSubstituted(
const ASTNode* node,
bool expandThis)
const {
432 return render(node, expandThis, RenderMode::substituted);
438 [[nodiscard]] std::string legendLine(
const ASTNode* node)
const {
439 std::string
const label =
"c" + std::to_string(labelIndex.at(node));
440 if (isAtomNode(*node)) {
441 return label +
" = " + formatOptional(nodeValue(*node));
443 return label +
" = " + renderSymbolic(node,
true).text +
" = " + renderSubstituted(node,
true).text +
" = " +
444 formatOptional(node->current.result);
455 [[nodiscard]] std::string elisionLine(
const ASTNode* node)
const {
456 return "e" + std::to_string(elisionIndex.at(node)) +
" = " + formatOptional(nodeValue(*node)) +
457 " (elided at the " + std::to_string(maxSteps) +
"-step limit)";
463namespace morph::units {
465template <auto U, std::u
int32_t DeclaredDecimals>
469 return {detail::formatOptional(payload)};
474 return {detail::formatOptional(payload)};
476 const detail::ASTNode* root = _ctx.node.get();
477 if (root ==
nullptr) {
478 return {detail::formatOptional(payload)};
480 if (root->name.has_value()) {
481 return {
"\"" + *root->name +
"\""};
483 if (detail::isLeafNode(*root)) {
484 return {detail::formatOptional(root->current.lhs)};
486 if (detail::isConversionNode(*root)) {
487 return {detail::formatOptional(root->current.result)};
495 detail::EquationRenderer renderer{maxSteps};
496 renderer.countRefs(root);
497 renderer.assignLabels(root,
true);
499 std::vector<std::string> lines;
500 lines.push_back(renderer.renderSymbolic(root,
true).text);
501 lines.push_back(
" = " + renderer.renderSubstituted(root,
true).text);
502 lines.push_back(
" = " + detail::formatOptional(root->current.result));
506 bool firstLegendLine =
true;
507 auto const appendLegend = [&lines, &firstLegendLine](
const std::string& body) {
508 lines.push_back((firstLegendLine ?
"where " :
" ") + body);
509 firstLegendLine =
false;
511 for (
const detail::ASTNode* node : renderer.placeholderOrder) {
512 appendLegend(renderer.legendLine(node));
514 for (
const detail::ASTNode* node : renderer.elisionOrder) {
515 appendLegend(renderer.elisionLine(node));
Node text(TextOptions const &options)
A text node.
Node steps(AnyHandle const &items, AnyHandle const ¤t={})
A steps node.
Concept: an enum with a UnitTraits specialisation (template param E).
Unit-tagged, optionally-empty exact values with a derivation trail.
Exact rational arithmetic for morph model/action values.
std::vector< std::string > equation(std::size_t maxSteps=kDefaultEquationSteps) const
The worked derivation as print-ready lines (see docs/spec/util/quantity_type.md).