Scala
casl2
comet2

ScalaでCASLIIとCOMETIIの実装してみた感想

0. はじめに

基本情報処理技術者試験用に作られた言語仕様のCASLIIと仮想マシンのCOMETIIをScalaで実装しました。
GitHub: ScaCASL2-ScaCOMET2
過去に色々な方が、色々な言語で実装されてきていますが、Scala版が見当たらなく、ちょうどScalaの脱初心者を目指していた自分には良い題材でしたのでチャレンジしてみました。
まだまだ不具合&改善ポイントありますので、コメント等々をお待ちしています。

1. なぜ作ったのか?

普段、業務ではScalaを使っていません。一応、職場では趣味でツール(Scala)を作ったりするレベル感です。Scala初心者レベルから脱却をしたかったのですが、職場でがっつりコーデイングする機会がなかったので、題材を探していた頃に以下のQiitaの記事に出会いました。

最強のCASL2/COMET2環境

C言語の勉強がてら、CASL II処理システムを実装した話

記事の中でも触れられているのですが、実装言語に対する理解や周辺ツールの理解に繋がることを期待して、Scalaでの実装を始めました。

動作する物ができたので、初心者から脱却したと言っても良いはず。。。

なお、Scala入門編に関しては以下の記事をどうぞ。
Scala入門時に役立つ情報まとめ

2. 身についた事

一旦、身についた事から書き出してみたいと思います。

Option

Scalaでは、nullは利用せずにNone(Option型)を使いましょうって話を頻繁に見かけます。色々な使い方は別の方に譲るとして、ここでは恥ずかしい失敗の話です。
値がある時はSome、存在しない時はNoneを利用するのですが、その二つともOptionを継承しています。この階層構造が頭に入っておらず、普通に以下のような宣言をしていました。

val hoge: Some[String] = hogehoge
hoge match {
  case Some(v) => test
  case None => println()
}

Someで型宣言してはダメですよね。Optionで宣言して利用しないとダメです。

val hoge: Option[String] = hogehoge
hoge match {
  case Some(v) => test
  case None => println()
}

こんな当たり前の事が、体で理解できていなかったダメ初心者です(でした)。

パターンマッチ

Scalaの魅力のひとつとして語られるのがパターンマッチです。if文での分岐の書き方との差をうまく表現できないのですが、『パターン(≒ 型)を考えてから分岐を考える』事ができるのが魅力のように感じました。
パターンマッチで使った箇所の例としては『コマンドラインからのパラメータ』処理です。

// 注)実際のコードとは異なります。
def main(args: Array[String]): Unit = {
  if(args(0) == "-v"){
    // version
  } else if(args(0) == "-h") {
    // help
  } else if(args.isEmpty) {
    // other
  } else ・・・ 
}

みたいに考えて書いていましたが、内部的に『処理』の型に変換を挟むイメージで考えるとコードがすっきりした気がします。

// command line interfaceの種類色々
sealed abstract class CliCommand
object CliCommand {
  case object Run extends CliCommand
  case object Debug extends CliCommand
  case object Help extends CliCommand
  case object Version extends CliCommand
  case object InputError extends CliCommand
}

各種コマンドのパラメータを判別してから、CliCommand(と他のパラメータ)を別のメソッドに渡すと言う形が作れたので、見通しが良くなった気がしています。

パーサコンビネータ

CASLIIのファイルを読み込んで、構文解析して・・・って大変なイメージがあるのですが、Scalaだと標準で構文を解析して変数に展開するライブラリが存在します。職場で作ったツールもこれを利用しており、Scalaが好きになったひとつの理由です。
基本情報処理技術者試験のCASLIIの試験要項及びシラバスで定義されている言語仕様をそのまま記述出来てる感じがします。(慣れないと"~"や"^^"は抵抗感があるかも)

言語仕様)

行の種類 オペランド有無 説明
命令行 オペランドあり [ラベル]{空白}{命令コード}{空白}{オペランド}[{空白}[コメント]]
命令行 オペランドなし [ラベル]{空白}{命令コード}[{空白}[{;}[コメント]]]
注釈行    [空白]{;}[コメント]

実際の定義例)

  private def instructions =
    inst_line_with_ope | inst_line_no_ope | comment_line

  private def inst_line_with_ope =
    opt(label) ~ casl_white_space ~ code ~ operands ~ opt(annotation) ^^ {
      case lbl ~ sp1 ~ inst_code ~ opes ~ cmt =>
        InstructionLine(lbl,
                        inst_code.trim,
                        Some(opes.map(e => e.trim)),
                        cmt,
                        0,
                        "")
    }

  private def inst_line_no_ope =
    opt(label) ~ casl_white_space ~ code ~ opt(annotation) ^^ {
      case lbl ~ sp ~ inst_code ~ cmt =>
        InstructionLine(lbl, inst_code, None, cmt, 0, "")
    }

  private def comment_line = opt(casl_white_space) ~> annotation ^^ {
    CommentLine(_, 0, "")
  }

  private def label = InstructionFactory.REGEX_LABEL.r

  private def code = """[A-Z]+""".r

  private def operands = casl_white_space ~> rep(arg_pat1 <~ opt(comma))
  private def comma = ","
  private def arg_pat1 = """('.*(?<!')'.*')|([^,;]+)""".r

  protected override val whiteSpace = """""".r

  private def casl_white_space = """\s+""".r

  private def annotation = """;.*$""".r

これだけの行数で、このように簡潔に表現できます。なお、細かい構文のチェックは別の処理でやっています。

OSS活動に欠かせないツールの利用経験

IntelliJ IDEA(Community Edition)を利用して開発しました。2017年8月頃から始めたのですが、徐々に慣れてきて、コードを書いて、テストを書いて、動作を確認すると言う流れが出来ました。カバレッジツールも使って開発をしていたのですが、なかなか網羅率をあげれなくて、コードを書き直す事もしばしば。結局今も100%にはならずです。Travis CIはpushのタイミングに、別環境で自動ビルド・自動テストを実施してくれるので、commit漏れも検知してくれるので本当に安心感がありました(元日はこけていたようですが)。

こうやって書き出してみると多いですね。。。
ただ、このあたりのツール類を使っていると継続して開発が続けられそうな気がします。

3. その他参考にしたサイト

楽しんで読ませてもらいました。
Scala上でアセンブリでFizzBuzz

見直してCASLIIのコードを読み取る部分を実装しました。
RegexParsersで手軽にScalaのパーサコンビネータを使ってみる

理解できていない部分が多いですがvarからvalへのヒントになるブログでした。
Scala で書く tetrix: 2日目

他も色々あった気がしますが、思い出したら書き足したいと思います。

4. まとめ

var と val の差が思ったよりも大きく、immutableにする事はコードをシンプルにするための制約(条件)のように感じる事ができました。これまではメンドクさいの感想しかなかったのですが。

今後は、引き続き良くないコードは書き直したいのと、無駄に並行処理とかにもチャレンジしたいな〜。

鉄のスプーンとかでの食事の時に嫌な感じが口元に広がる感覚があって、Macのシルバー感に抵抗があったのですが、サンプルとか見てるとMacが多いので、半年前にようやくMacデビューしました。Mac歴半年ですが、開発快適です。OS起動が早いので、ちょっとした時間でも開発する事が出来るようになりました。