Россия, Челябинск, Южно-Уральский Государственный Университет (НИУ) |
Семантический анализ. Внутреннее представление
Конструирование типов
Типы языков программирования конструируются из примитивных типов, таких как boolean , char , integer , real и void , с помощью конструкторов типов. К примитивным типам естественно отнести и тип, который не используется при программировании, но весьма полезен для сигнализации о возникшей ошибке в типах; это тип - invalid . Для построения более сложных типов из примитивных обычно используются следующие конструкторы:
- Массивы. Если T - тип, то array (I, T) - тип, обозначающий тип массива с элементами типа T и индексным множеством I . Например, описание языка Pascal:
var A: array [1..10] of integer;
связывает выражение над типами array (1..10, T) с A .
- Произведение. Если T1 и T2 - типы, то их декартово произведение T1 * T2 также является типом.
- Структуры. Конструктор struct применяется к кортежу пар ( имя поля, тип поля ). Например, фрагмент программы на языке Pascal:
type row = record address: integer; lexeme: array [1..15] of char end; var table: array [1..13] of row;
Тип row строится из примитивных типов следующим образом:
struct ((address*integer) (lexeme*array (1..15, char))).
- Указатели. Если T - тип, то и pointer (T) - тип, определяющий " указатель на объект типа T ". Например, описание языка Pascal:
var p: ^row;
определяет переменную p , имеющую тип pointer (row) .
- Функции. Если T1, T2 - типы, то proc (T1, T2) - тип, определяющий процедуру, типы формальных параметров которой есть T1, а тип результата - T2. Например, функция mod , вычисляющая остаток, имеет тип proc (int *int, int) ,
а функция, определенная какимеет тип proc (char char, pointer (integer)) .
function f (a, b: char) : ^integer;
Представление типов
Удобным путем представления выражений над типами являются графы. Мы можем конструировать деревья или DAG'и (ориентированные ациклические графы), листьями которых будут примитивные типы. Например:
Использование dag'ов более предпочтительно, поскольку в этом случае происходит иногда весьма значительная экономия памяти (вместо самих типов в этом случае хранится ссылка на них).
Мы можем использовать и линейное представление деревьев или dag'ов, например,
proc, 2, m1, m2, m2, где m1 - указатель в таблицу на тип pointer, integer, а m2 - указатель в таблицу на тип char.
Рассмотрим еще один способ кодирования типов, который был использован в компиляторе C, разработанном Ричи (D.M.Ritchie). Ограничимся тремя конструкторами типов: указателями, функциями и массивами: pointer (t) обозначает указатель на тип t , freturns (t) обозначает функцию от некоторых аргументов, которая возвращает значение типа t и, наконец, array (t) обозначает массив некоторой неопределенной длины элементов типа t . Приведем примеры типов:
char freturns (char) pointer (freturns (char)) array (pointer (freturns (char))).
Каждый из этих типов может быть представлен последовательностью битов. Поскольку у нас есть только три конструктора типа, мы можем использовать для кодирования два бита:
pointer 01 array 10 freturns 11
Примитивные типы кодируются четырьмя битами:
boolean 0000 char 0001 integer 0010 real 0011
Используя такой способ кодирования, приведенные выше типы мы можем закодировать следующим образом:
char 000000 0001 freturns (char) 000011 0001 pointer (freturns (char)) 000111 0001 array (pointer (freturns (char))) 100111 0001
Теперь вернемся к обсуждению структуры таблицы идентификаторов. Одно из полей ( toMode ) мы собирались использовать для определения типа идентификатора. Определив представление типов, которые могут появиться в программе, мы можем теперь зафиксировать, что поле toMode в - это либо указатель на дерево или dag , либо ссылка в таблицу типов ModeTab , хранящую линейное представление типов.