読者です 読者をやめる 読者になる 読者になる

TL/1 のエッジケース

プログラミング言語 TL/1 について、予約語と同じ名前の変数 (または関数や手続き) を宣言して使うことが可能であるということは以前に紹介した。 その規則には仕様書からは読み取れない若干の曖昧さがあるのではないかという意見を述べていたところ、当時の実際の処理系 (TL/1-FM) で様々な例を試した結果をコメントで貰ったのでひとつひとつ検証していこうと思う。

うまく動く例1

VAR VAR
ARRAY ARRAY[2]
BEGIN
  ARRAY[1]:=123
  ARRAY[2]:=234
  VAR:=ARRAY[1]+ARRAY[2]
  WRITE(0:VAR,CRLF)
END

これについては基本的な動作であり、特に考察が必要な要素はない。 ARRAY という名前の配列が宣言された時点でそれ以後は ARRAY は配列名として解釈される。

うまく動く例2

VAR VAR
ARRAY VAR[2]
BEGIN
  VAR[1]:=123
  VAR[2]:=234
  VAR[0]:=VAR[1]+VAR[2]
  WRITE(0:VAR[0],CRLF)
END

名前 VAR は単変数と配列の両方で宣言しているが、このとき単変数ではなく配列として機能する。 これも仕様通りの動作で、特に疑問なことはない。

以前に取り上げているが、複数の意味で宣言されているときにどれが優先されるかは仕様に明記されていて、単変数よりは配列が優先される。

TL/1 の変なところ (名前解決) - 主題のない日記

コンパイルエラーの例1

VAR VAR
ARRAY VAR[2]
BEGIN
  VAR[1]:=123
  VAR[2]:=234
  VAR:=VAR[1]+VAR[2]
  WRITE(0:VAR,CRLF)
END

エラーの内容

VAR VAR
ARRAY VAR[2]
BEGIN
VAR[1]:=123
VAR[2]:=234
VAR:=
Error : in 60

VAR を配列ではなく単変数で扱おうとしている個所でエラーになっている。 上で述べたように、単変数より配列としての解釈が優先されるので仕様通りであり、不自然なところはない。

コンパイルエラーの例2

VAR ARRAY
ARRAY FOO[2]
BEGIN
  FOO[1]:=123
  FOO[2]:=234
  ARRAY:=FOO[1]+FOO[2]
  WRITE(0:ARRAY,CRLF)
END

エラーの内容

VAR ARRAY
ARRAY FOO[
Error FOO in 20

これについては仕様からは読み取れなかった挙動である。 宣言がいつから有効になるのかという点について、個人的には BEGIN の直後からとするのが曖昧さが少ないと考えていたのだが、実際には宣言の直後からだったようだ。

コンパイルエラーの例3

VAR ARRAY
ARRAY:=123
BEGIN
 FOO[1]:=123
 FOO[2]:=234
 ARRAY:=FOO[1]+FOO[2]
 WRITE(0:ARRAY,CRLF)
END

エラーの内容

VAR ARRAY
ARRAY:=123
BEGIN

Error ARRAY in 30

これについては興味深い。 ARRAY:=123 に相当するオブジェクトコードは正しく生成されているそうなので、そこまでは正しいプログラムだとしてコンパイラは許容しているということになる。 おそらくは、複文 (仕様書にない用語だがBEGIN/END 、またはその他の文括弧で囲んだ複数の文を便宜上こう呼ぶことにする) の扱いについて仕様に書かれていない解釈が存在するのだと思う。

複文は複数の文をまとめたひとつの文であるので、文が現れることが出来る個所にはどこにでも現れることが出来る。 しかし、主プログラムや副プログラムの本体部分はたとえ文がひとつであろうとも BEGIN/END で囲まなければならず、他の文括弧を用いることもできない。 それが仕様書から読み取れる仕様である。

しかし、実際の挙動は主プログラムや副プログラムの本体部分は単に「文」であればなんでも良いのではないか。 他の文括弧を使った複文でも通りそうな気がする。

引数なし関数呼び出しの次の行の解釈でうまく動く例1

FUNC FOO
VAR X
BEGIN
  X:=FOO
  (X)
  WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

実行結果

2

TL/1 は行指向ではない (改行は単に無視される) ので、関数名と引数の間に改行を入れても関係なく引数として渡されると解釈すべきで、定義より多く引数を渡す分には変数が参照されることはないのだから問題なく動作するのだろう。

引数なし関数呼び出しの次の行の解釈

FUNC FOO
VAR X
BEGIN
 X:=FOO
 {X:=1}
 WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

実行結果

1

{} のかわりに () を文括弧として使った場合

FUNC FOO
VAR X
BEGIN
 X:=FOO
 (X:=1)
 WRITE(0:X)
END
FOO
BEGIN
 RETURN 2
END

エラーの内容

FUNC FOO
VAR X
BEGIN
X:=FOO
(X:=
Error : in 50

関数名の直後の開き丸括弧は常に引数の開始であると解釈するようだ。 当時のコンピュータの能力を考えると少ない先読みでトークンの意味を確定しようとする設計は合理的と言える。 おそらく TL/1 の文法を分類するなら LL(1) の範疇に収まるのではないかと思う。 一方で、そもそも丸括弧を式括弧にも文括弧にも引数にも用いようとしているのが解釈し難さの原因でもあるので最初から分けておくべきだったとも思う。

引数付関数呼び出しを2行に分けて記述してうまく(?)動く例

FUNC FOO
VAR X
BEGIN
  X:=FOO
  (1)
  WRITE(0:X)
END
FOO
VAR Y
BEGIN
 RETURN 2+Y
END

実行結果

3

これは少しばかり奇妙な挙動だと思う。 スタックレイアウトが想像できない。

Document ID: 6ed36823564fc58efd50e2fd7816e296