/bots

esolang for TSG 2018 summer camp

Primary LanguagePython

この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(プログラムの挙動)

プログラムの状態はデータの列であるスタックで表現されます.データは「識別子」もしくは「数値」もしくは「関数定義」です.初期状態では,状態スタックはソースコードで記述されたスタックとします.

プログラムは,スタックのトップにあるデータに従ってスタックを書き換える,というステップを繰り返すことによって実行されます.

以降の説明では,スタックの書き換えの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 xx42に置換した + 1 42 になります.

より詳しく書くと,関数ff(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についてxkdと同じであれば,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っていうのを作りたいんですがいいかんじの型システムを思いつけていないので募集中です.