如何为用户提供给定 boost::spirit 语法的自动完成建议?

2024-05-05

我正在使用 Boost::Spirit 在我的 C++ GUI 应用程序中为非技术用户构建简单的“数据过滤器”语言。语言与纯英语非常相似,并且可以解析为 AST。我被要求使该过程尽可能对用户友好,因此我希望提供类似 CLang 的错误消息(“无法识别的‘tokken’,您的意思是‘token’吗?”)和自动完成。

现在的问题是如何使用 Boost::Spirit 语法为两个目标生成可能的标记列表(我将使用简单的字符串距离算法来满足第一个要求)?

到目前为止我的想法:

  • 将源流中的位置和长度添加到从解析器获得的标记定义中。
  • 在语法中添加特殊的无效标记,它将匹配任何内容...好吧...无效:-)
  • 如果用户按 ctrl + space,构建 AST(使用无效标记,树将始终可构建),然后在当前光标位置内查找标记
  • 对所有可能的标记使用简单的匹配器(毕竟我有标记列表)并准备建议列表

此解决方案的问题是该建议还会建议对于给定位置无效的令牌。如果我添加(并且我会)可定义的标识符,我手头就有更大的问题......

还有一个限制:我希望只在一个地方定义这种语言的语法;如果语法发生变化,我希望自动完成程序在重新编译后意识到这一变化


出于好奇,我决定尝试一下这个概念。

这是我的尝试。

Plan

让我们用函数调用来解析算术表达式。

现在,我们想要解析具有可能未知标识符的(部分)表达式。

如果表达式不完整,我们希望“暗示”最小标记来完成它(并可能继续解析)。

如果标识符未知,我们希望对域中的已知标识符(变量或函数)进行模糊匹配,并按概率递减的顺序对它们进行排名。


基本定义

首先,我们决定希望输入位于内存中,这样我们就可以通过使用来引用位置/子字符串string_views:

#include <boost/utility/string_view.hpp>

using Source = boost::string_view;
using Location = Source::const_iterator;

完成提示

除了 AST 之外,我们还希望解析器生成完成提示(隐式完成标记和建议)

namespace Completion {

    using Candidates = std::vector<std::string>;

    class Hints {
        struct ByLocation {
            template <typename T, typename U>
            bool operator()(T const& a, U const& b) const { return loc(a) < loc(b); }
          private:
            static Location loc(Source const& s) { return s.begin(); }
            static Location loc(Location const& l) { return l; }
        };

      public:
        std::map<Location, std::string, ByLocation> incomplete;
        std::map<Source, Candidates, ByLocation> suggestions;

        /*explicit*/ operator bool() const { return incomplete.size() || suggestions.size(); }
    };

此外,让我们编写一个快速而肮脏的模糊标识符匹配评分函数。

我选择了一个简单的同步比较

  • 逐步对相应的字符运行进行评分,并且
  • 与跳过输入中的字符相比,更倾向于跳过候选字符(这意味着adj_diff是一个很好的模糊匹配adjacent_difference即使候选字符已被跳过,但是adj_qqq_diff更糟糕的是因为qqq输入无法匹配)
  • 该算法是递归完成的并且没有分配
  • 如果匹配的话第一个字符会得到提升(rate=1第一次调用时)
    static int fuzzy_match(Source input, boost::string_view candidate, int rate = 1) { // start with first-letter boost
        int score = 0;

        while (!(input.empty() || candidate.empty())) {
            if (input.front() != candidate.front()) {
                return score + std::max(
                    fuzzy_match(input.substr(1), candidate, std::max(rate-2,0)), //penalty for ignoring an input char
                    fuzzy_match(input, candidate.substr(1), std::max(rate-1,0)));
            }

            input.remove_prefix(1);
            candidate.remove_prefix(1);
            score += ++rate;
        }
        return score;
    }

} // namespace Completion

我们将看看它在语法中是如何使用的。

AST

一个普通的 AST,可以执行二进制表达式、字符串/数字文字、变量和(嵌套)函数调用:

#include <boost/variant.hpp>

namespace Ast {
    using NumLiteral = double;
    using StringLiteral = std::string; // de-escaped, not source view

    struct Identifier : std::string {
        using std::string::string;
        using std::string::operator=;
    };

    struct BinaryExpression;
    struct CallExpression;

    using Expression = boost::variant<
            NumLiteral,
            StringLiteral,
            Identifier,
            boost::recursive_wrapper<BinaryExpression>,
            boost::recursive_wrapper<CallExpression>
        >;

    struct BinaryExpression {
        Expression lhs;
        char op;
        Expression rhs;
    };

    using ArgList = std::vector<Expression>;

    struct CallExpression {
        Identifier function;
        ArgList args;
    };
}

Grammar


Basics

语法一开始也非常“基本”:

namespace Parsing {
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;

    template <typename It>
    struct Expression : qi::grammar<It, Ast::Expression()> {
        Expression(Completion::Hints& hints) : Expression::base_type(start), _hints(hints) {
            using namespace qi;

            start      = skip(space) [expression];

            expression = term   [_val = _1] >> *(char_("-+") >> expression) [_val = make_binary(_val, _1, _2)];
            term       = factor [_val = _1] >> *(char_("*/") >> term)       [_val = make_binary(_val, _1, _2)];
            factor     = simple [_val = _1] >> *(char_("^")  >> factor)     [_val = make_binary(_val, _1, _2)];

            simple     = call | variable | compound | number | string;

到目前为止一切顺利:构造函数存储了对Completion::Hints&被记录。所有这些规则均已声明为qi::rule<It, Expression(), qi::space_type>.

隐含代币

现在它变得更有趣了,这里的一些规则是词素

            number     = double_;
            identifier = raw[(alpha|'_') >> *(alnum|'_')];

有些产品可以容忍缺少结束标记:

            // imply the closing quotes
            string   %= '"' >> *('\\' >> char_ | ~char_('"')) >> implied('"');
            compound %= '(' >> expression >> implied(')');

The impliedmagic 使得如果预期的结束标记丢失,它将被记录为提示,并且解析继续:

            auto implied = [=](char ch) {
                return copy(omit[lit(ch) | raw[eps][imply(_1, ch)]]);
            };

当然,这引出了一个问题:imply(_1, ch)确实如此,我们稍后会看到。

现在,观察我们所做的raw[eps]获取(空)源iterator_range构建一个Location来自语义动作。

标识符查找

我们将“符号表”存储在Domain会员_variables and _functions:

        using Domain = qi::symbols<char>;
        Domain _variables, _functions;

然后我们声明一些可以对它们中的任何一个进行查找的规则:

        // domain identifier lookups
        qi::_r1_type _domain;
        qi::rule<It, Ast::Identifier(Domain const&)> maybe_known, known, unknown;

相应的声明将很快显示。

变量非常简单:

        variable   = maybe_known(phx::ref(_variables));

打电话比较棘手。如果一个名字是未知的,我们不想假设它暗示着一个function除非后面跟着一个'('特点。然而,如果标识符是已知的函数名称,我们甚至想暗示((这使得用户体验看起来自动完成,当用户键入时sqrt,它建议下一个字符是(神奇地)。

        // The heuristics:
        // - an unknown identifier followed by (
        // - an unclosed argument list implies )
        call %= ( known(phx::ref(_functions)) // known -> imply the parens
                     | &(identifier >> '(') >> unknown(phx::ref(_functions))
                     ) >> implied('(') >> -(expression % ',') >> implied(')');

这一切都建立在known, unknown and maybe_known:

            ///////////////////////////////
            // identifier loopkup, suggesting
            {
                maybe_known = known(_domain) | unknown(_domain);

                // distinct to avoid partially-matching identifiers
                using boost::spirit::repository::qi::distinct;
                auto kw     = distinct(copy(alnum | '_'));

                known       = raw[kw[lazy(_domain)]];
                unknown     = raw[identifier[_val=_1]] [suggest_for(_1, _domain)];
            }

调试、预定义变量/函数

剩下的就是一些繁文缛节:

            BOOST_SPIRIT_DEBUG_NODES(
                (start)
                (expression)(term)(factor)(simple)(compound)
                (call)(variable)
                (identifier)(number)(string)
                //(maybe_known)(known)(unknown) // qi::symbols<> non-streamable
            )

            _variables += "foo", "bar", "qux";
            _functions += "print", "sin", "tan", "sqrt", "frobnicate";
        }

      private:
        Completion::Hints& _hints;

        using Domain = qi::symbols<char>;
        Domain _variables, _functions;

        qi::rule<It, Ast::Expression()> start;
        qi::rule<It, Ast::Expression(), qi::space_type> expression, term, factor, simple;
        // completables
        qi::rule<It, Ast::Expression(), qi::space_type> compound;
        qi::rule<It, Ast::CallExpression(), qi::space_type> call;

        // implicit lexemes
        qi::rule<It, Ast::Identifier()> variable, identifier;
        qi::rule<It, Ast::NumLiteral()> number;
        qi::rule<It, Ast::StringLiteral()> string;

        // domain identifier lookups
        qi::_r1_type _domain;
        qi::rule<It, Ast::Identifier(Domain const&)> maybe_known, known, unknown;

凤凰助手

让我们从构建二进制 AST 节点的常用助手开始:

        ///////////////////////////////
        // binary expression factory
        struct make_binary_f {
            Ast::BinaryExpression operator()(Ast::Expression const& lhs, char op, Ast::Expression const& rhs) const {
                return {lhs, op, rhs};
            }
        };
        boost::phoenix::function<make_binary_f> make_binary;

类似地,我们可以有一个 Phoenix 函数对象来注册隐含字符:

        ///////////////////////////////
        // auto-completing incomplete expressions
        struct imply_f {
            Completion::Hints& _hints;
            void operator()(boost::iterator_range<It> where, char implied_char) const {
                auto inserted = 
                    _hints.incomplete.emplace(&*where.begin(), std::string(1, implied_char));
                // add the implied char to existing completion
                if (!inserted.second)
                    inserted.first->second += implied_char;
            }
        };
        boost::phoenix::function<imply_f> imply { imply_f { _hints } };

请注意,它按位置排序(这使用户体验更容易)并检测先前隐含令牌何时位于同一位置,在这种情况下,我们只需附加到它即可。

到目前为止,为未知标识符生成相关建议也是一个凤凰演员就不足为奇了:

        ///////////////////////////////
        // suggest_for
        struct suggester {
            Completion::Hints& _hints;

            void operator()(boost::iterator_range<It> where, Domain const& symbols) const {
                using namespace Completion;
                Source id(&*where.begin(), where.size());
                Candidates c;

                symbols.for_each([&](std::string const& k, ...) { c.push_back(k); });

                auto score = [id](Source v) { return fuzzy_match(id, v); };
                auto byscore = [=](Source a, Source b) { return score(a) > score(b); };

                sort(c.begin(), c.end(), byscore);
                c.erase( remove_if(c.begin(), c.end(), [=](Source s) { return score(s) < 3; }), c.end());

                _hints.suggestions.emplace(id, c);
            }
        };
        boost::phoenix::function<suggester> suggest_for {suggester{_hints}};

就这样。如果它看起来更复杂,那是因为它做了更多的事情:它对所有候选者进行模糊评分,按分数降序对它们进行排序,并删除分数

    };
}

BONUS

让我们稍微改变一下并允许','隐含在参数列表中:

        call %= ( known(phx::ref(_functions)) // known -> imply the parens
                | &(identifier >> '(') >> unknown(phx::ref(_functions))
                ) 
            >> implied('(') 
            >> -(expression % ',')
            >> implied(')')
            ;

我们来替换一下',' there:

            >> -(expression % (',' | !(')'|eoi) >> implied(',')))

NOTE: 检测似乎更有意义&expression断言后面有一个表达式,而不是断言尚未到达输入/参数列表的末尾。

但是,这样做会很糟糕,因为任何包含的表达式都可能会触发更多隐含标记作为副作用,从而导致重复的隐含标记。

这种(副作用语义操作)是我通常避免语义操作或使用它们仅在规则的(公开的)属性中存储效果的主要原因之一。看Boost Spirit:“语义行为是邪恶的”? https://stackoverflow.com/questions/8259440/boost-spirit-semantic-actions-are-evil

试驾员

如果没有一些好的测试用例,这篇文章就毫无意义。所以这里是:

int main() {
    for (Source const input : {
            "",       // invalid
            "(3",     // incomplete, imply ')'
            "3*(6+sqrt(9))^7 - 1e8", // completely valid
            "(3*(((6+sqrt(9))^7 - 1e8", // incomplete, imply ")))"
            "print(\"hello \\\"world!", // completes the string literal and the parameter list
            "foo",    // okay, known variable
            "baz",    // (suggest bar)
            "baz(",   // incomplete, imply ')', unknown function
            "taz(",   // incomplete, imply ')', unknown function
            "san(",   // 2 suggestions (sin/tan)
            "print(1, 2, \"three\", complicated(san(78",
            "(print sqrt sin 9)    -0) \"bye",
        })
    {
        std::cout << "-------------- '" << input << "'\n";
        Location f = input.begin(), l = input.end();

        Ast::Expression expr;
        Completion::Hints hints;
        bool ok = parse(f, l, Parsing::Expression<Location>{hints}, expr);

        if (hints) {
            std::cout << "Input: '" << input << "'\n";
        }
        for (auto& c : hints.incomplete) {
            std::cout << "Missing " << std::setw(c.first - input.begin()) << "" << "^ implied: '" << c.second << "'\n";
        }
        for (auto& id : hints.suggestions) {
            std::cout << "Unknown " << std::setw(id.first.begin() - input.begin()) << "" << std::string(id.first.size(), '^');
            if (id.second.empty()) 
                std::cout << " (no suggestions)\n";
            else {
                std::cout << " (did you mean ";
                once_t first;
                for (auto& s : id.second)
                    std::cout << (first?"":" or ") << "'" << s << "'";
                std::cout << "?)\n";
            }
        }

        if (ok) {
            std::cout << "AST:    " << expr << "\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f!=l)
            std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
    }
}

请注意,除了第一个输入 ("")一切都被启发式地解析为some一种表达方式!最后一个旨在展示所有参数列表是如何隐含的,因为print, sqrt and sin是已知函数。然后一些','是隐含的,最后未闭合的字符串文字和剩余的括号被闭合。这是(非调试)输出:

-------------- ''
Parse failed
-------------- '(3'
Input: '(3'
Missing   ^ implied: ')'
AST:    3
-------------- '3*(6+sqrt(9))^7 - 1e8'
AST:    ((3 * ((6 + (sqrt (9))) ^ 7)) - 1e+08)
-------------- '(3*(((6+sqrt(9))^7 - 1e8'
Input: '(3*(((6+sqrt(9))^7 - 1e8'
Missing                         ^ implied: ')))'
AST:    (3 * (((6 + (sqrt (9))) ^ 7) - 1e+08))
-------------- 'print("hello \"world!'
Input: 'print("hello \"world!'
Missing                      ^ implied: '")'
AST:    (print (hello "world!))
-------------- 'foo'
AST:    foo
-------------- 'baz'
Input: 'baz'
Unknown ^^^ (did you mean 'bar'?)
AST:    baz
-------------- 'baz('
Input: 'baz('
Missing     ^ implied: ')'
Unknown ^^^ (no suggestions)
AST:    (baz ())
-------------- 'taz('
Input: 'taz('
Missing     ^ implied: ')'
Unknown ^^^ (did you mean 'tan'?)
AST:    (taz ())
-------------- 'san('
Input: 'san('
Missing     ^ implied: ')'
Unknown ^^^ (did you mean 'sin' or 'tan'?)
AST:    (san ())
-------------- 'print(1, 2, "three", complicated(san(78'
Input: 'print(1, 2, "three", complicated(san(78'
Missing                                        ^ implied: ')))'
Unknown                      ^^^^^^^^^^^ (did you mean 'frobnicate' or 'print'?)
Unknown                                  ^^^ (did you mean 'sin' or 'tan'?)
AST:    (print (1, 2, three, (complicated ((san (78))))))
-------------- '(print sqrt sin 9)    -0) "bye'
Input: '(print sqrt sin 9)    -0) "bye'
Missing        ^ implied: '('
Missing             ^ implied: '('
Missing                 ^ implied: '('
Missing                           ^ implied: ','
Missing                               ^ implied: '"))'
AST:    (print ((sqrt (((sin (9)) - 0))), bye))

完整列表/现场演示

Live On Coliru http://coliru.stacked-crooked.com/a/c1b0c5c573e9ec78

//#define BOOST_SPIRIT_DEBUG
#include <boost/utility/string_view.hpp>

using Source = boost::string_view;
using Location = Source::const_iterator;

#include <map>
#include <vector>

namespace Completion {

    static int fuzzy_match(Source input, boost::string_view candidate, int rate = 1) { // start with first-letter boost
        int score = 0;

        while (!(input.empty() || candidate.empty())) {
            if (input.front() != candidate.front()) {
                return score + std::max(
                    fuzzy_match(input.substr(1), candidate, std::max(rate-2,0)), //penalty for ignoring an input char
                    fuzzy_match(input, candidate.substr(1), std::max(rate-1,0)));
            }

            input.remove_prefix(1);
            candidate.remove_prefix(1);
            score += ++rate;
        }
        return score;
    }

    using Candidates = std::vector<std::string>;

    class Hints {
        struct ByLocation {
            template <typename T, typename U>
            bool operator()(T const& a, U const& b) const { return loc(a) < loc(b); }
          private:
            static Location loc(Source const& s) { return s.begin(); }
            static Location loc(Location const& l) { return l; }
        };

      public:
        std::map<Location, std::string, ByLocation> incomplete;
        std::map<Source, Candidates, ByLocation> suggestions;

        /*explicit*/ operator bool() const { return incomplete.size() || suggestions.size(); }
    };
}

#include <boost/variant.hpp>

namespace Ast {
    using NumLiteral = double;
    using StringLiteral = std::string; // de-escaped, not source view

    struct Identifier : std::string {
        using std::string::string;
        using std::string::operator=;
    };

    struct BinaryExpression;
    struct CallExpression;

    using Expression = boost::variant<
            NumLiteral,
            StringLiteral,
            Identifier,
            boost::recursive_wrapper<BinaryExpression>,
            boost::recursive_wrapper<CallExpression>
        >;

    struct BinaryExpression {
        Expression lhs;
        char op;
        Expression rhs;
    };

    using ArgList = std::vector<Expression>;

    struct CallExpression {
        Identifier function;
        ArgList args;
    };
}

#include <boost/fusion/adapted/struct.hpp>
BOOST_FUSION_ADAPT_STRUCT(Ast::BinaryExpression, lhs, op, rhs)
BOOST_FUSION_ADAPT_STRUCT(Ast::CallExpression, function, args)

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/repository/include/qi_distinct.hpp>

// for debug printing:
namespace {
    struct once_t { // an auto-reset flag
        operator bool() { bool v = flag; flag = false; return v; }
        bool flag = true;
    };
}

// for debug printing:
namespace Ast {

    static inline std::ostream& operator<<(std::ostream& os, std::vector<Expression> const& args) {
        os << "(";
        once_t first;
        for (auto& a : args) os << (first?"":", ") << a;
        return os << ")";
    }

    static inline std::ostream& operator<<(std::ostream& os, BinaryExpression const& e) {
        return os << boost::fusion::as_vector(e);
    }
    static inline std::ostream& operator<<(std::ostream& os, CallExpression const& e) {
        return os << boost::fusion::as_vector(e);
    }
}

namespace Parsing {
    namespace qi  = boost::spirit::qi;
    namespace phx = boost::phoenix;

    template <typename It>
    struct Expression : qi::grammar<It, Ast::Expression()> {
        Expression(Completion::Hints& hints) : Expression::base_type(start), _hints(hints) {
            using namespace qi;

            start      = skip(space) [expression];

            expression = term   [_val = _1] >> *(char_("-+") >> expression) [_val = make_binary(_val, _1, _2)];
            term       = factor [_val = _1] >> *(char_("*/") >> term)       [_val = make_binary(_val, _1, _2)];
            factor     = simple [_val = _1] >> *(char_("^")  >> factor)     [_val = make_binary(_val, _1, _2)];

            simple     = call | variable | compound | number | string;

            auto implied = [=](char ch) {
                return copy(omit[lit(ch) | raw[eps][imply(_1, ch)]]);
            };

            variable   = maybe_known(phx::ref(_variables));

            compound  %= '(' >> expression >> implied(')');

            // The heuristics:
            // - an unknown identifier followed by (
            // - an unclosed argument list implies )
            call %= ( known(phx::ref(_functions)) // known -> imply the parens
                    | &(identifier >> '(') >> unknown(phx::ref(_functions))
                    ) 
                >> implied('(') 
                >> -(expression % (',' | !(')'|eoi) >> implied(',')))
                >> implied(')')
                ;

            // lexemes, primitive rules
            identifier  = raw[(alpha|'_') >> *(alnum|'_')];

            // imply the closing quotes
            string     %= '"' >> *('\\' >> char_ | ~char_('"')) >> implied('"'); // TODO more escapes

            number      = double_; // TODO integral arguments

            ///////////////////////////////
            // identifier loopkup, suggesting
            {
                maybe_known = known(_domain) | unknown(_domain);

                // distinct to avoid partially-matching identifiers
                using boost::spirit::repository::qi::distinct;
                auto kw     = distinct(copy(alnum | '_'));

                known       = raw[kw[lazy(_domain)]];
                unknown     = raw[identifier[_val=_1]] [suggest_for(_1, _domain)];
            }

            BOOST_SPIRIT_DEBUG_NODES(
                (start)
                (expression)(term)(factor)(simple)(compound)
                (call)(variable)
                (identifier)(number)(string)
                //(maybe_known)(known)(unknown) // qi::symbols<> non-streamable
            )

            _variables += "foo", "bar", "qux";
            _functions += "print", "sin", "tan", "sqrt", "frobnicate";
        }

      private:
        Completion::Hints& _hints;

        using Domain = qi::symbols<char>;
        Domain _variables, _functions;

        qi::rule<It, Ast::Expression()> start;
        qi::rule<It, Ast::Expression(), qi::space_type> expression, term, factor, simple;
        // completables
        qi::rule<It, Ast::Expression(), qi::space_type> compound;
        qi::rule<It, Ast::CallExpression(), qi::space_type> call;

        // implicit lexemes
        qi::rule<It, Ast::Identifier()> variable, identifier;
        qi::rule<It, Ast::NumLiteral()> number;
        qi::rule<It, Ast::StringLiteral()> string;

        // domain identifier lookups
        qi::_r1_type _domain;
        qi::rule<It, Ast::Identifier(Domain const&)> maybe_known, known, unknown;

        ///////////////////////////////
        // binary expression factory
        struct make_binary_f {
            Ast::BinaryExpression operator()(Ast::Expression const& lhs, char op, Ast::Expression const& rhs) const {
                return {lhs, op, rhs};
            }
        };
        boost::phoenix::function<make_binary_f> make_binary;

        ///////////////////////////////
        // auto-completing incomplete expressions
        struct imply_f {
            Completion::Hints& _hints;
            void operator()(boost::iterator_range<It> where, char implied_char) const {
                auto inserted = 
                    _hints.incomplete.emplace(&*where.begin(), std::string(1, implied_char));
                // add the implied char to existing completion
                if (!inserted.second)
                    inserted.first->second += implied_char;
            }
        };
        boost::phoenix::function<imply_f> imply { imply_f { _hints } };

        ///////////////////////////////
        // suggest_for
        struct suggester {
            Completion::Hints& _hints;

            void operator()(boost::iterator_range<It> where, Domain const& symbols) const {
                using namespace Completion;
                Source id(&*where.begin(), where.size());
                Candidates c;

                symbols.for_each([&](std::string const& k, ...) { c.push_back(k); });

                auto score = [id](Source v) { return fuzzy_match(id, v); };
                auto byscore = [=](Source a, Source b) { return score(a) > score(b); };

                sort(c.begin(), c.end(), byscore);
                c.erase( remove_if(c.begin(), c.end(), [=](Source s) { return score(s) < 3; }), c.end());

                _hints.suggestions.emplace(id, c);
            }
        };
        boost::phoenix::function<suggester> suggest_for {suggester{_hints}};

    };
}

#include <iostream>
#include <iomanip>

int main() {
    for (Source const input : {
            "",       // invalid
            "(3",     // incomplete, imply ')'
            "3*(6+sqrt(9))^7 - 1e8", // completely valid
            "(3*(((6+sqrt(9))^7 - 1e8", // incomplete, imply ")))"
            "print(\"hello \\\"world!", // completes the string literal and the parameter list
            "foo",    // okay, known variable
            "baz",    // (suggest bar)
            "baz(",   // incomplete, imply ')', unknown function
            "taz(",   // incomplete, imply ')', unknown function
            "san(",   // 2 suggestions (sin/tan)
            "print(1, 2, \"three\", complicated(san(78",
            "(print sqrt sin 9)    -0) \"bye",
        })
    {
        std::cout << "-------------- '" << input << "'\n";
        Location f = input.begin(), l = input.end();

        Ast::Expression expr;
        Completion::Hints hints;
        bool ok = parse(f, l, Parsing::Expression<Location>{hints}, expr);

        if (hints) {
            std::cout << "Input: '" << input << "'\n";
        }
        for (auto& c : hints.incomplete) {
            std::cout << "Missing " << std::setw(c.first - input.begin()) << "" << "^ implied: '" << c.second << "'\n";
        }
        for (auto& id : hints.suggestions) {
            std::cout << "Unknown " << std::setw(id.first.begin() - input.begin()) << "" << std::string(id.first.size(), '^');
            if (id.second.empty()) 
                std::cout << " (no suggestions)\n";
            else {
                std::cout << " (did you mean ";
                once_t first;
                for (auto& s : id.second)
                    std::cout << (first?"":" or ") << "'" << s << "'";
                std::cout << "?)\n";
            }
        }

        if (ok) {
            std::cout << "AST:    " << expr << "\n";
        } else {
            std::cout << "Parse failed\n";
        }

        if (f!=l)
            std::cout << "Remaining input: '" << std::string(f,l) << "'\n";
    }
}

¹ 提升精神船长问题 https://stackoverflow.com/questions/17072987/boost-spirit-skipper-issues/17073965#17073965

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何为用户提供给定 boost::spirit 语法的自动完成建议? 的相关文章

  • .NET 单点登录

    我一直在尝试使用 C 为 NET Web 应用程序实现 WEB SSO 服务提供程序插件 我将使用 shibboleth 身份提供商 我已经使用 OpenSAML 库为 java 应用程序实现了相同的功能 我想知道在 NET 应用程序中使用
  • ASP.NET Web 应用程序中的身份验证遇到问题

    我正在尝试对从登录页面登录我的 Web 应用程序的用户进行身份验证 我正在使用本教程 http support microsoft com kb 301240作为指南 它几乎准确地解释了我希望做什么 但是当我输入用户名和密码时 验证不起作用
  • NDK 应用 onDestroy 清理 - 如何 DetachCurrentThread

    因此 如果我们连接 我们必须在完成后分离线程 对吗 JNIEnv get jni env JNIEnv res JAVA VM gt GetEnv void res JNI VERSION 1 6 Using cached JavaVM J
  • 在没有 epsilon 的情况下可以将浮点数与 0.0 进行比较吗?

    我知道 要比较两个浮点值 需要使用一些 epsilon 精度 因为它们并不精确 但是 我想知道是否存在边缘情况 我不需要那个 epsilon 特别是 我想知道这样做是否总是安全的 double foo double x if x lt 0
  • 如何在C中同时运行两个子进程?

    所以我开始学习并发编程 但由于某种原因我什至无法掌握基础知识 我有一个名为 fork c 的文件 其中包含一个 main 方法 在此方法中 我将 main 分叉两次 分别进入子进程 1 和 2 在孩子 1 中 我打印了字符 A 50 次 在
  • ContentDialog 未与 UWP 中心对齐

    据我所知 ContentDialog的默认行为应该是使其在 PC 上居中并在移动设备上与顶部对齐 但就我而言 即使在 PC 上我也将其与顶部对齐 但我不明白发生了什么 我正在使用代码隐藏来创建它 这是我正在使用的代码片段 Creates t
  • Monotouch全局异常处理

    我在野外发现了一只令人讨厌的虫子 但我无法确定它的具体情况 有没有办法拥有全局 Try Catch 块 或者有办法处理 Monotouch 中未处理的任何异常 我可以包起来吗UIApplication Main args 在 try cat
  • 对作为函数参数传递的指针使用删除

    删除作为函数参数传递的指针是否可以 并且合法 如下所示 include
  • .NET 5 EF Core SaveChangesAsync 因错误而挂起

    尽管这个问题有很多结果 但没有一个真正给我明确的答案 每次我尝试通过 AddAsync 和 SaveChangesAsync 方法插入错误数据 例如重复的主键 时 我都会看到以下日志 执行 DbCommand 失败 15 毫秒 我还在 SQ
  • 应用程序处于中断模式。您的应用程序已进入中断状态,

    我发现自己遇到了同样的问题here https stackoverflow com questions 36204009 disable break mode page in vs2015 我在 dll 中使用 Windows 窗体 这是针
  • 我在使用 ado.net 时收到错误 Argument 2 may not be pass with ref keywords

    int t 0 cmd Parameters AddWithValue Res ref t 我在第二行收到错误 参数 2 不能与 ref 关键字一起传递 您只能通过引用传递参数ref if the 范围 is a ref参数也是如此 Add
  • 如何从外语线程调用Python函数(C++)

    我正在开发一个程序 使用 DirectShow 来抓取音频数据 媒体文件 DirectShow 使用线程将音频数据传递给回调 我的程序中的函数 然后我让该回调函数调用另一个函数 Python 中的函数 我使用 Boost Python 来包
  • 更改 Xamarin.Forms 应用中顶部栏和底部栏(ControlsBar、StatusBar)的颜色

    无论如何 即使后面需要特定于平台的代码 也可以更改顶部栏 蓝色的 和底部栏 黑色的 的颜色吗 我希望添加对浅色和深色模式的支持 因此我希望能够在运行时更改它 有可能的 Android Using Window SetStatusBarCol
  • 使用数据绑定,如何将包含表情符号的文本绑定到标签并使其正确显示?

    我正在编写一个应用程序来连接 WordPress BuddyPress API 该应用程序将允许用户通过 API 相互发送消息 当这些消息包含表情符号时 我很难正确显示它们 以下是 API 返回的消息文本的简短示例 Hi x1f642 ho
  • valgrind 在 Raspberry Pi 上返回未处理的指令

    我最近一直在尝试在运行 Debian GNU Linux7 0 喘息 的树莓派 型号 b 上使用 valgrind 来调试分段错误 每次我在编译的 C 程序上运行 valgrind 时 都会得到类似以下内容的信息 disInstr arm
  • 如何检测应用程序正在运行的 .NET 版本?

    我尝试使用Environment Version ToString 确定目标计算机上正在使用什么 NET 框架 但安装了 4 0 版本时 它说我正在使用 NET 2 0 如何检测目标计算机上正在运行的 NET Framework 版本 En
  • 使用多态对象数组进行 JSON 反序列化

    我在涉及多态对象数组的 JSON 反序列化方面遇到问题 我已经尝试过记录的序列化解决方案here https stackoverflow com questions 5186973 json serialization of array w
  • 当我的进程被终止时到底会发生什么?

    我有一个包含本机代码和托管代码的混合进程 在 Windows Server 2003 上运行 当我从进程资源管理器中终止进程时 它会进入 100 cpu 的状态 并在消失之前保持这种状态一段时间 有时甚至 10 分钟 在此期间我无法 杀死
  • 具有四个 && 的 LINQ Where 子句

    我正在尝试在Where 子句中创建一个带有4 个参数的LINQ 查询 这是一个 Windows 8 应用程序项目 我正在使用 SQLite 数据库 SQLite 实现 https github com praeclarum sqlite n
  • 如何在 C 中创建最低有效位设置为 1 的掩码

    这个功能如何运作 最低有效 n 位设置为 1 的掩码 Example n 6 gt 0x2F n 17 gt 0x1FFFF 我根本不明白这些 尤其是 n 6 gt 0x2F 另外 什么是面膜 通常的方法是采取1 并将其左移n位 这会给你类

随机推荐

  • 在 JavaScript onClick 事件处理程序中转义双引号

    下面的简单代码块可以在静态 HTML 页面中提供 但会导致 JavaScript 错误 您应该如何转义中嵌入的双引号onClick处理程序 即 xyz 请注意 HTML 是通过从数据库中提取数据动态生成的 其中的数据是其他可能带有单引号或双
  • 从列表中的每个项目的支持 bean 方法中设置 itemDisabled

    我有一个单选按钮列表 并且想根据支持 bean 方法的结果禁用某些项目
  • 如何检查 firebase 中是否存在孩子? [复制]

    这个问题在这里已经有答案了 我有一个 firebase 它有一个名为 users 的节点 用户给出一个用户名 我想检查该用户名是否已作为用户节点的子节点存在 这是我目前正在尝试的代码 平台是安卓 String myUsername user
  • 如何更改 Kotlin 上生成的 TODO() 函数体?

    我需要在我的代码上留下一些稍后要做的事情TODO 自动生成的会产生一个很长的评论 如下所示 TODO not implemented To change body of created functions use File Settings
  • Requests-html 导致 OSError: [Errno 8] 调用 html.render() 时执行格式错误

    我正在使用 requests html 并尝试渲染功能 但收效甚微 当我使用 python3 8 运行这个脚本时 usr bin python3 from requests html import HTML file scrape temp
  • 您忽略了哪些 PEP 8 准则,哪些是您坚持的? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 多年来 我编写的 Python 越多 我就越发现自己同意大多数准则 尽管我出于自己的原因始终有意地违反了一些准则 我很想知道 PEP 8 也可能
  • 在 Android 中的计时器内运行异步任务

    我正在开发一个基本的聊天类型应用程序 目前我正在运行代码 如下所示 class GetMsgs extends AsyncTask
  • 长按 UIButton

    我想知道如果有人按住 UIButton 按键的时间过长 我是否可以捕获 UIButton 的事件 通过通知或其他机制 比按一次按钮的时间更长 假设有人按住按钮几秒钟 谢谢 你可以加UILongPressGestureRecognizer h
  • Android 是否可以同时使用前后摄像头[重复]

    这个问题在这里已经有答案了 我想同时使用设备的前置和后置摄像头 在我的应用程序中 屏幕的前半部分将显示后置摄像头的预览 屏幕的下半部分将显示前置摄像头的预览 我尝试过设置两个不同的相机预览 但是当我打开应用程序时 屏幕的前半部分 显示后置相
  • 将文件附加到 WCF REST 服务响应

    我有一个看起来像这样的资源 users id summary format format When format是 xml 或 json 我用一个由 WCF 自动编码的用户摘要对象进行响应 到目前为止还不错 但当format等于 pdf 我
  • 什么是样板代码、热点代码和热点?

    我知道这些术语是在性能实现 优化的背景下使用的 最近一直在研究这个问题 并尝试过搜索 但没有得到任何例子 清楚地阐述 描述这些概念以及在现实世界开发场景中实现这些问题 概念 有人可以彻底解释这些术语 示例场景以及可能使用这些概念和术语的地方
  • 如何在 jQuery 中检查 null 对象

    我正在使用 jQuery 我想检查页面中是否存在某个元素 我写了以下代码 但它不起作用 if btext i null alert btext i text btext i text Branch i 如何检查元素是否存在 检查jQuery
  • Python多线程模型

    我已经研究 python 中的多线程有一段时间了 但是我对一些问题感到困惑 首先 python线程库创建的线程是用户级线程还是内核级线程 书上说用户级线程必须映射到内核线程并且 操作系统仅创建和维护内核级线程 python中将使用哪种线程模
  • 如何重命名 Rails 4 应用程序?

    rails plugin install git github com get Rename git将允许我们仅重命名 Rails 3 应用程序 是否有任何 gem 可用于重命名 Rails 4 应用程序 如果没有 请建议我更好的重命名方法
  • ASP .Net 报表查看器控件中的本地报表与服务器报表

    在我们当前工作的 ASP Net 站点之一中 我们有大量 SSRS 报告 我们对该站点进行了表单身份验证 并且已在报表服务器中创建并部署了报表 当我们设置报表查看器控件来访问服务器报表时 我们遇到了很多身份验证问题 我只是想知道使用本地报告
  • .NET WPF 窗口淡入和淡出动画

    下面是窗口淡入和淡出动画的代码片段 Create the fade in storyboard fadeInStoryboard new Storyboard fadeInStoryboard Completed new EventHand
  • 我应该在哪里划清词法分析器和解析器之间的界限?

    我正在为 IMAP 协议编写一个词法分析器 用于教育目的 但我很困惑应该在词法分析器和解析器之间划清界限 以 IMAP 服务器响应为例 FLAGS Answered Deleted 该响应的正式语法定义如下 mailbox data FLA
  • 是否可以将 pyobjc 与特权 XPC 帮助工具和 XPCInterface API 一起使用?

    我相信这个问题的答案是 否 但我将其发布给社区 以防有人比我更成功 我有一个特权帮助程序工具 客户端 Cocoa 应用程序将其与 NSXPCConnection 和 NSXPCInterface 一起使用 该接口本身包括一个通过完成处理程序
  • d3 地理投影从正交到 X 的过渡

    我正在开发一个教育地图项目 其中显示不同的地图投影 我想在选择不同投影之间实现变形过渡 我找到了一个很好的例子来实现它 并且我没有遇到太多的麻烦来重新创建它 不幸的是 我还需要裁剪投影的功能 这与目标状态完美配合 但在改变投影时则不然 当选
  • 如何为用户提供给定 boost::spirit 语法的自动完成建议?

    我正在使用 Boost Spirit 在我的 C GUI 应用程序中为非技术用户构建简单的 数据过滤器 语言 语言与纯英语非常相似 并且可以解析为 AST 我被要求使该过程尽可能对用户友好 因此我希望提供类似 CLang 的错误消息 无法识