// 「公式映射」表达式的编译 / 求值实现(详见 `FormulaEval.hpp`)。 #include "FormulaEval.hpp" #include #include #include #include #include #include namespace jdi { namespace { /** 公式允许的最大长度(防止恶意/误粘贴的超长文本导致解析耗时)。 */ constexpr std::size_t kMaxFormulaLength = 512; /** 允许的最大记号数(与长度一起兜底)。 */ constexpr std::size_t kMaxFormulaTokens = 512; /** 逆波兰式里的一个运算单元。 */ struct FormulaOp { enum class Kind : int { NUMBER = 0, /**< 常量。 */ VARIABLE, /**< 自变量(采样值)。 */ ADD, SUBTRACT, MULTIPLY, DIVIDE, NEGATE }; Kind kind = Kind::NUMBER; double value = 0.0; }; /** 单个自变量占用的栈槽(求值时用)。 */ constexpr std::size_t kMaxStack = 64; /** 记号类型。 */ enum class TokenKind : int { NUMBER = 0, VARIABLE, PLUS, MINUS, STAR, SLASH, LEFT_PAREN, RIGHT_PAREN, END }; struct Token { TokenKind kind = TokenKind::END; double value = 0.0; }; /** 把一个公式文本切成记号序列(失败返回 false 并写 error)。 */ bool Tokenize(const std::wstring& text, std::vector& tokens, std::wstring* error) { tokens.clear(); std::size_t index = 0; const std::size_t length = text.size(); if (length > kMaxFormulaLength) { if (error != nullptr) { *error = L"公式过长(最多 512 个字符)。"; } return false; } while (index < length) { const wchar_t ch = text[index]; if (std::iswspace(static_cast(ch)) != 0) { ++index; continue; } if (ch == L'+') { tokens.push_back(Token{TokenKind::PLUS, 0.0}); ++index; } else if (ch == L'-') { tokens.push_back(Token{TokenKind::MINUS, 0.0}); ++index; } else if (ch == L'*') { tokens.push_back(Token{TokenKind::STAR, 0.0}); ++index; } else if (ch == L'/') { tokens.push_back(Token{TokenKind::SLASH, 0.0}); ++index; } else if (ch == L'(') { tokens.push_back(Token{TokenKind::LEFT_PAREN, 0.0}); ++index; } else if (ch == L')') { tokens.push_back(Token{TokenKind::RIGHT_PAREN, 0.0}); ++index; } else if ((ch >= L'0' && ch <= L'9') || ch == L'.') { // 数字:手工扫描,交给 `wcstod` 转换(保证与内联编辑的数值语义一致)。 const std::size_t start = index; while (index < length && ((text[index] >= L'0' && text[index] <= L'9') || text[index] == L'.')) { ++index; } if (index < length && (text[index] == L'e' || text[index] == L'E')) { ++index; if (index < length && (text[index] == L'+' || text[index] == L'-')) { ++index; } while (index < length && text[index] >= L'0' && text[index] <= L'9') { ++index; } } const std::wstring literal = text.substr(start, index - start); wchar_t* stop = nullptr; const double value = std::wcstod(literal.c_str(), &stop); if (stop == literal.c_str() || !std::isfinite(value)) { if (error != nullptr) { *error = L"数字格式不正确。"; } return false; } tokens.push_back(Token{TokenKind::NUMBER, value}); } else if (std::iswalpha(static_cast(ch)) != 0) { // 标识符:只允许 X / x / f / f(t) / pi。 const std::size_t start = index; while (index < length && std::iswalpha(static_cast(text[index])) != 0) { ++index; } std::wstring name = text.substr(start, index - start); // 跳过空格后可能是 `(t)`(`f(t)` 的写法)。 std::size_t look = index; while (look < length && std::iswspace(static_cast(text[look])) != 0) { ++look; } if (look < length && text[look] == L'(') { std::size_t inner = look + 1; while (inner < length && std::iswspace(static_cast(text[inner])) != 0) { ++inner; } if (inner < length && (text[inner] == L't' || text[inner] == L'T')) { ++inner; while (inner < length && std::iswspace(static_cast(text[inner])) != 0) { ++inner; } if (inner < length && text[inner] == L')') { index = inner + 1; } } } for (wchar_t& letter : name) { letter = static_cast(std::towlower(static_cast(letter))); } if (name == L"x" || name == L"f") { tokens.push_back(Token{TokenKind::VARIABLE, 0.0}); } else if (name == L"pi") { tokens.push_back(Token{TokenKind::NUMBER, 3.14159265358979323846}); } else { if (error != nullptr) { *error = L"无法识别的标识符(自变量请写 f(t) 或 X)。"; } return false; } } else { if (error != nullptr) { *error = L"公式含有无法识别的字符。"; } return false; } } tokens.push_back(Token{TokenKind::END, 0.0}); if (tokens.size() > kMaxFormulaTokens) { if (error != nullptr) { *error = L"公式过于复杂。"; } return false; } return true; } /** 递归下降解析器(直接产出逆波兰式)。 */ class Parser { public: Parser(const std::vector& tokens, std::vector& output, std::wstring* error) : m_tokens(tokens), m_output(output), m_error(error) { } bool Parse() { if (!ParseExpression()) { return false; } if (Current().kind != TokenKind::END) { return SetError(L"公式存在多余的内容(请检查括号是否配对)。"); } return true; } private: const Token& Current() const { return m_tokens[m_position]; } void Advance() { if (m_position + 1 < m_tokens.size()) { ++m_position; } } bool SetError(const wchar_t* message) { if (m_error != nullptr) { *m_error = message; } return false; } bool ParseExpression() { if (!ParseTerm()) { return false; } while (Current().kind == TokenKind::PLUS || Current().kind == TokenKind::MINUS) { const bool add = (Current().kind == TokenKind::PLUS); Advance(); if (!ParseTerm()) { return false; } m_output.push_back(FormulaOp{add ? FormulaOp::Kind::ADD : FormulaOp::Kind::SUBTRACT, 0.0}); } return true; } bool ParseTerm() { if (!ParseUnary()) { return false; } while (Current().kind == TokenKind::STAR || Current().kind == TokenKind::SLASH) { const bool multiply = (Current().kind == TokenKind::STAR); Advance(); if (!ParseUnary()) { return false; } m_output.push_back(FormulaOp{multiply ? FormulaOp::Kind::MULTIPLY : FormulaOp::Kind::DIVIDE, 0.0}); } return true; } bool ParseUnary() { if (Current().kind == TokenKind::PLUS) { Advance(); return ParseUnary(); } if (Current().kind == TokenKind::MINUS) { Advance(); if (!ParseUnary()) { return false; } m_output.push_back(FormulaOp{FormulaOp::Kind::NEGATE, 0.0}); return true; } return ParsePrimary(); } bool ParsePrimary() { const Token& token = Current(); if (token.kind == TokenKind::NUMBER) { m_output.push_back(FormulaOp{FormulaOp::Kind::NUMBER, token.value}); Advance(); return true; } if (token.kind == TokenKind::VARIABLE) { m_output.push_back(FormulaOp{FormulaOp::Kind::VARIABLE, 0.0}); Advance(); return true; } if (token.kind == TokenKind::LEFT_PAREN) { Advance(); if (!ParseExpression()) { return false; } if (Current().kind != TokenKind::RIGHT_PAREN) { return SetError(L"缺少右括号。"); } Advance(); return true; } return SetError(L"公式不完整或存在多余的运算符。"); } const std::vector& m_tokens; std::vector& m_output; std::wstring* m_error; std::size_t m_position = 0; }; /** 编译结果(逆波兰式);空表示"不可用"。 */ using Program = std::vector; /** 线程内的单条编译缓存(按 data 指针 + 内容判定)。 */ struct FormulaCache { const wchar_t* data = nullptr; std::size_t size = 0; std::wstring text; Program program; bool compiled = false; bool valid = false; }; /** * @brief 取(必要时编译)公式对应的程序。 * @return 合法时返回程序指针;空文本或非法时返回 nullptr。 */ const Program* AcquireProgram(const std::wstring& text) { if (text.empty()) { return nullptr; } static thread_local FormulaCache cache; if (cache.data == text.data() && cache.size == text.size() && cache.compiled) { return cache.valid ? &cache.program : nullptr; } // 指针变化:先比内容,内容相同则直接复用(避免同一公式被多个样式副本反复编译)。 if (cache.compiled && cache.text == text) { cache.data = text.data(); cache.size = text.size(); return cache.valid ? &cache.program : nullptr; } cache.text = text; cache.data = text.data(); cache.size = text.size(); cache.program.clear(); cache.valid = false; cache.compiled = true; std::vector tokens; std::wstring error; if (!Tokenize(text, tokens, &error)) { return nullptr; } Parser parser(tokens, cache.program, &error); if (!parser.Parse()) { cache.program.clear(); return nullptr; } cache.valid = true; return &cache.program; } } // namespace bool ValidateFormula(const std::wstring& text, std::wstring* error) { if (error != nullptr) { error->clear(); } if (text.empty()) { return true; } std::vector tokens; if (!Tokenize(text, tokens, error)) { return false; } std::vector program; Parser parser(tokens, program, error); if (!parser.Parse()) { return false; } return true; } double EvaluateFormula(const std::wstring& text, double x) { const Program* program = AcquireProgram(text); if (program == nullptr) { return x; // 空 / 非法:不做映射(保证波形照常显示) } double stack[kMaxStack]; std::size_t depth = 0; for (const FormulaOp& op : *program) { switch (op.kind) { case FormulaOp::Kind::NUMBER: case FormulaOp::Kind::VARIABLE: if (depth >= kMaxStack) { return std::numeric_limits::quiet_NaN(); } stack[depth++] = (op.kind == FormulaOp::Kind::NUMBER) ? op.value : x; break; case FormulaOp::Kind::NEGATE: if (depth < 1) { return std::numeric_limits::quiet_NaN(); } stack[depth - 1] = -stack[depth - 1]; break; default: { if (depth < 2) { return std::numeric_limits::quiet_NaN(); } const double b = stack[depth - 1]; const double a = stack[depth - 2]; --depth; double result = 0.0; switch (op.kind) { case FormulaOp::Kind::ADD: result = a + b; break; case FormulaOp::Kind::SUBTRACT: result = a - b; break; case FormulaOp::Kind::MULTIPLY: result = a * b; break; case FormulaOp::Kind::DIVIDE: result = a / b; break; default: break; } stack[depth - 1] = result; break; } } } if (depth != 1) { return std::numeric_limits::quiet_NaN(); } return std::isfinite(stack[0]) ? stack[0] : std::numeric_limits::quiet_NaN(); } std::wstring NormalizeFormulaText(const std::wstring& text) { std::wstring output; output.reserve(text.size() + 8); const std::size_t length = text.size(); std::size_t index = 0; while (index < length) { const wchar_t ch = text[index]; if (std::iswalpha(static_cast(ch)) == 0) { output.push_back(ch); ++index; continue; } const std::size_t start = index; while (index < length && std::iswalpha(static_cast(text[index])) != 0) { ++index; } std::wstring name = text.substr(start, index - start); // 已经是 `f(t)`:跳过随后的 `(t)`,整体原样输出(统一写成小写形式)。 std::size_t look = index; while (look < length && std::iswspace(static_cast(text[look])) != 0) { ++look; } bool hasArgument = false; if (look < length && text[look] == L'(') { std::size_t inner = look + 1; while (inner < length && std::iswspace(static_cast(text[inner])) != 0) { ++inner; } if (inner < length && (text[inner] == L't' || text[inner] == L'T')) { ++inner; while (inner < length && std::iswspace(static_cast(text[inner])) != 0) { ++inner; } if (inner < length && text[inner] == L')') { hasArgument = true; index = inner + 1; } } } std::wstring lowered = name; for (wchar_t& letter : lowered) { letter = static_cast(std::towlower(static_cast(letter))); } if (hasArgument && (lowered == L"f" || lowered == L"x")) { // 统一写成小写规范形式 `f(t)`(保留用户其余写法)。 output += L"f(t)"; } else if (!hasArgument && (lowered == L"x" || lowered == L"f")) { output += L"f(t)"; } else { output += name; } } return output; } } // namespace jdi