root/usr.sbin/pmcstudy/eval_expr.c
/*-
 * Copyright (c) 2015 Netflix, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    in this position and unchanged.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include "eval_expr.h"
static struct expression *
alloc_and_hook_expr(struct expression **exp_p, struct expression **last_p)
{
        struct expression *ex, *at;

        ex = malloc(sizeof(struct expression));
        if (ex == NULL) {
                printf("Out of memory in exp allocation\n");
                exit(-2);
        }
        memset(ex, 0, sizeof(struct expression));
        if (*exp_p == NULL) {
                *exp_p = ex;
        }
        at = *last_p;
        if (at == NULL) {
                /* First one, its last */
                *last_p = ex;
        } else {
                /* Chain it to the end and update last */
                at->next = ex;
                ex->prev = at;
                *last_p = ex;
        }
        return (ex);
}


static int
validate_expr(struct expression *exp, int val1_is_set, int op_is_set, int val2_is_set, 
              int *op_cnt)
{
        int val1, op, val2;
        int open_cnt;
        val1 = op = val2 = 0;
        if (val1_is_set) {
                val1 = 1;
        }
        if (op_is_set) {
                op = 1;
        }
        if (val2_is_set) {
                val2 = 1;
        }
        open_cnt = *op_cnt;
        if (exp == NULL) {
                /* End of the road */
                if (val1 && op && val2 && (open_cnt == 0)) {
                        return(0);
                } else {
                        return(1);
                }
        }
        switch(exp->type) {
        case TYPE_OP_PLUS:
        case TYPE_OP_MINUS:
        case TYPE_OP_MULT:
        case TYPE_OP_DIVIDE:
                if (val1 && op && val2) {
                        /* We are at x + y + 
                         * collapse back to val/op
                         */
                        val1 = 1;
                        op = 1;
                        val2 = 0;
                } else if ((op == 0) && (val1)) {
                        op = 1;
                } else {
                        printf("Op but no val1 set\n");
                        return(-1);
                }
                break;
        case TYPE_PARN_OPEN:
                if (exp->next == NULL) {
                        printf("NULL after open paren\n");
                        exit(-1);
                }
                if ((exp->next->type == TYPE_OP_PLUS) ||
                    (exp->next->type == TYPE_OP_MINUS) ||
                    (exp->next->type == TYPE_OP_DIVIDE) ||
                    (exp->next->type == TYPE_OP_MULT)) {
                        printf("'( OP' -- not allowed\n");
                        return(-1);
                }
                if (val1 && (op == 0)) {
                        printf("'Val (' -- not allowed\n");
                        return(-1);
                }
                if (val1 && op && val2) {
                        printf("'Val OP Val (' -- not allowed\n");
                        return(-1);
                }
                open_cnt++;
                *op_cnt = open_cnt;
                if (val1) {
                        if (validate_expr(exp->next, 0, 0, 0, op_cnt) == 0) {
                                val2 = 1;
                        } else {
                                return(-1);
                        }
                } else {
                        return(validate_expr(exp->next, 0, 0, 0, op_cnt));
                }
                break;
        case TYPE_PARN_CLOSE:
                open_cnt--;
                *op_cnt = open_cnt;
                if (val1 && op && val2) {
                        return(0);
                } else {
                        printf("Found close paren and not complete\n");
                        return(-1);
                }
                break;
        case TYPE_VALUE_CON:
        case TYPE_VALUE_PMC:
                if (val1 == 0) {
                        val1 = 1;
                } else if (val1 && op) {
                        val2 = 1;
                } else {
                        printf("val1 set, val2 about to be set op empty\n");
                        return(-1);
                }
                break;
        default:
                printf("unknown type %d\n", exp->type);
                exit(-5);
                break;
        }
        return(validate_expr(exp->next, val1, op, val2, op_cnt));
}

void
print_exp(struct expression *exp)
{
        if (exp == NULL) {
                printf("\n");
                return;
        }
        switch(exp->type) {
        case TYPE_OP_PLUS:
                printf(" + ");
                break;
        case TYPE_OP_MINUS:
                printf(" - ");
                break;
        case TYPE_OP_MULT:
                printf(" * ");
                break;
        case TYPE_OP_DIVIDE:
                printf(" / ");
                break;
        case TYPE_PARN_OPEN:
                printf(" ( ");
                break;
        case TYPE_PARN_CLOSE:
                printf(" ) ");
                break;
        case TYPE_VALUE_CON:
                printf("%f", exp->value);
                break;
        case TYPE_VALUE_PMC:
                printf("%s", exp->name);
                break;
        default:
                printf("Unknown op %d\n", exp->type);
                break;
        }
        print_exp(exp->next);
}

static void
walk_back_and_insert_paren(struct expression **beg, struct expression *frm)
{
        struct expression *at, *ex;

        /* Setup our new open paren */
        ex = malloc(sizeof(struct expression));
        if (ex == NULL) {
                printf("Out of memory in exp allocation\n");
                exit(-2);
        }
        memset(ex, 0, sizeof(struct expression));
        ex->type = TYPE_PARN_OPEN;
        /* Now lets place it */
        at = frm->prev;
        if (at == *beg) {
                /* We are inserting at the head of the list */
        in_beg:
                ex->next = at;
                at->prev = ex;
                *beg = ex;
                return;
        } else if ((at->type == TYPE_VALUE_CON) ||
            (at->type == TYPE_VALUE_PMC)) {
                /* Simple case we have a value in the previous position */
        in_mid:
                ex->prev = at->prev;
                ex->prev->next = ex;
                ex->next = at;
                at->prev = ex;
                return;
        } else if (at->type == TYPE_PARN_CLOSE) {
                /* Skip through until we reach beg or all ( closes */
                int par_cnt=1;

                at = at->prev;
                while(par_cnt) {
                        if (at->type == TYPE_PARN_CLOSE) {
                                par_cnt++;
                        } else if (at->type == TYPE_PARN_OPEN) {
                                par_cnt--;
                                if (par_cnt == 0) {
                                        break;
                                }
                        }
                        at = at->prev;
                }
                if (at == *beg) {
                        /* At beginning we insert */
                        goto in_beg;
                } else {
                        goto in_mid;
                }
        } else {
                printf("%s:Unexpected type:%d?\n", 
                       __FUNCTION__, at->type);
                exit(-1);
        }
}

static void
walk_fwd_and_insert_paren(struct expression *frm, struct expression **added)
{
        struct expression *at, *ex;
        /* Setup our new close paren */
        ex = malloc(sizeof(struct expression));
        if (ex == NULL) {
                printf("Out of memory in exp allocation\n");
                exit(-2);
        }
        memset(ex, 0, sizeof(struct expression));
        ex->type = TYPE_PARN_CLOSE;
        *added = ex;
        /* Now lets place it */
        at = frm->next;
        if ((at->type == TYPE_VALUE_CON) ||
            (at->type == TYPE_VALUE_PMC)) {
                /* Simple case we have a value in the previous position */
        insertit:
                ex->next = at->next;
                ex->prev = at;
                at->next = ex;
                return;
        } else if (at->type == TYPE_PARN_OPEN) {
                int par_cnt=1;
                at = at->next;
                while(par_cnt) {
                        if (at->type == TYPE_PARN_OPEN) {
                                par_cnt++;
                        } else if (at->type == TYPE_PARN_CLOSE) {
                                par_cnt--;
                                if (par_cnt == 0) {
                                        break;
                                }
                        }
                        at = at->next;
                }
                goto insertit;
        } else {
                printf("%s:Unexpected type:%d?\n", 
                       __FUNCTION__,
                       at->type);
                exit(-1);
        }
}


static void
add_precendence(struct expression **beg, struct expression *start, struct expression *end)
{
        /* 
         * Between start and end add () around any * or /. This
         * is quite tricky since if there is a () set inside the
         * list we need to skip over everything in the ()'s considering
         * that just a value.
         */
        struct expression *at, *newone;
        int open_cnt;

        at = start;
        open_cnt = 0;
        while(at != end) {
                if (at->type == TYPE_PARN_OPEN) {
                        open_cnt++;
                }
                if (at->type == TYPE_PARN_CLOSE) {
                        open_cnt--;
                }
                if (open_cnt == 0) {
                        if ((at->type == TYPE_OP_MULT) ||
                            (at->type == TYPE_OP_DIVIDE)) {
                                walk_back_and_insert_paren(beg, at);
                                walk_fwd_and_insert_paren(at, &newone);
                                at = newone->next;
                                continue;
                        }
                }
                at = at->next;
        }
        
}

static void
set_math_precidence(struct expression **beg, struct expression *exp, struct expression **stopped)
{
        struct expression *at, *start, *end;
        int cnt_lower, cnt_upper;
        /* 
         * Walk through and set any math precedence to 
         * get proper precedence we insert () around * / over + -
         */
        end = NULL;
        start = at = exp;
        cnt_lower = cnt_upper = 0;
        while(at) { 
                if (at->type == TYPE_PARN_CLOSE) {
                        /* Done with that paren */
                        if (stopped) {
                                *stopped = at;
                        }
                        if (cnt_lower && cnt_upper) {
                                /* We have a mixed set ... add precedence between start/end */
                                add_precendence(beg, start, end);
                        }
                        return;
                }
                if (at->type == TYPE_PARN_OPEN) {
                        set_math_precidence(beg, at->next, &end);
                        at = end;
                        continue;
                } else if ((at->type == TYPE_OP_PLUS) ||
                           (at->type == TYPE_OP_MINUS)) {
                        cnt_lower++;
                } else if ((at->type == TYPE_OP_DIVIDE) ||
                           (at->type == TYPE_OP_MULT)) {
                        cnt_upper++;
                }
                at = at->next;
        }
        if (cnt_lower && cnt_upper) {
                add_precendence(beg, start, NULL);
        }
}

extern char **valid_pmcs;
extern int valid_pmc_cnt;

static void
pmc_name_set(struct expression *at)
{
        int i, idx, fnd;

        if (at->name[0] == '%') {
                /* Special number after $ gives index */
                idx = strtol(&at->name[1], NULL, 0);
                if (idx >= valid_pmc_cnt) {
                        printf("Unknown PMC %s -- largest we have is $%d -- can't run your expression\n",
                               at->name, valid_pmc_cnt);
                        exit(-1);
                }
                strcpy(at->name, valid_pmcs[idx]);
        } else {
                for(i=0, fnd=0; i<valid_pmc_cnt; i++) {
                        if (strcmp(valid_pmcs[i], at->name) == 0) {
                                fnd = 1;
                                break;
                        }
                }
                if (!fnd) {
                        printf("PMC %s does not exist on this machine -- can't run your expression\n",
                               at->name);
                        exit(-1);
                }
        }
}

struct expression *
parse_expression(char *str)
{
        struct expression *exp=NULL, *last=NULL, *at;
        int open_par, close_par;
        int op_cnt=0;
        size_t siz, i, x;
        /* 
         * Walk through a string expression and convert
         * it to a linked list of actions. We do this by:
         * a) Counting the open/close paren's, there must
         *    be a matching number.
         * b) If we have balanced paren's then create a linked list
         *    of the operators, then we validate that expression further.
         * c) Validating that we have:
         *      val OP val <or>
         *      val OP (  <and>
         *    inside every paran you have a:
         *      val OP val <or>
         *      val OP (   <recursively>
         * d) A final optional step (not implemented yet) would be
         *    to insert the mathematical precedence paran's. For
         *    the start we will just do the left to right evaluation and
         *    then later we can add this guy to add paran's to make it
         *    mathimatically correct... i.e instead of 1 + 2 * 3 we
         *    would translate it into 1 + ( 2 * 3).
         */
        open_par = close_par = 0;
        siz = strlen(str);
        /* No trailing newline please */
        if (str[(siz-1)] == '\n') {
                str[(siz-1)] = 0;
                siz--;
        }
        for(i=0; i<siz; i++) {
                if (str[i] == '(') {
                        open_par++;
                } else if (str[i] == ')') {
                        close_par++;
                }
        }
        if (open_par != close_par) {
                printf("Invalid expression '%s' %d open paren's and %d close?\n",
                       str, open_par, close_par);
                exit(-1);
        }
        for(i=0; i<siz; i++) {
                if (str[i] == '(') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_PARN_OPEN;
                } else if (str[i] == ')') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_PARN_CLOSE;
                } else if (str[i] == ' ') {
                        /* Extra blank */
                        continue;
                } else if (str[i] == '\t') {
                        /* Extra tab */
                        continue;
                } else if (str[i] == '+') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_OP_PLUS;
                } else if (str[i] == '-') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_OP_MINUS;
                } else if (str[i] == '/') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_OP_DIVIDE;
                } else if (str[i] == '*') {
                        at = alloc_and_hook_expr(&exp, &last);
                        at->type = TYPE_OP_MULT;
                } else {
                        /* Its a value or PMC constant */
                        at = alloc_and_hook_expr(&exp, &last);
                        if (isdigit(str[i]) || (str[i] == '.')) {
                                at->type = TYPE_VALUE_CON;
                        } else {
                                at->type = TYPE_VALUE_PMC;
                        }
                        x = 0;
                        while ((str[i] != ' ') && 
                               (str[i] != '\t') && 
                               (str[i] != 0) && 
                               (str[i] != ')') &&
                               (str[i] != '(')) {
                                /* We collect the constant until a space or tab */
                                at->name[x] = str[i];
                                i++;
                                x++;
                                if (x >=(sizeof(at->name)-1)) {
                                        printf("Value/Constant too long %d max:%d\n",
                                               (int)x, (int)(sizeof(at->name)-1));
                                        exit(-3);
                                }
                        }
                        if (str[i] != 0) {
                                /* Need to back up and see the last char since
                                 * the for will increment the loop.
                                 */
                                i--;
                        }
                        /* Now we have pulled the string, set it up */
                        if (at->type == TYPE_VALUE_CON) {
                                at->state = STATE_FILLED;
                                at->value = strtod(at->name, NULL);
                        } else {
                                pmc_name_set(at);
                        }
                }
        }
        /* Now lets validate its a workable expression */
        if (validate_expr(exp, 0, 0, 0, &op_cnt)) {
                printf("Invalid expression\n");
                exit(-4);
        }
        set_math_precidence(&exp, exp, NULL);
        return (exp);
}



static struct expression *
gather_exp_to_paren_close(struct expression *exp, double *val_fill)
{
        /*
         * I have been given ( ???
         * so I could see either
         * (
         * or
         * Val Op
         *
         */
        struct expression *lastproc;
        double val;

        if (exp->type == TYPE_PARN_OPEN) {
                lastproc = gather_exp_to_paren_close(exp->next, &val);
                *val_fill = val;
        } else {
                *val_fill = run_expr(exp, 0, &lastproc);
        }
        return(lastproc);
}


double
run_expr(struct expression *exp, int initial_call, struct expression **lastone)
{
        /* 
         * We expect to find either
         * a) A Open Paren
         * or
         * b) Val-> Op -> Val
         * or
         * c) Val-> Op -> Open Paren
         */
        double val1, val2, res;
        struct expression *op, *other_half, *rest;
        
        if (exp->type == TYPE_PARN_OPEN) {
                op = gather_exp_to_paren_close(exp->next, &val1);
        } else if(exp->type == TYPE_VALUE_CON) {
                val1 = exp->value;
                op = exp->next;
        } else if (exp->type ==  TYPE_VALUE_PMC) {
                val1 = exp->value;
                op = exp->next;
        } else {
                printf("Illegal value in %s huh?\n", __FUNCTION__);
                exit(-1);
        }
        if (op == NULL) {
                return (val1);
        }
more_to_do:
        other_half = op->next;
        if (other_half->type == TYPE_PARN_OPEN) {
                rest = gather_exp_to_paren_close(other_half->next, &val2);
        } else if(other_half->type == TYPE_VALUE_CON) {
                val2 = other_half->value;
                rest = other_half->next;
        } else if (other_half->type ==  TYPE_VALUE_PMC) {
                val2 = other_half->value;
                rest = other_half->next;
        } else {
                printf("Illegal2 value in %s huh?\n", __FUNCTION__);
                exit(-1);
        }
        switch(op->type) {
        case TYPE_OP_PLUS:
                res = val1 + val2;
                break;
        case TYPE_OP_MINUS:             
                res = val1 - val2;
                break;
        case TYPE_OP_MULT:
                res = val1 * val2;
                break;
        case TYPE_OP_DIVIDE:
                if (val2 != 0.0)
                        res = val1 / val2;
                else {
                        printf("Division by zero averted\n");
                        res = 1.0;
                }       
                break;
        default:
                printf("Op is not an operator -- its %d\n",
                       op->type);
                exit(-1);
                break;
        }
        if (rest == NULL) {
                if (lastone) {
                        *lastone = NULL;
                }
                return (res);
        }
        if ((rest->type == TYPE_PARN_CLOSE) && (initial_call == 0)) {
                if (lastone) {
                        *lastone = rest->next;
                }
                return(res);
        }
        /* There is more, as in
         * a + b + c
         * where we just did a + b
         * so now it becomes val1 is set to res and
         * we need to proceed with the rest of it.
         */
        val1 = res;
        op = rest;
        if ((op->type != TYPE_OP_PLUS) &&
            (op->type != TYPE_OP_MULT) &&
            (op->type != TYPE_OP_MINUS) &&
            (op->type != TYPE_OP_DIVIDE)) {
                printf("%s ending on type:%d not an op??\n", __FUNCTION__, op->type);
                return(res);
        }
        if (op)
                goto more_to_do;
        return (res);
}

#ifdef STAND_ALONE_TESTING

static double
calc_expr(struct expression *exp)
{
        struct expression *at;
        double xx;

        /* First clear PMC's setting */
        for(at = exp; at != NULL; at = at->next) {
                if (at->type == TYPE_VALUE_PMC) {
                        at->state = STATE_UNSET;
                }
        }
        /* Now for all pmc's make up values .. here is where I would pull them */
        for(at = exp; at != NULL; at = at->next) {
                if (at->type == TYPE_VALUE_PMC) {
                        at->value = (random() * 1.0);
                        at->state = STATE_FILLED;
                        if (at->value == 0.0) {
                                /* So we don't have div by 0 */
                                at->value = 1.0;
                        }
                }
        }
        /* Now lets calculate the expression */
        print_exp(exp);
        xx = run_expr(exp, 1, NULL);
        printf("Answer is %f\n", xx);
        return(xx);
}


int 
main(int argc, char **argv) 
{
        struct expression *exp;
        if (argc < 2) {
                printf("Use %s expression\n", argv[0]);
                return(-1);
        }
        exp = parse_expression(argv[1]);
        printf("Now the calc\n");
        calc_expr(exp);
        return(0);
}

#endif