難解プログラミング言語の作り方/HQ9+をもとにする
このページでは、HQ9+をもとにして難解プログラミング言語を自作します。
HQ9+って何ですか
編集HQ9+は、命令が4つしかない難解プログラミング言語です。
「w:HQ9+」も参照
命令 | 動作 |
---|---|
H
|
Hello, world! を表示
|
Q
|
クワイン[1] |
9
|
99 bottles of beer |
+
|
アキュムレータ(変数のようなもの)をインクリメント |
たったこれだけです。実装も非常に簡単なので、数多くの派生言語が作られています。 例えば...
- FizzBuzzを追加した「HQ9F+」。
- HとQと9がなくなってしまった「+」。
- アキュムレータが二次元配列になった「HQ9+2D」。
- Deadfishという別の言語とくっつけられた「FISHQ9+」。
などなど。
計画を立てましょう
編集HQ9+が何者か学んだので、計画を立てましょう。
命令は以下の通りです。
命令 | 意味 |
---|---|
H |
ランダムでHello, world!かGood afternoon!を表示。 |
Q |
クワイン。 |
E |
例外(アキュムレータに応じて変わる。下の表を参照)をスローする。 |
+ |
アキュムレータをインクリメント。 |
E
命令でスローされる例外は以下の通りです。
アキュムレータ | 例外の名前 | 用途 |
---|---|---|
1 | Exception | 例外全般。 |
2 | ArgumentNullException | nullを渡してはいけない関数にnullを渡した場合にスローされる。 |
3 | IOException | I/Oエラーが発生した際にスローされる。 |
4 | MissingMemberException | 存在しないメンバーに動的[4]にアクセスしようとした場合にスローされる。 |
5 | EntryPointNotFoundException | エントリポイント[5]が存在しない場合にスローされる。 |
それ以外 | Exception | 例外全般。 |
実装
編集計画ができました。作りましょう。
変数の実装
編集これは非常に簡単で、変数を必要な分用意するだけで終わります。
uint accumulator = 0; // アキュムレータ
Random rnd = new Random();
インタプリタの実装
編集前のページで作ったBrainfuck派生言語のソースコードを流用することにしましょう。
命令の実装
編集これはただ地道にSwitch文を書くだけですが、非常に重要です。がんばりましょう。
switch (cmd[i])
{
case 'H':
if (rnd.Next(0, 2) == 0) // 一文のみの場合、かっこをつけなくてよい。
Console.WriteLine("Good afternoon!");
else
Console.WriteLine("Hello, world!");
break;
case 'Q':
Console.WriteLine(cmd);
break;
case 'E':
switch (accumulator)
{
case 1:
throw new Exception(); // 例外を起こすときなど、必ず終わる場合break文は必要ない。
case 2:
throw new ArgumentNullException();
case 3:
throw new System.IO.IOException();
case 4:
throw new MissingMemberException();
case 5:
throw new EntryPointNotFoundException();
default:
throw new Exception();
}
case '+':
accumulator++;
break;
default:
Console.Write(cmd[i]);
break;
}
例外をスローするためにはthrow
文を使います。
テスト
編集H命令
編集だいたい二分の一でメッセージが表示されます。
コード:
HH
出力例:
Hello, world!
Good afternoon!
Q命令
編集入力をそのまま表示します。
コード:
Q
出力:
Q
全体図
編集全部のソースコードです。
using System;
namespace not_HQ9plus
{
class Program
{
static void Main()
{
while (true)
{
uint accumulator = 0; // アキュムレータ
Random rnd = new Random();
Console.Write("> ");
string cmd = Console.ReadLine();
for (int i = 0; i < cmd.Length; i++)
{
switch (cmd[i])
{
case 'H':
if (rnd.Next(0, 2) == 0) // 一文のみの場合、かっこをつけなくてよい。
Console.WriteLine("Good afternoon!");
else
Console.WriteLine("Hello, world!");
break;
case 'Q':
Console.WriteLine(cmd);
break;
case 'E':
switch (accumulator)
{
case 1:
throw new Exception(); // 例外を起こすときなど、必ず終わる場合break文は必要ない。
case 2:
throw new ArgumentNullException();
case 3:
throw new System.IO.IOException();
case 4:
throw new MissingMemberException();
case 5:
throw new EntryPointNotFoundException();
default:
throw new Exception();
}
case '+':
accumulator++;
break;
default:
Console.Write(cmd[i]);
break;
}
}
Console.WriteLine();
}
}
}
}
演習問題
編集- 上記のスクリプトのアキュムレータの初期値を10000に設定し、+命令の動作をデクリメントに書き換えましょう。
脚注
編集- ^ ソースコードをそのまま出力することです。ほとんどのプログラミング言語では実装が非常に大変ですが、もともとそれを簡単にするために設計されていたりするのでこれだけでおわります。
- ^ 32ビットの符号なし整数型。0~4,294,967,295までの整数を扱えます。
- ^ アセンブラにおける変数のようなものの一つ。サイズは32ビットと決まっています。ほかにもEBXやECXなどがあります。
- ^ コンパイルなど実行前に決めておかず、実行時にその都度決めておくこと。
- ^ 最初に実行される関数のこと。
Main()
など。