Изменить стиль страницы
  • Сейчас TINY примет только одну «программу» – пустую:

    PROGRAM . (или 'p.' в нашей стенографии).

    Заметьте, тем не менее, что компилятор генерирует для этой программы корректный код. Она будет выполняться и делать то, что можно ожидать от пустой программы, т.е. ничего кроме элегантного возвращения в ОС.

    Один из моих любимых бенчмарков для компиляторов заключается в компиляции, связывании и выполнении пустой программы для любого языка. Вы можете многое узнать о реализации измеряя предел времени, необходимый для компиляции тривиальной программы. Также интересно измерить количество полученного кода. Во многих компиляторах код может быть довольно большим, потому что они всегда включают целую run-time библиотеку независимо от того, нуждаются они в ней или нет. Ранние версии Turbo Pascal в этом случае производили объектный файл 12К. VAX C генерирует 50К!

    Самые маленькие пустые программы какие я видел, получены компиляторами Модула-2 и они занимают примерно 200-800 байт.

    В случае TINY у нас еще нет run-time библиотеки, так что объектный код действительно крошечный (tiny): два байта. Это стало рекордом, и вероятно останется таковым, так как это минимальный размер, требуемый ОС.

    Следующим шагом будет обработка кода для основной программы. Я буду использовать блок BEGIN из Pascal:

    ::= BEGIN END

    Здесь мы снова приняли решение. Мы могли бы потребовать использовать объявление вида «PROCEDURE MAIN», подобно C. Я должен допустить, что это совсем неплохая идея... Мне не особенно нравится подход Паскаля так как я предпочитаю не иметь проблем с определением местоположения основной программы в листинге Паскаля. Но альтернатива тоже немного неудобна, так как вы должны работать с проверкой ошибок когда пользователь опустит основную программу или сделает орфографическую ошибку в ее названии. Здесь я использую простой выход.

    Другое решение проблемы «где расположена основная программа» может заключаться в требовании имени для программы и заключения основной программы в скобки:

    BEGIN

    END

    аналогично соглашению Модула-2. Это добавляет в язык немного «синтаксического сахара». Подобные вещи легко добавлять и изменять по вашим симпатиям если вы сами проектируете язык.

    Для синтаксического анализа такого определения основного блока измените процедуру Prog следующим образом:

    {–}

    { Parse and Translate a Program }

    procedure Prog;

    begin

    Match('p');

    Header;

    Main;

    Match('.');

    end;

    {–}

    и добавьте новую процедуру:

    {–}

    { Parse and Translate a Main Program }

    procedure Main;

    begin

    Match('b');

    Prolog;

    Match('e');

    Epilog;

    end;

    {–}

    Теперь единственной допустимой программой является программа:

    PROGRAM BEGIN END. (или 'pbe.')

    Разве мы не делаем успехи??? Хорошо, как обычно это становится лучше. Вы могли бы попробовать сделать здесь некоторые преднамеренные ошибки подобные пропуску 'b' или 'e' и посмотреть что случится. Как всегда компилятор должен отметить все недопустимые входные символы.

    Объявления

    Очевидно на следующем шаге необходимо решить, что мы подразумеваем под объявлением. Я намереваюсь иметь два вида объявлений: переменных и процедур/функций. На верхнем уровне разрешены только глобальные объявления, точно как в C.

    Сейчас здесь могут быть только объявления переменных, идентифицируемые по ключевому слову VAR (сокращенно "v").

    ::= ( )*

    ::= VAR

    Обратите внимание, что так как имеется только один тип переменных, нет необходимости объявлять этот тип. Позднее, для полной версии KISS, мы сможем легко добавить описание типа.

    Процедура Prog становится:

    {–}

    { Parse and Translate a Program }

    procedure Prog;

    begin

    Match('p');

    Header;

    TopDecls;

    Main;

    Match('.');

    end;

    {–}

    Теперь добавьте две новые процедуры:

    {–}

    { Process a Data Declaration }

    procedure Decl;

    begin

    Match('v');

    GetChar;

    end;

    {–}

    { Parse and Translate Global Declarations }

    procedure TopDecls;

    begin

    while Look <> 'b' do

    case Look of

    'v': Decl;

    else Abort('Unrecognized Keyword ''' + Look + '''');

    end;

    end;

    {–}

    Заметьте, что на данный момент Decl – просто заглушка. Она не генерирует никакого кода и не обрабатывает список... каждая переменная должна быть в отдельном утверждении VAR.

    ОК, теперь у нас может быть любое число объявлений данных, каждое начинается с "v" вместо VAR, перед блоком BEGIN. Попробуйте несколько вариантов и посмотрите, что происходит.

    Объявления и идентификаторы

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

    С небольшим дополнительным кодом это легко сделать в процедуре Decl. Измените ее следующим образом:

    {–}

    { Parse and Translate a Data Declaration }

    procedure Decl;

    var Name: char;

    begin

    Match('v');

    Alloc(GetName);

    end;

    {–}

    Процедура Alloc просто выдает команду ассемблеру для распределения памяти:

    {–}

    { Allocate Storage for a Variable }

    procedure Alloc(N: char);

    begin

    WriteLn(N, ':', TAB, 'DC 0');

    end;

    {–}

    Погоняйте программу. Попробуйте входную последовательность, которая объявляет какие-нибудь переменные, например:

    pvxvyvzbe.

    Видите, как распределяется память? Просто, да? Заметьте также, что точка входа «MAIN» появляется в правильном месте.

    Кстати, «настоящий» компилятор имел бы также таблицу идентификаторов для записи используемых переменных. Обычно, таблица идентификаторов необходима для записи типа каждой переменной. Но так как в нашем случае все переменные имеют один и тот же тип, нам не нужна таблица идентификаторов. Оказывается, мы смогли бы находить идентификатор даже без различия типов, но давайте отложим это пока не возникнет такая необходимость.

    Конечно, в действительности мы не анализировали правильный синтаксис для объявления данных, так как он включает список переменных. Наша версия разрешает только одну переменную. Это также легко исправить.

    БНФ для следующая:

    ::= (, )*

    Добавление этого синтаксиса в Decl дает новую версию:

    {–}

    { Parse and Translate a Data Declaration }

    procedure Decl;

    var Name: char;

    begin

    Match('v');

    Alloc(GetName);

    while Look = ',' do begin

    GetChar;

    Alloc(GetName);

    end;

    end;

    {–}

    ОК, теперь откомпилируйте этот код и испытайте его. Попробуйте ряд строк с объявлениями VAR, попробуйте список из нескольких переменных в одной строке и комбинации этих двух. Работает?