三目並べは、皆さんお馴染みの二人で対戦するゲームです。ひとりが○側でもうひとりが×側を受け持ち、3 行 3 列のマス目に○×を書いて、3 つ並べた方が勝ちというゲームです。下図は○側が先手で引き分けになった例です。
┌─┬─┬─┐ │×│○│○│ ├─┼─┼─┤ │○│○│×│ ├─┼─┼─┤ │×│×│○│ └─┴─┴─┘ 図 : 三目並べ
三目並べは、両者が最善を尽くすと引き分けになることが知られています。実際、ミニマックス法を使って確かめてみると、初手がどこでも結果は引き分けになります。興味のある方は拙作のページ Puzzle DE Programming 「三目並べ」をお読みくださいませ。
ところで、参考文献『bit別冊 ゲームプログラミング』によると、三目並べで両者が次の戦略を用いると、ゲームは常に引き分けになります。
本当に引き分けになるのか、プログラムを作って確かめてみましょう。
最初に、ゲームに必要なデータ構造を定義します。
┌─┬─┬─┐ │0│1│2│ ├─┼─┼─┤ │3│4│5│ ├─┼─┼─┤ │6│7│8│ └─┴─┴─┘ 図 : 盤面の番号
リスト : データ構造の定義
// 駒の種類
enum Piece {Space, Maru, Batu};
// 盤面
Piece[] board = new Piece[9];
// 直線
int[,] lineTable = new int[8, 3] {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6},
};
駒の種類を enum で定義します。先手が Maru、後手が Batu、空き場所が Space です。盤面は配列 board で表します。盤面と配列の添字の対応は上図のように定義します。すると、駒が 3 つ並ぶ直線は二次元配列 lineTable で表すことができます。
勝敗の判定は関数 CheckWin() で行います。次のリストを見てください。
リスト : 勝敗の判定
Piece CheckWin() {
for (int i = 0; i < 8; i++) {
int x = lineTable[i, 0];
int y = lineTable[i, 1];
int z = lineTable[i, 2];
Piece p = board[x];
if (p != Piece.Space && board[y] == p && board[z] == p) return p;
}
return Piece.Space;
}
関数 CheckWin() は 8 本の直線を調べ、同じ駒が 3 つ並んでいるか調べます。直線の先頭に Maru か Batu があり、残り 2 つの場所に同じ駒があれば 3 つ並んでいることがわかります。Maru が 3 つ並んでいれば Maru を、Batu が 3 つ並んでいれば Batu を返します。どちらも 3 つ並んでいなければ Space を返します。
次は同じ駒を 3 つ並べることができる場所を探す関数 GetWinPosition() を作ります。
リスト : 駒を 3 つ並べられるか
int GetWinPosition(Piece p) {
for (int i = 0; i < 8; i++) {
int x = lineTable[i, 0];
int y = lineTable[i, 1];
int z = lineTable[i, 2];
if (board[x] == Piece.Space && board[y] == p && board[z] == p)
return x;
else if (board[y] == Piece.Space && board[x] == p && board[z] == p)
return y;
else if (board[z] == Piece.Space && board[x] == p && board[y] == p)
return z;
}
return -1;
}
引数 p は駒の種類を表します。空き場所が 1 つあり、残りの 2 か所が p と等しい直線を探します。見つけた場合は空き場所の位置を、見つからない場合は -1 を返します。
次はコンピュータの指し手を決める関数 MoveCom() を作ります。
リスト : コンピュータの指し手を決定する
int MoveCom(Piece turn) {
// 勝てる場所を探す
int p = GetWinPosition(turn);
if (p < 0) {
// 相手の手番で勝ちがあるか
p = GetWinPosition(turn == Piece.Maru ? Piece.Batu : Piece.Maru);
if (p < 0) {
// 中央が空いているか
if (board[4] == Piece.Space) {
p = 4;
} else {
// 隅が取れるか
p = GetCornerPosition();
if (p < 0) {
// 空き場所に駒を置く
p = GetSpacePosition();
}
}
}
}
return p;
}
関数 MoveCom() は戦略に従ってコンピュータの指し手を決定します。引数 turn は自分の駒の種類を表します。最初に GetWinPosition() を呼び出して、駒を 3 つ並べることができるか調べます。自分が勝てるのであれば、その場所 p に駒を置きます。そうでなければ、相手が駒を 3 つ並べることができるか調べます。これは GetWinPosition() で相手の駒の種類を渡せば調べることができます。そうであれば、その場所 p に自分の駒をおきます。
それ以外の場合は、優先順位(中央 -> 隅 -> 空き場所) に従って駒を置きます。空いている隅は関数 GetCornerPosition() で求めます。空き場所は関数 GetSpacePosition() で求めます。最後に指し手 p を返します。これで戦略に従ってコンピュータの指し手を決定することができます。
あとはとくに難しいところはないでしょう。詳細はプログラムリストをお読みくださいませ。
それでは実行結果を示します。
$ dotnet script tictactoe.csx 0 1 2 3 4 5 6 7 8 Input> 4 Maru: 4 0 1 2 3 O 5 6 7 8 Batu: 0 X 1 2 3 O 5 6 7 8 Input> 2 Maru: 2 X 1 O 3 O 5 6 7 8 Batu: 6 X 1 O 3 O 5 X 7 8 Input> 3 Maru: 3 X 1 O O O 5 X 7 8 Batu: 5 X 1 O O O X X 7 8 Input> 7 Maru: 7 X 1 O O O X X O 8 Batu: 1 X X O O O X X O 8 Input> 8 Maru: 8 X X O O O X X O O draw game $ dotnet script tictactoe.csx 0 1 2 3 4 5 6 7 8 Input> 4 Maru: 4 0 1 2 3 O 5 6 7 8 Batu: 0 X 1 2 3 O 5 6 7 8 Input> 8 Maru: 8 X 1 2 3 O 5 6 7 O Batu: 2 X 1 X 3 O 5 6 7 O Input> 1 Maru: 1 X O X 3 O 5 6 7 O Batu: 7 X O X 3 O 5 6 X O Input> 5 Maru: 5 X O X 3 O O 6 X O Batu: 3 X O X X O O 6 X O Input> 6 Maru: 6 X O X X O O O X O draw game
確かに引き分けになりますね。興味のある方はいろいろ試してみてください。
//
// tictactoe.csx : 三目並べ
//
// Copyright (C) 2016-2022 Makoto Hiroi
//
enum Piece {Space, Maru, Batu};
// 盤面
// 0 1 2
// 3 4 5
// 6 7 8
Piece[] board = new Piece[9];
// 直線
int[,] lineTable = new int[8, 3] {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
{0, 3, 6},
{1, 4, 7},
{2, 5, 8},
{0, 4, 8},
{2, 4, 6},
};
// コーナーの位置
int[] corner = new int[4] {0, 2, 6, 8};
// 3 個並ぶ場所を返す
int GetWinPosition(Piece p) {
for (int i = 0; i < 8; i++) {
int x = lineTable[i, 0];
int y = lineTable[i, 1];
int z = lineTable[i, 2];
if (board[x] == Piece.Space && board[y] == p && board[z] == p)
return x;
else if (board[y] == Piece.Space && board[x] == p && board[z] == p)
return y;
else if (board[z] == Piece.Space && board[x] == p && board[y] == p)
return z;
}
return -1;
}
// 空いている隅を返す
int GetCornerPosition() {
foreach(int x in corner) {
if (board[x] == Piece.Space) return x;
}
return -1;
}
// 空いている場所を返す
int GetSpacePosition() {
for (int i = 0; i < 9; i++) {
if (board[i] == Piece.Space) return i;
}
return -1;
}
// 勝利判定
Piece CheckWin() {
for (int i = 0; i < 8; i++) {
int x = lineTable[i, 0];
int y = lineTable[i, 1];
int z = lineTable[i, 2];
Piece p = board[x];
if (p != Piece.Space && board[y] == p && board[z] == p) return p;
}
return Piece.Space;
}
// 終了判定
bool GameOver() {
Piece p = CheckWin();
if (p == Piece.Space) {
return false;
} else if (p == Piece.Maru) {
Console.WriteLine("Maru Win!!");
} else {
Console.WriteLine("Batu Win!!");
}
return true;
}
// コンピュータの差し手を決める
int MoveCom(Piece turn) {
// 勝てる場所を探す
int p = GetWinPosition(turn);
if (p < 0) {
// 相手の手番で勝ちがあるか
p = GetWinPosition(turn == Piece.Maru ? Piece.Batu : Piece.Maru);
if (p < 0) {
// 中央が空いているか
if (board[4] == Piece.Space) {
p = 4;
} else {
// 隅が取れるか
p = GetCornerPosition();
if (p < 0) {
// 空き場所に駒を置く
p = GetSpacePosition();
}
}
}
}
return p;
}
// 人間の差し手を決める
int MoveHuman(Piece turn) {
while (true) {
Console.Write("Input> ");
string buff = Console.ReadLine();
int n;
if (int.TryParse(buff, out n)) {
if (0 <= n && n < 9 && board[n] == Piece.Space) return n;
}
Console.WriteLine("Please Input Number: 0 - 8");
}
}
// 盤面の表示
void PrintBoard() {
for (int i = 0; i < 9; i++) {
if (board[i] == Piece.Space)
Console.Write("{0} ", i);
else if (board[i] == Piece.Maru)
Console.Write("O ");
else
Console.Write("X ");
if (i % 3 == 2) Console.WriteLine("");
}
Console.WriteLine("");
}
void main() {
Piece turn = Piece.Maru;
PrintBoard();
while (!GameOver()) {
int n = turn == Piece.Maru ? MoveHuman(turn) : MoveCom(turn);
Console.WriteLine("{0}: {1}", turn, n);
board[n] = turn;
turn = turn == Piece.Maru ? Piece.Batu : Piece.Maru;
PrintBoard();
if (GetSpacePosition() == -1) {
Console.WriteLine("draw game");
break;
}
}
}
// 実行
main();
再帰降下法で式 (四則演算とカッコ) を計算するプログラムです。アルゴリズムの詳細は拙作のページ Scheme Programming 「電卓プログラムの作成」をお読みください。
//
// calc0.csx : 式の計算 (単純な再帰降下法)
//
// Copyright (C) 2016-2022 Makoto Hiroi
//
using System.Text;
// Calc 用例外クラス
class CalcError : Exception {
public CalcError(string msg) : base(msg) { }
}
// トークン種別
enum Token {Number, Add, Sub, Mul, Div, Lpar, Rpar, Semic, Others };
char ch; // 先読みバッファ
Token token; // トークン
double numValue; // 数値
// 記号の先読み
void NextChar() {
int c = Console.Read();
if (c == -1)
throw new CalcError("EOF");
ch = (char)c;
}
// 記号の読み込み
char GetChar() {
return ch;
}
// 整数値の読み込み
void GetFixNumber(StringBuilder sb) {
while (char.IsDigit(GetChar())) {
sb.Append(GetChar());
NextChar();
}
}
// 数値の読み込み
double GetNumber() {
double val;
var sb = new StringBuilder();
GetFixNumber(sb);
if (GetChar() == '.') {
sb.Append(GetChar());
NextChar();
GetFixNumber(sb);
}
if (GetChar() == 'e' || GetChar() == 'E') {
sb.Append(GetChar());
NextChar();
if (GetChar() == '+' || GetChar() == '-') {
sb.Append(GetChar());
NextChar();
}
GetFixNumber(sb);
}
if (!double.TryParse(sb.ToString(), out val)) {
throw new CalcError("Not Number");
}
return val;
}
// トークンの切り分け
void GetToken() {
while (char.IsWhiteSpace(GetChar())) {
NextChar();
}
if (char.IsDigit(GetChar())) {
token = Token.Number;
numValue = GetNumber();
} else if (GetChar() == '+') {
token = Token.Add;
NextChar();
} else if (GetChar() == '-') {
token = Token.Sub;
NextChar();
} else if (GetChar() == '*') {
token = Token.Mul;
NextChar();
} else if (GetChar() == '/') {
token = Token.Div;
NextChar();
} else if (GetChar() == '(') {
token = Token.Lpar;
NextChar();
} else if (GetChar() == ')') {
token = Token.Rpar;
NextChar();
} else if (GetChar() == ';') {
token = Token.Semic;
NextChar();
} else {
token = Token.Others;
}
}
// 構文解析
double Expr() {
double val = Term();
while (true) {
if (token == Token.Add) {
GetToken();
val += Term();
} else if (token == Token.Sub) {
GetToken();
val -= Term();
} else {
break;
}
}
return val;
}
// 項
double Term() {
double val = Factor();
while (true) {
if (token == Token.Mul) {
GetToken();
val *= Factor();
} else if (token == Token.Div) {
GetToken();
val /= Factor();
} else {
break;
}
}
return val;
}
// 因子
double Factor() {
if (token == Token.Lpar) {
GetToken();
double val = Expr();
if (token != Token.Rpar) {
throw new CalcError("')' expected");
}
GetToken();
return val;
} else if (token == Token.Number) {
GetToken();
return numValue;
} else if (token == Token.Add) {
GetToken();
return Factor();
} else if (token == Token.Sub) {
GetToken();
return -Factor();
} else {
throw new CalcError("unexpected token");
}
}
// トップレベル
void TopLevel() {
double val = Expr();
if (token == Token.Semic) {
Console.WriteLine(val);
Console.Write("Calc> ");
} else {
throw new CalcError("invalid token");
}
}
void CalcExec() {
Console.Write("Calc> ");
NextChar();
while (true) {
try {
GetToken();
TopLevel();
} catch(CalcError e) {
if (e.Message == "EOF") throw; // 例外の再送出
Console.WriteLine(e.Message);
// 入力のクリア
while (GetChar() != '\r') {
NextChar();
}
Console.Write("Calc> ");
}
}
}
// 実行
try {
CalcExec();
} catch(CalcError e) {
Console.WriteLine(e.Message);
}
$ dotnet script calc0.csx Calc> 1 + 2 + 3 + 4 + 5; 15 Calc> (1 + 2) * (3 - 4); -3 Calc> 1.11111111 * 1.11111111; 1.234567898765432 Calc> 1 / 7; 0.142857142857143 Calc> -10 * -10; 100 Calc> (1 + 2; ')' expected Calc> 1 + * 2; unexpected token Calc> (終了は CTRL-D を入力してください)
再帰降下法で構文木を構築して式 (四則演算とカッコ) を計算するプログラムです。アルゴリズムの詳細は拙作のページ お気楽 SML/NJ プログラミング入門 「電卓プログラムの作成」をお読みください。
//
// calc1.csx : 式の計算 (構文木の構築)
//
// Copyright (C) 2016-2022 Makoto Hiroi
//
using System.Text;
// Calc 用例外クラス
class CalcError : Exception {
public CalcError(string msg) : base(msg) { }
}
// 演算子
enum Op {Add, Sub, Mul, Div};
// 構文木
abstract class ExprTree { }
// 単項演算子
class Node1 : ExprTree {
public Op Operation { get; set; }
public ExprTree Right { get; set; }
public Node1(Op op, ExprTree expr) {
Operation = op;
Right = expr;
}
}
// 二項演算子
class Node2 : ExprTree {
public Op Operation { get; set; }
public ExprTree Left { get; set; }
public ExprTree Right { get; set; }
public Node2(Op op, ExprTree eLeft, ExprTree eRight) {
Operation = op;
Left = eLeft;
Right = eRight;
}
}
// 数値 (木構造の葉に相当)
class Leaf : ExprTree {
public double Value { get; set; }
public Leaf(double val) { Value = val; }
}
// トークン種別
enum Token {Number, Add, Sub, Mul, Div, Lpar, Rpar, Semic, Others };
char ch; // 先読みバッファ
Token token; // トークン
double numValue; // 数値
// 記号の先読み
void NextChar() {
int c = Console.Read();
if (c == -1)
throw new CalcError("EOF");
ch = (char)c;
}
// 記号の読み込み
char GetChar() {
return ch;
}
// 整数値の読み込み
void GetFixNumber(StringBuilder sb) {
while (char.IsDigit(GetChar())) {
sb.Append(GetChar());
NextChar();
}
}
// 数値の読み込み
double GetNumber() {
double val;
var sb = new StringBuilder();
GetFixNumber(sb);
if (GetChar() == '.') {
sb.Append(GetChar());
NextChar();
GetFixNumber(sb);
}
if (GetChar() == 'e' || GetChar() == 'E') {
sb.Append(GetChar());
NextChar();
if (GetChar() == '+' || GetChar() == '-') {
sb.Append(GetChar());
NextChar();
}
GetFixNumber(sb);
}
if (!double.TryParse(sb.ToString(), out val)) {
throw new CalcError("Not Number");
}
return val;
}
// トークンの切り分け
void GetToken() {
while (char.IsWhiteSpace(GetChar())) {
NextChar();
}
if (char.IsDigit(GetChar())) {
token = Token.Number;
numValue = GetNumber();
} else if (GetChar() == '+') {
token = Token.Add;
NextChar();
} else if (GetChar() == '-') {
token = Token.Sub;
NextChar();
} else if (GetChar() == '*') {
token = Token.Mul;
NextChar();
} else if (GetChar() == '/') {
token = Token.Div;
NextChar();
} else if (GetChar() == '(') {
token = Token.Lpar;
NextChar();
} else if (GetChar() == ')') {
token = Token.Rpar;
NextChar();
} else if (GetChar() == ';') {
token = Token.Semic;
NextChar();
} else {
token = Token.Others;
}
}
// 構文解析
ExprTree Expr() {
ExprTree e = Term();
while (true) {
if (token == Token.Add) {
GetToken();
e = new Node2(Op.Add, e, Term());
} else if (token == Token.Sub) {
GetToken();
e = new Node2(Op.Sub, e, Term());
} else {
break;
}
}
return e;
}
// 項
ExprTree Term() {
ExprTree e = Factor();
while (true) {
if (token == Token.Mul) {
GetToken();
e = new Node2(Op.Mul, e, Factor());
} else if (token == Token.Div) {
GetToken();
e = new Node2(Op.Div, e, Factor());
} else {
break;
}
}
return e;
}
// 因子
ExprTree Factor() {
if (token == Token.Lpar) {
GetToken();
ExprTree e = Expr();
if (token != Token.Rpar) {
throw new CalcError("')' expected");
}
GetToken();
return e;
} else if (token == Token.Number) {
GetToken();
return new Leaf(numValue);
} else if (token == Token.Add) {
GetToken();
return new Node1(Op.Add, Factor());
} else if (token == Token.Sub) {
GetToken();
return new Node1(Op.Sub, Factor());
} else {
throw new CalcError("unexpected token");
}
}
// 式の評価
double Eval(ExprTree e) {
Leaf a = e as Leaf;
if (a != null) return a.Value;
Node1 b = e as Node1;
if (b != null) {
if (b.Operation == Op.Add)
return Eval(b.Right);
else
return -Eval(b.Right);
}
Node2 c = (Node2)e;
double x = Eval(c.Left);
double y = Eval(c.Right);
if (c.Operation == Op.Add)
return x + y;
else if (c.Operation == Op.Sub)
return x - y;
else if (c.Operation == Op.Mul)
return x * y;
else
return x / y;
}
// トップレベル
void TopLevel() {
ExprTree e = Expr();
if (token == Token.Semic) {
Console.WriteLine(Eval(e));
Console.Write("Calc> ");
} else {
throw new CalcError("invalid token");
}
}
void CalcExec() {
Console.Write("Calc> ");
NextChar();
while (true) {
try {
GetToken();
TopLevel();
} catch(CalcError e) {
if (e.Message == "EOF") throw; // 例外の再送出
Console.WriteLine(e.Message);
// 入力のクリア
while (GetChar() != '\r') {
NextChar();
}
Console.Write("Calc> ");
}
}
}
// 実行
try {
CalcExec();
} catch(CalcError e) {
Console.WriteLine(e.Message);
}
小さな小さな Scheme ライクの Lisp インタプリタです。最小の Lisp については、拙作のページ Scheme Programming 「Scheme で作る micro Scheme」をお読みください。
//
// mscm.csx : micro Scheme インタプリタ
//
// Copyright (C) 2016-2022 Makoto Hiroi
//
using System.Text;
using System.Numerics;
using System.Collections.Generic;
// Scheme 用例外クラス
class SchemeError : Exception {
public SchemeError(string msg) : base(msg) { }
}
// コンスセル
class Cons {
public object car;
public object cdr;
public Cons(object x, object y) {
car = x;
cdr = y;
}
}
// シンボル
class Symbol {
public string Name { set; get; }
public object Value { set; get; }
public Symbol(string s, object val = null) {
Name = s;
Value = val;
}
public override string ToString() {
return Name;
}
}
// シンボル表
Dictionary<string, Symbol> symTable = new Dictionary<string, Symbol>();
// シンボルの生成
Symbol MakeSymbol(string name, object val = null) {
if (!symTable.ContainsKey(name)) {
symTable[name] = new Symbol(name, val);
}
return symTable[name];
}
// システムシンボル
Symbol sT = MakeSymbol("t");
Symbol sNil = MakeSymbol("nil");
Symbol sQuote = MakeSymbol("quote");
Symbol sDefine = MakeSymbol("define");
Symbol sLambda = MakeSymbol("lambda");
Symbol sIf = MakeSymbol("if");
// 述語
bool Consp(object xs) { return xs is Cons; }
bool Null(object xs) { return xs == sNil; }
bool Symbolp(object xs) { return xs is Symbol; }
bool Numberp(object xs) { return xs is BigInteger; }
// アクセス関数
object Car(object xs) {
if (!Consp(xs)) {
throw new SchemeError("Car: pair required");
}
return ((Cons)xs).car;
}
object Cdr(object xs) {
if (!Consp(xs)) {
throw new SchemeError("Cdr: pair required");
}
return ((Cons)xs).cdr;
}
// 数値のチェック
void CheckNumber(object xs) {
if (!Numberp(Car(xs)) || !Numberp(Car(Cdr(xs))))
throw new SchemeError("Number required");
}
// 組み込み関数
void MakePrimitive() {
sT.Value = sT;
sNil.Value = sNil;
Func<object, object> car = xs => Car(Car(xs));
MakeSymbol("car", car);
Func<object, object> cdr = xs => Cdr(Car(xs));
MakeSymbol("cdr", cdr);
Func<object, object> cons = xs => new Cons(Car(xs), Car(Cdr(xs)));
MakeSymbol("cons", cons);
Func<object, object> eq = xs => Car(xs) == Car(Cdr(xs)) ? sT : sNil;
MakeSymbol("eq?", eq);
Func<object, object> pair = xs => Consp(Car(xs)) ? sT : sNil;
MakeSymbol("pair?", pair);
Func<object, object> plus = xs => {
return (BigInteger)Car(xs) + (BigInteger)Car(Cdr(xs));
};
MakeSymbol("+", plus);
Func<object, object> mult = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) * (BigInteger)Car(Cdr(xs));
};
MakeSymbol("*", mult);
Func<object, object> minus = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) - (BigInteger)Car(Cdr(xs));
};
MakeSymbol("-", minus);
Func<object, object> div = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) / (BigInteger)Car(Cdr(xs));
};
MakeSymbol("/", div);
Func<object, object> equal = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) == (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol("=", equal);
Func<object, object> notEqual = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) != (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol("/=", notEqual);
Func<object, object> lt = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) < (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol("<", lt);
Func<object, object> le = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) <= (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol("<=", le);
Func<object, object> gt = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) > (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol(">", gt);
Func<object, object> ge = xs => {
CheckNumber(xs);
return (BigInteger)Car(xs) >= (BigInteger)Car(Cdr(xs)) ? sT : sNil;
};
MakeSymbol(">=", ge);
}
// 先読みバッファ
char ch;
// 記号の先読み
void NextChar() {
int c = Console.Read();
if (c == -1)
throw new SchemeError("EOF");
ch = (char)c;
}
// 記号の読み込み
char GetChar() {
return ch;
}
// 数値の読み込み
BigInteger GetNumber() {
var sb = new StringBuilder();
BigInteger val;
do {
sb.Append(GetChar());
NextChar();
} while (char.IsDigit(GetChar()));
if (!BigInteger.TryParse(sb.ToString(), out val)) {
throw new SchemeError("Number required");
}
return val;
}
// Symbol に含めてよい記号
bool IsSymbol(char c) {
string symCode = "!&*+-/:<=>?@^_~";
return char.IsLetter(GetChar()) || symCode.IndexOf(GetChar()) > 0;
}
// Symbol の読み込み
Symbol GetSymbol() {
var sb = new StringBuilder();
do {
sb.Append(GetChar());
NextChar();
} while (IsSymbol(GetChar()) || char.IsDigit(GetChar()));
return MakeSymbol(sb.ToString());
}
// 空白文字をスキップ
void SkipSpace() {
while (char.IsWhiteSpace(GetChar())) {
NextChar();
}
}
// リストの読み込み
object ReadList() {
SkipSpace();
if (GetChar() == ')') {
NextChar();
return sNil;
} else if (GetChar() == '.') {
NextChar();
object x = ReadS();
SkipSpace();
if (GetChar() != ')') {
throw new SchemeError("invalid dot list");
}
NextChar();
return x;
} else {
return new Cons(ReadS(), ReadList());
}
}
// S 式の読み込み
object ReadS() {
SkipSpace();
char c = GetChar();
if (char.IsDigit(c)) { // || c == '+' || c == '-') {
return GetNumber();
} else if (IsSymbol(GetChar()) ){
return GetSymbol();
} else if (GetChar() == '\'') {
NextChar();
return new Cons(sQuote, new Cons(ReadS(), sNil));
} else if (GetChar() == '(') {
NextChar();
return ReadList();
} else {
throw new SchemeError("invalid token");
}
}
// S 式の出力
void PrintS(object xs) {
if (Consp(xs)) {
Console.Write("(");
while (Consp(xs)) {
if (Consp(Car(xs))) {
PrintS(Car(xs));
} else {
Console.Write("{0}", Car(xs));
}
if (!Null(Cdr(xs))) Console.Write(" ");
xs = Cdr(xs);
}
if (!Null(xs)) Console.Write(". {0}", xs);
Console.Write(")");
} else {
Console.Write("{0}", xs);
}
}
// 環境
class Env {
public Symbol Key { get; set; }
public object Value { get; set; }
public Env Next { get; set; }
public Env(Symbol k, object v, Env n) {
Key = k;
Value = v;
Next = n;
}
public object Assoc(Symbol key) {
Env xs = this;
while (xs != null) {
if (xs.Key == key) return xs.Value;
xs = xs.Next;
}
return null;
}
}
// 変数の値を求める
object Lookup(Symbol key, Env env) {
object val = env != null ? env.Assoc(key) : null;
if (val == null) {
if (key.Value == null)
throw new SchemeError("unbound variable");
else
return key.Value;
}
return val;
}
// クロージャ
class Closure {
public object Args { set; get; }
public object Body { set; get; }
public Env Cenv { set; get; }
public Closure(object a, object b, Env e) {
Args = a;
Body = b;
Cenv = e;
}
}
// 引数の評価
object EvalArguments(object xs, Env env) {
if (Null(xs)) {
return sNil;
} else {
return new Cons(EvalS(Car(xs), env), EvalArguments(Cdr(xs), env));
}
}
// 変数束縛
Env AddBinding(object xs, object args, Env env) {
while (Consp(xs) && Consp(args)) {
env = new Env((Symbol)Car(xs), Car(args), env);
xs = Cdr(xs);
args = Cdr(args);
}
if (!Null(xs) || !Null(args)) {
throw new SchemeError("wrong number of arguments");
}
return env;
}
// 本体の評価
object EvalBody(object xs, Env env) {
object result = sNil;
while (Consp(xs)) {
result = EvalS(Car(xs), env);
xs = Cdr(xs);
}
return result;
}
object ApplyS(object fn, object args) {
if (fn is Delegate) {
// 組み込み関数
return ((Func<object, object>)fn)(args);
} else if (fn is Closure) {
// クロージャ
Closure c = (Closure)fn;
return EvalBody(c.Body, AddBinding(c.Args, args, c.Cenv));
} else {
throw new SchemeError("Function required");
}
}
// 特殊形式
object DefineS(object xs, Env env) {
if (!Symbolp(Car(xs)) || Null(Cdr(xs)))
throw new SchemeError("invalid define form");
((Symbol)Car(xs)).Value = EvalS(Car(Cdr(xs)), env);
return Car(xs);
}
object IfS(object xs, Env env) {
if (Consp(xs) && Consp(Cdr(xs))) {
object result = EvalS(Car(xs), env);
if (result != sNil) {
return EvalS(Car(Cdr(xs)), env);
} else if (Consp(Cdr(Cdr(xs)))) {
return EvalS(Car(Cdr(Cdr(xs))), env);
} else {
return sNil;
}
} else {
throw new SchemeError("invalid if form");
}
}
// S 式の評価
object EvalS(object xs, Env env) {
if (Numberp(xs)) {
return xs;
} else if (Symbolp(xs)) {
return Lookup((Symbol)xs, env);
} else if (Consp(xs)) {
// 特殊形式のチェック
if (Car(xs) == sQuote) {
return Car(Cdr(xs));
} else if (Car(xs) == sDefine) {
return DefineS(Cdr(xs), env);
} else if (Car(xs) == sIf) {
return IfS(Cdr(xs), env);
} else if (Car(xs) == sLambda) {
return new Closure(Car(Cdr(xs)), Cdr(Cdr(xs)), env);
} else {
// 関数呼び出し
object fn = EvalS(Car(xs), env);
return ApplyS(fn, EvalArguments(Cdr(xs), env));
}
} else {
throw new SchemeError("EvalS: invalid object");
}
}
// トップレベル
void TopLevel() {
Console.Write("mscm> ");
NextChar();
while (true) {
try {
PrintS(EvalS(ReadS(), null));
Console.WriteLine("");
} catch(SchemeError e) {
if (e.Message == "EOF") throw;
Console.WriteLine(e.Message);
// 入力のクリア
while (GetChar() != '\r') {
NextChar();
}
}
Console.Write("mscm> ");
}
}
// 実行
try {
MakePrimitive();
TopLevel();
} catch(SchemeError e) {
Console.WriteLine(e.Message);
}
$ dotnet script mscm.csx mscm> 'a a mscm> 12345 12345 mscm> '(1 2 3 4 5) (1 2 3 4 5) mscm> (car '(a b c)) a mscm> (cdr '(a b c)) (b c) mscm> (cons 'a 'b) (a . b) mscm> (pair? '(a b c)) t mscm> (pair? 'a) nil mscm> (eq? 'a 'a) t mscm> (eq? 'a 'b) nil mscm> (define a 10) a mscm> a 10 mscm> (define square (lambda (x) (* x x))) square mscm> (square 123) 15129 mscm> ((lambda (x) (* x x)) 10) 100 mscm> (define fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1)))))) fact mscm> (fact 10) 3628800 mscm> (fact 20) 2432902008176640000 mscm> (fact 50) 30414093201713378043612608166064768844377641568960512000000000000 mscm> (define iota (lambda (n m) (if (> n m) nil (cons n (iota (+ n 1) m))))) iota mscm> (iota 1 10) (1 2 3 4 5 6 7 8 9 10) mscm> (define map (lambda (f xs) (if (pair? xs) (cons (f (car xs)) (map f (cdr xs)))))) map mscm> (map (lambda (x) (* x x)) (iota 1 20)) (1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400) mscm> (define foo (lambda (x) (lambda (y) (+ x y)))) foo mscm> (define foo100 (foo 100)) foo100 mscm> (foo100 100) 200 mscm> (foo100 1000) 1100