Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Pascal_Unkn.doc
Скачиваний:
8
Добавлен:
03.11.2018
Размер:
1.63 Mб
Скачать

Лексический анализ

Конечно, вы знаете, что будет дальше: Мы должны преобразовать программу так, чтобы она могла работать с много символьными ключевыми словами, переводами строк и пробелами. Мы только что прошли все это в седьмой главе. Мы будем использовать метод распределенного сканера, который я показал вам в этой главе.      Фактическая реализация немного отличается, потому что различается способ, которым я обрабатываю переводы строк.

Для начала, давайте просто разрешим пробелы. Для этого необходимо только добавить вызовы SkipWhite в конец трех подпрограмм GetName, GetNum и Match. Вызов SkipWhite в Init запускает помпу в случае если есть ведущие пробелы.

Затем мы должны обрабатывать переводы строк. Это в действительности двух шаговый процесс так как  обработка переносов с одно-символьными токенами отличается от таковой для много символьных токенов.  Мы можем устранить часть работы сделав оба шага одновременно, но я чувствую себя спокойней, работая последовательно.

Вставьте новую процедуру:

{--------------------------------------------------------------}

{ 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):

{--------------------------------------------------------------}

{ 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';

{--------------------------------------------------------------}

Затем добавьте три процедуры, также из седьмой главы:

{--------------------------------------------------------------}

{ 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:

{--------------------------------------------------------------}

{ 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:

{---------------------------------------------------------------}

{ 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.  Вот затронутые подпрограммы:

{---------------------------------------------------------------}

{ 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;

{--------------------------------------------------------------}

Это должно работать. Если все изменения сделаны правильно, вы должны теперь анализировать программы, которые выглядят как программы. (Если вы не сделали всех изменений, не отчаивайтесь.  Полный листинг конечной формы дан ниже.)

Работает? Если да, то мы почти дома. Фактически, с несколькими небольшими исключениями, мы уже получили компилятор, пригодный для использования. Имеются еще несколько областей, требующих усовершенствования.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]