bic : AC intérprete y explorador de APIEste es un proyecto que permite a los desarrolladores explorar y probar C-API utilizando un bucle de impresión Eval Read, también conocido como Repl.

Las dependencias de tiempo de ejecución de BIC son las siguientes:
Para construir BIC, necesitarás:
Asegúrese de tenerlos instalados antes de construir BIC. El siguiente comando debe instalarlos en un sistema Debian/Ubuntu:
APT-GET Instale Build-Essential Libreadline-Dev Autoconf-Archive Libgmp-Dev Espere Flex Bison Automake M4 LBTOOL PKG-Config
También puede usar el siguiente comando para instalar las dependencias requeridas a través de Homebrew en un sistema MacOS.
Brew Instalar Bison Flex GMP Readline Autoconf-Archive
Puede compilar e instalar BIC con los siguientes comandos:
Autoreconf -i ./configure --enable DEBUG hacer hacer instalar
Para construir un sistema MacOS, debe cambiar la línea Configurar a:
Yacc = "$ (Brew - -Prefix Bison)/bin/bison -y" ./configure --enable -debug
Puede usar Docker para construir y ejecutar BIC con el siguiente comando:
Docker Build -t bic https://github.com/hexagonal-sun/bic.git#master
Una vez que se construye la imagen, puede ejecutar BIC con:
Docker Run -i Bic
Si está utilizando Arch Linux, puede instalar BIC desde AUR:
yay -s bic
Al invocar a BIC sin argumentos, se presenta al usuario un mensaje REPL:
Bic>
Aquí puede escribir declaraciones C y #include varios encabezados del sistema para proporcionar acceso a diferentes API en el sistema. Las declaraciones se pueden ingresar directamente en el repl; No es necesario definir una función para que sean evaluados. Digamos que deseamos ejecutar el siguiente programa C:
#include <stdio.h>
int main ()
{
FILE * f = fopen ( "out.txt" , "w" );
fputs ( "Hello, world!n" , f );
return 0 ;
}Podemos hacer esto en la replica con BIC usando los siguientes comandos:
Bic> #include <stdio.h>
Bic> archivo *f;
F
Bic> f = fopen ("test.txt", "w");
Bic> fputs ("¡Hola, mundo! N", f);
1
Bic>
Esto hará que BIC llame a las funciones C-Library fopen() y fputs() para crear un archivo y escribir la cadena Hello World en él. Si ahora sale de BIC, debería ver un archivo test.txt en el directorio de trabajo actual con la cadena Hello, Worldn contenida en él.
Observe que después de evaluar una expresión BIC imprimirá el resultado de la evaluación. Esto puede ser útil para probar expresiones simples:
Bic> 2 * 8 + fileno (f); 19
Puede usar BIC para obtener información sobre cualquier variable o tipo que haya sido declarado prefijo su nombre con A ? . Esta sintaxis especial solo funciona en el REPL, pero le permitirá obtener varias características sobre tipos y variables. Por ejemplo:
Bic> #include <stdio.h> Bic>? Stdout Stdout es un puntero a una estructura _io_file. El valor de STDOT es 0x7FF1325BC5C0. sizeof (stdout) = 8 bytes. Stdout fue declarado en: /usr/include/stdio.h:138.
Cuando comienza el replica, BIC verá si existe ~/.bic . Si lo hace, se evalúa automáticamente y el entorno resultante es utilizado por el repl. Esto puede ser útil para definir funciones o varibles que se usan comúnmente. Por ejemplo, digamos que nuestro archivo ~/.bic contiene:
#include <stdio.h>
int increment ( int a )
{
return a + 1 ;
}
puts ( "Good morning, Dave." );Cuando lanzamos el replico, obtenemos:
$ BIC Buenos días, Dave. Bic> incremento (2); 3
Si pasa BIC un archivo fuente, junto con -s , como argumento de línea de comando lo evaluará, llamando a una función main() . Por ejemplo, supongamos que tenemos el archivo test.c que contiene lo siguiente:
#include <stdio.h>
int factorial ( int n )
{
if (! n )
{
return 1 ;
}
return n * factorial ( n - 1 );
}
int main ()
{
printf ( "Factorial of 4 is: %dn" , factorial ( 4 ));
return 0 ;
} Luego podemos invocar BIC con -s test.c para evaluarlo:
$ bic -s test.c Factorial de 4 es: 24
Si desea aprobar argumentos a un archivo C, agregarlos a la línea de comando de BIC. Una vez que BIC ha procesado el argumento -s , todos los demás argumentos se tratan como parámetros que se pasarán al programa. Estos parámetros se crean como variables argc y argv y se pasan a main() . El valor de argv[0] es el nombre del archivo C que BIC está ejecutando. Considere el siguiente programa C:
#include <stdio.h>
int main ( int argc , char * argv [])
{
for ( int i = 0 ; i < argc ; i ++ )
printf ( "argv[%d] = %sn" , i , argv [ i ]);
return 0 ;
}Si no aprobamos ningún argumento:
$ bic -s test.c argv [0] = test.c
Mientras que si invocamos BIC con más argumentos, se pasan al programa:
$ bic -s test.c -a foo -s bar abc argv [0] = test.c argv [1] = -a argv [2] = foo argv [3] = -s argv [4] = bar argv [5] = a argv [6] = b argv [7] = c
También puede usar una expresión especial: <REPL>; En su código fuente para hacer que BIC lo envíe en el Repl en un punto particular de la evaluación del archivo:

Puede usar BIC para explorar las API de otras bibliotecas distintas de LIBC. Supongamos que deseamos explorar la biblioteca Capstone, pasamos una opción -l para hacer que BIC cargue esa biblioteca cuando se inicia. Por ejemplo:

Observe que cuando BIC imprime un tipo de datos compuestos (una struct o un union ), muestra todos los nombres de miembros y sus valores correspondientes.
En el corazón de la implementación de BIC está el objeto tree . Estos son objetos genéricos que se pueden usar para representar un programa completo, así como el estado del evaluador actual. Se implementa en tree.h y tree.c Cada tipo de árbol se define en c.lang . El archivo c.lang es una especificación similar a LISP de:
T_ADD .Addition .tADD .LHS y RHS .El código para crear un objeto con el conjunto anterior de atributos sería:
( deftype T_ADD " Addition " " tADD "
( " LHS " " RHS " ))Una vez definido, podemos usar este objeto en nuestro código C de la siguiente manera:
tree make_increment ( tree number )
{
tree add = tree_make ( T_ADD );
tADD_LHS ( add ) = number ;
tADD_RHS ( add ) = tree_make_const_int ( 1 );
return add ;
} Observe que se ha generado un conjunto de Macros de accesorios, tADD_LHS() y tADD_RHS() para que podamos acceder a las diferentes ranuras de propiedades. Cuando, --enable-debug se establece durante la compilación, cada una de estas macros se expande a una verificación para asegurarse de que al configurar la propiedad tADD_LHS de un objeto, el objeto es realmente una instancia de un T_ADD .
El archivo c.lang es leído por numerosos compiladores de fuente a fuente que generan fragmentos de código. Estas utilidades incluyen:
gentype : genera una lista de tipos de objetos de árbol.gentree : genera una estructura que contiene todos los datos de propiedad para objetos de árbol.genctypes : genera una lista de objetos de árbol de tipo C: estos representan los tipos de datos fundamentales en C.genaccess : genere macros de accesorios para propiedades del objeto de árbol.gengc : Genere una función de marca para cada objeto de árbol, esto permite que el recolector de basura atraviese árboles de objetos.gendump : Genere el código para descargar objetos de árbol de forma recursiva.gendot : Genere un archivo DOT para una jerarquía tree determinada, lo que permite que se visualice. La salida del Lexer & Parser es una jerarquía de objetos tree que luego se pasa al evaluador ( evaluator.c ). El evaluador evaluará recursivamente cada elemento de árbol, actualizando el estado del evaluador interno, ejecutando así un programa.
Las llamadas a las funciones externas al evaluador se manejan de manera dependiente de la plataforma. Actualmente, X86_64 y Aarch64 son las únicas plataformas compatibles y el código para manejar esto está en las carpetas x86_64 y aarch64 respectivamente. Esto funciona tomando un objeto tree de llamadas de función (representado por un T_FN_CALL ) del evaluador con todos los argumentos evaluados y organizándolos en una lista simple de vinculación. Esto se atraviesa en el ensamblaje para mover el valor al registro correcto de acuerdo con las conventiones de llamadas x86_64 o AARCH64 y luego ramificar a la dirección de función.
El analizador y el lexer se implementan en parser.m4 y lex.m4 respectivamente. Después de pasar por M4, la salida es dos analizadores de bisontes y dos lexers flexibles.
La razón de dos analizadores es que la gramática para una replica C es muy diferente a la de un archivo C. Por ejemplo, queremos que el usuario pueda escribir declaraciones para ser evaluados en el REPL sin la necesidad de envolverlos en una función. Desafortunadamente, escribir una declaración que está fuera de un cuerpo de funciones no es válida C. Como tal, no queremos que el usuario pueda escribir declaraciones desnudas en un archivo C. Para lograr esto, tenemos dos conjuntos diferentes de reglas de gramática que producen dos analizadores. La mayoría de las reglas de gramática se superponen y, por lo tanto, usamos un solo archivo M4 para ocuparse de las diferencias.