Island Life

< ピアノレッスン115回目 | ピアノレッスン116回目 >

2014/01/19

行継続

行指向の文法、つまり改行が単なる空白文字より大きな区切りを意味する文法において、 編集上の都合から見た目の行を折り返したい場合がある。 多くの場合は、行末に「次の行に継続する」ことを指示する文字を置くことで実現される。 行末バックスラッシュが多いのはUnixとC文化かな。 Common Lispのformat文字列では~。Fortranは&。Texinfoは@。 Visual Basicではアンダースコアだったっけ。

一方Vim Scriptでは、行末に文字を置くのではなく、継続する行の先頭に文字を置くよ、 という話を読んだ。

本当にキモい Vim script - 行継続編

さて、では一方 Vim script は、と言うと…

let g:quickrun_config = {
\   '_': {
\     'input': '=%{b:input}', 'cmdopt': '%{b:cmdopt}', 'args': '%{b:args}',
\     'runner': 'vimproc',
\     'runner/vimproc/updatetime': 500,
\   },
\ }

viとの互換性が理由だそうだ。

が、「次の行の方に前行からの継続を示すマーカがある」という文法を扱ったことが 何回かあるような気がしたので考えてみた。

RFC822のヘッダは原則1行で完結するけれど、次の行が空白文字で始まっている場合は それが継続行とみなされる。

Subject: Long subject line
  can be folded
  like this.

WiLiKiのマークアップは、行頭の ~ で前の行からの継続を示す。

強調などのマークアップは行をまたげないので、'''長い行をこんなふうに
~強調したかったら行継続を使う'''。

なぜこうしたのか忘れちゃったけど、wikiマークアップは「行頭にあること」 で意味を持つものが多いのでそれに合わせたのかも。

プログラミング言語で「次行行頭」にマーカを置く例はなかなか見つからない。 COBOLが "-" を「リテラルを前の行から継続」に使ってるけれど、 '-'を書くカラムが決まってるから「行頭」と言えるかどうか。

http://cobol.404i.com/basics.php

000340       DISPLAY 'This might be a very long string that
000350-    'needs to be continued onto the next line'

この表記を見てFTPのリプライ行継続マーカ (あれも-だ) を思い出したが、 FTPの方は「次に続く行がある場合」につけるので、「行末継続文字」グループの 親戚みたいなもんだろう。

★ ★ ★

技術的な観点では、「前の行から続いているよ」というマーカよりは、 「まだ行が続くよ」の方がパーザが書きやすい。前者では 次の行を読んでみないと今の行が終わるかどうかわからないので、 先読み、もしくはバッファリングが必要になる。

ただ、人間が普通のテキストを読み書きする際には、「まだ続くよ」マーカは あまり使われない。「行」が意味を持つ文章で、組版の都合でどうしても 行途中で折り返さないとならない時は、継続行がインデントで示されるのが普通だ。

Their sense thus weak, lost with their fears thus
  strong,
Made senseless things begin to do them wrong;
For briers and thorns at their apparel snatch,
Some sleeves, some hats, from yielders all things
  catch.

これは手元にあったペーパーバックからフォーマットをそのまま引用したものだけど、 "strong" や "catch"が前行からの継続であることは自然にわかる。 RFC822形式はある意味「人間にとって自然な記法」と言えるかもしれない。

「行の継続」だけでなくもう少しスコープを広げると、 結局先読みやバッファリングが必要になる構文はそこそこある。

  • Makefileのタブを使う文法は今となっては古くさいけれど、あの行頭タブは ひとつのルールのブロックを継続するマーカと言える。
  • インデント指向な文法でも、次の行のインデントを読んでみないと 今のブロックが終わるかどうかはわからない。

なので、パーザの書きやすさを理由に「次の行に続くよ」式文法を採用する意味は あまり大きくないかもしれない。

★ ★ ★

行継続構文についてもうひとつ考えるべき問題は、行をくっつけた時の「糊」の部分を どうするか、である。トークンの区切りとしての空白は何個あっても意味が変わらないので ただ行をくっつけるだけで良いが、リテラルの中で改行している場合は空白の個数が 意味を持ってくる。

「ただくっつける」という動作の場合、リテラル中に余分な空白を入れたくなければ、 次の行を行頭から始めないとならない:

  str = "This is a string \
literal"

けれども、意味的にインデントを揃えたいと思うのが人情だろう:

  str = "This is a string \
         literal."

このため、「次に来る行の行頭の空白を全て無視する」としている仕様もある。 Schemeの文字列リテラルや、Common Lispのformat文字列の ~がそうだ。

(define str "This is a string \
             litreral.")

しかしこれはこれで、「出力の行頭に一定数のスペースを含めたい」って時に ややこしい。例えばこんな出力が欲しいとき:

Usage: command [-a arg] [-b arg] file ...
Options:
   -a arg    Blah blah blah
   -b arg    Blah blah blah

物理行の先頭でインデントしててもその空白が食われてしまうので、 必要な空白の前に空白でない何かを置いとかないとならない:

(define (usage)
  (display "Usage: command [-a arg] [-b arg] file ...\
            \nOptions:\
            \n  -a arg    Blah blah blah\
            \n  -b arg    Blah blah blah\
            \n"))

Common Lispのformat文字列では「次の行の空白を保存する」という オプションもあるが、プログラムテキスト上の意味的なインデントと 出力に含めたいインデントを区別できないので、ちょっと足りてない感じだ。

「後続行に、前から続く行であることを示すマーカを入れる」という文法であれば、 空白文字以外のマーカを使うことでこの問題を解決できる。 たとえば仮想的に、行頭の\s*~が「前の行からの継続」を示す、 という文法があるなら、こんなふうに書けるだろう。

(define (usage)
  (display "Usage: command [-a arg] [-b arg] file ...\n
           ~Options:\n
           ~  -a arg    Blah blah blah\n
           ~  -b arg    Blah blah blah\n
           ~"))

リテラルに関してこれに近いことをやっていた言語があったような気もするのだけど 思い出せない。

なお、「人間にとって自然」に見えるRFC822の行折り返しだが、空白文字の扱いについて 微妙な点がある。英文の慣習では、\s*\n\s+ がひとつの空白に置換され、 RFC822もそれに沿っているわけだが、この文法だと「空白を含めずに折り返す」 というケースが書けない。Subjectヘッダの扱いに関して困った覚えがある。 (MIMEを用いても、各MIMEエンコーディングは物理行内で完結しないとならないので、仕様に厳密に従うと行継続の空白が残ってしまうのだ。実際のメールクライアントはそのへんヒューリスティックに対応しているっぽいが。 (追記2014/01/20 06:06:20 UTC)これは私の誤解だった。rfc2047の6.2節にてこのケースへの対応が示されている。コメント欄参照。) なので、「継続を示すマーカ」は空白文字とか区別できる方がいい。

特に結論めいたものはないけれど、新たな文法を考えるなら、 少なくともリテラルの行継続については、「行頭の <空白>* <非空白文字のマーカ>」というのは 案外良い選択なんではないかな、と思った。

Tag: Programming

Past comment(s)

nobsun (2014/01/20 02:32:55):

Haskellだと文字列リテラルの中で2つのバックスラッシュの間に空白文字を挟んだものを無視するという字句規則があります。これを使うと、行の継続は、行末と行頭にバックスラッシュがマークとして置かれているように見えます。

nobsun (2014/01/20 02:33:54):

あくまで文字列リテラルの中だけの話ですが。。。

shiro (2014/01/20 02:51:18):

ああ、最後の「これに近いこと」はHaskellの印象かも。最近の言語では本体の構文が行指向でないものが多いので、設計上の話は主にリテラルの問題になりますね。

な (2014/01/20 03:55:12):

本筋とは関係ありませんが、MIMEエンコードしたヘッダでは、隣り合ったencoded-wordの間のスペースは無視して表示されるべきと、RFC2047に書かれているので、行継続の空白も、表示されるときには取り除かれると考えて良いのではないでしょうか。

Subjectなどの非構造化ヘッダが空白がなく書かれている場合、ASCIIだけしか含まなくても、折り返すためにQエンコーディングされることもあります。

> When displaying a particular header field that contains multiple > 'encoded-word's, any 'linear-white-space' that separates a pair of > adjacent 'encoded-word's is ignored. (This is to allow the use of > multiple 'encoded-word's to represent long strings of unencoded text, > without having to separate 'encoded-word's where spaces occur in the > unencoded text.)

shiro (2014/01/20 06:03:05):

あれ、そうですね。rfc.mimeを実装したとき見落としていたようです。

ksmakoto (2014/01/23 09:20:49):

FORTRAN 77(以前)が、カラム1から5がブランクでカラム6に何かある、という行は、前の行の続き、という仕様です。 http://www.fortran.com/F77_std/rjcnf0001-sh-3.html#sh-3.2.3 『算法表現論』の第一章で、ソースコードをどんどんスパゲティにするために、プログラムをこれに対応させる、という改造を使っていたという記憶があります。

shiro (2014/01/23 18:39:17):

Fortranもそうだった気がしたんですが、ちょっとぐぐるとFortran90の行継続ばかり出てきたのであまり調べませんでした。コメント行も何カラム目かにCを書くって仕様じゃありませんでしたっけ。

Fortranは学部時代の算法概論の講義で触ったっきりだなあ。今はどの言語を使ってるんだろう。

ksmakoto (2014/01/24 03:37:09):

はい。リンク先のちょっと手前のあたりに "contains a C or an asterisk in column 1" と、コメント行の定義があります

thinca (2014/01/30 02:31:44):

なかなかおもしろいですね。

CoffeeScript などは文字列リテラルの行頭空白を削除しますが、一番浅いインデントの分だけを削除します。なので、Usage の例のようなものは自然に書けます。

> たとえば仮想的に、行頭の\s*~が「前の行からの継続」を示す、 という文法があるなら、

Vim script の継続行の \ は、手前に任意の空白を入れることが可能です。なので、ここで言っている文法と同じものになりますね。

shiro (2014/01/30 02:42:57):

なるほどー。「一番浅いインデントの分だけ削除」というのはなかなか良さげですね。

Post a comment

Name: