428 lines
7.9 KiB
C
428 lines
7.9 KiB
C
/*
|
|
* Copyright (C) 2020 Weber Yann
|
|
*
|
|
* This file is part of pyrpn.
|
|
*
|
|
* pyrpn is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* any later version.
|
|
*
|
|
* pyrpn is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with pyrpn. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "rpn_parse.h"
|
|
|
|
/**@brief Macro for @ref rpn_ops member definition
|
|
* @param NAME @ref rpn_lib.h symbol
|
|
* @param s The short (1 char) symbol code
|
|
* @param l The long (char*) symbole code
|
|
*/
|
|
#define __op(NAME, s, l) {&NAME, &CODE_SZ(NAME), s, l}
|
|
const rpn_op_t rpn_ops[] = {\
|
|
__op(rpn_add, '+', "add"),\
|
|
__op(rpn_sub, '-', "sub"),\
|
|
__op(rpn_div, '/', "div"),\
|
|
__op(rpn_mul, '*', "mul"),\
|
|
__op(rpn_mod, '%', "mod"),\
|
|
__op(rpn_neg, '!', "neg"),\
|
|
__op(rpn_not, '~', "not"),\
|
|
__op(rpn_and, '&', "and"),\
|
|
__op(rpn_or, '|', "or"),\
|
|
__op(rpn_xor, '^', "xor"),\
|
|
__op(rpn_shr, 'r', ">>"),\
|
|
__op(rpn_shl, 'l', "<<"),\
|
|
__op(rpn_xchg, 'x', "xchg"),\
|
|
__op(rpn_dup, 'd', "dup"),\
|
|
__op(rpn_pop_op, 'p', "pop"),\
|
|
};
|
|
#undef __op
|
|
const size_t RPN_OP_SZ = (sizeof(rpn_ops) / sizeof(rpn_op_t));
|
|
|
|
int rpn_tokenizer_start(rpn_tokenizer_t *tokenizer, rpn_tokenized_t *dst,
|
|
const char* expr, size_t argc)
|
|
{
|
|
int err;
|
|
|
|
bzero(tokenizer, sizeof(rpn_tokenizer_t));
|
|
tokenizer->orig = expr;
|
|
|
|
tokenizer->toks = dst;
|
|
tokenizer->toks->argc = argc;
|
|
tokenizer->toks->tokens_sz = 0;
|
|
tokenizer->toks->tokens = NULL;
|
|
|
|
|
|
if(!(tokenizer->buff = strdup(expr)))
|
|
{
|
|
err = errno;
|
|
snprintf(tokenizer->err_reason, 64,
|
|
"Error duplicating expression for tokenization : %s",
|
|
strerror(err));
|
|
errno = err;
|
|
return -1;
|
|
}
|
|
tokenizer->cur = tokenizer->buff;
|
|
|
|
return 0;
|
|
}
|
|
|
|
rpn_token_t* rpn_tok(rpn_tokenizer_t *tokenizer)
|
|
{
|
|
char *token;
|
|
rpn_token_t ret, *tmp, *res;
|
|
int err;
|
|
|
|
token = tokenizer->cur;
|
|
|
|
if(!*token) // end of expression
|
|
{
|
|
rpn_tokenizer_free(tokenizer);
|
|
return NULL;
|
|
}
|
|
|
|
while(*(tokenizer->cur))
|
|
{
|
|
if(*(tokenizer->cur) == ' ' ||
|
|
*(tokenizer->cur) == '\n' ||
|
|
*(tokenizer->cur) == '\t')
|
|
{
|
|
break;
|
|
}
|
|
tokenizer->cur++;
|
|
tokenizer->chr_no++;
|
|
}
|
|
if(*(tokenizer->cur))
|
|
{
|
|
// not end, go on next chr for further rpn_tok calls
|
|
*(tokenizer->cur) = '\0';
|
|
tokenizer->cur++;
|
|
tokenizer->chr_no++;
|
|
}
|
|
|
|
// we have a clean token '\0' terminated
|
|
if(rpn_tokenize(token, &ret, tokenizer->err_reason) < 0)
|
|
{
|
|
errno = 0;
|
|
return NULL;
|
|
}
|
|
if(ret.type == RPN_arg && ret.arg_n >= tokenizer->toks->argc)
|
|
{
|
|
// invalid argument number
|
|
if(tokenizer->toks->argc)
|
|
{
|
|
snprintf(tokenizer->err_reason, 64,
|
|
"Argument number is too big : should be in [0..%ld] but \"%s\" found",
|
|
tokenizer->toks->argc - 1, token);
|
|
}
|
|
else
|
|
{
|
|
snprintf(tokenizer->err_reason, 64,
|
|
"No argument accepted but \"%s\" found",
|
|
token);
|
|
}
|
|
errno = EINVAL;
|
|
return NULL;
|
|
}
|
|
|
|
if(tokenizer->toks->tokens_sz >= tokenizer->allocated_toks)
|
|
{
|
|
tokenizer->allocated_toks += 8;
|
|
tmp = realloc(tokenizer->toks->tokens,
|
|
sizeof(rpn_token_t) * tokenizer->allocated_toks);
|
|
if(!tmp)
|
|
{
|
|
err = errno;
|
|
snprintf(tokenizer->err_reason, 64,
|
|
"Unable to realloc tokens list : %s",
|
|
strerror(err));
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
tokenizer->toks->tokens = tmp;
|
|
}
|
|
|
|
res = &(tokenizer->toks->tokens[tokenizer->toks->tokens_sz]);
|
|
*res = ret;
|
|
tokenizer->toks->tokens_sz++;
|
|
return res;
|
|
}
|
|
|
|
int rpn_tokenize(const char *token, rpn_token_t *dst, char error[64])
|
|
{
|
|
int rep;
|
|
unsigned long num;
|
|
const char *orig;
|
|
|
|
if((rep = rpn_match_token_i(token)) >= 0)
|
|
{
|
|
dst->type = RPN_op;
|
|
dst->op_n = rep;
|
|
dst->op = &(rpn_ops[rep]);
|
|
return 0;
|
|
}
|
|
|
|
orig = token;
|
|
|
|
if(*token == 'A')
|
|
{
|
|
if(!token[1])
|
|
{
|
|
snprintf(error, 64,
|
|
"Argument number is missing, lonely \"A \" found");
|
|
return -1;
|
|
}
|
|
dst->type = RPN_arg;
|
|
token++;
|
|
}
|
|
else
|
|
{
|
|
dst->type = RPN_val;
|
|
}
|
|
|
|
if(rpn_match_number(token, &num) < 0)
|
|
{
|
|
snprintf(error, 64,
|
|
"Invalid %snumber : \"%s\"",
|
|
dst->type == RPN_arg?"argument ":"constant ",
|
|
orig);
|
|
return -1;
|
|
}
|
|
|
|
if(dst->type == RPN_arg)
|
|
{
|
|
dst->arg_n = num;
|
|
}
|
|
else
|
|
{
|
|
dst->value = num;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void rpn_tokenizer_free(rpn_tokenizer_t *tokenizer)
|
|
{
|
|
if(tokenizer->buff)
|
|
{
|
|
free(tokenizer->buff);
|
|
bzero(tokenizer, sizeof(rpn_tokenizer_t));
|
|
}
|
|
}
|
|
|
|
char* rpn_tokenized_expr(const rpn_tokenized_t *tokens, char long_op)
|
|
{
|
|
size_t expr_sz, i;
|
|
int err, written;
|
|
char *cur, *tmp, *expr;
|
|
rpn_token_t *token;
|
|
int ALLOC_CHUNK=22; /* 64 bit uint is 20 decimal digit */
|
|
|
|
#ifdef DEBUG
|
|
if(!tokens)
|
|
{
|
|
dprintf(2, "Error, NULL ptr given to rpn_rokenize_expr");
|
|
err = EINVAL;
|
|
goto ret_err;
|
|
}
|
|
#endif
|
|
errno = 0;
|
|
err = 0;
|
|
expr_sz = 0;
|
|
expr_sz = 128;
|
|
cur = expr = malloc(sizeof(char)*expr_sz);
|
|
if(!expr)
|
|
{
|
|
err=errno;
|
|
dprintf(2,"Error allocating memory for expression : %s",
|
|
strerror(err));
|
|
goto ret_err;
|
|
}
|
|
for(i=0; i<tokens->tokens_sz; i++)
|
|
{
|
|
token = &(tokens->tokens[i]);
|
|
if((size_t)(cur - expr) >= (expr_sz - ALLOC_CHUNK))
|
|
{
|
|
expr_sz += 128;
|
|
tmp = realloc(expr, sizeof(char) * expr_sz);
|
|
if(!tmp)
|
|
{
|
|
err=errno;
|
|
dprintf(2,"Error allocating memory for expression : %s",
|
|
strerror(err));
|
|
goto free_err;
|
|
}
|
|
cur = tmp + (cur - expr);
|
|
expr = tmp;
|
|
}
|
|
switch(token->type)
|
|
{
|
|
case RPN_op:
|
|
if(!long_op)
|
|
{
|
|
*cur = token->op->chr;
|
|
cur[1] = ' ';
|
|
cur[2] = '\0';
|
|
written = 2;
|
|
}
|
|
else
|
|
{
|
|
written = snprintf(cur, ALLOC_CHUNK,
|
|
"%s ", token->op->str);
|
|
written--;
|
|
}
|
|
break;
|
|
case RPN_arg:
|
|
written = snprintf(cur, ALLOC_CHUNK,
|
|
"A%lu ", token->arg_n);
|
|
break;
|
|
case RPN_val:
|
|
written = snprintf(cur, ALLOC_CHUNK,
|
|
"0x%lX ", token->value);
|
|
break;
|
|
default:
|
|
#ifdef DEBUG
|
|
dprintf(2,
|
|
"Invalid token type encountered : %d\n",
|
|
token->type);
|
|
#endif
|
|
err = EUCLEAN;
|
|
goto free_err;
|
|
}
|
|
#ifdef DEBUG
|
|
if(written > ALLOC_CHUNK)
|
|
{
|
|
dprintf(2, "Expression too long : %s",
|
|
token->op->str);
|
|
err = EINVAL;
|
|
goto free_err;
|
|
}
|
|
#endif
|
|
cur += written;
|
|
}
|
|
|
|
if(cur > expr)
|
|
{
|
|
*(cur-1) = '\0';
|
|
}
|
|
return expr;
|
|
|
|
free_err:
|
|
free(expr);
|
|
ret_err:
|
|
errno = err;
|
|
return NULL;
|
|
}
|
|
|
|
const rpn_op_t* rpn_match_token(const char* token)
|
|
{
|
|
int rep;
|
|
if((rep = rpn_match_token_i(token)) < 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
return &(rpn_ops[rep]);
|
|
}
|
|
|
|
const rpn_op_t* rpn_op_from_opcode(unsigned char opcode)
|
|
{
|
|
if(opcode > RPN_OP_SZ)
|
|
{
|
|
return NULL;
|
|
}
|
|
return &(rpn_ops[opcode]);
|
|
}
|
|
|
|
int rpn_match_token_i(const char* token)
|
|
{
|
|
unsigned char i;
|
|
if(token[1] == '\0') //strlen(token) == 1
|
|
{
|
|
foreach_rpn_ops(i)
|
|
{
|
|
if(rpn_ops[i].chr == token[0])
|
|
{
|
|
return (int)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
foreach_rpn_ops(i)
|
|
{
|
|
if(!strncmp(rpn_ops[i].str, token, 8))
|
|
{
|
|
return (int)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int rpn_match_number(const char* token, unsigned long *result)
|
|
{
|
|
char *endptr;
|
|
unsigned long long res;
|
|
int base;
|
|
|
|
if(*token == '\0')
|
|
{
|
|
return -1;
|
|
}
|
|
base = 10;
|
|
if(*token == '0' && token[1] != '\0')
|
|
{
|
|
token++;
|
|
if(*token == 'x')
|
|
{
|
|
token++;
|
|
base = 16;
|
|
}
|
|
else if(*token == 'o')
|
|
{
|
|
token++;
|
|
base = 8;
|
|
}
|
|
else if(*token == 'b')
|
|
{
|
|
token++;
|
|
base = 2;
|
|
}
|
|
}
|
|
res = strtoull(token, &endptr, base);
|
|
if(*endptr != '\0')
|
|
{
|
|
return -1;
|
|
}
|
|
*result = res;
|
|
return 0;
|
|
}
|
|
|
|
int rpn_token_snprintf(rpn_token_t *tok, char *dst, size_t sz)
|
|
{
|
|
switch(tok->type)
|
|
{
|
|
case RPN_op:
|
|
if(tok->op->chr)
|
|
{
|
|
return snprintf(dst, sz, "%c", tok->op->chr);
|
|
}
|
|
return snprintf(dst, sz, "%s", tok->op->str);
|
|
case RPN_val:
|
|
return snprintf(dst, sz, "0x%lX", tok->value);
|
|
case RPN_arg:
|
|
return snprintf(dst, sz, "A%lu", tok->arg_n);
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
size_t rpn_op_sz()
|
|
{
|
|
return sizeof(rpn_ops)/sizeof(rpn_op_t);
|
|
}
|