問題は、終端記号を見つけたときに何をするかです。ここには、実際には 2 つの別個の問題があります。
まず、各ルールの最初のシンボルだけを見ます。変数lsts
はルールのリストです。各ルールはシンボルのリストです。したがって(car (car lsts))
、最初のルールの最初のシンボルを取得します。
2 番目の問題は、終端記号を見つけた後に再帰する方法です。通常、terminals
リストのリストを渡します。ただし、終端記号 (つまり、で始まる記号) を見つけた後は、(member? ...)
それを渡します(car (cdr lsts))
。lsts
がリストのリストであることはわかっています。リスト(cdr lsts)
のリスト (またはもちろん空のリスト) も同様です。これ(car (cdr lsts))
は単なる通常のリストであることを意味します。
この問題を解決する最も簡単な方法は、2 つの関数に分割することだと思います。まず、単一のルールから端末を取得する関数を作成します。つまり、入力全体に注目するのではなく、 の((A cat) (B happy np) (A B sad))
ように一度に 1 つのルールに注目し(B happy np)
ます。したがって、 が与えられたときに が得られる関数を書き(B happy np)
ます(happy np)
。次に、この関数をすべてのルールに適用しlsts
、すべての出力を結合する関数を作成します。(append
ここで関数が役立つ場合があります。)
これを 2 つの別個の関数で記述すると、問題を検討しやすくなります。結果のコードもよりシンプルで読みやすくなり、これはおまけです。
また、スタイルとは関係のない問題:書くことができます(そして書くべきです)
(define foo
(lambda (args) ...))
なので
(define (foo args)
...)
これら 2 つの表現は同じ意味を持ちますが、2 番目の方が読みやすく、より頻繁に使用されます。
編集:コメントで言ったように、最初に非端末のリストを計算し、それを一度に1行ずつ実行する関数に使用します。2 つの関数をネストし、Scheme のレキシカル スコープを利用することで、これを簡単に使用できます。
(define (terminals rules)
(let ((non-terminals (n-terminals rules)))
(define (helper rule) ; this takes a rule and returns all its terminal symbols
... ; you can use non-terminals in here
)
; now here you have to call helper on every rule and combine the results
))
別のオプションは、ネストされていない 2 つの関数に分割し、非終端記号のリストを渡すことです。
(define (terminals-from-rule rule non-terminals)
... ; implement, using non-terminals as the list of non-terminal symbols
)
(define (terminals rules)
... ; here you need to calculate the list of non-terminals and pass
; it into each call to terminals-from-rule along with the rule.
)