bic : intérprete AC e API ExplorerEste é um projeto que permite aos desenvolvedores explorar e testar C-APIs usando um loop de impressão de leitura de leitura, também conhecido como repl.

As dependências de tempo de execução do BIC são as seguintes:
Para construir o BIC, você precisará:
Certifique -se de que você os instalem antes de construir o BIC. O comando a seguir deve instalá -los em um sistema Debian/Ubuntu:
instalação apt-get Build-essencial liberadline-dev autoconf-arckive libgmp-dev espera flex bison automake m4 libtool pkg-config
Você também pode usar o seguinte comando para instalar as dependências necessárias via homebrew em um sistema MacOS.
Brew Install Bison Flex GMP ReadLine AutoConf-Archive
Você pode compilar e instalar o BIC com os seguintes comandos:
autorECONF -I ./configure --enable-debug fazer faça instalar
Para construir em um sistema MacOS, você precisa alterar a linha de configuração para:
Yacc = "$ (cerveja -prefixo bisonte)/bin/bison -y" ./configure --enable -debug
Você pode usar o Docker para construir e executar o BIC com o seguinte comando:
Docker Build -t Bic https://github.com/hexagonal-sun/bic.git#master
Depois que a imagem estiver construída, você pode executar o BIC com:
Docker Run -I Bic
Se você estiver usando o Arch Linux, pode instalar o BIC da AUR:
yay -s Bic
Ao invocar o BIC sem argumentos, o usuário recebe um prompt Repl:
Bic>
Aqui você pode digitar instruções C e #include vários cabeçalhos do sistema para fornecer acesso a diferentes APIs no sistema. As declarações podem ser inseridas diretamente no REPL; Não há necessidade de definir uma função para que eles sejam avaliados. Digamos que desejamos executar o seguinte programa C:
#include <stdio.h>
int main ()
{
FILE * f = fopen ( "out.txt" , "w" );
fputs ( "Hello, world!n" , f );
return 0 ;
}Podemos fazer isso no Repl com o BIC usando os seguintes comandos:
Bic> #include <stdio.h>
BIC> arquivo *f;
f
Bic> f = fopen ("test.txt", "w");
Bic> fputs ("Olá, mundo! N", f);
1
Bic>
Isso fará com que o BIC chame para as funções fopen() e fputs() da biblioteca C para criar um arquivo e gravar a string hello world nele. Se você agora sair do BIC, verá um arquivo test.txt no diretório de trabalho atual com a string Hello, Worldn contido nele.
Observe que, depois de avaliar uma expressão, o BIC imprimirá o resultado da avaliação. Isso pode ser útil para testar expressões simples:
BIC> 2 * 8 + fileno (f); 19
Você pode usar o BIC para obter informações sobre qualquer variável ou tipo que tenha sido declarado prefixando seu nome com um ? . Essa sintaxe especial funciona apenas no REPL, mas permitirá que você obtenha várias características sobre tipos e variáveis. Por exemplo:
Bic> #include <stdio.h> Bic>? Stdout O stdout é um ponteiro para uma estrutura _io_file. O valor do stdout é 0x7FF1325BC5C0. sizeof (stdout) = 8 bytes. O stdout foi declarado em: /usr/include/stdio.h:138.
Quando o repl é iniciado, o BIC verá se existe ~/.bic . Se o fizer, é avaliado automaticamente e o ambiente resultante é usado pelo REPL. Isso pode ser útil para definir funções ou variáveis que são comumente usados. Por exemplo, digamos que nosso arquivo ~/.bic contém:
#include <stdio.h>
int increment ( int a )
{
return a + 1 ;
}
puts ( "Good morning, Dave." );Quando lançamos o REPL, obtemos:
$ BIC Bom dia, Dave. BIC> incremento (2); 3
Se você passar por um arquivo de origem BIC, juntamente com -s , como um argumento da linha de comando, ele o avaliará, chamando uma função main() . Por exemplo, suponha que tenhamos o test.c de arquivo.c que contém o seguinte:
#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 ;
} Podemos então invocar BIC com -s test.c para avaliá -lo:
$ bic -s test.c Fatorial of 4 é: 24
Se você deseja transmitir argumentos para um arquivo C, anexá -los à linha de comando da BIC. Depois que o BIC tiver processado o argumento -s , todos os outros argumentos são tratados como parâmetros a serem passados para o programa. Esses parâmetros são criados como variáveis argc e argv e passados para main() . O valor do argv[0] é o nome do arquivo C que o BIC está executando. Considere o seguinte 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 ;
}Se não passarmos nenhum argumento:
$ bic -s test.c argv [0] = test.c
Considerando que, se invocarmos BIC com mais argumentos, eles serão passados para o 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
Você também pode usar uma expressão especial: <REPL>; Em seu código -fonte para fazer o BIC deixar você no REPL em um ponto específico na avaliação de arquivos:

Você pode usar o BIC para explorar as APIs de outras bibliotecas além da libc. Suponhamos que desejemos explorar a biblioteca Capstone, passamos uma opção -l para fazer carga do BIC essa biblioteca quando ela iniciar. Por exemplo:

Observe que, quando o BIC imprime um tipo de dados composto (uma struct ou union ), ele mostra todos os nomes de membros e seus valores correspondentes.
No coração da implementação do BIC está o objeto tree . Esses são objetos genéricos que podem ser usados para representar um programa inteiro, bem como o estado atual do avaliador. É implementado em tree.h e tree.c . Cada tipo de árvore é definido em c.lang . O arquivo c.lang é uma especificação do tipo Lisp de:
T_ADD .Addition .tADD .LHS e RHS .O código para criar um objeto com o conjunto de atributos acima seria:
( deftype T_ADD " Addition " " tADD "
( " LHS " " RHS " ))Uma vez definido, podemos usar este objeto em nosso código C da seguinte maneira:
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 um conjunto de macros acessadoras, tADD_LHS() e tADD_RHS() , foram gerados para acessarmos os diferentes slots de propriedade. Quando --enable-debug é definido durante a compilação, cada uma dessas macros se expande para uma verificação para garantir que, ao definir a propriedade tADD_LHS de um objeto, o objeto seja realmente uma instância de um T_ADD .
O arquivo c.lang é lido por vários compiladores de origem a fonte que geram trechos de código. Esses utilitários incluem:
gentype : gera uma lista de tipos de objetos de árvore.gentree : gera uma estrutura que contém todos os dados da propriedade para objetos de árvore.genctypes : Gera uma lista de objetos de árvore do tipo C - eles representam os tipos de dados fundamentais em C.genaccess : Gere macros de acessadores para propriedades do objeto de árvore.gengc : Gere uma função de marca para cada objeto de árvore, isso permite que o coletor de lixo atravesse as árvores do objeto.gendump : Gere código para despejar objetos de árvore recursivamente.gendot : Gere um arquivo de pontos para uma determinada hierarquia tree , permitindo que ele seja visualizado. A saída do Lexer & Parser é uma hierarquia de objeto de tree que é passada para o avaliador ( evaluator.c ). O avaliador avaliará recursivamente cada elemento da árvore, atualizando o estado de avaliador interno, executando assim um programa.
As chamadas para funções externas ao avaliador são atendidas de maneira dependente da plataforma. Atualmente, x86_64 e AARCH64 são as únicas plataformas suportadas e o código para lidar com isso está nas pastas x86_64 e aarch64 , respectivamente. Isso funciona pegando um objeto tree de chamada de função (representado por um T_FN_CALL ) do avaliador com todos os argumentos avaliados e organizando-os em uma lista ligada simples. Isso é então atravessado na montagem para mover o valor para o registro correto de acordo com as conversões de chamada x86_64 ou aarch64 e depois se ramificar para o endereço da função.
O analisador e o Lexer são implementados no parser.m4 e lex.m4 , respectivamente. Depois de passar pelo M4, a saída é de dois analisadores de bisonte e dois Lexers Flex.
O motivo de dois analisadores é que a gramática para um repl é muito diferente do de um arquivo C. Por exemplo, queremos que o usuário possa digitar declarações a serem avaliadas no REPL sem a necessidade de envolvê -las em uma função. Infelizmente, escrevendo uma declaração que está fora de um corpo de função não é válida C. Como tal, não queremos que o usuário possa escrever instruções nuas em um arquivo C. Para conseguir isso, temos dois conjuntos diferentes de regras gramaticais que produzem dois analisadores. A maioria das regras gramaticais se sobrepõe e, portanto, usamos um único arquivo M4 para cuidar das diferenças.