/ T0 / T0Comp.cs
T0Comp.cs
   1  /*
   2   * Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
   3   *
   4   * Permission is hereby granted, free of charge, to any person obtaining 
   5   * a copy of this software and associated documentation files (the
   6   * "Software"), to deal in the Software without restriction, including
   7   * without limitation the rights to use, copy, modify, merge, publish,
   8   * distribute, sublicense, and/or sell copies of the Software, and to
   9   * permit persons to whom the Software is furnished to do so, subject to
  10   * the following conditions:
  11   *
  12   * The above copyright notice and this permission notice shall be 
  13   * included in all copies or substantial portions of the Software.
  14   *
  15   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  16   * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  18   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  19   * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  20   * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  21   * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22   * SOFTWARE.
  23   */
  24  
  25  using System;
  26  using System.Collections.Generic;
  27  using System.IO;
  28  using System.Reflection;
  29  using System.Text;
  30  
  31  /*
  32   * This is the main compiler class.
  33   */
  34  
  35  public class T0Comp {
  36  
  37  	/*
  38  	 * Command-line entry point.
  39  	 */
  40  	public static void Main(string[] args)
  41  	{
  42  		try {
  43  			List<string> r = new List<string>();
  44  			string outBase = null;
  45  			List<string> entryPoints = new List<string>();
  46  			string coreRun = null;
  47  			bool flow = true;
  48  			int dsLim = 32;
  49  			int rsLim = 32;
  50  			for (int i = 0; i < args.Length; i ++) {
  51  				string a = args[i];
  52  				if (!a.StartsWith("-")) {
  53  					r.Add(a);
  54  					continue;
  55  				}
  56  				if (a == "--") {
  57  					for (;;) {
  58  						if (++ i >= args.Length) {
  59  							break;
  60  						}
  61  						r.Add(args[i]);
  62  					}
  63  					break;
  64  				}
  65  				while (a.StartsWith("-")) {
  66  					a = a.Substring(1);
  67  				}
  68  				int j = a.IndexOf('=');
  69  				string pname;
  70  				string pval, pval2;
  71  				if (j < 0) {
  72  					pname = a.ToLowerInvariant();
  73  					pval = null;
  74  					pval2 = (i + 1) < args.Length
  75  						? args[i + 1] : null;
  76  				} else {
  77  					pname = a.Substring(0, j).Trim()
  78  						.ToLowerInvariant();
  79  					pval = a.Substring(j + 1);
  80  					pval2 = null;
  81  				}
  82  				switch (pname) {
  83  				case "o":
  84  				case "out":
  85  					if (pval == null) {
  86  						if (pval2 == null) {
  87  							Usage();
  88  						}
  89  						i ++;
  90  						pval = pval2;
  91  					}
  92  					if (outBase != null) {
  93  						Usage();
  94  					}
  95  					outBase = pval;
  96  					break;
  97  				case "r":
  98  				case "run":
  99  					if (pval == null) {
 100  						if (pval2 == null) {
 101  							Usage();
 102  						}
 103  						i ++;
 104  						pval = pval2;
 105  					}
 106  					coreRun = pval;
 107  					break;
 108  				case "m":
 109  				case "main":
 110  					if (pval == null) {
 111  						if (pval2 == null) {
 112  							Usage();
 113  						}
 114  						i ++;
 115  						pval = pval2;
 116  					}
 117  					foreach (string ep in pval.Split(',')) {
 118  						string epz = ep.Trim();
 119  						if (epz.Length > 0) {
 120  							entryPoints.Add(epz);
 121  						}
 122  					}
 123  					break;
 124  				case "nf":
 125  				case "noflow":
 126  					flow = false;
 127  					break;
 128  				default:
 129  					Usage();
 130  					break;
 131  				}
 132  			}
 133  			if (r.Count == 0) {
 134  				Usage();
 135  			}
 136  			if (outBase == null) {
 137  				outBase = "t0out";
 138  			}
 139  			if (entryPoints.Count == 0) {
 140  				entryPoints.Add("main");
 141  			}
 142  			if (coreRun == null) {
 143  				coreRun = outBase;
 144  			}
 145  			T0Comp tc = new T0Comp();
 146  			tc.enableFlowAnalysis = flow;
 147  			tc.dsLimit = dsLim;
 148  			tc.rsLimit = rsLim;
 149  			using (TextReader tr = new StreamReader(
 150  				Assembly.GetExecutingAssembly()
 151  				.GetManifestResourceStream("t0-kernel")))
 152  			{
 153  				tc.ProcessInput(tr);
 154  			}
 155  			foreach (string a in r) {
 156  				Console.WriteLine("[{0}]", a);
 157  				using (TextReader tr = File.OpenText(a)) {
 158  					tc.ProcessInput(tr);
 159  				}
 160  			}
 161  			tc.Generate(outBase, coreRun, entryPoints.ToArray());
 162  		} catch (Exception e) {
 163  			Console.WriteLine(e.ToString());
 164  			Environment.Exit(1);
 165  		}
 166  	}
 167  
 168  	static void Usage()
 169  	{
 170  		Console.WriteLine(
 171  "usage: T0Comp.exe [ options... ] file...");
 172  		Console.WriteLine(
 173  "options:");
 174  		Console.WriteLine(
 175  "   -o file    use 'file' as base for output file name (default: 't0out')");
 176  		Console.WriteLine(
 177  "   -r name    use 'name' as base for run function (default: same as output)");
 178  		Console.WriteLine(
 179  "   -m name[,name...]");
 180  		Console.WriteLine(
 181  "              define entry point(s)");
 182  		Console.WriteLine(
 183  "   -nf        disable flow analysis");
 184  		Environment.Exit(1);
 185  	}
 186  
 187  	/*
 188  	 * If 'delayedChar' is Int32.MinValue then there is no delayed
 189  	 * character.
 190  	 * If 'delayedChar' equals x >= 0 then there is one delayed
 191  	 * character of value x.
 192  	 * If 'delayedChar' equals y < 0 then there are two delayed
 193  	 * characters, a newline (U+000A) followed by character of
 194  	 * value -(y+1).
 195  	 */
 196  	TextReader currentInput;
 197  	int delayedChar;
 198  
 199  	/*
 200  	 * Common StringBuilder used to parse tokens; it is reused for
 201  	 * each new token.
 202  	 */
 203  	StringBuilder tokenBuilder;
 204  
 205  	/*
 206  	 * There may be a delayed token in some cases.
 207  	 */
 208  	String delayedToken;
 209  
 210  	/*
 211  	 * Defined words are referenced by name in this map. Names are
 212  	 * string-sensitive; for better reproducibility, the map is sorted
 213  	 * (ordinal order).
 214  	 */
 215  	IDictionary<string, Word> words;
 216  
 217  	/*
 218  	 * Last defined word is also referenced in 'lastWord'. This is
 219  	 * used by 'immediate'.
 220  	 */
 221  	Word lastWord;
 222  
 223  	/*
 224  	 * When compiling, this builder is used. A stack saves other
 225  	 * builders in case of nested definition.
 226  	 */
 227  	WordBuilder wordBuilder;
 228  	Stack<WordBuilder> savedWordBuilders;
 229  
 230  	/*
 231  	 * C code defined for words is kept in this map, by word name.
 232  	 */
 233  	IDictionary<string, string> allCCode;
 234  
 235  	/*
 236  	 * 'compiling' is true when compiling tokens to a word, false
 237  	 * when interpreting them.
 238  	 */
 239  	bool compiling;
 240  
 241  	/*
 242  	 * 'quitRunLoop' is set to true to trigger exit of the
 243  	 * interpretation loop when the end of the current input file
 244  	 * is reached.
 245  	 */
 246  	bool quitRunLoop;
 247  
 248  	/*
 249  	 * 'extraCode' is for C code that is to be added as preamble to
 250  	 * the C output.
 251  	 */
 252  	List<string> extraCode;
 253  
 254  	/*
 255  	 * 'extraCodeDefer' is for C code that is to be added in the C
 256  	 * output _after_ the data and code blocks.
 257  	 */
 258  	List<string> extraCodeDefer;
 259  
 260  	/*
 261  	 * 'dataBlock' is the data block in which constant data bytes
 262  	 * are accumulated.
 263  	 */
 264  	ConstData dataBlock;
 265  
 266  	/*
 267  	 * Counter for blocks of constant data.
 268  	 */
 269  	long currentBlobID;
 270  
 271  	/*
 272  	 * Flow analysis enable flag.
 273  	 */
 274  	bool enableFlowAnalysis;
 275  
 276  	/*
 277  	 * Data stack size limit.
 278  	 */
 279  	int dsLimit;
 280  
 281  	/*
 282  	 * Return stack size limit.
 283  	 */
 284  	int rsLimit;
 285  
 286  	T0Comp()
 287  	{
 288  		tokenBuilder = new StringBuilder();
 289  		words = new SortedDictionary<string, Word>(
 290  			StringComparer.Ordinal);
 291  		savedWordBuilders = new Stack<WordBuilder>();
 292  		allCCode = new SortedDictionary<string, string>(
 293  			StringComparer.Ordinal);
 294  		compiling = false;
 295  		extraCode = new List<string>();
 296  		extraCodeDefer = new List<string>();
 297  		enableFlowAnalysis = true;
 298  
 299  		/*
 300  		 * Native words are predefined and implemented only with
 301  		 * native code. Some may be part of the generated output,
 302  		 * if C code is set for them.
 303  		 */
 304  
 305  		/*
 306  		 * add-cc:
 307  		 * Parses next token as a word name, then a C code snippet.
 308  		 * Sets the C code for that word.
 309  		 */
 310  		AddNative("add-cc:", false, SType.BLANK, cpu => {
 311  			string tt = Next();
 312  			if (tt == null) {
 313  				throw new Exception(
 314  					"EOF reached (missing name)");
 315  			}
 316  			if (allCCode.ContainsKey(tt)) {
 317  				throw new Exception(
 318  					"C code already set for: " + tt);
 319  			}
 320  			allCCode[tt] = ParseCCode();
 321  		});
 322  
 323  		/*
 324  		 * cc:
 325  		 * Parses next token as a word name, then a C code snippet.
 326  		 * A new word is defined, that throws an exception when
 327  		 * invoked during compilation. The C code is set for that
 328  		 * new word.
 329  		 */
 330  		AddNative("cc:", false, SType.BLANK, cpu => {
 331  			string tt = Next();
 332  			if (tt == null) {
 333  				throw new Exception(
 334  					"EOF reached (missing name)");
 335  			}
 336  			Word w = AddNative(tt, false, cpu2 => {
 337  				throw new Exception(
 338  					"C-only word: " + tt);
 339  			});
 340  			if (allCCode.ContainsKey(tt)) {
 341  				throw new Exception(
 342  					"C code already set for: " + tt);
 343  			}
 344  			SType stackEffect;
 345  			allCCode[tt] = ParseCCode(out stackEffect);
 346  			w.StackEffect = stackEffect;
 347  		});
 348  
 349  		/*
 350  		 * preamble
 351  		 * Parses a C code snippet, then adds it to the generated
 352  		 * output preamble.
 353  		 */
 354  		AddNative("preamble", false, SType.BLANK, cpu => {
 355  			extraCode.Add(ParseCCode());
 356  		});
 357  
 358  		/*
 359  		 * postamble
 360  		 * Parses a C code snippet, then adds it to the generated
 361  		 * output after the data and code blocks.
 362  		 */
 363  		AddNative("postamble", false, SType.BLANK, cpu => {
 364  			extraCodeDefer.Add(ParseCCode());
 365  		});
 366  
 367  		/*
 368  		 * make-CX
 369  		 * Expects two integers and a string, and makes a
 370  		 * constant that stands for the string as a C constant
 371  		 * expression. The two integers are the expected range
 372  		 * (min-max, inclusive).
 373  		 */
 374  		AddNative("make-CX", false, new SType(3, 1), cpu => {
 375  			TValue c = cpu.Pop();
 376  			if (!(c.ptr is TPointerBlob)) {
 377  				throw new Exception(string.Format(
 378  					"'{0}' is not a string", c));
 379  			}
 380  			int max = cpu.Pop();
 381  			int min = cpu.Pop();
 382  			TValue tv = new TValue(0, new TPointerExpr(
 383  				c.ToString(), min, max));
 384  			cpu.Push(tv);
 385  		});
 386  
 387  		/*
 388  		 * CX  (immediate)
 389  		 * Parses two integer constants, then a C code snippet.
 390  		 * It then pushes on the stack, or compiles to the
 391  		 * current word, a value consisting of the given C
 392  		 * expression; the two integers indicate the expected
 393  		 * range (min-max, inclusive) of the C expression when
 394  		 * evaluated.
 395  		 */
 396  		AddNative("CX", true, cpu => {
 397  			string tt = Next();
 398  			if (tt == null) {
 399  				throw new Exception(
 400  					"EOF reached (missing min value)");
 401  			}
 402  			int min = ParseInteger(tt);
 403  			tt = Next();
 404  			if (tt == null) {
 405  				throw new Exception(
 406  					"EOF reached (missing max value)");
 407  			}
 408  			int max = ParseInteger(tt);
 409  			if (max < min) {
 410  				throw new Exception("min/max in wrong order");
 411  			}
 412  			TValue tv = new TValue(0, new TPointerExpr(
 413  				ParseCCode().Trim(), min, max));
 414  			if (compiling) {
 415  				wordBuilder.Literal(tv);
 416  			} else {
 417  				cpu.Push(tv);
 418  			}
 419  		});
 420  
 421  		/*
 422  		 * co
 423  		 * Interrupt the current execution. This implements
 424  		 * coroutines. It cannot be invoked during compilation.
 425  		 */
 426  		AddNative("co", false, SType.BLANK, cpu => {
 427  			throw new Exception("No coroutine in compile mode");
 428  		});
 429  
 430  		/*
 431  		 * :
 432  		 * Parses next token as word name. It begins definition
 433  		 * of that word, setting it as current target for
 434  		 * word building. Any previously opened word is saved
 435  		 * and will become available again as a target when that
 436  		 * new word is finished building.
 437  		 */
 438  		AddNative(":", false, cpu => {
 439  			string tt = Next();
 440  			if (tt == null) {
 441  				throw new Exception(
 442  					"EOF reached (missing name)");
 443  			}
 444  			if (compiling) {
 445  				savedWordBuilders.Push(wordBuilder);
 446  			} else {
 447  				compiling = true;
 448  			}
 449  			wordBuilder = new WordBuilder(this, tt);
 450  			tt = Next();
 451  			if (tt == null) {
 452  				throw new Exception(
 453  					"EOF reached (while compiling)");
 454  			}
 455  			if (tt == "(") {
 456  				SType stackEffect = ParseStackEffectNF();
 457  				if (!stackEffect.IsKnown) {
 458  					throw new Exception(
 459  						"Invalid stack effect syntax");
 460  				}
 461  				wordBuilder.StackEffect = stackEffect;
 462  			} else {
 463  				delayedToken = tt;
 464  			}
 465  		});
 466  
 467  		/*
 468  		 * Pops a string as word name, and two integers as stack
 469  		 * effect. It begins definition of that word, setting it
 470  		 * as current target for word building. Any previously
 471  		 * opened word is saved and will become available again as
 472  		 * a target when that new word is finished building.
 473  		 *
 474  		 * Stack effect is the pair 'din dout'. If din is negative,
 475  		 * then the stack effect is "unknown". If din is nonnegative
 476  		 * but dout is negative, then the word is reputed never to
 477  		 * return.
 478  		 */
 479  		AddNative("define-word", false, cpu => {
 480  			int dout = cpu.Pop();
 481  			int din = cpu.Pop();
 482  			TValue s = cpu.Pop();
 483  			if (!(s.ptr is TPointerBlob)) {
 484  				throw new Exception(string.Format(
 485  					"Not a string: '{0}'", s));
 486  			}
 487  			string tt = s.ToString();
 488  			if (compiling) {
 489  				savedWordBuilders.Push(wordBuilder);
 490  			} else {
 491  				compiling = true;
 492  			}
 493  			wordBuilder = new WordBuilder(this, tt);
 494  			wordBuilder.StackEffect = new SType(din, dout);
 495  		});
 496  
 497  		/*
 498  		 * ;  (immediate)
 499  		 * Ends current word. The current word is registered under
 500  		 * its name, and the previously opened word (if any) becomes
 501  		 * again the building target.
 502  		 */
 503  		AddNative(";", true, cpu => {
 504  			if (!compiling) {
 505  				throw new Exception("Not compiling");
 506  			}
 507  			Word w = wordBuilder.Build();
 508  			string name = w.Name;
 509  			if (words.ContainsKey(name)) {
 510  				throw new Exception(
 511  					"Word already defined: " + name);
 512  			}
 513  			words[name] = w;
 514  			lastWord = w;
 515  			if (savedWordBuilders.Count > 0) {
 516  				wordBuilder = savedWordBuilders.Pop();
 517  			} else {
 518  				wordBuilder = null;
 519  				compiling = false;
 520  			}
 521  		});
 522  
 523  		/*
 524  		 * immediate
 525  		 * Sets the last defined word as immediate.
 526  		 */
 527  		AddNative("immediate", false, cpu => {
 528  			if (lastWord == null) {
 529  				throw new Exception("No word defined yet");
 530  			}
 531  			lastWord.Immediate = true;
 532  		});
 533  
 534  		/*
 535  		 * literal  (immediate)
 536  		 * Pops the current TOS value, and add in the current word
 537  		 * the action of pushing that value. This cannot be used
 538  		 * when no word is being built.
 539  		 */
 540  		WordNative wliteral = AddNative("literal", true, cpu => {
 541  			CheckCompiling();
 542  			wordBuilder.Literal(cpu.Pop());
 543  		});
 544  
 545  		/*
 546  		 * compile
 547  		 * Pops the current TOS value, which must be an XT (pointer
 548  		 * to a word); the action of calling that word is compiled
 549  		 * in the current word.
 550  		 */
 551  		WordNative wcompile = AddNative("compile", false, cpu => {
 552  			CheckCompiling();
 553  			wordBuilder.Call(cpu.Pop().ToXT());
 554  		});
 555  
 556  		/*
 557  		 * postpone  (immediate)
 558  		 * Parses the next token as a word name, and add to the
 559  		 * current word the action of calling that word. This
 560  		 * basically removes immediatety from the next word.
 561  		 */
 562  		AddNative("postpone", true, cpu => {
 563  			CheckCompiling();
 564  			string tt = Next();
 565  			if (tt == null) {
 566  				throw new Exception(
 567  					"EOF reached (missing name)");
 568  			}
 569  			TValue v;
 570  			bool isVal = TryParseLiteral(tt, out v);
 571  			Word w = LookupNF(tt);
 572  			if (isVal && w != null) {
 573  				throw new Exception(String.Format(
 574  					"Ambiguous: both defined word and"
 575  					+ " literal: {0}", tt));
 576  			}
 577  			if (isVal) {
 578  				wordBuilder.Literal(v);
 579  				wordBuilder.CallExt(wliteral);
 580  			} else if (w != null) {
 581  				if (w.Immediate) {
 582  					wordBuilder.CallExt(w);
 583  				} else {
 584  					wordBuilder.Literal(new TValue(0,
 585  						new TPointerXT(w)));
 586  					wordBuilder.CallExt(wcompile);
 587  				}
 588  			} else {
 589  				wordBuilder.Literal(new TValue(0,
 590  					new TPointerXT(tt)));
 591  				wordBuilder.CallExt(wcompile);
 592  			}
 593  		});
 594  
 595  		/*
 596  		 * Interrupt compilation with an error.
 597  		 */
 598  		AddNative("exitvm", false, cpu => {
 599  			throw new Exception();
 600  		});
 601  
 602  		/*
 603  		 * Open a new data block. Its symbolic address is pushed
 604  		 * on the stack.
 605  		 */
 606  		AddNative("new-data-block", false, cpu => {
 607  			dataBlock = new ConstData(this);
 608  			cpu.Push(new TValue(0, new TPointerBlob(dataBlock)));
 609  		});
 610  
 611  		/*
 612  		 * Define a new data word. The data address and name are
 613  		 * popped from the stack.
 614  		 */
 615  		AddNative("define-data-word", false, cpu => {
 616  			string name = cpu.Pop().ToString();
 617  			TValue va = cpu.Pop();
 618  			TPointerBlob tb = va.ptr as TPointerBlob;
 619  			if (tb == null) {
 620  				throw new Exception(
 621  					"Address is not a data area");
 622  			}
 623  			Word w = new WordData(this, name, tb.Blob, va.x);
 624  			if (words.ContainsKey(name)) {
 625  				throw new Exception(
 626  					"Word already defined: " + name);
 627  			}
 628  			words[name] = w;
 629  			lastWord = w;
 630  		});
 631  
 632  		/*
 633  		 * Get an address pointing at the end of the current
 634  		 * data block. This is the address of the next byte that
 635  		 * will be added.
 636  		 */
 637  		AddNative("current-data", false, cpu => {
 638  			if (dataBlock == null) {
 639  				throw new Exception(
 640  					"No current data block");
 641  			}
 642  			cpu.Push(new TValue(dataBlock.Length,
 643  				new TPointerBlob(dataBlock)));
 644  		});
 645  
 646  		/*
 647  		 * Add a byte value to the data block.
 648  		 */
 649  		AddNative("data-add8", false, cpu => {
 650  			if (dataBlock == null) {
 651  				throw new Exception(
 652  					"No current data block");
 653  			}
 654  			int v = cpu.Pop();
 655  			if (v < 0 || v > 0xFF) {
 656  				throw new Exception(
 657  					"Byte value out of range: " + v);
 658  			}
 659  			dataBlock.Add8((byte)v);
 660  		});
 661  
 662  		/*
 663  		 * Set a byte value in the data block.
 664  		 */
 665  		AddNative("data-set8", false, cpu => {
 666  			TValue va = cpu.Pop();
 667  			TPointerBlob tb = va.ptr as TPointerBlob;
 668  			if (tb == null) {
 669  				throw new Exception(
 670  					"Address is not a data area");
 671  			}
 672  			int v = cpu.Pop();
 673  			if (v < 0 || v > 0xFF) {
 674  				throw new Exception(
 675  					"Byte value out of range: " + v);
 676  			}
 677  			tb.Blob.Set8(va.x, (byte)v);
 678  		});
 679  
 680  		/*
 681  		 * Get a byte value from a data block.
 682  		 */
 683  		AddNative("data-get8", false, new SType(1, 1), cpu => {
 684  			TValue va = cpu.Pop();
 685  			TPointerBlob tb = va.ptr as TPointerBlob;
 686  			if (tb == null) {
 687  				throw new Exception(
 688  					"Address is not a data area");
 689  			}
 690  			int v = tb.Blob.Read8(va.x);
 691  			cpu.Push(v);
 692  		});
 693  
 694  		/*
 695  		 *
 696  		 */
 697  		AddNative("compile-local-read", false, cpu => {
 698  			CheckCompiling();
 699  			wordBuilder.GetLocal(cpu.Pop().ToString());
 700  		});
 701  		AddNative("compile-local-write", false, cpu => {
 702  			CheckCompiling();
 703  			wordBuilder.PutLocal(cpu.Pop().ToString());
 704  		});
 705  
 706  		AddNative("ahead", true, cpu => {
 707  			CheckCompiling();
 708  			wordBuilder.Ahead();
 709  		});
 710  		AddNative("begin", true, cpu => {
 711  			CheckCompiling();
 712  			wordBuilder.Begin();
 713  		});
 714  		AddNative("again", true, cpu => {
 715  			CheckCompiling();
 716  			wordBuilder.Again();
 717  		});
 718  		AddNative("until", true, cpu => {
 719  			CheckCompiling();
 720  			wordBuilder.AgainIfNot();
 721  		});
 722  		AddNative("untilnot", true, cpu => {
 723  			CheckCompiling();
 724  			wordBuilder.AgainIf();
 725  		});
 726  		AddNative("if", true, cpu => {
 727  			CheckCompiling();
 728  			wordBuilder.AheadIfNot();
 729  		});
 730  		AddNative("ifnot", true, cpu => {
 731  			CheckCompiling();
 732  			wordBuilder.AheadIf();
 733  		});
 734  		AddNative("then", true, cpu => {
 735  			CheckCompiling();
 736  			wordBuilder.Then();
 737  		});
 738  		AddNative("cs-pick", false, cpu => {
 739  			CheckCompiling();
 740  			wordBuilder.CSPick(cpu.Pop());
 741  		});
 742  		AddNative("cs-roll", false, cpu => {
 743  			CheckCompiling();
 744  			wordBuilder.CSRoll(cpu.Pop());
 745  		});
 746  		AddNative("next-word", false, cpu => {
 747  			string s = Next();
 748  			if (s == null) {
 749  				throw new Exception("No next word (EOF)");
 750  			}
 751  			cpu.Push(StringToBlob(s));
 752  		});
 753  		AddNative("parse", false, cpu => {
 754  			int d = cpu.Pop();
 755  			string s = ReadTerm(d);
 756  			cpu.Push(StringToBlob(s));
 757  		});
 758  		AddNative("char", false, cpu => {
 759  			int c = NextChar();
 760  			if (c < 0) {
 761  				throw new Exception("No next character (EOF)");
 762  			}
 763  			cpu.Push(c);
 764  		});
 765  		AddNative("'", false, cpu => {
 766  			string name = Next();
 767  			cpu.Push(new TValue(0, new TPointerXT(name)));
 768  		});
 769  
 770  		/*
 771  		 * The "execute" word is valid in generated C code, but
 772  		 * since it jumps to a runtime pointer, its actual stack
 773  		 * effect cannot be computed in advance.
 774  		 */
 775  		AddNative("execute", false, cpu => {
 776  			cpu.Pop().Execute(this, cpu);
 777  		});
 778  
 779  		AddNative("[", true, cpu => {
 780  			CheckCompiling();
 781  			compiling = false;
 782  		});
 783  		AddNative("]", false, cpu => {
 784  			compiling = true;
 785  		});
 786  		AddNative("(local)", false, cpu => {
 787  			CheckCompiling();
 788  			wordBuilder.DefLocal(cpu.Pop().ToString());
 789  		});
 790  		AddNative("ret", true, cpu => {
 791  			CheckCompiling();
 792  			wordBuilder.Ret();
 793  		});
 794  
 795  		AddNative("drop", false, new SType(1, 0), cpu => {
 796  			cpu.Pop();
 797  		});
 798  		AddNative("dup", false, new SType(1, 2), cpu => {
 799  			cpu.Push(cpu.Peek(0));
 800  		});
 801  		AddNative("swap", false, new SType(2, 2), cpu => {
 802  			cpu.Rot(1);
 803  		});
 804  		AddNative("over", false, new SType(2, 3), cpu => {
 805  			cpu.Push(cpu.Peek(1));
 806  		});
 807  		AddNative("rot", false, new SType(3, 3), cpu => {
 808  			cpu.Rot(2);
 809  		});
 810  		AddNative("-rot", false, new SType(3, 3), cpu => {
 811  			cpu.NRot(2);
 812  		});
 813  
 814  		/*
 815  		 * "roll" and "pick" are special in that the stack slot
 816  		 * they inspect might be known only at runtime, so an
 817  		 * absolute stack effect cannot be attributed. Instead,
 818  		 * we simply hope that the caller knows what it is doing,
 819  		 * and we use a simple stack effect for just the count
 820  		 * value and picked value.
 821  		 */
 822  		AddNative("roll", false, new SType(1, 0), cpu => {
 823  			cpu.Rot(cpu.Pop());
 824  		});
 825  		AddNative("pick", false, new SType(1, 1), cpu => {
 826  			cpu.Push(cpu.Peek(cpu.Pop()));
 827  		});
 828  
 829  		AddNative("+", false, new SType(2, 1), cpu => {
 830  			TValue b = cpu.Pop();
 831  			TValue a = cpu.Pop();
 832  			if (b.ptr == null) {
 833  				a.x += (int)b;
 834  				cpu.Push(a);
 835  			} else if (a.ptr is TPointerBlob
 836  				&& b.ptr is TPointerBlob)
 837  			{
 838  				cpu.Push(StringToBlob(
 839  					a.ToString() + b.ToString()));
 840  			} else {
 841  				throw new Exception(string.Format(
 842  					"Cannot add '{0}' to '{1}'", b, a));
 843  			}
 844  		});
 845  		AddNative("-", false, new SType(2, 1), cpu => {
 846  			/*
 847  			 * We can subtract two pointers, provided that
 848  			 * they point to the same blob. Otherwise,
 849  			 * the subtraction second operand must be an
 850  			 * integer.
 851  			 */
 852  			TValue b = cpu.Pop();
 853  			TValue a = cpu.Pop();
 854  			TPointerBlob ap = a.ptr as TPointerBlob;
 855  			TPointerBlob bp = b.ptr as TPointerBlob;
 856  			if (ap != null && bp != null && ap.Blob == bp.Blob) {
 857  				cpu.Push(new TValue(a.x - b.x));
 858  				return;
 859  			}
 860  			int bx = b;
 861  			a.x -= bx;
 862  			cpu.Push(a);
 863  		});
 864  		AddNative("neg", false, new SType(1, 1), cpu => {
 865  			int ax = cpu.Pop();
 866  			cpu.Push(-ax);
 867  		});
 868  		AddNative("*", false, new SType(2, 1), cpu => {
 869  			int bx = cpu.Pop();
 870  			int ax = cpu.Pop();
 871  			cpu.Push(ax * bx);
 872  		});
 873  		AddNative("/", false, new SType(2, 1), cpu => {
 874  			int bx = cpu.Pop();
 875  			int ax = cpu.Pop();
 876  			cpu.Push(ax / bx);
 877  		});
 878  		AddNative("u/", false, new SType(2, 1), cpu => {
 879  			uint bx = cpu.Pop();
 880  			uint ax = cpu.Pop();
 881  			cpu.Push(ax / bx);
 882  		});
 883  		AddNative("%", false, new SType(2, 1), cpu => {
 884  			int bx = cpu.Pop();
 885  			int ax = cpu.Pop();
 886  			cpu.Push(ax % bx);
 887  		});
 888  		AddNative("u%", false, new SType(2, 1), cpu => {
 889  			uint bx = cpu.Pop();
 890  			uint ax = cpu.Pop();
 891  			cpu.Push(ax % bx);
 892  		});
 893  		AddNative("<", false, new SType(2, 1), cpu => {
 894  			int bx = cpu.Pop();
 895  			int ax = cpu.Pop();
 896  			cpu.Push(ax < bx);
 897  		});
 898  		AddNative("<=", false, new SType(2, 1), cpu => {
 899  			int bx = cpu.Pop();
 900  			int ax = cpu.Pop();
 901  			cpu.Push(ax <= bx);
 902  		});
 903  		AddNative(">", false, new SType(2, 1), cpu => {
 904  			int bx = cpu.Pop();
 905  			int ax = cpu.Pop();
 906  			cpu.Push(ax > bx);
 907  		});
 908  		AddNative(">=", false, new SType(2, 1), cpu => {
 909  			int bx = cpu.Pop();
 910  			int ax = cpu.Pop();
 911  			cpu.Push(ax >= bx);
 912  		});
 913  		AddNative("=", false, new SType(2, 1), cpu => {
 914  			TValue b = cpu.Pop();
 915  			TValue a = cpu.Pop();
 916  			cpu.Push(a.Equals(b));
 917  		});
 918  		AddNative("<>", false, new SType(2, 1), cpu => {
 919  			TValue b = cpu.Pop();
 920  			TValue a = cpu.Pop();
 921  			cpu.Push(!a.Equals(b));
 922  		});
 923  		AddNative("u<", false, new SType(2, 1), cpu => {
 924  			uint bx = cpu.Pop().UInt;
 925  			uint ax = cpu.Pop().UInt;
 926  			cpu.Push(new TValue(ax < bx));
 927  		});
 928  		AddNative("u<=", false, new SType(2, 1), cpu => {
 929  			uint bx = cpu.Pop().UInt;
 930  			uint ax = cpu.Pop().UInt;
 931  			cpu.Push(new TValue(ax <= bx));
 932  		});
 933  		AddNative("u>", false, new SType(2, 1), cpu => {
 934  			uint bx = cpu.Pop().UInt;
 935  			uint ax = cpu.Pop().UInt;
 936  			cpu.Push(new TValue(ax > bx));
 937  		});
 938  		AddNative("u>=", false, new SType(2, 1), cpu => {
 939  			uint bx = cpu.Pop();
 940  			uint ax = cpu.Pop();
 941  			cpu.Push(ax >= bx);
 942  		});
 943  		AddNative("and", false, new SType(2, 1), cpu => {
 944  			uint bx = cpu.Pop();
 945  			uint ax = cpu.Pop();
 946  			cpu.Push(ax & bx);
 947  		});
 948  		AddNative("or", false, new SType(2, 1), cpu => {
 949  			uint bx = cpu.Pop();
 950  			uint ax = cpu.Pop();
 951  			cpu.Push(ax | bx);
 952  		});
 953  		AddNative("xor", false, new SType(2, 1), cpu => {
 954  			uint bx = cpu.Pop();
 955  			uint ax = cpu.Pop();
 956  			cpu.Push(ax ^ bx);
 957  		});
 958  		AddNative("not", false, new SType(1, 1), cpu => {
 959  			uint ax = cpu.Pop();
 960  			cpu.Push(~ax);
 961  		});
 962  		AddNative("<<", false, new SType(2, 1), cpu => {
 963  			int count = cpu.Pop();
 964  			if (count < 0 || count > 31) {
 965  				throw new Exception("Invalid shift count");
 966  			}
 967  			uint ax = cpu.Pop();
 968  			cpu.Push(ax << count);
 969  		});
 970  		AddNative(">>", false, new SType(2, 1), cpu => {
 971  			int count = cpu.Pop();
 972  			if (count < 0 || count > 31) {
 973  				throw new Exception("Invalid shift count");
 974  			}
 975  			int ax = cpu.Pop();
 976  			cpu.Push(ax >> count);
 977  		});
 978  		AddNative("u>>", false, new SType(2, 1), cpu => {
 979  			int count = cpu.Pop();
 980  			if (count < 0 || count > 31) {
 981  				throw new Exception("Invalid shift count");
 982  			}
 983  			uint ax = cpu.Pop();
 984  			cpu.Push(ax >> count);
 985  		});
 986  
 987  		AddNative(".", false, new SType(1, 0), cpu => {
 988  			Console.Write(" {0}", cpu.Pop().ToString());
 989  		});
 990  		AddNative(".s", false, SType.BLANK, cpu => {
 991  			int n = cpu.Depth;
 992  			for (int i = n - 1; i >= 0; i --) {
 993  				Console.Write(" {0}", cpu.Peek(i).ToString());
 994  			}
 995  		});
 996  		AddNative("putc", false, new SType(1, 0), cpu => {
 997  			Console.Write((char)cpu.Pop());
 998  		});
 999  		AddNative("puts", false, new SType(1, 0), cpu => {
1000  			Console.Write("{0}", cpu.Pop().ToString());
1001  		});
1002  		AddNative("cr", false, SType.BLANK, cpu => {
1003  			Console.WriteLine();
1004  		});
1005  		AddNative("eqstr", false, new SType(2, 1), cpu => {
1006  			string s2 = cpu.Pop().ToString();
1007  			string s1 = cpu.Pop().ToString();
1008  			cpu.Push(s1 == s2);
1009  		});
1010  	}
1011  
1012  	WordNative AddNative(string name, bool immediate,
1013  		WordNative.NativeRun code)
1014  	{
1015  		return AddNative(name, immediate, SType.UNKNOWN, code);
1016  	}
1017  
1018  	WordNative AddNative(string name, bool immediate, SType stackEffect,
1019  		WordNative.NativeRun code)
1020  	{
1021  		if (words.ContainsKey(name)) {
1022  			throw new Exception(
1023  				"Word already defined: " + name);
1024  		}
1025  		WordNative w = new WordNative(this, name, code);
1026  		w.Immediate = immediate;
1027  		w.StackEffect = stackEffect;
1028  		words[name] = w;
1029  		return w;
1030  	}
1031  
1032  	internal long NextBlobID()
1033  	{
1034  		return currentBlobID ++;
1035  	}
1036  
1037  	int NextChar()
1038  	{
1039  		int c = delayedChar;
1040  		if (c >= 0) {
1041  			delayedChar = Int32.MinValue;
1042  		} else if (c > Int32.MinValue) {
1043  			delayedChar = -(c + 1);
1044  			c = '\n';
1045  		} else {
1046  			c = currentInput.Read();
1047  		}
1048  		if (c == '\r') {
1049  			if (delayedChar >= 0) {
1050  				c = delayedChar;
1051  				delayedChar = Int32.MinValue;
1052  			} else {
1053  				c = currentInput.Read();
1054  			}
1055  			if (c != '\n') {
1056  				delayedChar = c;
1057  				c = '\n';
1058  			}
1059  		}
1060  		return c;
1061  	}
1062  
1063  	/*
1064  	 * Un-read the character value 'c'. That value MUST be the one
1065  	 * that was obtained from NextChar().
1066  	 */
1067  	void Unread(int c)
1068  	{
1069  		if (c < 0) {
1070  			return;
1071  		}
1072  		if (delayedChar < 0) {
1073  			if (delayedChar != Int32.MinValue) {
1074  				throw new Exception(
1075  					"Already two delayed characters");
1076  			}
1077  			delayedChar = c;
1078  		} else if (c != '\n') {
1079  			throw new Exception("Cannot delay two characters");
1080  		} else {
1081  			delayedChar = -(delayedChar + 1);
1082  		}
1083  	}
1084  
1085  	string Next()
1086  	{
1087  		string r = delayedToken;
1088  		if (r != null) {
1089  			delayedToken = null;
1090  			return r;
1091  		}
1092  		tokenBuilder.Length = 0;
1093  		int c;
1094  		for (;;) {
1095  			c = NextChar();
1096  			if (c < 0) {
1097  				return null;
1098  			}
1099  			if (!IsWS(c)) {
1100  				break;
1101  			}
1102  		}
1103  		if (c == '"') {
1104  			return ParseString();
1105  		}
1106  		for (;;) {
1107  			tokenBuilder.Append((char)c);
1108  			c = NextChar();
1109  			if (c < 0 || IsWS(c)) {
1110  				Unread(c);
1111  				return tokenBuilder.ToString();
1112  			}
1113  		}
1114  	}
1115  
1116  	string ParseCCode()
1117  	{
1118  		SType stackEffect;
1119  		string r = ParseCCode(out stackEffect);
1120  		if (stackEffect.IsKnown) {
1121  			throw new Exception(
1122  				"Stack effect forbidden in this declaration");
1123  		}
1124  		return r;
1125  	}
1126  
1127  	string ParseCCode(out SType stackEffect)
1128  	{
1129  		string s = ParseCCodeNF(out stackEffect);
1130  		if (s == null) {
1131  			throw new Exception("Error while parsing C code");
1132  		}
1133  		return s;
1134  	}
1135  
1136  	string ParseCCodeNF(out SType stackEffect)
1137  	{
1138  		stackEffect = SType.UNKNOWN;
1139  		for (;;) {
1140  			int c = NextChar();
1141  			if (c < 0) {
1142  				return null;
1143  			}
1144  			if (!IsWS(c)) {
1145  				if (c == '(') {
1146  					if (stackEffect.IsKnown) {
1147  						Unread(c);
1148  						return null;
1149  					}
1150  					stackEffect = ParseStackEffectNF();
1151  					if (!stackEffect.IsKnown) {
1152  						return null;
1153  					}
1154  					continue;
1155  				} else if (c != '{') {
1156  					Unread(c);
1157  					return null;
1158  				}
1159  				break;
1160  			}
1161  		}
1162  		StringBuilder sb = new StringBuilder();
1163  		int count = 1;
1164  		for (;;) {
1165  			int c = NextChar();
1166  			if (c < 0) {
1167  				return null;
1168  			}
1169  			switch (c) {
1170  			case '{':
1171  				count ++;
1172  				break;
1173  			case '}':
1174  				if (-- count == 0) {
1175  					return sb.ToString();
1176  				}
1177  				break;
1178  			}
1179  			sb.Append((char)c);
1180  		}
1181  	}
1182  
1183  	/*
1184  	 * Parse a stack effect declaration. This method assumes that the
1185  	 * opening parenthesis has just been read. If the parsing fails,
1186  	 * then this method returns SType.UNKNOWN.
1187  	 */
1188  	SType ParseStackEffectNF()
1189  	{
1190  		bool seenSep = false;
1191  		bool seenBang = false;
1192  		int din = 0, dout = 0;
1193  		for (;;) {
1194  			string t = Next();
1195  			if (t == null) {
1196  				return SType.UNKNOWN;
1197  			}
1198  			if (t == "--") {
1199  				if (seenSep) {
1200  					return SType.UNKNOWN;
1201  				}
1202  				seenSep = true;
1203  			} else if (t == ")") {
1204  				if (seenSep) {
1205  					if (seenBang && dout == 1) {
1206  						dout = -1;
1207  					}
1208  					return new SType(din, dout);
1209  				} else {
1210  					return SType.UNKNOWN;
1211  				}
1212  			} else {
1213  				if (seenSep) {
1214  					if (dout == 0 && t == "!") {
1215  						seenBang = true;
1216  					}
1217  					dout ++;
1218  				} else {
1219  					din ++;
1220  				}
1221  			}
1222  		}
1223  	}
1224  
1225  	string ParseString()
1226  	{
1227  		StringBuilder sb = new StringBuilder();
1228  		sb.Append('"');
1229  		bool lcwb = false;
1230  		int hexNum = 0;
1231  		int acc = 0;
1232  		for (;;) {
1233  			int c = NextChar();
1234  			if (c < 0) {
1235  				throw new Exception(
1236  					"Unfinished literal string");
1237  			}
1238  			if (hexNum > 0) {
1239  				int d = HexVal(c);
1240  				if (d < 0) {
1241  					throw new Exception(String.Format(
1242  						"not an hex digit: U+{0:X4}",
1243  						c));
1244  				}
1245  				acc = (acc << 4) + d;
1246  				if (-- hexNum == 0) {
1247  					sb.Append((char)acc);
1248  					acc = 0;
1249  				}
1250  			} else if (lcwb) {
1251  				lcwb = false;
1252  				switch (c) {
1253  				case '\n': SkipNL(); break;
1254  				case 'x':
1255  					hexNum = 2;
1256  					break;
1257  				case 'u':
1258  					hexNum = 4;
1259  					break;
1260  				default:
1261  					sb.Append(SingleCharEscape(c));
1262  					break;
1263  				}
1264  			} else {
1265  				switch (c) {
1266  				case '"':
1267  					return sb.ToString();
1268  				case '\\':
1269  					lcwb = true;
1270  					break;
1271  				default:
1272  					sb.Append((char)c);
1273  					break;
1274  				}
1275  			}
1276  		}
1277  	}
1278  
1279  	static char SingleCharEscape(int c)
1280  	{
1281  		switch (c) {
1282  		case 'n': return '\n';
1283  		case 'r': return '\r';
1284  		case 't': return '\t';
1285  		case 's': return ' ';
1286  		default:
1287  			return (char)c;
1288  		}
1289  	}
1290  
1291  	/*
1292  	 * A backslash+newline sequence occurred in a literal string; we
1293  	 * check and consume the newline escape sequence (whitespace at
1294  	 * start of next line, then a double-quote character).
1295  	 */
1296  	void SkipNL()
1297  	{
1298  		for (;;) {
1299  			int c = NextChar();
1300  			if (c < 0) {
1301  				throw new Exception("EOF in literal string");
1302  			}
1303  			if (c == '\n') {
1304  				throw new Exception(
1305  					"Unescaped newline in literal string");
1306  			}
1307  			if (IsWS(c)) {
1308  				continue;
1309  			}
1310  			if (c == '"') {
1311  				return;
1312  			}
1313  			throw new Exception(
1314  				"Invalid newline escape in literal string");
1315  		}
1316  	}
1317  
1318  	static char DecodeCharConst(string t)
1319  	{
1320  		if (t.Length == 1 && t[0] != '\\') {
1321  			return t[0];
1322  		}
1323  		if (t.Length >= 2 && t[0] == '\\') {
1324  			switch (t[1]) {
1325  			case 'x':
1326  				if (t.Length == 4) {
1327  					int x = DecHex(t.Substring(2));
1328  					if (x >= 0) {
1329  						return (char)x;
1330  					}
1331  				}
1332  				break;
1333  			case 'u':
1334  				if (t.Length == 6) {
1335  					int x = DecHex(t.Substring(2));
1336  					if (x >= 0) {
1337  						return (char)x;
1338  					}
1339  				}
1340  				break;
1341  			default:
1342  				if (t.Length == 2) {
1343  					return SingleCharEscape(t[1]);
1344  				}
1345  				break;
1346  			}
1347  		}
1348  		throw new Exception("Invalid literal char: `" + t);
1349  	}
1350  
1351  	static int DecHex(string s)
1352  	{
1353  		int acc = 0;
1354  		foreach (char c in s) {
1355  			int d = HexVal(c);
1356  			if (d < 0) {
1357  				return -1;
1358  			}
1359  			acc = (acc << 4) + d;
1360  		}
1361  		return acc;
1362  	}
1363  
1364  	static int HexVal(int c)
1365  	{
1366  		if (c >= '0' && c <= '9') {
1367  			return c - '0';
1368  		} else if (c >= 'A' && c <= 'F') {
1369  			return c - ('A' - 10);
1370  		} else if (c >= 'a' && c <= 'f') {
1371  			return c - ('a' - 10);
1372  		} else {
1373  			return -1;
1374  		}
1375  	}
1376  
1377  	string ReadTerm(int ct)
1378  	{
1379  		StringBuilder sb = new StringBuilder();
1380  		for (;;) {
1381  			int c = NextChar();
1382  			if (c < 0) {
1383  				throw new Exception(String.Format(
1384  					"EOF reached before U+{0:X4}", ct));
1385  			}
1386  			if (c == ct) {
1387  				return sb.ToString();
1388  			}
1389  			sb.Append((char)c);
1390  		}
1391  	}
1392  
1393  	static bool IsWS(int c)
1394  	{
1395  		return c <= 32;
1396  	}
1397  
1398  	void ProcessInput(TextReader tr)
1399  	{
1400  		this.currentInput = tr;
1401  		delayedChar = -1;
1402  		Word w = new WordNative(this, "toplevel",
1403  			xcpu => { CompileStep(xcpu); });
1404  		CPU cpu = new CPU();
1405  		Opcode[] code = new Opcode[] {
1406  			new OpcodeCall(w),
1407  			new OpcodeJumpUncond(-2)
1408  		};
1409  		quitRunLoop = false;
1410  		cpu.Enter(code, 0);
1411  		for (;;) {
1412  			if (quitRunLoop) {
1413  				break;
1414  			}
1415  			Opcode op = cpu.ipBuf[cpu.ipOff ++];
1416  			op.Run(cpu);
1417  		}
1418  	}
1419  
1420  	void CompileStep(CPU cpu)
1421  	{
1422  		string tt = Next();
1423  		if (tt == null) {
1424  			if (compiling) {
1425  				throw new Exception("EOF while compiling");
1426  			}
1427  			quitRunLoop = true;
1428  			return;
1429  		}
1430  		TValue v;
1431  		bool isVal = TryParseLiteral(tt, out v);
1432  		Word w = LookupNF(tt);
1433  		if (isVal && w != null) {
1434  			throw new Exception(String.Format(
1435  				"Ambiguous: both defined word and literal: {0}",
1436  				tt));
1437  		}
1438  		if (compiling) {
1439  			if (isVal) {
1440  				wordBuilder.Literal(v);
1441  			} else if (w != null) {
1442  				if (w.Immediate) {
1443  					w.Run(cpu);
1444  				} else {
1445  					wordBuilder.CallExt(w);
1446  				}
1447  			} else {
1448  				wordBuilder.Call(tt);
1449  			}
1450  		} else {
1451  			if (isVal) {
1452  				cpu.Push(v);
1453  			} else if (w != null) {
1454  				w.Run(cpu);
1455  			} else {
1456  				throw new Exception(String.Format(
1457  					"Unknown word: '{0}'", tt));
1458  			}
1459  		}
1460  	}
1461  
1462  	string GetCCode(string name)
1463  	{
1464  		string ccode;
1465  		allCCode.TryGetValue(name, out ccode);
1466  		return ccode;
1467  	}
1468  
1469  	void Generate(string outBase, string coreRun,
1470  		params string[] entryPoints)
1471  	{
1472  		/*
1473  		 * Gather all words that are part of the generated
1474  		 * code. This is done by exploring references
1475  		 * transitively. All such words are thus implicitly
1476  		 * resolved.
1477  		 */
1478  		IDictionary<string, Word> wordSet =
1479  			new SortedDictionary<string, Word>(
1480  				StringComparer.Ordinal);
1481  		Queue<Word> tx = new Queue<Word>();
1482  		foreach (string ep in entryPoints) {
1483  			if (wordSet.ContainsKey(ep)) {
1484  				continue;
1485  			}
1486  			Word w = Lookup(ep);
1487  			wordSet[w.Name] = w;
1488  			tx.Enqueue(w);
1489  		}
1490  		while (tx.Count > 0) {
1491  			Word w = tx.Dequeue();
1492  			foreach (Word w2 in w.GetReferences()) {
1493  				if (wordSet.ContainsKey(w2.Name)) {
1494  					continue;
1495  				}
1496  				wordSet[w2.Name] = w2;
1497  				tx.Enqueue(w2);
1498  			}
1499  		}
1500  
1501  		/*
1502  		 * Do flow analysis.
1503  		 */
1504  		if (enableFlowAnalysis) {
1505  			foreach (string ep in entryPoints) {
1506  				Word w = wordSet[ep];
1507  				w.AnalyseFlow();
1508  				Console.WriteLine("{0}: ds={1} rs={2}",
1509  					ep, w.MaxDataStack, w.MaxReturnStack);
1510  				if (w.MaxDataStack > dsLimit) {
1511  					throw new Exception("'" + ep
1512  						+ "' exceeds data stack limit");
1513  				}
1514  				if (w.MaxReturnStack > rsLimit) {
1515  					throw new Exception("'" + ep
1516  						+ "' exceeds return stack"
1517  						+ " limit");
1518  				}
1519  			}
1520  		}
1521  
1522  		/*
1523  		 * Gather referenced data areas and compute their
1524  		 * addresses in the generated data block. The address
1525  		 * 0 in the data block is unaffected so that no
1526  		 * valid runtime pointer is equal to null.
1527  		 */
1528  		IDictionary<long, ConstData> blocks =
1529  			new SortedDictionary<long, ConstData>();
1530  		foreach (Word w in wordSet.Values) {
1531  			foreach (ConstData cd in w.GetDataBlocks()) {
1532  				blocks[cd.ID] = cd;
1533  			}
1534  		}
1535  		int dataLen = 1;
1536  		foreach (ConstData cd in blocks.Values) {
1537  			cd.Address = dataLen;
1538  			dataLen += cd.Length;
1539  		}
1540  
1541  		/*
1542  		 * Generated code is a sequence of "slot numbers", each
1543  		 * referencing either a piece of explicit C code, or an
1544  		 * entry in the table of interpreted words.
1545  		 *
1546  		 * Opcodes other than "call" get the slots 0 to 6:
1547  		 *
1548  		 *   0   ret           no argument
1549  		 *   1   const         signed value
1550  		 *   2   get local     local number
1551  		 *   3   put local     local number
1552  		 *   4   jump          signed offset
1553  		 *   5   jump if       signed offset
1554  		 *   6   jump if not   signed offset
1555  		 *
1556  		 * The argument, if any, is in "7E" format: the value is
1557  		 * encoded in 7-bit chunk, with big-endian signed
1558  		 * convention. Each 7-bit chunk is encoded over one byte;
1559  		 * the upper bit is 1 for all chunks except the last one.
1560  		 *
1561  		 * Words with explicit C code get the slot numbers
1562  		 * immediately after 6. Interpreted words come afterwards.
1563  		 */
1564  		IDictionary<string, int> slots = new Dictionary<string, int>();
1565  		int curSlot = 7;
1566  
1567  		/*
1568  		 * Get explicit C code for words which have such code.
1569  		 * We use string equality on C code so that words with
1570  		 * identical implementations get merged.
1571  		 *
1572  		 * We also check that words with no explicit C code are
1573  		 * interpreted.
1574  		 */
1575  		IDictionary<string, int> ccodeUni =
1576  			new Dictionary<string, int>();
1577  		IDictionary<int, string> ccodeNames =
1578  			new Dictionary<int, string>();
1579  		foreach (Word w in wordSet.Values) {
1580  			string ccode = GetCCode(w.Name);
1581  			if (ccode == null) {
1582  				if (w is WordNative) {
1583  					throw new Exception(String.Format(
1584  						"No C code for native '{0}'",
1585  						w.Name));
1586  				}
1587  				continue;
1588  			}
1589  			int sn;
1590  			if (ccodeUni.ContainsKey(ccode)) {
1591  				sn = ccodeUni[ccode];
1592  				ccodeNames[sn] += " " + EscapeCComment(w.Name);
1593  			} else {
1594  				sn = curSlot ++;
1595  				ccodeUni[ccode] = sn;
1596  				ccodeNames[sn] = EscapeCComment(w.Name);
1597  			}
1598  			slots[w.Name] = sn;
1599  			w.Slot = sn;
1600  		}
1601  
1602  		/*
1603  		 * Assign slot values to all remaining words; we know they
1604  		 * are all interpreted.
1605  		 */
1606  		int slotInterpreted = curSlot;
1607  		foreach (Word w in wordSet.Values) {
1608  			if (GetCCode(w.Name) != null) {
1609  				continue;
1610  			}
1611  			int sn = curSlot ++;
1612  			slots[w.Name] = sn;
1613  			w.Slot = sn;
1614  		}
1615  		int numInterpreted = curSlot - slotInterpreted;
1616  
1617  		/*
1618  		 * Verify that all entry points are interpreted words.
1619  		 */
1620  		foreach (string ep in entryPoints) {
1621  			if (GetCCode(ep) != null) {
1622  				throw new Exception(
1623  					"Non-interpreted entry point");
1624  			}
1625  		}
1626  
1627  		/*
1628  		 * Compute the code block. Each word (without any C code)
1629  		 * yields some CodeElement instances.
1630  		 */
1631  		List<CodeElement> gcodeList = new List<CodeElement>();
1632  		CodeElement[] interpretedEntry =
1633  			new CodeElement[numInterpreted];
1634  		foreach (Word w in wordSet.Values) {
1635  			if (GetCCode(w.Name) != null) {
1636  				continue;
1637  			}
1638  			int n = gcodeList.Count;
1639  			w.GenerateCodeElements(gcodeList);
1640  			interpretedEntry[w.Slot - slotInterpreted] =
1641  				gcodeList[n];
1642  		}
1643  		CodeElement[] gcode = gcodeList.ToArray();
1644  
1645  		/*
1646  		 * If there are less than 256 words in total (C +
1647  		 * interpreted) then we can use "one-byte code" which is
1648  		 * more compact when the number of words is in the
1649  		 * 128..255 range.
1650  		 */
1651  		bool oneByteCode;
1652  		if (slotInterpreted + numInterpreted >= 256) {
1653  			Console.WriteLine("WARNING: more than 255 words");
1654  			oneByteCode = false;
1655  		} else {
1656  			oneByteCode = true;
1657  		}
1658  
1659  		/*
1660  		 * Compute all addresses and offsets. This loops until
1661  		 * the addresses stabilize.
1662  		 */
1663  		int totalLen = -1;
1664  		int[] gcodeLen = new int[gcode.Length];
1665  		for (;;) {
1666  			for (int i = 0; i < gcode.Length; i ++) {
1667  				gcodeLen[i] = gcode[i].GetLength(oneByteCode);
1668  			}
1669  			int off = 0;
1670  			for (int i = 0; i < gcode.Length; i ++) {
1671  				gcode[i].Address = off;
1672  				gcode[i].LastLength = gcodeLen[i];
1673  				off += gcodeLen[i];
1674  			}
1675  			if (off == totalLen) {
1676  				break;
1677  			}
1678  			totalLen = off;
1679  		}
1680  
1681  		/*
1682  		 * Produce output file.
1683  		 */
1684  		using (TextWriter tw = File.CreateText(outBase + ".c")) {
1685  			tw.NewLine = "\n";
1686  
1687  			tw.WriteLine("{0}",
1688  @"/* Automatically generated code; do not modify directly. */
1689  
1690  #include <stddef.h>
1691  #include <stdint.h>
1692  
1693  typedef struct {
1694  	uint32_t *dp;
1695  	uint32_t *rp;
1696  	const unsigned char *ip;
1697  } t0_context;
1698  
1699  static uint32_t
1700  t0_parse7E_unsigned(const unsigned char **p)
1701  {
1702  	uint32_t x;
1703  
1704  	x = 0;
1705  	for (;;) {
1706  		unsigned y;
1707  
1708  		y = *(*p) ++;
1709  		x = (x << 7) | (uint32_t)(y & 0x7F);
1710  		if (y < 0x80) {
1711  			return x;
1712  		}
1713  	}
1714  }
1715  
1716  static int32_t
1717  t0_parse7E_signed(const unsigned char **p)
1718  {
1719  	int neg;
1720  	uint32_t x;
1721  
1722  	neg = ((**p) >> 6) & 1;
1723  	x = (uint32_t)-neg;
1724  	for (;;) {
1725  		unsigned y;
1726  
1727  		y = *(*p) ++;
1728  		x = (x << 7) | (uint32_t)(y & 0x7F);
1729  		if (y < 0x80) {
1730  			if (neg) {
1731  				return -(int32_t)~x - 1;
1732  			} else {
1733  				return (int32_t)x;
1734  			}
1735  		}
1736  	}
1737  }
1738  
1739  #define T0_VBYTE(x, n)   (unsigned char)((((uint32_t)(x) >> (n)) & 0x7F) | 0x80)
1740  #define T0_FBYTE(x, n)   (unsigned char)(((uint32_t)(x) >> (n)) & 0x7F)
1741  #define T0_SBYTE(x)      (unsigned char)((((uint32_t)(x) >> 28) + 0xF8) ^ 0xF8)
1742  #define T0_INT1(x)       T0_FBYTE(x, 0)
1743  #define T0_INT2(x)       T0_VBYTE(x, 7), T0_FBYTE(x, 0)
1744  #define T0_INT3(x)       T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)
1745  #define T0_INT4(x)       T0_VBYTE(x, 21), T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)
1746  #define T0_INT5(x)       T0_SBYTE(x), T0_VBYTE(x, 21), T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)
1747  
1748  /* static const unsigned char t0_datablock[]; */
1749  ");
1750  
1751  			/*
1752  			 * Add declarations (not definitions) for the
1753  			 * entry point initialisation functions, and the
1754  			 * runner.
1755  			 */
1756  			tw.WriteLine();
1757  			foreach (string ep in entryPoints) {
1758  				tw.WriteLine("void {0}_init_{1}(void *t0ctx);",
1759  					coreRun, ep);
1760  			}
1761  			tw.WriteLine();
1762  			tw.WriteLine("void {0}_run(void *t0ctx);", coreRun);
1763  
1764  			/*
1765  			 * Add preamble elements here. They may be needed
1766  			 * for evaluating constant expressions in the
1767  			 * code block.
1768  			 */
1769  			foreach (string pp in extraCode) {
1770  				tw.WriteLine();
1771  				tw.WriteLine("{0}", pp);
1772  			}
1773  
1774  			BlobWriter bw;
1775  			tw.WriteLine();
1776  			tw.Write("static const unsigned char"
1777  				+ " t0_datablock[] = {");
1778  			bw = new BlobWriter(tw, 78, 1);
1779  			bw.Append((byte)0);
1780  			foreach (ConstData cd in blocks.Values) {
1781  				cd.Encode(bw);
1782  			}
1783  			tw.WriteLine();
1784  			tw.WriteLine("};");
1785  
1786  			tw.WriteLine();
1787  			tw.Write("static const unsigned char"
1788  				+ " t0_codeblock[] = {");
1789  			bw = new BlobWriter(tw, 78, 1);
1790  			foreach (CodeElement ce in gcode) {
1791  				ce.Encode(bw, oneByteCode);
1792  			}
1793  			tw.WriteLine();
1794  			tw.WriteLine("};");
1795  
1796  			tw.WriteLine();
1797  			tw.Write("static const uint16_t t0_caddr[] = {");
1798  			for (int i = 0; i < interpretedEntry.Length; i ++) {
1799  				if (i != 0) {
1800  					tw.Write(',');
1801  				}
1802  				tw.WriteLine();
1803  				tw.Write("\t{0}", interpretedEntry[i].Address);
1804  			}
1805  			tw.WriteLine();
1806  			tw.WriteLine("};");
1807  
1808  			tw.WriteLine();
1809  			tw.WriteLine("#define T0_INTERPRETED   {0}",
1810  				slotInterpreted);
1811  			tw.WriteLine();
1812  			tw.WriteLine("{0}",
1813  @"#define T0_ENTER(ip, rp, slot)   do { \
1814  		const unsigned char *t0_newip; \
1815  		uint32_t t0_lnum; \
1816  		t0_newip = &t0_codeblock[t0_caddr[(slot) - T0_INTERPRETED]]; \
1817  		t0_lnum = t0_parse7E_unsigned(&t0_newip); \
1818  		(rp) += t0_lnum; \
1819  		*((rp) ++) = (uint32_t)((ip) - &t0_codeblock[0]) + (t0_lnum << 16); \
1820  		(ip) = t0_newip; \
1821  	} while (0)");
1822  			tw.WriteLine();
1823  			tw.WriteLine("{0}",
1824  @"#define T0_DEFENTRY(name, slot) \
1825  void \
1826  name(void *ctx) \
1827  { \
1828  	t0_context *t0ctx = ctx; \
1829  	t0ctx->ip = &t0_codeblock[0]; \
1830  	T0_ENTER(t0ctx->ip, t0ctx->rp, slot); \
1831  }");
1832  
1833  			tw.WriteLine();
1834  			foreach (string ep in entryPoints) {
1835  				tw.WriteLine("T0_DEFENTRY({0}, {1})",
1836  					coreRun + "_init_" + ep,
1837  					wordSet[ep].Slot);
1838  			}
1839  			tw.WriteLine();
1840  			if (oneByteCode) {
1841  				tw.WriteLine("{0}",
1842  @"#define T0_NEXT(t0ipp)   (*(*(t0ipp)) ++)");
1843  			} else {
1844  				tw.WriteLine("{0}",
1845  @"#define T0_NEXT(t0ipp)   t0_parse7E_unsigned(t0ipp)");
1846  			}
1847  			tw.WriteLine();
1848  			tw.WriteLine("void");
1849  			tw.WriteLine("{0}_run(void *t0ctx)", coreRun);
1850  			tw.WriteLine("{0}",
1851  @"{
1852  	uint32_t *dp, *rp;
1853  	const unsigned char *ip;
1854  
1855  #define T0_LOCAL(x)    (*(rp - 2 - (x)))
1856  #define T0_POP()       (*-- dp)
1857  #define T0_POPi()      (*(int32_t *)(-- dp))
1858  #define T0_PEEK(x)     (*(dp - 1 - (x)))
1859  #define T0_PEEKi(x)    (*(int32_t *)(dp - 1 - (x)))
1860  #define T0_PUSH(v)     do { *dp = (v); dp ++; } while (0)
1861  #define T0_PUSHi(v)    do { *(int32_t *)dp = (v); dp ++; } while (0)
1862  #define T0_RPOP()      (*-- rp)
1863  #define T0_RPOPi()     (*(int32_t *)(-- rp))
1864  #define T0_RPUSH(v)    do { *rp = (v); rp ++; } while (0)
1865  #define T0_RPUSHi(v)   do { *(int32_t *)rp = (v); rp ++; } while (0)
1866  #define T0_ROLL(x)     do { \
1867  	size_t t0len = (size_t)(x); \
1868  	uint32_t t0tmp = *(dp - 1 - t0len); \
1869  	memmove(dp - t0len - 1, dp - t0len, t0len * sizeof *dp); \
1870  	*(dp - 1) = t0tmp; \
1871  } while (0)
1872  #define T0_SWAP()      do { \
1873  	uint32_t t0tmp = *(dp - 2); \
1874  	*(dp - 2) = *(dp - 1); \
1875  	*(dp - 1) = t0tmp; \
1876  } while (0)
1877  #define T0_ROT()       do { \
1878  	uint32_t t0tmp = *(dp - 3); \
1879  	*(dp - 3) = *(dp - 2); \
1880  	*(dp - 2) = *(dp - 1); \
1881  	*(dp - 1) = t0tmp; \
1882  } while (0)
1883  #define T0_NROT()       do { \
1884  	uint32_t t0tmp = *(dp - 1); \
1885  	*(dp - 1) = *(dp - 2); \
1886  	*(dp - 2) = *(dp - 3); \
1887  	*(dp - 3) = t0tmp; \
1888  } while (0)
1889  #define T0_PICK(x)      do { \
1890  	uint32_t t0depth = (x); \
1891  	T0_PUSH(T0_PEEK(t0depth)); \
1892  } while (0)
1893  #define T0_CO()         do { \
1894  	goto t0_exit; \
1895  } while (0)
1896  #define T0_RET()        goto t0_next
1897  
1898  	dp = ((t0_context *)t0ctx)->dp;
1899  	rp = ((t0_context *)t0ctx)->rp;
1900  	ip = ((t0_context *)t0ctx)->ip;
1901  	goto t0_next;
1902  	for (;;) {
1903  		uint32_t t0x;
1904  
1905  	t0_next:
1906  		t0x = T0_NEXT(&ip);
1907  		if (t0x < T0_INTERPRETED) {
1908  			switch (t0x) {
1909  				int32_t t0off;
1910  
1911  			case 0: /* ret */
1912  				t0x = T0_RPOP();
1913  				rp -= (t0x >> 16);
1914  				t0x &= 0xFFFF;
1915  				if (t0x == 0) {
1916  					ip = NULL;
1917  					goto t0_exit;
1918  				}
1919  				ip = &t0_codeblock[t0x];
1920  				break;
1921  			case 1: /* literal constant */
1922  				T0_PUSHi(t0_parse7E_signed(&ip));
1923  				break;
1924  			case 2: /* read local */
1925  				T0_PUSH(T0_LOCAL(t0_parse7E_unsigned(&ip)));
1926  				break;
1927  			case 3: /* write local */
1928  				T0_LOCAL(t0_parse7E_unsigned(&ip)) = T0_POP();
1929  				break;
1930  			case 4: /* jump */
1931  				t0off = t0_parse7E_signed(&ip);
1932  				ip += t0off;
1933  				break;
1934  			case 5: /* jump if */
1935  				t0off = t0_parse7E_signed(&ip);
1936  				if (T0_POP()) {
1937  					ip += t0off;
1938  				}
1939  				break;
1940  			case 6: /* jump if not */
1941  				t0off = t0_parse7E_signed(&ip);
1942  				if (!T0_POP()) {
1943  					ip += t0off;
1944  				}
1945  				break;");
1946  
1947  			SortedDictionary<int, string> nccode =
1948  				new SortedDictionary<int, string>();
1949  			foreach (string k in ccodeUni.Keys) {
1950  				nccode[ccodeUni[k]] = k;
1951  			}
1952  			foreach (int sn in nccode.Keys) {
1953  				tw.WriteLine(
1954  @"			case {0}: {{
1955  				/* {1} */
1956  {2}
1957  				}}
1958  				break;", sn, ccodeNames[sn], nccode[sn]);
1959  			}
1960  
1961  			tw.WriteLine(
1962  @"			}
1963  
1964  		} else {
1965  			T0_ENTER(ip, rp, t0x);
1966  		}
1967  	}
1968  t0_exit:
1969  	((t0_context *)t0ctx)->dp = dp;
1970  	((t0_context *)t0ctx)->rp = rp;
1971  	((t0_context *)t0ctx)->ip = ip;
1972  }");
1973  
1974  			/*
1975  			 * Add the "postamblr" elements here. These are
1976  			 * elements that may need access to the data
1977  			 * block or code block, so they must occur after
1978  			 * their definition.
1979  			 */
1980  			foreach (string pp in extraCodeDefer) {
1981  				tw.WriteLine();
1982  				tw.WriteLine("{0}", pp);
1983  			}
1984  		}
1985  
1986  		int codeLen = 0;
1987  		foreach (CodeElement ce in gcode) {
1988  			codeLen += ce.GetLength(oneByteCode);
1989  		}
1990  		int dataBlockLen = 0;
1991  		foreach (ConstData cd in blocks.Values) {
1992  			dataBlockLen += cd.Length;
1993  		}
1994  
1995  		/*
1996  		 * Write some statistics on produced code.
1997  		 */
1998  		Console.WriteLine("code length: {0,6} byte(s)", codeLen);
1999  		Console.WriteLine("data length: {0,6} byte(s)", dataLen);
2000  		Console.WriteLine("total words: {0} (interpreted: {1})",
2001  			slotInterpreted + numInterpreted, numInterpreted);
2002  	}
2003  
2004  	internal Word Lookup(string name)
2005  	{
2006  		Word w = LookupNF(name);
2007  		if (w != null) {
2008  			return w;
2009  		}
2010  		throw new Exception(String.Format("No such word: '{0}'", name));
2011  	}
2012  
2013  	internal Word LookupNF(string name)
2014  	{
2015  		Word w;
2016  		words.TryGetValue(name, out w);
2017  		return w;
2018  	}
2019  
2020  	internal TValue StringToBlob(string s)
2021  	{
2022  		return new TValue(0, new TPointerBlob(this, s));
2023  	}
2024  
2025  	internal bool TryParseLiteral(string tt, out TValue tv)
2026  	{
2027  		tv = new TValue(0);
2028  		if (tt.StartsWith("\"")) {
2029  			tv = StringToBlob(tt.Substring(1));
2030  			return true;
2031  		}
2032  		if (tt.StartsWith("`")) {
2033  			tv = DecodeCharConst(tt.Substring(1));
2034  			return true;
2035  		}
2036  		bool neg = false;
2037  		if (tt.StartsWith("-")) {
2038  			neg = true;
2039  			tt = tt.Substring(1);
2040  		} else if (tt.StartsWith("+")) {
2041  			tt = tt.Substring(1);
2042  		}
2043  		uint radix = 10;
2044  		if (tt.StartsWith("0x") || tt.StartsWith("0X")) {
2045  			radix = 16;
2046  			tt = tt.Substring(2);
2047  		} else if (tt.StartsWith("0b") || tt.StartsWith("0B")) {
2048  			radix = 2;
2049  			tt = tt.Substring(2);
2050  		}
2051  		if (tt.Length == 0) {
2052  			return false;
2053  		}
2054  		uint acc = 0;
2055  		bool overflow = false;
2056  		uint maxV = uint.MaxValue / radix;
2057  		foreach (char c in tt) {
2058  			int d = HexVal(c);
2059  			if (d < 0 || d >= radix) {
2060  				return false;
2061  			}
2062  			if (acc > maxV) {
2063  				overflow = true;
2064  			}
2065  			acc *= radix;
2066  			if ((uint)d > uint.MaxValue - acc) {
2067  				overflow = true;
2068  			}
2069  			acc += (uint)d;
2070  		}
2071  		int x = (int)acc;
2072  		if (neg) {
2073  			if (acc > (uint)0x80000000) {
2074  				overflow = true;
2075  			}
2076  			x = -x;
2077  		}
2078  		if (overflow) {
2079  			throw new Exception(
2080  				"invalid literal integer (overflow)");
2081  		}
2082  		tv = x;
2083  		return true;
2084  	}
2085  
2086  	int ParseInteger(string tt)
2087  	{
2088  		TValue tv;
2089  		if (!TryParseLiteral(tt, out tv)) {
2090  			throw new Exception("not an integer: " + ToString());
2091  		}
2092  		return (int)tv;
2093  	}
2094  
2095  	void CheckCompiling()
2096  	{
2097  		if (!compiling) {
2098  			throw new Exception("Not in compilation mode");
2099  		}
2100  	}
2101  
2102  	static string EscapeCComment(string s)
2103  	{
2104  		StringBuilder sb = new StringBuilder();
2105  		foreach (char c in s) {
2106  			if (c >= 33 && c <= 126 && c != '%') {
2107  				sb.Append(c);
2108  			} else if (c < 0x100) {
2109  				sb.AppendFormat("%{0:X2}", (int)c);
2110  			} else if (c < 0x800) {
2111  				sb.AppendFormat("%{0:X2}%{0:X2}",
2112  					((int)c >> 6) | 0xC0,
2113  					((int)c & 0x3F) | 0x80);
2114  			} else {
2115  				sb.AppendFormat("%{0:X2}%{0:X2}%{0:X2}",
2116  					((int)c >> 12) | 0xE0,
2117  					(((int)c >> 6) & 0x3F) | 0x80,
2118  					((int)c & 0x3F) | 0x80);
2119  			}
2120  		}
2121  		return sb.ToString().Replace("*/", "%2A/");
2122  	}
2123  }