// $Id: Stmt.cc,v 1.49 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include "Expr.h"
#include "Event.h"
#include "Frame.h"
#include "File.h"
#include "Logger.h"
#include "NetVar.h"
#include "Stmt.h"
#include "Scope.h"
#include "Var.h"
#include "Debug.h"

const char* stmt_name(BroStmtTag t)
	{
	static const char* stmt_names[int(NUM_STMTS)] = {
		"log", "print", "event", "expr", "if", "switch",
		"for", "next", "break", "return", "add", "delete", "list",
		"<init>",
		"null",
	};

	return stmt_names[int(t)];
	}


Stmt::Stmt(BroStmtTag arg_tag)
	{
	tag = arg_tag;
	breakpoint_count = 0;

	SetLocationInfo(&start_location, &end_location);
	}

Stmt::~Stmt()
	{
	}

bool Stmt::SetLocationInfo(const Location* start, const Location* end)
	{
	if ( ! BroObj::SetLocationInfo(start, end) )
		return false;

	// Update the Filemap of line number -> statement mapping for
	// breakpoints (Debug.h).
	Filemap* map_ptr = (Filemap*) g_dbgfilemaps.Lookup(location->filename);
	if ( ! map_ptr )
		return false;

	Filemap& map = *map_ptr;

	StmtLocMapping* new_mapping = new StmtLocMapping(GetLocationInfo(), this);

	// Optimistically just put it at the end.
	map.push_back(new_mapping);

	int curr_idx = map.length() - 1;
	if ( curr_idx == 0 )
		return true;

	// In case it wasn't actually lexically last, bubble it to the
	// right place.
	while ( map[curr_idx - 1]->StartsAfter(map[curr_idx]) )
		{
		StmtLocMapping t = *map[curr_idx - 1];
		*map[curr_idx - 1] = *map[curr_idx];
		*map[curr_idx] = t;
		curr_idx--;
		}

	return true;
	}

Stmt* Stmt::Simplify()
	{
	return this;
	}

int Stmt::IsPure() const
	{
	return 0;
	}

void Stmt::Describe(ODesc* d) const
	{
	if ( ! d->IsReadable() || Tag() != STMT_EXPR )
		AddTag(d);
	}

void Stmt::AddTag(ODesc* d) const
	{
	if ( d->IsBinary() )
		d->Add(int(Tag()));
	else
		d->Add(stmt_name(Tag()));
	d->SP();
	}

void Stmt::DescribeDone(ODesc* d) const
	{
	if ( d->IsReadable() && ! d->IsShort() )
		d->Add(";");
	}

ExprListStmt::ExprListStmt(BroStmtTag t, ListExpr* arg_l)
: Stmt(t)
	{
	l = arg_l;

	const expr_list& e = l->Exprs();
	loop_over_list(e, i)
		{
		const BroType* t = e[i]->Type();
		if ( ! t || t->Tag() == TYPE_VOID )
			Error("value of type void illegal");
		}

	SetLocationInfo(arg_l->GetLocationInfo());
	}

ExprListStmt::~ExprListStmt()
	{
	Unref(l);
	}

Val* ExprListStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;

	val_list* vals = eval_list(f, l);
	if ( vals )
		{
		Val* result = DoExec(vals, flow);
		delete_vals(vals);
		return result;
		}
	else
		return 0;
	}

Stmt* ExprListStmt::Simplify()
	{
	l = simplify_expr_list(l, SIMPLIFY_GENERAL);
	DoSimplify();
	return this;
	}

Stmt* ExprListStmt::DoSimplify()
	{
	return this;
	}

void ExprListStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);
	l->Describe(d);
	DescribeDone(d);
	}

void ExprListStmt::PrintVals(ODesc* d, val_list* vals, int offset) const
	{
	describe_vals(vals, d, offset);
	}

Val* LogStmt::DoExec(val_list* vals, stmt_flow_type& /* flow */) const
	{
	ODesc d;
	PrintVals(&d, vals, 0);

	if ( log_hook )
		{
		ListExpr* args = new ListExpr();
		args->Append(new ConstExpr(new StringVal(d.Description())));

		CallExpr* ce =
			new CallExpr(new ConstExpr(new Val(log_hook)), args);

		Val* hook_eval = ce->Eval(0);
		int do_log = hook_eval->IsOne();

		Unref(ce);
		Unref(hook_eval);

		if ( ! do_log )
			return 0;
		}

	bro_logger->Log(d.Description());
	return 0;
	}

Val* PrintStmt::DoExec(val_list* vals, stmt_flow_type& /* flow */) const
	{
	FILE* f = stdout;
	int offset = 0;

	if ( vals->length() > 0 && (*vals)[0]->Type()->Tag() == TYPE_FILE )
		{
		f = (*vals)[0]->AsFile()->File();
		if ( ! f )
			return 0;

		++offset;
		}

	ODesc d(DESC_READABLE, f);
	d.SetFlush(0);

	PrintVals(&d, vals, offset);
	putc('\n', f);
	return 0;
	}

ExprStmt::ExprStmt(Expr* arg_e) : Stmt(STMT_EXPR)
	{
	e = arg_e;
	if ( e && e->IsPure() )
		Warn("expression value ignored");

	SetLocationInfo(arg_e->GetLocationInfo());
	}

ExprStmt::ExprStmt(BroStmtTag t, Expr* arg_e) : Stmt(t)
	{
	e = arg_e;

	if ( e )
		SetLocationInfo(e->GetLocationInfo());
	}

ExprStmt::~ExprStmt()
	{
	Unref(e);
	}

Val* ExprStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;

	Val* v = e->Eval(f);

	if ( v )
		{
		Val* ret_val = DoExec(f, v, flow);
		Unref(v);
		return ret_val;
		}
	else
		return 0;
	}

Val* ExprStmt::DoExec(Frame* /* f */, Val* /* v */, stmt_flow_type& /* flow */) const
	{
	return 0;
	}

Stmt* ExprStmt::Simplify()
	{
	e = simplify_expr(e, SIMPLIFY_GENERAL);
	return DoSimplify();
	}

Stmt* ExprStmt::DoSimplify()
	{
	return this;
	}

int ExprStmt::IsPure() const
	{
	return ! e || e->IsPure();
	}

void ExprStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);

	if ( d->IsReadable() && Tag() == STMT_IF )
		d->Add("(");
	e->Describe(d);

	if ( Tag() == STMT_IF || Tag() == STMT_SWITCH )
		{
		if ( d->IsReadable() )
			{
			if ( Tag() == STMT_IF )
				d->Add(")");
			d->SP();
			}
		}
	else
		DescribeDone(d);
	}

IfStmt::IfStmt(Expr* test, Stmt* arg_s1, Stmt* arg_s2) : ExprStmt(STMT_IF, test)
	{
	s1 = arg_s1;
	s2 = arg_s2;

	if ( ! e->IsError() && ! IsBool(e->Type()->Tag()) )
		e->Error("conditional in test must be boolean");

	const Location* loc1 = arg_s1->GetLocationInfo();
	const Location* loc2 = arg_s2->GetLocationInfo();
	SetLocationInfo(loc1, loc2);
	}

IfStmt::~IfStmt()
	{
	Unref(s1);
	Unref(s2);
	}

Val* IfStmt::DoExec(Frame* f, Val* v, stmt_flow_type& flow) const
	{
	// Treat 0 as false, but don't require 1 for true.
	Stmt* do_stmt = v->IsZero() ? s2 : s1;

	f->SetNextStmt(do_stmt);

	if ( ! pre_execute_stmt(do_stmt, f) )
		; // ### Abort or something

	Val* result = do_stmt->Exec(f, flow);

	if ( ! post_execute_stmt(do_stmt, f, result, &flow) )
		; // ### Abort or something

	return result;
	}

Stmt* IfStmt::DoSimplify()
	{
	s1 = simplify_stmt(s1);
	s2 = simplify_stmt(s2);

	if ( e->IsConst() )
		{
		if ( ! optimize )
			Warn("constant in conditional");

		return e->IsZero() ? s2->Ref() : s1->Ref();
		}

	if ( e->Tag() == EXPR_NOT )
		{
		Stmt* t = s1;
		s1 = s2;
		s2 = t;

		e = new NotExpr(e);

		return Simplify();
		}

	return this;
	}

int IfStmt::IsPure() const
	{
	return s1->IsPure() && s2->IsPure();
	}

void IfStmt::Describe(ODesc* d) const
	{
	ExprStmt::Describe(d);

	s1->Describe(d);

	if ( d->IsReadable() )
		{
		if ( s2->Tag() != STMT_NULL )
			{
			d->SP();
			d->Add("else");
			d->SP();
			s2->Describe(d);
			}
		}
	else
		s2->Describe(d);
	}

Case::~Case()
	{
	Unref(cases);
	Unref(s);
	}

void Case::Describe(ODesc* d) const
	{
	const expr_list& e = Cases()->Exprs();

	if ( ! d->IsBinary() )
		d->Add("case");

	d->AddCount(e.length());

	loop_over_list(e, j)
		{
		if ( j > 0 && ! d->IsReadable() )
			d->Add(",");

		d->SP();
		e[j]->Describe(d);
		}

	if ( d->IsReadable() )
		d->Add(":");

	d->PushIndent();
	Body()->Describe(d);
	d->PopIndent();
	}

SwitchStmt::SwitchStmt(Expr* index, case_list* arg_cases) :
	ExprStmt(STMT_SWITCH, index)
	{
	cases = arg_cases;

	//### need to loop over cases and make sure their type matches
	//### the index, and they're constant and not redundant
	}

SwitchStmt::~SwitchStmt()
	{
	loop_over_list(*cases, i)
		Unref((*cases)[i]);

	delete cases;
	}

Val* SwitchStmt::DoExec(Frame* /* f */, Val* /* v */, stmt_flow_type& /* flow */) const
	{
	printf("switch statement not implemented\n");
	return 0;
	}

Stmt* SwitchStmt::DoSimplify()
	{
	loop_over_list(*cases, i)
		{
		Case* c = (*cases)[i];
		ListExpr* new_cases = simplify_expr_list(c->Cases(), SIMPLIFY_GENERAL);
		Stmt* new_body = simplify_stmt(c->Body());

		if ( new_cases != c->Cases() || new_body != c->Body() )
			{
			cases->replace(i, new Case(new_cases, new_body));
			Unref(c);
			}
		}

	if ( e->IsConst() )
		{ // ### go through cases and pull out the one it matches
		if ( ! optimize )
			Warn("constant in switch");
		}

	return this;
	}

int SwitchStmt::IsPure() const
	{
	if ( ! e->IsPure() )
		return 0;

	loop_over_list(*cases, i)
		{
		Case* c = (*cases)[i];
		if ( ! c->Cases()->IsPure() || ! c->Body()->IsPure() )
			return 0;
		}

	return 1;
	}

void SwitchStmt::Describe(ODesc* d) const
	{
	ExprStmt::Describe(d);

	if ( ! d->IsBinary() )
		d->Add("{");

	d->PushIndent();
	d->AddCount(cases->length());
	loop_over_list(*cases, i)
		(*cases)[i]->Describe(d);
	d->PopIndent();

	if ( ! d->IsBinary() )
		d->Add("}");
	d->NL();
	}

AddStmt::AddStmt(Expr* arg_e) : ExprStmt(STMT_ADD, arg_e)
	{
	if ( ! e->CanAdd() )
		Error("illegal add statement");
	}

int AddStmt::IsPure() const
	{
	return 0;
	}

Val* AddStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;
	e->Add(f);
	return 0;
	}

DelStmt::DelStmt(Expr* arg_e) : ExprStmt(STMT_DELETE, arg_e)
	{
	if ( ! e->CanDel() )
		Error("illegal delete statement");
	}

int DelStmt::IsPure() const
	{
	return 0;
	}

Val* DelStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;
	e->Delete(f);
	return 0;
	}

EventStmt::EventStmt(EventExpr* arg_e) : ExprStmt(STMT_EVENT, arg_e)
	{
	event_expr = arg_e;
	}

Val* EventStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	val_list* args = eval_list(f, event_expr->Args());

	if ( args )
		mgr.QueueEvent(event_expr->Handler(), args);

	flow = FLOW_NEXT;

	return 0;
	}

ForStmt::ForStmt(ID* arg_loop_var, Expr* loop_expr)
: ExprStmt(STMT_FOR, loop_expr)
	{
	loop_var = arg_loop_var;
	body = 0;

	if ( e->Type()->Tag() != TYPE_TABLE )
		e->Error("target to iterate over must be table/set");

	else
		{
		const type_list* indices = e->Type()->AsTableType()->IndexTypes();
		if ( indices->length() != 1 )
			e->Error("can't iterate over multi-dimensional table/set");

		BroType* ind_type = (*indices)[0]->Ref();

		if ( loop_var->Type() )
			{
			if ( ! same_type(loop_var->Type(), ind_type) )
				loop_var->Type()->Error("type clash in iteration", ind_type);
			}

		else
			{
			(void) add_local(loop_var, ind_type->Ref(),
					 INIT_NONE, 0, 0, VAR_REGULAR);
			}
		}
	}

ForStmt::~ForStmt()
	{
	Unref(loop_var);
	Unref(body);
	}

Val* ForStmt::DoExec(Frame* f, Val* v, stmt_flow_type& flow) const
	{
	TableVal* tv = v->AsTableVal();
	PDict(TableEntryVal)* loop_vals = tv->AsTable();
	Val* ret = 0;

	HashKey* k;
	TableEntryVal* iter_val;
	IterCookie* c = loop_vals->InitForIteration();
	while ( (iter_val = loop_vals->NextEntry(k, c)) )
		{
		ListVal* ind_lv = tv->RecoverIndex(k);
		delete k;

		f->SetElement(loop_var->Offset(), ind_lv->Index(0)->Ref());
		Unref(ind_lv);

		flow = FLOW_NEXT;
		ret = body->Exec(f, flow);

		if ( flow == FLOW_BREAK || flow == FLOW_RETURN )
			break;
		}

	if ( flow == FLOW_BREAK )
		flow = FLOW_NEXT;	// we've now finished the "break"

	return ret;
	}

Stmt* ForStmt::DoSimplify()
	{
	body = simplify_stmt(body);

	if ( e->IsConst() )
		{
		PDict(TableEntryVal)* vt = e->ExprVal()->AsTable();

		if ( vt->Length() == 0 )
			return new NullStmt();
		}

	return this;
	}

int ForStmt::IsPure() const
	{
	return e->IsPure() && body->IsPure();
	}

void ForStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);

	if ( d->IsReadable() )
		d->Add("(");

	loop_var->Describe(d);

	if ( d->IsReadable() )
		d->Add(" in ");

	e->Describe(d);

	if ( d->IsReadable() )
		d->Add(")");

	d->SP();

	body->Describe(d);
	}


Val* NextStmt::Exec(Frame* /* f */, stmt_flow_type& flow) const
	{
	flow = FLOW_LOOP;
	return 0;
	}

int NextStmt::IsPure() const
	{
	return 1;
	}

void NextStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);
	Stmt::DescribeDone(d);
	}

Val* BreakStmt::Exec(Frame* /* f */, stmt_flow_type& flow) const
	{
	flow = FLOW_BREAK;
	return 0;
	}

int BreakStmt::IsPure() const
	{
	return 1;
	}

void BreakStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);
	Stmt::DescribeDone(d);
	}

ReturnStmt::ReturnStmt(Expr* arg_e) : ExprStmt(STMT_RETURN, arg_e)
	{
	Scope* s = current_scope();

	if ( ! s || ! s->ScopeID() )
		Error("return statement outside of function/event");

	else
		{
		FuncType* ft = s->ScopeID()->Type()->AsFuncType();
		BroType* yt = ft->YieldType();

		if ( ! yt || yt->Tag() == TYPE_VOID )
			{
			if ( e )
				Error("return statement cannot have an expression");
			}

		else if ( ! e )
			Error("return statement needs expression");

		else
			(void) check_and_promote_expr(e, yt);
		}
	}

Val* ReturnStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_RETURN;

	if ( e )
		return e->Eval(f);
	else
		return 0;
	}

void ReturnStmt::Describe(ODesc* d) const
	{
	Stmt::Describe(d);
	if ( ! d->IsReadable() )
		d->Add(e != 0);

	if ( e )
		{
		if ( ! d->IsBinary() )
			d->Add("(");
		e->Describe(d);
		if ( ! d->IsBinary() )
			d->Add(")");
		}

	DescribeDone(d);
	}

StmtList::StmtList() : Stmt(STMT_LIST)
	{
	}

StmtList::~StmtList()
	{
	loop_over_list(stmts, i)
		Unref(stmts[i]);
	}

Val* StmtList::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;

	loop_over_list(stmts, i)
		{
		f->SetNextStmt(stmts[i]);

		if ( ! pre_execute_stmt(stmts[i], f) )
			; // ### Abort or something

		Val* result = stmts[i]->Exec(f, flow);

		if ( ! post_execute_stmt(stmts[i], f, result, &flow) )
			; // ### Abort or something

		if ( flow != FLOW_NEXT || result )
			return result;
		}

	return 0;
	}

Stmt* StmtList::Simplify()
	{
	if ( stmts.length() == 0 )
		return new NullStmt();

	if ( stmts.length() == 1 )
		return stmts[0]->Ref();

	loop_over_list(stmts, i)
		stmts.replace(i, simplify_stmt(stmts[i]));

	return this;
	}

int StmtList::IsPure() const
	{
	loop_over_list(stmts, i)
		if ( ! stmts[i]->IsPure() )
			return 0;
	return 1;
	}

void StmtList::Describe(ODesc* d) const
	{
	if ( ! d->IsReadable() )
		{
		AddTag(d);
		d->AddCount(stmts.length());
		}

	if ( stmts.length() == 0 )
		DescribeDone(d);

	else
		{
		if ( ! d->IsBinary() )
			{
			d->Add("{");
			d->PushIndent();
			}

		loop_over_list(stmts, i)
			stmts[i]->Describe(d);

		if ( ! d->IsBinary() )
			{
			d->Add("}");
			d->PopIndent();
			}
		}
	}


Val* EventBodyList::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;

	loop_over_list(stmts, i)
		{
		f->SetNextStmt(stmts[i]);

		// Ignore the return value, since there shouldn't be
		// any; and ignore the flow, since we still execute
		// all of the event bodies even if one of them does
		// a FLOW_RETURN.
		if ( ! pre_execute_stmt(stmts[i], f) )
			; // ### Abort or something

		Val* result = stmts[i]->Exec(f, flow);

		if ( ! post_execute_stmt(stmts[i], f, result, &flow) )
			; // ### Abort or something
		}

	// Simulate a return so the hooks operate properly.
	stmt_flow_type ft = FLOW_RETURN;
	(void) post_execute_stmt(f->GetNextStmt(), f, 0, &ft);

	return 0;
	}

Stmt* EventBodyList::Simplify()
	{
	if ( stmts.length() <= 1 )
		// Don't simplify these, we don't want to lose our
		// "execute even across returns" property.
		return this;

	else
		return StmtList::Simplify();
	}

void EventBodyList::Describe(ODesc* d) const
	{
	if ( d->IsReadable() && stmts.length() > 0 )
		{
		loop_over_list(stmts, i)
			{
			if ( ! d->IsBinary() )
				{
				d->Add("{");
				d->PushIndent();
				}

			stmts[i]->Describe(d);

			if ( ! d->IsBinary() )
				{
				d->Add("}");
				d->PopIndent();
				}
			}
		}

	else
		StmtList::Describe(d);
	}

InitStmt::~InitStmt()
	{
	delete inits;
	}

Val* InitStmt::Exec(Frame* f, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;

	loop_over_list(*inits, i)
		{
		ID* aggr = (*inits)[i];
		BroType* t = aggr->Type();

		Val* v;
		if ( aggr->Type()->Tag() == TYPE_RECORD )
			v = new RecordVal(t->AsRecordType());
		else
			v = new TableVal(t->AsTableType(), aggr->Attrs());

		f->SetElement(aggr->Offset(), v);
		}

	return 0;
	}

void InitStmt::Describe(ODesc* d) const
	{
	AddTag(d);

	if ( ! d->IsReadable() )
		d->AddCount(inits->length());

	loop_over_list(*inits, i)
		{
		if ( ! d->IsBinary() && i > 0 )
			d->AddSP(",");

		(*inits)[i]->Describe(d);
		}

	DescribeDone(d);
	}

Val* NullStmt::Exec(Frame* /* f */, stmt_flow_type& flow) const
	{
	flow = FLOW_NEXT;
	return 0;
	}

int NullStmt::IsPure() const
	{
	return 1;
	}

void NullStmt::Describe(ODesc* d) const
	{
	if ( d->IsReadable() )
		DescribeDone(d);
	else
		AddTag(d);
	}


Stmt* simplify_stmt(Stmt* s)
	{
	for ( Stmt* ss = s->Simplify(); ss != s; ss = s->Simplify() )
		{
		Unref(s);
		s = ss;
		}

	return s;
	}

int same_stmt(const Stmt* s1, const Stmt* s2)
	{
	if ( s1 == s2 )
		return 1;

	if ( s1->Tag() != s2->Tag() )
		return 0;

	switch ( s1->Tag() ) {
	case STMT_LOG:
	case STMT_PRINT:
		{
		const ListExpr* l1 = ((const ExprListStmt*) s1)->ExprList();
		const ListExpr* l2 = ((const ExprListStmt*) s2)->ExprList();
		return same_expr(l1, l2);
		}

	case STMT_ADD:
	case STMT_DELETE:
	case STMT_RETURN:
	case STMT_EXPR:
	case STMT_EVENT:
		{
		const ExprStmt* e1 = (const ExprStmt*) s1;
		const ExprStmt* e2 = (const ExprStmt*) s2;
		return same_expr(e1->StmtExpr(), e2->StmtExpr());
		}

	case STMT_FOR:
		{
		const ForStmt* f1 = (const ForStmt*) s1;
		const ForStmt* f2 = (const ForStmt*) s2;

		return f1->LoopVar() == f2->LoopVar() &&
			same_expr(f1->LoopExpr(), f2->LoopExpr()) &&
			same_stmt(f1->LoopBody(), f2->LoopBody());
		}

	case STMT_IF:
		{
		const IfStmt* i1 = (const IfStmt*) s1;
		const IfStmt* i2 = (const IfStmt*) s2;

		if ( ! same_expr(i1->StmtExpr(), i2->StmtExpr()) )
			return 0;

		if ( i1->TrueBranch() || i2->TrueBranch() )
			{
			if ( ! i1->TrueBranch() || ! i2->TrueBranch() )
				return 0;
			if ( ! same_stmt(i1->TrueBranch(), i2->TrueBranch()) )
				return 0;
			}

		if ( i1->FalseBranch() || i2->FalseBranch() )
			{
			if ( ! i1->FalseBranch() || ! i2->FalseBranch() )
				return 0;
			if ( ! same_stmt(i1->FalseBranch(), i2->FalseBranch()) )
				return 0;
			}

		return 1;
		}

	case STMT_SWITCH:
		{
		const SwitchStmt* sw1 = (const SwitchStmt*) s1;
		const SwitchStmt* sw2 = (const SwitchStmt*) s2;

		if ( ! same_expr(sw1->StmtExpr(), sw2->StmtExpr()) )
			return 0;

		const case_list* c1 = sw1->Cases();
		const case_list* c2 = sw1->Cases();

		if ( c1->length() != c2->length() )
			return 0;

		loop_over_list(*c1, i)
			{
			if ( ! same_expr((*c1)[i]->Cases(), (*c2)[i]->Cases()) )
				return 0;
			if ( ! same_stmt((*c1)[i]->Body(), (*c2)[i]->Body()) )
				return 0;
			}

		return 1;
		}

	case STMT_LIST:
		{
		const stmt_list& l1 = ((const StmtList*) s1)->Stmts();
		const stmt_list& l2 = ((const StmtList*) s2)->Stmts();

		if ( l1.length() != l2.length() )
			return 0;

		loop_over_list(l1, i)
			if ( ! same_stmt(l1[i], l2[i]) )
				return 0;

		return 1;
		}

	case STMT_INIT:
		{
		const id_list* i1 = ((const InitStmt*) s1)->Inits();
		const id_list* i2 = ((const InitStmt*) s2)->Inits();

		if ( i1->length() != i2->length() )
			return 0;

		loop_over_list(*i1, i)
			if ( (*i1)[i] != (*i2)[i] )
				return 0;

		return 1;
		}

	case STMT_NEXT:
	case STMT_BREAK:
	case STMT_NULL:
		return 1;

	default:
		error("bad tag in same_stmt()");
	}

	return 0;
	}
