一括表示

− 野ざらし言 −

[5731] N88-BASICのRENUMのようなマクロ

としき 2025/03/29(Sat) 14:22
http://toshiki.la.coocan.jp/
よそ様で話題に出ていたネタです。長いです。90行を越えてしまった。

いにしえのN88-BASICには、行番号が必須です。
プログラムを作っている最中は行番号の並びなんてぐちゃぐちゃになってしまう
ことも多々ありますが、ある程度固まったら、例えば10番おきとかにきれいに
整列させたいという希望があります。
これ、BASICのインタプリタ環境にいるときはRENUMコマンド一発で行けますが、
Vzでコードを書いているときは、なかなか一筋縄ではいかない。なぜというに、
RENUMコマンドって、コードの中のGOTO文なんかの飛び先まで書き換えてくれる
のですが、これをVzマクロでやろうとするとなかなか大変。
この大変そうなものに挑戦しようと思いました。が、ほとんど思っただけ。
きちんと仕様を考えてたらとんでもないことがいろいろ発覚したので、まず最初
はサブセットで動くもの。とはいえ、できるかどうかわからない最終的な仕様を
目指して拡張性のあるものを作るくらいなら、最終仕様のものまで一気に作って
しまおうか、という気もする。

いろいろな例外条件はあるのですが、基本的な外部仕様というか、こういう入力
がこうなってくれる必要がある、という例を以下に示します。
まず変換元。

100 IF A=B THEN 130 ELSE 150
130 GOTO 200
150 GOSUB   330:END
330 REM SUBROUTINE

これを変換するとこうなってほしいという変換後。

100 IF A=B THEN 110 ELSE 120
110 GOTO 200
120 GOSUB   130:END
130 REM SUBROUTINE

ジャンプ先が実在する行番号は、行番号の付け替えに伴い、コードの中身も置き
換わってくれることが必須。一方で、ジャンプ先が実在しない場合は、変換例の
110行目のように、変換が発生しないようにしたい。実機でもこういう仕様だった
と記憶しています。

ところで、上記で「最初はサブセット」なんてことを書きました。フルセットに
しない理由は、N88-BASICの仕様では、行番号の最大値は65529。そんなに大きな
行数のプログラムを作ったことがないので知りませんでした。が、なかなか微妙
な値です。とはいえ、もし簡単にこの値を使ったコードが実現できるのならば、
一気に作りたい。



さて、ここまでは事前の仕様検討で気がついた点です。
ここから実際のマクロを作りながら仕様を検討したら、次々と問題点が発覚。
まずは行番号の上限値。
たとえば、仮にサブセットとして上限値は32700でもいいのですが、処理を始める
前に上限を突破するかを確認し、もし突破するようならメインルーチンに入る前
に中止したい。これは初期値と増分と論理行数がわかれば計算できそう。
ただ、条件判定で気になるのが、仮に増分を1000番刻みの場合、行番号が32000の
次は33000になるわけですが、素直に作ると、なんとなく比較が破綻しそうな値。
ここはサブセットならではの制限をかける必要がありそう。
あるいはフルセットにするか。

また、BASICとして正当ではない行がある場合。
この場合、このファイルはN88-BASICのコードではないという理由で変換開始前に
処理停止。要は、途中まで変換して、そこでやめた、ということは回避したい。
この正当性の要件ですが、とりあえず思いつくものとしては、行番号で始まって
いない論理行。空行がある場合も、これは行番号が付されていないわけですから
当然処理中止。また、行頭は数字でも、数字の並びのあとが半角空白かタブでは
ない場合、つまり数字のあとに英字や記号がくっついている場合も処理中止。



ということで、私の考えたマクロのラフスケッチが以下のとおり。

初期設定
 増分はハードコーディング
 初期値は、開いているファイル自体の最初の行の行頭から取得

ファイル正常性確認
 初期値、増分、論理行数から上限チェック
 全ての行の行頭が数値であることのチェック

メインループ

終了処理



ここでいくつか質問。
まず、論理行の要件チェック。
ループまわして各行チェック、というのはわかります。
そして、1文字ごとに cd で見ていくのが確実ですが、そうではない、なんか楽
な方法はないものか、と。
そして、行番号の上限チェック。
初期値の取得はカーソルをファイル先頭に移動させて &n ですが、論理行数って
カーソルをファイル末尾に移動して ln というのが常套手段でしょうかね。
そして、モロモロ掛け算とか足し算とかで、さて上限値を超えたときのチェック
はどうすればよいでしょうか。
さらに、将来的なフルセットの場合、行番号の初期値は40000だった、というもの
にも対応させたいわけですが、これって &n では対応できませんよね。ならどう
すればよいか、ということについてもアイディアをお伺いできればと思います。
 

[5733] Re: N88-BASICのRENUMのようなマクロ

めざら 2025/03/29(Sat) 23:29
[5731]へのコメント http://mezala.la.coocan.jp/
> RENUMコマンドって、コードの中のGOTO文なんかの飛び先まで書き換えてくれる

これは一番大事なキモというやつですが,色々なやり方があると思いますので,一旦置いておきましょう。

> 最終仕様のものまで一気に作ってしまおうか、という気もする。

そうですね。としきさんがサブセットで満足できるとは思えません。
ただ,現実的な一定の制限を持つ仕様は必要だと思います。

> ただ、条件判定で気になるのが、仮に増分を1000番刻みの場合、行番号が32000の
> 次は33000になるわけですが、素直に作ると、なんとなく比較が破綻しそうな値。

増分が1000番刻みって,あまりにも非現実的な気がするのですが,本当にそういうものを目指すのですか? そもそも増分をどこで設定するのでしょう。ここがはっきりしないと先に進めないように思います。

> また、BASICとして正当ではない行がある場合。

この判定は,無理ということはないですか?
そもそも正当な行とは?
> 数字のあとに英字や記号がくっついている場合も処理中止。
これだけならそれほど問題はありませんが……

> 論理行の要件チェック。

この要件を列挙していただけますか?
でないと,なぜ「1文字ごとに cd で見ていくのが確実」なのか分かりません。

> 行番号の上限チェック。

行数と増分の積というわけではないのですか?

> 初期値の取得

例えば,初期値と増分を決めておいてはいけないのですか?
経験的には,番号初期値は 1000 に決めていたりしたように思いますが……。
フルセットを目指すのは大切だと思いますが,行番号の初期値が 40000 とか,自分にとっては相当の違和感があります。

それは別として,&n で対応できないロング整数の取得の方法はないわけではないですが,けっこう面倒です。例えば,
「カーソル位置の数字列を数値として取得するには」(VZマクロFAQ)
http://mezala.la.coocan.jp/vz/faq/opera.html#opera_015

[5734] Re^2: N88-BASICのRENUMのようなマクロ

としき 2025/03/30(Sun) 17:19
[5733]へのコメント http://toshiki.la.coocan.jp/
まずはじめに。

> 一方で、ジャンプ先が実在しない場合は、変換例の
> 110行目のように、変換が発生しないようにしたい。実機でもこういう仕様だった
> と記憶しています。

タッチアンドゴーで実家に行き、ちょっとN88-BASICの仕様を確認してきました。
滞在時間5分。
結果、記憶違いもあって。
飛び先の部分の変換が発生しないという部分は正解でしたが、行番号の変換自体
は行われていました。そして、「Undefined line number」なるエラーを吐く。
このエラーは、例えばGOTOの飛び先がないときに発生するエラーなんで、わりと
よく見かけるものですが、まさかプログラムの実行時以外で見かけるとは覚えて
いなかった。
もうひとつ、RENUMコマンドの引数って「新行番号、旧行番号、増分」なんです
が、それぞれの引数は省略可能。そして増分を省略した場合のデフォルトは10。
ここで、先に例に出した4行のプログラムで「RENUM 65520」とかやると、最後
の行の行番号はデフォルトの最大値の65529を越えてしまいます。この場合は
「Illegal function call」エラーとなります。こちらはマニュアルにも明記
してあります。今回検討しているマクロでは、サブセット版であればこの上限値
は別の値でもいいでしょう。

ということで

> 増分が1000番刻みって,あまりにも非現実的な気がする

これはおっしゃるとおり。
ただ、「増分が1000」ではなく「初期値が65500」でもいいのですが、要は上限を
超えるときの挙動をあらかじめ決めておきたい、ということです。
そして、その挙動としては、変換を全くはじめることなくマクロが終了すること
を希望しています。

> そもそも増分をどこで設定するのでしょう。

これは、#5731にも書きましたが、マクロのコード内にハードコーディング。
絶対にないと思いますが、将来的には可変にしたい。

> > また、BASICとして正当ではない行がある場合。
> この判定は,無理ということはないですか?

テキスト処理だけで行うと無理かもしれない。だけど、#5731に書いた正統性要件
だけでもチェックしたい。つまり

・半角数字で始まっていない行がある
・行頭の半角数字を数値として取得したときに上限値を越えている
(上限値は別途定義)
・行頭の半角数字列のあとが半角空白でもタブでもない

これらいずれかの要件を満たす行が1つでもある場合は処理を開始したくない。
ということで、このチェックに当たって私が考えた方法が
「1文字ごとに cd で見ていく」
なのです。というか、これしか思いつかなかった。
もちろん、他にもっと良い方法があればご教示いただきたい。

> > 行番号の上限チェック。
> 行数と増分の積というわけではないのですか?

たぶんそれでいくと思うのですが、何か見落としがあることが怖い。

> > 初期値の取得
> 例えば,初期値と増分を決めておいてはいけないのですか?

上にも書きましたが、増分はマクロコード埋め込みでよいと思っております。
が、初期値については、BASICのコードから、すなわち処理対象ファイルから取得
したい。そして、どうやって取得するかというと、ファイル先頭の行の行番号を
採用するということで。って、このへんのことは#5731にも書いていましたね。

> 「カーソル位置の数字列を数値として取得するには」(VZマクロFAQ)

ぅぉぉ、こういうのがほしかったんですよ。
とても参考になります。
 

[5735] Re^3: N88-BASICのRENUMのようなマクロ

めざら 2025/03/30(Sun) 23:40
[5734]へのコメント http://mezala.la.coocan.jp/
> 要は上限を超えるときの挙動をあらかじめ決めておきたい、ということです。

それ,先に言ってくださいよ。(^^;
こちらも察することができなくて申し訳ないですが,極論から入るみたいな論法って元公務員は慣れていないので苦手なんです。

先に不整合な値を確認して実行可不可を決めるのは至極真っ当だと思います。

将来的に,開始行番号や増分の初期値を可変にするのは簡単ですね。起動時にオプションの初期値メニューを表示して,そのままでよければ一打で実行というのはよくある。わたしもそういうマクロは作りますから,部品を流用できます。

列挙していただいた正統性要件であれば,わりにチェックは簡単ですね。
一行ごとに判定するのは時間がかかりますが,まぁ,そこは人間が目視で判断しようとするのは現実的でないのは明らかですから,これも至極真っ当。

> 「1文字ごとに cd で見ていく」

カーソルの移動がともなうので,かなり速度が落ちますね。
他のやりようとしては,ラインバッファを文字列として取得し,文字列関数を使うという手が考えられます。実際に試してみないと,どの関数が適任かわからないけど例えば…
&Sw(p) : 文字列pのスペース/タブまでの文字数
こんなのが如何にも使えそうじゃないですか?

> ・半角数字で始まっていない行がある
これは超簡単で,(cd<$30 || cd>$39) で判定できますね。
ただし,どのみち
> ・行頭の半角数字を数値として取得
ということであれば,
行頭で &n を実行して戻り値が -1 なら数字ではないと判定しつつ
行番号も取得できます。

ただし,
> したときに上限値を越えている
……かどうかは,正確な判定が必要ですね。

実は,わたしにはその必要性がちょっと理解できませんでした。
行番号の値が上限値を越えていても,処理の中で丸めてしまえばよくないですか?

・行頭の半角数字列のあとが半角空白でもタブでもない
ここに &Sw(p) が使えそうですね。最初の空白文字まで5文字以下ならよさそう。
もちろん上限値も5文字に含まれるので,複数条件でチェックする必要がありますけれど,5桁で先頭の数字が5以下なら &n で取得した値で大丈夫(負になる可能性もあるので判定は慎重にすべきですが)。6だったら,より正確に判定が必要。5桁で先頭が7以上は不正な値確定。

> ぅぉぉ、こういうのがほしかったんですよ。

上限値が16ビットで(正負を注意すれば)扱える範囲なので,ご参考までに。

[5736] Re^4: N88-BASICのRENUMのようなマクロ

としき 2025/04/01(Tue) 00:28
[5735]へのコメント http://toshiki.la.coocan.jp/
> > 要は上限を超えるときの挙動をあらかじめ決めておきたい、ということです。
> それ,先に言ってくださいよ。(^^;

いや、申し訳ない。
実のところ、#5733を読んで、自分の中で考えが熟成されたのですよ。
とはいえ、#5731で書いたような、増分が1000番刻みでもN88-BASICは対応しているわけでして、確かに非現実かもしれませんが、個人的にはぜひとも対応したい。
という思いが先走ってしまったというのもあるかもしれません。
結果、十分に一般化された日本語表現ができていなかった、と。

> &Sw(p) : 文字列pのスペース/タブまでの文字数
> こんなのが如何にも使えそうじゃないですか?

そうか、文字列関数ってのがありましたね。
私がほとんど使ったことのない分野なんで、一から勉強、かな。
でも、ラインバッファを文字列として取得するってところからわからない。
別の変数にラインバッファの中身の実体をコピー?
それとも、ラインバッファのアドレスを別の変数にコピー?
はたまた、ラインバッファのアドレスを直接、文字列関数に渡す?

> 行番号の値が上限値を越えていても,処理の中で丸めてしまえばよくないですか?

ここはそうかもしれない。
いろいろと手抜きしたら、結果的にそうなるかもしれず。

最短で次の週末あたりには、手抜きバージョンが動き始めているかも。
もちろん、そんな甘甘の見積もりどおりに行くわけはないけど。

[5737] Re^5: N88-BASICのRENUMのようなマクロ

めざら 2025/04/01(Tue) 21:31
[5736]へのコメント http://mezala.la.coocan.jp/
> ラインバッファを文字列として取得するってところからわからない。

ごめんなさい。「取得」は不正確でした。
「ラインバッファの『文字列』化」と言うべきところでした。

どういうことかと言うと,文字列操作関数は対象が「文字列」でないと,
最悪暴走する可能性があるんですね。「文字列」とは釈迦に説法ですが,
終端にヌル(00h)がある文字キャラクタの並びです。ラインバッファは
他のバッファと異なり,もともと終端にヌルがありません。そのため
ラインバッファの終端にヌルを意図的に埋め込んで「文字列」にします。
そして,使い終わったら行編集のキャンセルコマンドをかませます。

> はたまた、ラインバッファのアドレスを直接、文字列関数に渡す?

要は,これになります。他のバッファに転送するわけではありません。

次のサンプルは対象とするコードの行番号正当性をチェックするだけのもの。
上限の値が16ビットに微妙に収まる範囲のようですので,より正確な判定は
もう少し仕様を固めてからでよいかと思いました。

= Test
*mad for vz!
1 ^\ "Bas_chk"
        ?.
;       #^                              ;ファイルの先頭からなら生かす
        #<
        e-,                             ;エラーのフラグ(仮) (e)
        ;; ==== メインのループ
 :L     &n (r+1)??{ e+, >E }            ;行番号取得,不正なら終了
        #28 wa..19.2-,                  ;ラインバッファの文字列化
        a=wa..18,                       ;ラインバッファ先頭アドレス (a)
        &Sw(a) b=r,                     ;行番号の桁数 (b)
        c=a.0,                          ;行頭キャラクタ (c)
        #54                             ;行編集のキャンセル
        (b>5)?>E                        ;行番号が5桁を超えるエラー
        (b==5)?{ (c-$30>6)?>E }         ;5桁の場合で先頭が6を超えるエラー
        ;; (上限を確認するか or 丸めるか)
        #> #d (r)??>L
        (e)?{   ;; ==== エラーの処理
        :E      &m("行番号不正またはファイルの最後")
        }
 :Z     ;; ==== Quit
        &d #] .
*

第一段階のチェックが終わったら,次は飛び先番号の洗い出しという手順かと。

[5744] Re^6: N88-BASICのRENUMのようなマクロ

めざら 2025/04/03(Thu) 08:31
[5737]へのコメント http://mezala.la.coocan.jp/
> 第一段階のチェックが終わったら,次は飛び先番号の洗い出しという手順かと。

【\s[0-9]+】の正規表現で,もれなく拾えるでしょうか?

見たところ,番号の手前に必ず空白があるようですが,後ろは空白とは限らず改行やコロン(:END)があると見受けられます。

行番号の前に空白があったら不正ですよね。

[5745] Re^7: N88-BASICのRENUMのようなマクロ

としき 2025/04/04(Fri) 00:15
[5744]へのコメント http://toshiki.la.coocan.jp/
今日、会社の昼休みに、ちょっとマクロをいじってみていました。

> 【\s[0-9]+】の正規表現で,もれなく拾えるでしょうか?

そうか、\s という表記がありましたね。
試してみたのは【[ \t]+[0-9]】でしたが、なんとなくうまく行っているっぽい。

ジャンプ先行番号の条件については、おそらくめざらさんは正しく認識されていると思います。

で、ちょっと悩んだのが、事前チェックの条件の一つである空行。
プログラムの終端というか最終行、大抵は空行なんですよね。
これの扱い、どうしようかな。

[5738] Re^5: N88-BASICのRENUMのようなマクロ

めざら 2025/04/02(Wed) 08:34
[5736]へのコメント http://mezala.la.coocan.jp/
> 最短で次の週末あたりには、手抜きバージョンが動き始めているかも。

次の週末って,今週の土曜付近ってことですか?
仕様のすり合わせが完了するとは思えないので難しいかと。

ただ,学校が春休みでヒマなため,先走って自分の案をひとつ出しておきます。


■ goto や gosub の飛び先と振り直す行番号をどう同期するのか。
VZマクロは一般の高級言語と違い,無限に配列を確保できるわけではないので,そんな制限を何とかするには別に新規ファイルを開いて,そこに一時的にリストアップしてゆく方法が考えられます。

・ファイル全体に対してチェックを行って,飛び先行番号リストを作る.
・初期値と増分の設定にしたがって,番号付替えを行ってゆく.
・行単位で,飛び先リストと照合し,一致したらファイル全体で置換.

問題は,部分一致で置換してしまう可能性をどうするのか。
これは検索オプション Ew(ワードサーチ)を有効にすればよいのかな。

あとは,複数回出現する飛び先があるときに,どうリストするのか。
一個を置換してしまった後で,他方を新しく付け替えた行番号にリンクしてしまったりしたら,それこそ最悪。

一旦,としきさんからの案を待ちます。

↑ このページの先頭