Оберон-клуб «ВЄДАsoft»

Твердыня модульных языков
Текущее время: 29 мар 2024, 12:29

Часовой пояс: UTC + 2 часа




Начать новую тему Ответить на тему  [ Сообщений: 72 ]  На страницу 1, 2, 3, 4, 5 ... 8  След.
Автор Сообщение
СообщениеДобавлено: 22 апр 2013, 20:30 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
На основе теоретических изысканий попробуем заставить транслятор Ofront генерировать "незацикливающийся FOR". Сразу оговорюсь, что я не являюсь большим специалистом по этому транслятору, поэтому сделанные мной исправления скорее всего несовершенны. Буду рад, если кто-то более сведущий меня поправит.

Как уже заметили, Ofront построен на основе "OP2: A PORTABLE OBERON–2 COMPILER", так что перед выполнением каких-либо модификаций полезно перечитать документ по вышеприведенной ссылке.

Общий принцип работы Ofront: парсер ОРР читает при помощи сканера OPS исходный Оберон-текст и строит синтаксическое дерево. OPV проходит по этому дереву и генерирует С-код, вызывая нужные процедуры генерации из OPC и OPM. Что именно мы должны здесь исправить?

Вообще модификация «FOR» возможна тремя способами:
    1) Исправить OPP в том месте, где строится дерево для FOR. Т.е. в дереве для «FOR n:=A TO B BY Step DO ...END» будут те операторы, какие нам нужно, как будто именно они и были написаны в исходном тексте.
    2) Исправить OPV, чтобы для FOR генерировался такой код на С, какой нам надо.
    3) Добавить модификацию дерева между OPP и OPV.
3-й путь вряд ли подходит, т. к. он достаточно трудоёмок (нужно идти по всему дереву, искать FOR, его заменять на что-то). Этот способ следует использовать, если нужно исправление, затрагивающее всё дерево в целом; если нельзя исправить что-то «локально, по месту», а нужно сделать в одном месте модификацию, которая зависит от куска в другом месте или от какой-то статистики по всему дереву (например, какие-то глобальные оптимизации).
Способ 2 подойдет, если OPP вставляет в дерево уникальный код для FOR и мы сможем потом понять, что это именно конструкция FOR. Поэтому сперва поищем в OPP построение дерева для оператора FOR.

После недолгих поисков находим в процедуре OPP.StatSeq нужный кусок кода. Я не вник во все тонкости, но добавлю комментарии о том, что удалось понять (в исходниках Ofront комментариев мало, а русских комментариев и вовсе нет, так что все такие комментарии - мои).
Код: "OBERON"
  1. PROCEDURE StatSeq(VAR stat: OPT.Node);
  2. VAR fpar, id, t, obj: OPT.Object; idtyp: OPT.Struct; e: BOOLEAN;
  3. s, x, y, z, apar, last, lastif: OPT.Node; pos: INTEGER; name: OPS.Name;
  4. ELSIF sym = for THEN (* построение синтаксического дерева для FOR *)
  5. OPS.Get(sym); (* взять символ после FOR *)
  6. IF sym = ident THEN qualident(id); (* если это идентификатор, уточнить его *)
  7. IF ~(id^.typ^.form IN intSet) THEN err(68) END ; (* он должен быть целого типа*)
  8. CheckSym(becomes); Expression(y); pos := OPM.errpos; (* потом д б «:= выражение (А)» *)
  9. x := OPB.NewLeaf(id); OPB.Assign(x, y); SetPos(x); (* вставим в дерево «n := А» *)
  10. CheckSym(to); Expression(y); pos := OPM.errpos; (* далее д б ТО выражение (В) *)
  11. IF y^.class # Nconst THEN (* если В — не константа*)
  12. name := "@@"; OPT.Insert(name, t); t^.name := "@for"; (* то нужно создать *)
  13. t^.mode := Var; t^.typ := x^.left^.typ; (* в таблице имен *)
  14. obj := OPT.topScope^.scope; (*вспомогательную *)
  15. IF obj = NIL THEN OPT.topScope^.scope := t (* переменную для В*)
  16. ELSE
  17. WHILE obj^.link # NIL DO obj := obj^.link END ;
  18. obj^.link := t
  19. END ; (* и вставить в дерево код для присвоение ей выражения В*)
  20. z := OPB.NewLeaf(t); OPB.Assign(z, y); SetPos(z); OPB.Link(stat, last, z); (* vB := B *)
  21. y := OPB.NewLeaf(t) (* y = < vB > *)
  22. ELSIF (y^.typ^.form < SInt) OR (* B д б целой константой *)
  23. (y^.typ^.form > x^.left^.typ^.form) THEN err(113) (* совместимого типа *)
  24. END ; (* теперь y - это «выражение В» (константа или вспомогательная переменная) *)
  25. OPB.Link(stat, last, x); (* вот это непонятно … *)
  26. IF sym = by THEN (* если далее задан шаг BY Step,*)
  27. OPS.Get(sym); ConstExpression(z) (* то читаем его как константное выражение*)
  28. ELSE
  29. z := OPB.NewIntConst(1) (*иначе берем константу 1*)
  30. END ; (* z – шаг цикла (константа)*)
  31. pos := OPM.errpos; x := OPB.NewLeaf(id); (* подготовим условие *)
  32. IF z^.conval^.intval > 0 THEN OPB.Op(leq, x, y) (* n <= B либо*)
  33. ELSIF z^.conval^.intval < 0 THEN OPB.Op(geq, x, y) (* n >= B , *)
  34. ELSE err(63); OPB.Op(geq, x, y) (* в зависимости от знака Step*)
  35. END ; (* х — это условие «n <= B» или «n >= B» *)
  36. CheckSym(do); StatSeq(s); (* дальше д б DO и тело цикла s *)
  37. y := OPB.NewLeaf(id); OPB.StPar1(y, z, incfn); SetPos(y); (* строим y=«INC(n,Step)» *)
  38. IF s = NIL THEN s := y (* если тело цикла — пусто, то там д б только INC(n,Step) *)
  39. ELSE z := s; (* иначе ищем конец тела цикла *)
  40. WHILE z^.link # NIL DO z := z^.link END ;
  41. z^.link := y (* и добавляем туда инкремент INC(n,Step) *)
  42. END ;
  43. CheckSym(end); (* в конце д б END *)
  44. OPB.Construct(Nwhile, x, s) (* строим WHILE n<=B (n>=B) DO *)
  45. (* тело цикла; INC(n,Step) END *)
  46. ELSE err(ident) (* ошибка , если после FOR не идентификатор*)
  47. END
Как видим, OPP.StatSeq строит для FOR такое дерево, как будто в исходном тексте был написан фрагмент ”n:=A;WHILE n<=B DO...”, как это и должно быть по стандарту ЯП Оберон. Поэтому мы не сможем применить 2-й и 3-й способы модификации, мы просто не сможем отличить в дереве «форовский» WHILE от «настоящих» WHILE-ов. Значит придется действовать 1-м способом — исправить кусок кода, что приведен выше.

Однако заставить ОРР генерировать такой, или иной подобный фрагмент из этой ветки, слишком сложно, уж очень они непохожи на WHILE n<=B DO. На данном этапе освоения Ofront нужно попробовать что-то менее сложное, нужно реализовать фрагмент кода более похожий на исходный. От генерируемого ОРР он отличается только дополнительной строчкой
Код: "OBERON"
  1. IF инкремент дал переполнение THEN выйти из цикла END.
Добавить только одну эту строчку после инкремента гораздо легче, чем менять почти всё.

Но как обнаружить переполнение? Мог бы помочь контроль флагов процессора, но ни из Оберона, ни из С к ним нет прямого доступа. Использовать ассемблерные вставки? Можно, но 1) это тоже сложно, нужно разбираться , 2)появляется платформозависимость, которой нет на чистом С. Поэтому флаги пока отложим.

Будем проверять не после инкремента, а до — «если следующий инкремент даст переполнение, то выйти из цикла», как здесь.
На С это будет примерно так:
Код: "C"
if (n>n+Step) break; (для Step>0) 
if (n<n+Step) break; (для Step<0)
Итак, наша задача свелась к такой: в зависимости от знака Step вставить одну из этих строчек в дерево перед INC(n,Step).
Для этого нужно уметь вставлять в дерево «операции»: IF, сложение, сравнения и break. Можно посмотреть, конечно, как строить дерево для IF (это чуть выше в OPP.StatSeq) , потом поискать прочие операции, но можно пойти более простым путем. Будем считать всю строчку if (n>n+Step) break; одной специальной процедурой breakfor(n,Step). Вернее введем две «спецоперации»:
Код: "C"
breakforright(n,Step):   	if (n>n+Step) break; (для Step>0) 
breakforleft(n,Step): if (n<n+Step) break; (для Step<0)
соответственно для контроля переполнения на правой и левой границе. В дерево вставим вызов одной из этих процедур (аналог для инкремента OPB.StPar1(y, z, incfn) мы видели). А в модуль генерации OPV вставим расшифровку этой процедуры (тоже аналогично incfn).

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 24 апр 2013, 15:29 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Итак, мы решили создать специальные процедуры breakforright(n,Step) и breakforleft(n,Step), чтобы в зависимости от знака Step вставить в дерево вызов одной из них, как это сделано для инкремента OPB.StPar1(y, z, incfn). Вот и посмотрим сейчас эту StPar1:
Код: "OBERON"
  1. (* Обработка стандартных процедур (операторов, команд) с двумя аргументами, вроде INC(x, c) *)
  2. PROCEDURE StPar1*(VAR par0: OPT.Node; x: OPT.Node; fctno: BYTE); (* x: second parameter of standard proc *)
  3. VAR f, L: SHORTINT; typ: OPT.Struct; p, t: OPT.Node;
  4.  
  5. (* создает новый узел указанного класса *)
  6. PROCEDURE NewOp(class, subcl: BYTE; left, right: OPT.Node): OPT.Node;
  7. VAR node: OPT.Node;
  8. BEGIN
  9. node := OPT.NewNode(class); node^.subcl := subcl;
  10. node^.left := left; node^.right := right; RETURN node
  11. END NewOp;
  12.  
  13. BEGIN p := par0; f := x^.typ^.form; (* p – 1-й параметр, f – тип (код типа) 2-го параметра *)
  14. CASE fctno OF (* ветвление по коду процедуры *)
  15. incfn, decfn: (*INC DEC*)
  16. IF (x^.class = Ntype) OR (x^.class = Nproc) (* если 2-й параметр идентификатор типа или *)
  17. THEN (* процедура (имя процедуры, не вызов *) ,
  18. err(126); p^.typ := OPT.notyp (* то это недопустимо *)
  19. ELSE
  20. IF x^.typ # p^.typ THEN (* если параметры разных типов*)
  21. IF (x^.class = Nconst) & (f IN intSet) (* 2-й параметр — константа? *)
  22. THEN Convert(x, p^.typ) (* константа приводится к типу переменной*)
  23. ELSE err(111) (* иначе ошибка *)
  24. END
  25. END ;
  26. p := NewOp(Nassign, fctno, p, x); (* создадим новый узел INC или DEC класса ПРИСВАИВАНИЕ *)
  27. p^.typ := OPT.notyp (* типа не имеет, т. к. это не выражение, а оператор*)
  28. END
  29. ELSE err(64)
  30. END ;
  31. par0 := p (* возвратить результат в 1-м параметре *)
  32. END StPar1;
Прежде всего нам понадобятся новые константы для наших операций, смотрим в начало модуля ОРВ
Код: "OBERON"
  1. MODULE OfrontOPB; (* RC 6.3.89 / 21.2.94 *) (* object model 17.1.93 *)
  2. ...
  3. (*function number*)
  4. assign = 0;
  5. haltfn = 0; newfn = 1; absfn = 2; capfn = 3; ordfn = 4;
  6. entierfn = 5; oddfn = 6; minfn = 7; maxfn = 8; chrfn = 9;
  7. shortfn = 10; longfn = 11; sizefn = 12; incfn = 13; decfn = 14;
  8. inclfn = 15; exclfn = 16; lenfn = 17; copyfn = 18; ashfn = 19; assertfn = 32;
  9.  
  10. (*SYSTEM function number*)
  11. adrfn = 20; ccfn = 21; lshfn = 22; rotfn = 23;
  12. getfn = 24; putfn = 25; getrfn = 26; putrfn = 27;
  13. bitfn = 28; valfn = 29; sysnewfn = 30; movefn = 31;
Вроде бы, здесь должны быть все коды функций, потому что именно модуль ОРВ их обрабатывает. Но на всякий случай нумерацию новых начнем с 50. Добавим в конец списка констант (*function number*) еще 2 строчки
Код: "OBERON"
  1. breakforright = 50; (* прерывает FOR при выходе за правую границу целого типа*)
  2. breakforleft = 51; (* прерывает FOR при выходе за левую границу целого типа*)
Также сразу добавим эти строки и в аналогичное место модуля ОРР:
Код: "OBERON"
  1. MODULE OfrontOPP; (* NW, RC 6.3.89 / 10.2.94 *) (* object model 4.12.93 *)
  2. ...
  3. (*function number*)
  4. haltfn = 0; newfn = 1; incfn = 13; sysnewfn = 30;
  5. breakforright = 50; (* прерывает FOR при выходе за правую границу целого типа *)
  6. breakforleft = 51; (* прерывает FOR при выходе за левую границу целого типа *)
  7.  
Возвратимся в процедуру OPP.StatSeq. Мы должны где-то перед строчкой
Код: "OBERON"
  1. y := OPB.NewLeaf(id); OPB.StPar1(y, z, incfn); SetPos(y); (* строим y=«INC(n,Step)» *)
(или сразу после нее) построить узел для наших операций breakfor и соединить его с инкрементом, чтобы затем вставить это соединение в конец тела цикла.
Перед обработкой самого тела цикла ( CheckSym(do); StatSeq(s);) имеем
Код: "OBERON"
  1. «х — это условие «n <= B» или «n >=,
  2. z – шаг цикла (константа),
  3. переменные y и s пока свободны.»
По аналогии c инкрементом пишем
Код: "OBERON"
  1. y := OPB.NewLeaf(id); (* y – это переменная цикла n*)
  2. IF z^.conval^.intval > 0 THEN
  3. OPB.StPar1(y, z , breakforright); (* breakforright (n , Step) *)
  4. ELSE (* z^.conval^.intval < 0 *)
  5. OPB.StPar1(y, z , breakforleft); (* breakforleft (n , Step) *)
  6. END; (* y - это breakforХХХ (n , Step) *)
  7. SetPos(y);
Дальше нужно не обрабатывать тело цикла, а построить инкремент. Строчка кода для него есть готовая, но там используется переменная y, которая занята. Зато s пока свободна, перепишем построение инкремента:
Код: "OBERON"
  1. s := OPB.NewLeaf(id); OPB.StPar1(s, z, incfn); SetPos(s); (* строим s=«INC(n,Step)» *)
Теперь соединим эти две конструкции в одну:
Код: "OBERON"
  1. y^.link :=s; (* y = «breakforХХХ (n , Step); INC ( n , Step ) » *)
А дальше уже по тексту CheckSym(do); StatSeq(s); и т. д.
Код: "OBERON"
  1. y := OPB.NewLeaf(id); (* y – это переменная цикла n*)
  2. IF z^.conval^.intval > 0 THEN
  3. OPB.StPar1(y, z , breakforright); (* breakforright (n , Step) *)
  4. ELSE (* z^.conval^.intval < 0 *)
  5. OPB.StPar1(y, z , breakforleft); (* breakforleft (n , Step) *)
  6. END; (* y - это breakforХХХ (n , Step) *)
  7. SetPos(y);
  8. s := OPB.NewLeaf(id); OPB.StPar1(s, z, incfn); SetPos(s); (* строим s=«INC(n,Step)» *)
  9. y^.link :=s; (* y = «breakforХХХ (n , Step); INC ( n , Step ) » *)
  10.  
  11. CheckSym(do); StatSeq(s);
  12. (* y := OPB.NewLeaf(id); OPB.StPar1(y, z, incfn); SetPos(y); - это уже построили *)
В модуле ОРР вроде бы всё. Только небольшое замечание про SetPos(y) и SetPos(s). Насколько я понял, эти процедуры создают ссылку от узла дерева к месту исходного текста. Это позволяет вставлять в нужное место текста маркер ошибки, которая произошла на данном узле. До модификации ошибка узла с инкрементом отсылала в конец тела цикла (перед END), сейчас ссылка приведет в конец заголовка (перед DO). Не думаю, что это очень существенно. Пока оставим как получилось.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 24 апр 2013, 15:45 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Перейдем теперь в модуль ОРВ. Мы уже вставили в начало этого модуля константы breakforright, breakforleft . Теперь займемся их обработкой в PROCEDURE StPar1. Создадим копию ветви CASE для incfn, decfn, заменив эти константы на breakforright, breakforleft. После этого изменим контроль типов, а в остальном ничего не изменится:
Код: "OBERON"
  1. | breakforright, breakforleft: (*прерывает FOR при выходе за правую \левую границу целого тип*)
  2. IF x^.class = Nconst (* 2-й параметр должен быть константой*)
  3. THEN
  4. IF x^.typ # p^.typ THEN (* если параметры разных типов*)
  5. IF f IN intSet (* 2-й параметр — целая константа? *)
  6. THEN Convert(x, p^.typ) (* константа приводится к типу переменной*)
  7. ELSE err(111) (* иначе ошибка *)
  8. END;
  9. END;
  10. p := NewOp(Nassign, fctno, p, x); (* создадим новый узел для breakfor*)
  11. p^.typ := OPT.notyp (* типа не имеет, т. к. это не выражение, а оператор*)
  12. ELSE
  13. err(126); p^.typ := OPT.notyp (* не конcтанта недопустима *)
  14. END;
В строчке p := NewOp(Nassign, fctno, p, x); смущает класс узла Nassign. Конечно, это не присваивание, но что же это? По типу аргументов более всего походит именно на присваивание — список аргументов содержит переменную и константу (частный случай выражения). Заводить новый класс специально для breakfor не хочется, пусть пока будет (псевдо)«присваивание»!
Можно даже попробовать откомпилировать и запустить модифицированный Ofront.
Вставлю здесь замечание для новичков о работе с модулями BlackBox. Если вы в данном сеансе работы с BB уже пробовали запускать Ofront, то ввести в действие новый откомпилированный вариант OPP \OPB одним нажатием Ctrl-F12 не получится — получится «unloading OfrontOPP failed». Это происходит потому, что модули BlackBox взаимосвязаны: нельзя выгрузить модуль, который используется (импортируется) каким-то другим загруженным в данный момент модулем. Нужно или выйти из BlackBox и запустить его снова или выгрузить и модули Ofront и все их использующие. Для этого открываем список модулей (Info\Loaded Modules), выделяем в этом окне все модули OfrontXXX и находящийся выше XdevCmd, а затем всё выделенное выгружаем командой Dev\Unload Module List. Вот после этого Оfront для трансляции будет использовать уже модифицированные модули (если вы не забыли их откомпилировать Ctrl-K или Ctrl-F12).
Попробуем так сделать, чтобы оттранслировать, например, ZXDev.Unsigned. Получим
Код: "OBERON"
  1. ТRAP
  2. invalid CASE
  3. OfrontOPV.stat[00002E40H]
Щелкаем по ромбику в конце строки «OfrontOPV.stat [00002E40H]» и попадаем в то место модуля OPV, где нужно внести исправления!
Код: "OBERON"
  1. MODULE OfrontOPV; (* J. Templ 16.2.95 / 3.7.96 *)
  2.  
  3. CASE n^.class OF
  4. ...
  5. | Nassign:
  6. CASE n^.subcl OF
  7. ...
  8. | incfn, decfn:
  9. expr(n^.left, MinPrec); OPC.Increment(n^.subcl = decfn); expr(n^.right, MinPrec)
  10. | inclfn, exclfn:
  11. . . .
Ну конечно же, при обработке дерева OPV при помощи CASE n^.class OF выбирает фрагмент кода для обработки узлов данного класса, а внутри CASE n^.subcl OF уточняет эту обработку (в данном случае, код для какой именно процедуры нужно генерировать). А поскольку обработку «псевдоприсваиваний» breakforXXX мы еще не написали, то и CASE нужный вариант среди своих ветвей не нашел! Не нашел и вызвал TRAP, который был настолько любезен, что привел нас прямо в нужное место. Конечно, так не всегда бывает, чаще нас отсылают в место, где проявилась ошибка, находящаяся где-то еще. В таком случае полезно пройти по цепочке вызовов назад, двигаясь по строчкам в окне окне TRAP сверху вниз и щелкая ромбики в конце соответствующих строчек.
Но сейчас мы уже в нужном месте, просто продублируем ветку для инкремента\декремента, но заменим incfn, decf на новые ключевые слова:
Код: "OBERON"
  1. | breakforright, breakforleft: (*прерывает FOR при выходе за правую \левую границу целого тип*)
  2. expr(n^.left, MinPrec); OPC.Increment(n^.subcl = decfn); expr(n^.right, MinPrec)
И конечно вставим в начало модуля OPV определение новых констант (скопируем из модуля ОРР или ОРВ).
Для тестирования внесем еще небольшое исправление — заменим «n^.subcl = decfn» на «n^.subcl = incfn». Больше ничего пока не правим, а попробуем выгрузить модули Ofront, перекомпилировать OPV и снова запустить трансляцию для ZXDev.Unsigned.
Получим для нашего цикла конструкцию WHILE с оператором декремента перед инкрементом. Конечно такой цикл сразу зациклится, но не это важно, важно, что мы на верном пути!

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 24 апр 2013, 15:58 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Генерация кода для инкремента\декремента состоит из 3-х операций:
Код: "OBERON"
  1. expr(n^.left, MinPrec); (* обрабатывает левую ветвь, чтобы в код вставилась переменная *)
  2. OPC.Increment(n^.subcl = decfn); (* генерирует знак операции «+=» либо «-=» *)
  3. expr(n^.right, MinPrec) (* обрабатывает правую ветвь, чтобы в код вставилось выражение *)
Для операций
Код: "C"
breakforright(n,Step):   	if (n>n+Step) break; (для Step>0) 
breakforleft(n,Step): if (n<n+Step) break; (для Step<0)
необходимо сначала генерировать «префикс» “if (“, потом левую ветвь expr(n^.left, MinPrec); ,затем знак > или <, в зависимости от операции n^.subcl , затем опять expr(n^.left, MinPrec), знак + , правую ветвь expr(n^.right, MinPrec) (что даст Step), а потом «постфикс» “) break;”.
Процедура OPC.Increment устроена довольно просто
Код: "OBERON"
  1.  
  2. PROCEDURE Increment* (decrement: BOOLEAN);
  3. BEGIN
  4. IF decrement THEN OPM.WriteString(" -= "); ELSE OPM.WriteString(" += "); END;
  5. END Increment;
Было бы методически верно написать какую-нибудь аналогичную спецпроцедуру для генерации знака > или < , но не будем влезать еще и в ОРС. Все-таки процедуры breakforright\left весьма специфические и для применения где-то кроме как в FOR не предназначены. Поэтому просто применим OPM.WriteString прямо внутри ветви | breakforright, breakforleft: Получим
Код: "OBERON"
  1. | breakforright, breakforleft: (*прерывает FOR при выходе за правую \левую границу целого тип*)
  2. OPM.WriteString("if ( "); expr(n^.left, MinPrec); (* генерация ” if ( n “ *)
  3. IF n^.subcl = breakforright THEN OPM.WriteString(" > "); ELSE OPM.WriteString(" < "); END;
  4. expr(n^.left, MinPrec); OPM.WriteString(" + "); expr(n^.right, MinPrec) ;
  5. OPM.WriteString(" ) break") (* опытным путем установил, что « ; » после break не нужно *)
Готово! Теперь нужно тщательно протестировать. Тесты показали, что С-код генерируется верно, так для
Код: "OBERON"
  1. FOR byte := 32760 TO MAX(INTEGER) DO
  2. B.PRWORD(byte); B.PRCHAR(" ");
  3. END;
получим
Код: "C"
	Unsigned_byte = 32760;
while (Unsigned_byte <= 32767) {
Basic_PRWORD(Unsigned_byte);
Basic_PRCHAR(' ');
if ( Unsigned_byte > Unsigned_byte + 1 ) break;
Unsigned_byte += 1;
}
А для
Код: "OBERON"
  1. FOR byte := -32760 TO MIN(INTEGER) BY -10 DO
  2. B.PRWORD(byte); B.PRCHAR(" ");
  3. END;
генерируется
Код: "C"
	Unsigned_byte = -32760;
while (Unsigned_byte >= -32768) {
Basic_PRWORD(Unsigned_byte);
Basic_PRCHAR(' ');
if ( Unsigned_byte < Unsigned_byte + -10 ) break;
Unsigned_byte += -10;
}
Хотя Unsigned_byte + -10 выглядит странно и не оптимально, но работает верно. Легко, конечно, чтобы вместо "+ -" получалось просто "-" , но пока не будем. Понадеемся на компилятор С -> ассемблер.
Cтандартный тип BYTE нельзя использовать в качестве параметра цикла. Для типа SHORTINT код цикла на С конечно же не изменится, но при выполнении FOR byte := 100 TO MAX(SHORTINT) DO происходит зацикливание! Так же как для FOR byte := -120 TO MIN(SHORTINT) BY -1. Для SYSTEM.SHORTCARD тоже зацикливание, хотя предохранительная строчка с break исправно вставилась.

Видимо у Си и\или у SDCC свои понятия о приведении операндов сложения и сравнения. Будем выяснять это в следующий раз.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 24 апр 2013, 23:39 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Уважаемый Олег! Очень тотально ты приступил к делу, так что желаю тебе успехов! :)

Поскольку цикл FOR — это настоящая рабочая лошадка, а поэтому действительно заманчиво иметь очень эффективную реализацию, то не помешает рассмотреть ещё и такой вариант. По-моему, он хорошо ложится на внутреннюю структуру Ofront'а.

Итак, Ofront заводит внутреннюю переменную цикла _for__x, где x — некоторое число. Переменная нужна для того, чтобы вычислить B только единожды и хранить в ней.

Макет типичного цикла:
Код: "C"
{
INTEGER n, _for__x;
 
_for__x = конечное значение цикла;
n = начальное значение цикла;
while (n <= _for__x) {
 
/* Тело */
 
n += step; // Прирост шага
}
}
Это то, что генерирует Ofront. Вставлять дополнительную проверку на переполнение в тело цикла — это серьёзно замедлить цикл. Конечно можно вычислить возможность переполнения ещё в самом начале, и вот что я предлагаю. Мы оставим эту внутреннюю переменную, как и была, но будем хранить в ней не конечное значение цикла B, а вычисленное заранее количество повторений цикла. Алгоритм будет такой:

Вычислить количество повторений цикла. Если A и B — константы, то в compile-time.
Если чего-то из них не константа, то вычисляем кол-во повторений в присваивании переменной-счётчику _for__x.
Если цикл не повторяется ни разу или же повторяется один раз, учесть (можно отдать этот момент на откуп сишному компилеру).

Далее сгенерировать следующий код:

Код: "C"
	n = начальное значение цикла;
_for__x = количество повторений цикла;
if (_for__x) {
do {
 
/* Тело */
 
n += step; _for__x--;
 
} while (_for__x);
}
Если A и B — константы, то if даже генерировать не нужно. Тогда это чистый цикл с постусловием. Самый эффективный (за счёт минимизированного к одному перехода и близости уменьшения счётчика повторений к самой проверке, что разрешает оптимизацию). На Z80 это может выглядеть так:
Код: "ASM"
		LD HL, _for__x
DEC (HL)
JR NZ, LoopStart$
Или даже ещё более эффективно если переменная _for__x хранится в регистре.

Именно из-за этой конструкции я не предлагаю генерировать, например, это:
Код: "C"
	n = начальное значение цикла;
_for__x = количество повторений цикла;
while (_for__x--) {
 
/* Тело */
 
n += step;
 
}
Как мы понимаем, в первом варианте на каждую итерацию цикла переход всего один [...while (_for__x)]. Во втором же варианте их два [1) начало while (_for__x--); 2) конец while (_for__x--)], причём первый переход хоть и исполняется всего раз в конце цикла — неисполнение условия всё равно приводит к потере машинного времени, хоть и незначительно, но всё же снижая скорость работы цикла.

Но наверное второй вариант следует рассмотреть тоже.

Плюсы: условные и безусловные переходы дороги. Они имеют значительно более низкую эффективность даже в современных x86-64 — из-за ограниченного объёма кэша команд и возможного их распараллеливания рекомендуется применять линейные последовательности вычислений с минимумом переходов. Сложение не так дорого, но операции сравнения с нулём — очень дешёвы. В Z80 сравнение двух двухбайтовых чисел, со знаком или без, это целая эпопея с вычитанием (или сложением) регистровых пар и ёмкой засылкой значений из памяти в память. Если счётчик будет типа байт, сравнение его с нулём — одна из самых быстрых операций, которую можно проделать даже прямо в памяти, адресуя переменную через пару HL или индексные регистры, экономя использование других регистров. Если на нуль сравнивается регистровая пара — тоже не больно страшно, всего лишь загрузить регистр младшим значением и сделать OR со старшим. Вобщем, получается хорошо.

Что смущает. Ёмкость вычисления количества повторений цикла не в compile-time. Это как минимум взятие по модулю, но лучше всё-таки сделать такое вычисление один раз в начале цикла, чем n раз внутри него проверять на переполнение.

Трудности. Тип переменной-счётчика для Z80 оптимальнее всего будет задавать на основании вычисленного количества повторений (если известно точно, что их будет <= 255, берём байт. Если точно неизвестно, берём счётчик такого типа, чтобы хватило значений). Здесь неважно, что тип описывается как знаковый, в таком варианте использования он будет фактически без знака.

Ну а вот для Win32 лучше всего будет брать счётчик цикла всегда не меньше, чем INTEGER. И только для длинных циклов LONGINT. Эту проблему надо обдумать.

Идея. Можно даже 256 повторов хранить в одном байте, для этого нужно будет только лишь предусмотреть это в начале цикла. Надо чуть видоизменить код. Как — пока не придумал.

Оптимизация. Не заводить внутренний счётчик, если его значение будет совпадать с n.

Проблема. Предлагаемая реализация цикла нечувствительна к изменению счётчика цикла n внутри цикла. Он всегда будет повторяться единожды заданное при вхождении количество раз. Это может привести к неработоспособности тех программ, которые каждую итерацию цикла полагаются на WHILE n <= (Заранее вычисленное)B. Что, как ни крути, всё-таки жёстко задано в стандарте Оберона-2.

На самом деле я не считаю это серьёзной проблемой, потому что нечего жалеть построителей корявых циклов. Ведь если кому-то нужно чистое WHILE n <= B, то пусть так и пишет, и без всякого FOR.

Вобщем, такие вот мысли. Если я не сделал в рассуждениях явного ляпа, можно попробовать посчитать какой выигрыш будет для разного размера типов на Спектруме (в тактах), и какой — на Win32.


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 10 май 2013, 19:06 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
К сожалению дела заставили на некоторое время покинуть этот интересный форум :( Но сейчас есть возможность продолжить, будем надеяться, плодотворно. :)
Zorko писал(а):
Поскольку цикл FOR — это настоящая рабочая лошадка, а поэтому действительно заманчиво иметь очень эффективную реализацию…

Вставлять дополнительную проверку на переполнение в тело цикла — это серьёзно замедлить цикл. .
Согласен, проверять и переполнение и выход за пределы В на каждой итерации неэффективно. Более эффективные реализации цикла FOR (в т.ч. вычисление количества повторений) были рассмотрены ранее. Но на данном этапе мне было важно получить работающую модификацию Ofront, пусть и неэффективную. Было бы трудно делать совсем новую конструкцию, вроде описанных в теоретической ветке. А вот одну доп.строчку вставить удалось.
Теперь понятно: Ofront можно модифицировать, понятно более-менее что у него за что отвечает и как изменить, чтобы остальное не нарушить. Удалось успешно модифицировать - уже дорога проложена. Пусть сама реализованная конструкция цикла не слишком удачна, зато удачно прошла модификация Ofront. А вот дальше можно попробовать другую, более оптимальную конструкцию для цикла реализовать.

Цитата:
Вычислить количество повторений цикла.

Код: "C"
	n = начальное значение цикла;
_for__x = количество повторений цикла;
if (_for__x) {
do {
 
/* Тело */
 
n += step; _for__x--;
 
} while (_for__x);
}
Что же, вычисление количества повторений – интересная реализация FOR, но во всех ли случаях она эффективна? Ведь инкремент переменной цикла мы должны делать по-прежнему. Зато теперь надо делать декремент вспомогательной переменной (на 1, но ведь раньше и вовсе не делали) и вычислять количество повторений (однократно, но в общем случае используя деление). Выигрываем только в сравнении - сравнение с нулем эффективнее сравнения с произвольным числом в памяти. Это сравнение происходит на всех итерациях, что даст выигрыш, если итераций много. Что значит «много», нужно еще оценить.
В каких пределах может быть количество повторений? Минимальное число повторений =0 (ни разу), максимальное будет при единичном шаге и равно MAX(T)-MIN(T)+1. Даже для беззнаковых чисел (MIN(T)=0) это значение в диапазон не влезает (MAX(T)-0+1> MAX(T)), оно на 1 больше. Как же выходит из положения в таком случаем, например, компилятор Дельфи? А он допускает переполнение и считает, что MAX(T)+1 это 0. И действительно, n:=0; REPEAT …DEC(n) UNTIL n=0; выполняется ровно MAX(T)+1 раз.
Но зато случай «ни разу» (n= «настоящему нулю») нужно обрабатывать отдельно, поэтому if (_for__x) в общем случае не пойдет, нужно вместо этого написать if (A<=B) …(или if (n<=B)…).

Цитата:
Идея. Можно даже 256 повторов хранить в одном байте, для этого нужно будет только лишь предусмотреть это в начале цикла. Надо чуть видоизменить код. Как — пока не придумал.
Видоизменить именно так, как написал выше (256=0).

Цитата:
Как мы понимаем, в первом варианте на каждую итерацию цикла переход всего один [...while (_for__x)]. Во втором же варианте их два [1) начало while (_for__x--); 2) конец while (_for__x--)], причём первый переход хоть и исполняется всего раз в конце цикла — неисполнение условия всё равно приводит к потере машинного времени, хоть и незначительно, но всё же снижая скорость работы цикла. Но наверное второй вариант следует рассмотреть тоже.
Да, REPEAT UNTIL в машинных кодах реализуется эффективнее, чем WHILE. Если конечно умный компилятор не заменяет автоматически WHILE P DO S; на GOTO label; REPEAT S; label: UNTIL ~P;
Цитата:
Что смущает. Ёмкость вычисления количества повторений цикла не в compile-time. Это как минимум взятие по модулю, но лучше всё-таки сделать такое вычисление один раз в начале цикла, чем n раз внутри него проверять на переполнение.
Небольшое уточнение: не взятие по модулю, а «деление нацело».
NumIters=(B-A) DIV Step + 1, при Step>0, A<=B.
В общем случае (если А,В – не константы, шаг не единичный и не степень двойки), операция деления для Z80 трудоемка. Типичная реализация деления для Z80 содержит цикл со сдвигами и сравнениями, но этот цикл выполняется столько раз, какова разрядность данных.Больше ли это, чем инкремент и сравнение с произвольным числом в цикле, который выполняется столько раз, чему равно частное? Видимо зависит от разрядности и частного. При небольшом шаге (что означает большое частное )деление эффективней, при большом - наоборот. Шаг задается константой, поэтому можно это оценить заранее, еще на этапе компиляции.
Цитата:
Трудности. Тип переменной-счётчика для Z80 оптимальнее всего будет задавать на основании вычисленного количества повторений (если известно точно, что их будет <= 255, берём байт. Если точно неизвестно, берём счётчик такого типа, чтобы хватило значений). Здесь неважно, что тип описывается как знаковый, в таком варианте использования он будет фактически без знака.
Верхнюю границу количества повторений можно оценить на этапе компиляции:
Цитата:
Если А не константа, берем А=MIN(T).
Если В не константа, берем В=MAX(T).
Вычисляем NumIters=(B-A) DIV Step + 1
Если получилось NumIters<=256, то можно использовать байтовый счетчик.

Цитата:
Оптимизация. Не заводить внутренний счётчик, если его значение будет совпадать с n.
Уточню. «Совпадать» означает единичный шаг ( ABS(Step)=1) и В=…Чему? Чему-то, с чем можно эффективно сравнить. Например, Step=-1,В=1, тогда сравнивать после декремента придется с нулем, что эффективно. Или для беззнаковых чисел Step=1,В=MAX(T), что тоже даст сравнение с нулем.
Кроме флага Z, у процессора Z80 есть еще V, что дает возможность эффективно использовать Step=1,В= 127 (Step=-1,В= 128). Для знаковых можно еще применить флаг S, что позволит эффективно реализовать случай Step=-1,В= 0. Жаль, что CY не меняется от инкремента\декремента.
Но это флаги процессора, а мы пока транслируем в С. Эффективно ли пройдет трансляция С->ассемблер?

Цитата:
Вобщем, такие вот мысли. Если я не сделал в рассуждениях явного ляпа, можно попробовать посчитать какой выигрыш будет для разного размера типов на Спектруме (в тактах), и какой — на Win32
Олег, спасибо за мысли. Думаю, на практике чаще будет встречаться единичный шаг (деление совсем не нужно) или шаг равный степени 2 (деление сводится к сдвигу). Ну и чаще наверно будет встречаться небольшой шаг, чем большой, т.е. число итераций будет велико, а значит выигрыш от быстрого сравнения тоже будет велик. Так что, эта реализация в большинстве случаев будет эффективной.
Вот только жаль, что уж очень сильно этот способ реализации отличается от стандартной схемы FOR. Значит придется очень сильно модифицировать Ofront, для чего придется получше его изучить.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 12 май 2013, 16:27 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Удалось модифицировать Ofront для реализации FOR через вычисление количества повторений. Пока реализовал черновой вариант, без всяких оптимизаций и учета особых случаев. Но вариант вполне рабочий, даже оптимальней чем я ожидал. Вернее в чем-то оптимальней, а в чем-то наоборот :(
Код: "OBERON"
  1. MODULE Unsigned;
  2. IMPORT SYSTEM, P:=Platform, B := Basic;
  3. TYPE
  4. BYTE = SYSTEM.SHORTCARD;
  5. VAR
  6. byte: BYTE; (* 0..255 *)
  7. BEGIN
  8. FOR byte := P.Unsigned(255) TO P.Unsigned(0) BY -3 DO
  9. B.PRWORD(byte); B.PRCHAR(" ");
  10. END;
  11. END Unsigned.
Код: "C"
/*  Ofront 1.2 -xtspkae */
#include "SYSTEM.h"
#include "Basic.h"
#include "Platform.h"
static SHORTCARD Unsigned_byte, Unsigned__for__1;
export void *Unsigned__init(void)
{
__DEFMOD;
__IMPORT(Basic);
__IMPORT(Platform);
__REGMOD("Unsigned", 0);
/* BEGIN */
Unsigned_byte = Platform_Unsigned(255);
Unsigned__for__1 = Platform_Unsigned(0);
if (Unsigned_byte >= Unsigned__for__1) {
Unsigned__for__1 = __DIV((int)(Unsigned_byte - Unsigned__for__1), 3) + 1;
do {
Basic_PRWORD(Unsigned_byte);
Basic_PRCHAR(' ');
Unsigned_byte += -3;
Unsigned__for__1 -= 1;
} while (!((int)Unsigned__for__1 == 0));
}
__ENDMOD;
}
Код: "OBERON"
  1. ;Unsigned.c:21: Unsigned_byte = Platform_Unsigned(255);
  2. ld hl,#_Unsigned_byte + 0
  3. ld (hl), #0xFF
  4. ;Unsigned.c:23: Unsigned__for__1 = __DIV((int)(Unsigned_byte - Unsigned__for__1), 3) + 1;
  5. ld hl,#_Unsigned__for__1 + 0
  6. ld (hl), #0x56
  7. ;Unsigned.c:24: do {
  8. 00101$:
  9. ;Unsigned.c:25: Basic_PRWORD(Unsigned_byte);
  10. ld iy,#_Unsigned_byte
  11. ld l,0 (iy)
  12. ld h,#0x00
  13. push hl
  14. call _Basic_PRWORD_ROM
  15. ;Unsigned.c:26: Basic_PRCHAR(' ');
  16. ld h,#0x20
  17. ex (sp),hl
  18. inc sp
  19. call _Basic_PRCHAR_ROM
  20. inc sp
  21. ;Unsigned.c:27: Unsigned_byte += -3;
  22. ld a,(#_Unsigned_byte + 0)
  23. ld hl,#_Unsigned_byte
  24. add a, #0xFD
  25. ld (hl),a
  26. ;Unsigned.c:28: Unsigned__for__1 -= 1;
  27. ld hl, #_Unsigned__for__1+0
  28. dec (hl)
  29. ;Unsigned.c:29: } while (!((int)Unsigned__for__1 == 0));
  30. ld hl,#_Unsigned__for__1 + 0
  31. ld h, (hl)
  32. ld l,#0x00
  33. ld a,l
  34. or a,h
  35. jr NZ,00101$
  36. ret
Видим, что произошло вычисление константного выражения при делении и отбрасывание IF с заведомо истиным условием. Неплохо также сделан инкремент байтовых переменных.
А вот сравнение (!((int)Unsigned__for__1 == 0)) оставляет желать лучшего :( Виновато ли в этом приведение к типу int? Проверим - уберем приведение (int) в С-файле и запустим SDCC (для этого просто запустим Unsigned.bat). Да, получилось лучше:
Код: "OBERON"
  1. ;Unsigned.c:29: } while (!(Unsigned__for__1 == 0));
  2. ld a,(#_Unsigned__for__1 + 0)
  3. or a, a
  4. jr NZ,00101$
Причина видимо в том, что по стандарту Си байтовые величины должны сначала приводится к int, с чем уже столкнулся разработчик LLVM Backend для Z80. Регулировать это сложно, потому что Ofront об этом похоже не заботится (а о чем заботится, если это стандарт?), а беззнаковые числа в ZXDev пока "прикручены скотчем сверху". Но с этим что-то нужно делать. Ведь наша цель заключается не в соблюдении стандартов Си любой ценой, программа на Си - лишь промежуточный этап при создании программ на Обероне.

Надеюсь, скоро выделю время, чтобы описать эту модификацию Ofront более подробно (хотя, наверно, и не так подробно как в первых постах этой ветки).

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 12 май 2013, 23:05 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
Молоток! Прекрасная работа, получены интересные результаты.

Saferoll писал(а):
А вот сравнение (!((int)Unsigned__for__1 == 0)) оставляет желать лучшего :(
...
Причина видимо в том, что по стандарту Си байтовые величины должны сначала приводится к int, с чем уже столкнулся разработчик LLVM Backend для Z80. Регулировать это сложно, потому что Ofront об этом похоже не заботится (а о чем заботится, если это стандарт?), а беззнаковые числа в ZXDev пока "прикручены скотчем сверху". Но с этим что-то нужно делать. Ведь наша цель заключается не в соблюдении стандартов Си любой ценой, программа на Си — лишь промежуточный этап при создании программ на Обероне.
Я уверен, что на Си можно действовать и в рамках других типов. Например, пишем: short int a = 120 + 120 (знаковый байт) и вполне закономерно огребаем переполнение, и без предупреждения со стороны компилятора. КП приводит все короткие вычисления к типу INTEGER, и наверное это разумно (чтобы потом присвоить короткому — надо указать явное SHORT()). А вот Оберон и Оберон-2, по-моему, имеют стратегию такую же, как и в Си. Судя по Ofront'у и OPCL. :)

Ну а почему Ofront кастует? Когда сравниваем n типа SHORTINT с константой — у него хватает ума проверить и понять, что константа влазит в тип SHORTINT. А вот когда тип "неизвестен" (прикручен скотчем, как ты верно заметил), то логично перед сравнением преобразовать его в базовый целый тип (int). Ведь даже Си, если я не ошибаюсь, не даст без ворчания (warning) сравнивать знаковые и беззнаковые переменные. Впрочем, надо проверить это.

Проверил. Да, не даёт.

Код: "C"
static void Prog_InitGame (void)
{
SHORTINT i; SHORTCARD c;
i = 120; c = 250;
if (i <= c) {
}
}
Prog.c:29: warning 185: comparison of 'signed char' with 'unsigned char' requires promotion to int
Prog.c:29: warning 110: conditional flow changed by optimizer: so said EVELYN the modified DOG

Обращаю твоё внимание, что это только варнинги, прога собирается. Однако даже если это будет работать — всё равно как-то не очень радостно получается.

Надо покумекать в сторону оптимизации. Уверен, что она хотя бы в некоторых случаях возможна (например, если первое сравниваемое имеет тип SHORTCARD, а второе сравниваемое — константа, то приводить к int не нужно). Кстати, замени тип счётчика цикла в своём примере на SHORTINT (с корректировкой 255 на 127, разумеется) и приятно удивишься более компактному коду. :)


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 13 май 2013, 12:10 
Не в сети
Администратор
Аватара пользователя

Сообщения: 273
Откуда: Россия
Как и обещал, привожу более подробные сведения о модификации Ofront для реализации FOR через количество повторений. Т.к. в начале этой ветки был приведен подробный разбор подобной реализации, ограничусь приведением измененного фрагмента модуля OPP
Код: "OBERON"
  1. PROCEDURE StatSeq(VAR stat: OPT.Node);
  2. VAR fpar, id, t, obj: OPT.Object; idtyp: OPT.Struct; e: BOOLEAN;
  3. s, x, y, z, apar, last, lastif: OPT.Node; pos: INTEGER; name: OPS.Name;
  4. ...
  5. ELSIF sym = for THEN (* построение синтаксического дерева для FOR *)
  6. OPS.Get(sym); (* взять символ после FOR *)
  7. IF sym = ident THEN qualident(id); (* если это идентификатор, уточнить его *)
  8. IF ~(id^.typ^.form IN intSet) THEN err(68) END ;(* он должен быть целого типа*)
  9. CheckSym(becomes); Expression(y); pos := OPM.errpos; (* потом д б «:= выражение (А)» *)
  10. x := OPB.NewLeaf(id); OPB.Assign(x, y); SetPos(x);(* вставим в дерево «id := А» *)
  11. OPB.Link(stat, last, x); (* не очень понятно, похоже как-то связано с присваиванием*)
  12. CheckSym(to); Expression(y); pos := OPM.errpos; (* далее д б ТО выражение (В) *)
  13.  
  14. (* если В задается константой, то это д б константа совместимого типа *)
  15. IF (y^.class = Nconst) & ((y^.typ^.form < SInt) OR (y^.typ^.form > x^.left^.typ^.form))
  16. THEN err(113)
  17. END;
  18. name := "@@"; OPT.Insert(name, t); t^.name := "@for"; (* создадим *)
  19. t^.mode := Var; t^.typ := x^.left^.typ; (* в таблице имен *)
  20. obj := OPT.topScope^.scope; (*вспомогательную *)
  21. IF obj = NIL THEN OPT.topScope^.scope := t (* переменную t для счетчика повторений *)
  22. ELSE
  23. WHILE obj^.link # NIL DO obj := obj^.link END ;
  24. obj^.link := t
  25. END ;
  26. x:= OPB.NewLeaf(t); OPB.Assign(x, y); SetPos(x); (* t := B*)
  27. OPB.Link(stat, last, x);
  28. IF sym = by THEN (* если далее задан шаг BY Step,*)
  29. OPS.Get(sym); ConstExpression(z) (* то читаем его как константное выражение*)
  30. ELSE
  31. z := OPB.NewIntConst(1) (*иначе берем константу 1*)
  32. END ;
  33. pos := OPM.errpos;
  34. x := OPB.NewLeaf(id);y:=OPB.NewLeaf(t);
  35. (* z = "Step", x = "id", y = "t" *)
  36. IF z^.conval^.intval > 0 THEN (* подготовим выражение "кол-во повторений" *)
  37. OPB.Op(minus, y,x); (* B-x *)
  38. OPB.Op(div, y, OPB.NewIntConst( z^.conval^.intval)); (* (B-x) div Step *)
  39. OPB.Op(plus, y, OPB.NewIntConst(1)); (* (B-x) div Step +1 *)
  40. x := y;
  41. ELSIF z^.conval^.intval < 0 THEN (* в зависимости от знака Step*)
  42. OPB.Op(minus, x, y); (* x-B *)
  43. OPB.Op(div, x, OPB.NewIntConst( -z^.conval^.intval)); (* (x-B) div Step *)
  44. OPB.Op(plus, x, OPB.NewIntConst(1)); (* (x-B) div Step +1 *)
  45. ELSE err(63); OPB.Op(minus, x, y)
  46. END ;
  47. (* x = " (B-x) div Step +1" или "(x-В) div Step +1 " *)
  48. y := OPB.NewLeaf(t);OPB.Assign(y, x); SetPos(y); (* y = "t := (B-x) div Step +1 " *)
  49. x := OPB.NewLeaf(id); OPB.StPar1(x, z, incfn); SetPos(x); (* x= "INC(id,Step)"*)
  50. y^.link := x; (* t := (B-x) div Step +1; INC(id,Step) *)
  51. x := OPB.NewLeaf(t); OPB.StPar1(x, OPB.NewIntConst(1), decfn); SetPos(x);
  52. y^.link^.link := x; (* t := (B-x) div Step +1; INC(id,Step); DEC(t) *)
  53.  
  54. CheckSym(do); StatSeq(s); (* дальше д б DO и тело цикла s *)
  55. IF s = NIL THEN s := y^.link; (* если тело цикла — пусто, то там д б только INC(id,Step);DEC(t) *)
  56. ELSE x := s; (* иначе ищем конец тела цикла *)
  57. WHILE x^.link # NIL DO x := x^.link END ;
  58. x^.link := y^.link; (* и добавляем туда INC(n,Step); DEC(t)*)
  59. END ;
  60. CheckSym(end); (* в конце д б END *)
  61. x := OPB.NewLeaf(t); OPB.Op(eql, x, OPB.NewIntConst(0)); (* условие "t = 0" *)
  62. OPB.Construct(Nrepeat, s,x);SetPos(s); (* REPEAT ...UNTIL t=0;*)
  63. y^.link := s; (* t := (B-x) div Step +1; REPEAT ... UNTIL t=0; *)
  64. s := y;
  65. x := OPB.NewLeaf(id);y := OPB.NewLeaf(t);
  66. IF z^.conval^.intval > 0 THEN OPB.Op(leq, x, y) (* условие id <= B*)
  67. ELSIF z^.conval^.intval < 0 THEN OPB.Op(geq, x, y) (* или id >= B*)
  68. ELSE err(63); OPB.Op(geq, x, y)
  69. END ;
  70. OPB.Construct(Nif, x, s); SetPos(x); lastif := x; (* строим IF id<=B THEN ...*)
  71. OPB.Construct(Nifelse, x, NIL); (* ветви ELSE нет*)
  72. OPB.OptIf(x); pos := OPM.errpos
  73. ELSE err(ident)
  74. END

Как видите, изменений гораздо больше, чем в предыдущей реализации. Это и понятно, в предыдущей реализации нужно было вставить всего 1 строчку, здесь же кроме добавления новых операторов необходимо заменить WHILE на REPEAT UNTIL и заключить это в IF (не считая построения сложного выражения для вычисления количества итераций). Но зато изменения необходимо внести только в модуль OPP. Кстати, необходимо изменить еще 1 строку в начале модуля OPP - добавить константу для операции декремента (значение константы подсмотрим в OPB):
Код: "OBERON"
  1. (*function number*)
  2. haltfn = 0; newfn = 1; incfn = 13; decfn = 14; sysnewfn = 30;
С какими новыми трудностями пришлось столкнуться при данном изменении Ofront?
В самую первую очередь, я пытался заменить WHILE id<=B DO на IF id<=B THEN, потому что они весьма похожи (вообще, IF - это цикл WHILE который выполняется не более 1 раза). Однако заменить одну строку
Код: "OBERON"
  1. OPB.Construct(Nwhile, x, s)
на
Код: "OBERON"
  1. OPB.Construct(Nif, x, s); SetPos(x); lastif := x;
оказалось недостаточно. Потому, что IF может иметь еще ветви ELSE и ELSIF. Отсутствующие ELSIF специальной обработки не требуют, но для пустой ELSE нужно обязательно указать
Код: "OBERON"
  1. OPB.Construct(Nifelse, x, NIL);OPB.OptIf(x);

Много времени я потратил на проблему «Почему иногда меняется константа Step - значение шага, которое я доношу в переменной z почти до самого конца обработки?». Пришлось расставить ASSERT, чтобы найти место «порчи» - OPB.Op(div, y, z). Оказалось, что для деления на степень двойки Ofront заменяет операцию деления на сдвиг, а делитель, соответственно, на величину такого сдвига. Поэтому операции DIV нельзя отдавать саму константу, а нужно создать новую. После замены этой строки на OPB.Op(div, y, OPB.NewIntConst( z^.conval^.intval)); всё стало нормально.

Ofront становится понятнее, но остаются темные места. Я до сих пор не понял, что делает OPB.Link(stat, last, x); похоже это как-то связано с присваиванием.
Не совсем понятны операторы pos := OPM.errpos и SetPos(x). Понятно, что это создает связь между построенным деревом и исходным текстом Оберон-программы, что позволит потом вставить маркеры ошибок в нужные места. Но в тонкостях этого процесса я пока не разобрался.

_________________
А кроме того, я думаю, что корFORген должен быть разрушен!


Вернуться к началу
 Профиль  
Ответить с цитатой  
СообщениеДобавлено: 13 май 2013, 18:04 
Не в сети
Аватара пользователя

Сообщения: 1019
Откуда: Днепропетровская обл.
OPM.errpos — это позиция последней найденной ошибки в исходном тексте модуля. Описывается одним числом (когда я модифицировал OPCL для вывода строки/колонки возникшей ошибки — пришлось ввести вместо одного числа, обозначающего позицию, запись со строкой и колонкой. А может были и ещё доп. поля. Деталей уже не помню).


Вернуться к началу
 Профиль  
Ответить с цитатой  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 72 ]  На страницу 1, 2, 3, 4, 5 ... 8  След.

Часовой пояс: UTC + 2 часа


Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 5


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Перейти:  
cron
Создано на основе phpBB® Forum Software © phpBB Group
© VEDAsoft Oberon Club