Awesome
このesolangはTSG夏合宿2018のために作られました.
2020/5/3アップデート
言語仕様をきれいにしました. 旧ドキュメントはREADME-old.mdにあります.
Bots
TSGのslackの#sandboxではさまざまなbotが動いています.
例)
http://hakatashi.hatenadiary.com/entry/2017/12/03/173400 http://kurogoma.hatenablog.com/entry/2017/12/08/000000 http://hakatashi.hatenadiary.com/entry/2017/12/03/185033
初期にはbotが暴走してメッセージを垂れ流すなどの事件も起こっていました. 今回の"Bots"はこれを参考にして作りました.
動かし方
python3 interpreter.py ソースコード
で実行できます.
実行オプション
- -ds, --debug-stack
1ステップ毎に現在のスタックを表示します. - -de, --debug-env
1ステップ毎に現在の環境を表示します. - -d, --debug
1ステップ毎に現在のスタックと環境を表示します.
デバッグ用命令
プログラム中に #s
もしくは #e
を書いておくと,その時点でのスタックもしくは環境を表示します.
Syntax(文法)
<program> ::= <stack>
<stack> ::= <element>*
<element> ::= <ident> | <number> | <operator> | <funcdef>
<funcdef> ::= <ident> "(){" <stack> "}" | <ident> "(" <arguments> "){" <stack> "}"
<arguments> ::= <ident> | <ident> "," <arguments>
<ident> ::= [0-9A-Za-z]+
<number> ::= [0-9]+
<operator> ::= [+-*/@?] | "ic" | "id" | "oc" | "od"
Syntaxの日本語訳
botsのプログラムは1つのstackです. stackは,複数個のelementの列です. elementには4種類あります.
- ident 関数名や変数名として用いる,アルファベットと数字からなる文字列
- number
数字を表す数字からなる文字列 - operator
組み込み関数 - funcdef
関数定義
関数定義は,まず関数名,その次に0個以上の関数の引数を","で区切って"()"でかこったもの,その後に関数のボディであるstackを"{}"でかこったもの,を並べた列です.
プログラム例
たとえば,以下はどれも文法的に正しいbotsのプログラムです.(実行時エラーは発生したりする)
ic + 1 od @ 0
hoge hug4 PiYo 123 000 + - * / @ ?
g(x){ + 1 x ? + @ 0 x oc }
f(){ ic g f }
f
11 f(x){
56 g(y,z,r){
h(){}
} 78
} 90
Semantics(プログラムの挙動)
<!-- プログラムの状態は,「スタック」と「環境」からなります.「スタック」は「データ」のスタックとし,「環境」はidから関数定義への部分関数とします.プログラムの「データ」は「文字列」もしくは「数値」です. 初期状態では,「スタック」はソースコードで記述されたスタックとし,「環境」は組み込み関数の定義(後述)のみとします. プログラムは,「スタック」のトップにある「データ」に従って「スタック」と「環境」を書き換える,というステップを繰り返すことによって実行されます. 以下の説明では,「スタック」の書き換えの1ステップを `e x1 x2 x3 ... xn S` -> `y1 y2 ... ym S` の形で書くことにします.これは,『「スタック」のトップにある「データ」がeのとき,「スタック」の先頭からn+1個の「データ」をe,x1,...,xnとし,「スタック」の残りの「データ」列をSとすると,1ステップ後には「スタック」は `y1 y2 ... ym S` というデータ列になっている』ということを意味しています. -->プログラムの状態はデータの列であるスタックで表現されます.データは「識別子」もしくは「数値」もしくは「関数定義」です.初期状態では,状態スタックはソースコードで記述されたスタックとします.
プログラムは,スタックのトップにあるデータに従ってスタックを書き換える,というステップを繰り返すことによって実行されます.
以降の説明では,スタックの書き換えの1ステップを
e x1 x2 x3 ... xn S -> y1 y2 ... ym S
の形で書くことにします.これは,『スタックのトップにあるデータがe
のとき,スタックの先頭からn+1個のデータをe,x1,...,xn
とし,残りのデータ列をS
とすると,1ステップ後にはスタックは y1 y2 ... ym S
というデータ列になっている』ということを意味しています.
組み込み関数の挙動
+ - * /
加減乗除を行います.除算は切り捨て(pythonの//
と同じ負方向への丸め)です.
+ a b f S -> f (a+bの値) S
- a b f S -> f (a-bの値) S
* a b f S -> f (a*bの値) S
/ a b f S -> f (a/bの値) S
たとえば,
+ 4 5 - 6 * 7 / 8 @
は
+ 4 5 - 6 * 7 / 8 @
- 9 6 * 7 / 8 @
* 3 7 / 8 @
/ 21 8 @
@ 2
となります.
ic,id
文字,数字を入力します。
ic f S -> f (読みこんだ文字の文字コード) S
id f S -> f (読みこんだ数字の値) S
たとえば,123
を入力した場合、(1
の文字コードは49)
ic + 2 @
は
ic + 2 @
+ 49 2 @
@ 51
id + 2 @
は
id + 2 @
+ 123 2 @
@ 125
となります.
oc,od
文字,数字を出力します。
oc a S -> S
(文字コードがa
の文字が出力される)
od a S -> S
(10進表記したa
の値が出力される)
たとえば,oc 49
は
oc 49
となり,1
が出力され,
od 49
は
od 49
となり,49
が出力されます.
?
条件分岐です.
? a f g S -> f S
(a
が0でない数値のとき)
? 0 f g S -> g S
たとえば,
id ? oc od 49
は,
もし0
が入力されたら
id ? oc od 49
? 0 oc od 49
od 49
となり,49
が出力されます.
もし1
が入力されたら
id ? oc od 49
? 1 oc od 49
oc 49
となり,1
が出力されます.
@
プログラムを終了させます.
@ a S -> S
(exitコードaでプログラムを終了させる)
たとえば,
@ 123
は,exitコード123でプログラムを終了させます.
関数定義と関数実行の挙動
funcdef, ident
関数定義と関数適用を行います.
スタックのトップが
f(a1, ... ,an){ d1 ... dm }
という関数定義の場合,「f
がスタックトップにあった際の挙動」が変更されます.
(このため,正確にはプログラムの状態は「スタック」と「環境」の2つ組,とするべきですが,簡便のため省きました.)
funcdef S -> S
(ただし環境がアップデートされる)
関数が定義された状態で関数を適用すると,定義に従ってスタックが書き換えられます.
たとえば,
f(x){+ 1 x} f 42
というプログラムを実行すると,スタックの遷移は以下のようになります.
f(x){+ 1 x} f 42 @
f 42 @
+ 1 42 @
@ 43
f 42
という関数適用を1ステップ進めると,f
の本体である + 1 x
のx
を42
に置換した + 1 42
になります.
より詳しく書くと,関数f
がf(a1, ... ,an){ d1 ... dm }
として定義されているとき,
f x1 ... xn S -> d1[x1/a1, ... xn/an] ... dm[x1/a1, ... xn/an] S
とします.ここで,d[x1/a1, ... xn/an]
は,
d
が数値のとき
d
のままd
が識別子のとき
あるkについてxk
がd
と同じであれば,ak
どれも違う時,d
のままd
が関数定義のとき
d
の本体のスタック中のデータに対して,再帰的に[x1/a1, ... xn/an]
による置換を施す
たとえば,
f(x){ g(x){ + x 4 } } f 3 g 2 @
は
f(x){ g(x){ + x 4 } } f 3 g 2 @
f 3 g 2 @
g(x){ + 3 4 } g 2 @
g 2 @
+ 3 4 @
@ 7
となります.(g
中のx
が置換されていることに注意してください)
Semantics まとめ
+ a b f S -> f (a+bの値) S
- a b f S -> f (a-bの値) S
* a b f S -> f (a*bの値) S
/ a b f S -> f (a/bの値) S
ic f S -> f (読みこんだ文字の文字コード) S
id f S -> f (読みこんだ数字の値) S
oc a S -> S (画面に文字コードがaの文字が出力される)
od a S -> S (画面に10進表記したaの値が出力される)
? a f g S -> f S (aが0でない数値のとき)
? 0 f g S -> g S
@ a S -> S (exitコードaでプログラムを終了させる)
funcdef S -> S (ただし環境がアップデートされる)
f x1 ... xn S -> d1[x1/a1, ... xn/an] ... dm[x1/a1, ... xn/an] S (関数fがf(a1, ... ,an){ d1 ... dm }として定義されている)
Misc
- 関数型言語、継続渡し、動的束縛などが元ネタです.それぞれ知っておくと書く時に役立つかも.
- 冷静になって考えてみると,動的束縛どころではなくnon-capture-avoidingな置換になっているのでなかなか非常識.さすがにこの仕様は修正するかも.
- 型をつけたtyped botsっていうのを作りたいんですがいいかんじの型システムを思いつけていないので募集中です.