FuncSketch
Loading...
Searching...
No Matches
expression_grammar.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2026 MusicScience37 (Kenta Kabashima)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
21
22#include <boost/bind.hpp>
23#include <boost/fusion/adapted/struct/adapt_struct.hpp>
24#include <boost/phoenix/bind.hpp>
25#include <boost/phoenix/fusion/at.hpp>
26#include <fmt/format.h>
27#include <fmt/ostream.h>
28
31
32BOOST_FUSION_ADAPT_STRUCT(
34BOOST_FUSION_ADAPT_STRUCT(
35 func_sketch::parser::ParsedIdentifier, (std::string, name))
38 std::vector<func_sketch::parser::ParsedExpression>, arguments))
39BOOST_FUSION_ADAPT_STRUCT(func_sketch::parser::ParsedUnaryExpression,
40 (std::string, operator_str)(func_sketch::parser::ParsedExpression, operand))
41BOOST_FUSION_ADAPT_STRUCT(func_sketch::parser::ParsedBinaryExpression,
42 (std::string, operator_str)(func_sketch::parser::ParsedExpression,
43 left_operand)(func_sketch::parser::ParsedExpression, right_operand))
44
45namespace func_sketch::parser {
46
48 : ExpressionGrammar::base_type(expr_rule_) {
49 using boost::phoenix::at_c;
50 using boost::phoenix::bind;
51 using boost::phoenix::push_back;
52 using boost::spirit::qi::alpha;
53 using boost::spirit::qi::char_;
54 using boost::spirit::qi::digit;
55 using boost::spirit::qi::double_;
56 using boost::spirit::qi::fail;
57 using boost::spirit::qi::int_;
58 using boost::spirit::qi::lexeme;
59 using boost::spirit::qi::on_error;
60 using boost::spirit::qi::real_parser;
61 using boost::spirit::qi::strict_real_policies;
62 using boost::spirit::qi::labels::_1;
63 using boost::spirit::qi::labels::_2;
64 using boost::spirit::qi::labels::_3;
65 using boost::spirit::qi::labels::_4;
66 using boost::spirit::qi::labels::_a;
67 using boost::spirit::qi::labels::_val;
68
69 // NOLINTBEGIN(bugprone-chained-comparison): This is a grammar definition, so the code is not a chained comparison.
70#ifdef __clang__
71#pragma clang diagnostic push
72#pragma clang diagnostic ignored "-Woverloaded-shift-op-parentheses"
73#endif
74
75 // Almost all rules are defined using expectation (>).
76 // Only function call expression rule has (>>) to allow parsing both
77 // "exp(1.23)" (function call expression) and "x" (identifier only).
78
79 const auto handle_imaginary_number = [](Complex& result,
80 const Real& value) {
81 result = Complex{0.0, value};
82 };
83 imaginary_number_rule_ =
84 lexeme[double_[bind(handle_imaginary_number, _val, _1)] >> 'i'];
85
86 const real_parser<double, strict_real_policies<double>> strict_double;
87 literal_rule_ = imaginary_number_rule_[at_c<0>(_val) = _1] |
88 strict_double[at_c<0>(_val) = _1] | int_[at_c<0>(_val) = _1];
89
90 identifier_rule_ = lexeme[(alpha | char_('_'))[at_c<0>(_val) += _1] >
91 *(alpha | char_('_') | digit)[at_c<0>(_val) += _1]];
92
93 function_call_expr_rule_ = identifier_rule_[at_c<0>(_val) = _1] >> '(' >
94 -(sum_expr_rule_[push_back(at_c<1>(_val), _1)] >
95 *(',' > sum_expr_rule_[push_back(at_c<1>(_val), _1)])) > ')';
96
97 atomic_value_expr_rule_ =
98 function_call_expr_rule_ | literal_rule_ | identifier_rule_;
99
100 value_expr_rule_ = atomic_value_expr_rule_ | '(' > sum_expr_rule_ > ')';
101
102 const auto handle_power = [](ParsedExpression& left,
103 const ParsedExpression& right) {
104 left = ParsedBinaryExpression{
105 .operator_str = "**", .left_operand = left, .right_operand = right};
106 };
107 factor_expr_rule_ = value_expr_rule_[_val = _1] >
108 -("**" > factor_expr_rule_[bind(handle_power, _val, _1)]);
109
110 const auto handle_unary_minus = [](ParsedExpression& result,
111 const ParsedExpression& operand) {
112 result = ParsedUnaryExpression{.operator_str = "-", .operand = operand};
113 };
114 unary_expr_rule_ =
115 ('-' > factor_expr_rule_[bind(handle_unary_minus, _val, _1)]) |
116 factor_expr_rule_[_val = _1];
117
118 const auto handle_multiplication = [](ParsedExpression& left,
119 const ParsedExpression& right) {
120 left = ParsedBinaryExpression{
121 .operator_str = "*", .left_operand = left, .right_operand = right};
122 };
123 const auto handle_division = [](ParsedExpression& left,
124 const ParsedExpression& right) {
125 left = ParsedBinaryExpression{
126 .operator_str = "/", .left_operand = left, .right_operand = right};
127 };
128 term_expr_rule_ = unary_expr_rule_[_val = _1] >
129 *('*' > unary_expr_rule_[bind(handle_multiplication, _val, _1)] |
130 '/' > unary_expr_rule_[bind(handle_division, _val, _1)]);
131
132 const auto handle_addition = [](ParsedExpression& left,
133 const ParsedExpression& right) {
134 left = ParsedBinaryExpression{
135 .operator_str = "+", .left_operand = left, .right_operand = right};
136 };
137 const auto handle_subtraction = [](ParsedExpression& left,
138 const ParsedExpression& right) {
139 left = ParsedBinaryExpression{
140 .operator_str = "-", .left_operand = left, .right_operand = right};
141 };
142 sum_expr_rule_ = term_expr_rule_[_val = _1] >
143 *('+' > term_expr_rule_[bind(handle_addition, _val, _1)] |
144 '-' > term_expr_rule_[bind(handle_subtraction, _val, _1)]);
145
146 expr_rule_ = sum_expr_rule_[_val = _1];
147
148#ifdef __clang__
149#pragma clang diagnostic pop
150#endif
151
152 // NOLINTEND(bugprone-chained-comparison)
153
154 imaginary_number_rule_.name("imaginary number");
155 literal_rule_.name("literal");
156 identifier_rule_.name("identifier");
157 // For error messages, we want to show all expressions as "expression"
158 // instead of actual rule names, since almost users will not know about
159 // those rules.
160 function_call_expr_rule_.name("expression");
161 atomic_value_expr_rule_.name("expression");
162 value_expr_rule_.name("expression");
163 factor_expr_rule_.name("expression");
164 unary_expr_rule_.name("expression");
165 term_expr_rule_.name("expression");
166 sum_expr_rule_.name("expression");
167 expr_rule_.name("expression");
168
169 const auto handle_error = [this](const auto& first, const auto& /*last*/,
170 const auto& error_pos, const auto& what) {
171 error_message_ = fmt::format("Expected {} at position {}.",
172 fmt::streamed(what), std::distance(first, error_pos));
173 };
174 on_error<fail>(expr_rule_, bind(handle_error, _1, _2, _3, _4));
175}
176
177const std::string& ExpressionGrammar::error_message() const noexcept {
178 return error_message_;
179}
180
181void ExpressionGrammar::clear_error_message() noexcept {
182 error_message_.clear();
183}
184
185ParsedExpression ExpressionGrammar::parse(const std::string& str) {
186 ParsedExpression parsed_expression;
187 auto iter = str.begin();
188 clear_error_message();
189 bool is_parsed = boost::spirit::qi::phrase_parse(
190 iter, str.end(), *this, boost::spirit::ascii::space, parsed_expression);
191 if (!is_parsed || iter != str.end()) {
192 const auto& error_message = this->error_message();
193 if (!error_message.empty()) {
194 throw InvalidExpressionException(error_message);
195 }
196 if (iter != str.end()) {
197 throw InvalidExpressionException(
198 fmt::format("Unexpected token at position {}.",
199 // Output in 1-based index.
200 std::distance(str.begin(), iter) + 1));
201 }
202 throw InvalidExpressionException("Failed to parse expression.");
203 }
204 return parsed_expression;
205}
206
207} // namespace func_sketch::parser
Class of grammar for parsing expressions.
double Real
Type of real numbers in this project.
std::variant< Integer, Real, Complex > Number
Type of numbers.
std::complex< Real > Complex
Type of complex numbers in this project.
Definition of exception classes.
Definition of ExpressionGrammar class.
Definition of ParsedExpression structure.
boost::variant< ParsedLiteral, ParsedIdentifier, boost::recursive_wrapper< ParsedFunctionCallExpression >, boost::recursive_wrapper< ParsedUnaryExpression >, boost::recursive_wrapper< ParsedBinaryExpression > > ParsedExpression
Variant of parsed expressions.
Struct of parsed binary expressions.
Struct of parsed function call expressions.
Struct of parsed identifiers.
Struct of parsed unary expressions.