HOME

rpnedit.sed

2017-4-12

これは何?

スタックを使って対話的にテキストを編集するための sed 用スクリプト。無愛想なコマンドしか持たないので、latexmacro.sed などのマクロ集とともに用いることを想定している(実例は「スタックを使って LaTeX の数式を編集せむ」参照)。

sed  -f rpnedit.sed

のように起動すると、「現在のスタックの内容を標準出力に書き出し、標準入力からの入力を待つ」が繰り返される。

ダウンロード

rpnedit.sed (Ver.0.74)

(注) \| および s コマンドの m オプションを利用しているので GNU sed のみ対応。e コマンドは使っていないので、そこのあたりは安心。

使用例

現在のスタック内容の表示
キーボードからの入力⏎

新しいスタックの内容
a
b
join⏎

ab
a
b
swap⏎

b
a
a
b
swap⏎

b
a
join⏎

ba

出入力

入力

標準入力を読み取り、1 行を一つの文字列として受け取る。文字列は入力されるとスタックに積み込まれる。

コマンドとして定義されている文字列は、スタックに積まれるとすぐさま機能する。

コマンドとして働く文字列をリテラルとして入力したい場合は、" " で囲む(そうでない文字列を " " で囲んでもかまわない)。このとき、右端の " は省略できる。"..." は "... より優先して解釈されるため、""" は " を 1 つだけ入力し、"" も " も空文字列を入力する。

undo コマンドは直近のコマンドを取り消す(マクロも一つのコマンドと扱われる)。undo ができるのは 1 回限りである。

出力

入力が 1 行あるごとに、標準出力に現在のスタックの中身を出力する(※)。スタックは、深いほうから順に表示される。なお、このページではスタックの各層をレベルと呼び、最も浅いレベルから順に、レベル1、レベル2……と称することにする。

………
レベル3
レベル2
レベル1

quit コマンドは、プロセスを終了させる。でも、たいてい Ctrl-D を打鍵したほうが楽。

write コマンドは、現在のスタックの後に [END_OF_STACK] という文字列だけからなる行をつけて rpnedit.tmp に書き込む。write コマンドが複数回使われると、そのつどファイル末尾に追加する。あらかじめ rpnedit.tmp ファイルが存在する場合、その内容は起動時に切り詰められる(これは sed の仕様)。

write1 コマンドは、write コマンドと全く同様だが、書き出すファイルが rpnedit.tmp1 という点だけが異なる。

print コマンドは、現在のスタックを標準出力に出力する。これは、sed が -n オプションつきで実行されているときに有用である。

エラー処理というものは、ない。オペランドが足りないという、典型的なエラー・シーンでは、コマンドを実行する代わりにコマンド名をスタックに積まれることが多い。エラーが出た場合も undo をすれば元に戻る。

※標準出力の書式について

rpnedit.sed の内部で、スタックはずっと

[改行]レベルN[改行]レベルN-1...[改行]レベル1

の形で扱われている。そして、標準出力に出力する直前になって、rpnedit.sed はこの先頭に [TAB] を、最後に [TAB][TAB] を付け加える。さらに、sed の仕様によって、実際に出力するときに、最後に改行がひとつ加えられる。したがって、標準出力は、

[TAB][改行]レベルN[改行]レベルN-1...[改行]レベル1[TAB][TAB][改行]

の形になる。したがって、ユーザーが 1 行入力するごとに、次々出力されるスタックを awk でスタックひとつぶんだけを表示したいような場合、[TAB][TAB][改行] をレコードセパレータに、[改行] をフィールドセパレータにして、1 フィールド目を捨てるとうまく処理できることが多い。

もし、必ず [改行] を区切文字と解釈する必要があるなら、[TAB] だけからなる行がスタックに先立って出力され、最後は [TAB][TAB][改行] で終わる」と考えるとうまくいく。

編集コマンド

putbetween

a
b
c
putbetween⏎

acb

bracket

a
b
c
bracket⏎

bac

join

a
b
join⏎

ab

decap

abc
decap

bc

chop

abc
chop

ab

decapstring

abcde
ab
decapstring

cde

chopstring

abcde
de
chopstring

abc

delete

スペースと@から成る文字列によって、削除する部分文字列を指定する。

abcde
  @@
delete

abe

insert

スペースと@から成る文字列によって、挿入位置を指定する。

abcde
  @
yyy
insert

abyyycde

replace

スペースと@から成る文字列によって、置換対象となる部分文字列を指定する。

abcde
  @@
xxx
replace

abxxxe

split

スペースと@から成る文字列によって、文字列の分割位置を指定する。

abcde
  @
split

ab
cde

splitb, splitblast, splita, splitalast, splitba, splitbalast

探索を用いて、文字列を分割する。

abcde
cd
splitb

ab
cde

ほかに、探索を使い文字列の分割を行うコマンドは 6 つある。

計算コマンド

1sub

1〜99 までの整数から 1 を引く。

50
1sub

49

1add

0〜98 までの整数に 1 を足す。

50
1add

51

divby

レベル2÷レベル1 を越えない最大の整数を得る。レベル1、2 とも 0 以上 99 以下である必要がある。

7
2
divby

3

countchars

レベル1 の文字数を数える(0以上99以下)。

abcde
countchars

5

depth

スタックの深さを調べる。

a
b
c
depth

a
b
c
3

スタック操作コマンド

clear

スタック中のすべてのレベルを削除する

drop

スタックのレベル1 を削除する。

nip

スタックのレベル1 以外のレベルを削除する。

dup

スタックのレベル1 を複製する。

pick

任意のレベルをコピーする。

roll と rolld

roll は任意のレベルを取り出してプッシュする。rolld はレベル1 を任意のレベルに挿入する。

swap

レベル1 とレベル2 を入れ替える。

マクロ作成のためのコマンド等

ntimes

ntimes コマンドは、レベル1 にある数の回数(1以上99以下)だけレベル2 にある文字列を複製して積み込む。このとき、複製された文字列がコマンドとして定義されているときは、実行される

a
b
c
"join"
2
ntimes

abc

手続き

手続き」は、「|||の後に文字列を置いたもの」の繰り返しからなる。
(例)
|||Hello[SPACE]|||swap|||join

「手続き」と ntimes コマンドを用いることにより、コマンド群を繰り返し実行することができる。

a
|||"!"|||join
3
ntimes

a!!!

配列

[3つ続いた任意の印刷可能文字][コマンドまたはそうでない文字列]の繰り返しからなる文字列。
(例)
///Hello[SPACE]///swap///join
AAAHello[SPACE]AAAswapAAAjoin

array->procedure

配列を手続に変換する。

***a***b***c
array->procedure

|||a|||b|||c  

<<< と >>>

rpnedit.sed は、<<< コマンドを受け取ると、以後入力された文字列をスタックに積まずに、別途用意されているキューに入れる。コマンドとして定義されている文字列も、たんなる文字列としてキューに入る。そして、>>> コマンドを受け取ると、今まで積んだ文字列をキューから 1 つずつ取り出して、スタックに積む。コマンドとして定義されている文字列は、ここに至って初めて機能する。

これを使ってマクロを書くと、undo コマンドはマクロをひとまとまりのものと見て、マクロ実行以前の状態を復元する。

したがって、マクロを定義したファイルは、

s/^マクロ名$/<<<\
文字列\
文字列\
文字列\
>>>/mg

のような sed の置換コマンドを集めたものを書けばよい。s コマンドに m オプションがつけられているのは、「マクロを定義したファイルの前のほうでマクロ名を置換した結果出現した文字列」を、同ファイルの後ろのほうで ^ と $ つきのマッチパターンで捕え、再度 s コマンドを適用できるようにするためである。

手続きはマクロ中に書かれた場合でも、キューに積まれた瞬間に解体される。したがって、ntimes のオペランドとして手続きを書くと、手続きの最後にある文字列(あるいはコマンド)だけが複製される。これを防ぐためには、マクロ中では手続きを配列に直して書いておき、array->procedure を用いるようにする必要がある。

真偽値

0 が「偽」で、0 以外は「真」として扱われる。

ifelse

ifelse は、真偽値に基いて、レベル1 またはレベル2 のうちどちらかを削除する。

true
a
b
ifelse

a
0
a
b
ifelse

b

not

0 を 1 にし、0 以外を 0 にする。

0
not

1
a
not

0

or

レベル2、レベル1 のいずれかが真なら 1 を積む。

and

レベル2、レベル1 のいずれもが真なら 1 を積む。

same?

レベル2 の内容が、レベル1 の内容と同じかどうかを調べる。

a
b
same?

0
a
a
same?

T

^match?

レベル2 の内容が、レベル1 の内容で始まっている場合に、1 を積む。そうでなければ 0 を積む。

abcd
ab
^match?

1
bcde
cd
^match?

0

match$?

レベル2 の内容が、レベル1 の内容で終わっている場合に、1 を積む。そうでなければ 0 を積む

abcd
cd
match$?

1
abcd
bc
match$?

0

include?

レベル1 の文字列がレベル2 の文字列中にいくつあるか調べる。1 つもなければ 0 になる。

abcabc
ab
include?

2

おわり