/*
 * Copyright (C) 2013 Oracle.
 *
 * 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, see http://www.gnu.org/copyleft/gpl.txt
 */

#include "smatch.h"

#define RECURSE_LIMIT 10

int recurse(struct expression *expr,
	    int (func)(struct expression *expr, void *p),
	    void *param, int nr)
{
	int ret;

	if (!expr)
		return 0;

	ret = func(expr, param);
	if (ret)
		return ret;

	if (nr > RECURSE_LIMIT)
		return -1;
	nr++;

	switch (expr->type) {
	case EXPR_PREOP:
		ret = recurse(expr->unop, func, param, nr);
		break;
	case EXPR_POSTOP:
		ret = recurse(expr->unop, func, param, nr);
		break;
	case EXPR_STATEMENT:
		return -1;
		break;
	case EXPR_LOGICAL:
	case EXPR_COMPARE:
	case EXPR_BINOP:
	case EXPR_COMMA:
		ret = recurse(expr->left, func, param, nr);
		if (ret)
			return ret;
		ret = recurse(expr->right, func, param, nr);
		break;
	case EXPR_ASSIGNMENT:
		ret = recurse(expr->right, func, param, nr);
		if (ret)
			return ret;
		ret = recurse(expr->left, func, param, nr);
		break;
	case EXPR_DEREF:
		ret = recurse(expr->deref, func, param, nr);
		break;
	case EXPR_SLICE:
		ret = recurse(expr->base, func, param, nr);
		break;
	case EXPR_CAST:
	case EXPR_FORCE_CAST:
		ret = recurse(expr->cast_expression, func, param, nr);
		break;
	case EXPR_SIZEOF:
	case EXPR_OFFSETOF:
	case EXPR_ALIGNOF:
		break;
	case EXPR_CONDITIONAL:
	case EXPR_SELECT:
		ret = recurse(expr->conditional, func, param, nr);
		if (ret)
			return ret;
		ret = recurse(expr->cond_true, func, param, nr);
		if (ret)
			return ret;
		ret = recurse(expr->cond_false, func, param, nr);
		break;
	case EXPR_CALL:
		return -1;
		break;
	case EXPR_INITIALIZER:
		return -1;
		break;
	case EXPR_IDENTIFIER:
		ret = recurse(expr->ident_expression, func, param, nr);
		break;
	case EXPR_INDEX:
		ret = recurse(expr->idx_expression, func, param, nr);
		break;
	case EXPR_POS:
		ret = recurse(expr->init_expr, func, param, nr);
		break;
	case EXPR_SYMBOL:
	case EXPR_STRING:
	case EXPR_VALUE:
		break;
	default:
		return -1;
		break;
	};
	return ret;
}

static int has_symbol_helper(struct expression *expr, void *_sym)
{
	struct symbol *sym = _sym;

	if (!expr || expr->type != EXPR_SYMBOL)
		return 0;
	if (expr->symbol == sym)
		return 1;
	return 0;
}

int has_symbol(struct expression *expr, struct symbol *sym)
{
	return recurse(expr, has_symbol_helper, sym, 0);
}

struct expr_name_sym {
	struct expression *expr;
	char *name;
	struct symbol *sym;
};

static int has_var_helper(struct expression *expr, void *_var)
{
	struct expr_name_sym *xns = _var;
	char *name;
	struct symbol *sym;
	int matched = 0;

	if (!expr)
		return 0;
	if (expr->type != xns->expr->type)
		return 0;
	// I hope this is defined for everything?  It should work, right?
	if (expr->op != xns->expr->op)
		return 0;

	name = expr_to_var_sym(expr, &sym);
	if (!name || !sym)
		goto free;
	if (sym == xns->sym && strcmp(name, xns->name) == 0)
		matched = 1;
free:
	free_string(name);
	return matched;
}

int has_variable(struct expression *expr, struct expression *var)
{
	struct expr_name_sym xns;
	int ret = -1;

	xns.expr = var;
	xns.name = expr_to_var_sym(var, &xns.sym);
	if (!xns.name || !xns.sym)
		goto free;
	ret = recurse(expr, has_var_helper, &xns, 0);
free:
	free_string(xns.name);
	return ret;
}

static int has_inc_dec_helper(struct expression *expr, void *unused)
{
	if (!expr)
		return 0;
	if (expr->type != EXPR_PREOP && expr->type != EXPR_POSTOP)
		return 0;
	if (expr->op == SPECIAL_INCREMENT || expr->op == SPECIAL_DECREMENT)
		return 1;
	return 0;
}

int has_inc_dec(struct expression *expr)
{
	return recurse(expr, has_inc_dec_helper, NULL, 0);
}

