1. HOME
  2. ブログ
  3. 技術系
  4. 魔法陣プログラミングMysticalの環境構築してみた

BLOG

ブログ

技術系

魔法陣プログラミングMysticalの環境構築してみた

この記事を読むのに必要な時間は約 10 分です。


この記事では、PostScriptに似た個性的なプログラミング言語「Mystical」の実行環境を、ChromeOS内のLinux (Debian) 環境に構築しようと試みた記録と、その過程で遭遇したエラー、そして最終的に解決に至った道のりを紹介します。Mysticalは、公式ドキュメントによると、プログラムコードから美しい幾何学模様(まるで魔法陣!)を生成できる言語です。今回はVSCodeをIDEとして使用しました。

Mysticalで生成される画像のイメージ (画像は公式ドキュメントより)


環境構築編【とにかく使いたい人向け】

まずは、Mysticalを動かすための環境構築手順を簡潔にまとめます。いくつかのハマりポイントがありましたが、以下の手順で進めれば、比較的スムーズにセットアップできるはずです。

1. 必要なツールのインストール

Mysticalの実行には、Git、Perl、そしてPostScript処理のコアとなるGhostscript、画像変換ユーティリティのNetpbmが必要です。ChromeOSのLinuxターミナルで以下を実行します。

sudo apt update
sudo apt install git perl ghostscript netpbm

netpbm-free はDebian系でのパッケージ名です。環境によっては netpbm-free の場合もあります。

2. Mystical と dmmlib の入手

Mysticalは、dmmlib というライブラリに依存しています。これらをGitでクローンします。

まず、Mystical本体のリポジトリをクローンします。(リポジトリのURLは適宜確認してください。記事執筆時点ではhttps://github.com/denismm/mystical_psです。)

cd ~ 
git clone https://github.com/denismm/mystical_ps mystical_ps 
cd mystical_ps

次に、mystical_ps ディレクトリの中に dmmlib をクローンします。SSHキーで問題が発生したため、HTTPS経由でのクローンがおすすめです。

git clone https://github.com/denismm/dmmlib.git

これで、~/mystical_ps/dmmlib/ というディレクトリ構造になります。

3. 【重要】mystical.ps の修正 (エラー回避)(2025/5/22時点のファイル)

これが最大のポイントです。オリジナルの mystical.ps のままでは、環境によってエラーが発生します。dmmlib 内のライブラリファイル群を、mystical_dict のスコープに入るに読み込むように修正します。

mystical.ps ファイルの先頭部分を以下のように変更してください。

修正前 (Original):

%!
/mystical_dict 30 dict def
mystical_dict begin

(dmmlib/base.ps) run
(dmmlib/textbase.ps) run
(dmmlib/lines.ps) run
(dmmlib/polar.ps) run
...

修正後 (Modified):

%!
(dmmlib/base.ps) run
(dmmlib/textbase.ps) run
(dmmlib/lines.ps) run
(dmmlib/polar.ps) run

/mystical_dict 30 dict def
mystical_dict begin
...

つまり、(dmmlib/...) run の行を、/mystical_dict 30 dict def および mystical_dict begin の行よりも上に移動させます。

4. ヘルパースクリプト gstopdf の準備 (推奨)

PostScriptファイル (.ps) をPDFに変換するために、Ghostscriptのオプションを適切に設定したヘルパースクリプトを用意すると便利です。公式ドキュメントで紹介されているものを参考に、Linux向けに調整します。

ホームディレクトリの bin フォルダ(なければ mkdir ~/bin で作成)に gstopdf という名前でファイルを作成し、以下の内容を記述します。

#!/usr/bin/perl
use strict;
use warnings;
use autodie;

die "No files" if @ARGV == 0;

foreach my $psfile (@ARGV) {
    my ($basefile) = $psfile =~ /^(.*)\.ps$/ or die "Not a ps file: $psfile";
    my $pdffile = $basefile . ".pdf";
    # Ghostscriptのパスは環境に合わせて確認してください (通常 /usr/bin/gs)
    system ("/usr/bin/gs", "-r720", "-dNOSAFER", "-dNOPAUSE", "-dNOEPS", "-sDEVICE=pdfwrite", "-sOutputFile=$pdffile", "-dBATCH", "$psfile");
}

実行権限を付与し、パスを通します。

chmod +x ~/bin/gstopdf
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

5. サンプルファイルの実行

公式ドキュメントにある最初のサンプル (first.ps) を実行してみましょう。mystical_ps ディレクトリ内に first.ps というファイル名で以下の内容を保存します。

%!PS

(mystical.ps) run

72 dup scale      % make the unit 1 inch
4.25 5.5 translate      % move to the center of an 8.5x11 page
4 dup scale      % make the unit 4 inches
{
    0 0 1 0 360 arc stroke
    0 -0.25 0.5 180 360 arc fill
    -0.5 1 0.5 {
        0.25 0.125 0 360 arc fill
    } for
} mystical

showpage

ターミナルで mystical_ps ディレクトリに移動し、以下を実行します。

gstopdf first.ps

first.pdf というファイルが生成され、円形の図形が表示されれば成功です!

5. 実際に魔法陣を書いてみた


エラー修正編【詳細な道のり】

さて、上記の手順にたどり着くまでには、いくつかのエラーとの長い戦いがありました。ここではその過程で遭遇した主なエラーと、原因究明のポイント、そして最終的な解決策に至った経緯を(やや詳細に)記録しておきます。

始まり:白紙のPDFと謎のエラー

最初のセットアップ後、first.ps を実行すると、PDFは生成されるものの白紙。Ghostscriptのログには以下のようなエラーが出ていました。

Error: /undefined in arg
Operand stack:
   --dict:7/12(L)--   Spell

ここから、/Spell という名前オブジェクトがオペランドスタックに予期せず存在し、何らかのオペレータがそれを不適切な引数として処理しようとしていることが推測されました。

迷宮:`dict_safe_func` とデバッグコードの罠

調査を進めると、dmmlib/base.ps 内で定義されている `dict_safe_func` という複雑な関数が怪しいとにらみました。この関数は、Mystical内の多くの関数定義でラッパーとして使用されています。そこで、この関数や、それが使われている関数(/draw_ring, /parsecodeなど)、さらにはそれらを呼び出す /mystical_get_spell に多数のデバッグプリント (print, pstack) を仕込みました。

しかし、デバッグは一筋縄ではいきません。デバッグコードの挿入方法が悪く、新たなエラーを誘発してしまうこともありました。

  • /syntaxerror in (binary token, type=159): /mystical_get_spell の定義を修正した際に、ファイルに何らかの不可視文字やエンコーディングの問題が混入したことで発生。コードを慎重に再記述することで解消。
  • /stackunderflow in --exch--/stackunderflow in --type--: デバッグのために手続きオブジェクトを == オペレータで表示しようとしたり、pstack のタイミングが悪かったりしたことで発生した、デバッグコード自体に起因するエラーでした。デバッグ方法をより安全なものに変更することで解消。

これらの回り道を経て、dict_safe_func 自体のロード時エラーの可能性は低いこと、問題はMysticalのランタイムロジックにあることが徐々に明らかになってきました。

解決の鍵:スコープとライブラリ読み込み順序

dmmlib/base.ps の先頭では、/arg { exch def } def というマクロが定義されています。これは、`dict_safe_func` をはじめとする多くの場所で引数処理のために使われる重要なものです。

オリジナルの mystical.ps では、以下のように mystical_dictという専用辞書のスコープ内で dmmlib のファイル群を読み込んでいました。

/mystical_dict 30 dict def
mystical_dict begin % カレント辞書が mystical_dict に
    (dmmlib/base.ps) run % /arg などが mystical_dict に定義される
    (dmmlib/textbase.ps) run
    (dmmlib/lines.ps) run
    (dmmlib/polar.ps) run
    % ... Mysticalの各種定義 ...
end % mystical_dict がスコープから外れる

この構造では、/arg やその他の dmmlib で定義されたユーティリティが mystical_dict の中に格納されます。しかし、dict_safe_func の内部処理や、それによって定義された関数が実際に実行される際の辞書スタックの状態によっては、この mystical_dict が期待通りに参照されず、結果として /arg が見つからない (`/undefined`) 状況が発生し、それが巡り巡って私たちの遭遇した不可解なスタックエラーや `/undefined in arg` を引き起こしていたと考えられます。

そこで、この仮説に基づき、dmmlib のファイル群の読み込みを、mystical_dict beginに移動しました。

(dmmlib/base.ps) run % これらを先に読み込む
(dmmlib/textbase.ps) run
(dmmlib/lines.ps) run
(dmmlib/polar.ps) run

/mystical_dict 30 dict def
mystical_dict begin
    % ... Mysticalの各種定義 ...
end

これにより、/arg などのユーティリティがよりグローバルな `userdict` に定義され、どのスコープからでも安定して参照できるようになり、問題が解決しました。まさに「目から鱗」の解決策でした。

まとめ

Mysticalのような個人開発のプロジェクトでは、このような環境依存や設計の機微に起因する問題に遭遇することがあります。今回のエラーは、PostScriptの辞書スタックとスコープ管理の複雑さが、ライブラリの設計と密接に関わっていることを示す良い例でした。

もし同様の問題に直面している方がいれば、この「ライブラリ読み込み順序の変更」が助けになるかもしれません。そして、時には原点に立ち返って作者のドキュメントやコミュニティの情報を探ることが、解決への近道になることもありますね。
今回のエラーについてはgitにてIssueとして報告させていただいております。https://github.com/denismm/mystical_ps/issues/9

紆余曲折ありましたが、無事に魔法陣(?)を描画できる環境が手に入り、大変満足しています!


  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

関連記事