- •Логические данные
- •Символьные данные
- •Хранение текста
- •Арифметические данные
- •Данные с фиксированной точкой
- •1111111101000111(2) - Инвертируем биты - 0000000010111000(2)
- •Данные с плавающей точкой
- •Простейшие приемы анализа погрешностей.
- •Методы оптимизации
- •Данные системы Turbo Pascal
- •Способы вычисления логических операций
- •Подпрограммы и их параметры
- •Способы передачи параметров
- •Структурное программирование, управляющие конструкции, пошаговая детализация.
- •Рекурсия и итерация
- •Метод итераций (повторений)
- •Метод инварианта цикла.
- •Метод инвариантной функции.
- •Метод индуктивной функции.
- •Защитное программирование
- •Абстрактные типы данных
- •Использование объектных средств
- •Динамические структуры данных
- •Языки и грамматики
- •Расширение нотации Бэкуса-Наура.
- •Интерпретатор математических формул, реализованный на основе метода Дейкстры.
- •Абстрактные структуры данных.
- •Часть 2 Последовательность.
- •Динамический вектор.
- •Множество
- •Нагруженное множество
- •Итераторы.
Интерпретатор математических формул, реализованный на основе метода Дейкстры.
Unit Aintr;
Interface
Uses Astream, Astack, Apro;
Type {Если мы будем проверять правильность соседства лексем, то во флаге удобно держать тип предыдущей лексемы. Для этого объявим новый тип.}
LexType = (opar { ( }, cpar { ) }, sgn {знак}, value{значение});
Type Pintr = ^Cintr;
Cintr = object
Public constructor Init (Pst:PFStreamTxt); {Передается указатель на поток}
Destructor Done; virtual;
Procedure Interpretate;
Private
P:PFStreamTxt;
Pro:Cpro;
St:CstackCh; {стек Дейкстры}
Oldlex:lextype; {флаг Дейкстры}
Procedure Failure (n:word);
Procedure Lexem; {процедура обработки очередной лексемы}
Procedure Number; {интерпретатор числа }
Function Weight (x:char):byte; {возвращает приоритет операции или скобки}
Function Right (x:byte):boolean; {возвращает значение ассоциативности; на входе – вес, на выходе истина если операции с таким весом правоассоциативны и ложь в противном случае}
Procedure PushPop (x:char); {организует выталкивание из стека Дейкстры}
End;
Implementation
Uses Crt;
Constructor Cintr.Init;
Begin
P:=PST;
Writeln(‘Pro.Create’);
Pro.Create;
St.Create;
{oldlex будет установлена в процессе интерпритации}
end;
Destructor Cintr.Done;
Begin
St.Destroy;
Writeln (‘Pro.Destroy’);
Pro.Destroy;
P:=nil;
End;
Procedure Cintr.Interpretate;
Var S:string;
Begin
St.Push ( ‘(‘ ); {помещаем первую неявную скобку}
Oldlex := opar;
While not p^.IsEnd do Lexem;
If not ((oldlex=value) or (oldlex=cpar)) then failure (1);
PushPop ( ‘)’ );
If not st.IsEmpty then failure(2);
Writeln (‘Pro.Show’);
Pro.Top2Str (s); {вершина стекового процессора преобразуется в строковую переменную}
Writeln (‘Ответ =’,s);
{далее необязательная часть}
Pro.Del; {чистим стековый процессор}
If not Pro.IsEmpty then Failure(3);
End;
Procedure Cintr.Lexem;
Var c:char;
Begin
C:= p^.getchar;
Case c of
‘0’..’9’: begin
if not ((oldlex=opar) or (oldlex=sgn)) then failure(11);
number;
oldlex :=value;
end;
‘(‘: begin if not ((oldlex=opar) or (oldlex=sgn)) then failure(12);
st.Push ( c );
p^.skip;
oldlex:=opar;
end;
‘)’:begin If not ((oldlex=value) or (oldlex=cpar)) then failure (13);
PushPop ( c );
P^.Skip;
Oldlex:=cpar;
End;
‘+’,’-‘,’*’,’/’:begin
if (c=’+’) and ((oldlex=opar) or (oldlex=sgn)) then
begin
if not ((weight(‘p’)>weight(st.top)) or
(((weight(‘p’)=weight(st.top)) and
right(weigth(‘p’))) then failure (14);
p^.skip;
Oldlex:=sgn;
End else
if (c=’-’) and ((oldlex=opar) or (oldlex=sgn)) then
begin
if not ((weight(‘m’)>weight(st.top)) or
(((weight(‘m’)=weight(st.top)) and
right(weigth(‘m’))) then failure (15);
PushPop(‘m’);
p^.skip;
Oldlex:=sgn;
End else if c in [‘+’,’-’,’*’,’/’] then
Begin
If not ((oldlex=value) or (oldlex=cpar)) then
failure (16);
PushPop( c );
P^.skip;
Oldlex:=sgn;
End {else if одноместные операции} ;
End;
Else failure(17);
End;
End;
{Текст можно было несколько сократить в части обработки операций, если запоминать значение веса вершины стека. Удобнее Top заменить на GetTop.}
procedure Cintr.PushPop;
var y:char;
begin
while true do
begin
if st.IsEmpty then failure(21);
if ((weight(x)>weight(st.top)) or
(((weight(x)=weight(st.top)) and right(weigth(x))) then break
else begin
st.pop(y);
case y of
‘m’:begin writeln (‘Pro.Inv’); pro.Inv; end;
‘+’: begin writeln (‘Pro.Add’); pro.Add; end;
‘-‘:begin writeln (‘Pro.Sub’); pro.sub; end;
‘*’ :begin writeln (‘Pro.Mul’); pro.Mul; end;
‘/’ :begin writeln (‘Pro.Div’); pro._Div; end;
end;
end;
end;
if x in [‘m’,’+’,’-‘,’*’,’/’] then st.Push(x);
else if x=’)’ then begin
if not (st.top=’(‘) then failure(22);
st.Pop(y);
end;
end;
function Cintr.Weight;
begin
case x of
‘(‘:weight:=0;
‘)’:weight:=1;
‘+’,’-‘:weight:=2;
‘/’:weight:=3;
‘*’:weight:=4;
‘p’,’m’:weight:=5;
else failure(31);
end;
end;
function Cintr.Right;
begin
case x of
2:Right:=false;
3:right:=true;
4:right:=false;
5:right:=true;
else right:=false;
end;
end;
procedure Cintr.Number;
…….
End;
Procedure Cintr.Failure;
Begin
……
halt(1);
end;
end.
Может возникнуть вполне резонный вопрос: какой из представленных двух компиляторов лучше? Ответ на него очень прост: первый проще создавать, а второй изменять. Если вы программировали первый вариант, то обратили внимание, что в нем практически пишешь программу по грамматике, при этом лишается всякая самостоятельность. Тогда как во втором компиляторе
приходится применять различные конструкции для получения нужного результата. А теперь попробуем изменить оба компилятора (попробуем хотя бы изменить приоритет операций). В первой программе на методе нисходящего разбора это потребует практически полной переписки программы, тогда как во втором достаточно всего лишь изменить цифру в процедуре Weight.
Похожим образом создаются языки программирования. Только там компилятор обеспечивает перевод программы с языка программирования на машинный язык. Кроме этого в нашей работе используется однопроходной вариант, не содержащий отдельного блока лексического анализа. Обычно вначале файл преобразуется: лексемы заменяются кодами. В наше случае лексемы достаточно просты поэтому дополнительное преобразование не понадобилось. В работе настоящего компилятора не ограничиваются однопроходным вариантом. Например, в языке Alpha компилятор выполняет около 30 проходов (притом из них 15 - оптимизация). В результате получается программа по скорости работы опережающая программу на Assembler'е.