[util] Add a mini expression parser

It is capable of parsing single expressions with fairly simple
operations. It current supports ints, enums, cvars and (external) data
structs. It is also thread-safe (in theory, needs proper testing) and
the memory it uses can be mass-freed.
This commit is contained in:
Bill Currie 2020-12-21 14:06:21 +09:00
parent 591667c36d
commit f3682638d4
8 changed files with 1185 additions and 0 deletions

118
include/QF/cexpr.h Normal file
View file

@ -0,0 +1,118 @@
/*
cexpr.h
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifndef __expr_h
#define __expr_h
#include <stdlib.h>
struct exprval_s;
struct exprctx_s;
typedef struct binop_s {
int op;
struct exprtype_s *other;
struct exprtype_s *result;
void (*func) (const struct exprval_s *val1,
const struct exprval_s *val2,
struct exprval_s *result,
struct exprctx_s *context);
} binop_t;
typedef struct unop_s {
int op;
struct exprtype_s *result;
void (*func) (const struct exprval_s *val, struct exprval_s *result,
struct exprctx_s *context);
} unop_t;
typedef struct exprtype_s {
const char *name;
size_t size;
binop_t *binops;
unop_t *unops;
void *data;
} exprtype_t;
typedef struct exprval_s {
exprtype_t *type;
void *value;
} exprval_t;
typedef struct exprsym_s {
const char *name;
exprtype_t *type;
void *value;
struct exprsym_s *next;
} exprsym_t;
typedef struct exprtab_s {
exprsym_t *symbols;
struct hashtab_s *tab;
} exprtab_t;
typedef struct exprctx_s {
exprval_t *result;
exprtab_t *symtab; // directly accessible symbols
exprtab_t *external_variables; // accessible via $id
struct memsuper_s *memsuper;
const struct plitem_s *item;
struct plitem_s *messages;
struct hashlink_s *hashlinks;
} exprctx_t;
typedef struct exprenum_s {
exprtype_t *type;
exprtab_t *symtab;
} exprenum_t;
int cexpr_parse_enum (exprenum_t *enm, const char *str,
const exprctx_t *context, void *data);
binop_t *cexpr_find_cast (exprtype_t *dst_type, exprtype_t *src_type) __attribute__((pure));
exprval_t *cexpr_value (exprtype_t *type, exprctx_t *ctx);
exprval_t *cexpr_value_reference (exprtype_t *type, void *data, exprctx_t *ctx);
int cexpr_eval_string (const char *str, exprctx_t *context);
void cexpr_error(exprctx_t *ctx, const char *fmt, ...) __attribute__((format(printf,2,3)));
void cexpr_struct_getfield (const exprval_t *a, const exprval_t *b,
exprval_t *c, exprctx_t *ctx);
exprval_t *cexpr_cvar (const char *name, exprctx_t *ctx);
exprval_t *cexpr_cvar_struct (exprctx_t *ctx);
void cexpr_init_symtab (exprtab_t *symtab, exprctx_t *ctx);
char *cexpr_yyget_text (void *scanner);
extern exprtype_t cexpr_int;
extern exprtype_t cexpr_uint;
extern exprtype_t cexpr_float;
extern exprtype_t cexpr_double;
extern exprtype_t cexpr_exprval;
extern exprtype_t cexpr_field;
extern binop_t cexpr_struct_binops[];
#endif

View file

@ -43,6 +43,10 @@ libs_util_libQFutil_la_SOURCES= \
libs/util/bspfile.c \
libs/util/buildnum.c \
libs/util/cbuf.c \
libs/util/cexpr-lex.l \
libs/util/cexpr-parse.y \
libs/util/cexpr-type.c \
libs/util/cexpr-vars.c \
libs/util/checksum.c \
libs/util/cmd.c \
libs/util/cmem.c \
@ -82,4 +86,13 @@ libs_util_libQFutil_la_SOURCES= \
libs/util/zone.c \
$(dirent) $(fnmatch) $(getopt)
BUILT_SOURCES += \
libs/util/cexpr-lex.c \
libs/util/cexpr-parse.c
libs/util/cexpr-parse.c: libs/util/cexpr-parse.y
$(AM_V_YACC)$(YACCCOMPILE) $< -o $@
libs/util/cexpr-lex.c: libs/util/cexpr-lex.l libs/util/cexpr-parse.h
$(AM_V_LEX)$(LEXCOMPILE) -o$@ $<
EXTRA_DIST += $(fnmatch_src) $(getopt_src)

281
libs/util/cexpr-lex.l Normal file
View file

@ -0,0 +1,281 @@
/*
cexpr-lex.l
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
%option bison-bridge
%option reentrant
%option prefix="cexpr_yy"
%option noyywrap
%{
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include "QF/cmem.h"
#include "QF/dstring.h"
#include "QF/hash.h"
#include "QF/qfplist.h"
#include "QF/sys.h"
#include "QF/cexpr.h"
#include "libs/util/cexpr-parse.h"
#define YY_NO_INPUT
#define YY_NO_UNPUT
#define YYSTYPE CEXPR_YYSTYPE
#define YY_EXTRA_TYPE exprctx_t *
exprctx_t *cexpr_yyget_extra (yyscan_t yyscanner) __attribute__((pure));
int cexpr_yyget_lineno (yyscan_t yyscanner) __attribute__((pure));
int cexpr_yyget_column (yyscan_t yyscanner) __attribute__((pure));
YYSTYPE *cexpr_yyget_lval (yyscan_t yyscanner) __attribute__((pure));
int cexpr_yyget_debug (yyscan_t yyscanner) __attribute__((pure));
char *cexpr_yyget_text (yyscan_t yyscanner) __attribute__((pure));
int cexpr_yyget_leng (yyscan_t yyscanner) __attribute__((pure));
FILE *cexpr_yyget_out (yyscan_t yyscanner) __attribute__((pure));
FILE *cexpr_yyget_in (yyscan_t yyscanner) __attribute__((pure));
static exprval_t *parse_int (const char *str, exprctx_t *context);
static exprval_t *parse_uint (const char *str, exprctx_t *context);
static exprval_t *parse_float (const char *str, exprctx_t *context);
static exprval_t *parse_double (const char *str, exprctx_t *context);
static exprsym_t *parse_name (const char *str, exprctx_t *context);
static exprval_t *parse_variable (const char *str, exprctx_t *context);
VISIBLE void
cexpr_error(exprctx_t *ctx, const char *fmt, ...)
{
va_list args;
dstring_t *string;
string = dstring_new ();
va_start (args, fmt);
dvsprintf (string, fmt, args);
va_end (args);
if (ctx->messages) {
PL_Message (ctx->messages, ctx->item, "%s", string->str);
} else {
Sys_Printf ("%s\n", string->str);
}
dstring_delete (string);
}
%}
s [ \t]
m [\-+]
D [0-9]
B [01]
X [0-9a-fA-F]
ID [a-zA-Z_][a-zA-Z_0-9]*
FLOAT ({D}+|{D}*\.{D}+|{D}+\.{D}*)([eE]{m}?{D}+)?
FLOATf {FLOAT}[fF]
FLOATd {FLOAT}[dD]
INT ({D}+|0[xX]{X}+|0[bB]{B})
STRING \"(\\.|[^"\\])*\"
%%
%{
__auto_type context = yyget_extra (yyscanner);
%}
{INT}+ {
yylval->value = parse_int (yytext, context);
return VALUE;
}
{INT}+[uU] {
yylval->value = parse_uint (yytext, context);
return VALUE;
}
{FLOAT} {
yylval->value = parse_double (yytext, context);
return VALUE;
}
{FLOATf} {
yylval->value = parse_float (yytext, context);
return VALUE;
}
{FLOATd} {
yylval->value = parse_double (yytext, context);
return VALUE;
}
{ID} {
yylval->symbol = parse_name (yytext, context);
return NAME;
}
\${ID} {
yylval->value = parse_variable (yytext + 1, context);
return VALUE;
}
{STRING} {
}
@ return '@';
'(\\[^xX0-7\r\n]|[^'\r\n]|\\[xX][0-9A-Fa-f]+|\\[0-7]+)*' {
}
[+\-*/&|^%]= {
}
"%%=" {
}
"<<=" {
}
">>=" {
}
[!(){}.*/&|^~+\-=\[\];,#%?:] {
return yytext[0];
}
"%%" {
}
"<<" return SHL;
">>" return SHR;
"&&" return AND;
"||" return OR;
"==" return EQ;
"!=" return NE;
"<=" return LE;
">=" return GE;
"<" return LT;
">" return GT;
"++" {
}
"--" {
}
<*>\r*\n {
}
<*>{s}* /* skip */
<*>. cexpr_error (context, "all your typo are belong to us");
%%
VISIBLE int
cexpr_eval_string (const char *str, exprctx_t *context)
{
int status;
yyscan_t scanner;
cexpr_yypstate *ps = cexpr_yypstate_new ();
yylex_init_extra (context, &scanner);
yy_scan_string (str, scanner);
do {
CEXPR_YYSTYPE lval;
int token = yylex (&lval, scanner);
status = cexpr_yypush_parse (ps, token, &lval, scanner, context);
} while (status == YYPUSH_MORE);
yylex_destroy (scanner);
cexpr_yypstate_delete (ps);
return status;
}
static exprval_t *parse_int (const char *str, exprctx_t *context)
{
exprval_t *val = cexpr_value (&cexpr_int, context);
*(int *) val->value = strtoimax (str, 0, 0);
return val;
}
static exprval_t *parse_uint (const char *str, exprctx_t *context)
{
exprval_t *val = cexpr_value (&cexpr_uint, context);
*(unsigned *) val->value = strtoumax (str, 0, 0);
return val;
}
static exprval_t *parse_float (const char *str, exprctx_t *context)
{
exprval_t *val = cexpr_value (&cexpr_int, context);
*(float *) val->value = strtof (str, 0);
return val;
}
static exprval_t *parse_double (const char *str, exprctx_t *context)
{
exprval_t *val = cexpr_value (&cexpr_int, context);
*(double *) val->value = strtod (str, 0);
return val;
}
static exprsym_t *
parse_name (const char *name, exprctx_t *context)
{
exprtab_t *symtab = context->symtab;
__auto_type sym = (exprsym_t *) Hash_Find (symtab->tab, name);
return sym;
}
static exprval_t *
parse_variable (const char *name, exprctx_t *context)
{
exprval_t *val = 0;
if (strcmp (name, "cvars") == 0) {
val = cexpr_cvar_struct (context);
} else {
exprtab_t *symtab = context->external_variables;
__auto_type sym = (exprsym_t *) Hash_Find (symtab->tab, name);
if (sym) {
val = cexpr_value_reference (sym->type, sym->value, context);
} else {
val = cexpr_cvar (name, context);
}
}
if (!val) {
cexpr_error (context, "undefined variable %s", name);
}
return val;
}

219
libs/util/cexpr-parse.y Normal file
View file

@ -0,0 +1,219 @@
/*
cexpr-parse.y
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
%define api.prefix {cexpr_yy}
%define api.pure full
%define api.push-pull push
%parse-param {void *scanner} {exprctx_t *context}
%{
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdio.h>
#include "QF/cmem.h"
#include "QF/hash.h"
#include "QF/qfplist.h"
#include "QF/sys.h"
#include "QF/cexpr.h"
static void assign_expr (exprval_t *dst, const exprval_t *src,
exprctx_t *context);
static exprval_t *binary_expr (int op, const exprval_t *a, const exprval_t *b,
exprctx_t *context);
static exprval_t *field_expr (const exprval_t *a, const exprval_t *b,
exprctx_t *context);
static void
yyerror (void *scanner, exprctx_t *context, const char *s)
{
cexpr_error (context, "%s before %s\n", s, cexpr_yyget_text (scanner));
}
%}
%left COMMA
%right <op> '=' ASX
%right '?' ':'
%left OR
%left AND
%left '|'
%left '^'
%left '&'
%left EQ NE
%left LE GE LT GT
%left SHL SHR
%left '+' '-'
%left '*' '/' '%' MOD
%right SIZEOF UNARY INCOP
%left HYPERUNARY
%left '.' '(' '['
%token <symbol> NAME
%token <value> VALUE
%type <value> expr field
%union {
int op;
exprsym_t *symbol;
exprval_t *value;
const char *string;
}
%%
start
: expr { assign_expr (context->result, $1, context); }
;
expr
: expr SHL expr { $$ = binary_expr (SHL, $1, $3, context); }
| expr SHR expr { $$ = binary_expr (SHR, $1, $3, context); }
| expr '+' expr { $$ = binary_expr ('+', $1, $3, context); }
| expr '-' expr { $$ = binary_expr ('-', $1, $3, context); }
| expr '*' expr { $$ = binary_expr ('*', $1, $3, context); }
| expr '/' expr { $$ = binary_expr ('/', $1, $3, context); }
| expr '&' expr { $$ = binary_expr ('&', $1, $3, context); }
| expr '|' expr { $$ = binary_expr ('|', $1, $3, context); }
| expr '^' expr { $$ = binary_expr ('^', $1, $3, context); }
| expr '%' expr { $$ = binary_expr ('%', $1, $3, context); }
| expr '.' field { $$ = field_expr ($1, $3, context); }
| expr MOD expr { $$ = binary_expr (MOD, $1, $3, context); }
| NAME
{
if ($1) {
$$ = (exprval_t *) cmemalloc (context->memsuper, sizeof (*$$));
$$->type = $1->type;
$$->value = $1->value;
} else {
cexpr_error (context, "undefined identifier %s",
cexpr_yyget_text (scanner));
}
}
| VALUE
;
field
: NAME
{
exprctx_t *ctx = context;
const char *name = cexpr_yyget_text (scanner);
size_t size = strlen (name) + 1;
//FIXME reuse strings
$$ = (exprval_t *) cmemalloc (ctx->memsuper, sizeof (exprval_t));
$$->type = &cexpr_field;
$$->value = cmemalloc (ctx->memsuper, size);
memcpy ($$->value, name, size);
}
;
%%
static void
assign_expr (exprval_t *dst, const exprval_t *src, exprctx_t *context)
{
binop_t *binop;
if (!src) {
return;
}
for (binop = dst->type->binops; binop && binop->op; binop++) {
if (binop->op == '=' && binop->other == src->type) {
break;
}
}
if (binop && binop->op) {
binop->func (dst, src, dst, context);
} else {
if (dst->type != src->type) {
cexpr_error (context,
"type mismatch in expression result: %s = %s\n",
dst->type->name, src->type->name);
return;
}
memcpy (dst->value, src->value, dst->type->size);
}
}
static exprval_t *
binary_expr (int op, const exprval_t *a, const exprval_t *b,
exprctx_t *context)
{
binop_t *binop;
for (binop = a->type->binops; binop->op; binop++) {
exprtype_t *otype = binop->other;
if (!otype) {
otype = a->type;
}
if (binop->op == op && binop->other == b->type) {
break;
}
}
exprtype_t *rtype = binop->result;
if (!rtype) {
rtype = a->type;
}
exprval_t *result = cexpr_value (rtype, context);
if (!binop->op) {
cexpr_error (context, "invalid binary expression: %s %c %s\n",
a->type->name, op, b->type->name);
} else {
binop->func (a, b, result, context);
}
return result;
}
static exprval_t *
field_expr (const exprval_t *a, const exprval_t *b, exprctx_t *context)
{
binop_t *binop;
exprval_t *result = 0;
for (binop = a->type->binops; binop->op; binop++) {
if (binop->op == '.' && binop->other == b->type) {
break;
}
}
if (!binop->op) {
cexpr_error (context, "invalid binary expression: %s.%s\n",
a->type->name, b->type->name);
} else {
exprval_t c = { 0, &result };
binop->func (a, b, &c, context);
}
return result;
}

314
libs/util/cexpr-type.c Normal file
View file

@ -0,0 +1,314 @@
/*
cexpr-type.c
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <math.h>
#include "QF/cexpr.h"
#include "libs/util/cexpr-parse.h"
#define BINOP(pre, opname, type, op) \
static void \
pre##_##opname (const exprval_t *a, const exprval_t *b, exprval_t *c, \
exprctx_t *ctx) \
{ \
(*(type *) c->value) = (*(type *) a->value) op (*(type *) b->value); \
}
#define UNOP(pre, opname, type, op) \
static void \
pre##_##opname (const exprval_t *a, exprval_t *b, exprctx_t *ctx) \
{ \
(*(type *) b->value) = op (*(type *) a->value); \
}
BINOP(int, shl, int, <<)
BINOP(int, shr, int, >>)
BINOP(int, add, int, +)
BINOP(int, sub, int, -)
BINOP(int, mul, int, *)
BINOP(int, div, int, /)
BINOP(int, band, int, &)
BINOP(int, bor, int, |)
BINOP(int, xor, int, ^)
BINOP(int, rem, int, %)
UNOP(int, pos, int, +)
UNOP(int, neg, int, -)
UNOP(int, tnot, int, !)
UNOP(int, bnot, int, ~)
static void
int_mod (const exprval_t *val1, const exprval_t *val2, exprval_t *result,
exprctx_t *ctx)
{
// implement true modulo for integers:
// 5 mod 3 = 2
// -5 mod 3 = 1
// 5 mod -3 = -1
// -5 mod -3 = -2
int a = *(int *) val1->value;
int b = *(int *) val2->value;
int c = a % b;
// % is really remainder and so has the same sign rules
// as division: -5 % 3 = -2, so need to add b (3 here)
// if c's sign is incorrect, but only if c is non-zero
int mask = (a ^ b) >> 31;
mask &= ~(!!c + 0) + 1; // +0 to convert bool to int (gcc)
*(int *) result->value = c + (mask & b);
}
binop_t int_binops[] = {
{ SHL, &cexpr_int, &cexpr_int, int_shl },
{ SHR, &cexpr_int, &cexpr_int, int_shr },
{ '+', &cexpr_int, &cexpr_int, int_add },
{ '-', &cexpr_int, &cexpr_int, int_sub },
{ '*', &cexpr_int, &cexpr_int, int_mul },
{ '/', &cexpr_int, &cexpr_int, int_div },
{ '&', &cexpr_int, &cexpr_int, int_band },
{ '|', &cexpr_int, &cexpr_int, int_bor },
{ '^', &cexpr_int, &cexpr_int, int_xor },
{ '%', &cexpr_int, &cexpr_int, int_rem },
{ MOD, &cexpr_int, &cexpr_int, int_mod },
{}
};
unop_t int_unops[] = {
{ '+', &cexpr_int, int_pos },
{ '-', &cexpr_int, int_neg },
{ '!', &cexpr_int, int_tnot },
{ '~', &cexpr_int, int_bnot },
{}
};
exprtype_t cexpr_int = {
"int",
sizeof (int),
int_binops,
int_unops,
};
BINOP(uint, shl, unsigned, <<)
BINOP(uint, shr, unsigned, >>)
BINOP(uint, add, unsigned, +)
BINOP(uint, sub, unsigned, -)
BINOP(uint, mul, unsigned, *)
BINOP(uint, div, unsigned, /)
BINOP(uint, band, unsigned, &)
BINOP(uint, bor, unsigned, |)
BINOP(uint, xor, unsigned, ^)
BINOP(uint, rem, unsigned, %)
UNOP(uint, pos, unsigned, +)
UNOP(uint, neg, unsigned, -)
UNOP(uint, tnot, unsigned, !)
UNOP(uint, bnot, unsigned, ~)
binop_t uint_binops[] = {
{ SHL, &cexpr_uint, &cexpr_uint, uint_shl },
{ SHR, &cexpr_uint, &cexpr_uint, uint_shr },
{ '+', &cexpr_uint, &cexpr_uint, uint_add },
{ '-', &cexpr_uint, &cexpr_uint, uint_sub },
{ '*', &cexpr_uint, &cexpr_uint, uint_mul },
{ '/', &cexpr_uint, &cexpr_uint, uint_div },
{ '&', &cexpr_uint, &cexpr_uint, uint_band },
{ '|', &cexpr_uint, &cexpr_uint, uint_bor },
{ '^', &cexpr_uint, &cexpr_uint, uint_xor },
{ '%', &cexpr_uint, &cexpr_uint, uint_rem },
{ MOD, &cexpr_uint, &cexpr_uint, uint_rem },
{}
};
unop_t uint_unops[] = {
{ '+', &cexpr_uint, uint_pos },
{ '-', &cexpr_uint, uint_neg },
{ '!', &cexpr_uint, uint_tnot },
{ '~', &cexpr_uint, uint_bnot },
{}
};
exprtype_t cexpr_uint = {
"uint",
sizeof (unsigned),
uint_binops,
uint_unops,
};
BINOP(float, add, float, +)
BINOP(float, sub, float, -)
BINOP(float, mul, float, *)
BINOP(float, div, float, /)
static void
float_rem (const exprval_t *val1, const exprval_t *val2, exprval_t *result,
exprctx_t *ctx)
{
float a = *(float *) val1->value;
float b = *(float *) val2->value;
*(float *) result->value = a - b * truncf (a / b);
}
static void
float_mod (const exprval_t *val1, const exprval_t *val2, exprval_t *result,
exprctx_t *ctx)
{
// implement true modulo for integers:
// 5 mod 3 = 2
// -5 mod 3 = 1
// 5 mod -3 = -1
// -5 mod -3 = -2
float a = *(float *) val1->value;
float b = *(float *) val2->value;
*(float *) result->value = a - b * floorf (a / b);
}
UNOP(float, pos, float, +)
UNOP(float, neg, float, -)
UNOP(float, tnot, float, !)
binop_t float_binops[] = {
{ '+', &cexpr_float, &cexpr_float, float_add },
{ '-', &cexpr_float, &cexpr_float, float_sub },
{ '*', &cexpr_float, &cexpr_float, float_mul },
{ '/', &cexpr_float, &cexpr_float, float_div },
{ '%', &cexpr_float, &cexpr_float, float_rem },
{ MOD, &cexpr_float, &cexpr_float, float_mod },
{}
};
unop_t float_unops[] = {
{ '+', &cexpr_float, float_pos },
{ '-', &cexpr_float, float_neg },
{ '!', &cexpr_float, float_tnot },
{}
};
exprtype_t cexpr_float = {
"float",
sizeof (float),
float_binops,
float_unops,
};
BINOP(double, add, double, +)
BINOP(double, sub, double, -)
BINOP(double, mul, double, *)
BINOP(double, div, double, /)
static void
double_rem (const exprval_t *val1, const exprval_t *val2, exprval_t *result,
exprctx_t *ctx)
{
double a = *(double *) val1->value;
double b = *(double *) val2->value;
*(double *) result->value = a - b * truncf (a / b);
}
static void
double_mod (const exprval_t *val1, const exprval_t *val2, exprval_t *result,
exprctx_t *ctx)
{
// implement true modulo for integers:
// 5 mod 3 = 2
// -5 mod 3 = 1
// 5 mod -3 = -1
// -5 mod -3 = -2
double a = *(double *) val1->value;
double b = *(double *) val2->value;
*(double *) result->value = a - b * floorf (a / b);
}
UNOP(double, pos, double, +)
UNOP(double, neg, double, -)
UNOP(double, tnot, double, !)
binop_t double_binops[] = {
{ '+', &cexpr_double, &cexpr_double, double_add },
{ '-', &cexpr_double, &cexpr_double, double_sub },
{ '*', &cexpr_double, &cexpr_double, double_mul },
{ '/', &cexpr_double, &cexpr_double, double_div },
{ '%', &cexpr_double, &cexpr_double, double_rem },
{ MOD, &cexpr_double, &cexpr_double, double_mod },
{}
};
unop_t double_unops[] = {
{ '+', &cexpr_double, double_pos },
{ '-', &cexpr_double, double_neg },
{ '!', &cexpr_double, double_tnot },
{}
};
exprtype_t cexpr_double = {
"double",
sizeof (double),
double_binops,
double_unops,
};
exprtype_t cexpr_exprval = {
"exprval",
sizeof (exprval_t *),
0, // can't actually do anything with an exprval
0,
};
exprtype_t cexpr_field = {
"field",
0, // has no size of its own, rather, it's the length of the name
0, // can't actually do anything with a field
0,
};
VISIBLE binop_t *
cexpr_find_cast (exprtype_t *dst_type, exprtype_t *src_type)
{
binop_t *binop = 0;
for (binop = dst_type->binops; binop && binop->op; binop++) {
if (binop->op == '=' && binop->other == src_type) {
break;
}
}
if (binop && binop->op) {
return binop;
}
return 0;
}
VISIBLE int
cexpr_parse_enum (exprenum_t *enm, const char *str, const exprctx_t *ctx,
void *data)
{
exprval_t result = { enm->type, data };
exprctx_t context = *ctx;
context.symtab = enm->symtab;
context.result = &result;
return cexpr_eval_string (str, &context);
}

154
libs/util/cexpr-vars.c Normal file
View file

@ -0,0 +1,154 @@
/*
cexpr-vars.c
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stddef.h>
#include <math.h>
#include "QF/cexpr.h"
#include "QF/cmem.h"
#include "QF/cvar.h"
#include "QF/hash.h"
#include "QF/qfplist.h"
#include "libs/util/cexpr-parse.h"
VISIBLE void
cexpr_struct_getfield (const exprval_t *a, const exprval_t *b, exprval_t *c,
exprctx_t *ctx)
{
__auto_type symtab = (exprtab_t *) a->type->data;
__auto_type name = (const char *) b->value;
__auto_type field = (exprsym_t *) Hash_Find (symtab->tab, name);
exprval_t *val = 0;
if (field) {
val = cmemalloc (ctx->memsuper, sizeof (exprval_t));
val->type = field->type;
val->value = a->value + (ptrdiff_t) field->value;
}
*(exprval_t **) c->value = val;
}
VISIBLE binop_t cexpr_struct_binops[] = {
{ '.', &cexpr_field, &cexpr_exprval, cexpr_struct_getfield },
{}
};
static void
cvar_get (const exprval_t *a, const exprval_t *b, exprval_t *c, exprctx_t *ctx)
{
__auto_type name = (const char *) b->value;
exprval_t *var = cexpr_cvar (name, ctx);
if (!var) {
PL_Message (ctx->messages, ctx->item, "unknown cvar %s", name);
}
*(exprval_t **) c->value = var;
}
static binop_t cvar_binops[] = {
{ '.', &cexpr_field, &cexpr_exprval, cvar_get },
{}
};
static exprtype_t cvar_type = {
"cvar",
sizeof (void *), // ref to struct (will always be 0)
cvar_binops,
0,
};
VISIBLE exprval_t *
cexpr_cvar (const char *name, exprctx_t *ctx)
{
cvar_t *var = Cvar_FindVar (name);
if (!var) {
var = Cvar_FindAlias (name);
}
if (!var) {
return 0;
}
exprtype_t *type = ctx->result->type;
binop_t *cast = cexpr_find_cast (type, &cexpr_int);
exprval_t *val = 0;
if (cast || val->type == &cexpr_int || val->type == &cexpr_uint) {
val = cexpr_value (type, ctx);
*(int *) val->value = var->int_val;
} else if (val->type == &cexpr_float) {
val = cexpr_value (type, ctx);
*(float *) val->value = var->value;
} else if (val->type == &cexpr_double) {
val = cexpr_value (type, ctx);
//FIXME cvars need to support double values
*(double *) val->value = var->value;
}
return val;
}
VISIBLE exprval_t *
cexpr_cvar_struct (exprctx_t *ctx)
{
exprval_t *cvars = cexpr_value (&cvar_type, ctx);
*(void **) cvars->value = 0;
return cvars;
}
static const char *expr_getkey (const void *s, void *unused)
{
__auto_type sym = (exprsym_t *) s;
return sym->name;
}
void cexpr_init_symtab (exprtab_t *symtab, exprctx_t *ctx)
{
exprsym_t *sym;
symtab->tab = Hash_NewTable (61, expr_getkey, 0, 0, &ctx->hashlinks);
for (sym = symtab->symbols; sym->name; sym++) {
Hash_Add (symtab->tab, sym);
}
}
VISIBLE exprval_t *
cexpr_value_reference (exprtype_t *type, void *data, exprctx_t *ctx)
{
__auto_type ref = (exprval_t *) cmemalloc (ctx->memsuper,
sizeof (exprval_t));
ref->type = type;
ref->value = data;
return ref;
}
VISIBLE exprval_t *
cexpr_value (exprtype_t *type, exprctx_t *ctx)
{
exprval_t *val = cexpr_value_reference (type, 0, ctx);
val->value = cmemalloc (ctx->memsuper, type->size);
return val;
}

View file

@ -1,5 +1,6 @@
libs_util_tests = \
libs/util/test/test-bary \
libs/util/test/test-cexpr \
libs/util/test/test-cmem \
libs/util/test/test-cs \
libs/util/test/test-darray \
@ -24,6 +25,10 @@ libs_util_test_test_bary_SOURCES=libs/util/test/test-bary.c
libs_util_test_test_bary_LDADD=libs/util/libQFutil.la
libs_util_test_test_bary_DEPENDENCIES=libs/util/libQFutil.la
libs_util_test_test_cexpr_SOURCES=libs/util/test/test-cexpr.c
libs_util_test_test_cexpr_LDADD=libs/util/libQFutil.la
libs_util_test_test_cexpr_DEPENDENCIES=libs/util/libQFutil.la
libs_util_test_test_cmem_SOURCES=libs/util/test/test-cmem.c
libs_util_test_test_cmem_LDADD=libs/util/libQFutil.la
libs_util_test_test_cmem_DEPENDENCIES=libs/util/libQFutil.la

View file

@ -0,0 +1,81 @@
/*
test-cexpr.c
Config expression parser. Or concurrent.
Copyright (C) 2020 Bill Currie <bill@taniwha.org>
This program 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 2
of the License, or (at your option) any later version.
This program 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 this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
#include "QF/cexpr.h"
#include "QF/cmem.h"
#include "QF/hash.h"
int a = 5;
int b = 6;
int c;
exprsym_t symbols[] = {
{ "a", &cexpr_int, &a },
{ "b", &cexpr_int, &b },
{}
};
exprval_t result = { &cexpr_int, &c };
exprtab_t symtab = {
symbols,
0
};
exprctx_t context = { &result, &symtab };
#define TEST_BINOP(op) \
do { \
c = -4096; \
cexpr_eval_string ("a " #op " b", &context); \
printf ("c = a %s b -> %d = %d %s %d\n", #op, c, a, #op, b); \
if (c != (a op b)) { \
ret |= 1; \
} \
} while (0)
int
main(int argc, const char **argv)
{
int ret = 0;
cexpr_init_symtab (&symtab, &context);
context.memsuper = new_memsuper();
TEST_BINOP (<<);
TEST_BINOP (>>);
TEST_BINOP (+);
TEST_BINOP (-);
TEST_BINOP (*);
TEST_BINOP (/);
TEST_BINOP (&);
TEST_BINOP (|);
TEST_BINOP (^);
TEST_BINOP (%);
Hash_DelTable (symtab.tab);
delete_memsuper (context.memsuper);
return ret;
}