По сути, это даже две идеи: разработать некий способ описания конструкции компьютера (и IDE, в котором можно это делать (можно даже взять это из MHRD)) (это не такая эзотерическая идея, она даже вполне практическая) и создать алгоритм автоматической реализации конструкции в Wireworld и Minecraft (это уже более эзотерическая идея
).
О, о, ну вы и неподъёмную (но интересную) задачу придумали!
Насчёт языка для описания конструкции возможно будут чем-то полезны языки типа VHDL, но я их не изучал. Этот язык насколько я помню более высокоуровневый и описывает больше преобразования сигналов, чем соединения конкретных элементов, и на таком скорее и больше захочется писать, а более конкретное представление получать уже как часть компиляции. А потом докомпилировать в совсем уж конкретные элементы — для майнкрафта конкретные блоки и для Wireworld отдельные клетки vs. некие абстрактные «это-то идёт оттуда туда». Впрочем вместо умножения разных видов представлений можно просто аннотировать самое первое высокоуровневое постепенно всё больше и больше, а потом отрезать всё лишнее и получить низкоуровневое на уровне расположения блоков.
Для того, чтобы определиться с самим высокоуровневым описанием, может наверно быть полезным вопрос, позволять ли данные неограниченной во время выполнения длины (как
int в питоне) или нет, и если нет, давать ли определять данные неограниченной, но известной до выполнения длины (хочу — выбираю
int32, а хочу —
int10005000). Если не допускать первого, преобразования должны будут довольно упроститься, а если и второго, то может ещё капельку, хотя то уже наверно перебор.
-- Сб окт 24, 2020 03:49:39 --Синтаксис этих операций должен быть примерно такой: PushOp(следующий_адрес, массив аргументов) (просто кладёт в стек массив вида [аргумент_1, аргумент_2, ...] (если это вызов функции, то первым аргументом идёт адрес, куда вернуться)) и PopOp(следующий_адрес, массив регистров) (достаёт массив из стека и перемещает аргументы в соответствующие регистры).
Исходно я думал, что будет браться по одному регистру за раз, но раз мы не особо ограничены в абстрактном представлении машины, то так наверно к лучшему!
Ладно, похоже, что без ReturnOp не обойтись (CallOp всё же не нужна).
Да я бы их обе оставил. Не очень понял как вы сэкономили на CallOp, надо будет перечитать…
Ммм… мне по-моему проще будет расписать, что я думал насчёт архитектуры, чем разобраться, что надо менять здесь, потому что я пропустил кучу изменений и в них долго разбираться.
Должно было быть так:
• Итого у нас есть стек данных, хранящий просто значения:
HFSet, и стек вызовов, который хранит кортежи (адрес возврата:
State, локальные регистры:
Dict[Register, HFSet]).
• Функция обращается всегда к своим локальным регистрам, смотря их в стек вызовов (и если вводить глобальные переменные, они будут смотреться в регистрах самой машины).
• ReturnOp() запоминает адрес возврата сверху стека вызовов, выкидывает верх этого стека и устанавливает следующим состоянием запомненный адрес. (А если стек пустой, как и раньше исключение.)
• CallOp(cs, rs) добавляет в стек вызовов кортеж (rs, пустой словарь) и ставит следующим состоянием cs.
• В самом начале выполнения машины мы как бы (да или так прямо и делаем) выполняем CallOp(начало главной функции, None). И когда главная функция выполнит ReturnOp(), следующим состоянием окажется None, машина станет его выполнять и благополучно завершится. Если главная функция не забыла свои результаты положить в стек данных, машине будет откуда их забрать. А, ну и конечно пусть она перед вызовом главной функции так же положит ей в стек данных аргументы.
Кажется, согласованно, проверяйте.