Executando verificação de segurança...
18
cyp
4 min de leitura ·

[PITCH] Criei um interpretador de LISP em C#

Olá à todos!

Há algum tempo eu venho trabalhando em um projeto chamado Motion, que é profundamente inspirado no LISP. Inicialmente o projeto foi feito por estudo, sem um motivo específico para existir isso, mas recentemente surgiu uma necessidade de processar arquivos .csv bem grandes, com milhões de dados, e não achava nada que podia me ajudar nisso.

Cada arquivo é uma coisa, preciso extrair uma certa informação e usar expressão regular não parecia a melhor solução. Até tentei, mas ficava algumas horas depurando problemas porque nem todas as listas seguiam o padrão que eu precisava. Eu cheguei a converter algumas para bancos de dados Sqlite mas tinha o tempo dessa conversão e o espaço em disco também.

Então pensei no projeto que estava parado, o Motion. Mas ele estava com um código muito feio no interpretador. Tinha muitos substring, indexof, e acredito que um tokenizador não pode ter gambiarras. Então comecei a fazer direito, criando um text interpreter, que lia um caractere por vez e ia incrementando a posição do buffer. Para cada caractere lido, onde ele "batia" é porque um token começou ou terminou.

Então escrevi o tokenizer, com 159 linhas no total. Tudo bem que no arquivo Token.cs tem algumas partes importantes do tokenizer, então eu separei algumas responsabilidades.

Após a compilação, é interpretado o que chamo de "compile-time", que roda os tokens que define funções, define constantes e tudo o que é necessário antes de rodar o código de verdade. Chamo essa etapa de "Modeler", pois modela como ficará o assembly compilado.

Então temos um assembly gerado com os tokens a partir do que foi lido em código, o que falta é rodar. O arquivo de interpretador possui um pouco mais de linhas, no total são 195 linhas. Mas ele quem faz o trabalho sujo.

Agora que disse um pouco do interpretador, vamos falar da linguagem em si.


O Motion

Como mencionado e assim como o LISP, o Motion foi feito para processar informação. A diferença, é que é totalmente integrado ao .NET por ser escrito nele. O código abaixo é um trecho de código em Motion, que obtém um valor de entrada e converte para um "Hello world".

(defun say-hello (name)
  "Diz hello world para o nome."
  (str:concat "Hello, " name "!"))

(write-line (say-hello @entrada))

pelo visto o TabNews não tem syntax highlighter para LISP

E posso chamar esse código com:

static void Main(string[] args)
{
    string code = File.ReadAllText("file.lsp");

    // cria uma instância do runtime e compila o código
    var runtime = new MotionRuntime.StandardLibrary();
    var module = MotionProvider.Compile(code, runtime.CreateRuntime());
    
    if (!module.Success)
    {
        DumpError(module.Error!);
    }

    try
    {
        // cria um contexto de execução e executa o código
        var context = module.CreateContext();
        context.Variables.Add("@entrada", "world");
        context.Evaluate();
    }
    catch (Exception ex)
    {
        DumpError(ex);
    }
}

Saída: Hello, world!

E o legal é que, module.CreateContext() não compila o código novamente. Eu posso usar o código compilado quantas vezes quiser, com quantos contextos. Então posso apontar para um arquivo e criar um contexto para cada linha que o Motion ler e trabalhar nela.

Eu reescrevi seu compilador em uma semana e meia, mas estou trabalhando no projeto há algum tempo. Também é capaz de apontar seus próprios métodos para dentro do Motion, pois o Runtime é você quem oferece ao compilador, contendo todas as funções que seu código irá usar.

[RuntimeMethod("write-line")]
public static EvaluationResult WriteLine(InvocationContext expression)
{
    // vê quantos argumentos tem
    expression.EnsureArgumentCount(1);
    
    // pega o primeiro valor, já interpretando ele
    object? result = expression.GetValue(0);
    
    // escreve no console
    System.Console.WriteLine(result);
    
    // como o método não retorna nada, ele retorna um Void
    return EvaluationResult.Void;
}

E usar no seu código com:

(write-line "Hello, world!")

Esse é o meu estudo. Espero que tenham gostado. Ele está suprindo uma necessidade atual e também colaborando com conhecimento de como funciona um compilador. Não sei dos planos com ele, mas não quero que fuja de ser uma linguagem funcional e simples, com um compilador menor que 5mb.

O projeto ainda está no protótipo, sequer existe uma especificação da linguagem. Sinceramente não sei se vai ter. Talvez tenha para que eu possa usar no futuro caso eu esqueça de como ela funciona.

O que você acha do Motion? Alguma sugestão? Melhoria? Crítica? Deixe aí.

Carregando publicação patrocinada...
1

Achei animal!

Eu mesmo gosto de brincar com interpretadores faz muito tempo kkkkkk
Uma dúvida, vc pretende mais pra frente não só interpretar mas também compilar pra outra linguagem?