Chapter 2
字句構造

この章では,Haskell の下位レベルの字句構造について説明する. このレポートを初めて読む場合には,詳細のほとんどは飛ばしてかまわない.

2.1 表記規約

次にあげる表記規約は構文を表現するために用いる.

[pattern]
オプション
{pattern}
0回またはそれ以上の繰り返し
(pattern)
グループ化
pat1 | pat2
選択
patpat′⟩
差— pat で生成された要素から
pat で生成されたものを除いたもの
fibonacci
終端構文はタイプライタフォント

この節で構文といえば 字句構文のことなので,すべての白空白は明示的に表現する. 隣接するシンボルの間には暗黙の空白は存在しない. 次のような生成ルールをもつ BNF 風の構文を用いる.

nonterm alt1 | alt2 |  | altn

| および [] のようなメタロジック構文と(タイプライタフォントで与えられた)| および [...] のような具象終端構文を区別するようにしなければならない. しかし,これらの区別は通常文脈から明らかなものである.

Haskell では Unicode [2] 文字集合を使う.しかしながら,ソースプログラムは現時点では早い段階の版で専ら使用されていた ASCII 文字集合に偏ったものである.

この構文は Unicode コンソーシアムが定義した Unicode 文字の性質に依存する. Haskell コンパイラは利用可能な Unicode の新しいバージョンを使うことが推奨されている.

2.2 プログラムの字句構造

program { lexeme | whitespace }
lexeme qvarid | qconid | qvarsym | qconsym
| literal | special | reservedop | reservedid
literal integer | float | char | string
special ( | ) | , | ; | [ | ] | ` | { | }
 
whitespace whitestuff {whitestuff}
whitestuff whitechar | comment | ncomment
whitechar newline | vertab | space | tab | uniWhite
newline return linefeed | return | linefeed | formfeed
return キャリッジリターン
linefeed ラインフィード
vertab 垂直タブ
formfeed フォームフィード
space 空白
tab 水平タブ
uniWhite 白空白として定義されている任意の Unicode 文字
 
comment dashes [ anysymbol {any} ] newline
dashes -- {-}
opencom {-
closecom -}
ncomment opencom ANY seq {ncomment ANY seq} closecom
ANY seq {ANY }⟨{ANY } ( opencom | closecom ) {ANY }⟩
ANY graphic | whitechar
any graphic | space | tab
graphic small | large | symbol | digit | special | " | '
 
small ascSmall | uniSmall | _
ascSmall a | b |  | z
uniSmall 任意の Unicode 小文字
 
large ascLarge | uniLarge
ascLarge A | B |  | Z
uniLarge 任意の Unicode 大文字あるい任意の Unicode 語頭文字
symbol ascSymbol | uniSymbolspecial | _ | " | '
 
ascSymbol ! | # | $ | % | & |  | + | . | / | < | = | > | ? | @
| \ | ^ | | | - | ~ | :
uniSymbol 任意の Unicode シンボルあるいは Unicode 句読点
digit ascDigit | uniDigit
ascDigit 0 | 1 |  | 9
uniDigit 任意の Unicode 十進数字
octit 0 | 1 |  | 7
hexit digit | A |  | F | a |  | f
 

字句解析では「最長一致」規則を使うべきで,それぞれの位置で 字句生成規則を満す最長の字句が読み込まれる.したがって,caseは予約語であるが,casesは予約語ではない. 同様に,= は予約されているが, == および ~= は予約されていない.

あらゆる種類の白空白は字句を区切る適切な区切り子である.

ANY の範疇に入らない文字はHaskellでは使用できない. 使用すれば字句解析エラーにならなければならない.

2.3 コメント

コメントは正当な白空白である.

通常のコメントは2つ以上連続したダッシュ(たとえば,--)で構成される字句で始まり次の改行までである.(コメント開始の)ダッシュの並びは正しい字句の一部にしては決してならない. たとえば,「-->」あるいは「|--」はコメントの開始にはならない.そのわけは,両方ともに正しい字句だからである. 一方,「--foo」はコメントの開始になる.

ネストしたコメントは{-」という字句で開始し,「-}」で終了する.正しい字句で「{-」から始まるものはない. したがって,たとえば, 「{---」は余分なダッシュが付いてはいるがネストしたコメントの開始となる.

コメントそのものは字句解析の対象にはならない. その代わりに最初の未対応「-}」によって対応するネストしたコメントは終了する. ネストしたコメントは何段にもネストすることが可能で,「{-」があらわれるたびに新しいネストしたコメントが開始され,それらは,それぞれ対応する「-}」で終了する.

(ネストコメントではない)通常のコメント内では,文字列「{-」および「-}」は特別な意味を持たない. また,ネストコメント内ではダッシュの並びには特別な意味はない.

ネストコメントはコンパイラに対するプラグマとしても用いられる. これについては12で説明する.

コードをネストコメントでコメントアウトした場合には,コメントアウトしたコード中の文字列(リテラル)内にある {- あるいは -} の出現に影響を受ける. また,コメントアウトされたコード中の行コメント内の{- あるいは -} の出現にも影響を受ける.

2.4 識別子および演算子

varid (small {small | large | digit | ' })reservedid
conid large {small | large | digit | ' }
reservedid case | class | data | default | deriving | do | else
| foreign | if | import | in | infix | infixl
| infixr | instance | let | module | newtype | of
| then | type | where | _

識別子は英字ではじまり,英字,数字,アンダースコア,シングルクウォートが 0 個ないしそれ以上つづく. 識別子は字句としては 2 つの名前空間(1.4節)に分けられ区別される. 小文字で始まる識別子(変数識別子)と,大文字で始まる識別子(構成子識別子)である. 識別子では,大文字,小文字は区別される. namenaMeNameはそれぞれ別の識別子である(最初の 2 つは変数識別子,最後のものは構成子識別子である).

アンダースコア「_」は小文字と見なされ,小文字が出現可能な場所で使用できる. しかしながら,「_」それ自身は予約済の識別子であり,パターンにおいてワイルドカードとして使用する. 未使用の識別子に対して警告を出すコンパイラではアンダースコアで始まる識別子に対する警告は抑制することを推奨されている. それ故、プログラマは不使用 を期待されているような「_foo」をパラメータとして使用することも可能である.

varsym ( symbol: {symbol} )reservedop | dashes
consym ( : {symbol})reservedop
reservedop .. | : | :: | = | \ | | | <- | -> | @ | ~ | =>

演算子シンボル は 1 つ以上のシンボル文字で上記のように構成される.また,字句としては 2 つの名前空間(1.4)に分けられ区別される.

コロンそのもの「:」は,Haskell のリスト構成子専用に予約されていることに注意すること. この扱いは,他にリスト構文,「[]」や「[a,b]などと同じである.

前置符号反転演算子の特別な構文除けば,すべての演算子は中置である. しかしながら,中置演算子は セクションで部分適用演算子として使用できる(3.5 節参照). 標準の中置演算子はすべてあらかじめ定義済のシンボルである. また,再定義可能である.

今後,このレポートでは 6 種類の名前を使いわける.

varid          変数
conid          構成子
tyvar varid     型変数
tycon conid     型構成子
tycls conid     型クラス
modid {conid .} conid     モジュール

変数と型変数は小文字で始まる識別子で表現され,それ以外の 4 つは大文字で始まる識別子で表現される. また,変数と構成子は中置形式をもち,それ以外の 4 つは中置形式はもたない. モジュール名は複数の conid をドットで区切って並べたものである. 名前空間については 1.4 節で議論する.

状況によっては,名前の前にモジュール名を置いて修飾することもある. この修飾子の付加は変数,構成子,型構成子,型クラスに対して行われるもので,型変数,モジュール名に対しては行われない. 修飾された名前についての詳細な議論は 5章で行う.

qvarid [modid .] varid
qconid [modid .] conid
qtycon [modid .] tycon
qtycls [modid .] tycls
qvarsym [modid .] varsym
qconsym [modid .] consym

修飾された名前は全体で 1 つの字句なので,修飾子と名前の間に空白を置くことはできない. 字句解析は単純に以下に示すように行われる.



これは このように字句解析される


f.g f . g (3 つのトークン)
F.g F.g(修飾された ‘g’)
f.. f ..(2 つのトークン)
F.. F..(修飾された ‘.’)
F. F .(2 つのトークン)


修飾子が名前の構文的扱いを変更することはない.たとえば,Prelude.+ は中置演算子であり,プレリュード(4.4.2 節)で定義されている + と同じ結合性を持つ.

2.5 数値リテラル

decimal digit{digit}
octal octit{octit}
hexadecimal hexit{hexit}

integer decimal
| 0o octal | 0O octal
| 0x hexadecimal | 0X hexadecimal
 
 
float decimal . decimal [exponent]
| decimal exponent
 
exponent (e | E) [+ | -] decimal

There are two distinct kinds of numeric literals: integer and floating. Integer literals may be given in decimal (the default), octal (prefixed by 0o or 0O) or hexadecimal notation (prefixed by 0x or 0X). Floating literals are always decimal. A floating literal must contain digits both before and after the decimal point; this ensures that a decimal point cannot be mistaken for another use of the dot character. Negative numeric literals are discussed in Section 3.4. The typing of numeric literals is discussed in Section 6.4.1.

2.6 文字リテラルおよび文字列リテラル

char ' (graphic' | \ | space | escape\&) '
string " {graphic" | \ | space | escape | gap} "
escape \ ( charesc | ascii | decimal | o octal | x hexadecimal )
charesc a | b | f | n | r | t | v | \ | " | ' | &
ascii ^cntrl | NUL | SOH | STX | ETX | EOT | ENQ | ACK
| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL
cntrl ascLarge | @ | [ | \ | ] | ^ | _
gap \ whitechar {whitechar} \

Character literals are written between single quotes, as in 'a', and strings between double quotes, as in "Hello".

Escape codes may be used in characters and strings to represent special characters. Note that a single quote ' may be used in a string, but must be escaped in a character; similarly, a double quote " may be used in a character, but must be escaped in a string. \ must always be escaped. The category charesc also includes portable representations for the characters 「alert」 (\a), 「backspace」 (\b), 「form feed」 (\f), 「new line」 (\n), 「carriage return」 (\r), 「horizontal tab」 (\t), and 「vertical tab」 (\v).

Escape characters for the Unicode character set, including control characters such as \^X, are also provided. Numeric escapes such as \137 are used to designate the character with decimal representation 137; octal (e.g. \o137) and hexadecimal (e.g. \x37) representations are also allowed.

Consistent with the 「maximal munch」 rule, numeric escape characters in strings consist of all consecutive digits and may be of arbitrary length. Similarly, the one ambiguous ASCII escape code, "\SOH", is parsed as a string of length 1. The escape character \& is provided as a 「null character」 to allow strings such as "\137\&9" および "\SO\&H" to be constructed (both of length two). Thus "\&" is equivalent to "" and the character '\&' is disallowed. Further equivalences of characters are defined in Section 6.1.2.

A string may include a 「gap」—two backslants enclosing white characters—which is ignored. This allows one to write long strings on more than one line by writing a backslant at the end of one line and at the start of the next. For example,

"Here is a backslant \\ as well as \137, \  
    \a numeric escape character, and \^X, a control character."

String literals are actually abbreviations for lists of characters (see Section 3.7).

2.7 レイアウト

Haskell permits the omission of the braces and semicolons used in several grammar productions, by using layout to convey the same information. This allows both layout-sensitive and layout-insensitive styles of coding, which can be freely mixed within one program. Because layout is not required, Haskell programs can be straightforwardly produced by other programs.

The effect of layout on the meaning of a Haskell program can be completely specified by adding braces and semicolons in places determined by the layout. The meaning of this augmented program is now layout insensitive.

Informally stated, the braces and semicolons are inserted as follows. The layout (or 「off-side」) rule takes effect whenever the open brace is omitted after the keyword where, let, do, or of. When this happens, the indentation of the next lexeme (whether or not on a new line) is remembered and the omitted open brace is inserted (the whitespace preceding the lexeme may include comments). For each subsequent line, if it contains only whitespace or is indented more, then the previous item is continued (nothing is inserted); if it is indented the same amount, then a new item begins (a semicolon is inserted); and if it is indented less, then the layout list ends (a close brace is inserted). If the indentation of the non-brace lexeme immediately following a where, let, do or of is less than or equal to the current indentation level, then instead of starting a layout, an empty list 「{}」 is inserted, and layout processing occurs for the current level (i.e. insert a semicolon or close brace). A close brace is also inserted whenever the syntactic category containing the layout list ends; that is, if an illegal lexeme is encountered at a point where a close brace would be legal, a close brace is inserted. The layout rule matches only those open braces that it has inserted; an explicit open brace must be matched by an explicit close brace. Within these explicit open braces, no layout processing is performed for constructs outside the braces, even if a line is indented to the left of an earlier implicit open brace.

Section 10.3 gives a more precise definition of the layout rules.

Given these rules, a single newline may actually terminate several layout lists. Also, these rules permit:

f x = let a = 1; b = 2  
          g y = exp2  
       in exp1

making a, b および g all part of the same layout list.

As an example, Figure 2.1 shows a (somewhat contrived) module and Figure 2.2 shows the result of applying the layout rule to it. Note in particular: (a) the line beginning }};pop, where the termination of the previous line invokes three applications of the layout rule, corresponding to the depth (3) of the nested where clauses, (b) the close braces in the where clause nested within the tuple and case expression, inserted because the end of the tuple was detected, and (c) the close brace at the very end, inserted because of the column 0 indentation of the end-of-file token.


module AStack( Stack, push, pop, top, size ) where  
data Stack a = Empty  
             | MkStack a (Stack a)  
 
push :: a -> Stack a -> Stack a  
push x s = MkStack x s  
 
size :: Stack a -> Int  
size s = length (stkToLst s)  where  
           stkToLst  Empty         = []  
           stkToLst (MkStack x s)  = x:xs where xs = stkToLst s  
 
pop :: Stack a -> (a, Stack a)  
pop (MkStack x s)  
  = (x, case s of r -> i r where i x = x) -- (pop Empty) is an error  
 
top :: Stack a -> a  
top (MkStack x s) = x                     -- (top Empty) is an error


Figure 2.1: A sample program

module AStack( Stack, push, pop, top, size ) where  
{data Stack a = Empty  
             | MkStack a (Stack a)  
 
;push :: a -> Stack a -> Stack a  
;push x s = MkStack x s  
 
;size :: Stack a -> Int  
;size s = length (stkToLst s)  where  
           {stkToLst  Empty         = []  
           ;stkToLst (MkStack x s)  = x:xs where {xs = stkToLst s  
 
}};pop :: Stack a -> (a, Stack a)  
;pop (MkStack x s)  
  = (x, case s of {r -> i r where {i x = x}}) -- (pop Empty) is an error  
 
;top :: Stack a -> a  
;top (MkStack x s) = x                        -- (top Empty) is an error  
}

Figure 2.2: Sample program with layout expanded