- 论坛徽章:
- 0
|
本帖最后由 sonicling 于 2011-11-30 15:22 编辑
回复 sonicling
我想做C的语法分析……
因为感觉那些传统的ctags,cscope工具弱爆了……
弱爆了有些夸张…… 要用也能用, 但就是各种蛋疼……
不过实力不够…… 而且又很懒…… 一直没能动手……
C++语法分析更是想都不敢想……
OwnWaterloo 发表于 2011-11-30 03:11 ![]()
代码编辑器这类工具的语法分析跟编译器的还不一样,我觉得比编译器的语法分析不一样。这类编辑器要即时分析,考虑到效率问题,不大可能每次输入的时候进行全文分析,而只能局部分析,所以要对文件进行动态分割、并对文法进行划分,对每个文件部分要选择正确的那部分文法进行分析,这是复杂的地方。简化的地方是编辑器不需要考虑语法树之类的全局结构,它只用标出每个token的属性就行了。
我之前考虑C/C++的语法分析使用自顶向下的分析,但是回溯太麻烦了,要不想回溯,就得文法变形,写了个自动化变形工具,也很麻烦。所以后来改用LR1,就不用回溯了,但是C/C++即便是用LR分析也存在二义性问题,最头疼的就是当你读到一个identifier时,基本上会遇到归约冲突,比如class-name、enum-name、typedef-name、unqualified-id、declarator-id、id-expression等等。然后又祭出文法变形的办法,还是没办法解决。
根本问题是:C/C++的文法根本就不是Context-Free的,最典型的:一个identifier在‘(’前到底如何归约依赖于之前的分析的语义属性。如果它之前声明为class、struct、union,那么就归约成class-name,如果之前声明为enum就是enum-name,如果被typedef,那就是typdef-name,否则就是unqualified-id。而一个unqualified-id在‘(’前也有归约冲突,如果是声明为函数或变量的,那就是id-expression,否则就是declarator-id。
我的解决办法是用lr动作表+pda构成一个generic_parser,这个generic_parser提供一个hint的virtual方法。当lr动作表中出现归约冲突的时候,把所有候选项和当前的归约栈传给hint,由它来决定如何归约。然后我做了一个cpp_parser继承自这个generic_parser,cpp_parser包含符号表和这个hint的实现(具体是parser把hint转交给另外一个独立的static实现)。- const lr_action & cpp_node_info::hint( cpp_parser *parser, const std::vector<cpp_ast_node *> value_stack, set_action_t::const_iterator &first, set_action_t::const_iterator &last )
- {
- static const parser_string unqualified_id = _T("unqualified-id");
- static const parser_string mem_initializer_id = _T("mem-initializer-id");
- static const parser_string class_name = _T("class-name");
- static const parser_string original_namespace_name = _T("original-namespace-name");
- static const parser_string namespace_alias = _T("namespace-alias");
- static const parser_string class_or_namespace_name = _T("class-or-namespace-name");
- static const parser_string declarator_id = _T("declarator-id");
- static const parser_string simple_type_specifier = _T("simple-type-specifier");
- static const parser_string primary_expression = _T("primary-expression");
- static const parser_string ptr_operator = _T("ptr-operator");
- static const parser_string unary_operator = _T("unary-operator");
- static const parser_string template_name = _T("template-name");
- static const parser_string elaborated_type_specifier = _T("elaborated-type-specifier");
- /*
- requisitions:
- 1. all actions are reductions
- 2. all productions are the same in length.
- */
- const int len = first->second.prod_length; // reduction length
- std::map<parser_string, const lr_action *> candidiates;
- for (set_action_t::const_iterator it = first; it != last; ++it)
- {
- const lr_action &action = it->second;
- assert(action.action == lr_action::reduct && len == action.prod_length);
- candidiates.insert(std::make_pair(parser_string(action.prod_name), &action));
- }
- if (len == 1 && !value_stack.empty())
- {
- const cpp_ast_node *node = value_stack.back();
- assert(node != NULL);
- const cpp_node_info &info = node->_info;
- /*
- if info.lang_element is a symbol, it would be *-id (unqualified-id, mem-initializer-id)
- if info.lang_element is a type, it would be *-name (class-name, original-namespace-name, namespace-alias, typedef-name)
- if info.lang_element is a template, it would be template-name
- if it is not matched, result will be guessed in following order :
- unqualified-id
- mem-initializer-id
- original-namespace-name
- class-name
- enum-name
- namespace-alias
- typedef-name
- template-name(<)
- the following pairs of collision will resolved in default:
- class-or-namespace-name
- type-name
- simple-type-specifier
- declarator-id
- declarator-id
- primary-expression
- ptr-operator
- unary-operator
- */
- // name lookup
- language_element *element = NULL;
- if (node->get_child_count() == 0)
- {
- symbol_scope *current = parser->get_current_scope();
- if (current)
- {
- element = current->find_symbol_inherited(node->get_ast_name());
- }
- }
- else
- {
- element = info.lang_element;
- }
- std::map<parser_string, const lr_action *>::const_iterator result;
- // check type of name
- #define try_return(name) \
- if ((result = candidiates.find(name)) != candidiates.end()) return *(result->second)
- if (QUERYID(language_symbol, element) != 0)
- {
- try_return(unqualified_id);
- try_return(mem_initializer_id);
- try_return(primary_expression); // try primary_expression first
- try_return(declarator_id);
- }
- if (QUERYID(composite_type, element) != 0)
- {
- try_return(class_name);
- try_return(simple_type_specifier);
- }
- if (QUERYID(namespace_manager, element) != 0)
- {
- try_return(original_namespace_name);
- }
- if (QUERYID(language_template, element) != 0)
- {
- try_return(template_name);
- }
- try_return(unqualified_id);
- try_return(mem_initializer_id);
- try_return(primary_expression);
- goto default_hint;
- }
- else if (len <= 3 && !value_stack.empty())
- {
- std::map<parser_string, const lr_action *>::const_iterator result;
- /*
- elaborated-type-specifier
- class-head
- */
- try_return(elaborated_type_specifier);
- assert(false);
- }
- else
- {
- assert(false);
- }
- default_hint:
- static const parser_string seq[] = {unqualified_id, mem_initializer_id, class_name, original_namespace_name, namespace_alias, simple_type_specifier, primary_expression, declarator_id, elaborated_type_specifier};
- for (size_t i=0; i<countof(seq); i++)
- {
- std::map<parser_string, const lr_action *>::const_iterator result = candidiates.find(seq[i]);
- if (result != candidiates.end())
- return *(result->second);
- }
- return first->second;
- }
复制代码 函数前面声明的那一大堆static const parser_string是所有可能冲突的非终结符。由lr动作表的生成程序决定的。生成程序会产生一个归约报告,表示这些非终结符会发生归约冲突,并显示这些归约的归约长度。
至此就基本解决了C++所有的归约冲突,C++03的文法也无需做任何变形,直接从标准中copy过来就可以用。 |
|