// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Parse stabs debug info.

#include "a.h"

int stabsdebug = 1;

// Hash table for type lookup by number.
Type *hash[1024];

// Look up type by number pair.
// TODO(rsc): Iant points out that n1 and n2 are always small and dense,
// so an array of arrays would be a better representation.
Type*
typebynum(uint n1, uint n2)
{
	uint h;
	Type *t;

	h = (n1*53+n2) % nelem(hash);
	for(t=hash[h]; t; t=t->next)
		if(t->n1 == n1 && t->n2 == n2)
			return t;
	t = emalloc(sizeof *t);
	t->next = hash[h];
	hash[h] = t;
	t->n1 = n1;
	t->n2 = n2;
	return t;
}

// Parse name and colon from *pp, leaving copy in *sp.
static int
parsename(char **pp, char **sp)
{
	char *p;
	char *s;

	p = *pp;
	while(*p != '\0' && *p != ':')
		p++;
	if(*p == '\0') {
		fprint(2, "parsename expected colon\n");
		return -1;
	}
	s = emalloc(p - *pp + 1);
	memmove(s, *pp, p - *pp);
	*sp = s;
	*pp = p+1;
	return 0;
}

// Parse single number from *pp.
static int
parsenum1(char **pp, vlong *np)
{
	char *p;

	p = *pp;
	if(*p != '-' && (*p < '0' || *p > '9')) {
		fprint(2, "parsenum expected minus or digit\n");
		return -1;
	}
	*np = strtoll(p, pp, 10);
	return 0;
}

// Parse type number - either single number or (n1, n2).
static int
parsetypenum(char **pp, vlong *n1p, vlong *n2p)
{
	char *p;

	p = *pp;
	if(*p == '(') {
		p++;
		if(parsenum1(&p, n1p) < 0)
			return -1;
		if(*p++ != ',') {
			if(stabsdebug)
				fprint(2, "parsetypenum expected comma\n");
			return -1;
		}
		if(parsenum1(&p, n2p) < 0)
			return -1;
		if(*p++ != ')') {
			if(stabsdebug)
				fprint(2, "parsetypenum expected right paren\n");
			return -1;
		}
		*pp = p;
		return 0;
	}

	if(parsenum1(&p, n1p) < 0)
		return -1;
	*n2p = 0;
	*pp = p;
	return 0;
}

// Integer types are represented in stabs as a "range"
// type with a lo and a hi value.  The lo and hi used to
// be lo and hi for the type, but there are now odd
// extensions for floating point and 64-bit numbers.
//
// Have to keep signs separate from values because
// Int64's lo is -0.
typedef struct Intrange Intrange;
struct Intrange
{
	int signlo;	// sign of lo
	vlong lo;
	int signhi;	// sign of hi
	vlong hi;
	int kind;
};

// NOTE(rsc): Iant says that these might be different depending
// on the gcc mode, though I haven't observed this yet.
Intrange intranges[] = {
	'+', 0, '+', 127, Int8,	// char
	'-', 128, '+', 127, Int8,	// signed char
	'+', 0, '+', 255, Uint8,
	'-', 32768, '+', 32767, Int16,
	'+', 0, '+', 65535, Uint16,
	'-', 2147483648LL, '+', 2147483647LL, Int32,
	'+', 0, '+', 4294967295LL, Uint32,

	// abnormal cases
	'-', 0, '+', 4294967295LL, Int64,
	'+', 0, '-', 1, Uint64,

	'+', 4, '+', 0, Float32,
	'+', 8, '+', 0, Float64,
	'+', 16, '+', 0, Void,
};

static int kindsize[] = {
	0,
	0,
	8,
	8,
	16,
	16,
	32,
	32,
	64,
	64,
};

// Parse a single type definition from *pp.
static Type*
parsedef(char **pp, char *name)
{
	char *p;
	Type *t, *tt;
	int i, signlo, signhi;
	vlong n1, n2, lo, hi;
	Field *f;
	Intrange *r;

	p = *pp;

	// reference to another type?
	if(isdigit(*p) || *p == '(') {
		if(parsetypenum(&p, &n1, &n2) < 0)
			return nil;
		t = typebynum(n1, n2);
		if(name && t->name == nil) {
			t->name = name;
			// save definitions of names beginning with $
			if(name[0] == '$' && !t->saved) {
				typ = erealloc(typ, (ntyp+1)*sizeof typ[0]);
				typ[ntyp] = t;
				ntyp++;
			}
		}

		// is there an =def suffix?
		if(*p == '=') {
			p++;
			tt = parsedef(&p, name);
			if(tt == nil)
				return nil;

			if(tt == t) {
				tt->kind = Void;
			} else {
				t->type = tt;
				t->kind = Typedef;
			}

			// assign given name, but do not record in typ.
			// assume the name came from a typedef
			// which will be recorded.
			if(name)
				tt->name = name;
		}

		*pp = p;
		return t;
	}

	// otherwise a type literal.  first letter identifies kind
	t = emalloc(sizeof *t);
	switch(*p) {
	default:
		fprint(2, "unknown type char %c\n", *p);
		*pp = "";
		return t;

	case '*':	// pointer
		p++;
		t->kind = Ptr;
		tt = parsedef(&p, nil);
		if(tt == nil)
			return nil;
		t->type = tt;
		break;

	case 'a':	// array
		p++;
		t->kind = Array;
		// index type
		tt = parsedef(&p, nil);
		if(tt == nil)
			return nil;
		t->size = tt->size;
		// element type
		tt = parsedef(&p, nil);
		if(tt == nil)
			return nil;
		t->type = tt;
		break;

	case 'e':	// enum type - record $names in con array.
		p++;
		for(;;) {
			if(*p == '\0')
				return nil;
			if(*p == ';') {
				p++;
				break;
			}
			if(parsename(&p, &name) < 0)
				return nil;
			if(parsenum1(&p, &n1) < 0)
				return nil;
			if(name[0] == '$') {
				con = erealloc(con, (ncon+1)*sizeof con[0]);
				name++;
				con[ncon].name = name;
				con[ncon].value = n1;
				ncon++;
			}
			if(*p != ',')
				return nil;
			p++;
		}
		break;

	case 'f':	// function
		p++;
		if(parsedef(&p, nil) == nil)
			return nil;
		break;

	case 'r':	// sub-range (used for integers)
		p++;
		if(parsedef(&p, nil) == nil)
			return nil;
		// usually, the return from parsedef == t, but not always.

		if(*p != ';' || *++p == ';') {
			if(stabsdebug)
				fprint(2, "range expected number: %s\n", p);
			return nil;
		}
		if(*p == '-') {
			signlo = '-';
			p++;
		} else
			signlo = '+';
		lo = strtoll(p, &p, 10);
		if(*p != ';' || *++p == ';') {
			if(stabsdebug)
				fprint(2, "range expected number: %s\n", p);
			return nil;
		}
		if(*p == '-') {
			signhi = '-';
			p++;
		} else
			signhi = '+';
		hi = strtoll(p, &p, 10);
		if(*p != ';') {
			if(stabsdebug)
				fprint(2, "range expected trailing semi: %s\n", p);
			return nil;
		}
		p++;
		t->size = hi+1;	// might be array size
		for(i=0; i<nelem(intranges); i++) {
			r = &intranges[i];
			if(r->signlo == signlo && r->signhi == signhi && r->lo == lo && r->hi == hi) {
				t->kind = r->kind;
				break;
			}
		}
		break;

	case 's':	// struct
	case 'u':	// union
		t->kind = Struct;
		if(*p == 'u')
			t->kind = Union;

		// assign given name, but do not record in typ.
		// assume the name came from a typedef
		// which will be recorded.
		if(name)
			t->name = name;
		p++;
		if(parsenum1(&p, &n1) < 0)
			return nil;
		t->size = n1;
		for(;;) {
			if(*p == '\0')
				return nil;
			if(*p == ';') {
				p++;
				break;
			}
			t->f = erealloc(t->f, (t->nf+1)*sizeof t->f[0]);
			f = &t->f[t->nf];
			if(parsename(&p, &f->name) < 0)
				return nil;
			f->type = parsedef(&p, nil);
			if(f->type == nil)
				return nil;
			if(*p != ',') {
				fprint(2, "expected comma after def of %s:\n%s\n", f->name, p);
				return nil;
			}
			p++;
			if(parsenum1(&p, &n1) < 0)
				return nil;
			f->offset = n1;
			if(*p != ',') {
				fprint(2, "expected comma after offset of %s:\n%s\n", f->name, p);
				return nil;
			}
			p++;
			if(parsenum1(&p, &n1) < 0)
				return nil;
			f->size = n1;
			if(*p != ';') {
				fprint(2, "expected semi after size of %s:\n%s\n", f->name, p);
				return nil;
			}

			// rewrite
			//	uint32 x : 8;
			// into
			//	uint8 x;
			// hooray for bitfields.
			while(f->type->kind == Typedef)
				f->type = f->type->type;
			while(Int16 <= f->type->kind && f->type->kind <= Uint64 && kindsize[f->type->kind] > f->size) {
				tt = emalloc(sizeof *tt);
				*tt = *f->type;
				f->type = tt;
				f->type->kind -= 2;
			}
			p++;
			t->nf++;
		}
		break;

	case 'x':
		// reference to struct, union not yet defined.
		p++;
		switch(*p) {
		case 's':
			t->kind = Struct;
			break;
		case 'u':
			t->kind = Union;
			break;
		default:
			fprint(2, "unknown x type char x%c", *p);
			*pp = "";
			return t;
		}
		if(parsename(&p, &t->name) < 0)
			return nil;
		break;
	}
	*pp = p;
	return t;
}


// Parse a stab type in p, saving info in the type hash table
// and also in the list of recorded types if appropriate.
void
parsestabtype(char *p)
{
	char *p0, *name;

	p0 = p;

	// p is the quoted string output from gcc -gstabs on a .stabs line.
	//	name:t(1,2)
	//	name:t(1,2)=def
	if(parsename(&p, &name) < 0) {
	Bad:
		// Use fprint instead of sysfatal to avoid
		// sysfatal's internal buffer size limit.
		fprint(2, "cannot parse stabs type:\n%s\n(at %s)\n", p0, p);
		sysfatal("stabs parse");
	}
	if(*p != 't' && *p != 'T')
		goto Bad;
	p++;

	// parse the definition.
	if(name[0] == '\0')
		name = nil;
	if(parsedef(&p, name) == nil)
		goto Bad;
	if(*p != '\0')
		goto Bad;
}

