本文使用C语言Melon库实现一个简易脚本语言解析器。 为了便于阅读和理解,解析器不会生成抽象语法树结构,只是对脚本文件的语法进行解析。第三方库 Melon是一个C语言库,其中提供了一个用宏封装好的脚本解释器相关的套件,如词法分析器和语法解析器生成器。使用非常简单,最简单的使用仅需非常简短的代码即可。 安装如下:gitclonehttps:github。comWaterMelonMelon。gitcdMelon。configure〔prefix。。。。〕makemakeinstall解释器程序 下面直接上代码,这里我对代码进行了完全注释。test。cincludestdio。hincludestring。hincludemlncore。hincludemlnlog。hincludemlnlex。hincludemlnalloc。hincludemlnparsergenerator。h声明语法解析器生成器,函数作用域为static,函数与结构体名称前缀为test,词素前缀为TEST。本例没有自定义词素。MLNDECLAREPARSERGENERATOR(static,test,TEST);定义语法解析器生成器,函数作用域为static,函数与结构体名称前缀为test,词素前缀为TEST。本例没有自定义词素。MLNDEFINEPARSERGENERATOR(static,test,TEST);产生式表,产生式的原理与代数非常相似,就是将同名的部分代入展开。start为所有语法的启始,xxxTKEOF表示语言读取完毕处本例中stm表示语句(statement),exp表示表达式(expression),addsub表示加减法表达式。TESTTKSEMIC为分号(;),TESTTKID为变量名(以下划线字母起始,后续自符有字母数字下划线组成),TESTTKDEC为整数。这里使用了词法分析器的默认词素切分规则生成的词素,开发者可根据自己需求对关键字、特殊运算符进行扩展。每一个产生式结构的第二个参数为一个处理函数,该函数会在本产生式冒号(:)右侧所有词素(确切的叫终结符与非终结符)都获取到时被调用。我们可以在这些回调函数中生成抽象语法树结构。staticmlnproductiontprodtbl〔〕{{start:stmTESTTKEOF,NULL},{stm:expTESTTKSEMICstm,NULL},{stm:,NULL},{exp:TESTTKIDaddsub,NULL},{exp:TESTTKDECaddsub,NULL},{addsub:TESTTKPLUSexp,NULL},{addsub:TESTTKSUBexp,NULL},{addsub:,NULL},};intmain(intargc,charargv〔〕){mlnlextlexNULL;mlnu8ptrtptr,框架初始化cattr。cattr。cattr。globalinitNULL;cattr。workerprocessNULL;if(mlncoreinit(cattr)0){fprintf(stderr,Meloninitfailed。);return1;}设置自定义语言文本文件路径mlnstringset(path,argv〔1〕);创建内存池,用于语法分析过程中使用,使用后可进行释放。这里需要注意,生成的抽象语法树结构尽量不要使用该内存池,开发者可能会习惯性按本示例一样在解析后进行释放。则在后续处理抽象语法树时就会发生越界访问。if((poolmlnallocinit())NULL){mlnlog(error,initmemorypoolfailed。);return1;}设置词法分析器内存池lattr。本例没有自定义关键字lattr。keywordsNULL;本例没有对运算符进行扩展memset(hooks,0,sizeof(hooks));lattr。启用预编译机制,启用后,自定义语言中可使用include、def、endif等预编译宏lattr。preprocess1;设置为文件路径名类型。待解析内容可以直接给出字符串内容,也可以是文本路径,可参考词法分析器中的定义。lattr。typeMINPUTTFILE;若type为MINPUTTFILE,则data为文件路径,否则为自定义语言字符串。lattr。初始化词法分析器mlnlexinitwithhooks(test,lex,lattr);if(lexNULL){mlnlog(error,initlexerfailed。);return1;}生成状态转换表ptrtestparsergenerate(prodtbl,sizeof(prodtbl)sizeof(mlnproductiont));if(ptrNULL){mlnlog(error,generatestateshifttablefailed。);return1;}设置语法解析器内存池pattr。设置产生式pattr。设置词法解析器,待解析的语言由词法分析器拆解后交由本解析器处理pattr。设置状态转换表pattr。本例没有自定义数据pattr。udataNULL;执行解析asttestparse(pattr);。。。对自定义的抽象语法树结构进行处理,本例中没有定义相关结构,因此会保持为NULL销毁词法分析器mlnlexdestroy(lex);释放内存池mlnallocdestroy(pool);return0;} 下面对程序进行编译ccotesttest,cIpathtomelonincludeLpathtomelonliblmelonlpthread 然后我们编辑一个文本a。test,其中包含我们的脚本语言:a1;11; 然后用我们的脚本解释器来对脚本文件进行解析:。testa。test 这时可以看到什么都没有输出,这代表语法通过了检查。 下面我们对a。test进行修改,故意将其改为违反语法规则的文本:a1; 再次运行程序去解析脚本文件,可得到如下输出:a。test:1:ParseError:Illegaltokennearby。结语 这个例子只是简单地展示了一个可以解析语法规则的解析器,对该内容感兴趣的读者可以自行对其扩展实现更多语法和抽象语法树等内容。 感谢阅读!