• 使用antlr4及java实现snl语言的解释器


    对于antlr4的基础使用,请参考我的前一篇文章《用antlr4来实现<按编译原理的思路设计的一个计算器>中的计算器》。

    其实我对于antlr4的理解也仅限于那篇文章的范围,但那些内容对于我们实现一个SNL语言已是足够了。

     

    SNL语言的简介可以百度百科(http://baike.baidu.com/subview/2298006/5909410.htm)中看到。

    简单说来,这是一个非常简单的结构化编程语言,它包含了一个编程语言应该有的最基本的东西,同时基本上不包含其它的所谓先进的元素。

     

    首先贴一下文法(我对原始的文法做了一些调整,如:原始的文法中if语句的else部分不是可选的,原始文法中record类型内部只能有基础类型或array类型,等等。但我所做的这些调整都是比较边缘的,以下文法基本上保持了SNL语言的原貌):


    grammar Snlc;

    @header{package net.yeah.zhouyou.mickey.snl.antlr4;}

    program: programHead declarePart programBody? ;

    programHead: PROGRAM programName=ID ;
    declarePart: typeDecpart? varDecpart? procDecpart? ;

    typeDecpart: TYPE typeDecList ;
    typeDecList: typeId=ID ':=' typeDef ';' typeDecList? ;

    typeDef: basicType
           | structureType
           | ID
           ;
    basicType: INTEGER | CHAR ;
    structureType: arrayType
                 | recordType
                 ;
    arrayType: ARRAY '[' low=INTC '..' top=INTC ']' OF basicType ;
    recordType: RECORD fieldDecList END ;
    fieldDecList: typeDef idList ';' fieldDecList? ;
    idList: ID (',' idList)? ;

    varDecpart: VAR varDecList ;
    varDecList: typeDef idList ';' varDecList? ;

    procDecpart: procDeclare procDecpart? ;
    procDeclare: PROCEDURE procName=ID '(' paramList? ')' ';'
                 declarePart programBody ;
    paramList: param (';' paramList)? ;
    param: VAR? typeDef idList ;

    programBody: BEGIN stmList END ;
    stmList: stm (';' stmList)? ;
    stm: conditionalStm
       | loopStm
       | inputStm
       | outputStm
       | assignment
       | callStm
       | RETURN
       ;
    assignment: variable ':=' exp ;
    callStm: ID '(' actParamList? ')' ;
    conditionalStm: IF condExp THEN stmList (ELSE stmList)? FI ;
    loopStm: WHILE condExp DO stmList ENDWH ;
    inputStm: READ '(' invar=ID ')' ;
    outputStm: WRITE '(' exp ')' ;
    actParamList: exp (',' actParamList)? ;
    condExp: exp (cmpOp=(EQ | LT) exp)? ;
    exp: exp op=(MUL | DIV) exp
       | exp op=(ADD | SUB) exp
       | factor
       ;
    factor: '(' exp ')'
          | intVal=INTC
          | variable
          ;
    variable: ID variMore? ;
    variMore: '[' exp ']'
            | DOT ID variMore?
            ;

    //
    PROGRAM: 'program' ;
    TYPE: 'type';
    INTEGER: 'integer' ;
    CHAR: 'char' ;
    ARRAY: 'array' ;
    OF: 'of' ;
    RECORD: 'record' ;
    BEGIN: 'begin' ;
    END: 'end' ;
    PROCEDURE: 'procedure' ;
    VAR: 'var' ;
    IF: 'if' ;
    THEN: 'then' ;
    ELSE: 'else' ;
    WHILE: 'while' ;
    DO: 'do';
    ENDWH: 'endwh' ;
    READ: 'read' ;
    WRITE: 'write' ;
    RETURN: 'return' ;
    FI: 'fi' ;
    ID: [a-zA-Z][a-zA-Z0-9]* ;
    INTC: [0-9]+ ;
    ADD: '+' ;
    SUB: '-' ;
    MUL: '*' ;
    DIV: '/' ;
    EQ: '=' ;
    LT: '<' ;
    DOT: '.' ;
    DDOT: '..' ;
    AMGT: ':=' ;
    SEM: ';' ;
    COMMA: ',' ;
    LBT: '(' ;
    RBT: ')' ;
    LSBT: '[' ;
    RSBT: ']' ;
    WS: [ \t\n\r]+ -> skip;


    读懂这个文法就完全清楚了这门语言的语法结构了,大家可以直接用它来开始开发了,对于语义的部分,大家猜都能猜得出个大概。

    但是文法对于学习一个语言来说是比较抽象的,虽然大家都能看懂,但过于理论化,所以我写几个例子贴在下面。

     

    例一,以下程序演示了参数传递,调用proc时传入三个参数1,2,3,在proc内部打印这三个参数的值到控制台——如果我们实现的没有问题,则它应该可以正确打印出这三个数字:


    program test
        procedure proc(integer a, b; integer c);
        begin
            write(a);
            write(b);
            write(c)
        end
    begin
        proc(1, 2, 3)
    end


    例二,以下程序中创建了一个整数数组,这个数组的下标是从6到8,共三个值(或三个引用),然后分写或读数组的每个元素。


    program test
        var array[6..8] of integer a;
    begin
        a[6] := 1;
        a[7] := 2;
        a[8] := 3;
       
        write(a[6]);
        write(a[7]);
        write(a[8])
    end


    例三,以下程序定义了一个过程f,当调用这个过程中,会打印当前的参数,并把参数+1传入下一次的递归调用中。如果没有实现“尾递归优化”,则这个程序的调用可预期的情况是“堆栈溢出”。


    program recursion
        procedure f(integer d);
        begin
            write(d);
            f(d + 1)
        end
    begin
        f(1)
    end


    例四,以下程序用来表示引用传参。我们把t做为参数传到ref过程中,过程中对参数n做了一些动作,结束这个过程后打印t——如果我们的实现中,把实参t传给形参n是值传递的,则最终会打印出5(因为过程内部无论做了什么都改变不了实参);如果我们的实现中,把实参t传给形参n是引用传递的,则n和t在语义上就是同一个对象,则作用于n的都会在t上有所体现。

     

    在我的实现中,它会打印出3,因为我把参数传递实现为引用传递,但把 := 实现为值传递。


    program test
        var integer t;
        procedure ref(integer n);
            var integer i;
        begin
            i := n;
            n := 3;
            i := 1
        end
    begin
        t := 5;
        ref(t);
        write(t)
    end


    例五,以下程序中的factorial过程用于计算n的阶乘,因为我实现的是引用传参,所以我们可以用参数从过程中返回结果。如果我们不设计成引用传参,则需要使用全局变量来传递数据了。


    program test
        var integer res;
        procedure factorial(integer n, r);
        begin
            r := 1;
            factorial_recursion(n, r)
        end
        procedure factorial_recursion(integer n, r);
        begin
            if 1 < n then
                r := r * n;
                factorial_recursion(n - 1, r)
            fi
        end
    begin
        factorial(10, res);
        write(res)
    end


    例六,以下程序用来测试类型定义的功能(其实是一个挺无聊的测试用例)


    program test
        type
            int := integer;
            array1to20 := array[1..20] of char;
            recordtype1 := record
                int a, b;
                array1to20 c;
            end;
            recordtype2 := record
                recordtype1 v;
                char c;
            end;
        var recordtype2 v;
            array1to20 v2;
    begin
        v.v.a := 1;
        write(v.v.a);
       
        v2[1] := 65;
        write(v2[1]);
       
        v.v.c[1] := 68;
        write(v.v.c[1]);

        v.c := 66;
        write(v.c)
    end


    例七,以下程序实现了一个冒泡排序(这是教材上给出的例子):


    program bubble
        var integer i, j, num;
            array [1..20] of integer a;

        procedure q(integer num);
            var integer i, j, k;
                integer t;
        begin
            i := 1;
            while i < num do
                j := num - i + 1;
                k := 1;
                while k < j do
                    if a[k + 1] < a[k] then
                        t := a[k];
                        a[k] := a[k + 1];
                        a[k + 1] := t
                    else
                        t := 0
                    fi;
                    k := k + 1
                endwh;
                i:= i + 1
            endwh
        end
    begin
        read(num);
        i := 1;
        while i < (num + 1) do
            read(j);
            a[i] := j;
            i := i + 1
        endwh;
        q(num);
        i := 1;
        while i < (num + 1) do
            write(a[i]);
            i := i + 1
        endwh
    end.


    经过以上的介绍,大家应该对SNL这个语言比较理解了,下面我选择这个解释器最重要的部分介绍一下开发思路。

    一、堆栈

    ………………

     

    二、上下文(符号表)

    ………………

     

    三、引用

    ………………

     

    四、类型

    ………………

    -------------------------------------------------------------------

    周游[http://www.cnblogs.com/naturemickey]版权所有,未经许可请勿转载

  • 相关阅读:
    UVa 1252 20个问题
    HDU 2196 Computer
    HDU 1520 Anniversary party
    HDU 2066 一个人的旅行
    UVa 10048 噪音恐惧症(Floyd)
    UVa 247 电话圈(Floyd传递闭包)
    HDU 2544 最短路(Dijkstra)
    HDU 1548 A strange lift (Dijkstra)
    UVa 1151 买还是建
    UVa 1395 苗条的生成树(Kruskal+并查集)
  • 原文地址:https://www.cnblogs.com/naturemickey/p/3774753.html
Copyright © 2020-2023  润新知