text: // 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.
  
  #include	<u.h>
  #include	<libc.h>
  #include	"go.h"
  #include	"y.tab.h"
  #include	<ar.h>
  
  #undef	getc
  #undef	ungetc
  #define	getc	ccgetc
  #define	ungetc	ccungetc
  
  extern int yychar;
  int windows;
  int yyprev;
  int yylast;
  
  static void	lexinit(void);
  static void	lexinit1(void);
  static void	lexfini(void);
  static void	yytinit(void);
  static int	getc(void);
  static void	ungetc(int);
  static int32	getr(void);
  static int	escchar(int, int*, vlong*);
  static void	addidir(char*);
  static int	getlinepragma(void);
  static char *goos, *goarch, *goroot;
  
  #define	BOM	0xFEFF
  
  // Compiler experiments.
  // These are controlled by the GOEXPERIMENT environment
  // variable recorded when the compiler is built.
  static struct {
  	char *name;
  	int *val;
  } exper[] = {
  //	{"rune32", &rune32},
  	{"fieldtrack", &fieldtrack_enabled},
  	{nil, nil},
  };
  
  static void
  addexp(char *s)
  {
  	int i;
  	
  	for(i=0; exper[i].name != nil; i++) {
  		if(strcmp(exper[i].name, s) == 0) {
  			*exper[i].val = 1;
  			return;
  		}
  	}
  	
  	print("unknown experiment %s\n", s);
  	exits("unknown experiment");
  }
  
  static void
  setexp(void)
  {
  	char *f[20];
  	int i, nf;
  	
  	// The makefile #defines GOEXPERIMENT for us.
  	nf = getfields(GOEXPERIMENT, f, nelem(f), 1, ",");
  	for(i=0; i<nf; i++)
  		addexp(f[i]);
  }
  
  char*
  expstring(void)
  {
  	int i;
  	static char buf[512];
  
  	strcpy(buf, "X");
  	for(i=0; exper[i].name != nil; i++)
  		if(*exper[i].val)
  			seprint(buf+strlen(buf), buf+sizeof buf, ",%s", exper[i].name);
  	if(strlen(buf) == 1)
  		strcpy(buf, "X,none");
  	buf[1] = ':';
  	return buf;
  }
  
  // Our own isdigit, isspace, isalpha, isalnum that take care 
  // of EOF and other out of range arguments.
  static int
  yy_isdigit(int c)
  {
  	return c >= 0 && c <= 0xFF && isdigit(c);
  }
  
  static int
  yy_isspace(int c)
  {
  	return c == ' ' || c == '\t' || c == '\n' || c == '\r';
  }
  
  static int
  yy_isalpha(int c)
  {
  	return c >= 0 && c <= 0xFF && isalpha(c);
  }
  
  static int
  yy_isalnum(int c)
  {
  	return c >= 0 && c <= 0xFF && isalnum(c);
  }
  
  // Disallow use of isdigit etc.
  #undef isdigit
  #undef isspace
  #undef isalpha
  #undef isalnum
  #define isdigit use_yy_isdigit_instead_of_isdigit
  #define isspace use_yy_isspace_instead_of_isspace
  #define isalpha use_yy_isalpha_instead_of_isalpha
  #define isalnum use_yy_isalnum_instead_of_isalnum
  
  #define	DBG	if(!debug['x']){}else print
  enum
  {
  	EOF		= -1,
  };
  
  void
  usage(void)
  {
  	print("usage: %cg [options] file.go...\n", thechar);
  	flagprint(1);
  	exits("usage");
  }
  
  void
  fault(int s)
  {
  	USED(s);
  
  	// If we've already complained about things
  	// in the program, don't bother complaining
  	// about the seg fault too; let the user clean up
  	// the code and try again.
  	if(nsavederrors + nerrors > 0)
  		errorexit();
  	fatal("fault");
  }
  
  void
  doversion(void)
  {
  	char *p;
  
  	p = expstring();
  	if(strcmp(p, "X:none") == 0)
  		p = "";
  	print("%cg version %s%s%s\n", thechar, getgoversion(), *p ? " " : "", p);
  	exits(0);
  }
  
  int
  main(int argc, char *argv[])
  {
  	int i;
  	NodeList *l;
  	char *p;
  
  #ifdef	SIGBUS	
  	signal(SIGBUS, fault);
  	signal(SIGSEGV, fault);
  #endif
  
  	localpkg = mkpkg(strlit(""));
  	localpkg->prefix = "\"\"";
  	
  	// pseudo-package, for scoping
  	builtinpkg = mkpkg(strlit("go.builtin"));
  
  	// pseudo-package, accessed by import "unsafe"
  	unsafepkg = mkpkg(strlit("unsafe"));
  	unsafepkg->name = "unsafe";
  
  	// real package, referred to by generated runtime calls
  	runtimepkg = mkpkg(strlit("runtime"));
  	runtimepkg->name = "runtime";
  
  	// pseudo-packages used in symbol tables
  	gostringpkg = mkpkg(strlit("go.string"));
  	gostringpkg->name = "go.string";
  	gostringpkg->prefix = "go.string";	// not go%2estring
  
  	itabpkg = mkpkg(strlit("go.itab"));
  	itabpkg->name = "go.itab";
  	itabpkg->prefix = "go.itab";	// not go%2eitab
  
  	weaktypepkg = mkpkg(strlit("go.weak.type"));
  	weaktypepkg->name = "go.weak.type";
  	weaktypepkg->prefix = "go.weak.type";  // not go%2eweak%2etype
  	
  	typelinkpkg = mkpkg(strlit("go.typelink"));
  	typelinkpkg->name = "go.typelink";
  	typelinkpkg->prefix = "go.typelink"; // not go%2etypelink
  
  	trackpkg = mkpkg(strlit("go.track"));
  	trackpkg->name = "go.track";
  	trackpkg->prefix = "go.track";  // not go%2etrack
  
  	typepkg = mkpkg(strlit("type"));
  	typepkg->name = "type";
  
  	goroot = getgoroot();
  	goos = getgoos();
  	goarch = thestring;
  	
  	setexp();
  
  	outfile = nil;
  	flagcount("+", "compiling runtime", &compiling_runtime);
  	flagcount("%", "debug non-static initializers", &debug['%']);
  	flagcount("A", "for bootstrapping, allow 'any' type", &debug['A']);
  	flagcount("B", "disable bounds checking", &debug['B']);
  	flagstr("D", "path: set relative path for local imports", &localimport);
  	flagcount("E", "debug symbol export", &debug['E']);
  	flagfn1("I", "dir: add dir to import search path", addidir);
  	flagcount("K", "debug missing line numbers", &debug['K']);
  	flagcount("L", "use full (long) path in error messages", &debug['L']);
  	flagcount("M", "debug move generation", &debug['M']);
  	flagcount("N", "disable optimizations", &debug['N']);
  	flagcount("P", "debug peephole optimizer", &debug['P']);
  	flagcount("R", "debug register optimizer", &debug['R']);
  	flagcount("S", "print assembly listing", &debug['S']);
  	flagfn0("V", "print compiler version", doversion);
  	flagcount("W", "debug parse tree after type checking", &debug['W']);
  	flagcount("complete", "compiling complete package (no C or assembly)", &pure_go);
  	flagcount("d", "debug declarations", &debug['d']);
  	flagcount("e", "no limit on number of errors reported", &debug['e']);
  	flagcount("f", "debug stack frames", &debug['f']);
  	flagcount("g", "debug code generation", &debug['g']);
  	flagcount("h", "halt on error", &debug['h']);
  	flagcount("i", "debug line number stack", &debug['i']);
  	flagcount("j", "debug runtime-initialized variables", &debug['j']);
  	flagcount("l", "disable inlining", &debug['l']);
  	flagcount("m", "print optimization decisions", &debug['m']);
  	flagstr("o", "obj: set output file", &outfile);
  	flagstr("p", "path: set expected package import path", &myimportpath);
  	flagcount("r", "debug generated wrappers", &debug['r']);
  	flagcount("race", "enable race detector", &flag_race);
  	flagcount("s", "warn about composite literals that can be simplified", &debug['s']);
  	flagcount("u", "reject unsafe code", &safemode);
  	flagcount("v", "increase debug verbosity", &debug['v']);
  	flagcount("w", "debug type checking", &debug['w']);
  	flagcount("x", "debug lexer", &debug['x']);
  	flagcount("y", "debug declarations in canned imports (with -d)", &debug['y']);
  	if(thechar == '6')
  		flagcount("largemodel", "generate code that assumes a large memory model", &flag_largemodel);
  
  	flagparse(&argc, &argv, usage);
  
  	if(argc < 1)
  		usage();
  
  	if(flag_race) {
  		racepkg = mkpkg(strlit("runtime/race"));
  		racepkg->name = "race";
  	}
  
  	// enable inlining.  for now:
  	//	default: inlining on.  (debug['l'] == 1)
  	//	-l: inlining off  (debug['l'] == 0)
  	//	-ll, -lll: inlining on again, with extra debugging (debug['l'] > 1)
  	if(debug['l'] <= 1)
  		debug['l'] = 1 - debug['l'];
  
  	if(thechar == '8') {
  		p = getgo386();
  		if(strcmp(p, "387") == 0)
  			use_sse = 0;
  		else if(strcmp(p, "sse2") == 0)
  			use_sse = 1;
  		else
  			sysfatal("unsupported setting GO386=%s", p);
  	}
  
  	pathname = mal(1000);
  	if(getwd(pathname, 999) == 0)
  		strcpy(pathname, "/???");
  
  	if(yy_isalpha(pathname[0]) && pathname[1] == ':') {
  		// On Windows.
  		windows = 1;
  
  		// Canonicalize path by converting \ to / (Windows accepts both).
  		for(p=pathname; *p; p++)
  			if(*p == '\\')
  				*p = '/';
  	}
  
  	fmtinstallgo();
  	betypeinit();
  	if(widthptr == 0)
  		fatal("betypeinit failed");
  
  	lexinit();
  	typeinit();
  	lexinit1();
  	yytinit();
  
  	blockgen = 1;
  	dclcontext = PEXTERN;
  	nerrors = 0;
  	lexlineno = 1;
  
  	for(i=0; i<argc; i++) {
  		infile = argv[i];
  		linehist(infile, 0, 0);
  
  		curio.infile = infile;
  		curio.bin = Bopen(infile, OREAD);
  		if(curio.bin == nil) {
  			print("open %s: %r\n", infile);
  			errorexit();
  		}
  		curio.peekc = 0;
  		curio.peekc1 = 0;
  		curio.nlsemi = 0;
  
  		// Skip initial BOM if present.
  		if(Bgetrune(curio.bin) != BOM)
  			Bungetrune(curio.bin);
  
  		block = 1;
  		iota = -1000000;
  
  		yyparse();
  		if(nsyntaxerrors != 0)
  			errorexit();
  
  		linehist(nil, 0, 0);
  		if(curio.bin != nil)
  			Bterm(curio.bin);
  	}
  	testdclstack();
  	mkpackage(localpkg->name);	// final import not used checks
  	lexfini();
  
  	typecheckok = 1;
  	if(debug['f'])
  		frame(1);
  
  	// Process top-level declarations in phases.
  
  	// Phase 1: const, type, and names and types of funcs.
  	//   This will gather all the information about types
  	//   and methods but doesn't depend on any of it.
  	defercheckwidth();
  	for(l=xtop; l; l=l->next)
  		if(l->n->op != ODCL && l->n->op != OAS)
  			typecheck(&l->n, Etop);
  
  	// Phase 2: Variable assignments.
  	//   To check interface assignments, depends on phase 1.
  	for(l=xtop; l; l=l->next)
  		if(l->n->op == ODCL || l->n->op == OAS)
  			typecheck(&l->n, Etop);
  	resumecheckwidth();
  
  	// Phase 3: Type check function bodies.
  	for(l=xtop; l; l=l->next) {
  		if(l->n->op == ODCLFUNC || l->n->op == OCLOSURE) {
  			curfn = l->n;
  			saveerrors();
  			typechecklist(l->n->nbody, Etop);
  			checkreturn(l->n);
  			if(nerrors != 0)
  				l->n->nbody = nil;  // type errors; do not compile
  		}
  	}
  
  	curfn = nil;
  	
  	if(nsavederrors+nerrors)
  		errorexit();
  
  	// Phase 4: Inlining
  	if(debug['l'] > 1) {
  		// Typecheck imported function bodies if debug['l'] > 1,
  		// otherwise lazily when used or re-exported.
  		for(l=importlist; l; l=l->next)
  			if (l->n->inl) {
  				saveerrors();
  				typecheckinl(l->n);
  			}
  		
  		if(nsavederrors+nerrors)
  			errorexit();
  	}
  
  	if(debug['l']) {
  		// Find functions that can be inlined and clone them before walk expands them.
  		for(l=xtop; l; l=l->next)
  			if(l->n->op == ODCLFUNC)
  				caninl(l->n);
  		
  		// Expand inlineable calls in all functions
  		for(l=xtop; l; l=l->next)
  			if(l->n->op == ODCLFUNC)
  				inlcalls(l->n);
  	}
  
  	// Phase 5: Escape analysis.
  	if(!debug['N'])
  		escapes(xtop);
  
  	// Phase 6: Compile top level functions.
  	for(l=xtop; l; l=l->next)
  		if(l->n->op == ODCLFUNC)
  			funccompile(l->n, 0);
  
  	if(nsavederrors+nerrors == 0)
  		fninit(xtop);
  
  	// Phase 7: Check external declarations.
  	for(l=externdcl; l; l=l->next)
  		if(l->n->op == ONAME)
  			typecheck(&l->n, Erv);
  
  	if(nerrors+nsavederrors)
  		errorexit();
  
  	dumpobj();
  
  	if(nerrors+nsavederrors)
  		errorexit();
  
  	flusherrors();
  	exits(0);
  	return 0;
  }
  
  void
  saveerrors(void)
  {
  	nsavederrors += nerrors;
  	nerrors = 0;
  }
  
  /*
   *	macro to portably read/write archive header.
   *	'cmd' is read/write/Bread/Bwrite, etc.
   */
  #define	HEADER_IO(cmd, f, h)	cmd(f, h.name, sizeof(h.name)) != sizeof(h.name)\
  				|| cmd(f, h.date, sizeof(h.date)) != sizeof(h.date)\
  				|| cmd(f, h.uid, sizeof(h.uid)) != sizeof(h.uid)\
  				|| cmd(f, h.gid, sizeof(h.gid)) != sizeof(h.gid)\
  				|| cmd(f, h.mode, sizeof(h.mode)) != sizeof(h.mode)\
  				|| cmd(f, h.size, sizeof(h.size)) != sizeof(h.size)\
  				|| cmd(f, h.fmag, sizeof(h.fmag)) != sizeof(h.fmag)
  
  static int
  arsize(Biobuf *b, char *name)
  {
  	struct ar_hdr a;
  
  	if (HEADER_IO(Bread, b, a))
  		return -1;
  
  	if(strncmp(a.name, name, strlen(name)) != 0)
  		return -1;
  
  	return atoi(a.size);
  }
  
  static int
  skiptopkgdef(Biobuf *b)
  {
  	char *p;
  	int sz;
  
  	/* archive header */
  	if((p = Brdline(b, '\n')) == nil)
  		return 0;
  	if(Blinelen(b) != 8)
  		return 0;
  	if(memcmp(p, "!<arch>\n", 8) != 0)
  		return 0;
  	/* symbol table is first; skip it */
  	sz = arsize(b, "__.GOSYMDEF");
  	if(sz < 0)
  		return 0;
  	Bseek(b, sz, 1);
  	/* package export block is second */
  	sz = arsize(b, "__.PKGDEF");
  	if(sz <= 0)
  		return 0;
  	return 1;
  }
  
  static void
  addidir(char* dir)
  {
  	Idir** pp;
  
  	if(dir == nil)
  		return;
  
  	for(pp = &idirs; *pp != nil; pp = &(*pp)->link)
  		;
  	*pp = mal(sizeof(Idir));
  	(*pp)->link = nil;
  	(*pp)->dir = dir;
  }
  
  // is this path a local name?  begins with ./ or ../ or /
  static int
  islocalname(Strlit *name)
  {
  	if(name->len >= 1 && name->s[0] == '/')
  		return 1;
  	if(windows && name->len >= 3 &&
  	   yy_isalpha(name->s[0]) && name->s[1] == ':' && name->s[2] == '/')
  	   	return 1;
  	if(name->len >= 2 && strncmp(name->s, "./", 2) == 0)
  		return 1;
  	if(name->len == 1 && strncmp(name->s, ".", 1) == 0)
  		return 1;
  	if(name->len >= 3 && strncmp(name->s, "../", 3) == 0)
  		return 1;
  	if(name->len == 2 && strncmp(name->s, "..", 2) == 0)
  		return 1;
  	return 0;
  }
  
  static int
  findpkg(Strlit *name)
  {
  	Idir *p;
  	char *q, *race;
  
  	if(islocalname(name)) {
  		if(safemode)
  			return 0;
  		// try .a before .6.  important for building libraries:
  		// if there is an array.6 in the array.a library,
  		// want to find all of array.a, not just array.6.
  		snprint(namebuf, sizeof(namebuf), "%Z.a", name);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  		snprint(namebuf, sizeof(namebuf), "%Z.%c", name, thechar);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  		return 0;
  	}
  
  	// local imports should be canonicalized already.
  	// don't want to see "encoding/../encoding/base64"
  	// as different from "encoding/base64".
  	q = mal(name->len+1);
  	memmove(q, name->s, name->len);
  	q[name->len] = '\0';
  	cleanname(q);
  	if(strlen(q) != name->len || memcmp(q, name->s, name->len) != 0) {
  		yyerror("non-canonical import path %Z (should be %s)", name, q);
  		return 0;
  	}
  
  	for(p = idirs; p != nil; p = p->link) {
  		snprint(namebuf, sizeof(namebuf), "%s/%Z.a", p->dir, name);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  		snprint(namebuf, sizeof(namebuf), "%s/%Z.%c", p->dir, name, thechar);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  	}
  	if(goroot != nil) {
  		race = "";
  		if(flag_race)
  			race = "_race";
  		snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s%s/%Z.a", goroot, goos, goarch, race, name);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  		snprint(namebuf, sizeof(namebuf), "%s/pkg/%s_%s%s/%Z.%c", goroot, goos, goarch, race, name, thechar);
  		if(access(namebuf, 0) >= 0)
  			return 1;
  	}
  	return 0;
  }
  
  static void
  fakeimport(void)
  {
  	importpkg = mkpkg(strlit("fake"));
  	cannedimports("fake.6", "$$\n");
  }
  
  void
  importfile(Val *f, int line)
  {
  	Biobuf *imp;
  	char *file, *p, *q, *tag;
  	int32 c;
  	int len;
  	Strlit *path;
  	char *cleanbuf, *prefix;
  
  	USED(line);
  
  	if(f->ctype != CTSTR) {
  		yyerror("import statement not a string");
  		fakeimport();
  		return;
  	}
  
  	if(f->u.sval->len == 0) {
  		yyerror("import path is empty");
  		fakeimport();
  		return;
  	}
  
  	if(isbadimport(f->u.sval)) {
  		fakeimport();
  		return;
  	}
  
  	// The package name main is no longer reserved,
  	// but we reserve the import path "main" to identify
  	// the main package, just as we reserve the import 
  	// path "math" to identify the standard math package.
  	if(strcmp(f->u.sval->s, "main") == 0) {
  		yyerror("cannot import \"main\"");
  		errorexit();
  	}
  
  	if(myimportpath != nil && strcmp(f->u.sval->s, myimportpath) == 0) {
  		yyerror("import \"%Z\" while compiling that package (import cycle)", f->u.sval);
  		errorexit();
  	}
  
  	if(strcmp(f->u.sval->s, "unsafe") == 0) {
  		if(safemode) {
  			yyerror("cannot import package unsafe");
  			errorexit();
  		}
  		importpkg = mkpkg(f->u.sval);
  		cannedimports("unsafe.6", unsafeimport);
  		return;
  	}
  	
  	path = f->u.sval;
  	if(islocalname(path)) {
  		if(path->s[0] == '/') {
  			yyerror("import path cannot be absolute path");
  			fakeimport();
  			return;
  		}
  		prefix = pathname;
  		if(localimport != nil)
  			prefix = localimport;
  		cleanbuf = mal(strlen(prefix) + strlen(path->s) + 2);
  		strcpy(cleanbuf, prefix);
  		strcat(cleanbuf, "/");
  		strcat(cleanbuf, path->s);
  		cleanname(cleanbuf);
  		path = strlit(cleanbuf);
  		
  		if(isbadimport(path)) {
  			fakeimport();
  			return;
  		}
  	}
  
  	if(!findpkg(path)) {
  		yyerror("can't find import: \"%Z\"", f->u.sval);
  		errorexit();
  	}
  	importpkg = mkpkg(path);
  
  	// If we already saw that package, feed a dummy statement
  	// to the lexer to avoid parsing export data twice.
  	if(importpkg->imported) {
  		file = strdup(namebuf);
  		tag = "";
  		if(importpkg->safe) {
  			tag = "safe";
  		}
  		p = smprint("package %s %s\n$$\n", importpkg->name, tag);
  		cannedimports(file, p);
  		return;
  	}
  	importpkg->imported = 1;
  
  	imp = Bopen(namebuf, OREAD);
  	if(imp == nil) {
  		yyerror("can't open import: \"%Z\": %r", f->u.sval);
  		errorexit();
  	}
  	file = strdup(namebuf);
  
  	len = strlen(namebuf);
  	if(len > 2 && namebuf[len-2] == '.' && namebuf[len-1] == 'a') {
  		if(!skiptopkgdef(imp)) {
  			yyerror("import %s: not a package file", file);
  			errorexit();
  		}
  	}
  	
  	// check object header
  	p = Brdstr(imp, '\n', 1);
  	if(strcmp(p, "empty archive") != 0) {
  		if(strncmp(p, "go object ", 10) != 0) {
  			yyerror("import %s: not a go object file", file);
  			errorexit();
  		}
  		q = smprint("%s %s %s %s", getgoos(), thestring, getgoversion(), expstring());
  		if(strcmp(p+10, q) != 0) {
  			yyerror("import %s: object is [%s] expected [%s]", file, p+10, q);
  			errorexit();
  		}
  		free(q);
  	}
  
  	// assume files move (get installed)
  	// so don't record the full path.
  	linehist(file + len - path->len - 2, -1, 1);	// acts as #pragma lib
  
  	/*
  	 * position the input right
  	 * after $$ and return
  	 */
  	pushedio = curio;
  	curio.bin = imp;
  	curio.peekc = 0;
  	curio.peekc1 = 0;
  	curio.infile = file;
  	curio.nlsemi = 0;
  	typecheckok = 1;
  
  	for(;;) {
  		c = getc();
  		if(c == EOF)
  			break;
  		if(c != '$')
  			continue;
  		c = getc();
  		if(c == EOF)
  			break;
  		if(c != '$')
  			continue;
  		return;
  	}
  	yyerror("no import in \"%Z\"", f->u.sval);
  	unimportfile();
  }
  
  void
  unimportfile(void)
  {
  	if(curio.bin != nil) {
  		Bterm(curio.bin);
  		curio.bin = nil;
  	} else
  		lexlineno--;	// re correct sys.6 line number
  
  	curio = pushedio;
  	pushedio.bin = nil;
  	incannedimport = 0;
  	typecheckok = 0;
  }
  
  void
  cannedimports(char *file, char *cp)
  {
  	lexlineno++;		// if sys.6 is included on line 1,
  
  	pushedio = curio;
  	curio.bin = nil;
  	curio.peekc = 0;
  	curio.peekc1 = 0;
  	curio.infile = file;
  	curio.cp = cp;
  	curio.nlsemi = 0;
  	curio.importsafe = 0;
  
  	typecheckok = 1;
  	incannedimport = 1;
  }
  
  static int
  isfrog(int c)
  {
  	// complain about possibly invisible control characters
  	if(c < ' ') {
  		return !yy_isspace(c);	// exclude good white space
  	}
  	if(0x7f <= c && c <= 0xa0)	// DEL, unicode block including unbreakable space.
  		return 1;
  	return 0;
  }
  
  typedef struct Loophack Loophack;
  struct Loophack {
  	int v;
  	Loophack *next;
  };
  
  static int32
  _yylex(void)
  {
  	int c, c1, clen, escflag, ncp;
  	vlong v;
  	char *cp, *ep;
  	Rune rune;
  	Sym *s;
  	static Loophack *lstk;
  	Loophack *h;
  
  	prevlineno = lineno;
  
  l0:
  	c = getc();
  	if(yy_isspace(c)) {
  		if(c == '\n' && curio.nlsemi) {
  			ungetc(c);
  			DBG("lex: implicit semi\n");
  			return ';';
  		}
  		goto l0;
  	}
  
  	lineno = lexlineno;	/* start of token */
  
  	if(c >= Runeself) {
  		/* all multibyte runes are alpha */
  		cp = lexbuf;
  		ep = lexbuf+sizeof lexbuf;
  		goto talph;
  	}
  
  	if(yy_isalpha(c)) {
  		cp = lexbuf;
  		ep = lexbuf+sizeof lexbuf;
  		goto talph;
  	}
  
  	if(yy_isdigit(c))
  		goto tnum;
  
  	switch(c) {
  	case EOF:
  		lineno = prevlineno;
  		ungetc(EOF);
  		return -1;
  
  	case '_':
  		cp = lexbuf;
  		ep = lexbuf+sizeof lexbuf;
  		goto talph;
  
  	case '.':
  		c1 = getc();
  		if(yy_isdigit(c1)) {
  			cp = lexbuf;
  			ep = lexbuf+sizeof lexbuf;
  			*cp++ = c;
  			c = c1;
  			goto casedot;
  		}
  		if(c1 == '.') {
  			c1 = getc();
  			if(c1 == '.') {
  				c = LDDD;
  				goto lx;
  			}
  			ungetc(c1);
  			c1 = '.';
  		}
  		break;
  
  	case '"':
  		/* "..." */
  		strcpy(lexbuf, "\"<string>\"");
  		cp = mal(8);
  		clen = sizeof(int32);
  		ncp = 8;
  
  		for(;;) {
  			if(clen+UTFmax > ncp) {
  				cp = remal(cp, ncp, ncp);
  				ncp += ncp;
  			}
  			if(escchar('"', &escflag, &v))
  				break;
  			if(v < Runeself || escflag) {
  				cp[clen++] = v;
  			} else {
  				rune = v;
  				c = runelen(rune);
  				runetochar(cp+clen, &rune);
  				clen += c;
  			}
  		}
  		goto strlit;
  	
  	case '`':
  		/* `...` */
  		strcpy(lexbuf, "`<string>`");
  		cp = mal(8);
  		clen = sizeof(int32);
  		ncp = 8;
  
  		for(;;) {
  			if(clen+UTFmax > ncp) {
  				cp = remal(cp, ncp, ncp);
  				ncp += ncp;
  			}
  			c = getr();
  			if(c == '\r')
  				continue;
  			if(c == EOF) {
  				yyerror("eof in string");
  				break;
  			}
  			if(c == '`')
  				break;
  			rune = c;
  			clen += runetochar(cp+clen, &rune);
  		}
  
  	strlit:
  		*(int32*)cp = clen-sizeof(int32);	// length
  		do {
  			cp[clen++] = 0;
  		} while(clen & MAXALIGN);
  		yylval.val.u.sval = (Strlit*)cp;
  		yylval.val.ctype = CTSTR;
  		DBG("lex: string literal\n");
  		strcpy(litbuf, "string literal");
  		return LLITERAL;
  
  	case '\'':
  		/* '.' */
  		if(escchar('\'', &escflag, &v)) {
  			yyerror("empty character literal or unescaped ' in character literal");
  			v = '\'';
  		}
  		if(!escchar('\'', &escflag, &v)) {
  			yyerror("missing '");
  			ungetc(v);
  		}
  		yylval.val.u.xval = mal(sizeof(*yylval.val.u.xval));
  		mpmovecfix(yylval.val.u.xval, v);
  		yylval.val.ctype = CTRUNE;
  		DBG("lex: codepoint literal\n");
  		strcpy(litbuf, "string literal");
  		return LLITERAL;
  
  	case '/':
  		c1 = getc();
  		if(c1 == '*') {
  			int nl;
  			
  			nl = 0;
  			for(;;) {
  				c = getr();
  				if(c == '\n')
  					nl = 1;
  				while(c == '*') {
  					c = getr();
  					if(c == '/') {
  						if(nl)
  							ungetc('\n');
  						goto l0;
  					}
  					if(c == '\n')
  						nl = 1;
  				}
  				if(c == EOF) {
  					yyerror("eof in comment");
  					errorexit();
  				}
  			}
  		}
  		if(c1 == '/') {
  			c = getlinepragma();
  			for(;;) {
  				if(c == '\n' || c == EOF) {
  					ungetc(c);
  					goto l0;
  				}
  				c = getr();
  			}
  		}
  		if(c1 == '=') {
  			c = ODIV;
  			goto asop;
  		}
  		break;
  
  	case ':':
  		c1 = getc();
  		if(c1 == '=') {
  			c = LCOLAS;
  			yylval.i = lexlineno;
  			goto lx;
  		}
  		break;
  
  	case '*':
  		c1 = getc();
  		if(c1 == '=') {
  			c = OMUL;
  			goto asop;
  		}
  		break;
  
  	case '%':
  		c1 = getc();
  		if(c1 == '=') {
  			c = OMOD;
  			goto asop;
  		}
  		break;
  
  	case '+':
  		c1 = getc();
  		if(c1 == '+') {
  			c = LINC;
  			goto lx;
  		}
  		if(c1 == '=') {
  			c = OADD;
  			goto asop;
  		}
  		break;
  
  	case '-':
  		c1 = getc();
  		if(c1 == '-') {
  			c = LDEC;
  			goto lx;
  		}
  		if(c1 == '=') {
  			c = OSUB;
  			goto asop;
  		}
  		break;
  
  	case '>':
  		c1 = getc();
  		if(c1 == '>') {
  			c = LRSH;
  			c1 = getc();
  			if(c1 == '=') {
  				c = ORSH;
  				goto asop;
  			}
  			break;
  		}
  		if(c1 == '=') {
  			c = LGE;
  			goto lx;
  		}
  		c = LGT;
  		break;
  
  	case '<':
  		c1 = getc();
  		if(c1 == '<') {
  			c = LLSH;
  			c1 = getc();
  			if(c1 == '=') {
  				c = OLSH;
  				goto asop;
  			}
  			break;
  		}
  		if(c1 == '=') {
  			c = LLE;
  			goto lx;
  		}
  		if(c1 == '-') {
  			c = LCOMM;
  			goto lx;
  		}
  		c = LLT;
  		break;
  
  	case '=':
  		c1 = getc();
  		if(c1 == '=') {
  			c = LEQ;
  			goto lx;
  		}
  		break;
  
  	case '!':
  		c1 = getc();
  		if(c1 == '=') {
  			c = LNE;
  			goto lx;
  		}
  		break;
  
  	case '&':
  		c1 = getc();
  		if(c1 == '&') {
  			c = LANDAND;
  			goto lx;
  		}
  		if(c1 == '^') {
  			c = LANDNOT;
  			c1 = getc();
  			if(c1 == '=') {
  				c = OANDNOT;
  				goto asop;
  			}
  			break;
  		}
  		if(c1 == '=') {
  			c = OAND;
  			goto asop;
  		}
  		break;
  
  	case '|':
  		c1 = getc();
  		if(c1 == '|') {
  			c = LOROR;
  			goto lx;
  		}
  		if(c1 == '=') {
  			c = OOR;
  			goto asop;
  		}
  		break;
  
  	case '^':
  		c1 = getc();
  		if(c1 == '=') {
  			c = OXOR;
  			goto asop;
  		}
  		break;
  
  	/*
  	 * clumsy dance:
  	 * to implement rule that disallows
  	 *	if T{1}[0] { ... }
  	 * but allows
  	 * 	if (T{1}[0]) { ... }
  	 * the block bodies for if/for/switch/select
  	 * begin with an LBODY token, not '{'.
  	 *
  	 * when we see the keyword, the next
  	 * non-parenthesized '{' becomes an LBODY.
  	 * loophack is normally 0.
  	 * a keyword makes it go up to 1.
  	 * parens push loophack onto a stack and go back to 0.
  	 * a '{' with loophack == 1 becomes LBODY and disables loophack.
  	 *
  	 * i said it was clumsy.
  	 */
  	case '(':
  	case '[':
  		if(loophack || lstk != nil) {
  			h = malloc(sizeof *h);
  			if(h == nil) {
  				flusherrors();
  				yyerror("out of memory");
  				errorexit();
  			}
  			h->v = loophack;
  			h->next = lstk;
  			lstk = h;
  			loophack = 0;
  		}
  		goto lx;
  	case ')':
  	case ']':
  		if(lstk != nil) {
  			h = lstk;
  			loophack = h->v;
  			lstk = h->next;
  			free(h);
  		}
  		goto lx;
  	case '{':
  		if(loophack == 1) {
  			DBG("%L lex: LBODY\n", lexlineno);
  			loophack = 0;
  			return LBODY;
  		}
  		goto lx;
  
  	default:
  		goto lx;
  	}
  	ungetc(c1);
  
  lx:
  	if(c > 0xff)
  		DBG("%L lex: TOKEN %s\n", lexlineno, lexname(c));
  	else
  		DBG("%L lex: TOKEN '%c'\n", lexlineno, c);
  	if(isfrog(c)) {
  		yyerror("illegal character 0x%ux", c);
  		goto l0;
  	}
  	if(importpkg == nil && (c == '#' || c == '$' || c == '?' || c == '@' || c == '\\')) {
  		yyerror("%s: unexpected %c", "syntax error", c);
  		goto l0;
  	}
  	return c;
  
  asop:
  	yylval.i = c;	// rathole to hold which asop
  	DBG("lex: TOKEN ASOP %c\n", c);
  	return LASOP;
  
  talph:
  	/*
  	 * cp is set to lexbuf and some
  	 * prefix has been stored
  	 */
  	for(;;) {
  		if(cp+10 >= ep) {
  			yyerror("identifier too long");
  			errorexit();
  		}
  		if(c >= Runeself) {
  			ungetc(c);
  			rune = getr();
  			// 0xb7 · is used for internal names
  			if(!isalpharune(rune) && !isdigitrune(rune) && (importpkg == nil || rune != 0xb7))
  				yyerror("invalid identifier character U+%04x", rune);
  			cp += runetochar(cp, &rune);
  		} else if(!yy_isalnum(c) && c != '_')
  			break;
  		else
  			*cp++ = c;
  		c = getc();
  	}
  	*cp = 0;
  	ungetc(c);
  
  	s = lookup(lexbuf);
  	switch(s->lexical) {
  	case LIGNORE:
  		goto l0;
  
  	case LFOR:
  	case LIF:
  	case LSWITCH:
  	case LSELECT:
  		loophack = 1;	// see comment about loophack above
  		break;
  	}
  
  	DBG("lex: %S %s\n", s, lexname(s->lexical));
  	yylval.sym = s;
  	return s->lexical;
  
  tnum:
  	cp = lexbuf;
  	ep = lexbuf+sizeof lexbuf;
  	if(c != '0') {
  		for(;;) {
  			if(cp+10 >= ep) {
  				yyerror("identifier too long");
  				errorexit();
  			}
  			*cp++ = c;
  			c = getc();
  			if(yy_isdigit(c))
  				continue;
  			goto dc;
  		}
  	}
  	*cp++ = c;
  	c = getc();
  	if(c == 'x' || c == 'X') {
  		for(;;) {
  			if(cp+10 >= ep) {
  				yyerror("identifier too long");
  				errorexit();
  			}
  			*cp++ = c;
  			c = getc();
  			if(yy_isdigit(c))
  				continue;
  			if(c >= 'a' && c <= 'f')
  				continue;
  			if(c >= 'A' && c <= 'F')
  				continue;
  			if(cp == lexbuf+2)
  				yyerror("malformed hex constant");
  			if(c == 'p')
  				goto caseep;
  			goto ncu;
  		}
  	}
  
  	if(c == 'p')	// 0p begins floating point zero
  		goto caseep;
  
  	c1 = 0;
  	for(;;) {
  		if(cp+10 >= ep) {
  			yyerror("identifier too long");
  			errorexit();
  		}
  		if(!yy_isdigit(c))
  			break;
  		if(c < '0' || c > '7')
  			c1 = 1;		// not octal
  		*cp++ = c;
  		c = getc();
  	}
  	if(c == '.')
  		goto casedot;
  	if(c == 'e' || c == 'E')
  		goto caseep;
  	if(c == 'i')
  		goto casei;
  	if(c1)
  		yyerror("malformed octal constant");
  	goto ncu;
  
  dc:
  	if(c == '.')
  		goto casedot;
  	if(c == 'e' || c == 'E' || c == 'p' || c == 'P')
  		goto caseep;
  	if(c == 'i')
  		goto casei;
  
  ncu:
  	*cp = 0;
  	ungetc(c);
  
  	yylval.val.u.xval = mal(sizeof(*yylval.val.u.xval));
  	mpatofix(yylval.val.u.xval, lexbuf);
  	if(yylval.val.u.xval->ovf) {
  		yyerror("overflow in constant");
  		mpmovecfix(yylval.val.u.xval, 0);
  	}
  	yylval.val.ctype = CTINT;
  	DBG("lex: integer literal\n");
  	strcpy(litbuf, "literal ");
  	strcat(litbuf, lexbuf);
  	return LLITERAL;
  
  casedot:
  	for(;;) {
  		if(cp+10 >= ep) {
  			yyerror("identifier too long");
  			errorexit();
  		}
  		*cp++ = c;
  		c = getc();
  		if(!yy_isdigit(c))
  			break;
  	}
  	if(c == 'i')
  		goto casei;
  	if(c != 'e' && c != 'E')
  		goto caseout;
  
  caseep:
  	*cp++ = c;
  	c = getc();
  	if(c == '+' || c == '-') {
  		*cp++ = c;
  		c = getc();
  	}
  	if(!yy_isdigit(c))
  		yyerror("malformed fp constant exponent");
  	while(yy_isdigit(c)) {
  		if(cp+10 >= ep) {
  			yyerror("identifier too long");
  			errorexit();
  		}
  		*cp++ = c;
  		c = getc();
  	}
  	if(c == 'i')
  		goto casei;
  	goto caseout;
  
  casei:
  	// imaginary constant
  	*cp = 0;
  	yylval.val.u.cval = mal(sizeof(*yylval.val.u.cval));
  	mpmovecflt(&yylval.val.u.cval->real, 0.0);
  	mpatoflt(&yylval.val.u.cval->imag, lexbuf);
  	if(yylval.val.u.cval->imag.val.ovf) {
  		yyerror("overflow in imaginary constant");
  		mpmovecflt(&yylval.val.u.cval->real, 0.0);
  	}
  	yylval.val.ctype = CTCPLX;
  	DBG("lex: imaginary literal\n");
  	strcpy(litbuf, "literal ");
  	strcat(litbuf, lexbuf);
  	return LLITERAL;
  
  caseout:
  	*cp = 0;
  	ungetc(c);
  
  	yylval.val.u.fval = mal(sizeof(*yylval.val.u.fval));
  	mpatoflt(yylval.val.u.fval, lexbuf);
  	if(yylval.val.u.fval->val.ovf) {
  		yyerror("overflow in float constant");
  		mpmovecflt(yylval.val.u.fval, 0.0);
  	}
  	yylval.val.ctype = CTFLT;
  	DBG("lex: floating literal\n");
  	strcpy(litbuf, "literal ");
  	strcat(litbuf, lexbuf);
  	return LLITERAL;
  }
  
  /*
   * read and interpret syntax that looks like
   * //line parse.y:15
   * as a discontinuity in sequential line numbers.
   * the next line of input comes from parse.y:15
   */
  static int
  getlinepragma(void)
  {
  	int i, c, n;
  	char *cp, *ep, *linep;
  	Hist *h;
  
  	c = getr();
  	if(c == 'g')
  		goto go;
  	if(c != 'l')	
  		goto out;
  	for(i=1; i<5; i++) {
  		c = getr();
  		if(c != "line "[i])
  			goto out;
  	}
  
  	cp = lexbuf;
  	ep = lexbuf+sizeof(lexbuf)-5;
  	linep = nil;
  	for(;;) {
  		c = getr();
  		if(c == EOF)
  			goto out;
  		if(c == '\n')
  			break;
  		if(c == ' ')
  			continue;
  		if(c == ':')
  			linep = cp;
  		if(cp < ep)
  			*cp++ = c;
  	}
  	*cp = 0;
  
  	if(linep == nil || linep >= ep)
  		goto out;
  	*linep++ = '\0';
  	n = 0;
  	for(cp=linep; *cp; cp++) {
  		if(*cp < '0' || *cp > '9')
  			goto out;
  		n = n*10 + *cp - '0';
  		if(n > 1e8) {
  			yyerror("line number out of range");
  			errorexit();
  		}
  	}
  	if(n <= 0)
  		goto out;
  
  	// try to avoid allocating file name over and over
  	for(h=hist; h!=H; h=h->link) {
  		if(h->name != nil && strcmp(h->name, lexbuf) == 0) {
  			linehist(h->name, n, 0);
  			goto out;
  		}
  	}
  	linehist(strdup(lexbuf), n, 0);
  	goto out;
  
  go:
  	cp = lexbuf;
  	ep = lexbuf+sizeof(lexbuf)-5;
  	*cp++ = 'g'; // already read
  	for(;;) {
  		c = getr();
  		if(c == EOF || c >= Runeself)
  			goto out;
  		if(c == '\n')
  			break;
  		if(cp < ep)
  			*cp++ = c;
  	}
  	*cp = 0;
  	ep = strchr(lexbuf, ' ');
  	if(ep != nil)
  		*ep = 0;
  
  	if(strcmp(lexbuf, "go:nointerface") == 0 && fieldtrack_enabled) {
  		nointerface = 1;
  		goto out;
  	}
  	if(strcmp(lexbuf, "go:noescape") == 0) {
  		noescape = 1;
  		goto out;
  	}
  	
  out:
  	return c;
  }
  
  int32
  yylex(void)
  {
  	int lx;
  	
  	lx = _yylex();
  	
  	if(curio.nlsemi && lx == EOF) {
  		// Treat EOF as "end of line" for the purposes
  		// of inserting a semicolon.
  		lx = ';';
  	}
  
  	switch(lx) {
  	case LNAME:
  	case LLITERAL:
  	case LBREAK:
  	case LCONTINUE:
  	case LFALL:
  	case LRETURN:
  	case LINC:
  	case LDEC:
  	case ')':
  	case '}':
  	case ']':
  		curio.nlsemi = 1;
  		break;
  	default:
  		curio.nlsemi = 0;
  		break;
  	}
  
  	// Track last two tokens returned by yylex.
  	yyprev = yylast;
  	yylast = lx;
  	return lx;
  }
  
  static int
  getc(void)
  {
  	int c, c1, c2;
  
  	c = curio.peekc;
  	if(c != 0) {
  		curio.peekc = curio.peekc1;
  		curio.peekc1 = 0;
  		goto check;
  	}
  	
  	if(curio.bin == nil) {
  		c = *curio.cp & 0xff;
  		if(c != 0)
  			curio.cp++;
  	} else {
  	loop:
  		c = Bgetc(curio.bin);
  		if(c == 0xef) {
  			c1 = Bgetc(curio.bin);
  			c2 = Bgetc(curio.bin);
  			if(c1 == 0xbb && c2 == 0xbf) {
  				yyerrorl(lexlineno, "Unicode (UTF-8) BOM in middle of file");
  				goto loop;
  			}
  			Bungetc(curio.bin);
  			Bungetc(curio.bin);
  		}
  	}
  
  check:
  	switch(c) {
  	case 0:
  		if(curio.bin != nil) {
  			yyerror("illegal NUL byte");
  			break;
  		}
  	case EOF:
  		// insert \n at EOF
  		if(curio.eofnl)
  			return EOF;
  		curio.eofnl = 1;
  		c = '\n';
  	case '\n':
  		if(pushedio.bin == nil)
  			lexlineno++;
  		break;
  	}
  	return c;
  }
  
  static void
  ungetc(int c)
  {
  	curio.peekc1 = curio.peekc;
  	curio.peekc = c;
  	if(c == '\n' && pushedio.bin == nil)
  		lexlineno--;
  }
  
  static int32
  getr(void)
  {
  	int c, i;
  	char str[UTFmax+1];
  	Rune rune;
  
  	c = getc();
  	if(c < Runeself)
  		return c;
  	i = 0;
  	str[i++] = c;
  
  loop:
  	c = getc();
  	str[i++] = c;
  	if(!fullrune(str, i))
  		goto loop;
  	c = chartorune(&rune, str);
  	if(rune == Runeerror && c == 1) {
  		lineno = lexlineno;
  		yyerror("illegal UTF-8 sequence");
  		flusherrors();
  		print("\t");
  		for(c=0; c<i; c++)
  			print("%s%.2x", c > 0 ? " " : "", *(uchar*)(str+c));
  		print("\n");
  	}
  	return rune;
  }
  
  static int
  escchar(int e, int *escflg, vlong *val)
  {
  	int i, u, c;
  	vlong l;
  
  	*escflg = 0;
  
  	c = getr();
  	switch(c) {
  	case EOF:
  		yyerror("eof in string");
  		return 1;
  	case '\n':
  		yyerror("newline in string");
  		return 1;
  	case '\\':
  		break;
  	default:
  		if(c == e)
  			return 1;
  		*val = c;
  		return 0;
  	}
  
  	u = 0;
  	c = getr();
  	switch(c) {
  	case 'x':
  		*escflg = 1;	// it's a byte
  		i = 2;
  		goto hex;
  
  	case 'u':
  		i = 4;
  		u = 1;
  		goto hex;
  
  	case 'U':
  		i = 8;
  		u = 1;
  		goto hex;
  
  	case '0':
  	case '1':
  	case '2':
  	case '3':
  	case '4':
  	case '5':
  	case '6':
  	case '7':
  		*escflg = 1;	// it's a byte
  		goto oct;
  
  	case 'a': c = '\a'; break;
  	case 'b': c = '\b'; break;
  	case 'f': c = '\f'; break;
  	case 'n': c = '\n'; break;
  	case 'r': c = '\r'; break;
  	case 't': c = '\t'; break;
  	case 'v': c = '\v'; break;
  	case '\\': c = '\\'; break;
  
  	default:
  		if(c != e)
  			yyerror("unknown escape sequence: %c", c);
  	}
  	*val = c;
  	return 0;
  
  hex:
  	l = 0;
  	for(; i>0; i--) {
  		c = getc();
  		if(c >= '0' && c <= '9') {
  			l = l*16 + c-'0';
  			continue;
  		}
  		if(c >= 'a' && c <= 'f') {
  			l = l*16 + c-'a' + 10;
  			continue;
  		}
  		if(c >= 'A' && c <= 'F') {
  			l = l*16 + c-'A' + 10;
  			continue;
  		}
  		yyerror("non-hex character in escape sequence: %c", c);
  		ungetc(c);
  		break;
  	}
  	if(u && (l > Runemax || (0xd800 <= l && l < 0xe000))) {
  		yyerror("invalid Unicode code point in escape sequence: %#llx", l);
  		l = Runeerror;
  	}
  	*val = l;
  	return 0;
  
  oct:
  	l = c - '0';
  	for(i=2; i>0; i--) {
  		c = getc();
  		if(c >= '0' && c <= '7') {
  			l = l*8 + c-'0';
  			continue;
  		}
  		yyerror("non-octal character in escape sequence: %c", c);
  		ungetc(c);
  	}
  	if(l > 255)
  		yyerror("octal escape value > 255: %d", l);
  
  	*val = l;
  	return 0;
  }
  
  static	struct
  {
  	char*	name;
  	int	lexical;
  	int	etype;
  	int	op;
  } syms[] =
  {
  /*	name		lexical		etype		op
   */
  /* basic types */
  	"int8",		LNAME,		TINT8,		OXXX,
  	"int16",	LNAME,		TINT16,		OXXX,
  	"int32",	LNAME,		TINT32,		OXXX,
  	"int64",	LNAME,		TINT64,		OXXX,
  
  	"uint8",	LNAME,		TUINT8,		OXXX,
  	"uint16",	LNAME,		TUINT16,	OXXX,
  	"uint32",	LNAME,		TUINT32,	OXXX,
  	"uint64",	LNAME,		TUINT64,	OXXX,
  
  	"float32",	LNAME,		TFLOAT32,	OXXX,
  	"float64",	LNAME,		TFLOAT64,	OXXX,
  
  	"complex64",	LNAME,		TCOMPLEX64,	OXXX,
  	"complex128",	LNAME,		TCOMPLEX128,	OXXX,
  
  	"bool",		LNAME,		TBOOL,		OXXX,
  	"string",	LNAME,		TSTRING,	OXXX,
  
  	"any",		LNAME,		TANY,		OXXX,
  
  	"break",	LBREAK,		Txxx,		OXXX,
  	"case",		LCASE,		Txxx,		OXXX,
  	"chan",		LCHAN,		Txxx,		OXXX,
  	"const",	LCONST,		Txxx,		OXXX,
  	"continue",	LCONTINUE,	Txxx,		OXXX,
  	"default",	LDEFAULT,	Txxx,		OXXX,
  	"else",		LELSE,		Txxx,		OXXX,
  	"defer",	LDEFER,		Txxx,		OXXX,
  	"fallthrough",	LFALL,		Txxx,		OXXX,
  	"for",		LFOR,		Txxx,		OXXX,
  	"func",		LFUNC,		Txxx,		OXXX,
  	"go",		LGO,		Txxx,		OXXX,
  	"goto",		LGOTO,		Txxx,		OXXX,
  	"if",		LIF,		Txxx,		OXXX,
  	"import",	LIMPORT,	Txxx,		OXXX,
  	"interface",	LINTERFACE,	Txxx,		OXXX,
  	"map",		LMAP,		Txxx,		OXXX,
  	"package",	LPACKAGE,	Txxx,		OXXX,
  	"range",	LRANGE,		Txxx,		OXXX,
  	"return",	LRETURN,	Txxx,		OXXX,
  	"select",	LSELECT,	Txxx,		OXXX,
  	"struct",	LSTRUCT,	Txxx,		OXXX,
  	"switch",	LSWITCH,	Txxx,		OXXX,
  	"type",		LTYPE,		Txxx,		OXXX,
  	"var",		LVAR,		Txxx,		OXXX,
  
  	"append",	LNAME,		Txxx,		OAPPEND,
  	"cap",		LNAME,		Txxx,		OCAP,
  	"close",	LNAME,		Txxx,		OCLOSE,
  	"complex",	LNAME,		Txxx,		OCOMPLEX,
  	"copy",		LNAME,		Txxx,		OCOPY,
  	"delete",	LNAME,		Txxx,		ODELETE,
  	"imag",		LNAME,		Txxx,		OIMAG,
  	"len",		LNAME,		Txxx,		OLEN,
  	"make",		LNAME,		Txxx,		OMAKE,
  	"new",		LNAME,		Txxx,		ONEW,
  	"panic",	LNAME,		Txxx,		OPANIC,
  	"print",	LNAME,		Txxx,		OPRINT,
  	"println",	LNAME,		Txxx,		OPRINTN,
  	"real",		LNAME,		Txxx,		OREAL,
  	"recover",	LNAME,		Txxx,		ORECOVER,
  
  	"notwithstanding",		LIGNORE,	Txxx,		OXXX,
  	"thetruthofthematter",		LIGNORE,	Txxx,		OXXX,
  	"despiteallobjections",		LIGNORE,	Txxx,		OXXX,
  	"whereas",			LIGNORE,	Txxx,		OXXX,
  	"insofaras",			LIGNORE,	Txxx,		OXXX,
  };
  
  static void
  lexinit(void)
  {
  	int i, lex;
  	Sym *s, *s1;
  	Type *t;
  	int etype;
  	Val v;
  
  	/*
  	 * initialize basic types array
  	 * initialize known symbols
  	 */
  	for(i=0; i<nelem(syms); i++) {
  		lex = syms[i].lexical;
  		s = lookup(syms[i].name);
  		s->lexical = lex;
  
  		etype = syms[i].etype;
  		if(etype != Txxx) {
  			if(etype < 0 || etype >= nelem(types))
  				fatal("lexinit: %s bad etype", s->name);
  			s1 = pkglookup(syms[i].name, builtinpkg);
  			t = types[etype];
  			if(t == T) {
  				t = typ(etype);
  				t->sym = s1;
  
  				if(etype != TANY && etype != TSTRING)
  					dowidth(t);
  				types[etype] = t;
  			}
  			s1->lexical = LNAME;
  			s1->def = typenod(t);
  			continue;
  		}
  
  		etype = syms[i].op;
  		if(etype != OXXX) {
  			s1 = pkglookup(syms[i].name, builtinpkg);
  			s1->lexical = LNAME;
  			s1->def = nod(ONAME, N, N);
  			s1->def->sym = s1;
  			s1->def->etype = etype;
  			s1->def->builtin = 1;
  		}
  	}
  
  	// logically, the type of a string literal.
  	// types[TSTRING] is the named type string
  	// (the type of x in var x string or var x = "hello").
  	// this is the ideal form
  	// (the type of x in const x = "hello").
  	idealstring = typ(TSTRING);
  	idealbool = typ(TBOOL);
  
  	s = pkglookup("true", builtinpkg);
  	s->def = nodbool(1);
  	s->def->sym = lookup("true");
  	s->def->type = idealbool;
  
  	s = pkglookup("false", builtinpkg);
  	s->def = nodbool(0);
  	s->def->sym = lookup("false");
  	s->def->type = idealbool;
  
  	s = lookup("_");
  	s->block = -100;
  	s->def = nod(ONAME, N, N);
  	s->def->sym = s;
  	types[TBLANK] = typ(TBLANK);
  	s->def->type = types[TBLANK];
  	nblank = s->def;
  
  	s = pkglookup("_", builtinpkg);
  	s->block = -100;
  	s->def = nod(ONAME, N, N);
  	s->def->sym = s;
  	types[TBLANK] = typ(TBLANK);
  	s->def->type = types[TBLANK];
  
  	types[TNIL] = typ(TNIL);
  	s = pkglookup("nil", builtinpkg);
  	v.ctype = CTNIL;
  	s->def = nodlit(v);
  	s->def->sym = s;
  }
  
  static void
  lexinit1(void)
  {
  	Sym *s, *s1;
  	Type *t, *f, *rcvr, *in, *out;
  
  	// t = interface { Error() string }
  	rcvr = typ(TSTRUCT);
  	rcvr->type = typ(TFIELD);
  	rcvr->type->type = ptrto(typ(TSTRUCT));
  	rcvr->funarg = 1;
  	in = typ(TSTRUCT);
  	in->funarg = 1;
  	out = typ(TSTRUCT);
  	out->type = typ(TFIELD);
  	out->type->type = types[TSTRING];
  	out->funarg = 1;
  	f = typ(TFUNC);
  	*getthis(f) = rcvr;
  	*getoutarg(f) = out;
  	*getinarg(f) = in;
  	f->thistuple = 1;
  	f->intuple = 0;
  	f->outnamed = 0;
  	f->outtuple = 1;
  	t = typ(TINTER);
  	t->type = typ(TFIELD);
  	t->type->sym = lookup("Error");
  	t->type->type = f;
  
  	// error type
  	s = lookup("error");
  	s->lexical = LNAME;
  	errortype = t;
  	errortype->sym = s;
  	s1 = pkglookup("error", builtinpkg);
  	s1->lexical = LNAME;
  	s1->def = typenod(errortype);
  
  	// byte alias
  	s = lookup("byte");
  	s->lexical = LNAME;
  	bytetype = typ(TUINT8);
  	bytetype->sym = s;
  	s1 = pkglookup("byte", builtinpkg);
  	s1->lexical = LNAME;
  	s1->def = typenod(bytetype);
  
  	// rune alias
  	s = lookup("rune");
  	s->lexical = LNAME;
  	runetype = typ(TINT32);
  	runetype->sym = s;
  	s1 = pkglookup("rune", builtinpkg);
  	s1->lexical = LNAME;
  	s1->def = typenod(runetype);
  }
  
  static void
  lexfini(void)
  {
  	Sym *s;
  	int lex, etype, i;
  	Val v;
  
  	for(i=0; i<nelem(syms); i++) {
  		lex = syms[i].lexical;
  		if(lex != LNAME)
  			continue;
  		s = lookup(syms[i].name);
  		s->lexical = lex;
  
  		etype = syms[i].etype;
  		if(etype != Txxx && (etype != TANY || debug['A']) && s->def == N) {
  			s->def = typenod(types[etype]);
  			s->origpkg = builtinpkg;
  		}
  
  		etype = syms[i].op;
  		if(etype != OXXX && s->def == N) {
  			s->def = nod(ONAME, N, N);
  			s->def->sym = s;
  			s->def->etype = etype;
  			s->def->builtin = 1;
  			s->origpkg = builtinpkg;
  		}
  	}
  
  	// backend-specific builtin types (e.g. int).
  	for(i=0; typedefs[i].name; i++) {
  		s = lookup(typedefs[i].name);
  		if(s->def == N) {
  			s->def = typenod(types[typedefs[i].etype]);
  			s->origpkg = builtinpkg;
  		}
  	}
  
  	// there's only so much table-driven we can handle.
  	// these are special cases.
  	s = lookup("byte");
  	if(s->def == N) {
  		s->def = typenod(bytetype);
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("error");
  	if(s->def == N) {
  		s->def = typenod(errortype);
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("rune");
  	if(s->def == N) {
  		s->def = typenod(runetype);
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("nil");
  	if(s->def == N) {
  		v.ctype = CTNIL;
  		s->def = nodlit(v);
  		s->def->sym = s;
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("iota");
  	if(s->def == N) {
  		s->def = nod(OIOTA, N, N);
  		s->def->sym = s;
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("true");
  	if(s->def == N) {
  		s->def = nodbool(1);
  		s->def->sym = s;
  		s->origpkg = builtinpkg;
  	}
  
  	s = lookup("false");
  	if(s->def == N) {
  		s->def = nodbool(0);
  		s->def->sym = s;
  		s->origpkg = builtinpkg;
  	}
  
  	nodfp = nod(ONAME, N, N);
  	nodfp->type = types[TINT32];
  	nodfp->xoffset = 0;
  	nodfp->class = PPARAM;
  	nodfp->sym = lookup(".fp");
  }
  
  struct
  {
  	int	lex;
  	char*	name;
  } lexn[] =
  {
  	LANDAND,	"ANDAND",
  	LASOP,		"ASOP",
  	LBREAK,		"BREAK",
  	LCASE,		"CASE",
  	LCHAN,		"CHAN",
  	LCOLAS,		"COLAS",
  	LCONST,		"CONST",
  	LCONTINUE,	"CONTINUE",
  	LDEC,		"DEC",
  	LDEFER,		"DEFER",
  	LELSE,		"ELSE",
  	LEQ,		"EQ",
  	LFALL,		"FALL",
  	LFOR,		"FOR",
  	LFUNC,		"FUNC",
  	LGE,		"GE",
  	LGO,		"GO",
  	LGOTO,		"GOTO",
  	LGT,		"GT",
  	LIF,		"IF",
  	LIMPORT,	"IMPORT",
  	LINC,		"INC",
  	LINTERFACE,	"INTERFACE",
  	LLE,		"LE",
  	LLITERAL,	"LITERAL",
  	LLSH,		"LSH",
  	LLT,		"LT",
  	LMAP,		"MAP",
  	LNAME,		"NAME",
  	LNE,		"NE",
  	LOROR,		"OROR",
  	LPACKAGE,	"PACKAGE",
  	LRANGE,		"RANGE",
  	LRETURN,	"RETURN",
  	LRSH,		"RSH",
  	LSTRUCT,	"STRUCT",
  	LSWITCH,	"SWITCH",
  	LTYPE,		"TYPE",
  	LVAR,		"VAR",
  };
  
  char*
  lexname(int lex)
  {
  	int i;
  	static char buf[100];
  
  	for(i=0; i<nelem(lexn); i++)
  		if(lexn[i].lex == lex)
  			return lexn[i].name;
  	snprint(buf, sizeof(buf), "LEX-%d", lex);
  	return buf;
  }
  
  struct
  {
  	char *have;
  	char *want;
  } yytfix[] =
  {
  	"$end",	"EOF",
  	"LLITERAL",	"literal",
  	"LASOP",	"op=",
  	"LBREAK",	"break",
  	"LCASE",	"case",
  	"LCOLAS",	":=",
  	"LCONST",	"const",
  	"LCONTINUE",	"continue",
  	"LDDD",	"...",
  	"LDEFAULT",	"default",
  	"LDEFER",	"defer",
  	"LELSE",	"else",
  	"LFALL",	"fallthrough",
  	"LFOR",	"for",
  	"LFUNC",	"func",
  	"LGO",	"go",
  	"LGOTO",	"goto",
  	"LIF",	"if",
  	"LIMPORT",	"import",
  	"LINTERFACE",	"interface",
  	"LMAP",	"map",
  	"LNAME",	"name",
  	"LPACKAGE",	"package",
  	"LRANGE",	"range",
  	"LRETURN",	"return",
  	"LSELECT",	"select",
  	"LSTRUCT",	"struct",
  	"LSWITCH",	"switch",
  	"LTYPE",	"type",
  	"LVAR",	"var",
  	"LANDAND",	"&&",
  	"LANDNOT",	"&^",
  	"LBODY",	"{",
  	"LCOMM",	"<-",
  	"LDEC",	"--",
  	"LINC",	"++",
  	"LEQ",	"==",
  	"LGE",	">=",
  	"LGT",	">",
  	"LLE",	"<=",
  	"LLT",	"<",
  	"LLSH",	"<<",
  	"LRSH",	">>",
  	"LOROR",	"||",
  	"LNE",	"!=",
  	
  	// spell out to avoid confusion with punctuation in error messages
  	"';'",	"semicolon or newline",
  	"','",	"comma",
  };
  
  static void
  yytinit(void)
  {
  	int i, j;
  	extern char *yytname[];
  	char *s, *t;
  
  	for(i=0; yytname[i] != nil; i++) {
  		s = yytname[i];
  		
  		if(strcmp(s, "LLITERAL") == 0) {
  			strcpy(litbuf, "literal");
  			yytname[i] = litbuf;
  			goto loop;
  		}
  		
  		// apply yytfix if possible
  		for(j=0; j<nelem(yytfix); j++) {
  			if(strcmp(s, yytfix[j].have) == 0) {
  				yytname[i] = yytfix[j].want;
  				goto loop;
  			}
  		}
  
  		// turn 'x' into x.
  		if(s[0] == '\'') {
  			t = strdup(s+1);
  			t[strlen(t)-1] = '\0';
  			yytname[i] = t;
  		}
  	loop:;
  	}		
  }
  
  void
  mkpackage(char* pkgname)
  {
  	Sym *s;
  	int32 h;
  	char *p, *q;
  
  	if(localpkg->name == nil) {
  		if(strcmp(pkgname, "_") == 0)
  			yyerror("invalid package name _");
  		localpkg->name = pkgname;
  	} else {
  		if(strcmp(pkgname, localpkg->name) != 0)
  			yyerror("package %s; expected %s", pkgname, localpkg->name);
  		for(h=0; h<NHASH; h++) {
  			for(s = hash[h]; s != S; s = s->link) {
  				if(s->def == N || s->pkg != localpkg)
  					continue;
  				if(s->def->op == OPACK) {
  					// throw away top-level package name leftover
  					// from previous file.
  					// leave s->block set to cause redeclaration
  					// errors if a conflicting top-level name is
  					// introduced by a different file.
  					if(!s->def->used && !nsyntaxerrors)
  						yyerrorl(s->def->lineno, "imported and not used: \"%Z\"", s->def->pkg->path);
  					s->def = N;
  					continue;
  				}
  				if(s->def->sym != s) {
  					// throw away top-level name left over
  					// from previous import . "x"
  					if(s->def->pack != N && !s->def->pack->used && !nsyntaxerrors) {
  						yyerrorl(s->def->pack->lineno, "imported and not used: \"%Z\"", s->def->pack->pkg->path);
  						s->def->pack->used = 1;
  					}
  					s->def = N;
  					continue;
  				}
  			}
  		}
  	}
  
  	if(outfile == nil) {
  		p = strrchr(infile, '/');
  		if(windows) {
  			q = strrchr(infile, '\\');
  			if(q > p)
  				p = q;
  		}
  		if(p == nil)
  			p = infile;
  		else
  			p = p+1;
  		snprint(namebuf, sizeof(namebuf), "%s", p);
  		p = strrchr(namebuf, '.');
  		if(p != nil)
  			*p = 0;
  		outfile = smprint("%s.%c", namebuf, thechar);
  	}
  }
  