(Este doctor es un trabajo en progreso ...)
Nota: La rama "CPP" contiene un puerto "tonto" de este proyecto de C a C ++, lo que hice por varias razones:
J2 es un sistema que combina un lenguaje de programación minimalista ("edicto", para "diccionario ejecutable") con un sistema de reflexión C/FFI. Le permite importar fácilmente bibliotecas compartidas y usar los tipos de datos, variables y funciones dentro de él (al menos las que están en el alcance global) directamente , sin necesidad de escribir ningún código de "pegamento".
Por ejemplo, si tengo un archivo "Inc.C" que contiene:
typedef struct mystruct { int i; float f; } mystruct;
extern mystruct increment(mystruct x) { x.i+=1; x.f+=1; return x; }
... y hago una biblioteca compartida:
gcc --shared -g inc.c -o inc.so
... Entonces puedo importar esa biblioteca compartida en el intérprete de edicto y hacer lo correcto a usarla:
e> loadlib([inc.so]) @mylib
Finished curating module
e> mylib<mystruct! <2@i 4@f> @x increment(x)>/ stack!
VMRES_STACK
<null>
structure_type "struct mystruct"
member "i"
base_type "int" 0x3 (0x7f361c0121a0)
member "f"
base_type "float" 5 (0x7f361c0121a4)
Parece simple, pero hay muchas cosas aquí debajo de las sábanas aquí ...
"Edict" es un lenguaje de programación minimalista que lo compara con su simplicidad al tener la capacidad incorporada para comprender y unir dinámicamente con las bibliotecas C, proporcionando acceso "nativo" a los tipos, variables y métodos de C, de complejidad arbitraria, sin escribir envolturas o código de pegamento. Alternativamente, puede verlo como una biblioteca de reflexión para los programas C que les permite exponer el acceso dinámico a sus propios internales en tiempo de ejecución.
El idioma se basa en una base de tres elementos:
Estos tres elementos se reúnen en tiempo de ejecución en un entorno programable multipropósito. El sistema se arremende por:
La evolución del edicto ha sido influenciada por Forth, Lisp, Joy, Factor, TCL, Mathematica y otros.
Puntos clave:
while (call=vm_dispatch(call)); // VM's inner loop
(Nota: Encontré esta estructura de "zigzag" que es muy similar (y es anterior) mi listree: https://www.nongnu.org/gzz/gi/gi.html)
El "Listree" es la estructura de datos centrales del edicto y la VM que lo implementa. Una instancia de un Listree consiste simplemente en un valor de Listree, que contiene:
La posibilidad de que un valor de Listree contenga (etiquetado) referencias a otros valores de Listree es lo que hace que el Listree sea una estructura de datos "jerárquica" o "recursiva". El Listree está en el núcleo del edicto y el sistema que lo implementa.
*No es explícito en esta simple explicación (y eso es a propósito), pero cada etiqueta en realidad se refiere a una lista de referencias a otros valores de Listree.
Como se mencionó, la VM mantiene todo su estado dentro de un valor de Listree, incluidos los sublistres de "pila de datos" y "diccionario", entre varios otros.
En edicto, hay dos tipos de valores de datos, pero los valores de ambos tipos se almacenan utilizando valores de Listree que existen en la pila de datos de la VM o dentro de su diccionario.
El tipo de valor más simple es un "literal". Los literales son solo texto, delineados por soportes cuadrados:
[This is a literal]
Si viene de un fondo Lisp, puede pensar que el intérprete descompone los literales en las expresiones S, pero este no es el caso. Todo entre los soportes está representado "literalmente" en el "búfer de datos" de un valor de Listree.
El intérprete de edicto realiza un seguimiento de los soportes cuadrados anidados, así que:
[This is a [nested] literal]
se interpreta como un solo literal con el valor "Este es un [anidado] literal".
Un "" personaje que aparece en la definición de un "escapa literal" del siguiente personaje, lo que permite que el intérprete cree literales que contengan (por ejemplo) soportes cuadrados desequilibrados:
[This is a literal containing an unbalanced [ bracket]
Cuando el intérprete se encuentra con uno, los valores literales (o más bien, las referencias al valor, una distinción importante) simplemente se colocan en la pila de datos.
OTOH, el intérprete procesa un poco más en cualquier cosa que no sea literal. (Hay otros tipos de valores que no son literales, que discutiré en una sección posterior ...)
El programador edicto puede asignar nombres a valores en la pila, y posteriormente se refiere a esos valores por esos nombres. La tarea se ve simplemente así:
@mylabel
La asignación de un nombre a un valor en realidad hace varias cosas:
Cuando el intérprete ve una referencia a una etiqueta, esa referencia se reemplaza por una referencia al valor asociado con esa etiqueta ... en otras palabras:
El edicto es diferente de otros idiomas de una manera importante. Muchos idiomas son "homoicónicos", es decir, el código y los datos se representan utilizando las mismas estructuras subyacentes. (Lisp es un ejemplo tradicional de un lenguaje homoicónico). El edicto no es ni homoicónico ni no homoicónico: no tiene "funciones" en absoluto. Simplemente tiene un operador de evaluación que se puede aplicar a los valores.
Una cosa básica de "función" en edicto podría parecer:
[1@x]
(Asigne la etiqueta "X" al valor "1".)
Tenga en cuenta que es solo un literal .
Para invocarlo , se utiliza el operador de evaluación :
[1@x]!
El resultado de esto es exactamente el mismo que si el intérprete acabara de leer directamente lo siguiente:
1@x
Todo lo que hace el operador de evaluación es alimentar el contenido de la parte superior de la pila al intérprete.
Ahora: recuerde que las etiquetas se pueden asignar a los valores, y que las etiquetas se pueden invocar para recordar sus valores, y esos valores se empujan a la pila, y que el operador de evaluación alimenta el contenido de la pila nuevamente en el intérprete:
[1@x]@f
f!
Esta pequeña secuencia hace lo siguiente:
EDICT puede importar tipos de biblioteca C e información de variables/funciones globales a través de la sección de depuración (enana) de una biblioteca, si está disponible. La información enana se procesa y almacena en el diccionario, y la VM puede "comprender" esta información y presentarla dentro del intérprete de edicto utilizando la misma sintaxis "nativa" simple que funciona con valores litentes.
int! @x
MyCGlobalInt @y
xy Multiply!
(Wip debajo ...)
ÁRBITRO | Referencia |
-ÁRBITRO | Referencia (cola) |
@ | Asignación a TOS |
@ÁRBITRO | Asignación a referencia |
/ | Liberar TOS |
/ÁRBITRO | Liberar Ref. |
^Ref. | Metareferencia |
Referencia^ | Fusionar la capa de pila superior a la referencia |
^Ref^(^ref +^) | Metareferencia y fusionar la capa de pila superior a la referencia |
¡! | Evaluar TOS |
Tos <...> | "Evaluar en DICT-Context": presione TOS a DICT, evalúe el contenido de <...>, Pop de la pila DICT a TOS. |
Tos (...) | "Evaluar en Code-Context": Push Null Stack/DICT capas, empuje TOS a la pila de códigos, evalúe el contenido de los padres, evalúe la pila superior de código, descarte la parte superior del dict, concatenate la parte superior de la pila a la capa anterior. |
Un programa reflexivo simple:
int! [3]@ square! stack!
Descomponer:
int | Buscar y empujar el valor de "int" (un tipo C nativo) en la pila |
! | Evalúe la primera piloto, en este caso asigne una instancia de un "INT" |
[3] | Empuje el literal "3" a la pila |
@ | Asignación; En este caso, la cadena "3" se coacciona automáticamente en una C "INT" |
square | Busque y empuje el valor de "cuadrado" (un método C nativo) en la pila |
! | Evalúe la parte superior de la pila, en este caso una llamada FFI al método C nativo "cuadrado" |
stack | Busque y empuje el valor de "pila" (un método C nativo) en la pila |
! | Evaluar la parte superior de la pila ... |
Verás int 0x9, es decir, 3 cuadrado.
La creación explícita y la asignación de un entero C se muestra solo para el ejemplo; Una versión más simple sería:
[3] square! stack!
La misma coerción se habría realizado automáticamente durante el ensarjamiento de los argumentos de FFI.
Tenga en cuenta que "código" no se evalúa automáticamente; La evaluación se invoca explícitamente a través de "!". El código es solo datos hasta que decida "ejecutarlo":
[3] square stack! | datos y "código" en pila, no evaluado |
! stack! | Evaluar TOS y observar resultados |
Factorial:
[@n int_iszero(n) 1 | int_mul(fact(int_dec(n)) n)]@fact
En un Listree, un valor contiene un diccionario de pares clave/CLL, donde cada CLL ("Lista circularmente vinculada", que implementa una cola de doble extremo) contiene una o más referencias a valores, cada una de las cuales contiene un diccionario de. .. Etcétera. El componente clave/DEQ de un Listree es una implementación BST basada en la variación "simple" de Rbtree de Arne Andersson. (http://user.it.uu.se/~arnea/ps/simp.pdf)
La VM es muy simple; Evalúa bytecodes, cada uno de los cuales es un índice en una matriz de métodos C que implementa el código de byto.
REINICIAR | Claro |
Extendido | Secuencia de caracteres de decodificación y establecer Lit ("[One Two 3]", "ABC") |
Ext_push | Puse iluminado en la pila de datos |
ÁRBITRO | Crear referencia de diccionario Ref de lit |
Desagüe | Resolver Ref en el diccionario |
ASIGNAR | Pop de la pila de datos e inserte en el diccionario en la ubicación ref ("@") |
ELIMINAR | Valor de liberación en REF |
Evaluación | Pense la parte superior de la pila de datos y a) llame a FFI, ob) empuje a la pila de código y obtenga VM |
Ctx_push | Ponga la parte superior de la pila de datos y empújalo al Jefe de la Pila del Diccionario, empuje la nueva capa a la pila de datos |
Ctx_pop | Fusione las dos capas superiores de la pila de datos, la pila de Pop Head of Dictionary y empuje la pila de datos |
Fun_push | Ponga la parte superior de la pila de datos y presione a FUNC Stack, agregue la capa a la pila de datos, agregue una capa nula al diccionario |
Fun_eval | Push {Fun_Pop} a la pila de código, establezca la parte superior de FUNC y haga "eval" |
Fun_pop | FUSE TOP TOP Capas de la pila, Deseche la capa del diccionario NULL |
TIRAR | Lanzar una excepción* |
ATRAPAR | Atrapa una excepción* |
*Las excepciones son cómo se implementan los errores y condicionales de VM, y juegan con el estado de la VM un poco más que la operación promedio, mereciendo su propio párrafo.
Este simple conjunto de operaciones es suficiente para interactuar con el diccionario, evaluar recursivamente las construcciones del lenguaje (la VM es una implementación sin pila) y, lo más importante, explotar tipos, funciones y datos. Es un marco básico que se basa en su acoplamiento apretado a C para extender sus capacidades sin esfuerzo.
(Cualquier distribución de GNU/Linux que valga la pena tiene estos:
Para ejecutar: suponiendo que GCC, CMake y las bibliotecas no son demasiado antiguos, simplemente ejecute "hacer" para construir y ingresar al repl.