|
Методология программирования.Методологии можно классифицировать по разным признакам. Например, по признаку внутренней структуры: 1) функциональные (большая программа как совокупность подпрограмм); 2) объектные (программа – совокупность взаимодействующих объектов); 3) объектно-ориентированные. Объекты, подпрограммы и программы будем называть модулями. Методологии: 1) Нисходящая (сверху - вниз), т. е. сначала пишутся модули верхних уровней иерархии, а потом нижних. Функционирование модуля A состоит из вызовов базовых модулей и модулей B1, B2, B3. При этом могут возникать рекурсии на верх. Легко видеть, что модули каждого уровня используют средства языка, друг друга и ранее написанные модули. При этом не нужно путать стратегию нисходящего проектирования с методом пошаговой детализации, так как метод нисходящего разбора – метод построения большой программной системы. В методе нисходящего разбора прежде всего контролируется правильность интерфейса. И мы можем продемонстрировать работу программной системы до ее завершения. В этом случае, если средства для работы еще не написаны, то можно использовать имитаторы (заглушки). 2) Восходящая (снизу-вверх) В этом случае необходимо построить модуль A на базе модулей Z. Модули каждого уровня используют модули более низких уровней и друг друга. При этом основным неудобством данного метода является отсутствие уверенности в том, что мы доберемся до A. Восходящая методология имеет смысл когда общая структура априоре известна. Например в случае разработки компилятора. Вывод: Если создается система, имеющая подходящий аналог, то лучше использовать восходящую методологию, а в противном случае нисходящую. 3) Смешанная (реально используется на практике).
Компиляция формулы методом нисходящего разбора. Поток – объект преобразующий формулу к виду необходимому объекту. Поток может быть глобальным к компилятору, а может быть его параметром. Гораздо удобнее когда поток не зависим от компилятора. При этом каждый метод компилятора должен иметь параметр потока, по этому разумнее передать параметр инициализирующей части компилятора, и в качестве поля запомнить имя потока.
Program CompileFormula; uses Crt, AComp, AStream; var DN:string; pst:PFStreamTxt; c:CComp; begin ClrScr; writeln('Откуда формула'); read(Dn); new(PSt,Create(DN)); c.Init(PSt); c.Compile; c.Done; dispose(Pst,Destroy); end.
Unit AComp; interface uses AStream; Type PComp = ^CComp; CComp = object public constructor Init(PS:PFStreamTxt); destructor Done;virtual; procedure Compile; private p:PFStreamTxt; procedure Failure(n:word); procedure Formula; procedure OperandP; procedure OperandD; procedure OperandM; procedure OperandE; procedure Number; procedure Variable; end;
implementation uses crt;
constructor CComp.Init; begin p:=ps; end;
destructor CComp.Done; begin p:=nil; end;
Procedure CComp.Compile; begin ClrScr; writeln('Pro.Create'); if p^.IsEnd then Failure(1); Formula; if not p^.IsEnd then Failure(2); writeln('Pro.ShowResult'); writeln('Pro.Destroy'); end;
Procedure CComp.Formula; {Откомпилировать самую большую не пустую формулу с данной позиции в потоке} (* <Formula>::=<OperandP>[{+,-} <OperandP>]* *) {Нашей задачей является перевод этого выражения на язык Pascal} var x:char; begin if p^.IsEnd then Failure(11); {Формула оказалась пустой - это ошибка} OperandP; {Вызываем обработку операнда P} while true do {Организация бесконечного цикла} begin if p^.IsEnd then break; {Если поток пуст, то выходим из цикла} x:=p^.GetChar; {берем символ из потока, по грамматике он должен быть либо + или -, поэтому далее проверяем на соответствие этому} case x of '+':begin p^.skip; OperandP; writeln('Pro.Add'); end; '-':begin p^.skip; OperandP; writeln('Pro.Sub'); end; else break; {Если не то и не другое, то выходим из цикла - может быть это закрывающая скобка} end; end; {Если поток не пуст и оставшийся символ не закрывающая скоба, то генерируем ошибку. Обратите внимание, что сначала необходимо проверить поток на пустоту, а уже в следующем вложенном if. Если написать, например, так if not p^.IsEnd and not (p^.GetChar in [')']) then failure (12); то можно получить сообщение об ошибке потока, т.к. сначала поток провериться на пустоту и независимо от того пуст он или полон из него будет взят символ (т.е. происходи полное вычисление логических выражений) } if not p^.IsEnd then if not (p^.GetChar in [')']) then failure (12); end;
Procedure CComp.OperandP; (* <OperandP>::=<OperandD>[ / <OperandP> ] *) var x:char; begin {Если поток пуст, то генерируем ошибку} if p^.IsEnd then Failure(21); OperandD; {Вызываем операнд D} {Если поток оказался не пуст, то тут возможно 2 ситуации: 1) Далее идет знак /, а за ним опять операнд P 2) Далее идет лексема не имеющая никакого отношения к операнду P} if not p^.IsEnd then begin x:=p^.GetChar; if x='/' then begin p^.skip; OperandP; writeln('Pro.Div'); end; {Если x это / - то получаем первый случай} end; if not p^.IsEnd then if not (p^.GetChar in [')','+','-']) then failure (22); {Если далее идет не)+- то это ошибка} end;
Procedure CComp.OperandD; (* <OperandD>::= <OperandM> [* <OperandM>]* *) var x:char; begin if p^.IsEnd then Failure(31); OperandM; while true do begin if p^.IsEnd then break; x:=p^.GetChar; case x of '*':begin p^.skip; OperandM; writeln('Pro.Mul'); end; else break; end; end; if not p^.IsEnd then if not (p^.GetChar in [')','+','-','/']) then failure (32); end;
Procedure CComp.OperandM; var x:char; begin {Полностью аналогичен Formula, правда было бы рациональнее вместо конструкции case использовать if..., но ради сохранения стиля оставляем все как есть} if p^.IsEnd then Failure(41); x:=p^.GetChar; case x of '+':begin p^.skip; OperandM; end; '-':begin p^.skip; OperandM; writeln('Pro.Inv'); end; else OperandE; end; if not p^.IsEnd then if not (p^.GetChar in [')','+','-','/','*']) then failure (42); {Обратите внимание, что число знаков увеличилось из-за того что надо проверять еще и на все предыдущие операции} end;
{Для работы с операндом E потребуются дополнительно константы-цифры и константы-буквы идентификатора} const Digits:set of char = ['0'..'9']; Letters:set of char = ['_','A'..'Z','a'..'z'];
Procedure CComp.OperandE; (* <OperandE>::=<Number> | <Variable> | (<Formula>) *) var x:char; begin if p^.IsEnd then Failure(51); {поток не пуст?} {берем символ из потока} x:=p^.Getchar; if x in digits then Number {Если x-цифра, то считываем число} else if x in Letters then Variable {Если x-буква, то считываем идентификатор} else if x='(' then {если x - открывающая скобка, то...} begin p^.skip; Formula; {пропускаем символ, вызываем обработку формулы} if p^.IsEnd then Failure(52) else {Если поток закончен то ошибка, иначе} if not (p^.GetChar in [')']) then failure (53) {Считываем символ} else p^.skip; {Если это), то все в порядке, иначе выдаем ошибку} end else failure(54); end;
Procedure CComp.Variable; (* <Variable>::=<Letters> [ <Letters>|<Digits> ]* *)
var x:char; begin if p^.IsEnd then Failure(61); x:=p^.GetChar; if not (x in Letters) then Failure(62); {Если x - не буква, то точно ошибка} write('Pro.LoadR(',x); p^.skip; while true do {Бесконечный цикл, в котором считывается идентификатор} begin if p^.IsEnd then Break; {Если вы хотите игнорировать символы табуляции в имени идентификатора, то комментарий ниже необходимо убрать} { if p^.IsAfterSpace then break;} x:=p^.GetChar; if x in letters+digits then begin writeln(x); p^.skip; end else break; end; writeln(')'); if not p^.IsEnd then Failure(52) else if not (p^.GetChar in [')','+','-','/','*']) then failure (63); end;
{Можно было точно так же написать считывания числа, но мы пойдем по другому пути. Следующая процедура работает по методу конечного автомата. Это очень мощный механизм организации работы. Суть данного метода в том, что в каждый момент времени фиксируется состояние алгоритма(исполнителя). Он будет находится в этом состоянии пока не произойдет фиксированное событие, изменяющее состояние алгоритма(исполнителя). В нашем случае это будет определенный символ (или его отсутствие). Для нашего алгоритма число возможных состояний равно 6: 1...9. 1...9 E + 1...9 ----- - ----- - - ----- - 1 2 3 4 5 6 7 1 состояние - считываются цифры (до первого нецифрового обозначения), после чего осуществляется переход во 2 состояние 2 состояние - считывается точка(если есть) и переход в состояние 3 3 состояние - считываются цифры (до первого нецифрового обозначения), после чего осуществляется переход во 4 состояние 4 состояние - считывается E (обозначение экспоненты) и переход в состояние 5 5 состояние - считывается или + или - после чего переход в 6 состояние 6 состояние - считываются цифры (до первого нецифрового обозначения), после чего осуществляется переход во 7 состояние 7 состояние - конец работы Естественно, что если состояние не обнаруживает те символы, которые оно должно считать, то осуществляется переход в следующее состояние. Использование метода конечных автоматов в этом случае - это излишество, но оно приведено здесь с целью ознакомления с алгоритмом }
procedure CIntr.Number;
function dig(c:char):byte; begin if c in digits then dig:=ord(c)-ord('0') else dig:=0; end;
var x: char; s: byte; sign:shortint; frc, pow,num:double;
begin if p^.IsEnd then failure(71); x:=p^.GetChar; if not (x in digits) then failure(72); write('Pro.LoadR('); s:=1; num:=0; pow:=0; frc:=0.1; sign:=1; while s<>0 do case s of 1: begin write(x); num:=num*10+dig(x); p^.Skip; if p^.IsEnd then s:=0 else begin x:=p^.GetChar; if x='.' then s:=2 else if x in ['e','E'] then s:=4 else if not (x in digits) then s:=0; end; end; 2: begin write('.'); p^.Skip; if p^.IsEnd then s:=0 else begin x:=p^.GetChar; if x in digits then s:=3 else if x in ['e','E'] then s:=4 else failure(75); end; end; 3: begin write(x); num:=num+frc*dig(x); frc:=0.1*frc; p^.Skip; if p^.IsEnd then s:=0 else begin if x in ['e','E'] then s:=4 else if not (x in digits) then s:=0; end; end; 4: begin write(x); p^.Skip; if p^.IsEnd then failure(76); x:=p^.GetChar; if x in digits then s:=6 else if x in ['+','-'] then s:=5 else failure(77); end; 5: begin write(x); if x='-' then sign:=-1; p^.Skip; if p^.IsEnd then failure(78); x:=p^.GetChar; if x in digits then s:=6 else failure(79); end; 6: begin write(x); pow:=pow*10+dig(x); p^.Skip; if p^.IsEnd then s:=0 else begin x:=p^.GetChar; if not (x in digits) then s:=0; end; end; end; writeln(')'); num:=num*exp(ln(10)*sign*pow); pro^.LoadR(num); if not p^.IsEnd then if not (p^.GetChar in [')','+','-','*','/']) then failure(80); end;
Procedure CComp.Failure; {Фатальный обработчик ошибок} begin write('Ошибка компилятора # ',n:1,' в методе '); case n of 1..10:writeln('Compile'); 11..20:writeln('Formula'); 21..30:writeln('OperandP'); 31..40:writeln('OperandD'); 41..50:writeln('OperandM'); 51..60:writeln('OperandE'); 61..70:writeln('Variable'); 71..80:writeln('Number'); end; case n of 1:writeln('Пустой текст'); 11,21,31,41,51,61,71:writeln('Нет операнда'); {..и т.д.} end; Halt(1); end;
begin end.
Что делать, если нет взаимности? А теперь спустимся с небес на землю. Приземлились? Продолжаем разговор... ЧТО И КАК ПИСАЛИ О МОДЕ В ЖУРНАЛАХ НАЧАЛА XX ВЕКА Первый номер журнала «Аполлон» за 1909 г. начинался, по сути, с программного заявления редакции журнала... Что способствует осуществлению желаний? Стопроцентная, непоколебимая уверенность в своем... Что будет с Землей, если ось ее сместится на 6666 км? Что будет с Землей? - задался я вопросом... Не нашли то, что искали? Воспользуйтесь поиском гугл на сайте:
|