编译原理2-语法分析-支持减除小括号
Posted coderlin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译原理2-语法分析-支持减除小括号相关的知识,希望对你有一定的参考价值。
语法分析-支持减法除法小括号
语法规则 加上减法除法规则就变了
add -> minus | minus + add // 加法被 减法 | 减法+加法替换
minus -> multiple | mulitple +|- add 减法被 乘法 或者 乘法加减加法
multiple -> primary | primary *|/ multiple 乘法 被 Number 或者 Number 乘除 乘法
primary -> NUMBER | (add) primary是括号 最下面,最先计算,优先级最高
全部代码:
// 只匹配0-9或者是+或者是X或者-或者/
const RegExpObject = /([0-9]+)|(\\+)|(\\*)|(-)|(\\/)|(\\()|(\\))/g;
const tokenTypes =
NUMBER: "NUMBER",
PLUS: "PLUS",
MULTIPLE: "MULTIPLE",
MINUS: "MINUS", //减法
DIVIDE: "DIVIDE",
LEFT_PARA: "LEFT_PARA", // (
RIGHT_PARA: "RIGHT_PARA", // )
;
const tokensName = [
tokenTypes.NUMBER,
tokenTypes.PLUS,
tokenTypes.MULTIPLE,
tokenTypes.MINUS,
tokenTypes.DIVIDE,
tokenTypes.LEFT_PARA,
tokenTypes.RIGHT_PARA,
];
//定义节点的类型
const ASTTypes =
Program: "Program",
Numeric: "Numeric",
Additive: "Additive",
Multiplicative: "Multiplicative",
Minus: "Minus", //-
Divide: "Divide", // 除
LEFT_PARA: "LEFT_PARA",
RIGHT_PARA: "RIGHT_PARA",
;
// 定义ast每个节点的类型
class ASTNode
constructor(type, value)
this.type = type;
if (value)
this.value = value;
this.children = [];
//添加在节点
appendChild(childNode)
this.children.push(childNode);
const token = type: "", value: "" ;
/**生成器函数 */
function* tokenizer(script)
while (true)
// 匹配过一次后继续往下匹配,第一次[2,2,undefined,undefined,...],
//第二次[+,undefined,+,undefined....],第三次[3,3,undefined,undefined...]
// 第四次 [*, undefined, undefined, *, ...]
const result = RegExpObject.exec(script);
if (!result)
break;
const index = result.findIndex((item, index) => index > 0 && item);
const token = type: tokensName[index - 1], value: result[0] ;
token.type = tokensName[index - 1];
//没执行一次函数,返回一个token
yield token;
class TokenReader
constructor(tokens)
this.tokens = tokens;
this.pos = 0;
//读取消耗token
read()
if (this.pos < this.tokens.length)
return this.tokens[this.pos++];
return null;
peek()
if (this.pos < this.tokens.length)
return this.tokens[this.pos];
return null;
//恢复
unread()
if (this.pos > 0)
this.pos--;
/**正则分词 */
function tokenize(script)
const tokens = [];
const it = tokenizer(script);
let isContinue = true;
while (isContinue)
const value, done = it.next();
if (value)
tokens.push(value);
isContinue = !done;
return new TokenReader(tokens);
/**
* 将jsx转成AST语法树
*/
function parse(code)
// 分词处理
const tokenReader = tokenize(code);
const ast = toAST(tokenReader);
const result = evaluate(ast);
console.dir(ast, depth: null );
console.log("result", result);
parse("4/(4-2)+3/(3+3)");
// (4/4) - (3+ (3/3))
/**
* 转换AST,实现语法分析推导过程
* 规则就两条,加法和乘法
add -> mulitple | multiple + add // 加法规则
multuple -> Number | Number * multuple // 乘法规则 , Number就是一个token,用来终结
//加上减法除法规则就变了
add -> minus | minus + add // 加法被 减法 | 减法+加法替换
minus -> multiple | mulitple +|- add 减法被 乘法 或者 乘法加减加法
multiple -> primary | primary *|/ multiple 乘法 被 Number 或者 Number 乘除 乘法
primary -> NUMBER | (add) primary是括号 最下面,最先计算,优先级最高
*/
function toAST(tokenReader)
//根节点
const rootNode = new ASTNode(ASTTypes.Program);
/**
* 开始推导,加法,乘法,先推导加法
* 实现的时候,函数additive对应加法规则
*/
const child = additive(tokenReader);
if (child)
rootNode.children.push(child);
return rootNode;
//递归下降算法,从加法,到乘法,到number
//加法,需要匹配减法或者减法+加法
function additive(tokenReader)
// 匹配minus,但是还没结束,还需要判断下一个token
const child1 = minus(tokenReader);
const token = tokenReader.peek();
let node = child1; //如果没匹配到+号,就是唯一的节点
if (child1 && token)
// 下一个字符如果是+号,就需要被 minus + add替换
if (token.type === tokenTypes.PLUS)
tokenReader.read(); //消耗+token
//准备递归,去匹配下一个
const child2 = additive(tokenReader);
if (child2)
//匹配到了+号,那么2+3就是同一层的节点
node = new ASTNode(ASTTypes.Additive);
node.appendChild(child1);
node.appendChild(child2);
return node;
//minus => multiple | mulitple +|- add
function minus(tokenReader)
// 匹配minus,但是还没结束,还需要判断下一个token
const child1 = multiple(tokenReader);
const token = tokenReader.peek();
let node = child1; //如果没匹配到+号,就是唯一的节点
if (child1 && token)
// 下一个字符如果是+|-号,就需要被 mulitple +|- add 替换
if (token.type === tokenTypes.MINUS || token.type === tokenTypes.PLUS)
tokenReader.read(); //消耗-token
//准备递归,去匹配下一个
const child2 = additive(tokenReader);
if (child2)
//匹配到了+/-号
node = new ASTNode(
token.type === tokenTypes.MINUS ? ASTTypes.Minus : ASTTypes.Additive
);
node.appendChild(child1);
node.appendChild(child2);
return node;
//乘法 multiple -> primary | primary *|/ multiple
function multiple(tokenReader)
// 判断multiple是被Number还是被Number+multiple替换
// 先匹配Numer,但是乘法匹配规则还没结束,得判断下一个token
const child1 = primary(tokenReader);
let node = child1; //没匹配*号,chld1就是单独的节点
//Number匹配成功
const token = tokenReader.peek();
if (child1 && token)
if (
token.type === tokenTypes.MULTIPLE ||
token.type === tokenTypes.DIVIDE
)
// TODO 匹配到了×号,所以Multiple要被Number*Mlutiple替换,所以3*4都是子节点了
tokenReader.read(); //消耗X号
//递归调用
const child2 = multiple(tokenReader);
if (child2)
// 匹配到了*号,那么child1和chil2就是同一层
node = new ASTNode(
token.type === tokenTypes.MULTIPLE
? ASTTypes.Multiplicative
: ASTTypes.Divide
);
node.appendChild(child1);
node.appendChild(child2);
return node;
// 括号 primary -> Number | (add)
function primary(tokenReader)
const child1 = number(tokenReader);
let node = child1;
if(!node)
const token = tokenReader.peek()
//匹配到小括号
if(token!==null && token.type === tokenTypes.LEFT_PARA)
tokenReader.read(); //去掉左括号
// 对于4/(4-2),因为(无法匹配number,所以走这个逻辑
// 去掉(后变成数字,继续走add逻辑,add匹配到4,后面是-,匹配到 muins -> multiple | mulitple +|- add的第二个multiple - add
// -去掉,继续走add(2),2匹配到了4,后面的右括号无法匹配,结束。所以继续走 tokenReader.read()去掉右括号
// 而这个primary规则就返回 4-2
node = additive(tokenReader) //这个node就是 type: "Minuse", children:[..value: 4, ...value: 2]
tokenReader.read() //去掉右括号
return node
//NUMBER
function number(tokenReader)
let node = null;
//看看当前的token
let token = tokenReader.peek();
//匹配到数字
if (token !== null && token.type === tokenTypes.NUMBER)
token = tokenReader.read(); //读取并消耗该token
//创建一个新的语法树节点
node = new ASTNode(ASTTypes.Numeric, token.value);
return node;
/**计算 */
function evaluate(node)
let result;
switch (node.type)
case ASTTypes.Program:
//根节点
for (let child of node.children)
result = evaluate(child);
break;
case ASTTypes.Additive:
// 加法节点,有两个children
result = evaluate(node.children[0]) + evaluate(node.children[1]);
break;
case ASTTypes.Numeric:
//如果是number
result = parseFloat(node.value);
break;
case ASTTypes.Minus:
result = evaluate(node.children[0]) - evaluate(node.children[1]);
break;
case ASTTypes.Divide:
result = evaluate(node.children[0]) / evaluate(node.children[1]);
break;
case ASTTypes.Multiplicative:
//乘法节点
result = evaluate(node.children[0]) * evaluate(node.children[1]);
break;
default:
break;
return result;
主要还是根据规则去一一匹配,通过递归下降算法,判断匹配的语法规则,调用对应的函数。这里的括号匹配主要的就是,primary这个函数,会将()里面的内容匹配出来,单独处理成一个节点,如(4-2),就处理成
type: MUINS, children: [type: Number, value:4, type: Number, value: 2]
children是优先计算的,所以括号的内容会优先计算,通过debugger可以了解是如何推导的。
例子:parse(“4/(4-2)+3/(3+3)”);;结果
ASTNode
type: 'Program',
children: [
ASTNode
type: 'Additive',
children: [
ASTNode
type: 'Divide',
children: [
ASTNode type: 'Numeric', value: '4', children: [] ,
ASTNode
type: 'Minus',
children: [
ASTNode type: 'Numeric', value: '4', children: [] ,
ASTNode type: 'Numeric', value: '2', children: []
]
]
,
ASTNode
type: 'Divide',
children: [
ASTNode type: 'Numeric', value: '3', children: [] ,
ASTNode
type: 'Additive',
children: [
ASTNode type: 'Numeric', value: '3', children: [] ,
ASTNode type: 'Numeric', value: '3', children: []
]
]
]
]
result 2.5
以上是关于编译原理2-语法分析-支持减除小括号的主要内容,如果未能解决你的问题,请参考以下文章
编译原理实战入门:用 JavaScript 写一个简单的四则运算编译器语法分析