Вверх ↑
Ответов: 9906
Рейтинг: 351
#1: 2014-06-23 11:18:32 ЛС | профиль | цитата
В FastMathParse проблема сия решалась кошмарным образом.
Как позже оказалось.
Не, когда планировалось - все казалось не очень сложным...

Выполнение последнего действия откладывалось "на потом". Поэтому результатом разбора "была как бы формула" с одним оператором. И, соответственно, указывались типы аргументов. Аргументы могли быть: константа (она и указывалась), данные в памяти (указывался адрес, определяемый при компиляции), данные в стеке сопроцессора (которые являлись результатом вычисления, и именно для них имелся код).

Какие тут возникали проблемы ...

Самая первая, философская. Никаких внешних функций/подпрограмм вызвать нельзя, без полного сброса стека сопроцессора. Как бы, каждый программный модуль имеет права на ВЕСЬ стек сопроцессора (не такой уж и глубокий - 16, кажется). Поэтому сразу идут лесом всякие массивы, матрицы, функции... А внешние данные/аргументы читаются только один раз, перед вычислением. А компилятор таки смекает (в смысле - флажки расставляет), какие надо читать, а какие - нет. Получается, что они все, как бы сразу -- в кэше.

Вторая проблема - конечный стек сопроцессора, а пользователя в длине формулы мы не ограничиваем. Возни на эту тему тоже хватало.

Третья проблема - большая вариантность. Ну прикинь: надо сложить (потому что, например, мы сейчас находимся в Level1b) <константа минус память> и <произведение память на стек>. В этом случае, для второго аргумента считается, что "потом" уже наступило - в его код добавляется команда произведения вершины стека на память. Плюс к нему (к коду) добавляется команда реверсного отнимания от вершины стека <памяти> из первого аргумента. И в результате объявляем нашим результатом -- <константа плюс стек>.
И т.д., и т.п..
Все эти варианты не страшные, просто их получилось до хрена. Столько, что пьяный ежик - хрен сломает

И для меня, если честно, в случае необходимости внесения существенных изменений (например, арктангенсы какие нибудь) в FastMathParse -- нужно не менее недели, чтобы разобраться со своим же кодом...
При этом, зная общую философию. Которую я излагал сейчас исключительно по памяти, не глядя в коды.


Вот

И, чего-то мне думается, что в случае MathParseEx - то же самое можно (и нужно) сделать гораздо проще.
Сам тот факт, что две первые вышеуказанные проблемы начисто отсутствуют - достойная причина для самого MathParseEx.
Как бы "переносимость" - немножко за уши притянуто. А вот отсутствие проблем масштабирования - аргумент объективный.

Мысли о реализации вышеупомянутого безобразия - есть у меня...
Расскажу о них. Через часик (перекур надо сделать)



------------ Дoбавленo в 10.17:
Значит так... Мысль примерно такая:
Assasin писал(а):
(потому что это вариант, требующий значительной переделки алгоритма компиляции)

Давай попробуем таки возвращать структуру, но так, чтобы это не требовало значительной переделки алгоритма компиляции.
Но давало возможность пошагового добавления неких проверок, и возможных модификаций кода по результатам этих проверок.
((типа, если не хочешь, или еще не придумал как - не делай, никто не заставляет))

Например, объявим:
#pas
type Titem = record adrConst:pchar; adrCmd:pchar; stackSize:integer; {может и еще чего потом придумается} end;
...
procedure Level0 (var item:Titem);
procedure Level1a(var item:Titem);
procedure Level1b(var item:Titem);
...
Как бы ничего особенного, пока мы не начинаем анализировать значение полей.
Вряд ли, это пока серьезные переделки. Грубо говоря, пока вообще никаких.

Вот, а теперь -- чего мы себе думаем про смысл этих полей...
А мы себе думаем, что код у нас ровно тот, что и сейчас, но если он получен с помощью константы, тогда первый адрес указывает на то место в "программе", где стоит CmdPush, а второй - на место, где этот "CmdPush" реализован. На последнюю команду, типа CmdAdd, в общем.
Код боевой, ничего "на потом" не отложено, его можно запускать, не обращая внимание ни на что.
А вот теперь, предположим, мы хотим прибавить еще константу.
Если у нас первый адрес не нулевой, а по второму адресу стоит CmdAdd, то кто нам мешает прибавить эту константу, в Compile-Time, по адресу после CmdPush.
Вроде бы никто. И это есть хорошо.

В чем мысль состоит то:
Занимаемся мы сложением в Level1b, скажем для формулы: (1 + %2 + (%1 - 2))/2
1) Получаем первым операндом {<адрес для CmdPush[1]>, nil, ... }. Это мы хотим, чтобы level6 именно так отреагировал на TokNumber. Почему бы и нет...
2) Получаем второй операнд (совместно с информацией о именно сложении) как {nil, <адрес для CmdRead>, ... }
3) Как и сейчас, притуляем в зад команду CmdAdd. Фиксируем в item адрес первой "константы", и адрес свежепритуленной команды. Кодинг пока, ровно тот же, что и сейчас.
4) Получаем третий операнд (совместно с информацией о именно сложении) как {<адрес для CmdPush[-2]>, <адрес для CmdAdd>, ... }
5) Если игнорировать содержимое tmpItem (это не наш аргумент, а локальная переменная, для возврата из других levelXXX), то ничего не остается, как притулить в зад CmdAdd. Что сегодня мы и делаем. А если подумать над содержимым tmpItem, то мы можем прибавить (-2) к ранее зафиксированной константе в item (по п.3 - и там получится -1), удалить из программы к чертовой матери CmdPush и CmdAdd для третьего опреранда. Ну и, естественно, притулить в зад свой собственный CmdAdd.
....
Как бы получается, что мы создаем программу, как и раньше. НО, иногда, вырезаем из нее лишнее (этого метода еще нет в TProgram), и модифицируем константы (как бы это и есть вычисления в Compile-Time), туда уже зашитые.

Подчеркну, это пока еще не опробованная мысль. Возможны некоторые "пеньки" по дороге.
Но, попробовать стоит.... Мне кажется.



------------ Дoбавленo в 11.18:
Да, я как бы по умолчанию предполагал, что сегодняшняя реализация TProgram - это уже за пределами добра и зла
С точки зрения эффективности, конечно

Надо делать примерно так:

#pas
type
PReal = ^real;
PInteger = ^integer;
....
THIMathParseEx = class(TDebug)
....
FPgm: array of byte;
Fpc: ^byte; // обычно PC - это Program Counter
FStack: array of real;
Fsp: Preal; // обычно SP - это Stack Pointer
.....

.....
.....

// в качестве примера для ExecuteExpressionPart
while true do begin
cmd := Fpc^; inc(Fpc);
if cmd=CmdEnd then exit; // конец программы
case cmd of
CmdPush: // [val:real] просто число
begin
inc(Fsp); // Обычно (но - обсуждаемо), стек указывает на последнее записанное число
Fsp^ := PReal(Fpc)^;
inc(Fpc,sizeof(real));
end;
CmdAdd, CmdSub, CmdMul, CmdDiv, CmdPow, CmdIDiv, CmdIMod:
begin
x2 := Fsp^; dec(Fsp);
case cmd of
CmdAdd: Fsp^ := Fsp^ + x2;
CmdSub: Fsp^ := Fsp^ - x2;
CmdMul: Fsp^ := Fsp^ * x2;
// Далее: про FPC пока не напрягаюсь, ибо это демка
CmdDiv: Fsp^ := Fsp^ / x2;
CmdIDiv: Fsp^ := Round(Fsp^) div Round(x2);
CmdIMod: Fsp^ := Round(Fsp^) mod Round(x2);
else Power(Fsp^, x2); // Это CmdPow (ибо он последний)
end;
end;
.....
.....
.....

Не думаю, что этот код настолько уж сложно прочитать Человеку Разумному.
НО, использовать два функциональных вызова, плюс мухобойный Move, чтобы прочитать в Run-Time всего ОДЫН байт -- это же полный беспредел
карма: 9

1
Голосовали:Assasin