LoginSignup
0
0

More than 5 years have passed since last update.

Scalaに代数型(datatype)を実装する その3

Last updated at Posted at 2013-07-13

構文解析器

次は、構文解析器です。

「Scalaに代数型(datatype)を実装する その1」で書いたように、
トップレベルとそれ以外にわけて考えていきます。

その前に、datatype文を解析するメソッドdatatypeDef()について、
ちょっと考えてみましょう。

datatypeDef()は、

 datatype Week = Mon | Tue | Wed | Thi | Fri | Sat | Sun

のような文を解析して、

sealed abstract class Week()
case class Mon() extends Week
case class Tue() extends Week
case class Wed() extends Week
case class Thi() extends Week
case class Fri() extends Week
case class Sat() extends Week
case class Sun() extends Week

のように内部で変換する方針です。
ですので、メソッドdatatypeDef()の返り値は複数のclass定義のリストになります。

class定義を解析するメソッドclassDef()などは、返り値の型はTreeですが、datatypeDef()の返り値の型はList[Tree]になるでしょう。
ただし、List[Tree]を返すのだと、classDef()と同じ場所で使うわけにはいかなくなります。

もちろん、クラスDatatypeDef()みたいなTree型の子クラスを作って返してやればいいのですが(意味解析やバックエンドも作り込むならこの方法がベストでしょう)、今回は楽をするため、List[Tree]を返すことにします。

では、どうすればいいのかというと、再帰降下法で構文解析をしている途中で、List[Tree]を返すメソッドがあるはずですので、そこに変更を加えていきます。

トークンのタイプ

最初に、トークン'datatype'がきちんと認識されるように、
メソッドisDclIntroに変更を加えます。

isTemplateIntroのほうにしなかったのはなんとなく違う気がしたから。

Parsers.scala
    def isDclIntro: Boolean = in.token match {
      case VAL | VAR | DEF | TYPE | DATATYPE => true
      case _ => false
    }

case文に、DATATYPE を追加しています。

トップレベル

トップレベルの処理は、メソッドtopStatSeq()からメソッドtopLevelTmplDefを呼び出して処理されているんでしたね。

・topStatSeq()の返り値の型が、List[Tree]。
・topLevelTmplDefの返り値の型が、Tree。

ですので、前述の方針にしたがって、datatypeDef()は、topStatSeq()から呼び出すようにしてやればいいでしょう。

Parsers.scala
    def topStatSeq(): List[Tree] = {
      val stats = new ListBuffer[Tree]
      while (!isStatSeqEnd) {
        stats ++= (in.token match {
          case PACKAGE  =>
            packageOrPackageObject(in.skipToken()) :: Nil
          case IMPORT =>
            in.flushDoc
            importClause()
          case x if x == AT || isTemplateIntro || isModifier =>
            joinComment(topLevelTmplDef :: Nil)
          case DATATYPE =>
            val annots = annotations(skipNewLines = true)
            val pos    = caseAwareTokenOffset
            val mods   = modifiers() withAnnotations annots
            datatypeDef(pos, mods)
          case _ =>
            if (isStatSep) Nil
            else syntaxErrorOrIncompleteAnd("expected class or object definition", skipIt = true)(Nil)
        })
        acceptStatSepOpt()
      }
      stats.toList
    }

case DATATYPE あたりを追加しています。

それ以外のレベル

一方、それ以外の場合には、メソッドdefOrDcl()からメソッドtmplDef()を呼び出しているのでした。

こちらは、

・defOrDcl()の返り値の型が、List[Tree]。
・tmplDef()の返り値の型が、Tree。

ですので、defOrDcl()のほうに変更を加えます。

Parsers.scala
    def defOrDcl(pos: Int, mods: Modifiers): List[Tree] = {
      if (mods.isLazy && in.token != VAL)
        syntaxError("lazy not allowed here. Only vals can be lazy", skipIt = false)
      in.token match {
        case VAL =>
          patDefOrDcl(pos, mods withPosition(VAL, tokenRange(in)))
        case VAR =>
          patDefOrDcl(pos, (mods | Flags.MUTABLE) withPosition (VAR, tokenRange(in)))
        case DEF =>
          List(funDefOrDcl(pos, mods withPosition(DEF, tokenRange(in))))
        case TYPE =>
          List(typeDefOrDcl(pos, mods withPosition(TYPE, tokenRange(in))))
        case DATATYPE =>
          datatypeDef(pos, mods)
        case _ =>
          List(tmplDef(pos, mods))
      }
    }

case DATATYPEあたりが追加したところです。

datatype構文の解析

では、実際にメソッドdatatypeDef()と関連するメソッドを定義します。

Parser.scala
    /** {{{
      *  DatatypeDef     ::= Id ClassParamClauses [ClassTemplateOpt] '=' DatatypeParamClauses
      *  }}}
      */
    def datatypeDef(start: Int, mods: Modifiers): List[Tree] = {
      // Id: get datatype name
      in.nextToken()
      val name = identForType()

      //
      savingClassContextBounds {
        classContextBounds = List()
        val tstart = (in.offset :: classContextBounds.map(_.pos.startOrPoint)).min

        val constrAnnots = constructorAnnotations()
        // ClassParamClauses
        val (constrMods, vparamss) = (accessModifierOpt(), paramClauses(name, classContextBounds, ofCaseClass = false))
        var mods1 = mods

        // [ClassTemplateOpt]
        val vpara = vparamss match {
          case List() => List(List())
          case _ => vparamss
        }
        val template = templateOpt(mods1, name, constrMods withAnnotations constrAnnots, vpara, tstart)

        val abstractParentClass = ClassDef(mods1 | Flags.ABSTRACT | Flags.SEALED, name, List(), template)

        // Context bounds generate implicit parameters (part of the template) with types
        // from tparams: we need to ensure these don't overlap
        if (!classContextBounds.isEmpty)
          ensureNonOverlapping(template, Nil)

        // check '='
        if(in.token != EQUALS){
          syntaxError("no '=' after datatype name.", skipIt = false)
        }
        in.nextToken()

        val result = abstractParentClass :: datatypeParamClauses(Ident(name), mods)
        result
      }
    }

    /** {{{
      *  DatatypeParamClauses  ::= DatatypeParamClause ['|' DatatypeParamClause]
      *  }}}
      */
    def datatypeParamClauses(superclass: Tree, mods: Modifiers): List[Tree] = {
      val clauses = new ListBuffer[Tree]
      val clause = datatypeParamClause(superclass, mods)
      clauses.append(clause)
      if ((in.token == NEWLINE || in.token == NEWLINES) && in.next.name == raw.BAR){
        in.nextToken()
      }
      while (isRawBar) {
        in.nextToken();
        val node = datatypeParamClause(superclass, mods)
        clauses.append(node)

        if ((in.token == NEWLINE || in.token == NEWLINES) && in.next.name == raw.BAR){
          in.nextToken()
        }
      }
      clauses.toList
    }

    /** {{{
      *  DatatypeParamClause  ::= Id ClassParamClauses [ClassTemplateOpt]
      *  }}}
      */
    def datatypeParamClause(superclass: Tree, mods: Modifiers): Tree = {
      // Id: get datatype name
      val nameOffset = in.offset
      val name = identForType()

      classContextBounds = List()
      val tstart = (in.offset :: classContextBounds.map(_.pos.startOrPoint)).min

      // ClassParamClauses
      val vparamss = paramClauses(name, classContextBounds, ofCaseClass = false)
      var mods1 = mods

      val constrAnnots = constructorAnnotations()
      val constrMods = accessModifierOpt()

      // [ClassTemplateOpt]
      val vpara = vparamss match {
        case List() => List(List())
        case _ => vparamss
      }
      val template = datatypeTemplateOpt(mods1, name, constrMods withAnnotations constrAnnots, vpara, tstart, superclass)

      val result = ClassDef(mods1 | Flags.CASE, name, List(), template)
      result
    }

    /** {{{
      *  DatatypeTemplateOpt ::= [TemplateBody]
      *  }}}
      */
    def datatypeTemplateOpt(mods: Modifiers, name: Name, constrMods: Modifiers, vparamss: List[List[ValDef]], tstart: Int, superclass: Tree): Template = {
      newLineOptWhenFollowedBy(LBRACE)
      val (self, body) = templateBodyOpt(parenMeansSyntaxError = mods.isTrait || name.isTermName)
      val parents: List[Tree] = superclass :: List(productConstr, serializableConstr)
      val tstart0 = if (body.isEmpty && in.lastOffset < tstart) in.lastOffset else tstart

      atPos(tstart0) {
        // Exclude only the 9 primitives plus AnyVal.
        Template(parents, self, constrMods, vparamss, body, o2p(tstart0))
      }
    }

上記コードの下から3行目の
Template(parents, self, constrMods, vparamss, body, o2p(tstart0))
は、最新のソースだと以下の記述に変える必要があるかもしれません。
gen.mkTemplate(parents, self, constrMods, vparamss, body, o2p(tstart0))

使い方

例文
// datatype定義
datatype Week = Mon | Tue | Wed | Thu | Fri | Sat | Sun
// コンストラクタ呼び出し
//  ・Sunだけだと型をあらわすので、必ずカッコが必要
val day: Week = Sun()
// パターンマッチ
day match {
  case Sun() => println("Sunday!")
  case _ => println("not Sunday.")
}

だいたい、上記のように使います。
実際に動くコードも出してみます。

datatypeSample.scala
object datatypeSample {

  datatype Week = Mon | Tue | Wed | Thi | Fri | Sat | Sun

  val day: Week = Wed()
  val result = day match {
    case Mon() => "月曜日"
    case Tue() => "火曜日"
    case Wed() => "水曜日"
    case Thi() => "木曜日"
    case Fri() => "金曜日"
    case Sat() => "土曜日"
    case Sun() => "日曜日"
  }

  datatype Week2 {
    def isWorkday: Boolean = true
    def isHolyday: Boolean = ! isWorkday
  }
  = Mon2 | Tue2 | Wed2 | Thi2 | Fri2 | Sat2 {
      override def isWorkday = false
    }
  | Sun2 {
      override def isWorkday = false
    }

  datatype Fig =
    Rect(val x: Double, val y: Double){
      def area(): Double = x * y
    }
  | Circle(val r: Double){
      val pi = 3.14
      def area(): Double = r * r * pi
  }

  def main(args: Array[String]): Unit = {
    println("result = " + result)

    val day2: Week2 = Sun2()
    println("Sun2.isWorkday = " + day2.isWorkday)
    println("Sun2.isHolyday = " + day2.isHolyday)

    val rect = Rect(3.0, 5.0)
    val area1 = rect.area()
    val circle = Circle(10.0)
    val area2 = circle.area()
    println("rect area = " + area1)
    println("circle area = " + area2)
  }
}

これを、あらたにコンパイルしたscalaを下記のコマンドで実行すると、

ScalaのコンパイルはScalaのソースコードを読んでみる その1を参照

$ build/pack/bin/scala datatypeSample.scala

以下の出力が得られます。

result = 水曜日
Sun2.isWorkday = false
Sun2.isHolyday = true
rect area = 15.0
circle area = 314.0

問題点:解決済み!!

「datatype宣言中に'|'が行の先頭にくることが出来ない」という問題は、解決しました。
メソッドdatatypeParamClausesをちょっと書き換えてあります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0