Анимация
JavaScript
|
Главная Библионтека соответствующей грамматики можно написать синтаксический анализатор, причем почти так же быстро, как мы можем вообще писать. Рассмотрим, например, язык, генерируемый грамматикой со следующими порождающими правилами: PROGRAM->begin DECLIST comma STATELIST end DECLIST->d semi DECLIST DECLIST->d STATELIST->s semi STATELIST STATELIST->s Сначала видоизменим грамматику: PROGRAM->begin DECLIST comma TATELIST end DECLIST->d X X->semi DECLIST X->e STATELIST->s Y Y->semi STATELIST Y->e. Теперь напишем процедуру распознавания каждого нетерминала. Начав с символа (PROGRAM), получим ргос PROGRAM = void: begin if symbol 4)egin tben error fi; symbol: = lexical; DECLIST; if symbol 9 comma then error fi; symbol: = lexical; STATELIST; if symbol 7 end then error fi end; proc DECLIST= void: begin if symbol 9d then error fi; symbol := lexical; end; proc X=void: if symbol = semi then symbol := lexical; DECLIST elif symbol = comma then skip else error proc STATELIST = void: begin if symbol then error fi; symbol := lexical; Y end; proc Y = void: if symbol = semi then symbol: = lexical; STATELIST elif symbol = end then skip else error В этих процедурах сделан ряд допущений: 1. error - процедура обращения с синтаксическими ошибками; детали нас здесь не интересуют; 2. lexical - процедура, которая читает литеры исходного текста и выдает следующий символ. Если лексический анализ выполняется предыдущим проходом, lexical просто читает следующий символ; 3. semi, comma и т. д. - идентификаторы, значениями которых являются представления соответствующих терминалов во время компиляции; 4. прежде чем вызывается PR0GRAM, происходит следующее присваивание: symbol: = lexical. Для разбора предложения языка может потребоваться много рекурсивных вызовов процедур, соответствующих нетерминалам в грамматике. Если мы решим представлять грамматику несколько иным путем, эту рекурсию можьо заменить итерацией: PROGRAM->begin DECLIST comma STATELIST end DECLIST->d (semi d)* STATELIST->s (semi s)* где (х)* означает нуль или более реализациий х. Тогда процедуры для DECLIST и STATELIST становятся такими: proc DECLIST = void: begin if symbol d then error fi: symbol: = lexical; while symbol = semi do symbol: = lexical; if symbol Ф d then error fi; symbol: = lexical od end: proc STATELIST = void: begin if symbol Ф s then error fi; symbol : = lexical; while symbol = semi do symbol: = lexical; if symbol Ф s then error fi : symbol : = lexical Замена рекурсии итерацией, возможно, делает анализатор более эффективным, а также (не бесспорно) более удобочитаемым. Преимущества написания рекурсивного нисходящего анализатора очевидны. Основные из них - это скорость написания анализатора на основании соответствующей грамматики. Другое преимущества заключается в соответствии между грамматикой и анализатором, благодаря которому увеличивается вероятность того, что анализатор окажется правильным, или, по крайней мере, того, что ошибки будут носить простой характер. Недостатки этого метода, хотя и менее очевидны, не менее реальны. Из-за большого числа вызовов процедур во время синтаксического анализа анализатор становится относительно медленным. Кроме того, он может быть довольно большим по сравнению с анализаторами, основанными на табличных методах разбора, которые описываются ниже. Несмотря на то что данный метод способствует включению в анализатор действия по генерированию кода, это неизбежно приводит к смешиванию различных фаз компиляции. Последнее снижает надежность компиляторов или усложняет обращение с ними и как бы привносит в анализатор зависимость от машины. Рекурсивный метод разбора сверху вниз родствен другому хорошо 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [ 18 ] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |