owned this note
owned this note
Published
Linked with GitHub
---
title: syntax-parse introduction
tags: presentation
slideOptions:
theme: white
controls: true
progress: true
slideNumber: 'c/t'
center: false
fragments: true
help: true
# defaultTiming: 120
transition: 'convex'
# none/fade/slide/convex/concave/zoom
transitionSpeed: 'default'
# default/fast/slow
keyboard: true
width: '93%'
height: '100%'
spotlight:
enabled: false
---
<style>
/* basic design */
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6,
.reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {
font-family: 'Meiryo UI', 'Source Sans Pro', Helvetica, sans-serif, 'Helvetica Neue', 'Helvetica', 'Arial', 'Hiragino Sans', 'ヒラギノ角ゴシック', YuGothic, 'Yu Gothic';
text-align: left;
line-height: 1.6;
letter-spacing: normal;
text-shadow: none;
word-wrap: break-word;
color: #444;
}
.reveal h1, .reveal h2, .reveal h3, .reveal h4, .reveal h5, .reveal h6 {font-weight: bold;}
.reveal h1, .reveal h2, .reveal h3 {color: #00a474;}
.reveal th {background: #DDD;}
.reveal section img {background:none; border:none; box-shadow:none; max-width: 95%; max-height: 95%;}
.reveal blockquote {width: 90%; padding: 0.5vw 3.0vw;}
.reveal table {margin: 1.0vw auto;}
.reveal code {line-height: 1.2;}
.reveal p, .reveal li {padding: 0vw; margin: 0vw;}
.reveal .box {margin: -0.5vw 1.5vw 2.0vw -1.5vw; padding: 0.5vw 1.5vw 0.5vw 1.5vw; background: #e4ffe5; border-radius: 1.5vw;}
/* table design */
.reveal table {background: #f5f5f5;}
.reveal th {background: #444; color: #fff;}
.reveal td {position: relative; transition: all 300ms;}
.reveal tbody:hover td { color: transparent; text-shadow: 0 0 3px #aaa;}
.reveal tbody:hover tr:hover td {color: #444; text-shadow: 0 1px 0 #fff;}
/* blockquote design */
.reveal blockquote {
width: 90%;
padding: 0.5vw 0 0.5vw 6.0vw;
font-style: italic;
background: #ddffff;
}
.reveal blockquote:before{
position: absolute;
top: 0.1vw;
left: 1vw;
content: "\f10d";
font-family: FontAwesome;
color: #00a474;
font-size: 3.0vw;
}
/* font size */
.reveal h1 {font-size: 5.0vw;}
.reveal h2 {font-size: 4.0vw;}
.reveal h3 {font-size: 2.8vw;}
.reveal h4 {font-size: 2.6vw;}
.reveal h5 {font-size: 2.4vw;}
.reveal h6 {font-size: 1.6vw;}
.reveal section, .reveal table, .reveal li, .reveal blockquote, .reveal th, .reveal td, .reveal p {font-size: 2.2vw;}
.reveal code {font-size: 1.6vw;}
/* new color */
.red {color: #EE6557;}
.blue {color: #16A6B6;}
/* split slide */
#right {left: -18.33%; text-align: left; float: left; width: 50%; z-index: -10;}
#left {left: 31.25%; text-align: left; float: left; width: 50%; z-index: -10;}
</style>
<style>
/* 背景デザイン */
.reveal {
background-color:/*背景色*/
#f8f8ff;
}
.reveal h1 {padding: 3.0vw 0vw;}
@media screen and (max-width: 1024px) {
.reveal h2 {margin: -2.0vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
@media screen and (min-width: 1025px) and (max-width: 1920px) {
.reveal h2 {margin: -1.5vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
@media screen and (min-width: 1921px) and (max-width: 100000px) {
.reveal h2 {margin: -1.0vw 0 0 0; padding: 0.0vw 0vw 3.0vw 2.0vw; }
}
</style>
<style>
/* specific design */
.reveal h2 {
padding: 0 1.5vw;
margin: 0.0vw 0 2.0vw -2.0vw;
border-left: solid 1.2vw #00a474;
border-bottom: solid 0.8vw #9e9e9e;
}
</style>
<!-- --------------------------------------------------------------------------------------- -->
#### 卒業研究スライド
# Syntax-Parse Introduction
<br>
<br>
#### 2021/11/19 & 11/26
### @ariken
---
## Introduction
<div class="box">
syntax-parse を使用するマクロは、マクロの構文パターンに埋め込まれた説明やメッセージに基づいて、エラーメッセージを自動的に生成する。
</div>
----
Racket の let フォームと同じ構文と動作を持つ mylet という名前のマクロを記述する。このマクロは、間違って使用された場合に適切なエラーメッセージを生成する必要がある。
```Racket
(mylet ([var-id rhs-expr] ...) body ...+)
(mylet loop-id ([var-id rhs-expr] ...) body ...+)
```
> lhs = Left Hand Side(左辺)
> rhs = Right Hand Side(右辺)
###### 簡単にするために、ここでは第1のケースだけを扱うこととする。2番目のケースについては、後ほど紹介。
----
マクロは、define-syntax-rule を使用して非常に簡単に実装できる。
```Racket
(define-syntax-rule (mylet ([var rhs] ...) body ...)
((lambda (var ...) body ...) rhs ...))
```
正しく使用された場合、マクロは動作するが、エラーが発生した場合の動作は非常に悪くなる。一部のケースでは、マクロは情報のないエラーメッセージを表示して単に失敗する。その他のケースでは、不正な構文を平然と受け入れて lambda に渡し、奇妙な結果になる。
----
## テスト
```Racket
> (mylet ([a 1] [b 2]) (+ a b))
3
> (mylet (b 2) (sub1 b))
mylet: use does not match pattern: (mylet ((var rhs) ...)
body ...)
in: (mylet (b 2) (sub1 b))
> (mylet ([1 a]) (add1 a))
lambda: not an identifier, identifier with default, or
keyword
at: 1
in: (lambda (1) (add1 a))
```
```Racket
> (mylet ([#:x 1] [y 2]) (* x y))
eval:1:0: arity mismatch;
the expected number of arguments does not match the given
number
expected: 0 plus an argument with keyword #:x
given: 2
arguments...:
1
2
```
----
syntax-parse を使用して、マクロのエラー動作を改善することができる。まず、マクロトランスフォーマの実装に syntax-parse を使用するので、トランスフォーマ環境に syntax-parse をインポートする。
`> (require (for-syntax syntax/parse))`
```racket
(syntax-parse stx-expr parse-option ... clause ...+)
```
----
以下は、上記の構文仕様を syntax-parse マクロ定義に書き直したものである。上記の define-syntax-rule を使用したバージョンと同じように動作する。
```Racket
> (define-syntax (mylet stx)
(syntax-parse stx
[(_ ([var-id rhs-expr] ...) body ...+)
#'((lambda (var-id ...) body ...) rhs-expr ...)]))
```
…は直前のパターンの0回以上の繰り返しに一致することを意味し、…+は1回以上の繰り返しに一致することを意味する。ただし、テンプレートでは … のみを使用することができる。
----
検証と高品質なエラー報告のための最初のステップは、マクロの各パターン変数に、許容される構文を記述する構文クラスをアノテーションすること。
mylet では、各変数は識別子 (id) であり、各右辺は expr (式) でなければならない。アノテーションされたパターン変数は、パターン変数名、コロン文字、および構文クラス名を連結して記述される。
```Racket
> (define-syntax (mylet stx)
(syntax-parse stx
[(_ ((var:id rhs:expr) ...) body ...+)
#'((lambda (var ...) body ...) rhs ...)]))
```
###### 構文クラス(syntax class)の注訳はテンプレートに表示されない (var:id ではなく var )
----
構文クラス(syntax class)の注釈は、マクロを使用する際にチェックされる。
```Racket
> (mylet ([a 1] [b 2]) (+ a b))
3
> (mylet (["a" 1]) (add1 a))
mylet: expected identifier
at: "a"
in: (mylet (("a" 1)) (add1 a))
```
----
expr 構文クラスは、マッチした項が有効な式であるかどうかを実際にはチェックしない。それには、拡張マクロを呼び出す必要があります。その代わり、expr はキーワードではないことを意味する。
```racket
> (mylet ([a #:whoops]) 1)
mylet: expected expression
at: #:whoops
in: (mylet ((a #:whoops)) 1)
```
----
----
また、syntax-parseは、いくつかの種類のエラーを何の助けもなく報告する方法を知っている。
```racket
> (mylet ([a 1 2]) (* a a))
mylet: unexpected term
at: 2
in: (mylet ((a 1 2)) (* a a))
```
----
しかし、他の種類のエラーもあり、このマクロは優雅に処理できない。
```racket
> (mylet (a 1) (+ a 2))
mylet: bad syntax
in: (mylet (a 1) (+ a 2))
```
マクロが "この式は(a 1)の周りに括弧のペアがない"と反応するのは無理がある。パターンマッチャーはそこまで賢くはないが、エラーの原因を特定することができる。それは、aに遭遇したとき、「結合ペア」と呼ばれるものを期待していたのですが、その用語はまだマクロの語彙には存在しない。
----
syntax-parseがより良いエラーを合成するためには、私たちが個別のシンタックスカテゴリとして認識しているパターンに説明を付けなければならない。そのための一つの方法は、新しいシンタックスクラスを定義することである。
`(define-syntax-class name-id stxclass-option ...
stxclass-variant ...+)`
----
```Racket
> (define-syntax (mylet stx)
(define-syntax-class binding
#:description "binding pair"
(pattern (var:id rhs:expr)))
(syntax-parse stx
[(_ (b:binding ...) body ...+)
#'((lambda (b.var ...) body ...) b.rhs ...)]))
```
ここで、b.varとb.rhsと書いていることに注意する。これは、注釈されたパターン変数bと、シンタックスクラスbindingの属性varとrhsから形成される入れ子の属性である。
----
これで、エラーメッセージは「binding pair」について語ることができるようになった。
```racket
> (mylet (a 1) (+ a 2))
mylet: expected binding pair
at: a
in: (mylet (a 1) (+ a 2))
```
----
エラーが発生した場合は、可能な限りより具体的な表現で報告される。
```racket
> (mylet (["a" 1]) (+ a 2))
mylet: expected identifier
at: "a"
in: (mylet (("a" 1)) (+ a 2))
parsing context:
while parsing binding pair
term: ("a" 1)
location: eval:16:0
```
----
myletの合法的な構文には、もうひとつ制約がある。異なる結合ペアによって結合される変数は、それぞれ異なるものでなければならない。そうでない場合、マクロは不正なラムダ形式を作成する。
```racket
> (mylet ([a 1] [a 2]) (+ a a))
lambda: duplicate argument name
at: a
in: (lambda (a a) (+ a a))
```
----
識別性の要求などの制約は、サイドコンディションとして表現される。
```Racket
> (define-syntax (mylet stx)
(define-syntax-class binding
#:description "binding pair"
(pattern (var:id rhs:expr)))
(syntax-parse stx
[(_ (b:binding ...) body ...+)
#:fail-when (check-duplicate-identifier
(syntax->list #'(b.var ...)))
"duplicate variable name"
#'((lambda (b.var ...) body ...) b.rhs ...)]))
```
#:fail-when の後には、条件とエラーメッセージの 2 つの式が続いている。条件が #f 以外の値で評価された場合、パターンは失敗する。さらに、条件がシンタックスオブジェクトとして評価された場合、そのシンタックスオブジェクトは失敗の原因を特定するために使用されます。
----
シンタックスクラスにもサイドコンディションを持たせることができる。以下のマクロは、"異なる結合ペアのシーケンス "を表す別の構文クラスを含むように書き換えられている。
```Racket
> (define-syntax (mylet stx)
(define-syntax-class binding
#:description "binding pair"
(pattern (var:id rhs:expr)))
(define-syntax-class distinct-bindings
#:description "sequence of distinct binding pairs"
(pattern (b:binding ...)
#:fail-when (check-duplicate-identifier
(syntax->list #'(b.var ...)))
"duplicate variable name"
#:with (var ...) #'(b.var ...)
#:with (rhs ...) #'(b.rhs ...)))
(syntax-parse stx
[(_ bs:distinct-bindings . body)
#'((lambda (bs.var ...) . body) bs.rhs ...)]))
```
ここでは、#:with句を紹介する。with節は、パターンと計算された用語をマッチさせる。ここでは、var と rhs をdistinct-bindings の属性としてバインドするために使用している。デフォルトでは、構文クラスはそのパターンのパターン変数のみを属性としてエクスポートし、ネストした属性はエクスポートしない。
* 別の方法としては、#:attribute オプションを使用して、入れ子になった属性 b.var と b.rhs を含むように distinct-bindings の属性を明示的に宣言する必要がある。この場合、マクロは bs.b.var および bs.b.rhs を参照する。
---
しかし、今のところ、このマクロは Racket の let が提供する機能の半分しか実装していない。named-let形式を追加する必要がある。これは、新しい節を追加するだけの簡単なことだとわかった。
```racket
> (define-syntax (mylet stx)
(define-syntax-class binding
#:description "binding pair"
(pattern (var:id rhs:expr)))
(define-syntax-class distinct-bindings
#:description "sequence of distinct binding pairs"
(pattern (b:binding ...)
#:fail-when (check-duplicate-identifier
(syntax->list #'(b.var ...)))
"duplicate variable name"
#:with (var ...) #'(b.var ...)
#:with (rhs ...) #'(b.rhs ...)))
(syntax-parse stx
[(_ bs:distinct-bindings body ...+)
#'((lambda (bs.var ...) body ...) bs.rhs ...)]
[(_ loop:id bs:distinct-bindings body ...+)
#'(letrec ([loop (lambda (bs.var ...) body ...)])
(loop bs.rhs ...))]))
```
distinct-bindings構文クラスを再利用することができるので、「named-let」構文の追加は3行で済む。
しかし、この新しいケースを追加することで、syntax-parseがエラーを特定して報告する機能に影響はないのか?
```rust
> (mylet ([a 1] [b 2]) (+ a b))
3
> (mylet (["a" 1]) (add1 a))
mylet: expected identifier
at: "a"
in: (mylet (("a" 1)) (add1 a))
parsing context:
while parsing binding pair
term: ("a" 1)
location: eval:23:0
while parsing sequence of distinct binding pairs
term: (("a" 1))
location: eval:23:0
> (mylet ([a #:whoops]) 1)
mylet: expected expression
at: #:whoops
in: (mylet ((a #:whoops)) 1)
parsing context:
while parsing binding pair
term: (a #:whoops)
location: eval:24:0
while parsing sequence of distinct binding pairs
term: ((a #:whoops))
location: eval:24:0
> (mylet ([a 1 2]) (* a a))
mylet: unexpected term
at: 2
in: (mylet ((a 1 2)) (* a a))
parsing context:
while parsing binding pair
term: (a 1 2)
location: eval:25:0
while parsing sequence of distinct binding pairs
term: ((a 1 2))
location: eval:25:0
> (mylet (a 1) (+ a 2))
mylet: expected binding pair
at: a
in: (mylet (a 1) (+ a 2))
parsing context:
while parsing sequence of distinct binding pairs
term: (a 1)
location: eval:26:0
> (mylet ([a 1] [a 2]) (+ a a))
mylet: duplicate variable name
at: a
in: (mylet ((a 1) (a 2)) (+ a a))
parsing context:
while parsing sequence of distinct binding pairs
term: ((a 1) (a 2))
location: eval:27:0
```
元の構文のエラー報告はそのままのようです。named-let構文が動作していること、syntax-parseが単にその節を無視していないことを確認する必要があります。
```rust
> (mylet loop ([a 1] [b 2]) (+ a b))
3
> (mylet loop (["a" 1]) (add1 a))
mylet: expected identifier
at: "a"
in: (mylet loop (("a" 1)) (add1 a))
parsing context:
while parsing binding pair
term: ("a" 1)
location: eval:29:0
while parsing sequence of distinct binding pairs
term: (("a" 1))
location: eval:29:0
> (mylet loop ([a #:whoops]) 1)
mylet: expected expression
at: #:whoops
in: (mylet loop ((a #:whoops)) 1)
parsing context:
while parsing binding pair
term: (a #:whoops)
location: eval:30:0
while parsing sequence of distinct binding pairs
term: ((a #:whoops))
location: eval:30:0
> (mylet loop ([a 1 2]) (* a a))
mylet: unexpected term
at: 2
in: (mylet loop ((a 1 2)) (* a a))
parsing context:
while parsing binding pair
term: (a 1 2)
location: eval:31:0
while parsing sequence of distinct binding pairs
term: ((a 1 2))
location: eval:31:0
> (mylet loop (a 1) (+ a 2))
mylet: expected binding pair
at: a
in: (mylet loop (a 1) (+ a 2))
parsing context:
while parsing sequence of distinct binding pairs
term: (a 1)
location: eval:32:0
> (mylet loop ([a 1] [a 2]) (+ a a))
mylet: duplicate variable name
at: a
in: (mylet loop ((a 1) (a 2)) (+ a a))
parsing context:
while parsing sequence of distinct binding pairs
term: ((a 1) (a 2))
location: eval:33:0
```
syntax-parseはどのようにしてプログラマが試みた節を決定し、それをエラー報告の基準として使用するのだろうか?結局のところ、named-let構文の悪い使い方は、通常の構文の悪い使い方でもあり、その逆もまた然りである。しかし、このマクロは "mylet: expected sequence of distinct binding pairs at: loop. "のようなエラーを出さない。
その答えは、syntax-parseが、すべての潜在的なエラー(distinct-bindingにマッチしないloopのようなものを含む)のリストと、各エラーの前に行われた進捗状況を記録するからである。最も進展したエラーだけが報告される。
たとえば、このマクロの悪い使い方では,、のようになる。
```racket
> (mylet loop (["a" 1]) (add1 a))
mylet: expected identifier
at: "a"
in: (mylet loop (("a" 1)) (add1 a))
parsing context:
while parsing binding pair
term: ("a" 1)
location: eval:34:0
while parsing sequence of distinct binding pairs
term: (("a" 1))
location: eval:34:0
```
ここでは2つのエラーが発生する可能性があるloopで期待されるdistinct-bindingsと "a "で期待されるidentifierである。2つ目のエラーは、1つ目のエラーよりも用語の中で発生しているため、報告される。
別の例として、この用語を考えてみる。
```raaket
> (mylet (["a" 1]) (add1 a))
mylet: expected identifier
at: "a"
in: (mylet (("a" 1)) (add1 a))
parsing context:
while parsing binding pair
term: ("a" 1)
location: eval:35:0
while parsing sequence of distinct binding pairs
term: (("a" 1))
location: eval:35:0
```
ここでも2つのエラーが発生する可能性がある: expected identifier at ([“a” 1]) と expected identifier at “a” である。どちらも第2項(あるいは第1引数)で発生しますが、2番目のエラーは項の中でより深く発生します。進捗状況は、構文の左から右へのトラバーサルに基づいている。
このセクションでは、構文クラス、サイドコンディション、進行順のエラー報告について紹介した。しかし、syntax-parseにはもっと多くの機能がある。他の機能のサンプルを実際のコードで見るには「Example」のセクションに続く。