本章では、報告書のこれまでの章で形式にこだわらずに説明してきたところを構文則にしたがって説明する。
本節では、拡張BNFで書かれたSchemeの構文則を述べる。
語法内にはいっているすべての空白は、読みやすさのために入れたものである。大文字と小文字の区別は意味がなく、例えば#x1A
と#X1a
とは等価である。<空>は空の文字列を表す。
BNFへの次の拡張は、説明をさらに簡約にするために使用している。<thing>*は、<thipng>がゼロ回以上発生することを意味する。<thing>+は、<thing>が少くとも一回発生することを意味する。
この項では、一つ一つのトークン(識別子、数、その他)が文字の連続からどのように生成されるかを説明する。続く項では、式とプログラムがトークンの連続からどのように生成されるかを説明する。
<トークン間の空白>はあらゆるトークンのいずれの側にもつけることができるが、トークン内部に入れることはできない。
暗黙の終了を必要とするトークン(識別子、数、文字、ドット)はいかなる<区切り文字>で終了することもできるが、必ずしもそれ以外の何で終了してもいいというわけではない。
以下の五文字は言語の将来の拡張のために予約されている: "[" "]" "{" "}" "|"。
<トークン> ---> <識別子> | <論理値> | <数> | <文字> | <文字列> | ( | ) | #( | ' | ` | , | ,@ | . <区切り文字> ---> <空白> | ( | ) | " | ; <空白> ---> <空白もしくは改行> <コメント> ---> ; <改行まで後続するすべての文字> <空白圏> ---> <空白> | <コメント> <トークン間の空白> ---> <空白圏>*
<識別子> ---> <頭字> <後続文字>* | <特殊な識別子> <頭字> ---> <文字> | <特殊な頭字> <文字> ---> a | b | c | ... | z <特殊な頭字> ---> ! | $ | % | & | * | / | : | < | = | > | ? | ~ | _ | ~ <後続文字> ---> <頭字> | <数字> | <特殊な後続文字> <数字> ---> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 <特殊な後続文字> ---> + | - | . | @ <特殊な識別子> ---> + | - | ... <構文キーワード> ---> <式のキーワード> | else | => | define | unquote | unquote-splicing <式のキーワード> ---> quote | lambda | if | set! | begin | cond | and | or | case | let | let* | letrec | do | delay | quasiquote <変数> ---> <同時に<構文キーワード>でないあらゆる<識別子>> <論理値> ---> #t | #f <文字> ---> #\ <あらゆる文字> | #\ <文字名> <文字名> ---> space | newline <文字列> ---> " <文字列要素>* " <文字列要素> ---> <"か\以外のあらゆる文字> | \" | \\
<数> ---> <基数2> | <基数8> | <基数10> | <基数16>
<数R>、<複素数R>、<実数R>、<符号なし実数R>、<符号なし整数R>、<接頭辞R>に対する以下の規則は、R = 2, 8, 10, 16のすべての場合に適用される。<2進小数>、<8進小数>、<16進小数>に関する規則は存在しない。これは、小数点もしくは指数を含む数の基数が10でなければならないことを意味する。
<数R> ---> <接頭辞R> <複素数R> <複素数R> ---> <実数R> | <実数R> @ <実数R> | <実数R> + <符号なし実数R> i | <実数R> - <符号なし実数R> i | <実数R> + i | <実数R> - i | + <符号なし実数R> i | - <符号なし実数R> i | + i | - i <実数R> ---> <符号> <符号なし実数R> <符号なし実数R> ---> <符号なし整数R> | <符号なし整数R> / <符号なし整数R> | <小数R> <10進小数> ---> <符号なし10進整数> <接尾辞> | . <10進数字>+ #* <接尾辞> | <10進数字>+ . <10進数字>* #* <接尾辞> | <10進数字>+ #+ . #* <接尾辞> <符号なし整数R> ---> <数字R>+ #* <接頭辞R> ---> <基数R> <完全性> | <完全性> <基数R>
<接頭辞> ---> <空> | <指数標識> <符号> <10進数字>+ <指数標識> ---> e | s | f | d | l <符号> ---> <空> | + | - <完全性> ---> <空> | #i | #e <基数2> ---> #b <基数8> ---> #o <基数10> ---> <空> | #d <基数16> ---> #x <2進数字> ---> 0 | 1 <8進数字> ---> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 <10進数字> ---> <数字> <16進数字> ---> <10進数字> | a | b | c | d | e | f
<データ>は、read
手続き(section 7.6.2 入力参照)が構文解析できたものである。<式>として構文解析されるあらゆる文字列は<データ>としても構文解析される。
<データ> ---> <単純データ> | <複合データ> <単純データ> ---> <論理値> | <数> | <文字> | <文字列> | <シンボル> <シンボル> ---> <識別子> <複合データ> ---> <リスト> | <ベクタ> <リスト> ---> (<データ>*) | (<データ>+ . <データ>) | <略記> <略記> ---> <略記接頭辞> <データ> <略記接頭辞> ---> ' | ` | , | ,@ <ベクタ> ---> #(<データ>*)
<式> ---> <変数> | <リテラル> | <手続き呼び出し> | <ラムダ式> | <条件式> | <割り当て> | <導出式> | <マクロの使用> | <マクロブロック> <リテラル> ---> <クォート式> | <自己評価式> <自己評価式> ---> <論理値> | <数> | <文字> | <文字列> <クォート式> '<データ> | (quote <データ>) <手続き呼び出し> ---> (<演算子> <被演算子>*) <演算子> ---> <式> <被演算子> ---> <式> <ラムダ式> ---> (lambda <仮引数> <ボディ>) <仮引数> ---> (<変数>*) | <変数> | (<変数>+ . <変数>) <ボディ> ---> <定義>* <シーケンス> <シーケンス> ---> <コマンド>* <式> <コマンド> ---> <式> <条件式> ---> (if <テスト> <帰結> <代わりの帰結>) <テスト> ---> <式> <帰結> ---> <式> <代わりの帰結> ---> <式> | <空> <割り当て> ---> (set! <変数> <式>) <導出式> ---> (cond <cond節>+) | (cond <cond節>* (else <シーケンス>)) | (case <式> <case節>+) | (case <式> <case節>* (else <シーケンス>)) | (and <テスト>*) | (or <テスト>*) | (let (<バインディング仕様>*) <ボディ>) | (let <変数> (<バインディング仕様>*) <ボディ>) | (let* (<バインディング仕様>*) <ボディ>) | (letrec (<バインディング仕様>*) <ボディ>) | (begin <シーケンス>) | (do (<繰り返し仕様>) (<テスト> <do式の結果>) <コマンド>*) | (delay <式>) | <バッククォート式> <cond節> ---> (<テスト> <シーケンス>) | (<テスト>) | (<テスト> => <受け入れ手続き>) <受け入れ手続き> ---> <式> <case節> ---> ((<データ>*) <シーケンス>) <バインディング仕様> ---> (<変数> <式>) <繰り返し仕様> ---> (<変数> <初期値> <ステップ>) | (<変数> <初期値>) <初期値> ---> <式> <ステップ> ---> <式> <do式の結果> ---> <シーケンス> | <空> <マクロの使用> ---> (<キーワード> <データ>*) <キーワード> ---> <識別子> <マクロブロック> ---> (let-syntax (<構文仕様>*) <ボディ>) | (letrec-syntax (<構文仕様>*) <ボディ>) <構文仕様> ---> (<キーワード> <変換手続き仕様>)
バッククォート式に関する以下の文法は文脈自由ではない。これは無限数の生成規則を生成するための方法として示すものである。D = 1、2、3 ...の場合の以下の規則を想像してみればよい。Dはネストレベルの深さを示す。
<バッククォート式> ---> <バッククォート式1> <qqテンプレート0> ---> <式> <バッククォート式D> ---> `<qqテンプレートD> | (quasiquote <qqテンプレートD>) <qqテンプレートD> ---> <単純データ> | <リストqqテンプレートD> | <ベクタqqテンプレートD> | <アンクォートD> <リストqqテンプレートD> ---> (<qqテンプレートもしくは挿入D>*) | (<qqテンプレートもしくは挿入D>+ . <qqテンプレートD>) | '<qqテンプレートD> | <バッククォート式D+1> <ベクタqqテンプレートD> ---> #(<qqテンプレートもしくは挿入D>*) <アンクォートD> ---> ,<qqテンプレートD-1> | (unquote <qqテンプレートD-1>) <qqテンプレートもしくは挿入D> ---> <qqテンプレートD> | <挿入アンクォートD> <挿入アンクォートD> ---> ,@<qqテンプレートD-1> | (unquote-splicing <qqテンプレートD-1>)
<バッククォート式>においては、<リストqqテンプレートD>が<アンクォートD>や<挿入アンクォートD>と混同されやすい場合がある。<アンクォートD>か<挿入アンクォートD>としての解釈の方が優先される。
<変換手続き仕様> ---> (syntax-rules (<識別子>*) <構文規則>*) <構文規則> ---> (<パターン> <テンプレート>) <パターン> ---> <パターン識別子> | (<パターン>*) | (<パターン>+ . <パターン>) | (<パターン>* <パターン> <略記号>) | #(<パターン>*) | #(<パターン>* <パターン> <略記号>) | <パターンデータ> <パターンデータ> ---> <文字列> | <文字> | <論理値> | <数> <テンプレート> ---> <パターン識別子> | (<テンプレート要素>*) | (<テンプレート要素>+ . <テンプレート>) | #(<テンプレート要素>*) | <テンプレートデータ> <テンプレート要素> ---> <テンプレート> | <テンプレート> <略記号> <テンプレートデータ> ---> <パターンデータ> <パターン識別子> ---> <... を除くあらゆる識別子> <略記号> ---> <識別子 ...>
<プログラム> ---> <コマンドもしくは定義>* <コマンドもしくは定義> ---> <コマンド> | <定義> | <構文定義> | (begin <コマンドもしくは定義>+) <定義> ---> (define <変数> <式>) | (define (<変数> <仮引数定義>) <ボディ>) | (begin <定義>*) <仮引数定義> ---> <変数>* | <変数>* . <変数> <構文定義> (define-syntax <キーワード> <変換手続き仕様>)
... Texinfo版にては省略 ...。
本節では、導出式型をプリミティブ式型(リテラル、変数、手続き呼び出し、lambda
、if
、set!
)に置き換えるマクロ定義を示す。delay
に関して可能な定義についてはsection 7.4 制御機能参照。
(define-syntax cond
(syntax-rules (else =>)
((cond (else result1 result2 ...))
(begin result1 result2 ...))
((cond (test => result))
(let ((temp test))
(if temp (result temp))))
((cond (test => result) clause1 clause2 ...)
(let ((temp test))
(if temp
(result temp)
(cond clause1 clause2 ...))))
((cond (test)) test)
((cond (test) clause1 clause2 ...)
(let ((temp test))
(if temp
temp
(cond clause1 clause2 ...))))
((cond (test result1 result2 ...))
(if test (begin result1 result2 ...)))
((cond (test result1 result2 ...)
clause1 clause2 ...)
(if test
(begin result1 result2 ...)
(cond clause1 clause2 ...)))))
(define-syntax case
(syntax-rules (else)
((case (key ...)
clauses ...)
(let ((atom-key (key ...)))
(case atom-key clauses ...)))
((case key
(else result1 result2 ...))
(begin result1 result2 ...))
((case key
((atoms ...) result1 result2 ...))
(if (memv key '(atoms ...))
(begin result1 result2 ...)))
((case key
((atoms ...) result1 result2 ...)
clause clauses ...)
(if (memv key '(atoms ...))
(begin result1 result2 ...)
(case key clause clauses ...)))))
(define-syntaxand
(syntax-rules () ((and)#t
) ((and test) test) ((and test1 test2 ...) (if test1 (and test2 ...)#f
))))
(define-syntaxor
(syntax-rules () ((or)#f
) ((or test) test) ((or test1 test2 ...) (let ((x test1)) (if x x (or test2 ...))))))
(define-syntax let
(syntax-rules ()
((let ((name val) ...) body1 body2 ...)
((lambda (name ...) body1 body2 ...)
val ...))
((let tag ((name val) ...) body1 body2 ...)
((letrec ((tag (lambda (name ...)
body1 body2 ...)))
tag)
val ...))))
(define-syntax let*
(syntax-rules ()
((let* () body1 body2 ...)
(let () body1 body2 ...))
((let* ((name1 val1) (name2 val2) ...)
body1 body2 ...)
(let ((name1 val1))
(let* ((name2 val2) ...)
body1 body2 ...)))))
次のletrec
マクロでは、記憶域内に格納されたときに、格納場所から格納された値を取り出そうとするとエラーになるようなオブジェクトを返す式に代えて、シンボル<undefined>
を使用している(このような式はSchemeでは定義されていない)。値を評価する順序の指定を避けるために必要な一時名を生成するために、一種の手品を使用している。これは補助的なマクロを使用して行なうこともできる。
(define-syntax letrec
(syntax-rules ()
((letrec ((var1 init1) ...) body ...)
(letrec "generate\_temp\_names"
(var1 ...)
()
((var1 init1) ...)
body ...))
((letrec "generate\_temp\_names"
()
(temp1 ...)
((var1 init1) ...)
body ...)
(let ((var1 <undefined>) ...)
(let ((temp1 init1) ...)
(set! var1 temp1)
...
body ...)))
((letrec "generate\_temp\_names"
(x y ...)
(temp ...)
((var1 init1) ...)
body ...)
(letrec "generate\_temp\_names"
(y ...)
(newtemp temp ...)
((var1 init1) ...)
body ...))))
(define-syntax begin
(syntax-rules ()
((begin exp ...)
((lambda () exp ...)))))
次のもう1つのbegin
式の展開では、ラムダ式のボディ内に2つ以上の式を書く機能を使用していない。いずれの場合にも構文規則は、begin
式のボディに定義が含まれない場合にのみ適用される点に注意する必要がある。
(define-syntax begin (syntax-rules () ((begin exp) exp) ((begin exp1 exp2 ...) (let ((x exp1)) (begin exp2 ...)))))
次のdo
式の定義では、変数節の展開に一種の手品を使用している。前のletrec
式の場合と同じく補助的なマクロを使うこともできる。不定の値を求めるために、式(if #f #f)
が使用されている。
(define-syntax do
(syntax-rules ()
((do ((var init step ...) ...)
(test expr ...)
command ...)
(letrec
((loop
(lambda (var ...)
(if test
(begin
(if #f #f)
expr ...)
(begin
command
...
(loop (do "step" var step ...)
...))))))
(loop init ...)))
((do "step" x)
x)
((do "step" x y)
y)))