Как позже оказалось.
Не, когда планировалось - все казалось не очень сложным...
Выполнение последнего действия откладывалось "на потом". Поэтому результатом разбора "была как бы формула" с одним оператором. И, соответственно, указывались типы аргументов. Аргументы могли быть: константа (она и указывалась), данные в памяти (указывался адрес, определяемый при компиляции), данные в стеке сопроцессора (которые являлись результатом вычисления, и именно для них имелся код).
Какие тут возникали проблемы ...
Самая первая, философская. Никаких внешних функций/подпрограмм вызвать нельзя, без полного сброса стека сопроцессора. Как бы, каждый программный модуль имеет права на ВЕСЬ стек сопроцессора (не такой уж и глубокий - 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 всего ОДЫН байт -- это же полный беспредел