编译原理 #03# 龙书中缀转后缀(JavaScript实现)
// 来自龙书第2章2.5小节-简单表达式的翻译器
笔记
既然是语法制导翻译(Syntax-directed translation),那么最重要的东西当然是描述该语言语法的文法,以下为中缀表达式文法(仅由+-以及0~9的数字构成):
expr -> expr + term | expr - term | term term -> 0~9的数字
接下来考虑如何利用该文法将原语言转化为后缀形式,此时可以脑补一下该文法的语法分析树(parse tree),例如:
严格来说,语法分析树是相对于某特定终结符号串生成的,叶子结点必须是终结符号,但是方便起见就省略了。expr1是expr的某个实体,便于和它的父结点区分。如果有能力构造这棵树的话,只需按照某种遍历求值顺序,很容易可以得到“expr->expr+term”到其后缀形式的语法制导定义(syntax-directed definition):
expr.后缀形式 -> expr1.后缀形式 term.后缀形式 +
龙书里把这种简单形式的语法制导定义称为简单语法制导定义(simple syntax-directed definition)。
一个语法制导定义把①每个文法符号和一个属性集合相关联,例如这里把expr和属性“后缀形式”相关联,并且把②每个产生式和一组语义规则(semantic rule)相关联,例如把“expr->expr+term”和“expr.后缀形式 -> expr1.后缀形式 term.后缀形式 +”相关联。
依据上面的语法制导定义我们可以得到一个语法制导翻译方案( syntax-directed translation scheme),也就是在文法产生式中附加一些程序片段来描述翻译结果的表示方法,被嵌入到产生式体中的程序片段称为语义动作(semantic action):
expr -> expr1 + term {print('+')} term -> 数字 {打印该数字}
但是这个语法制导翻译方案没法直接写成代码,因为会发生左递归的问题:
function expr() { expr() ... }
消除左递归(该过程需特别小心!详见书)得到:
expr -> term rest rest -> + term {print('+')} rest
代码有两点简化:
- 将rest函数的尾递归替换为迭代过程
- 将修改后的rest函数并入expr函数
截图与代码
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <link href="https://fonts.googleapis.com/css?family=Noto+Serif+SC" rel="stylesheet"> <style> main { /*对子元素开启弹性布局*/ display: flex; /*弹性元素在必要的时候换行*/ flex-wrap: wrap; /*将弹性元素居中*/ justify-content: center; } textarea, button { font-family: 'Noto Serif SC', STFangSong, serif; font-size: 17px; } </style> </head> <body> <main> <textarea name="input" rows="20" cols="40"></textarea> <textarea name="output" rows="20" cols="40"></textarea> <button name="execute">Execute</button> </main> <script> let inputBox = document.querySelector("textarea[name=input]"); let outputBox = document.querySelector("textarea[name=output]"); let btnExecute = document.querySelector("button[name=execute]"); btnExecute.addEventListener("click", event => { startParsing(inputBox.value); });
function startParsing(s) { str = s; cur = 0; result = ""; expr(); outputBox.value = result; } function expr() { term(); while (true) { if (str[cur] == '+') { match('+'); term(); result += '+'; } else if (str[cur] == '-') { match('-'); term(); result += '-'; } else { return; } } } function term() { if (/[0-9]/.test(str[cur])) { result += str[cur]; match(str[cur]); } else { report("存在语法错误,字符位置为:" + cur); } } function match(ch) { if (cur < str.length && str[cur] === ch) ++cur; else report("存在语法错误,字符位置为:" + cur); } function report(s) { outputBox.value = s; throw new Error(s); } </script> </body> </html>