tl; dr:如何在不执行预处理步骤的情况下使用jison模拟C #define
的等价物?
我正在研究一种相对简单的语法,它具有为一大块代码分配标识符的功能,以便稍后可以重复使用。例如:
# Valid grammar with various elements of different types
foo x.3 y.4 z.5
# Assign an id to a chunk of code. Everything after -> is assigned to id
fill_1-> bar a.1 b.2 c.3
# Use chunk of code later
# Should be equivalent to parsing: "baz d.4 bar a.1 b.2 c.3 e.5"
baz d.4 ["fill_1"] e.5
到目前为止,我的解析器设置正确,可以正确识别代码的分配行,并将该部分存储在' - >'的右侧。在字典中可用于其他解析器操作。与下面提供的定义操作相关的代码:
// Lexer
HSPC [ \t]
ID [a-zA-Z_][a-zA-Z0-9_]*
%%
{ID}{HSPC}*"->" {
this.begin("FINISHLINE");
yytext = yytext.replace("->", "").trim();
return "DEFINE";
}
('"'{ID}'"') {
yytext = yytext.slice(1,-1);
return "QUOTED_ID";
}
<FINISHLINE>.* {
this.begin("INITIAL");
yytext = yytext.trim();
return "REST_OF_LINE";
}
%%
// Parser
statement
: "[" QUOTED_ID "]"
{ $$ = (defines[$2] ? defines[$2] : ""); }
| DEFINE REST_OF_LINE
{
defines[$1] = $2;
}
;
%%
var defines = {};
如何让jison实际标记化并解析保存的代码片段?我需要采用AST方法吗?有没有办法将代码注入解析器?这应该发生在lexing阶段还是解析阶段?很想听到一个简短的例子片段可以采取的多种策略。
谢谢!
答案 0 :(得分:2)
如果通过&#34;采用AST方法&#34;,你的意思是&#34;为原始未取代的程序构建AST,对于替代,并将它们拼接在一起&#34;,你在&#34;很难过。我们无法保证您的替换字符串与您语法中的任何有效非终结符一致,因此为它构建树并不容易。替换前的主程序也极不可能通过完整的语法进行解析。 [你可以通过构建子串解析器和使用树片段粘合来实现魔法来克服这些困难,但是会做很多工作[我们正在为C预处理器分析器做这样的事情],我怀疑ANTLR会对你有多大帮助]。 / p>
通常的方法是让词法分析器保留一堆部分读取的输入流,底部流是主程序,嵌套流对应于部分读取的宏调用(如果需要一个宏,则需要多个可以调用另一个。当然你的语言允许&#34; fill2 - &gt; x.1 [fill1] y.3&#34 ;?这意味着词法分析者必须:
您可能有一天会决定在宏上需要参数。您通常也可以将这些实现为流。
你可以想象lexing标记并存储令牌流而不是文本作为宏体;那么宏调用检测和正文插入可能发生在词法分析器之后和解析器之前。由于两者之间可能存在接口,因此将代码置于其间来管理它似乎是一种实用的方法。如果您的语言允许在程序的不同位置以不同的方式解释相同的字符流,则可能会出现复杂情况;在这种情况下,宏捕获将如何知道如何释放宏内容?
我对ANTLR3知之甚少(或甚至更多),无法告诉您如何详细完成此事。
答案 1 :(得分:1)
实现像C预处理器这样的预处理器的常用方法是让它在词法分析器和解析器之间处理令牌流。因此,词法分析器识别令牌,将输入的字符流转换为令牌流,然后预处理器对该令牌流进行操作,将其转换为新的令牌流,最后解析器解析该令牌流。
现在,如果您正在使用yacc / lex(或bison / flex),这有点棘手,因为它们旨在通过调用yylex
的解析器直接与进行通信,中间没有任何东西。使用flex,您可以使用YY_DECL
宏来更改它定义的yylex
函数的声明,并插入您自己的yylex
函数:
%{
#define YY_DECL static int token()
%}
%%
... lex rules
%%
int yylex() {
int tok = token();
if (tok == IDENTIFIER) {
Macro *mac = find_macro(yylval.str);
if (mac) {
yypush_buffer_state(dummy_buffer);
yy_scan_string(mac->definition);
return yylex(); } }
return tok;
}
使用lex,您可以在适当的位置使用#define yylex token
/ #undef yylex
来获得相同的效果。
答案 2 :(得分:0)
因此,经过进一步阅读,这是一种不需要预处理的潜在方法。
我现在在我的解析器中为不同类型的节点生成一个抽象语法树。我的AST由各种类组成,一些代表基本单元,另一些代表元单元。我的“宏”用它们自己的AST元素表示,它包含对已解析的AST元素集合的引用。因此,我没有进行“代码替换”,而是代码被替换然后多次解析,我解析定义的块一次并存储对创建的AST元素的引用。 E.g:
# fundamental units
class TabElement
# subclasses of TabElement
class NoteElement
class SlideElement
class MuteElement
# meta element: holds a collection of TabElements's
class NoteGroupElement
# meta element: holds a collection of TabElements's and otherstatements, has an ID
# the collection is accessible via a global dict to other elements
class PredefElement
# meta element: represents a usage of a PredefElement
class PredefInvokeElement
这样做的缩写lexer和解析器规则类似于以下内容(省略了许多无关的东西,希望你能得到图片):
/* LEXER */
<INITIAL>[\s] /* ignore whitespace */
{ID}{HSPC}*"->" {
this.begin("DEFINE");
yytext = yytext.replace("->", "").trim();
return "DEFINE";
}
/* using a pre-def */
('"'{ID}'"')|("'"{ID}"'") {
yytext = yytext.slice(1,-1);
return "QUOTED_ID";
}
/* When defining a code chunk. Newlines delimit the end of the definition */
<DEFINE>{HSPC} /* ignore horizontal whitespace */
<DEFINE>({NL}|<<EOF>>) { this.begin("INITIAL"); return "NL"; }
/* PARSER */
statement
: statement_group
{ $$ = $1; }
| DEFINE statements NL
{
defines[$1] = new ast.PredefinedElement($1, $2, @1);
$$ = defines[$1]
}
| predefine_invoke
{ $$ = $1; }
| chord
{ $1 = $1; }
| bar_token
{ $$ = $1; }
| REPEAT_MEASURE
{ $$ = new ast.RepeatMeasureElement(@1); }
| REST
{ $$ = new ast.RestElement(@1); }
| DURATION
{ $$ = new ast.DurationElement($1, @1); }
| note_token
{ $$ = $1; }
;
predefine_invoke
: "[" QUOTED_ID "]"
%{
if (defines[$2]) { $$ = new ast.PredefinedInvokeElement(defines[$2], @2); }
else { $$ = undefined; }
%}
| "[" QUOTED_ID "]" "*" INTEGER
%{
if (defines[$2]) { $$ = new ast.PredefinedInvokeElement(defines[$2], @2, { repeat: parseInt($5) }); }
else { $$ = undefined; }
%}
;