Система программирования PascalABC.NET
Структура компилятора PascalABC.NET. Структура дерева и примеры узлов. Упрощенный синтаксис записи модулей. Объявление имен, совпадающих с ключевыми словами. Генерация узла семантического дерева. Сериализация и десериализация узлов семантического дерева.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | курсовая работа |
Язык | русский |
Дата добавления | 18.12.2011 |
Размер файла | 1,8 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
7.2.1 Операции is, as
В синтаксическом дереве операции is и as определяются одним узлом. В узле содержится флаг, который определяет тип операции. Операции были реализованы в виде одного узла, т.к. имеют одинаковые аргументы и схожий семантический смысл.
Функция визитора семантического анализатора для этого узла выглядит следующим образом:
public void visit(SyntaxTree.typecast_node node)
{
expression_node en = convert_strong(node.left);
type_node tp = convert_strong(node.right);
if (type_table.is_derived(en.type, tp) ||
type_table.is_derived(tp, en.type) || en.type==tp)
{
if (node.cast_op ==
PascalABCCompiler.SyntaxTree.op_typecast.is_op)
{
is_node isn = new is_node(en, tp,
get_location(node));
return_value(isn);
}
else
{
if (tp.is_value_type)
throw new OperatorAsMustBeUsedWithAReferenceType(tp.name,
get_location(node.right));
as_node asn = new as_node(en, tp,
get_location(node));
return_value(asn);
}
return;
}
throw new ExpectedDerivedClasses(get_location(node));
}
Дадим словесное описание алгоритма:
Конвертируется выражение, стоящее слева от операции
Конвертируется тип, стоящий справа от операции
Если тип выражения и тип справа связаны в иерархию или одного типа то
{
Если это оператор is то
Конструируем семантический узел is
иначе
{
Если тип справа это не ссылочный тип то
ошибка «Оператор as применим только к
ссылочным типам»
Конструируем семантический узел as
}
}
иначе ошибка «У обьекта и типа нет общего предка»
7.2.2 Операции typeof, sizeof
Операция typeof служит для получения обьекта типа System.Type из типа.
public void visit(SyntaxTree.typeof_operator node)
{
return_value(new typeof_operator(
find_type(node.type_name,
get_location(node.type_name)),
get_location(node))
);
}
При конструировании семантического узла все необходимые проверки делает функция find_type.
Операция sizeof служит для определения размера типа в байтах.
public void visit(SyntaxTree.sizeof_operator node)
{
type_node tn = find_type(node.type_name,
get_location(node.type_name));
if (!tn.is_value_type)
throw new OperatorSizeOfMustBeUsedWithAValueType(
tn.name, get_location(name));
return_value(new sizeof_operator(tn, get_location(node)));
}
Данная операция не применима к ссылочным типам. В случае, если параметр - ссылочный, то происходит ошибка «Оператор sizeof не применим к ссылочным типам».
7.2.3 Операция new
Операция new является синонимом вызова конструктора Create и позволяет создать новый объект класса. При конструировании операции new происходит поиск подходящего конструктора и создание узла вызова этого конструктора.
public void visit(SyntaxTree.new_expr _new_expr)
{
type_node tn = ret.visit(_new_expr.name_ref);
expressions_list exprs = null;
if (_new_expr.params_list != null)
exprs = convert_expression_list(
_new_expr.params_list.expressions);
else
exprs = new expressions_list();
return_value(create_constructor_call(tn, exprs,
get_location(_new_expr.name_ref)));
}
private base_function_call create_constructor_call(type_node tn, expressions_list exprs, location loc)
{
SymbolInfo si = tn.find_in_type(
TreeConverter.compiler_string_consts.default_constructor_name,
context.CurrentScope);
if (si == null)
throw new ConstructorNotFound(loc);
function_node fn = convertion_data_and_alghoritms.select_function(exprs, si, loc);
return create_static_method_call_with_params(fn, loc, tn, false, exprs);
}
Вначале ищется тип, который необходимо сконструировать. Далее с помощью функции find_in_type ищутся все его конструкторы. Если ни одного конструктора не найдено, то происходит ошибка. Функция convertion_data_and_alghoritms.select_function выбирает конструктор с подходящими параметрами, если подходящая функция не будет найдена, то происходит ошибка. Далее с помощью функции create_static_method_call_with_params создается вызов этого конструктора.
7.2.4 Типизированные и бинарные файлы
Типизированный файл - файл с произвольным доступом, хранящий элементы одного типа. Для типизированного файла применимы следующие операции: чтение, запись, переход к записи с определенным номером. Также можно узнать, сколько записей находится в файле. В качестве элементов файла могут выступать записи, примитивные типы, строки фиксированной длины и массивы. В составляющей элемента файла не допустимы ссылочные типы (например, файл не может состоять из записей, содержащих поле типа string или array of integer).
На языке PascalABC.NET типизированные файлы описываются так же как и на языке Object Pascal:
var f: file of file_type;
Бинарный файл - файл с последовательным доступом, хранящий элементы разного типа. Для бинарнтого файла применимы только операции чтения и записи. В качестве элементов файла могут выступать записи, примитивные типы, строки и массивы. В составляющей элемента файла не допустимы ссылочные типы.
На языке PasclaABC.NET типизированные файлы описываются так же как и на языке Object Pascal:
var f: file;
7.2.4.1 Часть кода, реализованная на PascalABC.NET
Большинство кода для реализации типизированных и бинарных файлов написано на самом языке PascalABC.NET и находится в системной библиотеке PABCSystem.pas:
Типизированный файл:
TypedFile = class
fi: FileInfo;
fs: FileStream;
br: BinaryReader;
bw: BinaryWriter;
ElementType: System.Type;
ElementSize: longint;
constructor(ElementType: System.Type);
begin
self.ElementType := ElementType;
ElementSize := RuntimeSizeOf(ElementType);
end;
public
function ToString: string; override;
begin
Result := string.Format('file of {0}', ElementType);
end;
end;
Бинарный файл:
BinaryFile = record
fi: FileInfo;
fs: FileStream;
br: BinaryReader;
bw: BinaryWriter;
end;
Функция, позволяющая получить из обычного массива массив System.Array:
function GetNullBasedArray(arr: object): System.Array;
var fi: FieldInfo;
begin
fi := arr.GetType.GetField(InternalNullBasedArrayName);
if fi = nil then
Result := nil
else Result := System.Array(fi.GetValue(arr));
end;
Функция, которая позволяет вычислить размер типа:
function RunTimeSizeOf(t: System.Type): integer;
var
t1: System.Type;
elem: object;
fa: array of FieldInfo;
NullBasedArray: System.Array;
i: integer;
fi: FieldInfo;
begin
if t.IsPrimitive then
begin
if t = typeof(integer) then
Result := sizeof(integer)
else if t = typeof(real) then
Result := sizeof(real)
else if t = typeof(boolean) then
Result := sizeof(boolean)
else if t = typeof(char) then
Result := sizeof(char)
else if t = typeof(byte) then
Result := sizeof(byte)
else if t = typeof(shortint) then
Result := sizeof(shortint)
else if t = typeof(smallint) then
Result := sizeof(smallint)
else if t = typeof(word) then
Result := sizeof(word)
else if t = typeof(longword) then
Result := sizeof(longword)
else if t = typeof(longint) then
Result := sizeof(longint)
else if t = typeof(uint64) then
Result := sizeof(uint64)
else if t = typeof(single) then
Result := sizeof(single)
end
else if t.IsValueType then
begin
elem := Activator.CreateInstance(t);
fa := t.GetFields;
Result := 0;
for i:=0 to fa.Length-1 do
Result := Result + RunTimeSizeOf(fa[i].FieldType)
end
else
begin
fi := t.GetField(InternalNullBasedArrayName);
if fi = nil then
raise new Exception('Bad Type in RunTimeSizeOf');
elem := Activator.CreateInstance(t);
NullBasedArray := GetNullBasedArray(elem);
t1 := NullBasedArray.GetType.GetElementType;
Result := RunTimeSizeOf(t1)*NullBasedArray.Length;
end;
end;
Приведем алгоритм работы последней функции:
Если это примитивный тип то
результат := sizeof(тип)
иначе
если это не ссылочный тип то
результат := просматриваем все поля вычисляя их размер
иначе
//это обычный массив
получаем массив индексируемый с нуля
результат := разимер элемента массива * количество элементов
Процедура чтения одного элемента из типизированного файла имеет вид:
function TypedFileReadT(f: TypedFile; t: System.Type): object;
var
t1: System.Type;
elem: object;
fa: array of FieldInfo;
NullBasedArray: System.Array;
i: integer;
begin
if t.IsPrimitive then
begin
if t = typeof(integer) then
Result := f.br.ReadInt32
else if t = typeof(real) then
Result := f.br.ReadDouble
else if t = typeof(boolean) then
Result := f.br.ReadBoolean
else if t = typeof(char) then
Result := f.br.ReadChar
else if t = typeof(byte) then
Result := f.br.ReadByte
else if t = typeof(shortint) then
Result := f.br.ReadSByte
else if t = typeof(smallint) then
Result := f.br.ReadInt16
else if t = typeof(word) then
Result := f.br.ReadUInt16
else if t = typeof(longword) then
Result := f.br.ReadUInt32
else if t = typeof(longint) then
Result := f.br.ReadInt64
else if t = typeof(uint64) then
Result := f.br.ReadUInt64
else if t = typeof(single) then
Result := f.br.ReadSingle
end
else if t.IsValueType then
begin
elem := Activator.CreateInstance(t);
fa := t.GetFields;
for i:=0 to fa.Length-1 do
fa[i].SetValue(elem,TypedFileReadT(f,fa[i].FieldType));
Result := elem;
end
else
begin
elem := Activator.CreateInstance(t);
NullBasedArray := GetNullBasedArray(elem);
if NullBasedArray<>nil then
begin
t1 := NullBasedArray.GetType.GetElementType;
for i:=0 to NullBasedArray.Length-1 do
NullBasedArray.SetValue(TypedFileReadT(f,t1),i);
end;
result := elem;
end;
end;
Приведем алгоритм работы процедуры чтения:
Если это примитивный тип то
считать из файла элемент соответствующего типа
иначе
если это не ссылочный тип то
создать элемент этого типа
просматриваем все поля и считываем элементы
соответствующего типа
иначе
//это обычный массив
создать такой массив
получаем массив индексируемый с нуля
выясняем тип элементов массива и его длину
считываем необходимое количество элементов
соответствующего типа
Процедура записи одного элемента из типизированного файла имеет вид:
procedure Write(f: TypedFile; val: object);
var
t: System.Type;
fa: array of FieldInfo;
i: integer;
NullBasedArray: System.Array;
begin
t:=val.GetType;
if t.IsPrimitive then
begin
if t = typeof(integer) then
f.bw.Write(integer(val))
else if t = typeof(real) then
f.bw.Write(real(val))
else if t = typeof(char) then
f.bw.Write(char(val))
else if t = typeof(boolean) then
f.bw.Write(boolean(val))
else if t = typeof(byte) then
f.bw.Write(byte(val))
else if t = typeof(shortint) then
f.bw.Write(shortint(val))
else if t = typeof(smallint) then
f.bw.Write(smallint(val))
else if t = typeof(word) then
f.bw.Write(word(val))
else if t = typeof(longword) then
f.bw.Write(longword(val))
else if t = typeof(longint) then
f.bw.Write(longint(val))
else if t = typeof(uint64) then
f.bw.Write(uint64(val))
else if t = typeof(single) then
f.bw.Write(single(val))
end
else if t.IsValueType then
begin
fa := t.GetFields;
for i:=0 to fa.Length-1 do
Write(f,fa[i].GetValue(val));
end
else
begin
NullBasedArray := GetNullBasedArray(val);
if NullBasedArray<>nil then
for i:=0 to NullBasedArray.Length-1 do
Write(f,NullBasedArray.GetValue(i));
end;
end;
Алгоритм данной процедуры аналогичен алгоритму чтения из типизированного файла.
Функция, возвращающая размер файла:
function FileSize(f: TypedFile): longint;
begin
if f.fs.Length mod f.ElementSize <> 0 then
raise new Exception('Bad typed file size');
Result := f.fs.Length div f.ElementSize;
end;
Функция, возвращающая текущую позицию в файле:
function FilePos(f: TypedFile): longint;
begin
Result := f.fs.Position div f.ElementSize;
end;
Процедура, осуществляющая переход к записи с номером n:
procedure Seek(f: TypedFile; n: integer);
begin
f.fs.Position := n*f.ElementSize;
end;
Процедура, осуществляющая обрезание файла с текущей позиции:
function Eof(f: TypedFile): boolean;
begin
if f.fs <> nil then
Result := f.fs.Position = f.fs.Length
else raise new Exception('File not opened');
end;
Как видно из кода, эти файлы реализованы на базе стандартных потоков System.IO.BinaryReader и System.IO.BinaryWriter. Обращение к компонентам элемента файла реализовано с помощью рефлексии. Процедура write полностью реализована на самом языке PascalABC.NET за исключением проверок параметров во время этапа компиляции.
7.2.4.2 Генерация узла семантического дерева
Процесс семантического анализа типизированых файлов происходит следующим образом: во время компиляции семантический анализатор находит уже переведенный в семантическое дерево тип TypedFile. В начале блока, где обявляется переменная этого типа, вставляются команды создания этого типа с параметром - типом элементов.
public void visit(SyntaxTree.file_type _file_type)
{
//Типизированый файл
type_node el_type =
convert_strong(_file_type.file_of_type);
if (!CanUseThisTypeForTypedFiles(el_type))
throw new InvalidElementTypeForTypedFile(el_type, get_location(_file_type.file_of_type));
return_value(context.create_typed_file_type(el_type, get_location(_file_type)));
}
Перевод бинарного файла происходит аналогично, за исключением того, что ему не нужно знать тип своих элементов.
7.2.4.3 Проверки этапа компиляции
На этапе компиляции необходимо делается проверка на допустимость элементов для типизированных файлов. Данная проверка раеализована с помощю рекурсивной функции CanUseThisTypeForTypedFiles:
private bool CanUseThisTypeForTypedFiles(type_node el_type)
{
if (el_type.is_value)
{
//проверка на пимитивный тип
if (SystemLibrary.CanUseThisTypeForTypedFiles(el_type))
return true;
//это запись
if (el_type is common_type_node)
{
common_type_node ctn = el_type as common_type_node;
foreach (class_field cf in ctn.fields)
if (!CanUseThisTypeForTypedFiles(cf.type))
return false;
return true;
}
//Это откомпилированная запись
if (el_type is compiled_type_node)
{
compiled_type_node ctn = el_type as
compiled_type_node;
System.Reflection.FieldInfo[] fields =
ctn.compiled_type.GetFields();
foreach (System.Reflection.FieldInfo fi in fields)
if (!fi.IsStatic)
if (!CanUseThisTypeForTypedFiles(
compiled_type_node.get_type_node(fi.FieldType)))
return false;
return true;
}
}
if (IsBoudedArray(el_type))
{
//это обычный массив
bounded_array_interface bai = (bounded_array_interface)el_type.get_internal_interface(internal_interface_kind.bounded_array_interface);
return CanUseThisTypeForTypedFiles(bai.element_type);
}
return false;
}
С помощью рекурсии обходятся все поля типа и проверяются на допустимость для типизированного файла. Необходимо отметить, что хотя в .NET массив представлятся ссылочным типом, в PascalABC.NET он считается размерным: при передаче как параметр либо присваивании массив копируется. Также массивы разрешены в качестве элементов типизированного файла.
Для процедур read и write также делаются следующие проверки:
· Если первый параметр - это типизированный файл, то сравнить все остальные параметры на совпадение их типа с типом элементов типизированного файла.
· Если тип хотя бы одного параметра не совпадает с типом элементов типизированного файла, то происходит ошибка времени компиляции.
7.2.4.4 Реализация процедуры read
Процедура read не может быть реализована на языке Pascal, т.к. имеет переменное число var-параметров. Решена эта проблема следующим образом: все вызовы процедуры Read, в которых первый параметр - это типизированный файл, заменяются вызовом специальной функции TypedFileRead, имеющей прототип:
function TypedFileRead(var f: TypedFile;
ElementType: System.Type): object;
и описанной в модуле PABCSystem.pas, при этом вставляется также узел явного приведения типов.
Пример:
var
x1, x2: integer;
f: file of integer;
begin
Read(f, x1, x2);
//заменяется на
x1 := integer(TypedFileRead(f, typeof(integer)));
x2 := integer(TypedFileRead(f, typeof(integer)));
//это эквивалентный код на PascalABC.NET дерева на которое
//происходит замена вызова процедуры Read
end.
Для бинарных файлов концепция аналогична, за исключением того, что специальная функция с именем BinaryFileRead имеет прототип
function BinaryFileRead(var f: BinaryFile;
ElementType: System.Type): object;
7.2.5 Изменяемые строки string
В платформе .NET строки неизменяемые. Для обеспечения совместимости с Object Pascal было необходимо сделать строки изменяемыми. Было рассмотрено несколько вариантов решения этой проблемы:
· сделать строки на базе StringBuilder;
· сделать строки на базе char[].
Но такие варианты вели к несовместимости строк в компиляторе со стороками .NET. Поэтому было принято следующее решение:
var s: string;
begin
s[1]:='x';
//заменяется на
StringDefaultPropertySet(s, 1, 'x');
//это эквивалентный код на PascalABC.NET дерева на которое
//происходит замена вызова s[1]:='x'
end.
Процедура StringDefaultPropertySet находится в модуле PABCSystem.pas:
procedure StringDefaultPropertySet(var s: string;
index: integer; c: char);
begin
s := string.Concat(s.Substring(0, index),
c,
s.Substring(index + 1));
end;
Таким образом, образуется новая строка из трех подстрок. Такое решение обладает несомненным плюсом: совместимостью с Object Pascal и строками .NET, но и очевидным минусом: такое присваивание индекса имеет очень маленькую скорость.
7.3 Генерация сообщений об ошибках
Каждая семантическая ошибка является классом. Семантические ошибки заключены в иерархию:
Exception -> Error -> SemanticError -> CompilationError -> CompilationErrorWithLocation
При генерации исключения в параметры конструктора передаются все необходимые сведения для каждой конткретной ошибки. В текущей версии компилятора - 120 классов семантических ошибок.
Приведем пример реализации класса семантической ошибки для неправильного типа элементов типизированного файла:
public class InvalidElementTypeForTypedFile : CompilationErrorWithLocation
{
private type_node el_type;
public InvalidElementTypeForTypedFile(type_node el_type, location loc)
: base(loc)
{
this.el_type = el_type;
}
public override string ToString()
{
return string.Format(StringResources.Get(
"INVALID_ELEMENT_TYPE_FOR_TYPED_FILE"));
}
}
Для получения текстового варианта сообщения о ошибке необходимо воспользоваться методом ToString.
Для обеспечения локализации ошибки на другие языки используется система локализации и класс StringResources, о котором будет расказано в главе 11.
Классы ошибок, порожденные от CompilationErrorWithLocation, имеют свойство Location, удовлетворяющее интерфейсу ILocation. Данное свойство содержит информацию о месте начала и конца ошибки в тексте программы (строка, столбец), а также имя файла программы.
8. Генератор кода
В качестве целевой платформы была выбрана платформа Microsoft .NET, т.к. генерируемый под эту платформу код (MSIL)[10] имеет ряд существенных достоинств:
• кроссплатформенность;
• объектная ориентированность;
• высокая скорость выполнения;
• простота генерации кода в сравнении с генерацией машинного кода.
Генератор кода является визитором по семантическому дереву.
IL (MSIL) - это единый байт-код для платформы .NET. IL ориентирован на работу со стеком, т. е. все его команды помещают операнды в стек исполнения и извлекают операнды из стека. Поскольку IL не поддерживает команды работы с регистрами, отпадает необходимость в распределении регистров. Кроме того, команд IL гораздо меньше, чем машинных (около 200).
8.1 Перевод семантического дерева в IL код
Для перевода используется стандартная библиотека Reflection.Emit.
Перевод семантического дерева в IL-код происходит в несколько этапов:
1. Создается динамическая сборка.
2. Создается главный статический класc cо статическим методом Main.
3. Если необходима отладочная информация, то для каждого пространства имен создается отладочный документ.
4. Переводятся все заголовки типов.
5. Переводятся все заголовки методов.
6. Пререводятся заголовки подпрограмм, глобальные переменные и константы.
7. Переводятся тела подпрограмм и методов.
8. Переводится тело основной программы.
9. Закрываются типы (требование .NET).
8.2 Перевод конструкций в IL код
Переврод большенсва конструкций был осуществлен Бондаревым И.
Перевод конструкций в основном осуществяется в соответсвующих методах visit визитора по семантическому дереву. При переводе используется набор алгоритмов, находящихся в NETGenerator.NETGeneratorTools. Алгоритмы из этого класса являютсся стаическими методами - обертками над вызовами метода System.Reflection.Emit.ILGenerator.Emit.
Прмер такого алгоритма - PushLdc, который кладет на стек константу:
public static void PushLdc(ILGenerator il, Type elem_type, object value)
{
switch (Type.GetTypeCode(elem_type))
{
case TypeCode.Boolean:
case TypeCode.Byte:
il.Emit(OpCodes.Ldc_I4_S, (byte)value);
break;
case TypeCode.SByte:
il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
break;
case TypeCode.Char:
il.Emit(OpCodes.Ldc_I4, (char)value);
break;
case TypeCode.Int16:
il.Emit(OpCodes.Ldc_I4, Convert.ToInt32(value));
break;
case TypeCode.UInt16:
il.Emit(OpCodes.Ldc_I4, (UInt16)value);
break;
case TypeCode.Int32:
il.Emit(OpCodes.Ldc_I4, (Int32)value);
break;
case TypeCode.UInt32:
il.Emit(OpCodes.Ldc_I4, (UInt32)value);
break;
case TypeCode.Int64:
il.Emit(OpCodes.Ldc_I8, (Int64)value);
break;
case TypeCode.UInt64:
if ((UInt64)value > Int64.MaxValue)
{
Int64 tmp =
(Int64)((UInt64)value - Int64.MaxValue - 1);
il.Emit(OpCodes.Ldc_I8, tmp);
il.Emit(OpCodes.Conv_U8);
il.Emit(OpCodes.Ldc_I8, Int64.MaxValue);
il.Emit(OpCodes.Conv_U8);
il.Emit(OpCodes.Add);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Add);
}
else
il.Emit(OpCodes.Ldc_I8, Convert.ToInt64(value));
break;
case TypeCode.Single:
il.Emit(OpCodes.Ldc_R4, (Single)value);
break;
case TypeCode.Double:
il.Emit(OpCodes.Ldc_R8, (Double)value);
break;
case TypeCode.String:
il.Emit(OpCodes.Ldstr, (string)value);
break;
default:
if (elem_type.IsEnum)
il.Emit(OpCodes.Ldc_I4, (Int32)value);
else
throw new Exception("Немогу положить PushLdc для " +
value.GetType().ToString());
break;
}
}
Исходя из типа константы, выбирается необходимая команда, и константа кладется на стек. Данный метод будет работать для всех примитивных типов, а также для перечислимых типов.
8.2.1 Операции is, as
Логическая операция is позволяет определить, принадлежит ли обьект к некоторому классу или его потомкам.
Метод visit для узла is:
public override void visit(SemanticTree.IIsNode value)
{
bool idexpr = is_dot_expr;
is_dot_expr = false;
value.left.visit(this);
is_dot_expr = idexpr;
Type right = helper.GetTypeReference(value.right).tp;
il.Emit(OpCodes.Isinst, right);
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Cgt_Un);
if (is_dot_expr)
NETGeneratorTools.CreateLocalAndLoad(il, typeof(bool));
}
Переменная is_dot_expr является членом класса ILGenerator и указывает на то, является ли вызов этой операции частью составного выражения. Перменная il является обьектом класса System.Reflection.Emit.ILGenerator. Вызов value.left.visit(this) запускает визитор для левого поддерева операции is. В результате этого на стек будет положен объект некоторого класса. Длаее получаем тип правого поддерева. После чего кладем команды
1. (OpCodes.Isinst, type) - проверка на то, является ли лежащий на стеке объект типом type либо его потомком. Если да, то на стек положится объект, приведенный к этому типу, иначе на стек кладется константа null.
2. OpCodes.Ldnull - кладет на стек константу null
3. OpCodes.Cgt_Un - производит сравнение двух значений на вершине стека. Если первое значение больше второго, то на стек кладется константа 1, иначе кладется константа 0.
Все эти команды снимают используемые ими аргументы со стека.
Если эта операция является частью составного выражения, то создаем локальную переменную и загружаем ее значение на стек.
Операция as необходима для приведения обекта к некоторому типу. Если приведение невозможно, то на стек положится контана null.
Метод visit для узла as имеет вид:
public override void visit(SemanticTree.IAsNode value)
{
bool idexpr = is_dot_expr;
is_dot_expr = false;
value.left.visit(this);
is_dot_expr = idexpr;
Type right = helper.GetTypeReference(value.right).tp;
il.Emit(OpCodes.Isinst, right);
}
Так как команда OpCodes.Isinst собственно и реализует функционал операции as, то данный код проще, чем для операции is: value.left.visit(this) запускает визитор для левого поддерева операции as. В результате этого на стек будет положен объект некоторого класса. Далее получаем тип правого поддерева. После чего кладем команду (OpCodes.Isinst, type).
8.2.2 Операции typeof, sizeof
Операция typeof позволяет из типа получить объект типа System.Type.
Метод visit для операции typeof:
public override void visit(ITypeOfOperator value)
{
il.Emit(OpCodes.Ldtoken,
helper.GetTypeReference(value.oftype).tp);
il.EmitCall(OpCodes.Call,
typeof(Type).GetMethod("GetTypeFromHandle"), null);
}
Первая команда кладет на стек токен типа, тип которого необходимо узнать. Вторая команда кладет на стек вызов метода System.Type.GetTypeFromHandle, который возвращает объект типа System.Type.
Операция sizeof позволяет узнать размер типа в байтах. В отличие до операции sizeof в С#, в PascalABC.NET эта операция позволяет определить размер не только примитивного типа но и любого размерного типа.
Метод visit для операции sizeof:
public override void visit(ISizeOfOperator value)
{
Type tp=helper.GetTypeReference(value.oftype).tp;
if (tp.IsPrimitive)
{
PushIntConst(TypeFactory.GetPrimitiveTypeSize(tp));
return;
}
if (tp.IsValueType)
{
NETGeneratorTools.PushTypeOf(il, tp);
Type typ = typeof(
System.Runtime.InteropServices.Marshal);
Type[] prms = new Type[1];
prms[0] = typeof(Type);
il.EmitCall(OpCodes.Call,
typ.GetMethod("SizeOf", prms), null);
return;
}
}
После получения типа мы выясняем:
1. Если это примитивный тип, то кладем на стек константу - размер этого типа. Это делается с помощю метода TypeFactory.GetPrimitiveTypeSize, котрый содержит хеш-таблицу с размерами примитивных типов.
2. Если это размерный тип, то кладем на стек вызов метода System.Runtime.InteropServices.Marshal.SizeOf, который позволяет определить размер value-типа.
9. Промежуточная форма хранения программы (PCU)
PCU (Pascal Compiled Unit) - промежуточная форма хранения программы в компиляторе PascalABC.NET. PCU является сохраненным в бинарный формат семантическим деревом программы. Особенности PCU:
· семантическое дерево может быть воссановлено из PCU частями;
· каждый модуль хранится в отдельном PCU файле.
9.1 Выбор промежуточной формы хранения программы
В качестве промежуточной формы хранения программы можно было бы выбрать не семантическое дерево, а IL код. Это быстрее, но менее гибко: если использовать внутренне предсавление ввиде IL кода то невозможно проводить высокоуровневые преобразования программ т.к. для них необходимо дерево прграммы. Кроме того прийдется делать поправки в IL коде при сборке нескольих программых модулей. Следует отметить что Delphi.NET испльзует для хранения промежуточного представления именно IL код.
Были сделаны замеры скорости работы различных частей компилятора. Они приводятся на диаграмме ниже.
Из данной диаграммы видно, что генерация кода по семантическому дереву занимает лишь 8% от общего времени компиляции. Следовательно, использование в качестве промежуточного представления семантического дерева, а не IL кода, не приведет к значительному замедлению компиляции.
9.2 Варианты использования PCU
Один из главных вариантов испрользования PCU - ускорение компиляции. Тесты показали, что при использовании PCU повторная компиляция многомодульных программ ускоряется многократно.
Т.к. внутреннее представление - это семнантическое дерево, а на IL код, то с ним можно проводить ряд высокоуровневых перобразований:
• оптимизирующие преобразования программ;
• распараллеливание программ;
• генерация текста программы на другом языке;
• анализ качества кода;
• визуализация кода (генерация UML-диаграмм классов).
Кроме того, информацию, содержащуюся в PCU-файлах можно использовать для подсказки выбора метода по точке при редактировании текста.
9.3 Схема компиляции с использованием PCU
Схема компиляции с использованием PCU выглядет следующим образом:
Первая компиляция обозначена на схеме зеленым цветом. При первой компиляции каждый модуль сохраняется в отдельный PCU-файл. Последующие компиляции оборначены на схеме красным цветом. При последущих компиляциях семантический анализатор восстанавливает нужные части дерева из PCU файлов. При этом если исходный код какого-либо модуля был изменен, то он перекомпилируется, а также перекомпилируются все зависящие от него модули.
9.4 Сериализация и десериализация некоторых узлов семантического дерева
Основные методы сохранания и чтения PCU файлов были разработаны Бондаревым И. Далее будут описаны некоторые узлы семантического дерева, реализованные автором. Методы чтения/записи из/в PCU в этих узлах используют уже готовые методы чтения/записи различных конструкций.
Функции для записи узлов as, is, typeof, sizeof, array_const имеют вид:
private void VisitIsNode(is_node node)
{
VisitExpression(node.left);
WriteTypeReference(node.right);
}
private void VisitAsNode(as_node node)
{
VisitExpression(node.left);
WriteTypeReference(node.right);
}
private void VisitTypeOfOperator(typeof_operator node)
{
WriteTypeReference(node.oftype);
}
private void VisitSizeOfOperator(sizeof_operator node)
{
WriteTypeReference(node.oftype);
}
private void VisitArrayConst(array_const node)
{
bw.Write(node.element_values.Count);
foreach (constant_node cn in node.element_values)
VisitExpression(cn);
}
Функции для чтения узлов as, is, typeof, sizeof, array_const имеют вид:
private is_node CreateIsNode()
{
return new is_node(CreateExpression(), GetTypeReference(), null);
}
private as_node CreateAsNode()
{
return new as_node(CreateExpression(), GetTypeReference(), null);
}
private typeof_operator CreateTypeOfOperator()
{
return new typeof_operator(GetTypeReference(), null);
}
private sizeof_operator CreateSizeOfOperator()
{
return new sizeof_operator(GetTypeReference(), null);
}
private array_const CreateArrayConst()
{
List<constant_node> element_values = new List<constant_node>();
int count = br.ReadInt32();
for (int i = 0; i < count; i++)
element_values.Add((constant_node)CreateExpression());
return new array_const(element_values, null);
}
10. Управляющий блок
Управляющий блок управляет процессом компиляции программы и является интерфейсом для подключения оболочек к компилятору. Управляющий блок содержит базовую иерархию ошибок, алгоритм компиляции модулей и алгоритмы чтения/записи внутреннего представления (PCU файлов). Управляющий блок контролирует работу следующих блоков компилятора:
· контроллер синтаксических анализаторов;
· конвертор синтаксического дерева в семантическое;
· генератор кода.
Интерфейс управляющего блока имеет вид:
public class Compiler
{
public uint LinesCompiled
public CompilerInternalDebug InternalDebug
public CompilerState State
public SupportedSourceFile[] SupportedSourceFiles
public CompilationUnitHashTable UnitTable
public CompilerOptions CompilerOptions
public delegate void ChangeCompilerStateEventDelegate(Compiler sender, CompilerState State, string FileName);
public event ChangeCompilerStateEventDelegate OnChangeCompilerState;
public List<Errors.Error> ErrorsList
public Compiler(SourceFilesProviderDelegate SourceFilesProvider)
public string Compile()
public void Reload()
}
Здесь параметр конструктора SourceFilesProvider - делегат предостовляющий компилятору механизм обращения к исходным кодам программы.
public delegate object SourceFilesProviderDelegate(string FileName, SourceFileOperation FileOperation);
public enum SourceFileOperation
{
GetText, GetLastWriteTime, Exists
}
Данный механизм необходим, т.к. визуальная оболочка может не сохранять изменения текста программы на диске. Для работы компилятора необходимо три операции с файлами:
· GetText - возвращает текст файла
· GetLastWriteTime - последнне вермя изменения
· Exists - существует ли файл
Реализация такого делегата для прямого доступа к файлам на диске имеет вид:
static object DefaultSourceFilesProvider(string FileName, SourceFileOperation FileOperation)
{
switch(FileOperation)
{
case SourceFileOperation.GetText:
if (!File.Exists(FileName)) return null;
TextReader tr = new StreamReader(FileName, System.Text.Encoding.GetEncoding(1251));
string Text = tr.ReadToEnd();
tr.Close();
return Text;
case SourceFileOperation.Exists:
return File.Exists(FileName);
case SourceFileOperation.GetLastWriteTime:
return File.GetLastWriteTime(FileName);
}
return null;
}
Опишем остальные члены класса Compiler:
LinesCompiled - количесво откомпилированых строк;
InternalDebug - свойство, предостовляющее доступ к различным настройкам компилятора. Данное свойсто будет описано в пункте 13.3;
State - состояние компилятора. Может принимать следующие занчения:
· Ready - компилятор закончил компиляцию;
· CompilationStarting - старт компиляции;
· Reloading - загрузка компилятора;
· BeginCompileFile - начало компиляции файла;
· CompileInterface - компиляция интерфейса модуля;
· CompileImplementation - компиляция части реализаций модуля;
· EndCompileFile - конец компиляции файла;
· ReadDLL - чтение сборки;
· ReadPCUFile - чтение PCU файла;
· SavePCUFile - сохранение PCU файла;
· CodeGeneration - генерация кода;
· CompilationFinished - компиляция окончена;
· PCUReadingError - произошла ошибка чтения PCU файла;
· PCUWritingError - произошла ошибка записи PCU файла.
Во время изменеия этого свойства срабатывет событие OnChangeCompilerState.
SupportedSourceFiles - поддерживаемые типы исходных файлов. Данное свойство зависит от синтаксических анализаторов, подключенных к компилятору. Класс SupportedSourceFile содержит следующие поля:
· string[] Extensions - расширения файлов;
· string LanguageName - имя языка.
UnitTable - таблица откомпилированных модулей. Данное свойство будет описано в пункте 13.2.
CompilerOptions - опции компиляции:
· OutputFileType - тип выходного файла. Може принимать значения:
СlassLibrary, ConsoleApplicaton, WindowsApplication, PascalCompiledUnit;
· SourceFileName - имя главного файла программы;
· OutputFileName - имя выходного файла;
· SystemDirectory - директория? в которой находятся системные модули;
· SearchDirectory - директоря для поиска модулей;
· StandartModules - имена стандартых модулей, которые поумолчанию подключаются к каждому модулю;
ErrorsList - список ошибок, произошедших во время компиляции.
Compile - функция, запускающая процесс компиляции. В случае успешной компиляции возвращает имя выходного файла, иначе возвращает null.
Reload - функция, осуществляющая перезагрузку всех частей компилятора. Позволяет переподключить синтаксические анализаторы.
10.1 Алгоритм компиляции модулей
Одним из самых сложных алгоритмов в компиляторе оказался алгоритм компиляции модулей. Нетривиальной является последовательность, в которой надо откомпилировать interface и implementation части модулей. Было принято следующее решение: управление порядком компиляции модулей будет осуществлять управляющий блок. Конвертор дерева при этом должен иметь две функции:
CompileInterface
CompileImplementation
При вызове этих функций секции uses должны быть откомпилированы, т.е. все интерфейсные части модулей из секции uses должны быть откомпилированы.
Идея алгоритма состоит в следующем:
1. сначала компилируются модули, которые не зависят от других модулей;
2. затем компилируются интерфейсные части тех модулей, для которых секции uses из части интерфейса откомпилированы (т.е. все интерфейсные части модулей из секции uses откомпилированы);
3. затем компилируются implementation части модулей, в которых интерфейсная часть уже откомпилирована;
4. далее шаг 2-3 повторяется, пока все модули не будут откомпилированы.
Рассмотрим пример:
Стрелки сверху - это uses из секции interface, например, на этой схеме модуль t3 в секции interface содержит uses t1.
Стрелка снизу указывает на uses из секции implementation, например, на этой схеме модуль t2 в секции implementation имеет uses t3.
Для такой связки модулей необходимо выполнить компиляцию в следующей последовательности:
Compiling t1.pas...
Parse t1.pas
Parse t2.pas
Compile Interface t2.pas
Parse t3.pas
Compile Interface t1.pas
Compile Implementation t1.pas
Compile Interface t3.pas
Compile Implementation t3.pas
Compile Implementation t2.pas
Посмотрим на порядок компиляции модулей указанным алгоритмом в случае более сложной завязки модулей:
Compiling t1.pas...
Parse t1.pas
Parse t2.pas
Parse t3.pas
Parse t4.pas
Compile Interface t4.pas
Compile Interface t3.pas
Parse t5.pas
Compile Interface t5.pas
Compile Implementation t5.pas
Compile Interface t2.pas
Compile Interface t1.pas
Compile Implementation t1.pas
Compile Implementation t4.pas
Compile Implementation t2.pas
Compile Implementation t3.pas
Далее приведен собственно сам рекурсивный алгоритм компиляции модулей.
*
* CompileUnit(ИмяФайла)
* 1.CompileUnit(new СписокМодулей,ИмяФайла)
* 2.Докомпилировать модули из СписокОтложенойКомпиляции;
*
* CompileUnit(СписокМодулей,ИмяФайла)
* 1.ТекущийМодуль=ТаблицаМодулей[ИмяФайла];
* Если (ТекущийМодуль!=0) то
* Если (ТекущийМодуль.Состояние==BeginCompilation)
* СписокМодулей.Добавить(ТекущийМодуль);
* Выход;
* иначе перейти к пункту 5
*
* 2.Если ЭтоФайлDLL(ИмяФайла) то
* Если ((ТекущийМодуль=СчитатьDLL(ИмяФайла))!=0) то
* СписокМодулей.Добавить(ТекущийМодуль);
* ТаблицаМодулей.Добавить(ТекущийМодуль);
* Выход;
* иначе
* Ошибка("Не могу подключить сборку");
* Выход;
*
* 3.Если ЭтоФайлPCU(ИмяФайла) то
* Если ((ТекущийМодуль=СчитатьPCU(ИмяФайла))!=0) то
* СписокМодулей.Добавить(ТекущийМодуль);
* ТаблицаМодулей.Добавить(ТекущийМодуль);
* Выход;
* иначе
* иначе перейти к пункту 4;
*
* 4.ТекущийМодуль=новыйМодуль();
* ТекущийМодуль.СинтаксическоеДерево=
Парасеры.Парсить(ИмяФайла,ТекущийМодуль.СписокОшибок);
* Если (ТекущийМодуль.СинтаксическоеДерево==0) то
* Если (ТекущийМодуль.СписокОшибок.Количество==0) то
* Ошибка("Модуль не неайден");
* иначе
* Ошибка(ТекущийМодуль.СписокОшибок[0]);
* ТаблицаМодулей[ИмяФайла]=ТекущийМодуль;
* ТекущийМодуль.Состояние=BeginCompilation;
*
* 5.СинтаксическийСписокМодулей=
ТекущийМодуль.СинтаксическоеДерево.Interface.UsesList;
* Для(i=СинтаксическийСписокМодулей.Количество-1-ТекущийМодуль.КомпилированыеВInterface.Количество;i>=0;i--)
* ТекушийМодуль.ТекущийUsesМодуль=
СинтаксическийСписокМодулей[i].ИмяФайла;
* ИмяUsesФайла=СинтаксическийСписокМодулей[i].ИмяФайла;
* Если (ТаблицаМодулей[ИмяUsesФайла]!=0)
* Если (ТаблицаМодулей[ИмяUsesФайла].Состояние==BeginCompilation)
* Если (ТаблицаМодулей[ТаблицаМодулей[ИмяUsesФайла].
ТекущийUsesМодуль].Состояние=BeginCompilation)
* Ошибка("Циклическая связь модулей");
* CompileUnit(ТекущийМодуль.КомпилированыеВInterface,ИмяUsesФайла);
* Если (ТекушийМодуль.Состояние==Compiled) то
* СписокМодулей.Добавить(ТекушийМодуль);
* Выход;
*
* 6.ТекущийМодуль.СемантическоеДерево=
КонверторДерева.КонвертироватьInterfaceЧасть(
ТекущийМодуль.СинтаксическоеДерево,
ТекущийМодуль.КомпилированыеВInterface,
ТекущийМодуль.СписокОшибок);
* СписокМодулей.Добавить(ТекущийМодуль);
* СинтаксическийСписокМодулей=
ТекущийМодуль.СинтаксическоеДерево.Implementation.UsesList;
* Для(i=СинтаксическийСписокМодулей.Количество-1;i>=0;i--)
* Если (ТаблицаМодулей[СинтаксическийСписокМодулей[i].ИмяФайла].
Cостояние=BeginCompilation)
* СписокОтложенойКомпиляции.Добавить(
ТаблицаМодулей[СинтаксическийСписокМодулей[i].ИмяФайла]);
* иначе
* CompileUnit(ТекущийМодуль.КомпилированыеВImplementation,
СинтаксическийСписокМодулей[i].ИмяФайла);
* Если(ДобавлялиХотябыОдинВСписокОтложенойКомпиляции)
* СписокОтложенойКомпиляции.Добавить(ТекущийМодуль);
* выход;
* иначе
* КонверторДерева.КонвертироватьImplementationЧасть(
ТекущийМодуль.СинтаксическоеДерево,
ТекущийМодуль.СемантическоеДерево,
ТекущийМодуль.КомпилированыеВImplementation
ТекущийМодуль.СписокОшибок);
* ТекущийМодуль.Состояние=Compiled;
* СохранитьPCU(ТекущийМодуль);
*
*
*
* [краткая верcия алгоритма компиляции модулей]
*
* CompileUnit(ИмяФайла)
* 1.CompileUnit(new СписокМодулей,ИмяФайла)
* 2.Докомпилировать модули из СписокОтложенойКомпиляции;
*
* CompileUnit(СписокМодулей,ИмяФайла);
* 1.Если у этого модуля откомпилирован хотябы интерфейс то
* добавить его в СписокМодулей
* выход
* 2.Если это DLL то
* считать
* добавить его в СписокМодулей
* выход
* 3.Если это PCU то
* считать
* добавить его в СписокМодулей
* выход
* 4.создать новый компилируемыйМодуль
* РаспарситьТекст(ИмяФайла)
* Состояние компилируемогоМодуля установить на BeginCompilation
* 5.Для всех модулей из Interface части
компилируемогоМодуля справа налево
* Если мы уже начаинали компилировать этот модуль
* Если состояние модуля BeginCompilation
* Если состояние последнего
компилируемого им модуля BeginCompilation
* ошибка("Циклическая связь модулей")
* выход
* CompileUnit(Список из Interface части компилируемогоМодуля,модуль.имя)
* Если компилируемыйМодуль.Состояние Compiled то
* добавить его в СписокМодулей
* выход
* 6.Откомпилировать Interface часть компилируемогоМодуля
* Для всех модулей из Implementation части компилируемогоМодуля справа налево
* Если состояние очередного модуля BeginCompilation то
* добавить его в список отложеной компиляции;
* иначе
* CompileUnit(Список из Implementation части компилируемогоМодуля,модуль.имя)
* Если Добавляли Хотябы Один В Список Отложеной Компиляции то
* добавить компилируемыйМодуль в список отложеной компиляции
* выход
* Откомпилировать Implementation часть компилируемогоМодуля
* Состояние компилируемогоМодуля установить на Compiled
* добавить его в СписокМодулей
* Сохранить компилируемыйМодуль в виде PCU файла на диск
*
*
************************************************************
11. Система локализации
Система локализации служит для отображения текстовой информации компилятора на другом языке. Система локализации реализована в отдельной dll и не зависит от других частей компилятора.
Идея локализации состоит в следующем: вместо обычных строк используются специальные строки-идентификаторы (ключи): например, «IDENT», далее мы ставим в соответвие таким идентификаторам текст на разных языках:
файл rus.dat:
IDENT=идентификатор
файл eng.dat:
IDENT=identifier
Все такие строки-идентефикаторы в коде оборачиваются вызовом специальной функции, которой на вход подается строка-идентификатор (ключ), а на выходе она возвращает соответствующий текст, зависящий от заданого языка.
Система локализации реализована в виде статического класса StringResources в сборке Localisation.dll.
public class StringResources
{
static StringResources()
public static string Get(string keyName)
public static string ResDirectoryName
public static void SetTextForObject(object obj, string prefix)
public static void UpdateObjectsText()
}
Статический конструктор загружает в таблицу соответствия язык по умолчанию. Язык по умолчанию хранится в ресурсе. Файл ресурса создается программой, написаной на PascalABC.NET.
Get - функция, возвращающая текст, соответвующий ключу KEY.
ResDirectoryName - Задает директорию, из которой считываются языковые файлы. При изменении этого свойсва происходит считывание.
SetTextForAllObject - Функция, производящая замену всех строковых полей объекта obj, содержащих ключ с перфиксом prefix, на соответвующие значания. Если обьект содержит свойство, возвращающее массив объектов, то замена производится и в этих объектах. Это необходимо для реализации локализации для визуальных объектов таких как форма или кнопка. Данная возможнось используется в визуальной оболочке компилятора PascalABC.NET. Все поля, которые подвергались замене, запоминаются в специальной таблице соответствия ObjectHashtable. Функция реализована с помощю рефлексии.
UpdateObjectsText - производит замену всех значений полей объектов из таблицы ObjectHashtable на соответвующие значения. Эта функция необходима для реализации механизма смены языка «на лету».
Языковые файлы могут содержать строки следующего формата:
IDENT=value - устанавливает ключу IDENT значение value
%PREFIX%=prefix - устанавливает префикс для всех последующих ключей
//coment - коментарий. Не учитывается
IDENT=nLINES
line1
...
linen
- устанавливает ключу IDENT многострочное значение line1 ... linen
Управление языками производит статический класс StringResourcesLanguage:
public class StringResourcesLanguage
{
public static List<string> AccessibleLanguages
public static string ConfigDirectory
public static void LoadDefaultConfig()
public static string CurrentLanguageName
}
Здесь:
AccessibleLanguages - список доступных языков.
ConfigDirectory - директория, в которой находятся папки с языковыми файлами. В каждой папке с языковыми файлами должен быть файл с именем “.LanguageName” в котором содержится имя данного языка. При изменении данного свойсва происходит сканирование подпапок и заполнение списка AccessibleLanguages.
LoadDefaultConfig - устанавливает значение ConfigDirectory на «папка_сборки\lng».
CurrentLanguageName - задает текущий язык, после чего из соответвующей папки загружаются языковые файлы и вызывется StringResources.UpdateObjectsText().
Рассмотрим пример использования системы локализации для генерации сообщения об ошибках. Класс ошибки NameRedefinition (повторное определение имени) содерит функцию
public override string ToString()
{
return string.Format(
StringResources.Get("NAME_REDEFINITION_{0}"), _name);
}
Как видно из кода, для формирования сообщения о ошибке используется ключ “NAME_REDEFINITION_{0}” который заменяется на значение с помощью вызова функции StringResources.Get, а затем в полученую строку подставляется параметр - имя повторно объявленого идентефикатора.
В русском языке ключ имеет следующее значение:
NAME_REDEFINITION_{0}=Повторно объявленный идентификатор {0}
в английском:
NAME_REDEFINITION_{0}=Name redefinition {0}
12. Консольная оболочка компилятора
Консольная оболочка необходима для компиляции программ с помощью командной строки. Консольная оболочка также обладает рядом специфических возможностей, которые используются разработчиками компилятора для его тестирования и оценки производительности компилятора в целом, либо отдельных его частей.
Командная строка:
pabcnetc source_file_name
source_file_name - имя исходного файла главной программы
В случае запуска без параметров консольная оболочка входит в командный режим. Возможны следующие команды:
? - Помощь
cd dir - Переход в директорию dir
file_name - Компилировать файл
files_mask - Компилировать файлы по маске
Reset - Перезапустить компилятор
ResetOnCompile=0|1 - Перезапускать копилятор перед компиляцией [0]
Rebuild=0|1 - Перекомпилировать PCU файлы [0]
Debug=0|1 - Генерировать отладочную информацию (PDB файлы) [0]
OutDir=output_directory - Задать выходную директорию
ClrScr - Очистить экран
ShowAllMessages=0|1 - Выводить все сообщения компилятора [0]
Language=n - Выбрать язык
ScanSubdirs=0|1 - сканировать поддиректории [0]
[] - значение поумолчанию.
13. Модули визуальной оболочки
Модули визуальной оболочки (плагины) необходимы для расширения функциональности визуальной оболочки. Модулям доступно внутреннее представление компилятора (синтаксическое и семантическое дерево), с которым они могут делать различные преобразования, например визуалирацию.
13.1 Интерфейс подключения
Интерфейсы для плагинов находятся в сборке PluginsSupport.dll. Интерфейс, который должен реализовать плагин, имеет вид:
public interface IVisualPascalABCPlugin
{
string Name
{
get;
}
string Banner
{
get;
}
void GetGUI(List<IPluginGUIItem> MenuItems,
List<IPluginGUIItem> ToolBarItems);
}
Name - возвращает имя плагина.
Banner - возвращает имя и версию плагина.
GetGUI - служит для получения визуального интерфейса плагина.
MenuItems - пункты в меню;
ToolBarItems - пункты на панели.
Интерфейс IPluginGUIItem служит для создания визуальных элементов плакина всраеваемых в оболочку(пунктов меню, кнопок на панели).
public interface IPluginGUIItem
{
string Text
{
get;
}
string Hint
{
get;
}
System.Drawing.Image Image
{
get;
}
System.Drawing.Color ImageTransparentColor
{
get;
}
System.Windows.Forms.Keys ShortcutKeys
{
get;
}
string ShortcutKeyDisplayString
{
get;
}
void Execute();
}
Text - текст на кнопке;
Hint - всплывающая подсказка;
Image - изображение;
ImageTransparentColor - фоновый цвет изображения;
ShortcutKeys - горячие клавиши;
ShortcutKeyDisplayString - тест, отображающий сочетание горячих клавиш;
Execute - функция, которая выполняется в тот момент, когда нажали данную кнопку.
Интерфейс, который должна реализовывать оболочка, к которой подключается такой плагин:
public interface IVisualEnvironmentCompiler
{
PascalABCCompiler.Compiler Compiler
{
get;
}
void ExecuteSourceLocationAction(
PascalABCCompiler.SourceLocation SourceLocation,
SourceLocationAction Action);
object ExecuteAction(VisualEnvironmentCompilerAction Action,
object obj);
}
Compiler - компилятор, обьект класса PascalABCCompiler.Compiler.
ExecuteSourceLocationAction - выполняет манипуляции с курсором в исходном тексте программы. Action - действие которое необходимо выполнить с положением курсора SourceLocation.
public enum SourceLocationAction
{
SelectAndGotoBeg,
SelectAndGotoEnd,
GotoBeg,
GotoEnd
}
ExecuteAction - функция, которая позволяет выполнить некоторое действие оболочки. Возможные действия:
public enum VisualEnvironmentCompilerAction
{
Run,
Build,
Rebuild,
Stop,
OpenFile,
GetDirectory
}
Run - запустить пограмму;
Build - откомпилировать программу;
Rebuild - перекомпилировать программу;
Stop - остановить программу;
OpenFile - открыть файл;
GetDirectory - получить одну из стандартных директорий.
Также сборка содержит класс PluginGUIItem, реализующий интерфейс IPluginGUIItem:
public delegate void PluginGUIItemExecuteDelegate();
public class PluginGUIItem : IPluginGUIItem
{
string text;
string hint;
Image image;
Color imageTransparentColor;
PluginGUIItemExecuteDelegate executeDelegate;
System.Windows.Forms.Keys shortcutKeys = System.Windows.Forms.Keys.None;
string shortcutKeyDisplayString = null;
public PluginGUIItem(string text, string hint, Image image, Color imageTransparentColor, PluginGUIItemExecuteDelegate executeDelegate)
{
this.text = text;
this.hint = hint;
this.image = image;
this.imageTransparentColor = imageTransparentColor;
this.executeDelegate = executeDelegate;
}
public PluginGUIItem(string text, string hint, Image image, Color imageTransparentColor, PluginGUIItemExecuteDelegate executeDelegate, System.Windows.Forms.Keys shortcutKeys, string shortcutKeyDisplayString)
{
this.text = text;
this.hint = hint;
this.image = image;
this.imageTransparentColor = imageTransparentColor;
this.executeDelegate = executeDelegate;
this.shortcutKeys = shortcutKeys;
this.shortcutKeyDisplayString = shortcutKeyDisplayString;
}
public string Text
{
get { return text; }
}
public string Hint
{
get { return hint; }
}
public Image Image
{
get { return image; }
}
public Color ImageTransparentColor
{
get { return imageTransparentColor; }
}
public void Execute()
{
executeDelegate();
}
public System.Windows.Forms.Keys ShortcutKeys
{
get { return shortcutKeys; }
}
public string ShortcutKeyDisplayString
{
get { return shortcutKeyDisplayString; }
}
}
Это позволяет при реализации плагинов пользоваться уже готовым классом для визуальных компонентов, либо порождать от него наследника.
13.2 Модуль «Визуализатор синтаксического дерева»
Плагин «Визуализатор синтаксического дерева» служит для визуализации синтаксического дерева программы и позволяет осуществлять переход от узла дерева к тексту программы, из которого был получен данный узел. Данный плагин может быть использован при обучении для того чтобы показать, как тест программы преобразуется в синтаксическое дерево.
Подобные документы
Рассмотрение нелинейных динамических структур данных в виде бинарного дерева. Построение дерева двоичного поиска. Реализация трех обходов дерева, выведение обходов на экран компьютера. Разработка текста программы. Симметричноправая прошивка дерева.
контрольная работа [81,6 K], добавлен 14.12.2011Сбалансированные многоходовые деревья поиска. Исследование структуры B+-дерева, её основные операции. Доказательство их вычислительной сложности. Утверждение о высоте. Поиск, вставка, удаление записи, поиск по диапазону. B+-деревья в системах баз данных.
курсовая работа [705,5 K], добавлен 26.12.2013Организация таблицы идентификаторов, ее содержание и назначение. Метод бинарного дерева и цепочек. Проектирование лексического анализатора и схема распознавателя. Построение дерева вывода, синтаксический анализатор. Анализ результатов работы программы.
курсовая работа [1,0 M], добавлен 25.12.2014Простой и быстрый переход по иерархической файловой системе. Наличие файлового менеджера. Ряд глобальных переменных. Основные пользовательские функции. Основная идея формирования дерева. Функция добавления записи в список. Обновление дерева каталогов.
курсовая работа [243,1 K], добавлен 04.06.2011Понятие и базовые свойства ориентированного дерева. Обходы (способы нумерации вершин) в глубину и ширину. Представление бинарных графов с помощью указателей и массива, скобочной записи, списком прямых предков. Сбалансированность дерева двоичного поиска.
презентация [330,6 K], добавлен 19.10.2014Организация бинарного дерева. Порядок размещения данных в нелинейных структурах. Организация пользовательского интерфейса. Симметричный обход дерева. Параллельная работа обработчиков исключений. Расширенный графический интерфейс и его возможности.
курсовая работа [426,0 K], добавлен 24.06.2013Краткая история становления языка программирования Pascal и основные понятия графики. Основные функции и процедуры работы с графикой в PascalABC. Создание графического проекта: понятие "фрактал" и реализация треугольника. Построения фрактала "Дерево".
курсовая работа [1,4 M], добавлен 26.10.2014Разработка программы на языке С#, которая будет заниматься построением бинарного дерева для исходных данных и их редактированием, поиском информации о товарах по заданному ключу. Графические схемы алгоритмов поиска и удаления элемента бинарного дерева.
курсовая работа [796,9 K], добавлен 22.02.2016Понятие семантики; обзор и анализ существующих средств семантического разбора естественно-языковых текстов. Разработка алгоритма работы системы на основе семантического анализа, его реализация на языке программирования; проектирование интерфейса системы.
дипломная работа [1,7 M], добавлен 18.03.2012Изучение структуры доменных имен и описание возможностей их системы по использованию символьных наименований узлов в IP-сетях. Записи ресурсов домена и функции сети по расширению имен и зон обратного просмотра. Делегирование ответственности за домены.
презентация [104,2 K], добавлен 25.10.2013