1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Markdownライクな構文のスクリプト言語「Turbine」のEnum

Posted at

TurbineのEnumの機能

前回の記事(Markdownライクな構文のスクリプト言語「Turbine」を開発中)では、Turbineの概要について紹介しました。今回は他の言語と少し違う、Enum(列挙型)について掘り下げてみます。

TurbineのEnumとフィールド

他の多くの言語では、Enumを使って状態やフラグを識別するために、列挙子に連番やビット値を割り当てる仕組み(iota()auto()など)があります。

基本的な使い方は、以下のC++コードのように、列挙子の整数を使って switchif 文で処理を分けるというものです。

また、1 << 01 << 1 など、2の冪乗を使ってビットフラグとして利用するケースもあります(この場合は、スコープ付き定数として扱われることが多く、列挙型とは少し意味合いが異なります)。

#include <iostream>
 
int main()
{
    enum class Color { red, green = 20, blue };
    Color r = Color::blue;
 
    switch(r)
    {
        case Color::red  : std::cout << "red\n";   break;
        case Color::green: std::cout << "green\n"; break;
        case Color::blue : std::cout << "blue\n";  break;
    }
 
    // ...
}

Turbineは、この整数値をどうやって割り当てるか(連番)や、どういう値にするか(フラグ)ということを考える負担を取り除くための試みがあります。整数値のことを忘れていい代わりに、もっと意味のある値をそのままフィールドとして割り当てられるようにしました。

以下のTurbineコードのように、フィールドは1つ以上の任意の数を割り当てることができます。

## Color enum
  : symbol , name    , rgba_index, bgra_index
  - R      , "red"   , 0         , 2
  - G      , "green" , 1         , 1
  - B      , "blue"  , 2         , 0
  - A      , "alpha" , 3         , 3

# main() int
  - r = Color.B
  print(r.name)
  - idx = r.bgra_index
  // ...
  return 0

フィールドを使えば、switch文の使用を削減でき、例えば、RGBAやBGRAのような異なる描画カラーフォーマットに対応したインデックスも統一的に扱えます。このようにTurbineのEnumでは2次元のテーブル構造を利用して関連する情報をまとめて管理できます。

開発当初、Enumという言葉ではなく、実際にTableという用語で開発をしていて、構文もマークダウンのテーブル記法そのものでした。しかし、実際にコードを書いてみると、

  • |記号がビット和演算子と競合した(フィールドに定数式を書く場合)
  • Enumと比べて説明の手間がかかる
  • 他言語の「テーブル型」と混乱を招く

といった問題が生じたため、最終的には「Enum」を採用しました。「2次元のEnum」として説明すると、ほとんどのプログラマーが直感的に理解してくれることも理由のひとつです。

TurbineのEnumとForループ

UIでドロップダウンメニューを作るときや、設定可能な選択肢を列挙したいとき、Enumの全要素を順番に処理したくなる場面は少なくありません。

例えば、色の選択肢や並び順のオプション、操作モードなど、コードとUIの選択肢を一貫して保つためには、Enumをそのまま走査できると非常に便利です

## Month enum
  : symbol , name         , num
  - Jan    , "January"    , 1
  - Feb    , "February"   , 2
  - Mar    , "March"      , 3
  - Apr    , "April"      , 4
  - May    , "May"        , 5
  - Jun    , "June"       , 6
  - Jul    , "July"       , 7
  - Aug    , "August"     , 8
  - Sep    , "September"  , 9
  - Oct    , "October"    , 10
  - Nov    , "November"   , 11
  - Dec    , "December"   , 12

# main(args vec{string}) int
   // 最初の項目はプレースホルダ文字列
  - month_menu = vec{"Select Month"}

  for m in Month
    vecpush(month_menu, m.name)

  // month_menuをドロップダウンメニューのアイテムリストとして使用
  // ...
  
  return 0

Enumを使ったForループの文法

Turbineで整数範囲やコレクション型を走査するループは、文法上inの後に必ずが来ることになっています。しかしEnumの場合だけ特別扱いをしてMonthというが来ることを許しています。この特例は、実際のユースケース(UIの選択肢など)で頻繁に使われること、また可読性とのバランスを重視してあえて導入しています。

文法の一貫性は少し犠牲になりますが、実用上は上記のように書けることの方が自然に感じられると思います。通常の式の中ではMonthのようにEnumの型単体を書くと文法エラーになります。パーサーはEnum名の後に必ず.が続いて列挙子が来ることを期待しているからです。

まとめ

TurbineのEnumは、「ただの定数の集まり」ではなく、「名前付きのデータ行(レコード)」の集合と捉えることができます。これは、設計段階で「テーブル記法」から始まった経緯を考えると、ごく自然な進化形です。

プログラムの中で小さなデータ表を管理したいとき、TurbineのEnumはとても便利なツールになるはずです。

リンク

現在は開発中で、仕様や文法を少しずつ固めています。
詳細や開発ログは以下で公開しています:

👉 GitHub
👉 ドキュメント(英語)

興味のある方へ

  • 新しい言語の設計に興味がある方
  • C/C++と相性の良い小さなスクリプト言語を探している方
  • Markdown風の軽量な構文に興味がある方

ぜひコメントやフィードバックをいただけると嬉しいです!
今後も開発進捗や実装例などを投稿していく予定です。

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?