Implementando O Parser

Um parser é em essência um componente que analisa e transforma dados em uma representação mais estruturada de tais dados. No nosso caso: vamos converter texto em uma pilha e executar operações que aparecerem. Nosso parser vai ler a entrada, token por token. Um token para nós, será ou um número fracionário, ou uma operação. Vamos definir a interface do nosso parser. Para representar o token, vamos definir uma união rotulada (ou união disjunta, ou tipo soma).

Arquivo parser.h

#ifndef PARSER_H
#define PARSER_H

#include "ops.h"

struct parser {
    char const *cursor;
};

enum token_kind {
    token_num,
    token_op,
    token_end,
    token_error
};

union token_data {
    enum operation op;
    double num;
};

struct token {
    union token_data data;
    enum token_kind kind;
};

struct token parse_token(struct parser *parser);

#endif

Vamos agora implementar. Uma função que pula espaços em branco, uma função que analisa o caso de um número, uma que analisa o caso de um operador, uma que atualiza o ponteiro se for o token for operador. Todas elas privadas. E, claro, a função que junta todas.

Arquivo parser.c:

#include "parser.h"
#include <string.h>
#include <stdlib.h>

static int is_whitespace(char ch)
{
    switch (ch) {
    case ' ':
    case '\n':
    case '\r':
    case '\t':
    case '\f':
    case '\v':
        return 1;
    default:
        return 0;
    }
}

static void skip_whitespace(struct parser *parser)
{
    while (is_whitespace(*parser->cursor)) {
        parser->cursor++;
    }
}

static int advance_op(struct parser *parser, char const *test, size_t test_n)
{
    int success = 0;
    int ch;

    size_t size = test_n - 1;

    if (strncmp(parser->cursor, test, size) == 0) {
        ch = parser->cursor[size];
        if (ch == 0 || is_whitespace(ch)) {
            success = 1;
            parser->cursor += size;
        }
    }

    return success;
}

static int parse_num(struct parser *parser, double *output)
{
    char *end;
    int success = 0;

    *output = strtod(parser->cursor, &end);

    if (end > parser->cursor) {
        success = 1;
        parser->cursor = end;
    }

    return success;
}

static int parse_op(struct parser *parser, enum operation *output)
{
    char const *end= parser->cursor;
    int success = 1;

    if (advance_op(parser, OP_ADD_SYM, sizeof(OP_ADD_SYM))) {
        *output = op_add;
    } else if (advance_op(parser, OP_SUB_SYM, sizeof(OP_SUB_SYM))) {
        *output = op_sub;
    } else if (advance_op(parser, OP_MUL_SYM, sizeof(OP_MUL_SYM))) {
        *output = op_mul;
    } else if (advance_op(parser, OP_DIV_SYM, sizeof(OP_DIV_SYM))) {
        *output = op_div;
    } else {
        success = 0;
    }

    return success;
}

struct token parse_token(struct parser *parser)
{
    struct token token;

    skip_whitespace(parser);

    if (*parser->cursor == 0) {
        token.kind = token_end;
    } else if (parse_num(parser, &token.data.num)) {
        token.kind = token_num;
    } else if (parse_op(parser, &token.data.op)) {
        token.kind = token_op;
    } else {
        token.kind = token_error;
    }

    return token;
}

Vamos ver se não ocorre algum erro de compilação (no Linux):

gcc -o parser.o -c parser.c

Perfeito, não ocorre.

Agora, vamos ao commit:

git add .
git status
git commit -m 'implementado o parser para operações básicas'
git push