陳鍾誠

Version 1.0

第 19 章 - 遞迴下降法 Recursive Descendent

遞迴下降法是用來《剖析語言》的一種方法,經常被用在《編譯器、自然語言處理》等領域。

要了解遞迴下降法必須先了解 BNF/EBNF 語法,請先閱讀以下文章!

關於《語言處理技術》更詳細的內容請參考我寫的以下書籍!

我們可以用《遞迴下降法》來產生語句,或者撰寫《編譯器程式》!

自動產生語句

接著請想想,這種程式該怎麼做呢?

讓我們用一個更簡單的例子示範,那就是自動產生數學運算式。

檔案: https://github.com/cccbook/algjs/blob/master/code/19-recursiveDescendent/genexp1.js

var R = require('./random')

// === BNF Grammar =====
// E = T [+-*/] E | T
// T = [0-9] | (E)

function print(s) {
  process.stdout.write(s);
}

function E() {
  if (R.randInt(1,10) <= 5) {
    T(); print(R.randChar("+-*/")); E();
  } else {
    T();
  }
}

function T() {
  if (R.randInt(1,10) < 7) {
    print(R.randChar("0123456789"));
  } else {
    print("("); E(); print(")");
  }
}

for (var i=0; i<10; i++) {
  E();
  print("\n");
}

執行結果

nqu-192-168-61-142:code mac020$ node genexp
4
0/0+(2)*9
4-(9)*((((3*(4))-(8))+(0)+8/(8)+2)+2/6)
3/(((((1*8+6)))))*((6/4/3))/(((2+9))+(((2))+8/((4*(5))*2))/4)
(1+(1))-((7))
(2+(((4))))+(5)
((1/(((3+(7)-(4-1)/9*8/7-6)/(4)-3+3)-6-9*(((2+(((6*4/4)))*(8/3))))-9-0-1+5*8*((5)/(3)-1/(1)-9)+(5+5*5))))*5/2
8
1
(0)*7

編譯數學運算式為中間碼

檔案: https://github.com/cccbook/algjs/blob/master/code/19-recursiveDescendent/compileExp.js

/* 語法
E = T ([+-/*] E)*
T = N | (E)

範例:3+(5*4)-2
*/

var c = console;

var tagMap={
  N : ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
  OP: ["+", "-", "*", "-"]
};

var wi = 0;

function isNext(tag) {
  if (words[wi] === tag) return true;
  var tagWords=tagMap[tag];
  if (typeof tagWords === "undefined") 
    return false;
  else
    return (tagWords.indexOf(words[wi])>=0);
}

function next(tag) {
  c.log("tag="+tag+" word="+words[wi]);
  if (isNext(tag)) {
    return words[wi++];
  }
  throw Error("Error !");
}

var tempIdx = 0;
var getTemp=function() {
    return tempIdx++;
}
// E = T ([+-/*] E)*
function E() {
  var t = T();
  while (isNext("OP")) {
    var op = next("OP");
    var e = E();
    var result = getTemp();
    console.log(" T%d=T%d%sT%d", result, t, op, e);
    t = result;
  }
  return t;
}

// T = N | (E)
function T() {
  if (isNext("N")) {
    var t = getTemp();
    var number = next("N");
    console.log(" T%d=%d", t, number);
    return t;
  } else {
    next("(");
    var e = E();
    next(")");
    return e;
  }
}

var words="3+(5*4)-2";
c.log("%j", words);
E(words);

執行

$ node compileExp
"3+(5*4)-2"
tag=N word=3
 T0=3
tag=OP word=+
tag=( word=(
tag=N word=5
 T1=5
tag=OP word=*
tag=N word=4
 T2=4
 T3=T1*T2
tag=) word=)
tag=OP word=-
tag=N word=2
 T4=2
 T5=T3-T4
 T6=T0+T5