Supongo que quiere una máquina virtual en lugar de un mero intérprete. Creo que son dos puntos en un continuo. Un intérprete trabaja en algo cercano a la representación original del programa. Una máquina virtual funciona con instrucciones más primitivas (y autónomas). Esto significa que necesita una etapa de compilación para traducir el uno al otro. No sé si quieres trabajar en eso primero o si aún tienes una sintaxis de entrada en mente.
Para un lenguaje dinámico, desea un lugar que almacene datos (como pares clave/valor) y algunas operaciones que actúen sobre él. La VM mantiene la tienda. El programa que se ejecuta en él es una secuencia de instrucciones (incluido el flujo de control). Debes definir el conjunto de instrucciones. Yo sugeriría un conjunto sencillo para empezar, como:
- operaciones aritméticas básicas, incluyendo comparaciones aritméticas, acceder al almacén
- flujo de control básico
- incorporado en la impresión
Usted Puede que quiera usar un enfoque de cálculo basado en la pila para la aritmética, como lo hacen muchas máquinas virtuales. Todavía no hay mucha dinámica en lo anterior. Para llegar a eso queremos dos cosas: la capacidad de calcular los nombres de las variables en tiempo de ejecución (esto solo significa operaciones de cadena), y algún tratamiento del código como datos. Esto podría ser tan simple como permitir referencias de función.
La entrada a la máquina virtual sería idealmente en bytecode. Si aún no tiene un compilador, esto podría generarse a partir de un ensamblador básico (que podría ser parte de la VM).
La máquina virtual en sí consiste en el bucle:
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.
Tratar con los nombres de variables calculadas podría ser complicado: una instrucción necesita especificar qué variables los nombres calculados están en Esto se podría hacer al permitir que las instrucciones para hacer referencia. a un conjunto de constantes de cadena proporcionadas en la entrada.
un programa de ejemplo (en el montaje y código de bytes):
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"
Los códigos de instrucción implícita son:
"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.
Es necesario variantes para cuando las variables son nombrados por otras variables, etc. (y los niveles de indirección son difíciles de razonar). El ensamblador mira los argumentos como "ADD .x, .y, .x" y genera el bytecode correcto para agregar desde las constantes de cadena (y no las variables calculadas).
Si todavía está interesado, escribí una máquina virtual muy simple en C. Eche un vistazo: http://github.com/tekknolagi/carp – tekknolagi