两周自制脚本语言-第5天 设计语法分析器

第5天 设计语法分析器

5.1 Stone语言的语法

代码清单 5.1 Stone 语言的语法定义

    primary     : "(" expr ")"  | NUMBER | IDENTIFIER | STRING
    factor      : "-" primary | primary
    expr        : factor { OP factor }
    block       : "{" [ statement ] { (";" | EOL) [ statement ] } "}"
    simple      : expr
    statement   : "if" expr block [ "else" block ]
                | "while" expr block
                | simple
    program     : [ statement ] (";" | EOF)

5.2 使用解析器和组合子

Parser库: 一种解析器组合子类型的库 工作是将BNF写成的语法规则改写成Java语言程序 在书中第十七章有详细解说

代码清单 5.2 Stone 语言的语法分析器BasicParser.java

//代码清单5.2 由代码清单5.1中列出的Stone语言语法转换而成的语法分析程序。

/*
  A basic Parser for Stone grammatical analysis
 */
package stone;

import stone.Parser.Operators;
import stone.ast.*;

import java.util.HashSet;

import static stone.Parser.rule;

public class BasicParser {
    HashSet<String> reserved = new HashSet<String>();
    Operators operators = new Operators();
    Parser expr0 = rule();
    Parser primary = rule(PrimaryExpr.class)
        .or(rule().sep("(").ast(expr0).sep(")"),
            rule().number(NumberLiteral.class),
            rule().identifier(Name.class, reserved),
            rule().string(StringLiteral.class));
    Parser factor = rule().or(rule(NegativeExpr.class).sep("-").ast(primary),
                              primary);
    Parser expr = expr0.expression(BinaryExpr.class, factor, operators);

    Parser statement0 = rule();
    Parser block = rule(BlockStmnt.class)
        .sep("{").option(statement0)
        .repeat(rule().sep(";", Token.EOL).option(statement0))
        .sep("}");
    Parser simple = rule(PrimaryExpr.class).ast(expr);
    Parser statement = statement0.or(
            rule(IfStmnt.class).sep("if").ast(expr).ast(block)
                               .option(rule().sep("else").ast(block)),
            rule(WhileStmnt.class).sep("while").ast(expr).ast(block),
            simple);

    Parser program = rule().or(statement, rule(NullStmnt.class))
                           .sep(";", Token.EOL);

    public BasicParser() {
        reserved.add(";");
        reserved.add("}");
        reserved.add(Token.EOL);

        operators.add("=", 1, Operators.RIGHT);
        operators.add("==", 2, Operators.LEFT);
        operators.add(">", 2, Operators.LEFT);
        operators.add("<", 2, Operators.LEFT);
        operators.add("+", 3, Operators.LEFT);
        operators.add("-", 3, Operators.LEFT);
        operators.add("*", 4, Operators.LEFT);
        operators.add("/", 4, Operators.LEFT);
        operators.add("%", 4, Operators.LEFT);
    }
    public ASTree parse(Lexer lexer) throws ParseException {
        return program.parse(lexer);
    }
}

Parser类与Operators类都是由库提供的类。

rule方法是Parser类中的一个static方法

primary字段的定义基于非终结符primary的语法规则。factor与block同理,都是相应的Java语言形式的语法规则。

终结符:不能单独出现在推导式左边的符号,也就是说终结符不能再进行
推导。

非终结符: 不是终结符的都是非终结符。非终结符可理解为一个可拆分元素,而终结符是不可拆分的最小元素。

表5.1 Parser类的方法

语法规则的处理

paren : "(" expr ")"

转换为Java语言后将得到下面的代码

Parser paren = rule().sep("(").ast(expr).sep(")");

factor : "-" primary | primary

对应factor字段的定义如下

Parser factor = rule().or(rule().sep("-").ast(primary), primary);

expr : factor { OP factor }

=>

Parser expr = expr0.expression(BinaryExpr.class, factor, operators);

运算符表以Operators对象的形式保存,它是expression方法的第三个参数。

operators.add("=", 1, Operators.RIGHT);
// Operator.RIGHT 右结合
// Operator.LEFT 左结合

? add方法的参数分别是用于表示运算符的字符串、它的优先级以及左右结合顺序。用于表示优先级的数字是一个从1开始的int类型数值,该值越大,优先级越高。

5.3 由语法分析器生成的抽象语法树

Parser对象的parse方法将在成功执行语法分析后以抽象语法树的形式返回分析结果。

语法规则

adder: NUMBER "+" NUMBER

改写为Java语言

Parser adder = rule().number().token("+").number();

以token添加 + 号

Parser adder = rule(BinaryExpr.class).number(NumberLiteral.class)
.token("+")
.number(NumberLiteral.class)

使用sep向模式添加分隔符

Parser adder = rule().number().sep(“+”).number();

ast方法向模式添加非终结符。

Parser eq = rule().ast(adder).token("==").ast(adder);

特殊的规定:如果子节点只有一个,Parser库将不会另外创建一个额外的节点。

规定不适用于rule方法的参数接收了一个类的情况
创建以NegativeExpr对象为根的子树

rule(NegativeExpr.class).sep("-").ast(primary)

如果希望在rule方法接受参数时也应用这条特殊规则,需要想下面这样作为参数的类定义签名方法

public static ASTree create(List<ASTree> c) {
        return c.size() == 1 ? c.get(0) : new PrimaryExpr(c);
}

将非终结符progrm的语法规则改写成Java语言

program : [ statement ] ("," | EOF)
Parser program = rule().or(statement, rule(NullStmnt.class))
.sep(";",Token.EOF);

5.4 测试语法分析器

知识补充

什么是DSL

简单查询了DSL,发现还是挺重要的 有时间详细学习下

Wikipedia 对于 DSL 的定义

A specialized computer language designed for a specific task.

为了解决某一类任务而专门设计的计算机语言。

DSL 是 Domain Specific Language 的缩写,中文翻译为领域特定语言(下简称 DSL);而与 DSL 相对的就是 GPL.

GPL 是 General Purpose Language 的简称,即通用编程语言,也就是我们非常熟悉的 Objective-C、Java、Python 以及 C 语言等等。

DSL 通过在表达能力上做的妥协换取在某一领域内的高效

参考

https://www.cnblogs.com/feng9exe/p/10901595.html

https://zhuanlan.zhihu.com/p/22824177

原文地址:https://www.cnblogs.com/ZCWang/p/12207520.html

时间: 2024-09-29 05:50:46

两周自制脚本语言-第5天 设计语法分析器的相关文章

两周自制脚本语言-第9天 设计面向对象语言

第9天 设计面向对象语言 目标:为Stone语言添加类和对象的支持.仅支持单一继承 9.1 设计用于操作类与对象的语法 添加的类与对象的处理功能后,下面的Stone语言就能被正确执行了 class Position { x = y = 0 def move(nx,ny) { x = nx; y = ny } } p = Position.new p.move(3, 4) p.x = 10 print p.x + p.y 首先定义一个Position类,方法由def语句定义.类中字段通过变量表示,

两周自制脚本语言-第7天 添加函数功能

第7天 添加函数功能 基本的函数定义与调用执行.引入闭包使Stone语言可以将变量赋值为函数,或将函数作为参数传递给其他函数 有些函数将有返回值的归为函数,没有返回值的归为子程序 7.1 扩充语法规则 函数定义语句的语法规则 此书将函数定义语句称为def语句.def语句仅能用于最外层代码,用户无法在代码块中定义函数 Stone语言将最后执行语句(表达式)的计算结果将作为函数的返回值返回 代码清单 7.1 与函数相关的语法规则 param : IDENTIFIER params : param {

两周自制脚本语言-第10天 无法割舍的数组

第10天 无法割舍的数组 目标:为Stone语言添加简单的数组功能,下标(index)只能使用整数值. 10.1扩展语法分析器 代码清单10.1 与数组相关的语法规则 elements : expr { "," expr } primary : ( "[" [ elements ] "]" | "(" expr ")" | NUMBER | IDENTIFIER | STRING ) { postfix }

两周自制脚本语言-第6天 通过解释器执行程序

第6天 通过解释器执行程序 解释器从抽象语法树的根节点开始遍历该树直至叶节点,并计算各节点的内容 6.1 eval方法与环境对象 eval方法:eval是evaluate(求值)的缩写.eval方法将计算与该节点为根的子树对应的语句.表达式及子表达式,并返回执行结果. eval方法递归调用子节点的eval方法 不同类型的节点的类,对eval方法有着不同的定义 eval方法的简化版本 public Object eval(Environment env){ Object left = left()

两周自制脚本语言-第11天 优化变量读写性能

第11天 优化变量读写性能 以变量值的读写为例,向读者介绍基于这种理念的语言处理器性能优化方式. 11.1 通过简单数组来实现环境 假如函数包含局部变量x与y,程序可以事先将x设为数组的第0个元素,将y设为第1个元素,以此类推.这样一来,语言处理器引用变量时就无需计算哈希值.也就是说,这是一个通过编号,而非名称来查找变量值的环境 为了实现这种设计,语言处理器需要在函数定义完成后遍历对应的抽象语法树节点,获取该节点使用的所有函数参数与局部变量.遍历之后程序将得到函数中用到的参数与局部变量的数量,于

Razor Engine,动态脚本语言,mvc上的语法,适用于文件内容生成,静态网页生成等。

https://razorengine.codeplex.com/ 下载并引用:razorengine.dll 代码里这样写,用mvc的人都会!Razor语法! string template = "Hello @Model.Name! Welcome to Razor!"; string result = Razor.Parse(template, new { Name = "World" });

L脚本语言语法手册 0.10版

L脚本语言语法手册 0.10版 赵亮       简  介 L脚本语言是一个轻量级的,旨在接近自然语言的编程语言,目前支持在中文.英文基础上的编程.并可扩展为任意语种.L脚本语言的语法结构简单,程序结构相对松散,易学易用. 目前L脚本语言仍处于开发初期,功能尚不完善.目前提供了一个简单的源码编辑器,建议使用notepad++或者ultraedit进行源码编辑. 目录 一.        介绍.. 2 二.        注释.. 3 三.        对象定义和引用.. 3 四.       

javascript(脚本语言)

javascript(脚本语言)一.注释语法:1.单行注释 //注释内容2.多行注释 /*注释内容*/二.输出语法js语言格式,尽量靠下写,属双标签<script type=”text/javascript”>中间需要写的内容</script>? alert(“……”),显示信息对话框,括号里是要弹出的信息,例如,alert(“hello”),输出hello.? confirm(“……”),交互对话框,用户可以点击确定或者取消,括号里是要弹出的信息.经常用在需要用户确定的地方.?

PHP_认识脚本语言

一.什么是脚本语言: 大多数网站开发都是使用脚本语言,它是使用一种特定的描述性语言,依据一定的格式编写的可执行文件. 脚本是批处理文件的延伸,是一种纯文本保存的程序,一般来说的计算机脚本程序是确定的一系列控制计算机进行运算操作动作的组合,在其中可以实现一定的逻辑分支等. 脚本简单地说就是一条条的文字命令,这些文字命令是可以看到的(如可以用记事本打开查看.编辑),脚本程序在执行时,是由系统的一个解释器,将其一条条的翻译成机器可识别的指令,并按程序顺序执行. 因为脚本在执行时多了一道翻译的过程,所以