Criando um compilador em csharp: Parte 7

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Criando um compilador em csharp: Parte 7

    Parte 7! Chegamos!


    Habemus for!


    E mais uma vez implementamos uma funcionalidade ao vivo! Gostei desse formato.







    Agora é possível escrever códigos como esse:






    for int i = 1 to 10 step 2
    print(1)
    end







    Porém, se você não assistiu, não tem problema: vou repassar o código de ponta a ponta para que você não perca nada.


    Então, vamos ao que viemos.


    Se você nos acompanha, já sabe que começamos sempre fazendo o mapeamento dos tokens:


    Pasta CodeAnalysis:






    public enum TokenType
    {
    ..
    For,
    To,
    Step,
    ...
    }

    public class Token
    {
    ...
    public const string FOR = "for";
    public const string TO = "to";
    public const string STEP = "step";
    ...
    public static Token For(int position)
    => new(TokenType.For, FOR, position);
    public static Token To(int position)
    => new(TokenType.To, TO, position);
    public static Token Step(int position)
    => new(TokenType.Step, STEP, position);
    }







    Basicamente, adicionamos novos itens de enum que mapeiam os novos tokens: for, to e step.


    Agora é extrair do código:






    public class Lexer
    {
    private Token ExtractKeyword()
    {
    ...

    if (identifier.Value == Token.FOR)
    return Token.For(position);
    if (identifier.Value == Token.TO)
    return Token.To(position);
    if (identifier.Value == Token.STEP)
    return Token.Step(position);

    return Token.Identifier(position, identifier.Value);
    }
    }







    Sem segredo e sem suar…


    Agora precisamos alterar nossa classe SyntaxParser. O mais interessante é que, após muitas implementações, temos basicamente um padrão a ser seguido para adicionar uma funcionalidade nova.


    Na live fiz questão de mostrar novamente como penso no momento de implementação de uma nova feature.


    Analise o código abaixo:






    for int i = 1 to 10 step 2
    print(1)
    end







    Para que eu “visualize” o fluxo de implementação, gosto de escrever o código dessa forma:






    $for [int i =] {1} to {10} $step {2} $end
    .......................................^.......... ..^
    |






    Explicando:
    • O que começa com $ são tokens que preciso validar a sua existência (caso seja obrigatório) e sua posição no código.
    • O que está entre colchetes é um evaluate interno, isto é, eu valido os tokens de declaração de variável e jogo na memória global (OK, não é a forma ideal, mas isso vai mudar num futuro) .
    • O que está entre { } é uma expressão e, sendo assim, vamos aplicar o EvaluateExpression. Dessa forma, podemos colocar qualquer expressão válida para calcularmos o valor.
    • O que está entre é um processo que vai ser executado inúmeras vezes, e para isso vamos invocar o método ParseBlock que por sua vez efetua um EvaluateToken caso o token atual não seja um token delimitador de bloco.


    Basicamente é isso. E caso você não tenha entendido o que é um token delimitador de bloco, explico: um bloco de código é aquele que começa com um determinado token e termina com end ou - no caso do if - else.






    if -> end
    if -> else -> end
    while -> end
    for -> end







    Sendo, assim, precisamos saber exatamente quando existiu uma abertura e um fechamento de bloco. Falei mais sobre isso no post anterior.


    Arquivo CodeAnalysis/SyntaxParser.cs:






    public class SyntaxParser(Dictionarystring, Identifier> variables, ListToken> tokens)
    {
    ...
    private Identifier EvaluateToken()
    => CurrentToken.Type switch
    {
    ...
    TokenType.For => EvaluateFor()
    ...
    };
    ...

    private Identifier EvaluateFor()
    {
    // Valido se o token é o for
    NextIfTokenIs(TokenType.For);

    /// Extraio a declaração de variável
    NextIfTokenIs(TokenType.DataType);
    var variableToken = NextIfTokenIs(TokenType.Identifier);
    var variableName = variableToken.Value;

    // Inicializo o valor da variável que dá início ao for como 1
    var from = new Identifier(DataTypes.Int, 1);
    // Caso exista uma atribuição na declaração de variável, o evaluate expression é invocado.
    if (CurrentToken.Type == TokenType.Assign)
    {
    NextIfTokenIs(TokenType.Assign);
    from = EvaluateExpression();
    }
    // Seto o valor do from na "memória"
    variables[variableName] = from;

    // Validamos e aavançamos até o to
    NextIfTokenIs(TokenType.To);
    // Obtemos o valor informado ao to a partir de uma expressão.
    var to = EvaluateExpression();
    var end = to.ToInt();

    // O step é opcional sendo seu valor inicial 1
    var stepValue = 1;
    // Caso o step seja informado, o seu valor é obtido a partir de uma expressão.
    if (CurrentToken.Type == TokenType.Step)
    {
    NextIfTokenIs(TokenType.Step);
    var step = EvaluateExpression();
    stepValue = step.ToInt();
    }

    // Após obter todos os dados referente a declaração do for, armazeno a posição atual do token. Isso serve para que o looping volte sempre para o lugar correto.
    var forPosition = _currentPosition;
    // efetuamos o looping (tanto crescente, quanto decrescente)
    while (
    (stepValue > 0 && variables[variableName].ToInt() end) ||
    (stepValue 0 && variables[variableName].ToInt() >= end)
    )
    {
    // Fazemos o parse do corpo do for
    ParseBlock();
    variables[variableName] = new Identifier(DataTypes.Int, variables[variableName].ToInt() + stepValue);
    _currentPosition = forPosition;
    }

    // Avançamos até sair do for
    SkipBlockUntil();

    // Validamos se o for termina com o end
    NextIfTokenIs(TokenType.End);
    return Identifier.None;
    }
    }







    Fiz questão de explicar trecho por trecho no próprio código e acredito que não tem segredo.


    O segredo é “visualizar” o trecho de código que será analisado, demarcando cada ponto. Dessa forma, fica muito fácil implementar cada parte já que temos praticamente tudo feito pelo fato de termos implementado if/else e while.


    É isso. Simples assim.


    Curtiu?


    Código-fonte está em https://github.com/angelobelchior/Pu...er/tree/parte7


    Muito obrigado e até a próxima!




    More...
Working...