#include "logic_editor_service.h" #include "project_service.h" #include "domain/project_limits.h" #include #include #include #include #include #include #include namespace { bool isBlank(const std::string &value) { return value.empty() || std::all_of( value.cbegin(), value.cend(), [](unsigned char character) { return std::isspace(character) != 0; }); } bool containsLineBreak(const std::string &value) { return value.find('\r') != std::string::npos || value.find('\n') != std::string::npos; } std::string makeUniqueLogicId(const Project &project) { int suffix = 1; while (true) { const std::string candidate = "logic-" + std::to_string(suffix); const bool found = std::any_of( project.controlLogics.cbegin(), project.controlLogics.cend(), [&candidate](const ControlLogic &logic) { return logic.id == candidate; }); if (!found) { return candidate; } ++suffix; } } LogicNode makeNode(const std::string &id, const LogicNodeConfig &config) { return {id, config, false}; } ConditionExpression makeContainer( const std::string &id, ConditionExpressionKind kind, ConditionExpression first, ConditionExpression second) { ConditionExpression expression; expression.id = id; expression.kind = kind; expression.children.push_back(std::move(first)); expression.children.push_back(std::move(second)); return expression; } ConditionExpression *findParentExpression( ConditionExpression &expression, const std::string &child_id) { for (ConditionExpression &child : expression.children) { if (child.id == child_id) { return &expression; } if (ConditionExpression *parent = findParentExpression(child, child_id)) { return parent; } } return nullptr; } const ConditionExpression *findParentExpression( const ConditionExpression &expression, const std::string &child_id) { for (const ConditionExpression &child : expression.children) { if (child.id == child_id) { return &expression; } if (const ConditionExpression *parent = findParentExpression( child, child_id)) { return parent; } } return nullptr; } using NodeIdSet = std::unordered_set; NodeIdSet conditionLeafIds(const ConditionExpression &expression) { NodeIdSet ids; if (expression.kind == ConditionExpressionKind::Node || expression.kind == ConditionExpressionKind::Wire) { ids.insert(expression.id); return ids; } for (const ConditionExpression &child : expression.children) { NodeIdSet child_ids = conditionLeafIds(child); ids.insert(child_ids.cbegin(), child_ids.cend()); } return ids; } int expressionColumns(const ConditionExpression &expression) { if (expression.kind == ConditionExpressionKind::Node) { return 1; } if (expression.kind == ConditionExpressionKind::Wire) { return expression.wire->columnSpan; } int columns = expression.kind == ConditionExpressionKind::Series ? 0 : 1; for (const ConditionExpression &child : expression.children) { const int child_columns = expressionColumns(child); columns = expression.kind == ConditionExpressionKind::Series ? columns + child_columns : std::max(columns, child_columns); } return columns; } bool isSubset(const NodeIdSet &subset, const NodeIdSet &values) { return std::all_of( subset.cbegin(), subset.cend(), [&values](const std::string &value) { return values.count(value) != 0U; }); } bool sameValues(const NodeIdSet &left, const NodeIdSet &right) { return left.size() == right.size() && isSubset(left, right); } void addParallelSibling( ConditionExpression *target, ConditionExpression branch, const std::string &container_id) { if (target->kind == ConditionExpressionKind::Parallel) { target->children.push_back(std::move(branch)); return; } ConditionExpression original = std::move(*target); *target = makeContainer( container_id, ConditionExpressionKind::Parallel, std::move(original), std::move(branch)); } bool addParallelForSelection( ConditionExpression *expression, const NodeIdSet &selected_ids, ConditionExpression *branch, const std::string ¶llel_id, const std::string &series_id) { const NodeIdSet expression_ids = conditionLeafIds(*expression); if (sameValues(expression_ids, selected_ids)) { addParallelSibling(expression, std::move(*branch), parallel_id); return true; } std::vector child_ids; child_ids.reserve(expression->children.size()); std::vector matching_children; for (std::size_t index = 0; index < expression->children.size(); ++index) { child_ids.push_back(conditionLeafIds(expression->children[index])); const bool intersects = std::any_of( child_ids.back().cbegin(), child_ids.back().cend(), [&selected_ids](const std::string &id) { return selected_ids.count(id) != 0U; }); if (intersects) { matching_children.push_back(index); } } if (matching_children.empty()) { return false; } if (matching_children.size() == 1U) { const std::size_t child_index = matching_children.front(); if (expression->kind == ConditionExpressionKind::Parallel && sameValues(child_ids[child_index], selected_ids)) { expression->children.push_back(std::move(*branch)); return true; } return addParallelForSelection( &expression->children[child_index], selected_ids, branch, parallel_id, series_id); } if (expression->kind != ConditionExpressionKind::Series) { return false; } const std::size_t first = matching_children.front(); const std::size_t last = matching_children.back(); if (last - first + 1U != matching_children.size()) { return false; } NodeIdSet range_ids; for (std::size_t index = first; index <= last; ++index) { range_ids.insert(child_ids[index].cbegin(), child_ids[index].cend()); } if (!sameValues(range_ids, selected_ids)) { return false; } ConditionExpression range; range.id = series_id; range.kind = ConditionExpressionKind::Series; auto range_begin = expression->children.begin() + static_cast(first); auto range_end = expression->children.begin() + static_cast(last + 1U); range.children.insert( range.children.end(), std::make_move_iterator(range_begin), std::make_move_iterator(range_end)); range_begin = expression->children.erase(range_begin, range_end); ConditionExpression parallel = makeContainer( parallel_id, ConditionExpressionKind::Parallel, std::move(range), std::move(*branch)); expression->children.insert(range_begin, std::move(parallel)); return true; } bool addParallelForWireCellRange( ConditionExpression *expression, const std::string &wire_id, int first_column, int selected_columns, ConditionExpression *branch, const std::string ¶llel_id, const std::string &series_id, const std::string &leading_wire_id, const std::string &trailing_wire_id) { if (expression == nullptr) { return false; } if (expression->kind == ConditionExpressionKind::Wire && expression->id == wire_id) { const int total_columns = expression->wire->columnSpan; if (first_column < 0 || selected_columns <= 0 || first_column + selected_columns > total_columns) { return false; } if (first_column == 0 && selected_columns == total_columns) { addParallelSibling(expression, std::move(*branch), parallel_id); return true; } ConditionExpression replacement; replacement.id = series_id; replacement.kind = ConditionExpressionKind::Series; if (first_column > 0) { replacement.children.push_back(ConditionExpression::fromWire( leading_wire_id, first_column)); } ConditionExpression selected_wire = ConditionExpression::fromWire( wire_id, selected_columns); replacement.children.push_back(makeContainer( parallel_id, ConditionExpressionKind::Parallel, std::move(selected_wire), std::move(*branch))); const int trailing_columns = total_columns - first_column - selected_columns; if (trailing_columns > 0) { replacement.children.push_back(ConditionExpression::fromWire( trailing_wire_id, trailing_columns)); } *expression = std::move(replacement); return true; } if (expression->kind == ConditionExpressionKind::Node || expression->kind == ConditionExpressionKind::Wire) { return false; } for (ConditionExpression &child : expression->children) { if (addParallelForWireCellRange( &child, wire_id, first_column, selected_columns, branch, parallel_id, series_id, leading_wire_id, trailing_wire_id)) { return true; } } return false; } struct WireCellLocation { const ConditionExpression *series = nullptr; std::size_t child_index = 0U; int child_start = 0; }; bool findWireCellLocation( const ConditionExpression &expression, const std::string &wire_id, WireCellLocation *location) { if (expression.kind == ConditionExpressionKind::Node || expression.kind == ConditionExpressionKind::Wire) { return false; } if (expression.kind == ConditionExpressionKind::Series) { int child_start = 0; for (std::size_t index = 0; index < expression.children.size(); ++index) { const ConditionExpression &child = expression.children[index]; if (child.kind == ConditionExpressionKind::Wire && child.id == wire_id) { if (location != nullptr) { *location = {&expression, index, child_start}; } return true; } if (findWireCellLocation(child, wire_id, location)) { return true; } child_start += expressionColumns(child); } return false; } for (const ConditionExpression &child : expression.children) { if (findWireCellLocation(child, wire_id, location)) { return true; } } return false; } bool addParallelForWireCellSeriesRange( ConditionExpression *root, const std::string &series_id, std::size_t first_child, std::size_t last_child, int leading_columns, int selected_columns, int trailing_columns, ConditionExpression *branch, const std::string &selected_wire_id, const std::string ¶llel_id, const std::string &leading_wire_id, const std::string &trailing_wire_id) { if (root == nullptr || branch == nullptr) { return false; } ConditionExpression *series = findConditionExpression(*root, series_id); if (series == nullptr || series->kind != ConditionExpressionKind::Series || first_child > last_child || last_child >= series->children.size() || leading_columns < 0 || selected_columns <= 0 || trailing_columns < 0) { return false; } for (std::size_t index = first_child; index <= last_child; ++index) { if (series->children[index].kind != ConditionExpressionKind::Wire) { return false; } } ConditionExpression selected_wire = ConditionExpression::fromWire( selected_wire_id, selected_columns); ConditionExpression parallel = makeContainer( parallel_id, ConditionExpressionKind::Parallel, std::move(selected_wire), std::move(*branch)); std::vector replacement; replacement.reserve( series->children.size() - (last_child - first_child) + 3U); for (std::size_t index = 0; index < first_child; ++index) { replacement.push_back(std::move(series->children[index])); } if (leading_columns > 0) { replacement.push_back(ConditionExpression::fromWire( leading_wire_id, leading_columns)); } replacement.push_back(std::move(parallel)); if (trailing_columns > 0) { replacement.push_back(ConditionExpression::fromWire( trailing_wire_id, trailing_columns)); } for (std::size_t index = last_child + 1U; index < series->children.size(); ++index) { replacement.push_back(std::move(series->children[index])); } series->children = std::move(replacement); return true; } bool removeExpressionRecursive( ConditionExpression *expression, const std::string &expression_id) { if (expression == nullptr || expression->kind == ConditionExpressionKind::Node || expression->kind == ConditionExpressionKind::Wire) { return false; } const auto removable = std::find_if( expression->children.begin(), expression->children.end(), [&expression_id](const ConditionExpression &child) { return child.id == expression_id; }); if (removable != expression->children.end()) { expression->children.erase(removable); return true; } for (ConditionExpression &child : expression->children) { if (removeExpressionRecursive(&child, expression_id)) { return true; } } return false; } std::optional selectedExpressionColumns( const ConditionExpression &expression, const NodeIdSet &selected_ids) { const NodeIdSet expression_ids = conditionLeafIds(expression); if (sameValues(expression_ids, selected_ids)) { return expressionColumns(expression); } if (expression.kind == ConditionExpressionKind::Node || expression.kind == ConditionExpressionKind::Wire) { return std::nullopt; } std::vector child_ids; std::vector matching_children; child_ids.reserve(expression.children.size()); for (std::size_t index = 0; index < expression.children.size(); ++index) { child_ids.push_back(conditionLeafIds(expression.children[index])); const bool intersects = std::any_of( child_ids.back().cbegin(), child_ids.back().cend(), [&selected_ids](const std::string &id) { return selected_ids.count(id) != 0U; }); if (intersects) { matching_children.push_back(index); } } if (matching_children.size() == 1U) { return selectedExpressionColumns( expression.children[matching_children.front()], selected_ids); } if (matching_children.empty() || expression.kind != ConditionExpressionKind::Series) { return std::nullopt; } const std::size_t first = matching_children.front(); const std::size_t last = matching_children.back(); if (last - first + 1U != matching_children.size()) { return std::nullopt; } NodeIdSet range_ids; int columns = 0; for (std::size_t index = first; index <= last; ++index) { range_ids.insert(child_ids[index].cbegin(), child_ids[index].cend()); columns += expressionColumns(expression.children[index]); } return sameValues(range_ids, selected_ids) ? std::optional{columns} : std::nullopt; } LadderRung *findEditableRung( Project &project, const std::string &logic_id, const std::string &rung_id) { const auto logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == project.controlLogics.end()) { return nullptr; } const auto rung = std::find_if( logic->rungs.begin(), logic->rungs.end(), [&rung_id](const LadderRung &candidate) { return candidate.id == rung_id; }); return rung == logic->rungs.end() ? nullptr : &*rung; } } // namespace LogicEditorService::LogicEditorService(ProjectService &project_service) : project_service_(project_service) { } LogicEditorService::HistoryState LogicEditorService::captureState() const { return {project_service_.project().controlLogics}; } void LogicEditorService::recordHistory(HistoryState before) { const HistoryState after = captureState(); history_.record(std::move(before), after, &LogicEditorService::statesEqual); } void LogicEditorService::rollbackEdit( HistoryState before, bool modified_before) { project_service_.editProject().controlLogics = std::move(before.logics); project_service_.restoreModifiedState(modified_before); } bool LogicEditorService::statesEqual( const HistoryState &left, const HistoryState &right) { if (left.logics.size() != right.logics.size()) { return false; } for (std::size_t index = 0; index < left.logics.size(); ++index) { if (!logicsEqual(left.logics[index], right.logics[index])) { return false; } } return true; } bool LogicEditorService::logicsEqual( const ControlLogic &left, const ControlLogic &right) { if (left.id != right.id || left.name != right.name || left.enabled != right.enabled || left.rungs.size() != right.rungs.size()) { return false; } for (std::size_t index = 0; index < left.rungs.size(); ++index) { if (!rungsEqual(left.rungs[index], right.rungs[index])) { return false; } } return true; } bool LogicEditorService::rungsEqual( const LadderRung &left, const LadderRung &right) { if (left.id != right.id || left.name != right.name || left.comment != right.comment || left.condition.has_value() != right.condition.has_value() || left.output.has_value() != right.output.has_value()) { return false; } return (!left.condition.has_value() || expressionsEqual(*left.condition, *right.condition)) && (!left.output.has_value() || nodesEqual(*left.output, *right.output)); } bool LogicEditorService::expressionsEqual( const ConditionExpression &left, const ConditionExpression &right) { if (left.id != right.id || left.kind != right.kind || left.node.has_value() != right.node.has_value() || left.wire.has_value() != right.wire.has_value() || left.children.size() != right.children.size()) { return false; } if (left.node.has_value() && !nodesEqual(*left.node, *right.node)) { return false; } if (left.wire.has_value() && left.wire->columnSpan != right.wire->columnSpan) { return false; } for (std::size_t index = 0; index < left.children.size(); ++index) { if (!expressionsEqual(left.children[index], right.children[index])) { return false; } } return true; } bool LogicEditorService::nodesEqual( const LogicNode &left, const LogicNode &right) { return left.id == right.id && left.config.index() == right.config.index() && left.configured == right.configured && configsEqual(left.config, right.config); } bool LogicEditorService::configsEqual( const LogicNodeConfig &left, const LogicNodeConfig &right) { return std::visit( [](const auto &left_config, const auto &right_config) { using Left = std::decay_t; using Right = std::decay_t; if constexpr (!std::is_same_v) { return false; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.comparison == right_config.comparison && left_config.value == right_config.value; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.presetMs == right_config.presetMs; } else if constexpr (std::is_same_v) { return left_config.address == right_config.address && left_config.mode == right_config.mode && left_config.currentValueAddress == right_config.currentValueAddress && left_config.preset.kind == right_config.preset.kind && left_config.preset.address == right_config.preset.address && left_config.preset.constant == right_config.preset.constant && left_config.resetAddress == right_config.resetAddress; } else if constexpr (std::is_same_v) { return left_config.source.kind == right_config.source.kind && left_config.source.address == right_config.source.address && left_config.source.constant == right_config.source.constant && left_config.destination == right_config.destination; } else { return left_config.operation == right_config.operation && left_config.left.kind == right_config.left.kind && left_config.left.address == right_config.left.address && left_config.left.constant == right_config.left.constant && left_config.right.kind == right_config.right.kind && left_config.right.address == right_config.right.address && left_config.right.constant == right_config.right.constant && left_config.destination == right_config.destination; } }, left, right); } LogicEditorResult LogicEditorService::historyFailure(const std::string &message) { return {false, LogicEditorError::InvalidOperation, message, {}}; } const ControlLogic *LogicEditorService::findLogic(const std::string &logic_id) const { const auto &logics = project_service_.project().controlLogics; const auto logic = std::find_if( logics.cbegin(), logics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); return logic == logics.cend() ? nullptr : &*logic; } const LadderRung *LogicEditorService::findRung( const std::string &logic_id, const std::string &rung_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return nullptr; } const auto rung = std::find_if( logic->rungs.cbegin(), logic->rungs.cend(), [&rung_id](const LadderRung &candidate) { return candidate.id == rung_id; }); return rung == logic->rungs.cend() ? nullptr : &*rung; } const LogicNode *LogicEditorService::findNode( const std::string &logic_id, const std::string &node_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return nullptr; } for (const LadderRung &rung : logic->rungs) { if (rung.output.has_value() && rung.output->id == node_id) { return &*rung.output; } if (rung.condition.has_value()) { if (const LogicNode *node = findConditionNode(*rung.condition, node_id)) { return node; } } } return nullptr; } const ConditionExpression *LogicEditorService::findExpression( const std::string &logic_id, const std::string &rung_id, const std::string &expression_id) const { const LadderRung *rung = findRung(logic_id, rung_id); return rung == nullptr || !rung->condition.has_value() ? nullptr : findConditionExpression(*rung->condition, expression_id); } std::string LogicEditorService::firstLogicId() const { const auto &logics = project_service_.project().controlLogics; return logics.empty() ? std::string{} : logics.front().id; } std::string LogicEditorService::firstRungId(const std::string &logic_id) const { const ControlLogic *logic = findLogic(logic_id); return logic == nullptr || logic->rungs.empty() ? std::string{} : logic->rungs.front().id; } std::string LogicEditorService::rungIdForNode( const std::string &logic_id, const std::string &node_id) const { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return {}; } for (const LadderRung &rung : logic->rungs) { if ((rung.output.has_value() && rung.output->id == node_id) || (rung.condition.has_value() && findConditionNode(*rung.condition, node_id) != nullptr)) { return rung.id; } } return {}; } std::string LogicEditorService::registerCommentFor( const RegisterAddress &address) const { const RegisterComment *comment = project_service_.project().findRegisterComment(address); return comment == nullptr ? std::string{} : comment->text; } LogicEditorResult LogicEditorService::ensureDefaultLogic() { if (!project_service_.project().controlLogics.empty()) { return {true, LogicEditorError::None, {}, firstLogicId()}; } ControlLogic logic; logic.id = "logic-1"; logic.name = "控制逻辑 1"; Project &project = project_service_.editProject(); project.controlLogics.push_back(std::move(logic)); return {true, LogicEditorError::None, {}, project.controlLogics.back().id}; } LogicEditorResult LogicEditorService::addLogic(const std::string &name) { if (isBlank(name)) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能为空"); } const Project ¤t = project_service_.project(); if (current.controlLogics.size() >= ProjectLimits::kMaximumControlLogics) { return failure(LogicEditorError::InvalidOperation, "单个工程最多包含 128 组控制逻辑"); } if (name.size() > ProjectLimits::kMaximumTextBytes) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能超过 4096 个 UTF-8 字节"); } const bool duplicate = std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&name](const ControlLogic &logic) { return logic.name == name; }); if (duplicate) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } ControlLogic logic; logic.id = makeUniqueLogicId(current); logic.name = name; HistoryState before = captureState(); Project &project = project_service_.editProject(); project.controlLogics.push_back(std::move(logic)); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, project.controlLogics.back().id}; } LogicEditorResult LogicEditorService::renameLogic( const std::string &logic_id, const std::string &name) { if (isBlank(name)) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能为空"); } if (name.size() > ProjectLimits::kMaximumTextBytes) { return failure(LogicEditorError::InvalidOperation, "控制逻辑名称不能超过 4096 个 UTF-8 字节"); } const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const bool duplicate = std::any_of( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id, &name](const ControlLogic &candidate) { return candidate.id != logic_id && candidate.name == name; }); if (duplicate) { return failure(LogicEditorError::DuplicateName, "控制逻辑名称必须唯一"); } if (logic->name == name) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto editable = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); editable->name = name; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::removeLogic(const std::string &logic_id) { const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (current.controlLogics.size() <= 1) { return failure(LogicEditorError::LastLogicRequired, "工程至少需要保留一个控制逻辑"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); project.controlLogics.erase( std::remove_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }), project.controlLogics.end()); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::moveLogic(const std::string &logic_id, int offset) { if (offset != -1 && offset != 1) { return failure(LogicEditorError::InvalidOperation, "控制逻辑每次只能上移或下移一位"); } const Project ¤t = project_service_.project(); const auto logic = std::find_if( current.controlLogics.cbegin(), current.controlLogics.cend(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (logic == current.controlLogics.cend()) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const auto index = std::distance(current.controlLogics.cbegin(), logic); const std::ptrdiff_t target_index = index + offset; if (target_index < 0 || target_index >= static_cast(current.controlLogics.size())) { return failure(LogicEditorError::InvalidOperation, "控制逻辑已经位于目标边界"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); std::iter_swap( project.controlLogics.begin() + index, project.controlLogics.begin() + target_index); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::setLogicEnabled( const std::string &logic_id, bool enabled) { if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const ControlLogic *existing = findLogic(logic_id); if (existing->enabled == enabled) { return {true, LogicEditorError::None, {}, logic_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); logic->enabled = enabled; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, logic_id}; } LogicEditorResult LogicEditorService::addRung(const std::string &logic_id) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (logic->rungs.size() >= ProjectLimits::kMaximumRungsPerLogic) { return failure(LogicEditorError::InvalidOperation, "单组控制逻辑最多包含 1024 个网络"); } LadderRung rung; rung.id = makeUniqueRungId(*logic); rung.name = "网络 " + std::to_string(logic->rungs.size() + 1U); HistoryState before = captureState(); Project &project = project_service_.editProject(); auto target = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); target->rungs.push_back(std::move(rung)); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, target->rungs.back().id}; } LogicEditorResult LogicEditorService::removeRung( const std::string &logic_id, const std::string &rung_id) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto target = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); target->rungs.erase( std::remove_if( target->rungs.begin(), target->rungs.end(), [&rung_id](const LadderRung &rung) { return rung.id == rung_id; }), target->rungs.end()); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_id}; } LogicEditorResult LogicEditorService::updateRungComment( const std::string &logic_id, const std::string &rung_id, const std::string &comment) { const LadderRung *rung = findRung(logic_id, rung_id); if (rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (containsLineBreak(comment)) { return failure( LogicEditorError::InvalidOperation, "梯形图网络注释只能使用单行文本"); } if (comment.size() > ProjectLimits::kMaximumRungCommentBytes) { return failure( LogicEditorError::InvalidOperation, "梯形图网络注释不能超过 128 个 UTF-8 字节"); } if (rung->comment == comment) { return {true, LogicEditorError::None, {}, rung_id}; } HistoryState before = captureState(); LadderRung &editable = *findEditableRung( project_service_.editProject(), logic_id, rung_id); editable.comment = comment; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, rung_id}; } LogicEditorResult LogicEditorService::appendCondition( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } const bool create_rung = rung_id.empty(); if (!create_rung && findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (create_rung && logic->rungs.size() >= ProjectLimits::kMaximumRungsPerLogic) { return failure(LogicEditorError::InvalidOperation, "单组控制逻辑最多包含 1024 个网络"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "梯形图条件不能使用线圈节点"); } // 先生成稳定节点 ID,再把新节点接到已有表达式的串联末尾 const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); const std::string target_rung_id = create_rung ? makeUniqueRungId(*logic) : rung_id; ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); auto editable_logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (create_rung) { editable_logic->rungs.push_back({ target_rung_id, "网络 " + std::to_string(editable_logic->rungs.size() + 1U), {}, std::nullopt, std::nullopt}); } LadderRung *rung = findEditableRung(project, logic_id, target_rung_id); if (!rung->condition.has_value()) { rung->condition = std::move(leaf); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(leaf)); } else { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(*rung->condition), std::move(leaf)); } std::string validation_error; // 编辑服务修改后立即做整条网络校验;失败就恢复包括脏标记在内的完整快照 if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::insertConditionAtColumn( const std::string &logic_id, const std::string &rung_id, int column, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr) { return failure( logic == nullptr ? LogicEditorError::LogicNotFound : LogicEditorError::RungNotFound, logic == nullptr ? "未找到控制逻辑" : "未找到梯形图网络"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "梯形图条件不能使用输出节点"); } if (column < 0 || column >= ProjectLimits::kMaximumConditionColumns) { return failure(LogicEditorError::InvalidOperation, "条件插入列必须位于第 1~10 列"); } const int occupied_columns = existing_rung->condition.has_value() ? expressionColumns(*existing_rung->condition) : 0; if (column < occupied_columns) { return failure(LogicEditorError::InvalidOperation, "所选网格已被条件或横线占用"); } // 点击远端空网格时,用持久化横线填充前置空档,再放入真实条件节点 const int leading_wire_columns = column - occupied_columns; const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (leading_wire_columns > 0) { ConditionExpression wire = ConditionExpression::fromWire( makeUniqueWireId(*logic), leading_wire_columns); if (!rung->condition.has_value()) { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(wire), std::move(leaf)); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(wire)); rung->condition->children.push_back(std::move(leaf)); } else { ConditionExpression series; series.id = makeUniqueExpressionId(*logic); series.kind = ConditionExpressionKind::Series; series.children.push_back(std::move(*rung->condition)); series.children.push_back(std::move(wire)); series.children.push_back(std::move(leaf)); rung->condition = std::move(series); } } else if (!rung->condition.has_value()) { rung->condition = std::move(leaf); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(leaf)); } else { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(*rung->condition), std::move(leaf)); } std::string validation_error; // 原子提交要求候选表达式整体通过校验,否则撤销本次所有局部拼接 if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::insertConditionInBranchAtColumn( const std::string &logic_id, const std::string &rung_id, const std::string &branch_expression_id, int column, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure( logic == nullptr ? LogicEditorError::LogicNotFound : LogicEditorError::RungNotFound, logic == nullptr ? "未找到控制逻辑" : "未找到梯形图网络"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "并联空网格只能插入条件节点"); } const ConditionExpression *branch = findExpression( logic_id, rung_id, branch_expression_id); const ConditionExpression *branch_parent = findParentExpression( *existing_rung->condition, branch_expression_id); if (branch == nullptr || branch_parent == nullptr || branch_parent->kind != ConditionExpressionKind::Parallel) { return failure( LogicEditorError::ExpressionNotFound, "未找到并联分支空网格"); } const int branch_columns = expressionColumns(*branch); const int parallel_columns = expressionColumns(*branch_parent); if (column < branch_columns || column >= parallel_columns) { return failure( LogicEditorError::InvalidOperation, "所选位置不是并联分支中的可用空网格"); } const int leading_wire_columns = column - branch_columns; const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); const std::string wire_id = leading_wire_columns > 0 ? makeUniqueWireId(*logic) : std::string{}; const std::string series_id = makeUniqueExpressionId(*logic); ConditionExpression leaf = ConditionExpression::fromNode( makeNode(node_id, config)); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); ConditionExpression *editable_branch = findConditionExpression( *rung->condition, branch_expression_id); if (editable_branch->kind == ConditionExpressionKind::Series) { if (leading_wire_columns > 0) { editable_branch->children.push_back(ConditionExpression::fromWire( wire_id, leading_wire_columns)); } editable_branch->children.push_back(std::move(leaf)); } else { ConditionExpression series; series.id = series_id; series.kind = ConditionExpressionKind::Series; series.children.push_back(std::move(*editable_branch)); if (leading_wire_columns > 0) { series.children.push_back(ConditionExpression::fromWire( wire_id, leading_wire_columns)); } series.children.push_back(std::move(leaf)); *editable_branch = std::move(series); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::appendWire( const std::string &logic_id, const std::string &rung_id, int column_span) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } WireSegment wire{column_span}; std::string error; if (!wire.validate(&error)) { return failure(LogicEditorError::InvalidOperation, error); } const LadderRung *existing_rung = rung_id.empty() ? nullptr : findRung(logic_id, rung_id); if (!rung_id.empty() && existing_rung == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } bool create_rung = rung_id.empty(); if (!create_rung && column_span == 1 && existing_rung->condition.has_value() && expressionColumns(*existing_rung->condition) >= ProjectLimits::kMaximumConditionColumns) { // 无选中目标的连续追加达到十列后,原子切换到下一个网络 create_rung = true; } if (create_rung && logic->rungs.size() >= ProjectLimits::kMaximumRungsPerLogic) { return failure(LogicEditorError::InvalidOperation, "单组控制逻辑最多包含 1024 个网络"); } const std::string target_rung_id = create_rung ? makeUniqueRungId(*logic) : rung_id; const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression leaf = ConditionExpression::fromWire(wire_id, column_span); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); auto editable_logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (create_rung) { editable_logic->rungs.push_back({ target_rung_id, "网络 " + std::to_string(editable_logic->rungs.size() + 1U), {}, std::nullopt, std::nullopt}); } LadderRung *rung = findEditableRung(project, logic_id, target_rung_id); if (!rung->condition.has_value()) { rung->condition = std::move(leaf); } else if (rung->condition->kind == ConditionExpressionKind::Series) { rung->condition->children.push_back(std::move(leaf)); } else { rung->condition = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(*rung->condition), std::move(leaf)); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::insertConditionAfter( const std::string &logic_id, const std::string &rung_id, const std::string &target_node_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } const LadderRung *existing_rung = findRung(logic_id, rung_id); if (!isConditionConfig(config) || existing_rung == nullptr || !existing_rung->condition.has_value() || findConditionNode(*existing_rung->condition, target_node_id) == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到串联插入目标"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); ConditionExpression *target = findConditionExpression(*rung->condition, target_node_id); ConditionExpression *parent = findParentExpression(*rung->condition, target_node_id); if (parent != nullptr && parent->kind == ConditionExpressionKind::Series) { const auto target_iterator = std::find_if( parent->children.begin(), parent->children.end(), [&target_node_id](const ConditionExpression &child) { return child.id == target_node_id; }); const auto insertion_position = target_iterator + 1; if (insertion_position != parent->children.end() && insertion_position->kind == ConditionExpressionKind::Wire) { // 横线代表已占用的网格位置,连续插入时先消费一格而不是扩展网络 if (insertion_position->wire->columnSpan == 1) { *insertion_position = std::move(leaf); } else { --insertion_position->wire->columnSpan; parent->children.insert(insertion_position, std::move(leaf)); } } else { parent->children.insert(insertion_position, std::move(leaf)); } } else { ConditionExpression original = std::move(*target); *target = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(original), std::move(leaf)); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::insertWireAfter( const std::string &logic_id, const std::string &rung_id, const std::string &target_expression_id, int column_span) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); const ConditionExpression *target = findExpression( logic_id, rung_id, target_expression_id); WireSegment wire{column_span}; std::string error; if (logic == nullptr || existing_rung == nullptr || target == nullptr) { return failure(LogicEditorError::ExpressionNotFound, "未找到横线插入位置"); } if ((target->kind != ConditionExpressionKind::Node && target->kind != ConditionExpressionKind::Wire) || !wire.validate(&error)) { return failure( LogicEditorError::InvalidOperation, error.empty() ? "只能在触点或横线后插入横线" : error); } const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression leaf = ConditionExpression::fromWire(wire_id, column_span); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); ConditionExpression *editable_target = findConditionExpression( *rung->condition, target_expression_id); ConditionExpression *parent = findParentExpression( *rung->condition, target_expression_id); if (parent != nullptr && parent->kind == ConditionExpressionKind::Series) { const auto target_iterator = std::find_if( parent->children.begin(), parent->children.end(), [&target_expression_id](const ConditionExpression &child) { return child.id == target_expression_id; }); parent->children.insert(target_iterator + 1, std::move(leaf)); } else { ConditionExpression original = std::move(*editable_target); *editable_target = makeContainer( makeUniqueExpressionId(*logic), ConditionExpressionKind::Series, std::move(original), std::move(leaf)); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::replaceWireWithCondition( const std::string &logic_id, const std::string &rung_id, const std::string &wire_expression_id, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); const ConditionExpression *wire = findExpression( logic_id, rung_id, wire_expression_id); if (logic == nullptr || wire == nullptr || wire->kind != ConditionExpressionKind::Wire) { return failure(LogicEditorError::ExpressionNotFound, "未找到要替换的横线"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "横线只能替换为条件节点"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); ConditionExpression *editable = findConditionExpression( *rung->condition, wire_expression_id); *editable = ConditionExpression::fromNode(makeNode(node_id, config)); std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidNode, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::replaceWireColumnWithCondition( const std::string &logic_id, const std::string &rung_id, const std::string &wire_expression_id, int column_offset, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); const ConditionExpression *wire = findExpression( logic_id, rung_id, wire_expression_id); if (logic == nullptr || wire == nullptr || wire->kind != ConditionExpressionKind::Wire) { return failure(LogicEditorError::ExpressionNotFound, "未找到要替换的横线"); } if (!isConditionConfig(config)) { return failure(LogicEditorError::InvalidNode, "横线只能替换为条件节点"); } if (column_offset < 0 || column_offset >= wire->wire->columnSpan) { return failure(LogicEditorError::InvalidOperation, "横线网格偏移超出有效范围"); } if (wire->wire->columnSpan == 1) { return replaceWireWithCondition( logic_id, rung_id, wire_expression_id, config); } const int leading_columns = column_offset; const int trailing_columns = wire->wire->columnSpan - column_offset - 1; const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression replacement; replacement.id = makeUniqueExpressionId(*logic); replacement.kind = ConditionExpressionKind::Series; if (leading_columns > 0) { replacement.children.push_back(ConditionExpression::fromWire( wire_expression_id, leading_columns)); } replacement.children.push_back( ConditionExpression::fromNode(makeNode(node_id, config))); if (trailing_columns > 0) { replacement.children.push_back(ConditionExpression::fromWire( leading_columns > 0 ? makeUniqueWireId(*logic) : wire_expression_id, trailing_columns)); } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); ConditionExpression *editable = findConditionExpression( *rung->condition, wire_expression_id); *editable = std::move(replacement); normalizeConditionExpression(&rung->condition); std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidNode, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::addParallelBranch( const std::string &logic_id, const std::string &rung_id, const std::vector &selected_node_ids, const LogicNodeConfig &config) { const ControlLogic *logic = findLogic(logic_id); if (logic == nullptr || findRung(logic_id, rung_id) == nullptr) { return failure(LogicEditorError::RungNotFound, "未找到梯形图网络"); } if (!isConditionConfig(config) || selected_node_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "建立并联支路前必须选择节点"); } const LadderRung *existing_rung = findRung(logic_id, rung_id); if (!existing_rung->condition.has_value()) { return failure(LogicEditorError::InvalidOperation, "梯形图网络尚无条件节点"); } NodeIdSet selected_ids(selected_node_ids.cbegin(), selected_node_ids.cend()); if (selected_ids.size() != selected_node_ids.size()) { return failure( LogicEditorError::InvalidOperation, "并联选择中存在重复节点"); } const NodeIdSet available_ids = conditionLeafIds(*existing_rung->condition); if (!isSubset(selected_ids, available_ids)) { return failure( LogicEditorError::NodeNotFound, "并联选择中包含未知节点"); } const std::string node_id = makeUniqueNodeId(*logic, nodePrefix(config)); ConditionExpression leaf = ConditionExpression::fromNode(makeNode(node_id, config)); const std::string parallel_id = makeUniqueExpressionId(*logic); std::string series_id = parallel_id + "-range"; while (findExpression(logic_id, rung_id, series_id) != nullptr) { series_id += "-range"; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (!addParallelForSelection( &*rung->condition, selected_ids, &leaf, parallel_id, series_id)) { rollbackEdit(std::move(before), modified_before); return failure( LogicEditorError::InvalidOperation, "并联选择必须是一个连续的逻辑范围"); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::addParallelWireBranch( const std::string &logic_id, const std::string &rung_id, const std::vector &selected_expression_ids) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图条件网络"); } if (selected_expression_ids.empty()) { return failure( LogicEditorError::InvalidOperation, "建立竖线连接前必须选择连续的触点或横线"); } NodeIdSet selected_ids( selected_expression_ids.cbegin(), selected_expression_ids.cend()); if (selected_ids.size() != selected_expression_ids.size()) { return failure(LogicEditorError::InvalidOperation, "并联选择中存在重复对象"); } const NodeIdSet available_ids = conditionLeafIds(*existing_rung->condition); if (!isSubset(selected_ids, available_ids)) { return failure(LogicEditorError::ExpressionNotFound, "并联选择中包含未知对象"); } const std::optional column_span = selectedExpressionColumns( *existing_rung->condition, selected_ids); if (!column_span.has_value() || *column_span > WireSegment::kMaximumColumnSpan) { return failure( LogicEditorError::InvalidOperation, "竖线连接只能围绕同一支路中的连续逻辑范围"); } const std::string wire_id = makeUniqueWireId(*logic); ConditionExpression wire = ConditionExpression::fromWire( wire_id, *column_span); const std::string parallel_id = makeUniqueExpressionId(*logic); std::string series_id = parallel_id + "-range"; while (findExpression(logic_id, rung_id, series_id) != nullptr) { series_id += "-range"; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); if (!addParallelForSelection( &*rung->condition, selected_ids, &wire, parallel_id, series_id)) { rollbackEdit(std::move(before), modified_before); return failure( LogicEditorError::InvalidOperation, "竖线连接只能围绕同一支路中的连续逻辑范围"); } std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, wire_id}; } LogicEditorResult LogicEditorService::addParallelWireBranchAtCells( const std::string &logic_id, const std::string &rung_id, const std::vector> &selected_wire_cells) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = findRung(logic_id, rung_id); if (logic == nullptr || existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图条件网络"); } if (selected_wire_cells.empty()) { return failure( LogicEditorError::InvalidOperation, "建立竖线连接前必须选择横线网格"); } struct SelectedWireCell { std::string wire_id; int offset = 0; int absolute_column = 0; }; std::vector cells; cells.reserve(selected_wire_cells.size()); for (const auto &cell : selected_wire_cells) { if (cell.first.empty()) { return failure( LogicEditorError::ExpressionNotFound, "未找到选中的横线"); } const ConditionExpression *wire = findExpression( logic_id, rung_id, cell.first); if (wire == nullptr || wire->kind != ConditionExpressionKind::Wire || !wire->wire.has_value() || cell.second < 0 || cell.second >= wire->wire->columnSpan) { return failure( LogicEditorError::InvalidOperation, "竖线网格列偏移超出横线范围"); } cells.push_back({cell.first, cell.second, 0}); } const bool same_wire = std::all_of( cells.cbegin(), cells.cend(), [&cells](const SelectedWireCell &cell) { return cell.wire_id == cells.front().wire_id; }); const std::string wire_expression_id = cells.front().wire_id; const ConditionExpression *selected_wire = findExpression( logic_id, rung_id, wire_expression_id); if (selected_wire == nullptr || !selected_wire->wire.has_value()) { return failure(LogicEditorError::ExpressionNotFound, "未找到选中的横线"); } int leading_columns = 0; int trailing_columns = 0; std::string selected_series_id; std::size_t selected_first_child = 0U; std::size_t selected_last_child = 0U; if (same_wire) { std::vector offsets; offsets.reserve(cells.size()); for (const SelectedWireCell &cell : cells) { offsets.push_back(cell.offset); } std::sort(offsets.begin(), offsets.end()); if (std::adjacent_find(offsets.cbegin(), offsets.cend()) != offsets.cend() || offsets.back() - offsets.front() + 1 != static_cast(offsets.size())) { return failure( LogicEditorError::InvalidOperation, "竖线网格必须是连续列"); } for (SelectedWireCell &cell : cells) { cell.absolute_column = cell.offset; } leading_columns = offsets.front(); trailing_columns = selected_wire->wire->columnSpan - offsets.front() - static_cast(offsets.size()); std::sort( cells.begin(), cells.end(), [](const SelectedWireCell &left, const SelectedWireCell &right) { return left.absolute_column < right.absolute_column; }); } else { const ConditionExpression *source_series = nullptr; for (SelectedWireCell &cell : cells) { WireCellLocation location; if (!findWireCellLocation( *existing_rung->condition, cell.wire_id, &location) || location.series == nullptr) { return failure( LogicEditorError::InvalidOperation, "竖线网格必须位于同一条连续串联路径"); } if (source_series == nullptr) { source_series = location.series; } else if (source_series != location.series) { return failure( LogicEditorError::InvalidOperation, "竖线网格必须位于同一条连续串联路径"); } cell.absolute_column = location.child_start + cell.offset; } std::sort( cells.begin(), cells.end(), [](const SelectedWireCell &left, const SelectedWireCell &right) { return left.absolute_column < right.absolute_column; }); if (std::adjacent_find( cells.cbegin(), cells.cend(), [](const SelectedWireCell &left, const SelectedWireCell &right) { return left.absolute_column == right.absolute_column; }) != cells.cend() || cells.back().absolute_column - cells.front().absolute_column + 1 != static_cast(cells.size())) { return failure( LogicEditorError::InvalidOperation, "竖线网格必须是连续列"); } int child_start = 0; std::size_t first_child = source_series->children.size(); std::size_t last_child = source_series->children.size(); const int range_begin = cells.front().absolute_column; const int range_end = cells.back().absolute_column + 1; for (std::size_t index = 0; index < source_series->children.size(); ++index) { const ConditionExpression &child = source_series->children[index]; const int child_end = child_start + expressionColumns(child); if (child_end > range_begin && child_start < range_end) { if (child.kind != ConditionExpressionKind::Wire) { return failure( LogicEditorError::InvalidOperation, "竖线网格不能跨越触点或并联支路"); } if (first_child == source_series->children.size()) { first_child = index; } last_child = index; } child_start = child_end; } if (first_child == source_series->children.size()) { return failure( LogicEditorError::InvalidOperation, "未找到连续的横线网格范围"); } selected_series_id = source_series->id; selected_first_child = first_child; selected_last_child = last_child; int layout_start = 0; for (std::size_t index = 0; index < first_child; ++index) { layout_start += expressionColumns(source_series->children[index]); } int selected_end = layout_start; for (std::size_t index = first_child; index <= last_child; ++index) { selected_end += expressionColumns(source_series->children[index]); } leading_columns = cells.front().absolute_column - layout_start; trailing_columns = selected_end - (cells.back().absolute_column + 1); } const int first_column = cells.front().absolute_column; const int selected_columns = static_cast(cells.size()); std::unordered_set generated_wire_ids; const auto makeGeneratedWireId = [&]() { std::string candidate = makeUniqueWireId(*logic); while (generated_wire_ids.count(candidate) != 0U || findExpression(logic_id, rung_id, candidate) != nullptr) { candidate += "-split"; } generated_wire_ids.insert(candidate); return candidate; }; const std::string branch_wire_id = makeGeneratedWireId(); const std::string selected_wire_id = same_wire ? wire_expression_id : cells.front().wire_id; const std::string leading_wire_id = leading_columns > 0 ? makeGeneratedWireId() : std::string{}; const std::string trailing_wire_id = trailing_columns > 0 ? makeGeneratedWireId() : std::string{}; ConditionExpression branch = ConditionExpression::fromWire( branch_wire_id, selected_columns); const std::string parallel_id = makeUniqueExpressionId(*logic); std::string series_id = parallel_id + "-range"; while (findExpression(logic_id, rung_id, series_id) != nullptr) { series_id += "-range"; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); bool inserted = false; if (same_wire) { inserted = addParallelForWireCellRange( &*rung->condition, wire_expression_id, first_column, selected_columns, &branch, parallel_id, series_id, leading_wire_id, trailing_wire_id); } else { inserted = addParallelForWireCellSeriesRange( &*rung->condition, selected_series_id, selected_first_child, selected_last_child, leading_columns, selected_columns, trailing_columns, &branch, selected_wire_id, parallel_id, leading_wire_id, trailing_wire_id); } if (!inserted) { rollbackEdit(std::move(before), modified_before); return failure( LogicEditorError::InvalidOperation, "竖线连接只能围绕同一条横线中的连续网格建立"); } normalizeConditionExpression(&rung->condition); std::string validation_error; if (!rung->validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, branch_wire_id}; } LogicEditorResult LogicEditorService::setOutput( const std::string &logic_id, const std::string &rung_id, const LogicNodeConfig &config, bool configured) { const ControlLogic *logic = findLogic(logic_id); const LadderRung *existing_rung = rung_id.empty() ? nullptr : findRung(logic_id, rung_id); if (logic == nullptr || (!rung_id.empty() && existing_rung == nullptr) || !isOutputConfig(config)) { return failure( logic == nullptr ? LogicEditorError::LogicNotFound : existing_rung == nullptr && !rung_id.empty() ? LogicEditorError::RungNotFound : LogicEditorError::InvalidNode, logic == nullptr ? "未找到控制逻辑" : existing_rung == nullptr && !rung_id.empty() ? "未找到梯形图网络" : "梯形图输出必须使用有效的输出指令"); } if (rung_id.empty() && logic->rungs.size() >= ProjectLimits::kMaximumRungsPerLogic) { return failure(LogicEditorError::InvalidOperation, "单组控制逻辑最多包含 1024 个网络"); } const bool create_rung = rung_id.empty(); const std::string target_rung_id = create_rung ? makeUniqueRungId(*logic) : rung_id; const std::string node_id = existing_rung != nullptr && existing_rung->output.has_value() ? existing_rung->output->id : makeUniqueNodeId(*logic, nodePrefix(config)); LogicNode node = makeNode(node_id, config); node.configured = configured; std::string validation_error; if (!node.validate(&validation_error)) { return failure(LogicEditorError::InvalidNode, validation_error); } if (existing_rung != nullptr && existing_rung->output.has_value() && nodesEqual(*existing_rung->output, node)) { return {true, LogicEditorError::None, {}, node_id}; } HistoryState before = captureState(); Project &project = project_service_.editProject(); auto editable_logic = std::find_if( project.controlLogics.begin(), project.controlLogics.end(), [&logic_id](const ControlLogic &candidate) { return candidate.id == logic_id; }); if (create_rung) { editable_logic->rungs.push_back({ target_rung_id, "网络 " + std::to_string(editable_logic->rungs.size() + 1U), {}, std::nullopt, std::nullopt}); } findEditableRung(project, logic_id, target_rung_id)->output = std::move(node); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } LogicEditorResult LogicEditorService::updateNodeConfig( const std::string &logic_id, const std::string &node_id, const LogicNodeConfig &config) { const LogicNode *node = findNode(logic_id, node_id); if (node == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } if (node->config.index() != config.index()) { return failure( LogicEditorError::UnsupportedNodeChange, "节点创建后不能修改节点类别"); } LogicNode candidate{node_id, config, true}; std::string error; if (!candidate.validate(&error)) { return failure(LogicEditorError::InvalidNode, error); } if (nodesEqual(*node, candidate)) { return {true, LogicEditorError::None, {}, node_id}; } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); for (ControlLogic &logic : project.controlLogics) { if (logic.id != logic_id) { continue; } for (LadderRung &rung : logic.rungs) { if (rung.output.has_value() && rung.output->id == node_id) { *rung.output = candidate; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } if (rung.condition.has_value()) { if (LogicNode *editable = findConditionNode(*rung.condition, node_id)) { *editable = candidate; recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_id}; } } } } project_service_.restoreModifiedState(modified_before); return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } LogicEditorResult LogicEditorService::removeNode( const std::string &logic_id, const std::string &node_id) { return removeNodes(logic_id, {node_id}); } LogicEditorResult LogicEditorService::removeNodes( const std::string &logic_id, const std::vector &node_ids) { if (findLogic(logic_id) == nullptr) { return failure(LogicEditorError::LogicNotFound, "未找到控制逻辑"); } if (node_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的逻辑节点"); } std::unordered_set selected_ids; for (const std::string &node_id : node_ids) { if (!selected_ids.insert(node_id).second) { return failure(LogicEditorError::InvalidOperation, "删除列表中存在重复节点"); } if (findNode(logic_id, node_id) == nullptr) { return failure(LogicEditorError::NodeNotFound, "未找到逻辑节点"); } } HistoryState before = captureState(); const bool modified_before = project_service_.isModified(); Project &project = project_service_.editProject(); ControlLogic *editable_logic = nullptr; for (ControlLogic &candidate : project.controlLogics) { if (candidate.id == logic_id) { editable_logic = &candidate; break; } } for (LadderRung &rung : editable_logic->rungs) { if (rung.output.has_value() && selected_ids.find(rung.output->id) != selected_ids.end()) { rung.output.reset(); } if (rung.condition.has_value()) { for (const std::string &node_id : node_ids) { if (rung.condition->kind == ConditionExpressionKind::Node && rung.condition->id == node_id) { rung.condition.reset(); break; } removeExpressionRecursive(&*rung.condition, node_id); } if (rung.condition.has_value()) { normalizeConditionExpression(&rung.condition); } } } for (const LadderRung &rung : editable_logic->rungs) { std::string validation_error; if (!rung.validate(&validation_error)) { rollbackEdit(std::move(before), modified_before); return failure(LogicEditorError::InvalidOperation, validation_error); } } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, node_ids.front()}; } LogicEditorResult LogicEditorService::removeExpression( const std::string &logic_id, const std::string &rung_id, const std::string &expression_id) { if (findExpression(logic_id, rung_id, expression_id) == nullptr) { return failure(LogicEditorError::ExpressionNotFound, "未找到条件支路"); } HistoryState before = captureState(); Project &project = project_service_.editProject(); LadderRung *rung = findEditableRung(project, logic_id, rung_id); if (rung->condition->id == expression_id) { rung->condition.reset(); } else { removeExpressionRecursive(&*rung->condition, expression_id); normalizeConditionExpression(&rung->condition); } recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, expression_id}; } LogicEditorResult LogicEditorService::removeExpressions( const std::string &logic_id, const std::string &rung_id, const std::vector &expression_ids) { const LadderRung *existing_rung = findRung(logic_id, rung_id); if (existing_rung == nullptr || !existing_rung->condition.has_value()) { return failure(LogicEditorError::RungNotFound, "未找到梯形图条件网络"); } if (expression_ids.empty()) { return failure(LogicEditorError::InvalidOperation, "请先选择要删除的条件或横线"); } NodeIdSet unique_ids; for (const std::string &expression_id : expression_ids) { const ConditionExpression *expression = findExpression( logic_id, rung_id, expression_id); if (!unique_ids.insert(expression_id).second) { return failure(LogicEditorError::InvalidOperation, "删除列表中存在重复对象"); } if (expression == nullptr) { return failure( LogicEditorError::ExpressionNotFound, "删除列表中包含未知条件表达式"); } } HistoryState before = captureState(); LadderRung *rung = findEditableRung( project_service_.editProject(), logic_id, rung_id); for (const std::string &expression_id : expression_ids) { if (!rung->condition.has_value()) { break; } if (rung->condition->id == expression_id) { rung->condition.reset(); break; } removeExpressionRecursive(&*rung->condition, expression_id); } normalizeConditionExpression(&rung->condition); recordHistory(std::move(before)); return {true, LogicEditorError::None, {}, expression_ids.front()}; } bool LogicEditorService::canUndo() const { return history_.canUndo(); } bool LogicEditorService::canRedo() const { return history_.canRedo(); } LogicEditorResult LogicEditorService::undo() { const std::optional target = history_.undo(captureState()); if (!target.has_value()) { return historyFailure("没有可撤销的梯形图编辑操作"); } project_service_.editProject().controlLogics = target->logics; return {true, LogicEditorError::None, {}, {}}; } LogicEditorResult LogicEditorService::redo() { const std::optional target = history_.redo(captureState()); if (!target.has_value()) { return historyFailure("没有可重做的梯形图编辑操作"); } project_service_.editProject().controlLogics = target->logics; return {true, LogicEditorError::None, {}, {}}; } void LogicEditorService::clearHistory() { history_.clear(); } bool LogicEditorService::isConditionConfig(const LogicNodeConfig &config) { return std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config); } bool LogicEditorService::isOutputConfig(const LogicNodeConfig &config) { return std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config) || std::holds_alternative(config); } std::string LogicEditorService::nodePrefix(const LogicNodeConfig &config) { return std::visit( [](const auto &value) -> std::string { using Config = std::decay_t; if constexpr (std::is_same_v) { return "contact"; } else if constexpr (std::is_same_v) { return "edge"; } else if constexpr (std::is_same_v) { return "timer-contact"; } else if constexpr (std::is_same_v) { return "counter-contact"; } else if constexpr (std::is_same_v) { return "coil"; } else if constexpr (std::is_same_v) { return "ton"; } else if constexpr (std::is_same_v) { return "counter"; } else if constexpr (std::is_same_v) { return "move"; } else if constexpr (std::is_same_v) { return "arithmetic"; } else { return "compare"; } }, config); } std::string LogicEditorService::makeUniqueNodeId( const ControlLogic &logic, const std::string &prefix) { for (std::size_t index = 1;; ++index) { const std::string candidate = prefix + '-' + std::to_string(index); bool found = false; for (const LadderRung &rung : logic.rungs) { found = found || (rung.output.has_value() && rung.output->id == candidate) || (rung.condition.has_value() && findConditionNode(*rung.condition, candidate) != nullptr); } if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueWireId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "wire-" + std::to_string(index); const bool found = std::any_of( logic.rungs.cbegin(), logic.rungs.cend(), [&candidate](const LadderRung &rung) { return rung.condition.has_value() && findConditionExpression(*rung.condition, candidate) != nullptr; }); if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueExpressionId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "expression-" + std::to_string(index); bool found = false; for (const LadderRung &rung : logic.rungs) { found = found || (rung.condition.has_value() && findConditionExpression(*rung.condition, candidate) != nullptr); } if (!found) { return candidate; } } } std::string LogicEditorService::makeUniqueRungId(const ControlLogic &logic) { for (std::size_t index = 1;; ++index) { const std::string candidate = "rung-" + std::to_string(index); if (std::none_of( logic.rungs.cbegin(), logic.rungs.cend(), [&candidate](const LadderRung &rung) { return rung.id == candidate; })) { return candidate; } } } LogicEditorResult LogicEditorService::failure( LogicEditorError error, const std::string &message) { return {false, error, message, {}}; }