Skip to content

本Demo项目主要用于简单展示:1、词法分析器;2、语法分析器;3、脚本编译器;4、编译执行器,四个过程,因为他们是实现自定义脚本引擎的基础。该项目体现的也是all-in-one低代码平台中一分部分思想。

Notifications You must be signed in to change notification settings

bossfriday/expression-engine-protype

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 

Repository files navigation

《编译原理抽象语法树生成及执行实例》https://blog.csdn.net/camelials/article/details/123415475

项目简介

打印1+1结果搞得这么复杂并非炫技,只是用于简单展示:1、词法分析器;2、语法分析器;3、脚本编译器;4、编译执行器,四个过程,因为他们是实现自定义脚本引擎的基础。基于这种方式,后续扩展条件(if/else),循环(for/while),其他类型(map,对象,list,set……),其他语法特性(import,匿名函数)等都是比较方便及可控。一套脚本支持了常用数据类型和语法之后,称其为自定义脚本引擎就名副其实了。还需要说明的是:该项目体现的也是all-in-one低代码平台中一分部分思想。

过程简介

1、词法分析器(开发完成)

1、扫描源代码文本,从左到右扫描文本,把文本拆成一些单词。
2、分析出拆出来的单词是什么:关键字、标识符、符号、,注释……,其结果产物为Token。

上面的话不太好懂,举个栗子:

var a = 1 + 1;  // Comment

var :关键字
a : 标识符
= : 符号
1 : 数字
// Comment : 注释

测试代码:

public static void main(String[] args) throws Exception {
        String strScript = "var a = 1 + 1; // Comment";
        ScriptTokenRegister tokenRegister = new ScriptTokenRegister();
        List<Token> tokens = tokenRegister.getTokens(strScript);
        tokens.forEach(token->{
            System.out.println(token.toString());
        });
    }

分词结果如下(注释由于对脚本执行无意义,直接丢弃处理):

Token{value='var', lineNo=0, offset=0, type='Keyword'}
Token{value='a', lineNo=0, offset=4, type='Identifier'}
Token{value='=', lineNo=0, offset=6, type='SingleSymbol'}
Token{value='1', lineNo=0, offset=8, type='Integer'}
Token{value='+', lineNo=0, offset=10, type='SingleSymbol'}
Token{value='1', lineNo=0, offset=12, type='Integer'}
Token{value=';', lineNo=0, offset=13, type='SingleSymbol'}

2、语法分析器(开发完成)

1、token序列会经过语法解析器识别出文本中的各类短语。
2、根据语言的文法规则(rules of grammar)输出解析树,这棵树(AST:抽象语法树)是对代码的树形描述。文法就是用简单的符号解决了无穷语言的有穷表述问题。举个栗子:5 + (12 * 1)根据对应的文法生成的解析树为:
IMG

  • 语法规则由配置文件表达,这样做的好处是在实现脚本多语言时,只需要定义新文法配置文件,然后写对应的statementHandler即可。目前我们的脚本语法选择高度贴合JavaScript,如果未来想搞套其他语言的语法实现则会相对方便(例如.Net支持 C#、VB.Net、JS.Net 3种语法,但编译后生成的中间语言相同)。
  • 语法规则配置文件(上述的文法规则:rules of grammar)本身由语法分析器自身完成解析,语法规则配置文件如下,格式为一行一条数据,每行格式为:[规则名称]:规则表达式。规则表达式中?*+等符号含义同正则表达式对应含义(? 零次或一次;* 零次或多次;+ 一次或多次)
root : statementList? ;

statementList : statement+ ;

statement : variableStatement
    | expressionStatement
    ;

variableStatement : variableDeclarationList ';' ;

variableDeclarationList : 'var' variableDeclaration (',' variableDeclaration)*;

variableDeclaration : Identifier ('=' singleExpression)? ;

expressionStatement :  expressionSequence ';' ;

expressionSequence : singleExpression (',' singleExpression)* ;

arguments : '('(argument (',' argument)* ','?)?')' ;

argument : singleExpression | Identifier ;

singleExpression
    : singleExpression arguments                                            # ArgumentsExpression
    | singleExpression ('+' | '-') singleExpression                         # AdditiveExpression
    | singleExpression '=' singleExpression                                 # AssignmentExpression
    | singleExpression assignmentOperator singleExpression                  # AssignmentOperatorExpression
    | Identifier                                                            # IdentifierExpression
    | literal                                                               # LiteralExpression
    ;

assignmentOperator : '*=' | '/=' | '%=' | '+=' | '-=' | '&=' | '^=' | '|=';

literal : Null  | Boolean | String | MultiLineString | numericLiteral ;

numericLiteral : Float | Integer;

3、编译器

1、根据AST生成四元式。
2、序列化四元式(有一定压缩优化,如果满大街的很长的标识符,显然很容易对其优化),由于元组的编解码不是想阐述的核心内容,目前demo中没有实现完全(这半夜的我刚调试Demo完毕就来发博客,各位老铁,利用业余时间分享点东西我也不容易啊),后续再找时间把元组序列化实现完全把。。。

关于四元式举个栗子(当然现在大家基本上都是用带返回的用三元式去搞):

5 + 6 -> 四元式:

5 : p1
6 : p2
+ : operator
11 : result

4、执行器

1、 简单来说就是按照顺序执行四元式数组,当然还有jump。执行层面基本上就2种情况,顺序执行以及执行到某个位置之后跳到另外的位置。 我们可以想想for循环转换为do while、可以想想if/else的执行,基本上很多的语法都是在基础语法之上包装出来的“语法糖”。
2、 由于脚本引擎基于JAVA实现,运行时执行效率只能做到接近JVM,原则上可以做到1个量级左右的差别,运行时尽量避免String.Equal之类的东西,以及本身逻辑尽量避免无谓的性能损耗。这里只是一个原型Demo,在执行器中原始的用:operator.equals("xxx"),显然是不合适的。

测试代码及结果

测试代码:

public class ExpressionEngine {
    private ASTPattern astPattern;
    private ScriptTokenRegister tokenRegister;

    public ExpressionEngine() throws Exception {
        tokenRegister = new ScriptTokenRegister();
        astPattern = ASTPattern.compileWithFile("AstParser.conf");
    }

    /**
     * apply
     */
    public void apply(String script) throws Exception {
        List<Token> tokens = tokenRegister.getTokens(script);   // 1.词法分析
        System.out.println("---------Tokens-----------");
        tokens.forEach(token -> {
            System.out.println(token.toFullString());
        });

        System.out.println("---------AST Result-----------");   // 2.语法分析
        ASTMatcher astResult = astPattern.match(tokens);
        System.out.println(astResult.toString());


        System.out.println("---------Tuples-----------");
        MethodStack methodStack = AbstractStatementHandle.parseMethodStack(astResult);  // 3.根据AST生成四元式
        Arrays.asList(methodStack.getTuples()).forEach(tuple -> {
            System.out.println(tuple.toString());
        });

        System.out.println("---------apply-----------");
        TupleExecutor executor = new TupleExecutor(methodStack);
        executor.apply(null);       // 4.四元式执行器
    }

    public static void main(String[] args) throws Exception {
        String script = "var a = 1 + 1; printl(a);";
        ExpressionEngine expEngine = new ExpressionEngine();
        expEngine.apply(script);
  

执行结果:

---------Tokens-----------
Token{value='var', lineNo=0, offset=0, type='Keyword'}
Token{value='a', lineNo=0, offset=4, type='Identifier'}
Token{value='=', lineNo=0, offset=6, type='SingleSymbol'}
Token{value='1', lineNo=0, offset=8, type='Integer'}
Token{value='+', lineNo=0, offset=10, type='SingleSymbol'}
Token{value='1', lineNo=0, offset=12, type='Integer'}
Token{value=';', lineNo=0, offset=13, type='SingleSymbol'}
Token{value='printl', lineNo=0, offset=15, type='Identifier'}
Token{value='(', lineNo=0, offset=21, type='SingleSymbol'}
Token{value='a', lineNo=0, offset=22, type='Identifier'}
Token{value=')', lineNo=0, offset=23, type='SingleSymbol'}
Token{value=';', lineNo=0, offset=24, type='SingleSymbol'}
---------AST Result-----------
'root':['statementList':['statement':['variableStatement':['variableDeclarationList':[var, 'variableDeclaration':[a, =, 'singleExpression#AdditiveExpression':['singleExpression#LiteralExpression':['literal':['numericLiteral':[1]]], +, 'singleExpression#LiteralExpression':['literal':['numericLiteral':[1]]]]]], ;]], 'statement':['expressionStatement':['expressionSequence':['singleExpression#ArgumentsExpression':['singleExpression#IdentifierExpression':[printl], 'arguments':[(, 'argument':['singleExpression#IdentifierExpression':[a]], )]]], ;]]]]
---------Tuples-----------
QuaternionTuple{p1=1, p2=1, op=+, result=#0, end=false, lineNo=0}
QuaternionTuple{p1=a, p2=#0, op=:=, result=a, end=true, lineNo=0}
FunctionTuple{name='printl', arguments=[a], result=#1}
---------apply-----------
2

About

本Demo项目主要用于简单展示:1、词法分析器;2、语法分析器;3、脚本编译器;4、编译执行器,四个过程,因为他们是实现自定义脚本引擎的基础。该项目体现的也是all-in-one低代码平台中一分部分思想。

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages