ЛЕКСИЧЕСКИЙ АНАЛИЗ
Конечно, вы знаете, что будет дальше: Мы должны преобразовать программу так, чтобы она могла работать с много символьными ключевыми словами, переводами строк и пробелами. Мы только что прошли все это в седьмой главе. Мы будем использовать метод распределенного сканера, который я показал вам в этой главе. Фактическая реализация немного отличается, потому что различается способ, которым я обрабатываю переводы строк.
Для начала, давайте просто разрешим пробелы. Для этого необходимо только добавить вызовы SkipWhite в конец трех подпрограмм GetName, GetNum и Match. Вызов SkipWhite в Init запускает помпу в случае если есть ведущие пробелы.
Затем мы должны обрабатывать переводы строк. Это в действительности двух шаговый процесс так как обработка переносов с одно-символьными токенами отличается от таковой для много символьных токенов. Мы можем устранить часть работы сделав оба шага одновременно, но я чувствую себя спокойней, работая последовательно.
Вставьте новую процедуру:
Code: |
{ Skip Over an End-of-Line } procedure NewLine; begin while Look = CR do begin GetChar; if Look = LF then GetChar; SkipWhite; end; end; |
Заметьте, что мы видели эту процедуру раньше в виде процедуры Fin. Я изменил имя, так как новое кажется более соответствующим фактическому назначению. Я также изменил код чтобы учесть множественные переносы и строки только с пробелами.
Следующим шагом будет вставка вызовов NewLine везде, где мы посчитаем перенос допустимым. Как я подчеркивал ранее, этот момент может очень различаться для разных языков. В TINY я решил разрешить их практически в любом месте. Это означает, что нам нужно вызывать NewLine в начале (не в конце как с SkipWhite) процедур GetName, GetNum и Match.
Для процедур, которые имеют циклы While, таких как TopDecl, нам нужен вызов NewLine в начале процедуры и в конце каждого цикла. Таким способом мы можем быть уверены, что NewLine вызывается в начале каждого прохода через цикл.
Если вы все это сделали, испытайте программу и проверьте, что она действительно обрабатывает пробелы и переносы.
Если это так, тогда мы готовы работать с много символьными токенами и ключевыми словами. Для начала, добавьте дополнительные объявления (скопированные почти дословно из главы 7):
Code: |
{ Type Declarations } type Symbol = string[8]; SymTab = array[1..1000] of Symbol; TabPtr = ^SymTab;
{ Variable Declarations } var Look : char; { Lookahead Character } Token: char; { Encoded Token } Value: string[16]; { Unencoded Token } ST: Array['A'..'Z'] of char;
{ Definition of Keywords and Token Types } const NKW = 9; NKW1 = 10; const KWlist: array[1..NKW] of Symbol = ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE', 'VAR', 'BEGIN', 'END', 'PROGRAM'); const KWcode: string[NKW1] = 'xilewevbep'; |
Затем добавьте три процедуры, также из седьмой главы:
Code: |
{ Table Lookup } function Lookup(T: TabPtr; s: string; n: integer): integer; var i: integer; found: Boolean; begin found := false; i := n; while (i > 0) and not found do if s = T^[i] then found := true else dec(i); Lookup := i; end;
. .
{ Get an Identifier and Scan it for Keywords } procedure Scan; begin GetName; Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1]; end;
. .
{ Match a Specific Input String } procedure MatchString(x: string); begin if Value <> x then Expected('''' + x + ''''); end; |
Теперь мы должны сделать довольно много тонких изменений в оставшихся процедурах. Сначала мы должны изменить функцию GetName на процедуру, снова как в главе 7:
Code: |
{ Get an Identifier } procedure GetName; begin NewLine; if not IsAlpha(Look) then Expected('Name'); Value := ''; while IsAlNum(Look) do begin Value := Value + UpCase(Look); GetChar; end; SkipWhite; end; |
Обратите внимание, что эта процедура оставляет свой результат в глобальной строковой переменной Value.
Затем, мы должны изменить каждую обращение к GetName чтобы отразить ее новую форму. Они происходят в Factor, Assignment и Decl:
Code: |
{---------------------------------------------------------------} { Parse and Translate a Math Factor } procedure BoolExpression; Forward; procedure Factor; begin if Look = '(' then begin Match('('); BoolExpression; Match(')'); end else if IsAlpha(Look) then begin GetName; LoadVar(Value[1]); end else LoadConst(GetNum); end;
. .
{ Parse and Translate an Assignment Statement } procedure Assignment; var Name: char; begin Name := Value[1]; Match('='); BoolExpression; Store(Name); end; {---------------------------------------------------------------} . .
{ Parse and Translate a Data Declaration } procedure Decl; begin GetName; Alloc(Value[1]); while Look = ',' do begin Match(','); GetName; Alloc(Value[1]); end; end; |
(Заметьте, что мы все еще разрешаем только одно-символьные имена переменных поэтому мы используем здесь простое решение и просто используем первый символ строки.)
Наконец, мы должны внести изменения, позволяющие использовать Token вместо Look как символа для проверки и вызывать Scan в подходящих местах. По большей части это включает удаление вызовов Match, редкие замены вызовов Match на вызовы MatchString, и замену вызовов NewLine на вызовы Scan. Вот затронутые подпрограммы:
Code: |
{---------------------------------------------------------------} { Recognize and Translate an IF Construct } procedure Block; Forward; procedure DoIf; var L1, L2: string; begin BoolExpression; L1 := NewLabel; L2 := L1; BranchFalse(L1); Block; if Token = 'l' then begin L2 := NewLabel; Branch(L2); PostLabel(L1); Block; end; PostLabel(L2); MatchString('ENDIF'); end;
{ Parse and Translate a WHILE Statement } procedure DoWhile; var L1, L2: string; begin L1 := NewLabel; L2 := NewLabel; PostLabel(L1); BoolExpression; BranchFalse(L2); Block; MatchString('ENDWHILE'); Branch(L1); PostLabel(L2); end;
{ Parse and Translate a Block of Statements } procedure Block; begin Scan; while not(Token in ['e', 'l']) do begin case Token of 'i': DoIf; 'w': DoWhile; else Assignment; end; Scan; end; end;
{ Parse and Translate Global Declarations } procedure TopDecls; begin Scan; while Token <> 'b' do begin case Token of 'v': Decl; else Abort('Unrecognized Keyword ' + Value); end; Scan; end; end;
{ Parse and Translate a Main Program } procedure Main; begin MatchString('BEGIN'); Prolog; Block; MatchString('END'); Epilog; end;
{ Parse and Translate a Program } procedure Prog; begin MatchString('PROGRAM'); Header; TopDecls; Main; Match('.'); end;
{ Initialize } procedure Init; var i: char; begin for i := 'A' to 'Z' do ST[i] := ' '; GetChar; Scan; end; |
Это должно работать. Если все изменения сделаны правильно, вы должны теперь анализировать программы, которые выглядят как программы. (Если вы не сделали всех изменений, не отчаивайтесь. Полный листинг конечной формы дан ниже.)
Работает? Если да, то мы почти дома. Фактически, с несколькими небольшими исключениями, мы уже получили компилятор, пригодный для использования. Имеются еще несколько областей, требующих усовершенствования.
Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.
ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!