/***** spin: flow.c *****/

/* Copyright (c) 1989-2003 by Lucent Technologies, Bell Laboratories.     */
/* All Rights Reserved.  This software is for educational purposes only.  */
/* No guarantee whatsoever is expressed or implied by the distribution of */
/* this code.  Permission is given to distribute this code provided that  */
/* this introductory message is not removed and no monies are exchanged.  */
/* Software written by Gerard J. Holzmann.  For tool documentation see:   */
/*             http://spinroot.com/                                       */
/* Send all bug-reports and/or questions to: bugs@spinroot.com            */

#include "spin.h"
#ifdef PC
#include "y_tab.h"
#else
#include "y.tab.h"
#endif

extern Symbol	*Fname;
extern int	nr_errs, lineno, verbose;
extern short	has_unless, has_badelse;

Element *Al_El = ZE;
Label	*labtab = (Label *) 0;
int	Unique=0, Elcnt=0, DstepStart = -1;

static Lbreak	*breakstack = (Lbreak *) 0;
static Lextok	*innermost;
static SeqList	*cur_s = (SeqList *) 0;
static int	break_id=0;

static Element	*if_seq(Lextok *);
static Element	*new_el(Lextok *);
static Element	*unless_seq(Lextok *);
static void	add_el(Element *, Sequence *);
static void	attach_escape(Sequence *, Sequence *);
static void	mov_lab(Symbol *, Element *, Element *);
static void	walk_atomic(Element *, Element *, int);

void
open_seq(int top)
{	SeqList *t;
	Sequence *s = (Sequence *) emalloc(sizeof(Sequence));

	t = seqlist(s, cur_s);
	cur_s = t;
	if (top) Elcnt = 1;
}

void
rem_Seq(void)
{
	DstepStart = Unique;
}

void
unrem_Seq(void)
{
	DstepStart = -1;
}

static int
Rjumpslocal(Element *q, Element *stop)
{	Element *lb, *f;
	SeqList *h;

	/* allow no jumps out of a d_step sequence */
	for (f = q; f && f != stop; f = f->nxt)
	{	if (f && f->n && f->n->ntyp == GOTO)
		{	lb = get_lab(f->n, 0);
			if (!lb || lb->Seqno < DstepStart)
			{	lineno = f->n->ln;
				Fname = f->n->fn;
				return 0;
		}	}
		for (h = f->sub; h; h = h->nxt)
		{	if (!Rjumpslocal(h->this->frst, h->this->last))
				return 0;
	
	}	}
	return 1;
}

void
cross_dsteps(Lextok *a, Lextok *b)
{
	if (a && b
	&&  a->indstep != b->indstep)
	{	lineno = a->ln;
		Fname  = a->fn;
		fatal("jump into d_step sequence", (char *) 0);
	}
}

int
is_skip(Lextok *n)
{
	return (n->ntyp == PRINT
	||	n->ntyp == PRINTM
	||	(n->ntyp == 'c'
		&& n->lft
		&& n->lft->ntyp == CONST
		&& n->lft->val  == 1));
}

void
check_sequence(Sequence *s)
{	Element *e, *le = ZE;
	Lextok *n;
	int cnt = 0;

	for (e = s->frst; e; le = e, e = e->nxt)
	{	n = e->n;
		if (is_skip(n) && !has_lab(e, 0))
		{	cnt++;
			if (cnt > 1
			&&  n->ntyp != PRINT
			&&  n->ntyp != PRINTM)
			{	if (verbose&32)
					printf("spin: line %d %s, redundant skip\n",
						n->ln, n->fn->name);
				if (e != s->frst
				&&  e != s->last
				&&  e != s->extent)
				{	e->status |= DONE;	/* not unreachable */
					le->nxt = e->nxt;	/* remove it */
					e = le;
				}
			}
		} else
			cnt = 0;
	}
}

void
prune_opts(Lextok *n)
{	SeqList *l;
	extern Symbol *context;
	extern char *claimproc;

	if (!n
	|| (context && claimproc && strcmp(context->name, claimproc) == 0))
		return;

	for (l = n->sl; l; l = l->nxt)	/* find sequences of unlabeled skips */
		check_sequence(l->this);
}

Sequence *
close_seq(int nottop)
{	Sequence *s = cur_s->this;
	Symbol *z;

	if (nottop > 0 && (z = has_lab(s->frst, 0)))
	{	printf("error: (%s:%d) label %s placed incorrectly\n",
			(s->frst->n)?s->frst->n->fn->name:"-",
			(s->frst->n)?s->frst->n->ln:0,
			z->name);
		switch (nottop) {
		case 1:
			printf("=====> stmnt unless Label: stmnt\n");
			printf("sorry, cannot jump to the guard of an\n");
			printf("escape (it is not a unique state)\n");
			break;
		case 2:
			printf("=====> instead of  ");
			printf("\"Label: stmnt unless stmnt\"\n");
			printf("=====> always use  ");
			printf("\"Label: { stmnt unless stmnt }\"\n");
			break;
		case 3:
			printf("=====> instead of  ");
			printf("\"atomic { Label: statement ... }\"\n");
			printf("=====> always use  ");
			printf("\"Label: atomic { statement ... }\"\n");
			break;
		case 4:
			printf("=====> instead of  ");
			printf("\"d_step { Label: statement ... }\"\n");
			printf("=====> always use  ");
			printf("\"Label: d_step { statement ... }\"\n");
			break;
		case 5:
			printf("=====> instead of  ");
			printf("\"{ Label: statement ... }\"\n");
			printf("=====> always use  ");
			printf("\"Label: { statement ... }\"\n");
			break;
		case 6:
			printf("=====>instead of\n");
			printf("	do (or if)\n");
			printf("	:: ...\n");
			printf("	:: Label: statement\n");
			printf("	od (of fi)\n");
			printf("=====>always use\n");
			printf("Label:	do (or if)\n");
			printf("	:: ...\n");
			printf("	:: statement\n");
			printf("	od (or fi)\n");
			break;
		case 7:
			printf("cannot happen - labels\n");
			break;
		}
		alldone(1);
	}

	if (nottop == 4
	&& !Rjumpslocal(s->frst, s->last))
		fatal("non_local jump in d_step sequence", (char *) 0);

	cur_s = cur_s->nxt;
	s->maxel = Elcnt;
	s->extent = s->last;
	if (!s->last)
		fatal("sequence must have at least one statement", (char *) 0);
	return s;
}

Lextok *
do_unless(Lextok *No, Lextok *Es)
{	SeqList *Sl;
	Lextok *Re = nn(ZN, UNLESS, ZN, ZN);
	Re->ln = No->ln;
	Re->fn = No->fn;

	has_unless++;
	if (Es->ntyp == NON_ATOMIC)
		Sl = Es->sl;
	else
	{	open_seq(0); add_seq(Es);
		Sl = seqlist(close_seq(1), 0);
	}

	if (No->ntyp == NON_ATOMIC)
	{	No->sl->nxt = Sl;
		Sl = No->sl;
	} else	if (No->ntyp == ':'
		&& (No->lft->ntyp == NON_ATOMIC
		||  No->lft->ntyp == ATOMIC
		||  No->lft->ntyp == D_STEP))
	{
		int tok = No->lft->ntyp;

		No->lft->sl->nxt = Sl;
		Re->sl = No->lft->sl;

		open_seq(0); add_seq(Re);
		Re = nn(ZN, tok, ZN, ZN);
		Re->sl = seqlist(close_seq(7), 0);
		Re->ln = No->ln;
		Re->fn = No->fn;

		Re = nn(No, ':', Re, ZN);	/* lift label */
		Re->ln = No->ln;
		Re->fn = No->fn;
		return Re;
	} else
	{	open_seq(0); add_seq(No);
		Sl = seqlist(close_seq(2), Sl);
	}

	Re->sl = Sl;
	return Re;
}

SeqList *
seqlist(Sequence *s, SeqList *r)
{	SeqList *t = (SeqList *) emalloc(sizeof(SeqList));

	t->this = s;
	t->nxt = r;
	return t;
}

static Element *
new_el(Lextok *n)
{	Element *m;

	if (n)
	{	if (n->ntyp == IF || n->ntyp == DO)
			return if_seq(n);
		if (n->ntyp == UNLESS)
			return unless_seq(n);
	}
	m = (Element *) emalloc(sizeof(Element));
	m->n = n;
	m->seqno = Elcnt++;
	m->Seqno = Unique++;
	m->Nxt = Al_El; Al_El = m;
	return m;
}

static int
has_chanref(Lextok *n)
{
	if (!n) return 0;

	switch (n->ntyp) {
	case 's':	case 'r':
#if 0
	case 'R':	case LEN:
#endif
	case FULL:	case NFULL:
	case EMPTY:	case NEMPTY:
		return 1;
	default:
		break;
	}
	if (has_chanref(n->lft))
		return 1;

	return has_chanref(n->rgt);
}

void
loose_ends(void)	/* properly tie-up ends of sub-sequences */
{	Element *e, *f;

	for (e = Al_El; e; e = e->Nxt)
	{	if (!e->n
		||  !e->nxt)
			continue;
		switch (e->n->ntyp) {
		case ATOMIC:
		case NON_ATOMIC:
		case D_STEP:
			f = e->nxt;
			while (f && f->n->ntyp == '.')
				f = f->nxt;
			if (0) printf("link %d, {%d .. %d} -> %d (ntyp=%d) was %d\n",
				e->seqno,
				e->n->sl->this->frst->seqno,
				e->n->sl->this->last->seqno,
				f?f->seqno:-1, f?f->n->ntyp:-1,
				e->n->sl->this->last->nxt?e->n->sl->this->last->nxt->seqno:-1);
			if (!e->n->sl->this->last->nxt)
				e->n->sl->this->last->nxt = f;
			else
			{	if (e->n->sl->this->last->nxt->n->ntyp != GOTO)
				{	if (!f || e->n->sl->this->last->nxt->seqno != f->seqno)
					non_fatal("unexpected: loose ends", (char *)0);
				} else
					e->n->sl->this->last = e->n->sl->this->last->nxt;
				/*
				 * fix_dest can push a goto into the nxt position
				 * in that case the goto wins and f is not needed
				 * but the last fields needs adjusting
				 */
			}
			break;
	}	}
}

static Element *
if_seq(Lextok *n)
{	int	tok = n->ntyp;
	SeqList	*s  = n->sl;
	Element	*e  = new_el(ZN);
	Element	*t  = new_el(nn(ZN,'.',ZN,ZN)); /* target */
	SeqList	*z, *prev_z = (SeqList *) 0;
	SeqList *move_else  = (SeqList *) 0;	/* to end of optionlist */
	int	ref_chans = 0;

	for (z = s; z; z = z->nxt)
	{	if (!z->this->frst)
			continue;
		if (z->this->frst->n->ntyp == ELSE)
		{	if (move_else)
				fatal("duplicate `else'", (char *) 0);
			if (z->nxt)	/* is not already at the end */
			{	move_else = z;
				if (prev_z)
					prev_z->nxt = z->nxt;
				else
					s = n->sl = z->nxt;
				continue;
			}
		} else
			ref_chans |= has_chanref(z->this->frst->n);
		prev_z = z;
	}
	if (move_else)
	{	move_else->nxt = (SeqList *) 0;
		/* if there is no prev, then else was at the end */
		if (!prev_z) fatal("cannot happen - if_seq", (char *) 0);
		prev_z->nxt = move_else;
		prev_z = move_else;
	}
	if (prev_z
	&&  ref_chans
	&&  prev_z->this->frst->n->ntyp == ELSE)
	{	prev_z->this->frst->n->val = 1;
		has_badelse++;
		non_fatal("dubious use of 'else' combined with i/o,",
			(char *)0);
		nr_errs--;
	}

	e->n = nn(n, tok, ZN, ZN);
	e->n->sl = s;			/* preserve as info only */
	e->sub = s;
	for (z = s; z; prev_z = z, z = z->nxt)
		add_el(t, z->this);	/* append target */
	if (tok == DO)
	{	add_el(t, cur_s->this); /* target upfront */
		t = new_el(nn(n, BREAK, ZN, ZN)); /* break target */
		set_lab(break_dest(), t);	/* new exit  */
		breakstack = breakstack->nxt;	/* pop stack */
	}
	add_el(e, cur_s->this);
	add_el(t, cur_s->this);
	return e;			/* destination node for label */
}

static void
escape_el(Element *f, Sequence *e)
{	SeqList *z;

	for (z = f->esc; z; z = z->nxt)
		if (z->this == e)
			return;	/* already there */

	/* cover the lower-level escapes of this state */
	for (z = f->esc; z; z = z->nxt)
		attach_escape(z->this, e);

	/* now attach escape to the state itself */

	f->esc = seqlist(e, f->esc);	/* in lifo order... */
#ifdef DEBUG
	printf("attach %d (", e->frst->Seqno);
	comment(stdout, e->frst->n, 0);
	printf(")	to %d (", f->Seqno);
	comment(stdout, f->n, 0);
	printf(")\n");
#endif
	switch (f->n->ntyp) {
	case UNLESS:
		attach_escape(f->sub->this, e);
		break;
	case IF:
	case DO:
		for (z = f->sub; z; z = z->nxt)
			attach_escape(z->this, e);
		break;
	case D_STEP:
		/* attach only to the guard stmnt */
		escape_el(f->n->sl->this->frst, e);
		break;
	case ATOMIC:
	case NON_ATOMIC:
		/* attach to all stmnts */
		attach_escape(f->n->sl->this, e);
		break;
	}
}

static void
attach_escape(Sequence *n, Sequence *e)
{	Element *f;

	for (f = n->frst; f; f = f->nxt)
	{	escape_el(f, e);
		if (f == n->extent)
			break;
	}
}

static Element *
unless_seq(Lextok *n)
{	SeqList	*s  = n->sl;
	Element	*e  = new_el(ZN);
	Element	*t  = new_el(nn(ZN,'.',ZN,ZN)); /* target */
	SeqList	*z;

	e->n = nn(n, UNLESS, ZN, ZN);
	e->n->sl = s;			/* info only */
	e->sub = s;

	/* need 2 sequences: normal execution and escape */
	if (!s || !s->nxt || s->nxt->nxt)
		fatal("unexpected unless structure", (char *)0);

	/* append the target state to both */
	for (z = s; z; z = z->nxt)
		add_el(t, z->this);

	/* attach escapes to all states in normal sequence */
	attach_escape(s->this, s->nxt->this);

	add_el(e, cur_s->this);
	add_el(t, cur_s->this);
#ifdef DEBUG
	printf("unless element (%d,%d):\n", e->Seqno, t->Seqno);
	for (z = s; z; z = z->nxt)
	{	Element *x; printf("\t%d,%d,%d :: ",
		z->this->frst->Seqno,
		z->this->extent->Seqno,
		z->this->last->Seqno);
		for (x = z->this->frst; x; x = x->nxt)
			printf("(%d)", x->Seqno);
		printf("\n");
	}
#endif
	return e;
}

Element *
mk_skip(void)
{	Lextok  *t = nn(ZN, CONST, ZN, ZN);
	t->val = 1;
	return new_el(nn(ZN, 'c', t, ZN));
}

static void
add_el(Element *e, Sequence *s)
{
	if (e->n->ntyp == GOTO)
	{	Symbol *z = has_lab(e, (1|2|4));
		if (z)
		{	Element *y; /* insert a skip */
			y = mk_skip();
			mov_lab(z, e, y); /* inherit label */
			add_el(y, s);
	}	}
#ifdef DEBUG
	printf("add_el %d after %d -- ",
	e->Seqno, (s->last)?s->last->Seqno:-1);
	comment(stdout, e->n, 0);
	printf("\n");
#endif
	if (!s->frst)
		s->frst = e;
	else
		s->last->nxt = e;
	s->last = e;
}

static Element *
colons(Lextok *n)
{
	if (!n)
		return ZE;
	if (n->ntyp == ':')
	{	Element *e = colons(n->lft);
		set_lab(n->sym, e);
		return e;
	}
	innermost = n;
	return new_el(n);
}

void
add_seq(Lextok *n)
{	Element *e;

	if (!n) return;
	innermost = n;
	e = colons(n);
	if (innermost->ntyp != IF
	&&  innermost->ntyp != DO
	&&  innermost->ntyp != UNLESS)
		add_el(e, cur_s->this);
}

void
set_lab(Symbol *s, Element *e)
{	Label *l; extern Symbol *context;

	if (!s) return;
	for (l = labtab; l; l = l->nxt)
		if (l->s == s && l->c == context)
		{	non_fatal("label %s redeclared", s->name);
			break;
		}
	l = (Label *) emalloc(sizeof(Label));
	l->s = s;
	l->c = context;
	l->e = e;
	l->nxt = labtab;
	labtab = l;
}

Element *
get_lab(Lextok *n, int md)
{	Label *l;
	Symbol *s = n->sym;

	for (l = labtab; l; l = l->nxt)
		if (s == l->s)
			return (l->e);

	lineno = n->ln;
	Fname = n->fn;
	if (md) fatal("undefined label %s", s->name);
	return ZE;
}

Symbol *
has_lab(Element *e, int special)
{	Label *l;

	for (l = labtab; l; l = l->nxt)
	{	if (e != l->e)
			continue;
		if (special == 0
		||  ((special&1) && !strncmp(l->s->name, "accept", 6))
		||  ((special&2) && !strncmp(l->s->name, "end", 3))
		||  ((special&4) && !strncmp(l->s->name, "progress", 8)))
			return (l->s);
	}
	return ZS;
}

static void
mov_lab(Symbol *z, Element *e, Element *y)
{	Label *l;

	for (l = labtab; l; l = l->nxt)
		if (e == l->e)
		{	l->e = y;
			return;
		}
	if (e->n)
	{	lineno = e->n->ln;
		Fname  = e->n->fn;
	}
	fatal("cannot happen - mov_lab %s", z->name);
}

void
fix_dest(Symbol *c, Symbol *a)		/* c:label name, a:proctype name */
{	Label *l; extern Symbol *context;

#if 0
	printf("ref to label '%s' in proctype '%s', search:\n",
		c->name, a->name);
	for (l = labtab; l; l = l->nxt)
		printf("	%s in	%s\n", l->s->name, l->c->name);
#endif

	for (l = labtab; l; l = l->nxt)
	{	if (strcmp(c->name, l->s->name) == 0
		&&  strcmp(a->name, l->c->name) == 0)	/* ? */
			break;
	}
	if (!l)
	{	printf("spin: label '%s' (proctype %s)\n", c->name, a->name);
		non_fatal("unknown label '%s'", c->name);
		if (context == a)
		printf("spin: cannot remote ref a label inside the same proctype\n");
		return;
	}
	if (!l->e || !l->e->n)
		fatal("fix_dest error (%s)", c->name);
	if (l->e->n->ntyp == GOTO)
	{	Element	*y = (Element *) emalloc(sizeof(Element));
		int	keep_ln = l->e->n->ln;
		Symbol	*keep_fn = l->e->n->fn;

		/* insert skip - or target is optimized away */
		y->n = l->e->n;		  /* copy of the goto   */
		y->seqno = find_maxel(a); /* unique seqno within proc */
		y->nxt = l->e->nxt;
		y->Seqno = Unique++; y->Nxt = Al_El; Al_El = y;

		/* turn the original element+seqno into a skip */
		l->e->n = nn(ZN, 'c', nn(ZN, CONST, ZN, ZN), ZN);
		l->e->n->ln = l->e->n->lft->ln = keep_ln;
		l->e->n->fn = l->e->n->lft->fn = keep_fn;
		l->e->n->lft->val = 1;
		l->e->nxt = y;		/* append the goto  */
	}
	l->e->status |= CHECK2;	/* treat as if global */
	if (l->e->status & (ATOM | L_ATOM | D_ATOM))
	{	non_fatal("cannot reference label inside atomic or d_step (%s)",
			c->name);
	}
}

int
find_lab(Symbol *s, Symbol *c, int markit)
{	Label *l;

	for (l = labtab; l; l = l->nxt)
	{	if (strcmp(s->name, l->s->name) == 0
		&&  strcmp(c->name, l->c->name) == 0)
		{	l->visible |= markit;
			return (l->e->seqno);
	}	}
	return 0;
}

void
pushbreak(void)
{	Lbreak *r = (Lbreak *) emalloc(sizeof(Lbreak));
	Symbol *l;
	char buf[64];

	sprintf(buf, ":b%d", break_id++);
	l = lookup(buf);
	r->l = l;
	r->nxt = breakstack;
	breakstack = r;
}

Symbol *
break_dest(void)
{
	if (!breakstack)
		fatal("misplaced break statement", (char *)0);
	return breakstack->l;
}

void
make_atomic(Sequence *s, int added)
{	Element *f;

	walk_atomic(s->frst, s->last, added);

	f = s->last;
	switch (f->n->ntyp) {	/* is last step basic stmnt or sequence ? */
	case NON_ATOMIC:
	case ATOMIC:
		/* redo and search for the last step of that sequence */
		make_atomic(f->n->sl->this, added);
		break;

	case UNLESS:
		/* escapes are folded into main sequence */
		make_atomic(f->sub->this, added);
		break;

	default:
		f->status &= ~ATOM;
		f->status |= L_ATOM;
		break;
	}
}

static void
walk_atomic(Element *a, Element *b, int added)
{	Element *f; Symbol *ofn; int oln;
	SeqList *h;

	ofn = Fname;
	oln = lineno;
	for (f = a; ; f = f->nxt)
	{	f->status |= (ATOM|added);
		switch (f->n->ntyp) {
		case ATOMIC:
			if (verbose&32)
			  printf("spin: warning, line %3d %s, atomic inside %s (ignored)\n",
			  f->n->ln, f->n->fn->name, (added)?"d_step":"atomic");
			goto mknonat;
		case D_STEP:
			if (!(verbose&32))
			{	if (added) goto mknonat;
				break;
			}
			printf("spin: warning, line %3d %s, d_step inside ",
			 f->n->ln, f->n->fn->name);
			if (added)
			{	printf("d_step (ignored)\n");
				goto mknonat;
			}
			printf("atomic\n");
			break;
		case NON_ATOMIC:
mknonat:		f->n->ntyp = NON_ATOMIC; /* can jump here */
			h = f->n->sl;
			walk_atomic(h->this->frst, h->this->last, added);
			break;
		case UNLESS:
			if (added)
			{ printf("spin: error, line %3d %s, unless in d_step (ignored)\n",
			 	 f->n->ln, f->n->fn->name);
			}
		}
		for (h = f->sub; h; h = h->nxt)
			walk_atomic(h->this->frst, h->this->last, added);
		if (f == b)
			break;
	}
	Fname = ofn;
	lineno = oln;
}

void
dumplabels(void)
{	Label *l;

	for (l = labtab; l; l = l->nxt)
		if (l->c != 0 && l->s->name[0] != ':')
		printf("label	%s	%d	<%s>\n",
		l->s->name, l->e->seqno, l->c->name);
}
