ねらい
Pxemによるコードゴルフ民である私はこれまで、プログラミング言語Pxemでコードゴルフをしてきた。その過程で自分が理解してきた仕様において自分でPxemの処理系を実装したことがある。しかし、ある日、その実装が他の実装とズレてることがわかった。その他、明記されていた仕様と実際の実装の仕様のズレもある。このままではコードゴルフという競技において私はいったいどういうルールでどうすればいいのか、支障をきたすことになる。そこで、この記事ではそもそもどんな実装をリファレンスとすべきか、あとなんやかんやをなんやかんやしてプログラミング言語Pxemの仕様を。いや、この記事だけに収まると思うか?とにかく標準化してしまおうと思う。いや、そんなのあってたまるかね。
この世にある実装
この世にある実装として、マジで標準化のもととすべきものとして派閥二つを上げる。
pxemi.7z
めちゃくちゃリファレンス実装。
旧名はpxemi-win.zipまたはpxem-source.cpp.zipである。これは「ぬこ」(現nk.)氏が配布しているものであり、最初は2008年11月に配布されてた。その後2018年に「pxemi.7z」としてもう一度配布された。C++わず。
実際のファイルが必要。
パスのデリミータとしてスラッシュとバックスラッシュを使用。これを開発してた当時ってWindows xpかWindows Vistaでやってたのかな?ファイルシステムとしてNTFSを想定してたんだろうなとは思われる。
2008年当時のサイトにおいて問題報告を歓迎する記述があった。つまり不完全である可能性があると主張。
text2pxem.pl
これは正式じゃないPxemの処理系、てか正式なPxemのプログラムにするスクリプト。同じく「ぬこ」(現nk.)氏の。普通のテキストファイルから実際にファイルを作成し、そこにプログラムを配置するやつ。
- Pxemの思い出 2024年2月29日アーカイブ
rpxem
wktk氏のRuby実装。初版2012年12月3日。現在の最新版は0.0.7で、2021年3月27日リリース。
ライブラリでもありコマンドラインで使える処理系でもある。これも実際のファイルが必要。あとライブラリそのものはパス名を既にbasenameにされたものを期待してるっぽく、そうじゃなきゃディレクトリ部分も普通にプログラムのソースとしてあるがまま読み込むのではないかと。
- rpxem - RubyGems
esolang-boxのPxem(Dockerコンテナ)
これはwktk/rpxemのラッパー。この実装では一つのテキストファイルを入力ファイルとする。最初の行をファイル名とし、残りの行をファイルの内容のようにする。
そしてこの実装も最初の行で表されるファイル名のディレクトリ部分を何も加工せずにあるまま読み込んでるじゃないか!basenameしてくれよ!とは思った。
- https://github.com/hakatashi/esolang-box
- esolang/pxem - Docker Hub
対象外となるPxemもどきとなる実装
rhwckl/pxem
コンパイルタイムなC++のPxem「もどき」。文法体系がもとのPxemと互換性がなく、なんじゃこれって思ったので対象外とした。例えば.i
に該当するやつがこっちでは.l
である。
プログラムの書き方
プログラムの書き方を明確にする。
Pxemのプログラムは、ファイル名とそのファイルの内容の二つの文字列により表現される。前者はメインルーチンに相当し、後者はサブルーチンならびに文字列定数に相当する。
ファイル名の制約
環境依存だの実装依存だのとするにはあまりにもいい加減すぎるのではないか。
その実装が置かれているファイルシステムの制約に従うことになる。
例えばNTFS、ext2/ext3/ext4、ZFS、あとなんやかんや。あるいはVMSとか。
しかし、そのファイルシステムにおいて、大文字と小文字の区別がつく英字のほか、+-!$%
の五つの記号もファイル名に含めることができるべきではないかと思う。例えば昔のDOSのファイル名、8.3形式しか使えないファイルシステムはPxemのサポートから外すべきである。
で、コードゴルファー的にはバイナリも使いたい。あと単バイナリ以外のエンコード方式も。その場合ヌル文字は使えないことは普通に納得でありこれこそマジで標準化の一つとすべきではなかろうかと。
あと文字数の制約としては何文字以上は作らせてくれ的なやつは秒では思いつかない。
basenameへの変更のやつ
ファイル名のやつは基本、ディレクトリ部分はコメントに相当するべきであると2008年の仕様ではそうなってた。しかし、ベースネーム部分の取得方法の標準化とか必要じゃないか。
いい例はPerlのモジュールFile::Specではなかろうかと思う。
ファイルの内容の制約
バイナリでもなんでもよいっていうルールにすべきである。あと好きなエンコードで書かせてくれ。特にUTF-8とかで。
正式じゃないプログラムの書き方
なんやかんやあって正式なプログラムの書き方に変換できればいいのではないか。
データ型
元の実装ではint
を使っていた。よって16ビット以上の整数もしくは任意桁数を保存できる整数を唯一のデータ型とすべきである。
正しい文法
pxemi.7zもRPxemも、正しくない文法のPxemをめちゃくちゃそのまま通してる。よってループ閉じがなされてなくても実行できる分まで実行するように標準化すべきではないか。
ループ系コマンドに関して、対応していないやつは全て未定義処理とすべきではなかろうか。
.a
は一律としてそうする。
.w
等のやつは、truthyによるループ入りの場合はそのまま内部に入り、プログラムのおわりを以って.d
と同等のことをすべきではないか。一方でfalseyによるループブレイクの場合は、未定義処理とすべきではないか。
リテラル
たのむから誰か1バイト単位として以外の値として解釈させてくれよ!UTF-8とかやらせてくれよ!でかい値がほしい時だってあるんだよ!
入出力
入出力の手段として標準入出力のみを想定するものとし、それ以外の入出力の手段があればそれは処理系拡張とする。
文字の入出力
EOF到達時に-1を得るものとすることはpxemi.7zもRPxemも合意を得てる。
文字も文字で今のところバイト単位「以外」のやつとかねえの?誰か!
んでエンコード方式をなんちゃらかんちゃら。
数値の入出力
一律として十進数。今のところどの実装もめちゃくちゃASCIIで。
非負整数は全て符号なしとして出力。
入力に関してはscanf("%d", &num)
の形式でなされてる。これはnum = strtol(str, &strend, 10)
に等価だとISO Cで規定されてる。よってそうするって方向で標準化すべきだ。すなわち、
- 空白文字(
isspace
)は全て消化 - 数値として読めるぶんだけ読む
んで、読んだ結果数値じゃなかった場合は、数値じゃなかった文字の前までの入力を食ったものとして現状ではゼロを入力したものとしてるっぽい。んで、ハイフンまたはプラスがあって、その次が数字じゃなかった場合はその記号だけ食ってゼロを得た的な何かってことでよくね?
あとオーバーフローやアンダーフローとかどうなのか。誰も規定したことがない。特に対策しなければ、「読めた分だけ読みまくり、INT_MAXを取得する」ものとなりそう。
それにしても、getchar()もscanf("%d",&num)も持ってるタイプのesolangって、特に後者の実装って何かしらの制約が生じがちではないか。例えば一行単位じゃないとムリ的なやつとか。
「一時領域」という表現
「ヌル値を初期値とするレジスタ」と表現を変えるべきだ。
.c
空だったらマジでEOFとすべきだ。pxemi.7zは遵守せず。
.s
いいんじゃね?
.v
(イテレータだけ逆にするという方法で処理を軽くするとかどう?)
.f
まあ特記するほどでもないか
.e
この仕様をesolangのディスコードサーバで伝えたらクソ仕様だと言われたし骸骨の絵文字でリアクションされた。スタックがフォークされるタイプのスタック型言語とは、なんとひどいことなんだろうかとは思われる。
字句解析のやりかた
.
の次の文字を以てもコマンドにならなければそれはリテラルの一部とすべきだという方向は合意されたのではないかと思う▽。
.r
これ、RPxemでは実装されてない。
ポップされた値が>0のときに[0,x)
を取得すること「だけ」は標準化されてる。
0の場合は着実にゼロのみを取得し、<0の場合は(x,0]
を取得するってことの方向性で標準化しないかね?
.w
等のループ開始コマンドと.a
によるループ終了コマンド
以下、「ループする条件」をtruthyとする。
2008年の仕様では(実例もまじえて)「ポップしたい要素の数が足りない」(つまりempty)場合をfalseyとしてるが、pxemi.7zはtruthyとしてる。RPxemもtruthyとしてる。これ、コードゴルフ民としてはわりと助かってる場合が多いかと記憶してる。
よってemptyをtruthyとするかどうかを処理系依存とし、ドキュメント「しなさい」ってことにするってのはどう?んで、emptyをランダムにtruthyにしたりfalseyにしたりするってことがあってもよくね?
.t
emptyだったらnopってことでよくね?
.m
レジスタがnullだったらnopってことでpxemi.7zもRPxemも2008年の仕様も一致してるっぽい
.d
サブルーチンから呼び出し元に処理のフローを戻し、スタックの中身を…うわ…吐くわこれ…この仕様はそのまま標準化すべきだ。
演算のやつ
アンダーフローとかオーバーフローとか誰も何もやってないし、ほったらかしていいんじゃないかな?
んでゼロ除算をめちゃくちゃ未定義処理とか処理系依存とかにしてるわけだね。
私だったら「ゼロ除算でリストが作れる」というふざけた拡張も考えてた。
あと減算と除算に関しては「二つの値x、yをポップし、大きい値をxしてop(x,y)する」ことにはなっている。設計した当初、どちらも非負整数であることしか想定してなかったろうなと思う。実際としてpxemi.7zもRPxemも、片方もしくは両方が負数であろうとも同様にするという仕様になってる。しかし、このルールを変えて、「絶対値が大きい値をxとしてop(x,y)する。ただし異符号の場合は正の方をxとする」としてもいいんじゃないのかとは思う。よって標準化するとなればこういう二つのやり方のうちどちらかにすべきでありどっちでやってるのかを明記すべきであるっていうルールにしたらどうなのか。
.e
しすぎて関数コール用のスタックが溢れたりしそうだけどいいの?
どうやって無限ループを最適化するんだろうねとは思う。最適化できるんだったらできてくれってはなるかな。
まとめ
まとめるにはどうなんだこれ。こんなんでいいのか。