Полагаю, вам нужна виртуальная машина, а не просто интерпретатор. Я думаю, что они две точки на континууме. Интерпретатор работает над чем-то близким к оригинальному представлению программы. ВМ работает на более примитивных (и автономных) инструкциях. Это означает, что вам нужен этап компиляции для перевода одного на другой. Я не знаю, хотите ли вы сначала поработать над этим или у вас еще есть вводный синтаксис.
Для динамического языка вам нужно где-то, где хранятся данные (как пары ключ / значение) и некоторые операции, которые воздействуют на него. ВМ поддерживает магазин. Программа, запущенная на нем, представляет собой последовательность инструкций (включая поток управления). Вам необходимо определить набор инструкций. Я бы предложил начать с простого набора, например:
- основные арифметические операции, включая арифметические сравнения, доступ к магазину
- базовый поток управления
- встроенная печать
Возможно, вы захотите использовать подход к вычислениям на основе стека, как это делают многие виртуальные машины. В вышесказанном пока не так много динамики. Чтобы достичь этого, нам нужны две вещи: способность вычислять имена переменных во время выполнения (это просто означает строковые операции) и некоторая обработка кода как данных. Это может быть так же просто, как разрешить ссылки на функции.
В идеале ввод в ВМ должен быть в байт-коде. Если у вас еще нет компилятора, он может быть сгенерирован из базового ассемблера (который может быть частью виртуальной машины).
Сама виртуальная машина состоит из цикла:
1. Look at the bytecode instruction pointed to by the instruction pointer.
2. Execute the instruction:
* If it's an arithmetic instruction, update the store accordingly.
* If it's control flow, perform the test (if there is one) and set the instruction pointer.
* If it's print, print a value from the store.
3. Advance the instruction pointer to the next instruction.
4. Repeat from 1.
Работа с именами вычисляемых переменных может быть сложной: инструкция должна указывать, в каких переменных находятся вычисляемые имена. Это можно сделать, разрешив инструкциям ссылаться на пул строковых констант, предоставленных во входных данных.
Пример программы (в сборке и байт-код):
offset bytecode (hex) source
0 01 05 0E // LOAD 5, .x
3 01 03 10 // .l1: LOAD 3, .y
6 02 0E 10 0E // ADD .x, .y, .x
10 03 0E // PRINT .x
12 04 03 // GOTO .l1
14 78 00 // .x: "x"
16 79 00 // .y: "y"
Предполагаемые коды инструкций:
"LOAD x, k" (01 x k) Load single byte x as an integer into variable named by string constant at offset k.
"ADD k1, k2, k3" (02 v1 v2 v3) Add two variables named by string constants k1 and k2 and put the sum in variable named by string constant k3.
"PRINT k" (03 k) Print variable named by string constant k.
"GOTO a" (04 a) Go to offset given by byte a.
Вам нужны варианты, когда переменные именуются другими переменными и т. Д. (И уровни косвенности сложно обдумать). Ассемблер просматривает аргументы типа «ADD .x, .y, .x» и генерирует правильный байт-код для добавления из строковых констант (а не вычисляемых переменных).