サンプルプログラム9(PL JavaScript編)

初歩的な電卓(3)

メインプログラム

構文解析(2):優先度のある数式を扱う

Array.prototype.parse=function(){
  var stk=[]
  function parse1(tk){
     // console.log('parse1:',tk,stk)
     switch(typeof tk) {
       case 'number': case 'string':
         stk.push(tk) ; break
       case 'symbol' :    // 演算子
         let op1=stk[stk.length-2]      // スタックの上から2番目
         // console.log('op1=',op1)
         if(prior(tk)>prior(op1)) stk.push(tk)
         else {
           var r=stk.pop(), op=stk.pop(), l=stk.pop()
           stk.push(new Node(op,l,r))
           parse1(tk)
         }
         break;
       default: console.log(`unknown token ${tk.toString()}`)
     }
  // return nothing
  }
  // トークン列(末尾にENDマークを追加しておいて)を前から順にparse1に渡す
  [...this,sym('END')].forEach(e=>parse1(e))
  // スタックの上から2番目に残るデータが計算結果となる
  return stk[stk.length-2]      // or   stk[0]
}

ここまでに示したプログラムを1つにまとめたソースコードをここに置いた

さらに改良

  1. 括弧への対応
      if(! op1 || tk==sym('(') ||  // 条件を増やした(括弧を扱うため) ***
           prior(tk)>prior(op1) ) stk.push(tk)
      else if(op1==sym('(') && tk==sym(')') ) {
                     // 対向する括弧の間に要素が1つ  ***
        let r=stk.pop(),  _=stk.pop()
        stk.push(r)  // op1 は破棄し、要素だけをスタックに戻す ***
      } else {
         //   ... 3つ組を縮約するフェーズは、変更なし
      }
  2. 変数を使う
  3. 若干の不都合の解消

寄り道3 再帰の例

  1. 階乗

    const fact=(n)=>n<=1?1:n*fact(n-1)
    // または
    function fact(n) {
      if(n<=1) return n
      else return n*fact(n-1)
    }
  2. アッカーマン関数

    var x, y, test, count=0
    function ack(x,y) {
      test>0 && (count+=1) 
      test>1 && console.log(`ack(${x},${y})`)
      if(x==0) return y+1
      else if(y==0) return ack(x-1,1)
      else return ack(x-1,ack(x,y-1))
    }
    var args=process.argv.slice(2)
    console.log(args)
    x=parseInt(args.shift||'3')
    y=parseInt(args.shift||'3')
    test=parseInt(args.shift||'0')
    console.log(`ack(${x},${y})=${ack(x,y)}`)
    test>0 && console.log(`(${count} times)`)

    (ソースはここ)