Анимация
JavaScript
|
Главная Библионтека Вы должны запомнить: лексический анализатор будет вызываться часто! Фактически один раз для каждой лексемы во всей исходной программе. Эксперименты показали, что средний компилятор тратит где-то от 20 до 40 процентов своего времени на подпрограммах лексического анализа. Если существовало когда-либо место, где эффективность заслуживает пристального рассмотрения, то это оно. По этой причине большинство создателей компиляторов заставляют лексический анализатор выполнять немного больше работы, "токенизируя" входной поток. Идея состоит в том, чтобы сравнивать каждую лексему со списком допустимых ключевых слов и операторов и возвращать уникальный код для каждой распознанной. В случае обычного имени переменной или числа мы просто возвращаем код, который говорит, к какому типу лексем они относятся и сохраняем где-нибудь текущую строку. Первое, что нам нужно - это способ идентификации ключевых слов. Мы всегда можем сделать это с помощью последовательных проверок IF, но несомненно было бы хорошо, если бы мы имели универсальную подпрограмму, которая могла бы сравнивать данную строку с таблицей ключевых слов. (Между прочим, позднее нам понадобится такая же подпрограмма для работы с таблицей идентификаторов). Это обычно выявляет проблему Паскаля, потому что стандартный Паскаль не имеет массивов переменной длины. Это настоящая головная боль - обьявлять различные подпрограммы поиска для каждой таблицы. Стандартный Паскаль также не позволяет инициализировать массивы, поэтому вам придется видеть код типа: Table[1] := IF; Table[2] := ELSE; Table[n] := END; что может получиться довольно длинным если есть много ключевых слов. К счастью Turbo Pascal 4.0 имеет расширения, которые устраняют обе эти проблемы. Массивы-константы могут быть обьявлены с использованием средства TP "типизированные константы" а переменные размерности могут быть поддержаны с помощью Си-подобных расширений для указателей. Сначала, измените ваши объявления подобным образом: { Type Declarations } type Symbol = string[8]; SymTab = array[1..1000] of Symbol; TabPtr = SymTab; (Размерность, использованная в SymTab не настоящая... память не распределяется непосредственно этим объявлением, а размерность должна быть только "достаточно большой") Затем, сразу после этих объявлений, добавьте следующее: { Definition of Keywords and Token Types } const KWlist: array [1..4] of Symbol = (IF, ELSE, ENDIF, END); Затем, вставьте следующую новую функцию: { Table Lookup } { If the input string matches a table entry, return the entry index. If not, return a zero. } 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; Чтобы проверить ее вы можете временно изменить основную программу следующим образом: { Main Program } begin ReadLn(Token); WriteLn(Lookup(Addr(KWList), Token, 4)); end. Обратите внимание как вызывается Lookup: функция Addr устанавливает указатель на KWList, который передается в Lookup. ОК, испытайте ее. Так как здесь мы пропускаем Scan, для получения соответствия вы должны набирать ключевые слова в верхнем регистре. Теперь, когда мы можем распознавать ключевые слова, далее необходимо договориться о возвращаемых для них кодах. Итак, какие кода мы должны возвращать? В действительности есть только два приемлемых варианта. Это похоже на идеальное применения перечислимого типа Паскаля. К примеру, вы можете определить что-то типа SymType = (IfSym, ElseSym, EndifSym, EndSym, Ident, Number, Operator); и договориться возвращать переменную этого типа. Давайте попробуем это. Вставьте строку выше в описание типов. Теперь добавьте два описания переменных: Token: Symtype; { Current Token } Value: String[16]; { String Token of Look } Измените сканер так: { Lexical Scanner } procedure Scan; var k: integer; begin while Look = CR do Fin; if IsAlpha(Look) then begin Value := GetName; k := Lookup(Addr(KWlist), Value, 4); if k = 0 then Token := Ident else Token := SymType(k - 1); else if IsDigit(Look) then begin Value := GetNum; Token := Number; end else if IsOp(Look) then begin Value := GetOp; Token := Operator; end else begin Value := Look; Token := Operator; GetChar; end; SkipWhite; end; (Заметьте, что Scan сейчас стала процедурой а не функцией). Наконец, измените основную программу: { Main Program } begin Init; repeat Scan; case Token of Ident: write(Ident ); Number: Write(Number ); Operator: Write(Operator ); IfSym, ElseSym, EndifSym, EndSym: Write(Keyword ); end; Writeln(Value); until Token = EndSym; end. Мы заменили строку Token, используемую раньше, на перечислимый тип. Scan возвращает тип в переменной Token и возвращает саму строку в новой переменной Value. ОК, откомпилируйте программу и погоняйте ее. Если все работает, вы должны увидеть, что теперь мы распознаем ключевые слова. Теперь у нас все работает правильно, и было легко сгенерировать это из того, что мы имели раньше. Однако, она все равно кажется мне немного "перегруженной". Мы можем ее немного упростить, позволив GetName, GetNum, GetOp и Scan работать с глобальными переменными Token и Value, вследствие этого удаляя их локальные копии. Кажется немного умней было бы переместить просмотр таблицы в GetName. Тогда новая форма для этих четырех процедур будет такой: { Get an Identifier } procedure GetName; var k: integer; begin Value := ; if not IsAlpha(Look) then Expected(Name); while IsAlNum(Look) do begin Value := Value + UpCase(Look); GetChar; end; k := Lookup(Addr(KWlist), Value, 4); if k = 0 then Token := Ident 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 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |