Scalaでパーサーを作ってみる〜5:文字列とprintln

Scalaの勉強をはじめたので、とりあえず簡単なパーサーを作ってみてます。
http://d.hatena.ne.jp/nowokay/20111101#1320102262


前回はif式で条件分岐してみました。
http://d.hatena.ne.jp/nowokay/20111106#1320542912


ここいらで計算過程を表示できるように、文字列とprintlnを導入しておきます。「+」演算子で文字列の連結ができるようにします。


それぞれの構文要素のためのケースクラスを用意します。

case class StringVal(value: String) extends AST
case class PrintLine(value: AST) extends AST

こんな感じで、さも「ケースクラスについてもう理解したぜ」風に書いてますが、まだ言葉に振り回されてる気もします。


それはいいとして。


それぞれ構文規則を定義します。今回、文字列はダブルクオートでくくりますが、使える文字はアルファベットと数字と記号いくつかくらいにしておきます。めんどいから。

  def stringLiteral : Parser[AST] = "\""~>"""[a-zA-Z0-9:*/+\- ]*""".r<~"\""^^{
    value => StringVal(value)
  }
  def printLine: Parser[AST] = "println"~"("~>expr<~")"^^{
    value => PrintLine(value)
  }


文字列リテラルはfactorのところに入れときます。

  //factor ::= intLiteral | "(" expr ")"
  def factor: Parser[AST] = intLiteral | stringLiteral | "("~>expr<~")"^^{


printlnは一番最初に入れときます。

  def expr: Parser[AST] = condOp|ifExpr|printLine


あとは、それぞれの評価の処理を行います。printlnは、表示した値をそのまま返すようにしておきます。

      case StringVal(value) => value
      case PrintLine(value) => {
          val v = visit(value);
          println(v); 
          v
      }


それと、「+」演算子を文字列に対応させておきます。どちらかが文字列なら文字列の連結です。けど、まあそのまま足し算になりますね。

      case AddOp(left, right) =>{
          (visit(left), visit(right)) match{
            case (lval:Int, rval:Int) => lval + rval
            case (lval:String, rval) => lval + rval
            case (lval, rval:String) => lval + rval
          }
      }


さてこんな感じで、式を与えてみます。

    val expr = "\"result: \"+(3+(if (3 < 5) println(12 * 2) else if(3+2) 15 + 3 else 0))"


表示されました!

24
result: 27


というかなんか、表示されると面白い!
副作用ばんざい!


ソースはこんな感じになってます。
そろそろどっかに置いたほうがいいですかね。

package miniparser

import scala.util.parsing.combinator.RegexParsers

object Main {

  def main(args: Array[String]): Unit = {
    val expr = "\"result: \"+(3+(if (3 < 5) println(12 * 2) else if(3+2) 15 + 3 else 0))"//"12-5*3+7*(2+5)*1+0"
    
    val parser = new MiniParser
    val ast = parser.parse(expr).get

    val visitor = new ExprVisitor
    var result = visitor.visit(ast);
    
    println(result)
  }
}

class ExprVisitor{
  def visit(ast:AST):Any = {
    ast match{
      case IfExpr(cond, pos, neg) =>{
          visit(cond) match{
            case true => visit(pos)
            case false => visit(neg)
          }
      }
      case LessOp(left, right) =>{
          (visit(left), visit(right)) match{
            case (lval:Int, rval:Int) => lval < rval
          }
      }
      case AddOp(left, right) =>{
          (visit(left), visit(right)) match{
            case (lval:Int, rval:Int) => lval + rval
            case (lval:String, rval) => lval + rval
            case (lval, rval:String) => lval + rval
          }
      }
      case SubOp(left, right) =>{
          (visit(left), visit(right)) match{
            case (lval:Int, rval:Int) => lval - rval
          }
      }
      case MulOp(left, right) =>{
          (visit(left), visit(right)) match{
            case (lval:Int, rval:Int) => lval * rval
          }
      }
      case IntVal(value) => value
      case StringVal(value) => value
      case PrintLine(value) => {
          val v = visit(value);
          println(v); 
          v
      }
    }
  }
}

trait AST
case class IfExpr(cond:AST, pos:AST, neg:AST) extends AST
case class LessOp(left: AST, right:AST) extends AST
case class AddOp(left: AST, right:AST) extends AST
case class SubOp(left: AST, right:AST) extends AST
case class MulOp(left: AST, right:AST) extends AST
case class StringVal(value: String) extends AST
case class PrintLine(value: AST) extends AST
case class IntVal(value: Int) extends AST

class MiniParser extends RegexParsers{
  //expr ::= cond | if
  def expr: Parser[AST] = condOp|ifExpr|printLine
  //if ::= "if" "(" expr ")" expr "else" expr
  def ifExpr: Parser[AST] = "if"~"("~>expr~")"~expr~"else"~expr^^{
    case cond~_~pos~_~neg => IfExpr(cond, pos, neg)
  }
  //cond ::= add {"<" add}
  def condOp: Parser[AST] = chainl1(add, 
    "<"^^{op => (left:AST, right:AST) => LessOp(left, right)})
  //add ::= term {"+" term | "-" term}.
  def add:  Parser[AST] = chainl1(term,
    "+"^^{op => (left:AST, right:AST) => AddOp(left, right)}|
    "-"^^{op => (left:AST, right:AST) => SubOp(left, right)})
  //term ::= factor {"*" factor}
  def term : Parser[AST] = chainl1(factor, 
    "*"^^{op => (left:AST, right:AST) => MulOp(left, right)})
  //factor ::= intLiteral | "(" expr ")"
  def factor: Parser[AST] = intLiteral | stringLiteral | "("~>expr<~")"^^{
    x=>x}
  //intLiteral ::= ["1"-"9"] {"0"-"9"}
  def intLiteral : Parser[AST] = """[1-9][0-9]*|0""".r^^{
    value => IntVal(value.toInt)}
  def stringLiteral : Parser[AST] = "\""~>"""[a-zA-Z0-9:*/+\- ]*""".r<~"\""^^{
    value => StringVal(value)
  }
  def printLine: Parser[AST] = "println"~"("~>expr<~")"^^{
    value => PrintLine(value)
  }

  def parse(str:String) = parseAll(expr, str)
}