term

Table of Contents

fdisk

ちょっと息抜きにOpenBSDでのfdiskをやってみる。味噌はvnconfigを利用して 普通のファイルをdiskに見立てる技だなあ。河豚板さんからの情報によります。

ob$ dd if=/dev/zero of=mydisk bs=1M count=4000
4000+0 records in
4000+0 records out
4194304000 bytes transferred in 60.370 secs (69476627 bytes/sec)
ob$ doas vnconfig vnd0 /home/sakae/mydisk
ob$ doas fdisk vnd0
Disk: vnd0      geometry: 81920/1/100 [8192000 Sectors]
Offset: 0       Signature: 0x0
            Starting         Ending         LBA Info:
 #: id      C   H   S -      C   H   S [       start:        size ]
-------------------------------------------------------------------------------
 0: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 1: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 2: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 3: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
ob$ doas fdisk -iy vnd0
Writing MBR at offset 0.
ob$ doas fdisk vnd0
Disk: vnd0      geometry: 81920/1/100 [8192000 Sectors]
Offset: 0       Signature: 0xAA55
            Starting         Ending         LBA Info:
 #: id      C   H   S -      C   H   S [       start:        size ]
-------------------------------------------------------------------------------
 0: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 1: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 2: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
*3: A6      1   0  29 -  81919   0 100 [         128:     8191872 ] OpenBSD

ob$ doas fdisk -gy vnd0
ob$ doas fdisk  -v vnd0
Primary GPT:
Disk: vnd0       Usable LBA: 34 to 8191966 [8192000 Sectors]
GUID: 6b16cdd8-db24-4e03-b46a-554dbc1d1e1b
   #: type                                 [       start:         size ]
      guid                                 name
------------------------------------------------------------------------
   0: OpenBSD                              [          64:      8191903 ]
      b1f99615-5eba-47fe-8e94-cbd3c194dd5c OpenBSD Area

Secondary GPT:
Disk: vnd0       Usable LBA: 34 to 8191966 [8192000 Sectors]
GUID: 6b16cdd8-db24-4e03-b46a-554dbc1d1e1b
   #: type                                 [       start:         size ]
      guid                                 name
------------------------------------------------------------------------
   0: OpenBSD                              [          64:      8191903 ]
      b1f99615-5eba-47fe-8e94-cbd3c194dd5c OpenBSD Area

MBR:
Disk: vnd0      geometry: 81920/1/100 [8192000 Sectors]
Offset: 0       Signature: 0xAA55
            Starting         Ending         LBA Info:
 #: id      C   H   S -      C   H   S [       start:        size ]
-------------------------------------------------------------------------------
 0: EE      0   0   2 -  81919   0 100 [           1:     8191999 ] EFI GPT
 1: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 2: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
 3: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused

詳細は、/usr/src/sbin/fdisk/part.c あたりを見て楽しめ、との事。 rdroot/boottmp/usbfadmの大作もお忘れなく。fdiskの編集コマンドを作成して それを実行するとは大変な苦労だなあ。

bug gone

前回発見したバグを潰しておく。

fn make_colection(sec string) ![]string {
        org :=os.ls('/usr/share/man/man${sec}')!
        mut rv := []string{}
        for s in org {
                rv << s.before('.${sec}')
        }
        rv.sort()
        return rv
}

元はbefore('.')だったものに、セクション番号を追加しただけ。

ob$ ./mc 5 login
login.conf

これで安心。待て、ループ内の不変式は、ループ外へ移動させとくのが常識だろう。 毎回計算するのは無駄。でも賢いコンパイラーなら、そんなの自動でやってくれるよ。

そんな事より、beforeの実装を見ておく方が有益だぞ。

// before returns the contents before `sub` in the string.
// If the substring is not found, it returns the full input string.
// Example: assert '23:34:45.234'.before('.') == '23:34:45'
// Example: assert 'abcd'.before('.') == 'abcd'
// TODO: deprecate and remove either .before or .all_before
pub fn (s string) before(sub string) string {
        pos := s.index_(sub)
        if pos == -1 {
                return s.clone()
        }
        return s[..pos]
}

ここでもスライスが大活躍してました。いや、それより、こちらの方が重大な責務を 負っている裏方ですよ。

// index returns the position of the first character of the input string.
// It will return `-1` if the input string can't be found.
@[direct_array_access]
fn (s string) index_(p string) int {
        if p.len > s.len || p.len == 0 {
                return -1
        }
        if p.len > 2 {
                return s.index_kmp(p)
        }
        mut i := 0
        for i < s.len {
                mut j := 0
                for j < p.len && unsafe { s.str[i + j] == p.str[j] } {
                        j++
                }
                if j == p.len {
                        return i
                }
                i++
        }
        return -1
}

今回の場合は、こちらが発動されるのか。

// index_kmp does KMP search.
@[direct_array_access; manualfree]
fn (s string) index_kmp(p string) int {

クヌース–モリス–プラット法

term

今度は前回ちょろっと作成したコード部分で出力を充実させてみよう。目標は補完 の候補をemacsの様にドロップダウン・リストの形式で表示する事。候補リストが段々 絞られてリストが縮小してく様を見てみたいんだ。

これには前前回にひょんな事から登場したtermライブラリィーで何とかなりそうな雰囲気が あるぞ。早速、調べてみると、例が出てた。ちょっと改変した。

import term
import os

fn main() {
        term.clear() // clears the content in the terminal
        width, height := 20, 5
        println(term.header_left('TITLE', '='))
        term.set_cursor_position(x: width / 2, y: 1 + (height / 2))
        println(term.bg_blue('hello world'))
        term.set_cursor_position(x: 0, y: height)
        for {
                if var := os.input_opt('press q to quit: ') {
                        if var != 'q' {
                                continue
                        }
                        break
                }
                println('')
                break
        }
        println('Goodbye.')
}

実行結果。

==== TITLE =====================================================================

         hello world

press q to quit: q
Goodbye.
sakae@lu:mc$

大分、期待してたのとは違う。色落ちしちゃってるけど、青くなってるのは、hello world 文字列のバックグラウンドだけ。全体が青になると思っていた。タイトルの = がエリア サイズの20を無視して、本来の端末幅まで広がってる。widthで宣言した20でクリッピング してくれないの? 兎も角、期待した様な画面制御は実施してくれていなかった。ちゃんとやろうとすると、 茨の道だな。 どうせ茨ならグラフィック関係に道が開けている、 A V module for designing terminal UI apps の方が良さそうだな。

>>> import term
>>> term.yellow('hello')
hello    ;; 黄色で文字列が表示されました。
>>> term.yellow('hello').split('')
['', '3', '3', 'm', 'h', 'e', 'l', 'l', 'o', '', '3', '9', 'm']
>>> mut out := ''
>>> for x in s { out += '${x.hex()} ' }
>>> out
1b 5b 33 33 6d 68 65 6c 6c 6f 1b 5b 33 39 6d

termって基本色付け師なんですね。最後はschemeで関数を実行するっぽくhexに変換 してみた。これを見ると、 エスケープ・シーケンスな文字列変換の寄せ集めだ。

なお、yellow()は、入力の値によってのみ出力が決まる純粋関数だ。それに対して set_cursor_position() みたいな関数には、出力が無い。そんな関数に意味が 有るんか? ええ、この関数を呼び出すとカーソルの位置が変更されるって言う、 副作用バリバリの関数なんだ。この種の関数も結構定義されてるぞ。

examples of term

ちょっとつまらないので、豊富なサンプルを炙り出してみた。

sakae@lu:examples$ find . -name '*.v' | xargs grep 'import term'
./veb/websocket/server.v:import term
./flag/animated_help_text.v:import term.ui as tui
./flag/flag_layout_editor.v:import term.ui as tui
./brainvuck.v:import term
./path_tracing.v:import term
./term_display_sixel.v:import term
./poll_coindesk_bitcoin_vs_usd_rate.v:import term
./term.ui/rectangles.v:import term.ui as tui
./term.ui/pong.v:import term
./term.ui/pong.v:import term.ui
./term.ui/term_drawing.v:import term.ui
./term.ui/event_viewer.v:import term.ui as tui
./term.ui/cursor_chaser.v:import term.ui as tui
./term.ui/vyper.v:import term.ui as termui
./term.ui/text_editor.v:import term.ui as tui
./terminal_control.v:import term
./process/wrapping_interactive_python.v:import term
./term_key_pressed.v:import term
./vmatrix.v:import term
./vmatrix.v:import term.termios
./pendulum-simulation/modules/sim/runner.v:import term
./websocket/ping.v:import term
./websocket/client-server/client.v:import term
./websocket/client-server/server.v:import term
./game_of_life/life.v:import term

vmatrix.vって以前python版を試したね。どんな違いが有るか比べてみるのの一興 かと。

term.ui/text_editor.v は昔懐かしいスクリーン・エディターだ。かの昔に富士通のFM-11と言う パソコンを使っていた時、自作したんだった。このパソコンは面白い事に、2CPU構成に なってて、いわゆるsub CPUと呼ばれる奴がIO制御を担当してた。メインCPU側では、IOの リクエスト・テーブルを用意してコールする。大型コンピュータで言うIOプロセッサーの 発想を継承してた。だから、とっても画面制御なんかは、やり易かった。

例の奴は典型的なイベント・ドリブンな作りになってる。グラフィックなアプリだと皆、 こういう構造にはなるな。皆大好きなvscodeも一緒だからね。

fn event(e &tui.Event, mut a App) {
        mut buffer := a.ed
        if e.typ == .key_down {
                match e.code {
                        .escape {
                                exit(0)
                        }
                        .enter {
                                buffer.put('\n')
                        }
			:
                        48...57, 97...122 { // 0-9a-zA-Z
                                if e.modifiers == .ctrl {
                                        if e.code == .s {
                                                a.save()
                                        }
                                } else if !(e.modifiers.has(.ctrl | .alt) || e.code == .null) {
                                        buffer.put(e.ascii.ascii_str())
                                }
                        }
                        else {
                                if e.modifiers == .alt {
                                        if e.code == .comma {
                                                a.visit_prev_file()
                                                return
                                        }
                                        if e.code == .period {
                                                a.visit_next_file()
                                                return
                                        }
                                }

                                buffer.put(e.utf8)
                        }
                }

このコードを読むと、Ctl-s でファイルを保存。Alt-, 若しくは、Alt-. で、 編集対象ファイルを切り替え。それ意外の場合は、画面に反映だ。 ここでは省略しちゃったけど、マウス操作のイベントにも対応してる。 これを充実させていけば、emacsになるか。viみたいにモードが無いeditorは、 ウェルカムであります。

イベントをシステムコールと思えば、これはもうカーネルと作りが同じ構造ですよ。 幾ら行数が多くても(654行)作りが単純なんで読むの苦労は無い。

test of term

上で見てきた様に、termモジュールは、端末の状態を変化させる関数が定義されている (例: set_cursor_position )。これらは、どう試験されているんだろう? テストに 目覚めた今では、興味津々であります。

ちょいと予想してみる。

  1. そもそも無理だから、テストはしない。
  2. コードにフックが仕掛けてあり、テストの時だけエスケープ文字列が返る。
  3. 頑張って秘密の方法を編み出して、テストしてる。

さあ、どれか。2の予想を確認するには、提供されてる関数の定義を見れば一発だ。

// Sources for ANSI Control Sequences
// https://github.com/RajeshPatkarInstitute/Panim
// https://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html
// https://en.wikipedia.org/wiki/ANSI_escape_code
// Support for Windows
// https://en.wikipedia.org/wiki/ANSI.SYS
// #include <windows.h>
// C.SetConsoleMode(C.ENABLE_VIRTUAL_TERMINAL_INPUT)
// Setting cursor to the given position
// x is the x coordinate
// y is the y coordinate
pub fn set_cursor_position(c Coord) {
        print('\x1b[${c.y};${c.x}' + 'H')
        flush_stdout()
}

ご丁寧に出所元も提示されてた。特に変な仕掛けは施してない。 それじゃ、テストコードにご対面。

fn test_get_cursor_position() {
        original_position := term.get_cursor_position()!
        cursor_position_1 := term.get_cursor_position()!
        assert original_position.x == cursor_position_1.x
        assert original_position.y == cursor_position_1.y

        term.set_cursor_position(
                x: 10
                y: 11
        )
        cursor_position_2 := term.get_cursor_position()!

        term.set_cursor_position(
                x: 5
                y: 6
        )
        cursor_position_3 := term.get_cursor_position()!

        term.set_cursor_position(original_position)
        eprintln('original_position: ${original_position}')
        eprintln('cursor_position_2: ${cursor_position_2}')
        eprintln('cursor_position_3: ${cursor_position_3}')
        // 0,0 is returned on dumb terminals
        if cursor_position_2.x == 0 && cursor_position_2.y == 0 {
                return
        }
        if cursor_position_3.x == 0 && cursor_position_3.y == 0 {
                return
        }
        assert cursor_position_2.x == 10
        assert cursor_position_2.y == 11
        assert cursor_position_3.x == 5
        assert cursor_position_3.y == 6
}

冒頭で軽くチェックしてからセットしゲットして、それを比較とな。 運良く、対称的な関数が存在してたから、事無きを得たけど、term.clear() なんてのは、テスト出来るのかな?

// clear clears current terminal screen.
pub fn clear() bool {
        if os.is_atty(1) <= 0 || os.getenv('TERM') == 'dumb' {
                return false
        }
        print('\x1b[2J')
        print('\x1b[H')
        flush_stdout()
        return true
}

定義は、これ。で、テストは諦めたようだ。画面がクリアーされちゃうと、ユーザー からクレームが来るかも知れないって小心者なんだろうか? そんなのテストだから しょうがないでしょって強い態度に出る風潮は無くなったのか。

いやいや、それは違うで。画面がクリアーされたかどうかを確認する術が無いのさ。 だからテストが出来ないんだ。どうしてもやりたかったら、端末の前に座っている ユーザー(猫でもいいけど)に参加を強いる事になる。センサー代りになってもらって クリアされたならtrueを、何の変化も無かったらfalseを入力してもらう。これぞ、 ユーザー参加型テストさ。

module term

// utf8_getchar returns an utf8 rune from standard input
pub fn utf8_getchar() ?rune {
  :

犬も歩けば棒に当たる、かも知れないので、こんなのが将来役にたつかも知れないな。 心に留めておこう。

div by 0

ちょいと前回の割り算のゼロ割りは、未定義が疑問だったので質問。

Q: 割り算について質問します。ゼロで割るのは数学的にもコンピュータ的にも禁止 されてます。素人考えでは、答は無限大でも良さそうに思えます。 何故禁止なんでしょうか?

A: とても良い質問ですね! 「ゼロで割ると無限大でいいんじゃないの?」という疑問は、誰もが一度は抱く素直な発想です。 でも、数学的にもコンピュータ的にも「ゼロ除算(0で割ること)」が禁止されているのには、**深い理由**があります。以下でわかりやすく説明しますね。

### ✅ まず、「割り算」とは何か?

割り算(例えば \( a ÷ b \))は、本質的に「掛け算の逆」です。

つまり、「\( a ÷ b = c \)」というのは「\( b × c = a \)」を意味します。

たとえば:

\[ 6 ÷ 3 = 2 \quad \text{(なぜなら } 3 × 2 = 6 \text{)} \]

### ❌ ゼロで割るとはどういうこと?

たとえば「\( 6 ÷ 0 = ? \)」と考えたとします。

この意味は「**0 に何を掛けたら 6 になるか?**」という問いになります。

しかし、**どんな数を 0 に掛けても、結果は常に 0** ですよね?

\[ 0 × 1 = 0,\quad 0 × 9999 = 0,\quad 0 × ∞ = \text{未定義} \]

つまり、**「0 × 何か = 6」になるような「何か」は存在しない**んです。 だから、数学的に「答が存在しない」=「定義できない」となるわけです。

### 🔁 「無限大」でいいんじゃないの?という疑問

この疑問も自然です。たとえば、こんなことを考えるかもしれません:

\[ \frac{1}{1} = 1,\quad \frac{1}{0.1} = 10,\quad \frac{1}{0.01} = 100,\quad \dots \]

→ 確かに、**分母が0に近づくと答が大きくなる**から、 「ゼロで割れば無限大でいいのでは?」と思えるわけです。

でも!

  • \(\lim_{x \to 0^+} \frac{1}{x} = +∞\)
  • \(\lim_{x \to 0^-} \frac{1}{x} = -∞\)

というように、**ゼロに近づく方向によって答が正にも負にもなる**ため、 「0で割る」ことを「∞」と決めつけると**矛盾や混乱**が生じてしまいます。

### 💻 コンピュータ的にも困る理由

プログラムで「0で割る」とどうなるかというと…

  • **クラッシュする**(ゼロ除算エラー)
  • 例外(division by zero)として処理が止まる
  • 一部の言語ではNaNやInfinityが返るが、予測不能なバグの元

たとえば:

int x = 5 / 0; // 実行時エラー!

これは安全面からも、システム保護のためにも**厳しく禁止**されてます。

### 🧠 結論

  • ゼロで割ると、「何を掛けても元に戻らない」ため**数学的に定義できない**。
  • ゼロに近づけて「無限大」とする考えは、**方向によって値が変わる**ので不安定。
  • コンピュータでは**システム的にエラーやクラッシュの原因**になるため禁止。

気になったら、**「極限」や「不定形(∞÷∞など)」の概念**も面白いですよ! 興味あればそこも深掘りしますね。

README

SHAPE なんて本を読んでいる。幾何学の考え方を社会に適用したらどうなるって新鮮な 切り口の本。じっくり読まねば。気分転換に、 他人屋のゆうれい なんてのも読み始めた。それともう一冊、ドリトル先生航海記。

家から図書館の蔵書検索をしたら在庫してた。ただ家での検索では、収納棚番号は 不明。図書館で端末を使えば棚の位置が出てくるけど、もうWindowsの日本語 入力方法を失念してしまっている(ええ、年初から完全なSKKユーザーです)。

IT弱者を装って、ちゃっかり司書さんに聞いちゃいましたよ。日本十進分類で整理 されてましたね。図書事業をCCCへアウトソーシング(例: 佐賀県武雄市)すると、独自の分類番号で 管理されるみたいだけど、普通の分類方法で良かった。家からの検索でも 惜しまずに分類番号を公開してよね。配列のインデックスぐらいは公開しても 問題無いっしょ。待て、配列のインデックス検索なら、

933 = 国会図書館.index('ドリトル先生航海記')

どうやら、こんな風に調べておくと良さそう。 一般的には、これで当りをつけるみたい。 日本十進分類(NDC)検索 でも、オイラーは分類に弱いからなあ。例えばホームセンターへコーヒー・フィルターを購入 しに行ったとする。どこら辺に有る? ドリップ用品の付属品と考えれば、台所用品 売り場。豆を買うついでに、フィルターもお忘れなくって思えば、コーヒー売り場に あって、しかるべくだ。右往左往してAI監視カメラにマークされるぞ。

んな訳で、3冊をパラレル読書してる。が、ゆうれい本が面白くて、俄然優先度が 上昇。いわゆる一気読み状態。こういう時は、20-20-20のルールを励行すべし。 目をいたわりましょうってんで米国の眼科医が推奨してる。 20分事に、20フィート(6m)離れた所を、20秒間みつめましょう。 ついでに、肩と首のストレッチで肩を廻す、首も廻すせば完璧。


This year's Index

Home