shithub: purgatorio

ref: a411870ee4640241e3c494367d922847da84f972
dir: /appl/cmd/limbo/lex.b/

View raw version
Leof:		con -1;
Linestart:	con 0;

Mlower,
Mupper,
Munder,
Mdigit,
Msign,
Mexp,
Mhex,
Mradix:		con byte 1 << iota;
Malpha:		con Mupper|Mlower|Munder;

HashSize:	con 1024;

Keywd: adt
{
	name:	string;
	token:	int;
};

#
# internals
#
savec:		int;
files:		array of ref File;			# files making up the module, sorted by absolute line
nfiles:		int;
lastfile := 0;						# index of last file looked up
incpath :=	array[MaxIncPath] of string;
symbols :=	array[HashSize] of ref Sym;
strings :=	array[HashSize] of ref Sym;
map :=		array[256] of byte;
bins :=		array [MaxInclude] of ref Iobuf;
bin:		ref Iobuf;
linestack :=	array[MaxInclude] of (int, int);
lineno:		int;
linepos:	int;
bstack:		int;
lasttok:	int;
lastyylval:	YYSTYPE;
dowarn:		int;
maxerr:		int;
dosym:		int;
toterrors:	int;
fabort:		int;
srcdir:		string;
outfile:	string;
stderr:		ref Sys->FD;
dontinline:	int;

escmap :=	array[256] of
{
	'\'' =>		'\'',
	'"' =>		'"',
	'\\' =>		'\\',
	'a' =>		'\a',
	'b' =>		'\b',
	'f' =>			'\f',
	'n' =>		'\n',
	'r' =>		'\r',
	't' =>		'\t',
	'v' =>		'\v',
	'0' =>		'\u0000',

	* =>		-1
};
unescmap :=	array[256] of 
{
	'\'' =>		'\'',
	'"' =>		'"',
	'\\' =>		'\\',
	'\a' =>		'a',
	'\b' =>		'b',
	'\f' =>		'f',
	'\n' =>		'n',
	'\r' =>		'r',
	'\t' =>		't',
	'\v' =>		'v',
	'\u0000' =>	'0',

	* =>		0
};

keywords := array [] of
{
	Keywd("adt",		Ladt),
	Keywd("alt",		Lalt),
	Keywd("array",		Larray),
	Keywd("big",		Ltid),
	Keywd("break",		Lbreak),
	Keywd("byte",		Ltid),
	Keywd("case",		Lcase),
	Keywd("chan",		Lchan),
	Keywd("con",		Lcon),
	Keywd("continue",	Lcont),
	Keywd("cyclic",		Lcyclic),
	Keywd("do",		Ldo),
	Keywd("dynamic",	Ldynamic),
	Keywd("else",		Lelse),
	Keywd("exception",	Lexcept),
	Keywd("exit",		Lexit),
	Keywd("fixed",	Lfix),
	Keywd("fn",		Lfn),
	Keywd("for",		Lfor),
	Keywd("hd",		Lhd),
	Keywd("if",		Lif),
	Keywd("implement",	Limplement),
	Keywd("import",		Limport),
	Keywd("include",	Linclude),
	Keywd("int",		Ltid),
	Keywd("len",		Llen),
	Keywd("list",		Llist),
	Keywd("load",		Lload),
	Keywd("module",		Lmodule),
	Keywd("nil",		Lnil),
	Keywd("of",		Lof),
	Keywd("or",		Lor),
	Keywd("pick",		Lpick),
	Keywd("raise",	Lraise),
	Keywd("raises",	Lraises),
	Keywd("real",		Ltid),
	Keywd("ref",		Lref),
	Keywd("return",		Lreturn),
	Keywd("self",		Lself),
	Keywd("spawn",		Lspawn),
	Keywd("string",		Ltid),
	Keywd("tagof",		Ltagof),
	Keywd("tl",		Ltl),
	Keywd("to",		Lto),
	Keywd("type",		Ltype),
	Keywd("while",		Lwhile),
};

tokwords := array[] of
{
	Keywd("&=",	Landeq),
	Keywd("|=",	Loreq),
	Keywd("^=",	Lxoreq),
	Keywd("<<=",	Llsheq),
	Keywd(">>=",	Lrsheq),
	Keywd("+=",	Laddeq),
	Keywd("-=",	Lsubeq),
	Keywd("*=",	Lmuleq),
	Keywd("/=",	Ldiveq),
	Keywd("%=",	Lmodeq),
	Keywd("**=",	Lexpeq),
	Keywd(":=",	Ldeclas),
	Keywd("||",	Loror),
	Keywd("&&",	Landand),
	Keywd("::",	Lcons),
	Keywd("==",	Leq),
	Keywd("!=",	Lneq),
	Keywd("<=",	Lleq),
	Keywd(">=",	Lgeq),
	Keywd("<<",	Llsh),
	Keywd(">>",	Lrsh),
	Keywd("<-",	Lcomm),
	Keywd("++", 	Linc),
	Keywd("--",	Ldec),
	Keywd("->", 	Lmdot),
	Keywd("=>", 	Llabs),
	Keywd("**",	Lexp),
	Keywd("EOF",	Leof),
};

lexinit()
{
	for(i := 0; i < 256; i++){
		map[i] = byte 0;
		if(i == '_' || i > 16ra0)
			map[i] |= Munder;
		if(i >= 'A' && i <= 'Z')
			map[i] |= Mupper;
		if(i >= 'a' && i <= 'z')
			map[i] |= Mlower;
		if(i >= 'A' && i <= 'F' || i >= 'a' && i <= 'f')
			map[i] |= Mhex;
		if(i == 'e' || i == 'E')
			map[i] |= Mexp;
		if(i == 'r' || i == 'R')
			map[i] |= Mradix;
		if(i == '-' || i == '+')
			map[i] |= Msign;
		if(i >= '0' && i <= '9')
			map[i] |= Mdigit;
	}

	for(i = 0; i < len keywords; i++)
		enter(keywords[i].name, keywords[i].token);
}

cmap(c: int): byte
{
	if(c<0)
		return byte 0;
	if(c<256)
		return map[c];
	return Mlower;
}

lexstart(in: string)
{
	savec = 0;
	bstack = 0;
	nfiles = 0;
	addfile(ref File(in, 1, 0, -1, nil, 0, -1));
	bin = bins[bstack];
	lineno = 1;
	linepos = Linestart;

	(srcdir, nil) = str->splitr(in, "/");
}

getc(): int
{
	if(c := savec){
		if(savec >= 0){
			linepos++;
			savec = 0;
		}
		return c;
	}
	c = bin.getc();
	if(c < 0){
		savec = -1;
		return savec;
	}
	linepos++;
	return c;
}

#
# dumps '\u0000' chararcters
#
ungetc(c: int)
{
	if(c > 0)
		linepos--;
	savec = c;
}

addinclude(s: string)
{
	for(i := 0; i < MaxIncPath; i++){
		if(incpath[i] == nil){
			incpath[i] = s;
			return;
		}
	}
	fatal("out of include path space");
}

addfile(f: ref File): int
{
	if(lastfile >= nfiles)
		lastfile = 0;
	if(nfiles >= len files){
		nf := array[nfiles+32] of ref File;
		nf[0:] = files;
		files = nf;
	}
	files[nfiles] = f;
	return nfiles++;
}

#
# include a new file
#
includef(file: ref Sym)
{
	linestack[bstack] = (lineno, linepos);
	bstack++;
	if(bstack >= MaxInclude)
		fatal(lineconv(lineno<<PosBits)+": include file depth too great");
	buf := file.name;
	if(buf[0] != '/')
		buf = srcdir+buf;
	b := bufio->open(buf, Bufio->OREAD);
	for(i := 0; b == nil && i < MaxIncPath && incpath[i] != nil && file.name[0] != '/'; i++){
		buf = incpath[i] + "/" + file.name;
		b = bufio->open(buf, Bufio->OREAD);
	}
	bins[bstack] = b;
	if(bins[bstack] == nil){
		yyerror("can't include "+file.name+": "+sprint("%r"));
		bstack--;
	}else{
		addfile(ref File(buf, lineno+1, -lineno, lineno, nil, 0, -1));
		lineno++;
		linepos = Linestart;
	}
	bin = bins[bstack];
}

#
# we hit eof in the current file
# revert to the file which included it.
#
popinclude()
{
	savec = 0;
	bstack--;
	bin = bins[bstack];
	(oline, opos) := linestack[bstack];
	(f, ln) := fline(oline);
	lineno++;
	linepos = opos;
	addfile(ref File(f.name, lineno, ln-lineno, f.in, f.act, f.actoff, -1));
}

#
# convert an absolute Line into a file and line within the file
#
fline(absline: int): (ref File, int)
{
	if(absline < files[lastfile].abs
	|| lastfile+1 < nfiles && absline >= files[lastfile+1].abs){
		lastfile = 0;
		l := 0;
		r := nfiles - 1;
		while(l <= r){
			m := (r + l) / 2;
			s := files[m].abs;
			if(s <= absline){
				l = m + 1;
				lastfile = m;
			}else
				r = m - 1;
		}
	}
	return (files[lastfile], absline + files[lastfile].off);
}

#
# read a comment; process #line file renamings
#
lexcom(): int
{
	i := 0;
	buf := "";
	while((c := getc()) != '\n'){
		if(c == Bufio->EOF)
			return -1;
		buf[i++] = c;
	}

	lineno++;
	linepos = Linestart;

	if(len buf < 6
	|| buf[len buf - 1] != '"'
	|| buf[:5] != "line " && buf[:5] != "line\t")
		return 0;
	for(s := 5; buf[s] == ' ' || buf[s] == '\t'; s++)
		;
	if((cmap(buf[s]) & Mdigit) == byte 0)
		return 0;
	n := 0;
	for(; (cmap(c = buf[s]) & Mdigit) != byte 0; s++)
		n = n * 10 + c - '0';
	for(; buf[s] == ' ' || buf[s] == '\t'; s++)
		;
	if(buf[s++] != '"')
		return 0;
	buf = buf[s:len buf - 1];
	f := files[nfiles - 1];
	if(n == f.off+lineno && buf == f.name)
		return 1;
	act := f.name;
	actline := lineno + f.off;
	if(f.act != nil){
		actline += f.actoff;
		act = f.act;
	}
	addfile(ref File(buf, lineno, n-lineno, f.in, act, actline - n, -1));

	return 1;
}

curline(): Line
{
	return (lineno << PosBits) | (linepos & PosMask);
}

lineconv(line: Line): string
{
	line >>= PosBits;
	if(line < 0)
		return "<noline>";
	(f, ln) := fline(line);
	s := "";
	if(f.in >= 0){
		s = ": " + lineconv(f.in << PosBits);
	}
	if(f.act != nil)
		s = " [ " + f.act + ":" + string(f.actoff+ln) + " ]" + s;
	return f.name + ":" + string ln + s;
}

posconv(s: Line): string
{
	if(s < 0)
		return "nopos";
	spos := s & PosMask;
	s >>= PosBits;
	(f, ln) := fline(s);
	return f.name + ":" + string ln + "." + string spos;
}

srcconv(src: Src): string
{
	s := posconv(src.start);
	s[len s] = ',';
	s += posconv(src.stop);
	return s;
}

lexid(c: int): int
{
	id := "";
	i := 0;
	for(;;){
		if(i < StrSize)
			id[i++] = c;
		c = getc();
		if(c == Bufio->EOF
		|| (cmap(c) & (Malpha|Mdigit)) == byte 0){
			ungetc(c);
			break;
		}
	}
	sym := enter(id, Lid);
	t := sym.token;
	if(t == Lid || t == Ltid)
		yyctxt.lval.tok.v.idval = sym;
	return t;
}

maxfast := array[37] of
{
	2 =>	31,
	4 =>	15,
	8 =>	10,
	10 =>	9,
	16 =>	7,
	32 =>	6,
	* =>	0,
};

strtoi(t: string, bbase: big): big
{
	#
	# do the first part in ints
	#
	v := 0;
	bv: big;
	base := int bbase;
	n := maxfast[base];

	neg := 0;
	i := 0;
	if(i < len t && t[i] == '-'){
		neg = 1;
		i++;
	}else if(i < len t && t[i] == '+')
		i++;

	for(; i < len t; i++){
		c := t[i];
		if(c >= '0' && c <= '9')
			c -= '0';
		else if(c >= 'a' && c <= 'z')
			c -= 'a' - 10;
		else
			c -= 'A' - 10;
		if(c >= base){
			yyerror("digit '"+t[i:i+1]+"' is not radix "+string base);
			return big -1;
		}
		if(i < n)
			v = v * base + c;
		else{
			if(i == n)
				bv = big v;
			bv = bv * bbase + big c;
		}
	}
	if(i <= n)
		bv = big v;
	if(neg)
		return -bv;
	return bv;
}

digit(c: int, base: int): int
{
	ck: byte;
	cc: int;

	cc = c;
	ck = cmap(c);
	if((ck & Mdigit) != byte 0)
		c -= '0';
	else if((ck & Mlower) != byte 0)
		c = c - 'a' + 10;
	else if((ck & Mupper) != byte 0)
		c = c - 'A' + 10;
	else if((ck & Munder) != byte 0)
		;
	else
		return -1;
	if(c >= base){
		s := "z";
		s[0] = cc;
		yyerror("digit '" + s + "' not radix " + string base);
	}
	return c;
}

strtodb(t: string, base: int): real
{
	num, dem, rbase: real;
	neg, eneg, dig, exp, c, d: int;

	t[len t] = 0;

	num = 0.0;
	rbase = real base;
	neg = 0;
	dig = 0;
	exp = 0;
	eneg = 0;

	i := 0;
	c = t[i++];
	if(c == '-' || c == '+'){
		if(c == '-')
			neg = 1;
		c = t[i++];
	}
	while((d = digit(c, base)) >= 0){
		num = num*rbase + real d;
		c = t[i++];
	}
	if(c == '.')
		c = t[i++];
	while((d = digit(c, base)) >= 0){
		num = num*rbase + real d;
		dig++;
		c = t[i++];
	}
	if(c == 'e' || c == 'E'){
		c = t[i++];
		if(c == '-' || c == '+'){
			if(c == '-'){
				dig = -dig;
				eneg = 1;
			}
			c = t[i++];
		}
		while((d = digit(c, base)) >= 0){
			exp = exp*base + d;
			c = t[i++];
		}
	}
	exp -= dig;
	if(exp < 0){
		exp = -exp;
		eneg = !eneg;
	}
	dem = rpow(rbase, exp);
	if(eneg)
		num /= dem;
	else
		num *= dem;
	if(neg)
		return -num;
	return num;
}

#
# parse a numeric identifier
# format [0-9]+(r[0-9A-Za-z]+)?
# or ([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?
#
lexnum(c: int): int
{
	Int, Radix, RadixSeen, Frac, ExpSeen, ExpSignSeen, Exp, FracB: con iota;

	i := 0;
	buf := "";
	buf[i++] = c;
	state := Int;
	if(c == '.')
		state = Frac;
	radix := "";

done:	for(;;){
		c = getc();
		if(c == Bufio->EOF){
			yyerror("end of file in numeric constant");
			return Leof;
		}

		ck := cmap(c);
		case state{
		Int =>
			if((ck & Mdigit) != byte 0)
				break;
			if((ck & Mexp) != byte 0){
				state = ExpSeen;
				break;
			}
			if((ck & Mradix) != byte 0){
				radix = buf;
				buf = "";
				i = 0;
				state = RadixSeen;
				break;
			}
			if(c == '.'){
				state = Frac;
				break;
			}
			break done;
		RadixSeen or
		Radix =>
			if((ck & (Mdigit|Malpha)) != byte 0){
				state = Radix;
				break;
			}
			if(c == '.'){
				state = FracB;
				break;
			}
			break done;
		Frac =>
			if((ck & Mdigit) != byte 0)
				break;
			if((ck & Mexp) != byte 0)
				state = ExpSeen;
			else
				break done;
		FracB =>
			if((ck & (Mdigit|Malpha)) != byte 0)
				break;
			break done;
		ExpSeen =>
			if((ck & Msign) != byte 0){
				state = ExpSignSeen;
				break;
			}
			if((ck & Mdigit) != byte 0){
				state = Exp;
				break;
			}
			break done;
		ExpSignSeen or
		Exp =>
			if((ck & Mdigit) != byte 0){
				state = Exp;
				break;
			}
			break done;
		}
		buf[i++] = c;
	}

	ungetc(c);
	v: big;
	case state{
	* =>
		yyerror("malformed numerical constant '"+radix+buf+"'");
		yyctxt.lval.tok.v.ival = big 0;
		return Lconst;
	Radix =>
		v = strtoi(radix, big 10);
		if(v < big 2 || v > big 36){
			yyerror("radix '"+radix+"' is not between 2 and 36");
			break;
		}
		v = strtoi(buf[1:], v);
	Int =>
		v = strtoi(buf, big 10);
	Frac or
	Exp =>
		yyctxt.lval.tok.v.rval = real buf;
		return Lrconst;
	FracB =>
		v = strtoi(radix, big 10);
		if(v < big 2 || v > big 36){
			yyerror("radix '"+radix+"' is not between 2 and 36");
			break;
		}
		yyctxt.lval.tok.v.rval = strtodb(buf[1:], int v);
		return Lrconst;
	}
	yyctxt.lval.tok.v.ival = v;
	return Lconst;
}

escchar(): int
{
	c := getc();
	if(c == Bufio->EOF)
		return Bufio->EOF;
	if(c == 'u'){
		v := 0;
		for(i := 0; i < 4; i++){
			c = getc();
			ck := cmap(c);
			if(c == Bufio->EOF || (ck & (Mdigit|Mhex)) == byte 0){
				yyerror("malformed \\u escape sequence");
				ungetc(c);
				break;
			}
			if((ck & Mdigit) != byte 0)
				c -= '0';
			else if((ck & Mlower) != byte 0)
				c = c - 'a' + 10;
			else if((ck & Mupper) != byte 0)
				c = c - 'A' + 10;
			v = v * 16 + c;
		}
		return v;
	}
	if(c < len escmap && (v := escmap[c]) >= 0)
		return v;
	s := "";
	s[0] = c;
	yyerror("unrecognized escape \\"+s);
	return c;
}

lexstring()
{
	s := "";
	i := 0;
Loop:
	for(;;){
		case c := getc(){
		'\\' =>
			c = escchar();
			if(c != Bufio->EOF)
				s[i++] = c;
		Bufio->EOF =>
			yyerror("end of file in string constant");
			break Loop;
		'\n' =>
			yyerror("newline in string constant");
			lineno++;
			linepos = Linestart;
			break Loop;
		'"' =>
			break Loop;
		* =>
			s[i++] = c;
		}
	}
	yyctxt.lval.tok.v.idval = enterstring(s);
}

lexrawstring()
{
	s := "";
	i := 0;
	startlno := lineno;
Loop:
	for(;;){
		case c := getc(){
		Bufio->EOF =>
			t := lineno;
			lineno = startlno;
			yyerror("end of file in raw string constant");
			lineno = t;
			break Loop;
		'\n' =>
			s[i++] = c;
			lineno++;
			linepos = Linestart;
		'`' =>
			break Loop;
		* =>
			s[i++] = c;
		}
	}
	yyctxt.lval.tok.v.idval = enterstring(s);
}

lex(): int
{
	for(;;){
		yyctxt.lval.tok.src.start = (lineno << PosBits) | (linepos & PosMask);
		case c := getc(){
		Bufio->EOF =>
			bin.close();
			if(bstack == 0)
				return Leof;
			popinclude();
		'#' =>
			if(lexcom() < 0){
				bin.close();
				if(bstack == 0)
					return Leof;
				popinclude();
			}
		'\n' =>
			lineno++;
			linepos = Linestart;
		' ' or
		'\t' or
		'\r' or
		'\v' =>
			;
		'"' =>
			lexstring();
			return Lsconst;
		'`' =>
			lexrawstring();
			return Lsconst;
		'\'' =>
			c = getc();
			if(c == '\\')
				c = escchar();
			if(c == Bufio->EOF){
				yyerror("end of file in character constant");
				return Bufio->EOF;
			}else
				yyctxt.lval.tok.v.ival = big c;
			c = getc();
			if(c != '\''){
				yyerror("missing closing '");
				ungetc(c);
			}
			return Lconst;
		'(' or
		')' or
		'[' or
		']' or
		'{' or
		'}' or
		',' or
		';' or
		'~' =>
			return c;
		':' =>
			c = getc();
			if(c == ':')
				return Lcons;
			if(c == '=')
				return Ldeclas;
			ungetc(c);
			return ':';
		'.' =>
			c = getc();
			ungetc(c);
			if(c != Bufio->EOF && (cmap(c) & Mdigit) != byte 0)
				return lexnum('.');
			return '.';
		'|' =>
			c = getc();
			if(c == '=')
				return Loreq;
			if(c == '|')
				return Loror;
			ungetc(c);
			return '|';
		'&' =>
			c = getc();
			if(c == '=')
				return Landeq;
			if(c == '&')
				return Landand;
			ungetc(c);
			return '&';
		'^' =>
			c = getc();
			if(c == '=')
				return Lxoreq;
			ungetc(c);
			return '^';
		'*' =>
			c = getc();
			if(c == '=')
				return Lmuleq;
			if(c == '*'){
				c = getc();
				if(c == '=')
					return Lexpeq;
				ungetc(c);
				return Lexp;
			}
			ungetc(c);
			return '*';
		'/' =>
			c = getc();
			if(c == '=')
				return Ldiveq;
			ungetc(c);
			return '/';
		'%' =>
			c = getc();
			if(c == '=')
				return Lmodeq;
			ungetc(c);
			return '%';
		'=' =>
			c = getc();
			if(c == '=')
				return Leq;
			if(c == '>')
				return Llabs;
			ungetc(c);
			return '=';
		'!' =>
			c = getc();
			if(c == '=')
				return Lneq;
			ungetc(c);
			return '!';
		'>' =>
			c = getc();
			if(c == '=')
				return Lgeq;
			if(c == '>'){
				c = getc();
				if(c == '=')
					return Lrsheq;
				ungetc(c);
				return Lrsh;
			}
			ungetc(c);
			return '>';
		'<' =>
			c = getc();
			if(c == '=')
				return Lleq;
			if(c == '-')
				return Lcomm;
			if(c == '<'){
				c = getc();
				if(c == '=')
					return Llsheq;
				ungetc(c);
				return Llsh;
			}
			ungetc(c);
			return '<';
		'+' =>
			c = getc();
			if(c == '=')
				return Laddeq;
			if(c == '+')
				return Linc;
			ungetc(c);
			return '+';
		'-' =>
			c = getc();
			if(c == '=')
				return Lsubeq;
			if(c == '-')
				return Ldec;
			if(c == '>')
				return Lmdot;
			ungetc(c);
			return '-';
		'0' to '9' =>
			return lexnum(c);
		* =>
			if((cmap(c) & Malpha) != byte 0)
				return lexid(c);
			s := "";
			s[0] = c;
			yyerror("unknown character '"+s+"'");
		}
	}
}

YYLEX.lex(nil: self ref YYLEX): int
{
	t := lex();
	yyctxt.lval.tok.src.stop = (lineno << PosBits) | (linepos & PosMask);
	lasttok = t;
	lastyylval = yyctxt.lval;
	return t;
}

toksp(t: int): string
{
	case(t){
		Lconst =>
			return sprint("%bd", lastyylval.tok.v.ival);
		Lrconst =>
			return sprint("%f", lastyylval.tok.v.rval);
		Lsconst =>
			return sprint("\"%s\"", lastyylval.tok.v.idval.name);
		Ltid or Lid =>
			return lastyylval.tok.v.idval.name;
	}
	for(i := 0; i < len keywords; i++)
		if(t == keywords[i].token)
			return keywords[i].name;
	for(i = 0; i < len tokwords; i++)
		if(t == tokwords[i].token)
			return tokwords[i].name;
	if(t < 0 || t > 255)
		fatal(sprint("bad token %d in toksp()", t));
	buf := "Z";
	buf[0] = t;
	return buf;
}

enterstring(name: string): ref Sym
{
	h := 0;
	n := len name;
	for(i := 0; i < n; i++){
		c := d := name[i];
		c ^= c << 6;
		h += (c << 11) ^ (c >> 1);
		h ^= (d << 14) + (d << 7) + (d << 4) + d;
	}

	h &= HashSize-1;
	for(s := strings[h]; s != nil; s = s.next){
		sn := s.name;
		if(len sn == n && sn == name)
			return s;
	}


	s = ref Sym;
	s.token = -1;
	s.name = name;
	s.hash = h;
	s.next = strings[h];
	strings[h] = s;
	return s;
}

stringcat(s, t: ref Sym): ref Sym
{
	return enterstring(s.name+t.name);
}

enter(name: string, token: int): ref Sym
{
	h := 0;
	n := len name;
	for(i := 0; i < n; i++){
		c := d := name[i];
		c ^= c << 6;
		h += (c << 11) ^ (c >> 1);
		h ^= (d << 14) + (d << 7) + (d << 4) + d;
	}

	h &= HashSize-1;
	for(s := symbols[h]; s != nil; s = s.next){
		sn := s.name;
		if(len sn == n && sn == name)
			return s;
	}

	if(token == 0)
		token = Lid;
	s = ref Sym;
	s.token = token;
	s.name = name;
	s.hash = h;
	s.next = symbols[h];
	symbols[h] = s;
	return s;
}

stringpr(sym: ref Sym): string
{
	s := sym.name;
	n := len s;
	if(n > 10)
		n = 10;
	sb := "\"";
	for(i := 0; i < n; i++){
		case c := s[i]{
		'\\' or
		'"' or
		'\n' or
		'\r' or
		'\t' or
		'\b' or
		'\a' or
		'\v' or
		'\u0000' =>
			sb[len sb] = '\\';
			sb[len sb] = unescmap[c];
		* =>
			sb[len sb] = c;
		}
	}
	if(n != len s)
		sb += "...";
	sb[len sb] = '"';
	return sb;
}

warn(line: Line, msg: string)
{
	if(errors || !dowarn)
		return;
	fprint(stderr, "%s: warning: %s\n", lineconv(line), msg);
}

nwarn(n: ref Node, msg: string)
{
	if(errors || !dowarn)
		return;
	fprint(stderr, "%s: warning: %s\n", lineconv(n.src.start), msg);
}

error(line: Line, msg: string)
{
	errors++;
	if(errors > maxerr)
		return;
	fprint(stderr, "%s: %s\n", lineconv(line), msg);
	if(errors == maxerr)
		fprint(stderr, "too many errors, stopping\n");
}

nerror(n: ref Node, msg: string)
{
	errors++;
	if(errors > maxerr)
		return;
	fprint(stderr, "%s: %s\n", lineconv(n.src.start), msg);
	if(errors == maxerr)
		fprint(stderr, "too many errors, stopping\n");
}

YYLEX.error(nil: self ref YYLEX, msg: string)
{
	errors++;
	if(errors > maxerr)
		return;
	if(lasttok != 0)
		fprint(stderr, "%s: near ` %s ` : %s\n", lineconv(lineno<<PosBits), toksp(lasttok), msg);
	else
		fprint(stderr, "%s: %s\n", lineconv(lineno<<PosBits), msg);
	if(errors == maxerr)
		fprint(stderr, "too many errors, stopping\n");
}

yyerror(msg: string)
{
	yyctxt.error(msg);
}

fatal(msg: string)
{
	if(errors == 0 || fabort)
		fprint(stderr, "fatal limbo compiler error: %s\n", msg);
	if(bout != nil)
		sys->remove(outfile);
	if(fabort){
		n: ref Node;
		if(n.ty == nil);	# abort
	}
	raise "fail:error";
}

hex(v, n: int): string
{
	return sprint("%.*ux", n, v);
}

bhex(v: big, n: int): string
{
	return sprint("%.*bux", n, v);
}