本次分析的JamPlus为Jamplus0.3(基于Jam2.5)
阅读本篇文章需要对Lex&Yacc有一定了解,如果没有基础,推荐看完《Lex&Yacc入门》和《使用Yacc》再来阅读本篇文章。
Jam的解析阶段
Jam一共有4个阶段的操作,启动,解析,绑定,更新。
Jam has four phases of operation: start-up, parsing, binding, and updating.
在解析阶段,jam会读取和执行Jambase文件,默认情况是内置的那个。它由jam语言书写。Jambase的最后一个操作是(通过include规则)读取用户指定的Jamfile文件。
In the parsing phase, jam reads and executes the Jambase file, by default the built-in one. It is written in the jam language. See Language below. The last action of the Jambase is to read (via the "include" rule) a user-provided file called "Jamfile".
总的来说,Jambase和Jamfile的共同作用是为构建目标及源文件命名,构建它们之间的依赖关系图,并将构建动作与目标关联起来。Jambase定义了通用的规则和变量赋值,而Jamfile则利用这些规则来具体指明目标文件与源文件之间的实际关系。相关信息请参阅《Jambase参考手册》及《使用Jamfile与Jambase》文档。
Collectively, the purpose of the Jambase and the Jamfile is to name built target and source files, construct the dependency graph among them, and associate build actions with targets. The Jambase defines boilerplate rules and variable assignments, and the Jamfile uses these to specify the actual relationship among the target and source files. See the Jambase Reference and the document Using Jamfiles and Jambase for information.
Parse_File函数
在JamPlus的源码中,对应到Parse阶段的函数是位于parse.c文件中的parse_file函数。 它首先会在Jam.c文件中的main函数中被调用,parse_file("+")。
另外include规则("include" rule)在找到文件的具体路径后,也会调用到parse_file这个函数。可以说parse_file基本上等同于include规则。
void
parse_file( const char *f )
{IncludeFile(f);for(;;){LOL l;PARSE *p;int jmp = 0; /* JMP_NONE */LIST *head = NULL;/* $(<) and $(>) empty in outer scope. */lol_init( &l );/* Filled by yyparse() calling parse_save() */yypsave = 0;/* If parse error or empty parse, outta here */if( yyparse() || !( p = yypsave ) )break;/* Run the parse tree. */;head= ((p->func)( p, &l, &jmp ));list_free( head );parse_free( p );}
}
IncludeFile函数(include函数)
我们看到,parse_file函数首先调用了IncludeFile函数, 这个函数的作用就是传入一个文件名,然后将其包装成include结构体,然后存储到scan.c文件中的includeFileHead这个字段中。
includeFileHead这个字段表示当前正在解析的文件。当语法分析器(parser)想要获取token记号的时候,词法分析器会从includeFileHead对应的文件中读取。
这里值得一提的是,main函数中调用的parse_file("+")传入的文件名是"+",这个文件名会在IncludeFile中特殊处理,将文件的具体内容设置为jambaseBootstrapString这个字符串。
if( !strcmp( s, "+" ) )i->strings = jambaseBootstrapString;
jambaseBootstrapString这个字符串在jambase-j.c文件中定义,这段字符串的作用主要是定义了一些符号到VariableDict中,以及最后一行include Jambase.jam指令compile的时候会读取执行Jambase.jam文件,这里的Jambase.jam是Jam自带的底层库文件。
yyparse函数(语法解析入口函数)
yyparse 函数是语法解析的入口函数,它的定义位于jamgram.c, jamgram.c由Yacc/Bison根据语法规则文件jamgram.y文件生成。 yyparse会调用词法分析器生成的yylex函数获取token记号,然后不断地匹配语法规则,匹配成功后会执行对应的行为操作(action code)。
由于本人对Jamplus和Yacc的理解有限,此处介绍可能有误,或者出现难以理解的情况。建议读者自己亲自调试一下,可以在
parse_make,compile_set几个函数下断。
比如语法规则文件jamgram.y中的run这个规则会是语法解析树的root节点。
run : /* empty *//* do nothing */| rules{ parse_save( $1.parse ); };
在解析完一个jamfile文件后, 正常情况下会完成一次 run语法规则的匹配,这个时候会执行parse_save($1.parse)代码, parse_save函数会将传入的parse结构体存储到yypsave这个全局字段中。 细心的读者可以发现在之前的parse_file文件中就用到了这个yypsave字段。
简单介绍一下yacc的规则,这里$$会被替换成冒号左侧符号对应的值,$1会被替换成冒号右侧第一个符号的值,$2会被替换成冒号右侧第二个符号的值。本来这些值的类型会由一个联合体Union表示,在Jam工程中,它是一个在scan.h中定义的结构体YYSTYPE
由于$1对应的 rules 这个符号对应的值,所以传入$1.parse就是传入了rules这个符号的值的parse字段。可以在附录中的YYSTYPE结构体中看到有这个字段。
总的来说, yyparse函数在这里的作用就是构建了语法解析树,它的每个节点的类型都是PARSE,可以由childParse1,childParse2等字段找到子节点。 具体的语法树构建过程可以参考jamgram.y文件和parse_make函数。
我们可以再看一下rules这个规则。在构造语法树的过程中,action code会为每个符号对应的parse设置函数,比如rules规则,当它匹配到 rules: rule rules时,就会执行prules这个宏,然后将compile_rules这个函数的指针存储到PARSE的func字段中。
# define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 )
rules : rule{ $$.parse = $1.parse; }| rule rules{ $$.parse = prules( $1.parse, $2.parse ); }| LOCAL_t list _SEMIC_t block{ $$.parse = plocal( $2.parse, pnull(), $4.parse ); }| LOCAL_t list _EQUALS_t list _SEMIC_t block{ $$.parse = plocal( $2.parse, $4.parse, $6.parse ); };
接着我们实战应用,简单看一下具体需要解析的内容。
对于jambaseBootstrapString,第一行"JAMBASE ?= Jambase.jam ;\n", 它在匹配arg assign list规则后,紧接着会匹配rule : arg assign list _SEMIC_t这个规则,这里会调用对应的action code { $$.parse = pset( $1.parse, $3.parse, $2.number ); },
然后是判断当前操作系统再执行逻辑的的if-elseif-elseif-elseif
"if $(UNIX) {\n",
"DOT default = . ;\n",
"DOTDOT default = .. ;\n",
"SLASH default = / ;\n",
"} else if $(NT) || $(OS2) {\n",
"DOT default = . ;\n",
"DOTDOT default = .. ;\n",
"SLASH default = \\\\ ;\n",
"} else if $(VMS) {\n",
"DOT default = [] ;\n",
"DOTDOT default = [-] ;\n",
"SLASH default = . ;\n",
"} else if $(MAC) {\n",
"DOT default = \":\" ;\n",
"DOTDOT default = \"::\" ;\n",
"SLASH default = \":\" ;\n",
"}\n",
这里会先根据rule: IF_t expr _LBRACE_t block _RBRACE_t规约最后一个if $(MAC)这部分的逻辑,然后再按照rule: IF_t expr _LBRACE_t block _RBRACE_t ELSE_t rule规则规约3次。它们的action code都会为rule这个符号对应的Parse设置函数compile_if。
接着是 SEARCH on $(JAMBASE) =\n ... 会匹配 rule: arg ON_t list assign list _SEMIC_t,解析后会调用{ $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); }函数。
最后是include $(JAMBASE) ;\n 会匹配规则rule: | INCLUDE_t list _SEMIC_t ,对应的action code是 { $$.parse = pincl( $2.parse ); },最后解释执行的时候会调用compile_include函数。
这里parse中设置的函数会在文本解析完成后,从解析树的root节点开始调用func(也就是yypsave这个全局字段)从而解释执行代码。
打印ParseTree
为了能够更清楚地理清解析树是如何构成的,我们可以将每个ParseNode的依赖关系按照mermaid的语法打印,然后放到mermaid编辑器中观察,
为了获得 mermaid 语法表示的 解析树关系图,我们需要修改以下几处代码, 首先是jamgram.c文件中 找到yyreduce这个label,在YY_SYMBOL_PRINT之后,YYPOPSTACK 之前插入下面代码。
以及parse.c文件中 调用yyparse结束后,需要调用PrintParseTree函数。
//jamgram.c/*-----------------------------.
| yyreduce -- Do a reduction. |
`-----------------------------*/
yyreduce:...YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);//这里yyr1 传入规则ID 可以获得 规则左侧symbol的int类型,
// yytname传入symbol类型可以获得对应的字符串
// 更多细节可以 参考 yy_reduce_print函数
if( yyval.parse != NULL)yyval.parse->SymbolName = (char *)yytname[ yyr1[yyn] ];YYPOPSTACK (yylen);
void PrintParseRecursive(PARSE *pRoot, FILE *file)
{PARSE *parse1 = pRoot->childParse1;PARSE *parse2 = pRoot->childParse2;PARSE *parse3 = pRoot->childParse3;if(parse1 != NULL)fprintf(file, "\t%d(%s) --> %d(%s)\n",pRoot,pRoot->SymbolName, parse1,parse1->SymbolName);if(parse2 != NULL)fprintf(file, "\t%d(%s) --> %d(%s)\n",pRoot,pRoot->SymbolName, parse2,parse2->SymbolName);if(parse3 != NULL)fprintf(file, "\t%d(%s) --> %d(%s)\n",pRoot,pRoot->SymbolName, parse3,parse3->SymbolName);if(parse1 != NULL)PrintParseRecursive(parse1, file);if(parse2 != NULL)PrintParseRecursive(parse2, file);if(parse3 != NULL)PrintParseRecursive(parse3, file);}void PrintParseTree(PARSE *pRoot)
{FILE *file;if(pRoot == NULL)return;file = fopen("output.txt", "w");if (file == NULL) {printf("无法打开文件!\n");return;}fprintf(file, "graph TD\n");PrintParseRecursive(pRoot, file);fclose(file);}

解释执行
执行完yyparse后,parse_file函数会接着执行head= ((p->func)( p, &l, &jmp )); 调用解析树Root节点的func函数。
由于yypsave中存储的是rules符号对应的Parse,因此这里调用p->func会进入到compile_rules这个函数中(由rules: rule rules这个规则的action code{ $$.parse = prules( $1.parse, $2.parse ); }设置。)
虽然这里函数名是
compile_rules但实际上,compile.c文件里面的这些函数会通过之前构建的解析树,解释执行对应的jamfile文件中的脚本代码。
compile_rules
compile_rules主要对应的是jamgram.y 154行的规则,它会递归匹配rules,Action Code会生成一个parse赋值给rules符号的值,然后将栈中最近匹配的rule的parse设置为childParse1, 以及rules的parse设置为childParse2
rules: rule rules{ $$.parse = prules( $1.parse, $2.parse ); }
这条规则对应了 compile_rules代码里面的 while循环这部分,它会检测func是否为compile_rules如果是,说明匹配的是上面这个规则。那么代码会循环拆解上面的递归规则。
LIST *
compile_rules(PARSE *parse,LOL *args,int *jmp )
{/* Ignore result from first statement; return the 2nd. *//* Optimize recursion on the right by looping. */LIST *result = 0;while( *jmp == JMP_NONE && parse->func == compile_rules ){list_free( result );result = (*parse->childParse1->func)( parse->childParse1, args, jmp );parse = parse->childParse2;}if( *jmp == JMP_NONE ){list_free( result );result = (*parse->func)( parse, args, jmp );}return result;
}
这里我们step into childParse1->func函数的执行会进入到compile_set函数。也就是上面mermaid 解析树图中最左侧部分的分支。
compile_set
compile_set函数 用于设置全局Variable的值, 它会从 childParse1中获取变量名,然后从childParse2中获取对应的值。
然后调用var_set 将childParse2中获取的值 赋值 childParse1获取的变量名对应的变量。
LIST *
compile_set(PARSE *parse,LOL *args,int *jmp )
{LIST *nt = (*parse->childParse1->func)( parse->childParse1, args, jmp );LIST *ns = (*parse->childParse2->func)( parse->childParse2, args, jmp );LIST *l;/* Call var_set to set variable *//* var_set keeps ns, so need to copy it */for( l = nt; l; l = list_next( l ) )var_set( l->string, list_copy( L0, ns ), parse->num );list_free( nt );return ns;
}
var_set() - set a variable in jam's user defined symbol table
compile_if 函数
执行完 compile_set,compile_rules会取出childParse2这个递归生成的rules规则,然后调用它的childParse1对应的函数,这里也就是compile_if 函数。
这里 childParse1中存储的是expr这个符号的Parse,也就是if判断里面的内容,如果执行后l存在,表示表达式判断的结果为true,那么执行childParse2里面的函数(if对应的代码),否则执行childParse3对应的函数(else对应的代码)。
LIST *
compile_if(PARSE *p,LOL *args,int *jmp )
{LIST *l = (*p->childParse1->func)( p->childParse1, args, jmp );p = l ? p->childParse2 : p->childParse3;list_free( l );return (*p->func)( p, args, jmp );
}
rule:...| IF_t expr _LBRACE_t block _RBRACE_t{ $$.parse = pif( $2.parse, $4.parse, pnull() ); }| IF_t expr _LBRACE_t block _RBRACE_t ELSE_t rule{ $$.parse = pif( $2.parse, $4.parse, $7.parse ); }
compile_AddTargetSettings
if else 这段脚本执行完后,会接着执行SEARCH ON $(JAMBASE) =...这段脚本, 这里对应到jamgram.y 170行的规则匹配, rule: arg ON_t list assign list _SEMIC_t
这个函数的功能会为 target设置settings。
compile_include
最后一行脚本include $(JAMBASE)\n会调用compile_include函数。
这里会先搜索 target对应的文件名,之前SEARCH ON ...脚本给Target的Setting赋值了,然后这里需要临时将其设置为全局信息(Variable Dict),这样search函数里面会用到。
然后就是再调用 parse_file, 继续加载新的脚本解析并执行,这个函数就是main函数中 调用的`parse_file("+")对应着的Parse阶段。
LIST *
compile_include(PARSE *parse,LOL *args,int *jmp )
{LIST *nt = (*parse->childParse1->func)( parse->childParse1, args, jmp );if( nt ){TARGET *t = bindtarget( nt->string );pushsettings( t->settings );t->boundname = search( t->name, &t->time );popsettings( t->settings );/* Don't parse missing file if NOCARE set */if( t->time || !( t->flags & T_FLAG_NOCARE ) )parse_file( t->boundname );}list_free( nt );return L0;
}
scan.c 词法分析器
正常情况下,当我们调用yyparse它会不断地调用lex生成的yylex函数获取token记号。但在Jam工程中,手写的词法分析器scan.c代替了lex生成的。
附录
YYSTYPE
//scan.h
typedef struct _YYSTYPE {int type;const char *string;PARSE *parse;LIST *list;int number;
/* commented out so jamgram.y can compile #ifdef OPT_ACTION_MAXTARGETS_EXT */int number2;int number3;
/* commented out so jamgram.y can compile #endif */
} YYSTYPE;
Parse
typedef struct _PARSE PARSE;
struct _PARSE {LIST *(*func)( PARSE *p, LOL *args, int *jmp );// child node of current parse node in parse treePARSE *childParse1; PARSE *childParse2;PARSE *childParse3;const char *string;const char *string1;int num;
/* commented out so jamgram.y can compile #ifdef OPT_ACTION_MAXTARGETS_EXT */int num2;int num3;
/* commented out so jamgram.y can compile #endif */int refs;
} ;
jambaseBootstrapString
/* Generated by mkjambase from Jambase */
const char *jambaseBootstrapString[] = {
/* Jambase-j */
"JAMBASE ?= Jambase.jam ;\n",
"if $(UNIX) {\n",
"DOT default = . ;\n",
"DOTDOT default = .. ;\n",
"SLASH default = / ;\n",
"} else if $(NT) || $(OS2) {\n",
"DOT default = . ;\n",
"DOTDOT default = .. ;\n",
"SLASH default = \\\\ ;\n",
"} else if $(VMS) {\n",
"DOT default = [] ;\n",
"DOTDOT default = [-] ;\n",
"SLASH default = . ;\n",
"} else if $(MAC) {\n",
"DOT default = \":\" ;\n",
"DOTDOT default = \"::\" ;\n",
"SLASH default = \":\" ;\n",
"}\n",
"SEARCH on $(JAMBASE) =\n",
"$(DOT)\n",
"$(JAM_PROCESS_PATH)\n",
"$(JAM_PROCESS_PATH)$(SLASH)$(DOTDOT)\n",
";\n",
"include $(JAMBASE) ;\n",
0 };
jamgram.y jam语法规则文件
%token _BANG_t
%token _BANG_EQUALS_t
%token _AMPER_t
%token _AMPERAMPER_t
%token _LPAREN_t
%token _RPAREN_t
%token _PLUS_EQUALS_t
%token _MINUS_EQUALS_t
%token _COLON_t
%token _SEMIC_t
%token _LANGLE_t
%token _LANGLE_EQUALS_t
%token _EQUALS_t
%token _RANGLE_t
%token _RANGLE_EQUALS_t
%token _QUESTION_EQUALS_t
%token _LBRACKET_t
%token _RBRACKET_t
%token ACTIONS_t
%token BIND_t
%token BREAK_t
%token CASE_t
%token CONTINUE_t
%token DEFAULT_t
%token ELSE_t
%token EXISTING_t
%token FOR_t
%token IF_t
%token IGNORE_t
%token IN_t
%token INCLUDE_t
%token LOCAL_t
%token LUA_t
%token MAXLINE_t
%token MAXTARGETS_t
%token ON_t
%token PIECEMEAL_t
%token QUIETLY_t
%token REMOVEEMPTYDIRS_t
%token RESPONSE_t
%token RETURN_t
%token RULE_t
%token SCREENOUTPUT_t
%token SWITCH_t
%token TOGETHER_t
%token UPDATED_t
%token WHILE_t
%token _LBRACE_t
%token _BAR_t
%token _BARBAR_t
%token _RBRACE_t
/** Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.** This file is part of Jam - see jam.c for Copyright information.*//** jamgram.yy - jam grammar** 04/13/94 (seiwald) - added shorthand L0 for null list pointer* 06/01/94 (seiwald) - new 'actions existing' does existing sources* 08/23/94 (seiwald) - Support for '+=' (append to variable)* 08/31/94 (seiwald) - Allow ?= as alias for "default =".* 09/15/94 (seiwald) - if conditionals take only single arguments, so* that 'if foo == bar' gives syntax error (use =).* 02/11/95 (seiwald) - when scanning arguments to rules, only treat* punctuation keywords as keywords. All arg lists* are terminated with punctuation keywords.* 09/11/00 (seiwald) - Support for function calls; rules return LIST *.* 01/22/01 (seiwald) - replace evaluate_if() with compile_eval()* 01/24/01 (seiwald) - 'while' statement* 03/23/01 (seiwald) - "[ on target rule ]" support* 02/27/02 (seiwald) - un-break "expr : arg in list" syntax* 03/02/02 (seiwald) - rules can be invoked via variable names* 03/12/02 (seiwald) - set YYMAXDEPTH for big, right-recursive rules* 02/28/02 (seiwald) - merge EXEC_xxx flags in with RULE_xxx* 06/21/02 (seiwald) - support for named parameters* 10/22/02 (seiwald) - working return/break/continue statements*/%token ARG STRING%left _BARBAR_t _BAR_t
%left _AMPERAMPER_t _AMPER_t
%left _EQUALS_t _BANG_EQUALS_t IN_t
%left _LANGLE_t _LANGLE_EQUALS_t _RANGLE_t _RANGLE_EQUALS_t
%left _BANG_t%{
#include "jam.h"#include "lists.h"
#include "variable.h"
#include "parse.h"
#include "scan.h"
#include "compile.h"
#include "newstr.h"
#include "rules.h"# define YYMAXDEPTH 10000 /* for OSF and other less endowed yaccs */# define F0 (LIST *(*)(PARSE *, LOL *, int *))0
# define P0 (PARSE *)0
# define S0 (char *)0# define pappend( l,r ) parse_make( compile_append,l,r,P0,S0,S0,0 )
# define pbreak( l,f ) parse_make( compile_break,l,P0,P0,S0,S0,f )
# define peval( c,l,r ) parse_make( compile_eval,l,r,P0,S0,S0,c )
# define pfor( s,l,r ) parse_make( compile_foreach,l,r,P0,s,S0,0 )
# define pif( l,r,t ) parse_make( compile_if,l,r,t,S0,S0,0 )
# define pincl( l ) parse_make( compile_include,l,P0,P0,S0,S0,0 )
# define plist( s ) parse_make( compile_list,P0,P0,P0,s,S0,0 )
# define plocal( l,r,t ) parse_make( compile_local,l,r,t,S0,S0,0 )
# define pnull() parse_make( compile_null,P0,P0,P0,S0,S0,0 )
# define pon( l,r ) parse_make( compile_on,l,r,P0,S0,S0,0 )
# define prule( a,p ) parse_make( compile_rule,a,p,P0,S0,S0,0 )
# define prules( l,r ) parse_make( compile_rules,l,r,P0,S0,S0,0 )
# define pset( l,r,a ) parse_make( compile_set,l,r,P0,S0,S0,a )
# define pset1( l,r,t,a ) parse_make( compile_settings,l,r,t,S0,S0,a )
# define psetc( s,l,r ) parse_make( compile_setcomp,l,r,P0,s,S0,0 )
# define psete( s,l,s1,f,f2,f3 ) parse_make3( compile_setexec,l,P0,P0,s,s1,f,f2,f3 )
# define pswitch( l,r ) parse_make( compile_switch,l,r,P0,S0,S0,0 )
# define pwhile( l,r ) parse_make( compile_while,l,r,P0,S0,S0,0 )# define pnode( l,r ) parse_make( F0,l,r,P0,S0,S0,0 )
# define psnode( s,l ) parse_make( F0,l,P0,P0,s,S0,0 )%}%%run : /* empty *//* do nothing */| rules{ parse_save( $1.parse ); };/** block - zero or more rules* rules - one or more rules* rule - any one of jam's rules* right-recursive so rules execute in order.*/block : /* empty */{ $$.parse = pnull(); }| rules{ $$.parse = $1.parse; };rules : rule{ $$.parse = $1.parse; }| rule rules{ $$.parse = prules( $1.parse, $2.parse ); }| LOCAL_t list _SEMIC_t block{ $$.parse = plocal( $2.parse, pnull(), $4.parse ); }| LOCAL_t list _EQUALS_t list _SEMIC_t block{ $$.parse = plocal( $2.parse, $4.parse, $6.parse ); };rule : _LBRACE_t block _RBRACE_t{ $$.parse = $2.parse; }| INCLUDE_t list _SEMIC_t{ $$.parse = pincl( $2.parse ); }| arg lol _SEMIC_t{ $$.parse = prule( $1.parse, $2.parse ); }| arg assign list _SEMIC_t{ $$.parse = pset( $1.parse, $3.parse, $2.number ); }| arg ON_t list assign list _SEMIC_t{ $$.parse = pset1( $1.parse, $3.parse, $5.parse, $4.number ); }| BREAK_t list _SEMIC_t{ $$.parse = pbreak( $2.parse, JMP_BREAK ); }| CONTINUE_t list _SEMIC_t{ $$.parse = pbreak( $2.parse, JMP_CONTINUE ); }| RETURN_t list _SEMIC_t{ $$.parse = pbreak( $2.parse, JMP_RETURN ); }| FOR_t ARG IN_t list _LBRACE_t block _RBRACE_t{ $$.parse = pfor( $2.string, $4.parse, $6.parse ); }| SWITCH_t list _LBRACE_t cases _RBRACE_t{ $$.parse = pswitch( $2.parse, $4.parse ); }| IF_t expr _LBRACE_t block _RBRACE_t{ $$.parse = pif( $2.parse, $4.parse, pnull() ); }| IF_t expr _LBRACE_t block _RBRACE_t ELSE_t rule{ $$.parse = pif( $2.parse, $4.parse, $7.parse ); }| WHILE_t expr _LBRACE_t block _RBRACE_t{ $$.parse = pwhile( $2.parse, $4.parse ); }| RULE_t ARG params _LBRACE_t block _RBRACE_t{ $$.parse = psetc( $2.string, $3.parse, $5.parse ); }| ON_t arg rule{ $$.parse = pon( $2.parse, $3.parse ); }| ACTIONS_t eflags ARG bindlist _LBRACE_t{ yymode( SCAN_STRING ); }STRING{ yymode( SCAN_NORMAL ); }_RBRACE_t{ $$.parse = psete( $3.string,$4.parse,$7.string,$2.number,$2.number2,$2.number3 ); };/** assign - = or += or -=*/assign : _EQUALS_t{ $$.number = VAR_SET; }| _PLUS_EQUALS_t{ $$.number = VAR_APPEND; }| _MINUS_EQUALS_t{ $$.number = VAR_REMOVE; }| _QUESTION_EQUALS_t{ $$.number = VAR_DEFAULT; }| DEFAULT_t _EQUALS_t{ $$.number = VAR_DEFAULT; };/** expr - an expression for if*/expr : arg{ $$.parse = peval( EXPR_EXISTS, $1.parse, pnull() ); }| expr _EQUALS_t expr{ $$.parse = peval( EXPR_EQUALS, $1.parse, $3.parse ); }| expr _BANG_EQUALS_t expr{ $$.parse = peval( EXPR_NOTEQ, $1.parse, $3.parse ); }| expr _LANGLE_t expr{ $$.parse = peval( EXPR_LESS, $1.parse, $3.parse ); }| expr _LANGLE_EQUALS_t expr{ $$.parse = peval( EXPR_LESSEQ, $1.parse, $3.parse ); }| expr _RANGLE_t expr{ $$.parse = peval( EXPR_MORE, $1.parse, $3.parse ); }| expr _RANGLE_EQUALS_t expr{ $$.parse = peval( EXPR_MOREEQ, $1.parse, $3.parse ); }| expr _AMPER_t expr{ $$.parse = peval( EXPR_AND, $1.parse, $3.parse ); }| expr _AMPERAMPER_t expr{ $$.parse = peval( EXPR_AND, $1.parse, $3.parse ); }| expr _BAR_t expr{ $$.parse = peval( EXPR_OR, $1.parse, $3.parse ); }| expr _BARBAR_t expr{ $$.parse = peval( EXPR_OR, $1.parse, $3.parse ); }| arg IN_t list{ $$.parse = peval( EXPR_IN, $1.parse, $3.parse ); }| _BANG_t expr{ $$.parse = peval( EXPR_NOT, $2.parse, pnull() ); }| _LPAREN_t expr _RPAREN_t{ $$.parse = $2.parse; };/** cases - action elements inside a 'switch'* case - a single action element inside a 'switch'* right-recursive rule so cases can be examined in order.*/cases : /* empty */{ $$.parse = P0; }| case cases{ $$.parse = pnode( $1.parse, $2.parse ); };case : CASE_t ARG _COLON_t block{ $$.parse = psnode( $2.string, $4.parse ); };/** params - optional parameter names to rule definition* right-recursive rule so that params can be added in order.*/params : /* empty */{ $$.parse = P0; }| ARG _COLON_t params{ $$.parse = psnode( $1.string, $3.parse ); }| ARG{ $$.parse = psnode( $1.string, P0 ); };/** lol - list of lists* right-recursive rule so that lists can be added in order.*/lol : list{ $$.parse = pnode( P0, $1.parse ); }| list _COLON_t lol{ $$.parse = pnode( $3.parse, $1.parse ); };/** list - zero or more args in a LIST* listp - list (in puncutation only mode)* arg - one ARG or function call*/list : listp{ $$.parse = $1.parse; yymode( SCAN_NORMAL ); };listp : /* empty */{ $$.parse = pnull(); yymode( SCAN_PUNCT ); }| listp arg{ $$.parse = pappend( $1.parse, $2.parse ); };arg : ARG{ $$.parse = plist( $1.string ); }| _LBRACKET_t { yymode( SCAN_NORMAL ); } func _RBRACKET_t{ $$.parse = $3.parse; };/** func - a function call (inside [])* This needs to be split cleanly out of 'rule'*/func : arg lol{ $$.parse = prule( $1.parse, $2.parse ); }| ON_t arg arg lol{ $$.parse = pon( $2.parse, prule( $3.parse, $4.parse ) ); }| ON_t arg RETURN_t list{ $$.parse = pon( $2.parse, $4.parse ); };/** eflags - zero or more modifiers to 'executes'* eflag - a single modifier to 'executes'*/eflags : /* empty */{ $$.number = $$.number2 = $$.number3 = 0; }| eflags eflag{ $$.number = $1.number | $2.number; if ($2.number2 != 0) $$.number2 = $2.number2; if ($2.number3 != 0) $$.number3 = $2.number3; };eflag : UPDATED_t{ $$.number = RULE_UPDATED; }| TOGETHER_t{ $$.number = RULE_TOGETHER; }| IGNORE_t{ $$.number = RULE_IGNORE; }| QUIETLY_t{ $$.number = RULE_QUIETLY; }| PIECEMEAL_t{ $$.number = RULE_PIECEMEAL; }| EXISTING_t{ $$.number = RULE_EXISTING; }| MAXLINE_t ARG{ $$.number = RULE_MAXLINE; $$.number2 = atoi( $2.string ); }| RESPONSE_t{ $$.number = RULE_RESPONSE; }| LUA_t{ $$.number = RULE_LUA; }| MAXTARGETS_t ARG{ $$.number = RULE_MAXTARGETS; $$.number3 = atoi( $2.string ); }| SCREENOUTPUT_t{ $$.number = RULE_SCREENOUTPUT; }| REMOVEEMPTYDIRS_t{ $$.number = RULE_REMOVEEMPTYDIRS; };/** bindlist - list of variable to bind for an action*/bindlist : /* empty */{ $$.parse = pnull(); }| BIND_t list{ $$.parse = $2.parse; };