/*
 * Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

/*
 * This is the main compiler class.
 */

public class T0Comp {

	/*
	 * Command-line entry point.
	 */
	public static void Main(string[] args)
	{
		try {
			List<string> r = new List<string>();
			string outBase = null;
			List<string> entryPoints = new List<string>();
			string coreRun = null;
			bool flow = true;
			int dsLim = 32;
			int rsLim = 32;
			for (int i = 0; i < args.Length; i ++) {
				string a = args[i];
				if (!a.StartsWith("-")) {
					r.Add(a);
					continue;
				}
				if (a == "--") {
					for (;;) {
						if (++ i >= args.Length) {
							break;
						}
						r.Add(args[i]);
					}
					break;
				}
				while (a.StartsWith("-")) {
					a = a.Substring(1);
				}
				int j = a.IndexOf('=');
				string pname;
				string pval, pval2;
				if (j < 0) {
					pname = a.ToLowerInvariant();
					pval = null;
					pval2 = (i + 1) < args.Length
						? args[i + 1] : null;
				} else {
					pname = a.Substring(0, j).Trim()
						.ToLowerInvariant();
					pval = a.Substring(j + 1);
					pval2 = null;
				}
				switch (pname) {
				case "o":
				case "out":
					if (pval == null) {
						if (pval2 == null) {
							Usage();
						}
						i ++;
						pval = pval2;
					}
					if (outBase != null) {
						Usage();
					}
					outBase = pval;
					break;
				case "r":
				case "run":
					if (pval == null) {
						if (pval2 == null) {
							Usage();
						}
						i ++;
						pval = pval2;
					}
					coreRun = pval;
					break;
				case "m":
				case "main":
					if (pval == null) {
						if (pval2 == null) {
							Usage();
						}
						i ++;
						pval = pval2;
					}
					foreach (string ep in pval.Split(',')) {
						string epz = ep.Trim();
						if (epz.Length > 0) {
							entryPoints.Add(epz);
						}
					}
					break;
				case "nf":
				case "noflow":
					flow = false;
					break;
				default:
					Usage();
					break;
				}
			}
			if (r.Count == 0) {
				Usage();
			}
			if (outBase == null) {
				outBase = "t0out";
			}
			if (entryPoints.Count == 0) {
				entryPoints.Add("main");
			}
			if (coreRun == null) {
				coreRun = outBase;
			}
			T0Comp tc = new T0Comp();
			tc.enableFlowAnalysis = flow;
			tc.dsLimit = dsLim;
			tc.rsLimit = rsLim;
			using (TextReader tr = new StreamReader(
				Assembly.GetExecutingAssembly()
				.GetManifestResourceStream("t0-kernel")))
			{
				tc.ProcessInput(tr);
			}
			foreach (string a in r) {
				Console.WriteLine("[{0}]", a);
				using (TextReader tr = File.OpenText(a)) {
					tc.ProcessInput(tr);
				}
			}
			tc.Generate(outBase, coreRun, entryPoints.ToArray());
		} catch (Exception e) {
			Console.WriteLine(e.ToString());
			Environment.Exit(1);
		}
	}

	static void Usage()
	{
		Console.WriteLine(
"usage: T0Comp.exe [ options... ] file...");
		Console.WriteLine(
"options:");
		Console.WriteLine(
"   -o file    use 'file' as base for output file name (default: 't0out')");
		Console.WriteLine(
"   -r name    use 'name' as base for run function (default: same as output)");
		Console.WriteLine(
"   -m name[,name...]");
		Console.WriteLine(
"              define entry point(s)");
		Console.WriteLine(
"   -nf        disable flow analysis");
		Environment.Exit(1);
	}

	/*
	 * If 'delayedChar' is Int32.MinValue then there is no delayed
	 * character.
	 * If 'delayedChar' equals x >= 0 then there is one delayed
	 * character of value x.
	 * If 'delayedChar' equals y < 0 then there are two delayed
	 * characters, a newline (U+000A) followed by character of
	 * value -(y+1).
	 */
	TextReader currentInput;
	int delayedChar;

	/*
	 * Common StringBuilder used to parse tokens; it is reused for
	 * each new token.
	 */
	StringBuilder tokenBuilder;

	/*
	 * There may be a delayed token in some cases.
	 */
	String delayedToken;

	/*
	 * Defined words are referenced by name in this map. Names are
	 * string-sensitive; for better reproducibility, the map is sorted
	 * (ordinal order).
	 */
	IDictionary<string, Word> words;

	/*
	 * Last defined word is also referenced in 'lastWord'. This is
	 * used by 'immediate'.
	 */
	Word lastWord;

	/*
	 * When compiling, this builder is used. A stack saves other
	 * builders in case of nested definition.
	 */
	WordBuilder wordBuilder;
	Stack<WordBuilder> savedWordBuilders;

	/*
	 * C code defined for words is kept in this map, by word name.
	 */
	IDictionary<string, string> allCCode;

	/*
	 * 'compiling' is true when compiling tokens to a word, false
	 * when interpreting them.
	 */
	bool compiling;

	/*
	 * 'quitRunLoop' is set to true to trigger exit of the
	 * interpretation loop when the end of the current input file
	 * is reached.
	 */
	bool quitRunLoop;

	/*
	 * 'extraCode' is for C code that is to be added as preamble to
	 * the C output.
	 */
	List<string> extraCode;

	/*
	 * 'extraCodeDefer' is for C code that is to be added in the C
	 * output _after_ the data and code blocks.
	 */
	List<string> extraCodeDefer;

	/*
	 * 'dataBlock' is the data block in which constant data bytes
	 * are accumulated.
	 */
	ConstData dataBlock;

	/*
	 * Counter for blocks of constant data.
	 */
	long currentBlobID;

	/*
	 * Flow analysis enable flag.
	 */
	bool enableFlowAnalysis;

	/*
	 * Data stack size limit.
	 */
	int dsLimit;

	/*
	 * Return stack size limit.
	 */
	int rsLimit;

	T0Comp()
	{
		tokenBuilder = new StringBuilder();
		words = new SortedDictionary<string, Word>(
			StringComparer.Ordinal);
		savedWordBuilders = new Stack<WordBuilder>();
		allCCode = new SortedDictionary<string, string>(
			StringComparer.Ordinal);
		compiling = false;
		extraCode = new List<string>();
		extraCodeDefer = new List<string>();
		enableFlowAnalysis = true;

		/*
		 * Native words are predefined and implemented only with
		 * native code. Some may be part of the generated output,
		 * if C code is set for them.
		 */

		/*
		 * add-cc:
		 * Parses next token as a word name, then a C code snippet.
		 * Sets the C code for that word.
		 */
		AddNative("add-cc:", false, SType.BLANK, cpu => {
			string tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing name)");
			}
			if (allCCode.ContainsKey(tt)) {
				throw new Exception(
					"C code already set for: " + tt);
			}
			allCCode[tt] = ParseCCode();
		});

		/*
		 * cc:
		 * Parses next token as a word name, then a C code snippet.
		 * A new word is defined, that throws an exception when
		 * invoked during compilation. The C code is set for that
		 * new word.
		 */
		AddNative("cc:", false, SType.BLANK, cpu => {
			string tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing name)");
			}
			Word w = AddNative(tt, false, cpu2 => {
				throw new Exception(
					"C-only word: " + tt);
			});
			if (allCCode.ContainsKey(tt)) {
				throw new Exception(
					"C code already set for: " + tt);
			}
			SType stackEffect;
			allCCode[tt] = ParseCCode(out stackEffect);
			w.StackEffect = stackEffect;
		});

		/*
		 * preamble
		 * Parses a C code snippet, then adds it to the generated
		 * output preamble.
		 */
		AddNative("preamble", false, SType.BLANK, cpu => {
			extraCode.Add(ParseCCode());
		});

		/*
		 * postamble
		 * Parses a C code snippet, then adds it to the generated
		 * output after the data and code blocks.
		 */
		AddNative("postamble", false, SType.BLANK, cpu => {
			extraCodeDefer.Add(ParseCCode());
		});

		/*
		 * make-CX
		 * Expects two integers and a string, and makes a
		 * constant that stands for the string as a C constant
		 * expression. The two integers are the expected range
		 * (min-max, inclusive).
		 */
		AddNative("make-CX", false, new SType(3, 1), cpu => {
			TValue c = cpu.Pop();
			if (!(c.ptr is TPointerBlob)) {
				throw new Exception(string.Format(
					"'{0}' is not a string", c));
			}
			int max = cpu.Pop();
			int min = cpu.Pop();
			TValue tv = new TValue(0, new TPointerExpr(
				c.ToString(), min, max));
			cpu.Push(tv);
		});

		/*
		 * CX  (immediate)
		 * Parses two integer constants, then a C code snippet.
		 * It then pushes on the stack, or compiles to the
		 * current word, a value consisting of the given C
		 * expression; the two integers indicate the expected
		 * range (min-max, inclusive) of the C expression when
		 * evaluated.
		 */
		AddNative("CX", true, cpu => {
			string tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing min value)");
			}
			int min = ParseInteger(tt);
			tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing max value)");
			}
			int max = ParseInteger(tt);
			if (max < min) {
				throw new Exception("min/max in wrong order");
			}
			TValue tv = new TValue(0, new TPointerExpr(
				ParseCCode().Trim(), min, max));
			if (compiling) {
				wordBuilder.Literal(tv);
			} else {
				cpu.Push(tv);
			}
		});

		/*
		 * co
		 * Interrupt the current execution. This implements
		 * coroutines. It cannot be invoked during compilation.
		 */
		AddNative("co", false, SType.BLANK, cpu => {
			throw new Exception("No coroutine in compile mode");
		});

		/*
		 * :
		 * Parses next token as word name. It begins definition
		 * of that word, setting it as current target for
		 * word building. Any previously opened word is saved
		 * and will become available again as a target when that
		 * new word is finished building.
		 */
		AddNative(":", false, cpu => {
			string tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing name)");
			}
			if (compiling) {
				savedWordBuilders.Push(wordBuilder);
			} else {
				compiling = true;
			}
			wordBuilder = new WordBuilder(this, tt);
			tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (while compiling)");
			}
			if (tt == "(") {
				SType stackEffect = ParseStackEffectNF();
				if (!stackEffect.IsKnown) {
					throw new Exception(
						"Invalid stack effect syntax");
				}
				wordBuilder.StackEffect = stackEffect;
			} else {
				delayedToken = tt;
			}
		});

		/*
		 * Pops a string as word name, and two integers as stack
		 * effect. It begins definition of that word, setting it
		 * as current target for word building. Any previously
		 * opened word is saved and will become available again as
		 * a target when that new word is finished building.
		 *
		 * Stack effect is the pair 'din dout'. If din is negative,
		 * then the stack effect is "unknown". If din is nonnegative
		 * but dout is negative, then the word is reputed never to
		 * return.
		 */
		AddNative("define-word", false, cpu => {
			int dout = cpu.Pop();
			int din = cpu.Pop();
			TValue s = cpu.Pop();
			if (!(s.ptr is TPointerBlob)) {
				throw new Exception(string.Format(
					"Not a string: '{0}'", s));
			}
			string tt = s.ToString();
			if (compiling) {
				savedWordBuilders.Push(wordBuilder);
			} else {
				compiling = true;
			}
			wordBuilder = new WordBuilder(this, tt);
			wordBuilder.StackEffect = new SType(din, dout);
		});

		/*
		 * ;  (immediate)
		 * Ends current word. The current word is registered under
		 * its name, and the previously opened word (if any) becomes
		 * again the building target.
		 */
		AddNative(";", true, cpu => {
			if (!compiling) {
				throw new Exception("Not compiling");
			}
			Word w = wordBuilder.Build();
			string name = w.Name;
			if (words.ContainsKey(name)) {
				throw new Exception(
					"Word already defined: " + name);
			}
			words[name] = w;
			lastWord = w;
			if (savedWordBuilders.Count > 0) {
				wordBuilder = savedWordBuilders.Pop();
			} else {
				wordBuilder = null;
				compiling = false;
			}
		});

		/*
		 * immediate
		 * Sets the last defined word as immediate.
		 */
		AddNative("immediate", false, cpu => {
			if (lastWord == null) {
				throw new Exception("No word defined yet");
			}
			lastWord.Immediate = true;
		});

		/*
		 * literal  (immediate)
		 * Pops the current TOS value, and add in the current word
		 * the action of pushing that value. This cannot be used
		 * when no word is being built.
		 */
		WordNative wliteral = AddNative("literal", true, cpu => {
			CheckCompiling();
			wordBuilder.Literal(cpu.Pop());
		});

		/*
		 * compile
		 * Pops the current TOS value, which must be an XT (pointer
		 * to a word); the action of calling that word is compiled
		 * in the current word.
		 */
		WordNative wcompile = AddNative("compile", false, cpu => {
			CheckCompiling();
			wordBuilder.Call(cpu.Pop().ToXT());
		});

		/*
		 * postpone  (immediate)
		 * Parses the next token as a word name, and add to the
		 * current word the action of calling that word. This
		 * basically removes immediatety from the next word.
		 */
		AddNative("postpone", true, cpu => {
			CheckCompiling();
			string tt = Next();
			if (tt == null) {
				throw new Exception(
					"EOF reached (missing name)");
			}
			TValue v;
			bool isVal = TryParseLiteral(tt, out v);
			Word w = LookupNF(tt);
			if (isVal && w != null) {
				throw new Exception(String.Format(
					"Ambiguous: both defined word and"
					+ " literal: {0}", tt));
			}
			if (isVal) {
				wordBuilder.Literal(v);
				wordBuilder.CallExt(wliteral);
			} else if (w != null) {
				if (w.Immediate) {
					wordBuilder.CallExt(w);
				} else {
					wordBuilder.Literal(new TValue(0,
						new TPointerXT(w)));
					wordBuilder.CallExt(wcompile);
				}
			} else {
				wordBuilder.Literal(new TValue(0,
					new TPointerXT(tt)));
				wordBuilder.CallExt(wcompile);
			}
		});

		/*
		 * Interrupt compilation with an error.
		 */
		AddNative("exitvm", false, cpu => {
			throw new Exception();
		});

		/*
		 * Open a new data block. Its symbolic address is pushed
		 * on the stack.
		 */
		AddNative("new-data-block", false, cpu => {
			dataBlock = new ConstData(this);
			cpu.Push(new TValue(0, new TPointerBlob(dataBlock)));
		});

		/*
		 * Define a new data word. The data address and name are
		 * popped from the stack.
		 */
		AddNative("define-data-word", false, cpu => {
			string name = cpu.Pop().ToString();
			TValue va = cpu.Pop();
			TPointerBlob tb = va.ptr as TPointerBlob;
			if (tb == null) {
				throw new Exception(
					"Address is not a data area");
			}
			Word w = new WordData(this, name, tb.Blob, va.x);
			if (words.ContainsKey(name)) {
				throw new Exception(
					"Word already defined: " + name);
			}
			words[name] = w;
			lastWord = w;
		});

		/*
		 * Get an address pointing at the end of the current
		 * data block. This is the address of the next byte that
		 * will be added.
		 */
		AddNative("current-data", false, cpu => {
			if (dataBlock == null) {
				throw new Exception(
					"No current data block");
			}
			cpu.Push(new TValue(dataBlock.Length,
				new TPointerBlob(dataBlock)));
		});

		/*
		 * Add a byte value to the data block.
		 */
		AddNative("data-add8", false, cpu => {
			if (dataBlock == null) {
				throw new Exception(
					"No current data block");
			}
			int v = cpu.Pop();
			if (v < 0 || v > 0xFF) {
				throw new Exception(
					"Byte value out of range: " + v);
			}
			dataBlock.Add8((byte)v);
		});

		/*
		 * Set a byte value in the data block.
		 */
		AddNative("data-set8", false, cpu => {
			TValue va = cpu.Pop();
			TPointerBlob tb = va.ptr as TPointerBlob;
			if (tb == null) {
				throw new Exception(
					"Address is not a data area");
			}
			int v = cpu.Pop();
			if (v < 0 || v > 0xFF) {
				throw new Exception(
					"Byte value out of range: " + v);
			}
			tb.Blob.Set8(va.x, (byte)v);
		});

		/*
		 * Get a byte value from a data block.
		 */
		AddNative("data-get8", false, new SType(1, 1), cpu => {
			TValue va = cpu.Pop();
			TPointerBlob tb = va.ptr as TPointerBlob;
			if (tb == null) {
				throw new Exception(
					"Address is not a data area");
			}
			int v = tb.Blob.Read8(va.x);
			cpu.Push(v);
		});

		/*
		 *
		 */
		AddNative("compile-local-read", false, cpu => {
			CheckCompiling();
			wordBuilder.GetLocal(cpu.Pop().ToString());
		});
		AddNative("compile-local-write", false, cpu => {
			CheckCompiling();
			wordBuilder.PutLocal(cpu.Pop().ToString());
		});

		AddNative("ahead", true, cpu => {
			CheckCompiling();
			wordBuilder.Ahead();
		});
		AddNative("begin", true, cpu => {
			CheckCompiling();
			wordBuilder.Begin();
		});
		AddNative("again", true, cpu => {
			CheckCompiling();
			wordBuilder.Again();
		});
		AddNative("until", true, cpu => {
			CheckCompiling();
			wordBuilder.AgainIfNot();
		});
		AddNative("untilnot", true, cpu => {
			CheckCompiling();
			wordBuilder.AgainIf();
		});
		AddNative("if", true, cpu => {
			CheckCompiling();
			wordBuilder.AheadIfNot();
		});
		AddNative("ifnot", true, cpu => {
			CheckCompiling();
			wordBuilder.AheadIf();
		});
		AddNative("then", true, cpu => {
			CheckCompiling();
			wordBuilder.Then();
		});
		AddNative("cs-pick", false, cpu => {
			CheckCompiling();
			wordBuilder.CSPick(cpu.Pop());
		});
		AddNative("cs-roll", false, cpu => {
			CheckCompiling();
			wordBuilder.CSRoll(cpu.Pop());
		});
		AddNative("next-word", false, cpu => {
			string s = Next();
			if (s == null) {
				throw new Exception("No next word (EOF)");
			}
			cpu.Push(StringToBlob(s));
		});
		AddNative("parse", false, cpu => {
			int d = cpu.Pop();
			string s = ReadTerm(d);
			cpu.Push(StringToBlob(s));
		});
		AddNative("char", false, cpu => {
			int c = NextChar();
			if (c < 0) {
				throw new Exception("No next character (EOF)");
			}
			cpu.Push(c);
		});
		AddNative("'", false, cpu => {
			string name = Next();
			cpu.Push(new TValue(0, new TPointerXT(name)));
		});

		/*
		 * The "execute" word is valid in generated C code, but
		 * since it jumps to a runtime pointer, its actual stack
		 * effect cannot be computed in advance.
		 */
		AddNative("execute", false, cpu => {
			cpu.Pop().Execute(this, cpu);
		});

		AddNative("[", true, cpu => {
			CheckCompiling();
			compiling = false;
		});
		AddNative("]", false, cpu => {
			compiling = true;
		});
		AddNative("(local)", false, cpu => {
			CheckCompiling();
			wordBuilder.DefLocal(cpu.Pop().ToString());
		});
		AddNative("ret", true, cpu => {
			CheckCompiling();
			wordBuilder.Ret();
		});

		AddNative("drop", false, new SType(1, 0), cpu => {
			cpu.Pop();
		});
		AddNative("dup", false, new SType(1, 2), cpu => {
			cpu.Push(cpu.Peek(0));
		});
		AddNative("swap", false, new SType(2, 2), cpu => {
			cpu.Rot(1);
		});
		AddNative("over", false, new SType(2, 3), cpu => {
			cpu.Push(cpu.Peek(1));
		});
		AddNative("rot", false, new SType(3, 3), cpu => {
			cpu.Rot(2);
		});
		AddNative("-rot", false, new SType(3, 3), cpu => {
			cpu.NRot(2);
		});

		/*
		 * "roll" and "pick" are special in that the stack slot
		 * they inspect might be known only at runtime, so an
		 * absolute stack effect cannot be attributed. Instead,
		 * we simply hope that the caller knows what it is doing,
		 * and we use a simple stack effect for just the count
		 * value and picked value.
		 */
		AddNative("roll", false, new SType(1, 0), cpu => {
			cpu.Rot(cpu.Pop());
		});
		AddNative("pick", false, new SType(1, 1), cpu => {
			cpu.Push(cpu.Peek(cpu.Pop()));
		});

		AddNative("+", false, new SType(2, 1), cpu => {
			TValue b = cpu.Pop();
			TValue a = cpu.Pop();
			if (b.ptr == null) {
				a.x += (int)b;
				cpu.Push(a);
			} else if (a.ptr is TPointerBlob
				&& b.ptr is TPointerBlob)
			{
				cpu.Push(StringToBlob(
					a.ToString() + b.ToString()));
			} else {
				throw new Exception(string.Format(
					"Cannot add '{0}' to '{1}'", b, a));
			}
		});
		AddNative("-", false, new SType(2, 1), cpu => {
			/*
			 * We can subtract two pointers, provided that
			 * they point to the same blob. Otherwise,
			 * the subtraction second operand must be an
			 * integer.
			 */
			TValue b = cpu.Pop();
			TValue a = cpu.Pop();
			TPointerBlob ap = a.ptr as TPointerBlob;
			TPointerBlob bp = b.ptr as TPointerBlob;
			if (ap != null && bp != null && ap.Blob == bp.Blob) {
				cpu.Push(new TValue(a.x - b.x));
				return;
			}
			int bx = b;
			a.x -= bx;
			cpu.Push(a);
		});
		AddNative("neg", false, new SType(1, 1), cpu => {
			int ax = cpu.Pop();
			cpu.Push(-ax);
		});
		AddNative("*", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax * bx);
		});
		AddNative("/", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax / bx);
		});
		AddNative("u/", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax / bx);
		});
		AddNative("%", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax % bx);
		});
		AddNative("u%", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax % bx);
		});
		AddNative("<", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax < bx);
		});
		AddNative("<=", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax <= bx);
		});
		AddNative(">", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax > bx);
		});
		AddNative(">=", false, new SType(2, 1), cpu => {
			int bx = cpu.Pop();
			int ax = cpu.Pop();
			cpu.Push(ax >= bx);
		});
		AddNative("=", false, new SType(2, 1), cpu => {
			TValue b = cpu.Pop();
			TValue a = cpu.Pop();
			cpu.Push(a.Equals(b));
		});
		AddNative("<>", false, new SType(2, 1), cpu => {
			TValue b = cpu.Pop();
			TValue a = cpu.Pop();
			cpu.Push(!a.Equals(b));
		});
		AddNative("u<", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop().UInt;
			uint ax = cpu.Pop().UInt;
			cpu.Push(new TValue(ax < bx));
		});
		AddNative("u<=", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop().UInt;
			uint ax = cpu.Pop().UInt;
			cpu.Push(new TValue(ax <= bx));
		});
		AddNative("u>", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop().UInt;
			uint ax = cpu.Pop().UInt;
			cpu.Push(new TValue(ax > bx));
		});
		AddNative("u>=", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax >= bx);
		});
		AddNative("and", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax & bx);
		});
		AddNative("or", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax | bx);
		});
		AddNative("xor", false, new SType(2, 1), cpu => {
			uint bx = cpu.Pop();
			uint ax = cpu.Pop();
			cpu.Push(ax ^ bx);
		});
		AddNative("not", false, new SType(1, 1), cpu => {
			uint ax = cpu.Pop();
			cpu.Push(~ax);
		});
		AddNative("<<", false, new SType(2, 1), cpu => {
			int count = cpu.Pop();
			if (count < 0 || count > 31) {
				throw new Exception("Invalid shift count");
			}
			uint ax = cpu.Pop();
			cpu.Push(ax << count);
		});
		AddNative(">>", false, new SType(2, 1), cpu => {
			int count = cpu.Pop();
			if (count < 0 || count > 31) {
				throw new Exception("Invalid shift count");
			}
			int ax = cpu.Pop();
			cpu.Push(ax >> count);
		});
		AddNative("u>>", false, new SType(2, 1), cpu => {
			int count = cpu.Pop();
			if (count < 0 || count > 31) {
				throw new Exception("Invalid shift count");
			}
			uint ax = cpu.Pop();
			cpu.Push(ax >> count);
		});

		AddNative(".", false, new SType(1, 0), cpu => {
			Console.Write(" {0}", cpu.Pop().ToString());
		});
		AddNative(".s", false, SType.BLANK, cpu => {
			int n = cpu.Depth;
			for (int i = n - 1; i >= 0; i --) {
				Console.Write(" {0}", cpu.Peek(i).ToString());
			}
		});
		AddNative("putc", false, new SType(1, 0), cpu => {
			Console.Write((char)cpu.Pop());
		});
		AddNative("puts", false, new SType(1, 0), cpu => {
			Console.Write("{0}", cpu.Pop().ToString());
		});
		AddNative("cr", false, SType.BLANK, cpu => {
			Console.WriteLine();
		});
		AddNative("eqstr", false, new SType(2, 1), cpu => {
			string s2 = cpu.Pop().ToString();
			string s1 = cpu.Pop().ToString();
			cpu.Push(s1 == s2);
		});
	}

	WordNative AddNative(string name, bool immediate,
		WordNative.NativeRun code)
	{
		return AddNative(name, immediate, SType.UNKNOWN, code);
	}

	WordNative AddNative(string name, bool immediate, SType stackEffect,
		WordNative.NativeRun code)
	{
		if (words.ContainsKey(name)) {
			throw new Exception(
				"Word already defined: " + name);
		}
		WordNative w = new WordNative(this, name, code);
		w.Immediate = immediate;
		w.StackEffect = stackEffect;
		words[name] = w;
		return w;
	}

	internal long NextBlobID()
	{
		return currentBlobID ++;
	}

	int NextChar()
	{
		int c = delayedChar;
		if (c >= 0) {
			delayedChar = Int32.MinValue;
		} else if (c > Int32.MinValue) {
			delayedChar = -(c + 1);
			c = '\n';
		} else {
			c = currentInput.Read();
		}
		if (c == '\r') {
			if (delayedChar >= 0) {
				c = delayedChar;
				delayedChar = Int32.MinValue;
			} else {
				c = currentInput.Read();
			}
			if (c != '\n') {
				delayedChar = c;
				c = '\n';
			}
		}
		return c;
	}

	/*
	 * Un-read the character value 'c'. That value MUST be the one
	 * that was obtained from NextChar().
	 */
	void Unread(int c)
	{
		if (c < 0) {
			return;
		}
		if (delayedChar < 0) {
			if (delayedChar != Int32.MinValue) {
				throw new Exception(
					"Already two delayed characters");
			}
			delayedChar = c;
		} else if (c != '\n') {
			throw new Exception("Cannot delay two characters");
		} else {
			delayedChar = -(delayedChar + 1);
		}
	}

	string Next()
	{
		string r = delayedToken;
		if (r != null) {
			delayedToken = null;
			return r;
		}
		tokenBuilder.Length = 0;
		int c;
		for (;;) {
			c = NextChar();
			if (c < 0) {
				return null;
			}
			if (!IsWS(c)) {
				break;
			}
		}
		if (c == '"') {
			return ParseString();
		}
		for (;;) {
			tokenBuilder.Append((char)c);
			c = NextChar();
			if (c < 0 || IsWS(c)) {
				Unread(c);
				return tokenBuilder.ToString();
			}
		}
	}

	string ParseCCode()
	{
		SType stackEffect;
		string r = ParseCCode(out stackEffect);
		if (stackEffect.IsKnown) {
			throw new Exception(
				"Stack effect forbidden in this declaration");
		}
		return r;
	}

	string ParseCCode(out SType stackEffect)
	{
		string s = ParseCCodeNF(out stackEffect);
		if (s == null) {
			throw new Exception("Error while parsing C code");
		}
		return s;
	}

	string ParseCCodeNF(out SType stackEffect)
	{
		stackEffect = SType.UNKNOWN;
		for (;;) {
			int c = NextChar();
			if (c < 0) {
				return null;
			}
			if (!IsWS(c)) {
				if (c == '(') {
					if (stackEffect.IsKnown) {
						Unread(c);
						return null;
					}
					stackEffect = ParseStackEffectNF();
					if (!stackEffect.IsKnown) {
						return null;
					}
					continue;
				} else if (c != '{') {
					Unread(c);
					return null;
				}
				break;
			}
		}
		StringBuilder sb = new StringBuilder();
		int count = 1;
		for (;;) {
			int c = NextChar();
			if (c < 0) {
				return null;
			}
			switch (c) {
			case '{':
				count ++;
				break;
			case '}':
				if (-- count == 0) {
					return sb.ToString();
				}
				break;
			}
			sb.Append((char)c);
		}
	}

	/*
	 * Parse a stack effect declaration. This method assumes that the
	 * opening parenthesis has just been read. If the parsing fails,
	 * then this method returns SType.UNKNOWN.
	 */
	SType ParseStackEffectNF()
	{
		bool seenSep = false;
		bool seenBang = false;
		int din = 0, dout = 0;
		for (;;) {
			string t = Next();
			if (t == null) {
				return SType.UNKNOWN;
			}
			if (t == "--") {
				if (seenSep) {
					return SType.UNKNOWN;
				}
				seenSep = true;
			} else if (t == ")") {
				if (seenSep) {
					if (seenBang && dout == 1) {
						dout = -1;
					}
					return new SType(din, dout);
				} else {
					return SType.UNKNOWN;
				}
			} else {
				if (seenSep) {
					if (dout == 0 && t == "!") {
						seenBang = true;
					}
					dout ++;
				} else {
					din ++;
				}
			}
		}
	}

	string ParseString()
	{
		StringBuilder sb = new StringBuilder();
		sb.Append('"');
		bool lcwb = false;
		int hexNum = 0;
		int acc = 0;
		for (;;) {
			int c = NextChar();
			if (c < 0) {
				throw new Exception(
					"Unfinished literal string");
			}
			if (hexNum > 0) {
				int d = HexVal(c);
				if (d < 0) {
					throw new Exception(String.Format(
						"not an hex digit: U+{0:X4}",
						c));
				}
				acc = (acc << 4) + d;
				if (-- hexNum == 0) {
					sb.Append((char)acc);
					acc = 0;
				}
			} else if (lcwb) {
				lcwb = false;
				switch (c) {
				case '\n': SkipNL(); break;
				case 'x':
					hexNum = 2;
					break;
				case 'u':
					hexNum = 4;
					break;
				default:
					sb.Append(SingleCharEscape(c));
					break;
				}
			} else {
				switch (c) {
				case '"':
					return sb.ToString();
				case '\\':
					lcwb = true;
					break;
				default:
					sb.Append((char)c);
					break;
				}
			}
		}
	}

	static char SingleCharEscape(int c)
	{
		switch (c) {
		case 'n': return '\n';
		case 'r': return '\r';
		case 't': return '\t';
		case 's': return ' ';
		default:
			return (char)c;
		}
	}

	/*
	 * A backslash+newline sequence occurred in a literal string; we
	 * check and consume the newline escape sequence (whitespace at
	 * start of next line, then a double-quote character).
	 */
	void SkipNL()
	{
		for (;;) {
			int c = NextChar();
			if (c < 0) {
				throw new Exception("EOF in literal string");
			}
			if (c == '\n') {
				throw new Exception(
					"Unescaped newline in literal string");
			}
			if (IsWS(c)) {
				continue;
			}
			if (c == '"') {
				return;
			}
			throw new Exception(
				"Invalid newline escape in literal string");
		}
	}

	static char DecodeCharConst(string t)
	{
		if (t.Length == 1 && t[0] != '\\') {
			return t[0];
		}
		if (t.Length >= 2 && t[0] == '\\') {
			switch (t[1]) {
			case 'x':
				if (t.Length == 4) {
					int x = DecHex(t.Substring(2));
					if (x >= 0) {
						return (char)x;
					}
				}
				break;
			case 'u':
				if (t.Length == 6) {
					int x = DecHex(t.Substring(2));
					if (x >= 0) {
						return (char)x;
					}
				}
				break;
			default:
				if (t.Length == 2) {
					return SingleCharEscape(t[1]);
				}
				break;
			}
		}
		throw new Exception("Invalid literal char: `" + t);
	}

	static int DecHex(string s)
	{
		int acc = 0;
		foreach (char c in s) {
			int d = HexVal(c);
			if (d < 0) {
				return -1;
			}
			acc = (acc << 4) + d;
		}
		return acc;
	}

	static int HexVal(int c)
	{
		if (c >= '0' && c <= '9') {
			return c - '0';
		} else if (c >= 'A' && c <= 'F') {
			return c - ('A' - 10);
		} else if (c >= 'a' && c <= 'f') {
			return c - ('a' - 10);
		} else {
			return -1;
		}
	}

	string ReadTerm(int ct)
	{
		StringBuilder sb = new StringBuilder();
		for (;;) {
			int c = NextChar();
			if (c < 0) {
				throw new Exception(String.Format(
					"EOF reached before U+{0:X4}", ct));
			}
			if (c == ct) {
				return sb.ToString();
			}
			sb.Append((char)c);
		}
	}

	static bool IsWS(int c)
	{
		return c <= 32;
	}

	void ProcessInput(TextReader tr)
	{
		this.currentInput = tr;
		delayedChar = -1;
		Word w = new WordNative(this, "toplevel",
			xcpu => { CompileStep(xcpu); });
		CPU cpu = new CPU();
		Opcode[] code = new Opcode[] {
			new OpcodeCall(w),
			new OpcodeJumpUncond(-2)
		};
		quitRunLoop = false;
		cpu.Enter(code, 0);
		for (;;) {
			if (quitRunLoop) {
				break;
			}
			Opcode op = cpu.ipBuf[cpu.ipOff ++];
			op.Run(cpu);
		}
	}

	void CompileStep(CPU cpu)
	{
		string tt = Next();
		if (tt == null) {
			if (compiling) {
				throw new Exception("EOF while compiling");
			}
			quitRunLoop = true;
			return;
		}
		TValue v;
		bool isVal = TryParseLiteral(tt, out v);
		Word w = LookupNF(tt);
		if (isVal && w != null) {
			throw new Exception(String.Format(
				"Ambiguous: both defined word and literal: {0}",
				tt));
		}
		if (compiling) {
			if (isVal) {
				wordBuilder.Literal(v);
			} else if (w != null) {
				if (w.Immediate) {
					w.Run(cpu);
				} else {
					wordBuilder.CallExt(w);
				}
			} else {
				wordBuilder.Call(tt);
			}
		} else {
			if (isVal) {
				cpu.Push(v);
			} else if (w != null) {
				w.Run(cpu);
			} else {
				throw new Exception(String.Format(
					"Unknown word: '{0}'", tt));
			}
		}
	}

	string GetCCode(string name)
	{
		string ccode;
		allCCode.TryGetValue(name, out ccode);
		return ccode;
	}

	void Generate(string outBase, string coreRun,
		params string[] entryPoints)
	{
		/*
		 * Gather all words that are part of the generated
		 * code. This is done by exploring references
		 * transitively. All such words are thus implicitly
		 * resolved.
		 */
		IDictionary<string, Word> wordSet =
			new SortedDictionary<string, Word>(
				StringComparer.Ordinal);
		Queue<Word> tx = new Queue<Word>();
		foreach (string ep in entryPoints) {
			if (wordSet.ContainsKey(ep)) {
				continue;
			}
			Word w = Lookup(ep);
			wordSet[w.Name] = w;
			tx.Enqueue(w);
		}
		while (tx.Count > 0) {
			Word w = tx.Dequeue();
			foreach (Word w2 in w.GetReferences()) {
				if (wordSet.ContainsKey(w2.Name)) {
					continue;
				}
				wordSet[w2.Name] = w2;
				tx.Enqueue(w2);
			}
		}

		/*
		 * Do flow analysis.
		 */
		if (enableFlowAnalysis) {
			foreach (string ep in entryPoints) {
				Word w = wordSet[ep];
				w.AnalyseFlow();
				Console.WriteLine("{0}: ds={1} rs={2}",
					ep, w.MaxDataStack, w.MaxReturnStack);
				if (w.MaxDataStack > dsLimit) {
					throw new Exception("'" + ep
						+ "' exceeds data stack limit");
				}
				if (w.MaxReturnStack > rsLimit) {
					throw new Exception("'" + ep
						+ "' exceeds return stack"
						+ " limit");
				}
			}
		}

		/*
		 * Gather referenced data areas and compute their
		 * addresses in the generated data block. The address
		 * 0 in the data block is unaffected so that no
		 * valid runtime pointer is equal to null.
		 */
		IDictionary<long, ConstData> blocks =
			new SortedDictionary<long, ConstData>();
		foreach (Word w in wordSet.Values) {
			foreach (ConstData cd in w.GetDataBlocks()) {
				blocks[cd.ID] = cd;
			}
		}
		int dataLen = 1;
		foreach (ConstData cd in blocks.Values) {
			cd.Address = dataLen;
			dataLen += cd.Length;
		}

		/*
		 * Generated code is a sequence of "slot numbers", each
		 * referencing either a piece of explicit C code, or an
		 * entry in the table of interpreted words.
		 *
		 * Opcodes other than "call" get the slots 0 to 6:
		 *
		 *   0   ret           no argument
		 *   1   const         signed value
		 *   2   get local     local number
		 *   3   put local     local number
		 *   4   jump          signed offset
		 *   5   jump if       signed offset
		 *   6   jump if not   signed offset
		 *
		 * The argument, if any, is in "7E" format: the value is
		 * encoded in 7-bit chunk, with big-endian signed
		 * convention. Each 7-bit chunk is encoded over one byte;
		 * the upper bit is 1 for all chunks except the last one.
		 *
		 * Words with explicit C code get the slot numbers
		 * immediately after 6. Interpreted words come afterwards.
		 */
		IDictionary<string, int> slots = new Dictionary<string, int>();
		int curSlot = 7;

		/*
		 * Get explicit C code for words which have such code.
		 * We use string equality on C code so that words with
		 * identical implementations get merged.
		 *
		 * We also check that words with no explicit C code are
		 * interpreted.
		 */
		IDictionary<string, int> ccodeUni =
			new Dictionary<string, int>();
		IDictionary<int, string> ccodeNames =
			new Dictionary<int, string>();
		foreach (Word w in wordSet.Values) {
			string ccode = GetCCode(w.Name);
			if (ccode == null) {
				if (w is WordNative) {
					throw new Exception(String.Format(
						"No C code for native '{0}'",
						w.Name));
				}
				continue;
			}
			int sn;
			if (ccodeUni.ContainsKey(ccode)) {
				sn = ccodeUni[ccode];
				ccodeNames[sn] += " " + EscapeCComment(w.Name);
			} else {
				sn = curSlot ++;
				ccodeUni[ccode] = sn;
				ccodeNames[sn] = EscapeCComment(w.Name);
			}
			slots[w.Name] = sn;
			w.Slot = sn;
		}

		/*
		 * Assign slot values to all remaining words; we know they
		 * are all interpreted.
		 */
		int slotInterpreted = curSlot;
		foreach (Word w in wordSet.Values) {
			if (GetCCode(w.Name) != null) {
				continue;
			}
			int sn = curSlot ++;
			slots[w.Name] = sn;
			w.Slot = sn;
		}
		int numInterpreted = curSlot - slotInterpreted;

		/*
		 * Verify that all entry points are interpreted words.
		 */
		foreach (string ep in entryPoints) {
			if (GetCCode(ep) != null) {
				throw new Exception(
					"Non-interpreted entry point");
			}
		}

		/*
		 * Compute the code block. Each word (without any C code)
		 * yields some CodeElement instances.
		 */
		List<CodeElement> gcodeList = new List<CodeElement>();
		CodeElement[] interpretedEntry =
			new CodeElement[numInterpreted];
		foreach (Word w in wordSet.Values) {
			if (GetCCode(w.Name) != null) {
				continue;
			}
			int n = gcodeList.Count;
			w.GenerateCodeElements(gcodeList);
			interpretedEntry[w.Slot - slotInterpreted] =
				gcodeList[n];
		}
		CodeElement[] gcode = gcodeList.ToArray();

		/*
		 * If there are less than 256 words in total (C +
		 * interpreted) then we can use "one-byte code" which is
		 * more compact when the number of words is in the
		 * 128..255 range.
		 */
		bool oneByteCode;
		if (slotInterpreted + numInterpreted >= 256) {
			Console.WriteLine("WARNING: more than 255 words");
			oneByteCode = false;
		} else {
			oneByteCode = true;
		}

		/*
		 * Compute all addresses and offsets. This loops until
		 * the addresses stabilize.
		 */
		int totalLen = -1;
		int[] gcodeLen = new int[gcode.Length];
		for (;;) {
			for (int i = 0; i < gcode.Length; i ++) {
				gcodeLen[i] = gcode[i].GetLength(oneByteCode);
			}
			int off = 0;
			for (int i = 0; i < gcode.Length; i ++) {
				gcode[i].Address = off;
				gcode[i].LastLength = gcodeLen[i];
				off += gcodeLen[i];
			}
			if (off == totalLen) {
				break;
			}
			totalLen = off;
		}

		/*
		 * Produce output file.
		 */
		using (TextWriter tw = File.CreateText(outBase + ".c")) {
			tw.NewLine = "\n";

			tw.WriteLine("{0}",
@"/* Automatically generated code; do not modify directly. */

#include <stddef.h>
#include <stdint.h>
#include <pgmspace.h>

typedef struct {
	uint32_t *dp;
	uint32_t *rp;
	const unsigned char *ip;
} t0_context;

static uint32_t
t0_parse7E_unsigned(const unsigned char **p)
{
	uint32_t x;

	x = 0;
	for (;;) {
		unsigned y;

		y = pgm_read_byte((*p)++);
		x = (x << 7) | (uint32_t)(y & 0x7F);
		if (y < 0x80) {
			return x;
		}
	}
}

static int32_t
t0_parse7E_signed(const unsigned char **p)
{
	int neg;
	uint32_t x;

	neg = (pgm_read_byte(*p) >> 6) & 1;
	x = (uint32_t)-neg;
	for (;;) {
		unsigned y;

		y = pgm_read_byte((*p)++);
		x = (x << 7) | (uint32_t)(y & 0x7F);
		if (y < 0x80) {
			if (neg) {
				return -(int32_t)~x - 1;
			} else {
				return (int32_t)x;
			}
		}
	}
}

#define T0_VBYTE(x, n)   (unsigned char)((((uint32_t)(x) >> (n)) & 0x7F) | 0x80)
#define T0_FBYTE(x, n)   (unsigned char)(((uint32_t)(x) >> (n)) & 0x7F)
#define T0_SBYTE(x)      (unsigned char)((((uint32_t)(x) >> 28) + 0xF8) ^ 0xF8)
#define T0_INT1(x)       T0_FBYTE(x, 0)
#define T0_INT2(x)       T0_VBYTE(x, 7), T0_FBYTE(x, 0)
#define T0_INT3(x)       T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)
#define T0_INT4(x)       T0_VBYTE(x, 21), T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)
#define T0_INT5(x)       T0_SBYTE(x), T0_VBYTE(x, 21), T0_VBYTE(x, 14), T0_VBYTE(x, 7), T0_FBYTE(x, 0)

/* static const unsigned char t0_datablock[]; */
");

			/*
			 * Add declarations (not definitions) for the
			 * entry point initialisation functions, and the
			 * runner.
			 */
			tw.WriteLine();
			foreach (string ep in entryPoints) {
				tw.WriteLine("void {0}_init_{1}(void *t0ctx);",
					coreRun, ep);
			}
			tw.WriteLine();
			tw.WriteLine("void {0}_run(void *t0ctx);", coreRun);

			/*
			 * Add preamble elements here. They may be needed
			 * for evaluating constant expressions in the
			 * code block.
			 */
			foreach (string pp in extraCode) {
				tw.WriteLine();
				tw.WriteLine("{0}", pp);
			}

			BlobWriter bw;
			tw.WriteLine();
			tw.Write("static const unsigned char"
				+ " t0_datablock[] PROGMEM = {\n");
			bw = new BlobWriter(tw, 78, 1);
			bw.Append((byte)0);
			foreach (ConstData cd in blocks.Values) {
				cd.Encode(bw);
			}
			tw.WriteLine();
			tw.WriteLine("};");

			tw.WriteLine();
			tw.Write("static const unsigned char"
				+ " t0_codeblock[] PROGMEM = {\n");
			bw = new BlobWriter(tw, 78, 1);
			foreach (CodeElement ce in gcode) {
				ce.Encode(bw, oneByteCode);
			}
			tw.WriteLine();
			tw.WriteLine("};");

			tw.WriteLine();
			tw.Write("static const uint16_t t0_caddr[] PROGMEM = {\n");
			for (int i = 0; i < interpretedEntry.Length; i ++) {
				if (i != 0) {
					tw.Write(',');
				}
				tw.WriteLine();
				tw.Write("\t{0}", interpretedEntry[i].Address);
			}
			tw.WriteLine();
			tw.WriteLine("};");

			tw.WriteLine();
			tw.WriteLine("#define T0_INTERPRETED   {0}",
				slotInterpreted);
			tw.WriteLine();
			tw.WriteLine("{0}",
@"#define T0_ENTER(ip, rp, slot)   do { \
		const unsigned char *t0_newip; \
		uint32_t t0_lnum; \
		t0_newip = &t0_codeblock[pgm_read_word(&t0_caddr[(slot) - T0_INTERPRETED])]; \
		t0_lnum = t0_parse7E_unsigned(&t0_newip); \
		(rp) += t0_lnum; \
		*((rp) ++) = (uint32_t)((ip) - &t0_codeblock[0]) + (t0_lnum << 16); \
		(ip) = t0_newip; \
	} while (0)");
			tw.WriteLine();
			tw.WriteLine("{0}",
@"#define T0_DEFENTRY(name, slot) \
void \
name(void *ctx) \
{ \
	t0_context *t0ctx = ctx; \
	t0ctx->ip = &t0_codeblock[0]; \
	T0_ENTER(t0ctx->ip, t0ctx->rp, slot); \
}");

			tw.WriteLine();
			foreach (string ep in entryPoints) {
				tw.WriteLine("T0_DEFENTRY({0}, {1})",
					coreRun + "_init_" + ep,
					wordSet[ep].Slot);
			}
			tw.WriteLine();
			if (oneByteCode) {
				tw.WriteLine("{0}",
@"#define T0_NEXT(t0ipp)   (pgm_read_byte((*t0ipp)++))" );
			} else {
				tw.WriteLine("{0}",
@"#define T0_NEXT(t0ipp)   t0_parse7E_unsigned(t0ipp)");
			}
			tw.WriteLine();
			tw.WriteLine("void");
			tw.WriteLine("{0}_run(void *t0ctx)", coreRun);
			tw.WriteLine("{0}",
@"{
	uint32_t *dp, *rp;
	const unsigned char *ip;

#define T0_LOCAL(x)    (*(rp - 2 - (x)))
#define T0_POP()       (*-- dp)
#define T0_POPi()      (*(int32_t *)(-- dp))
#define T0_PEEK(x)     (*(dp - 1 - (x)))
#define T0_PEEKi(x)    (*(int32_t *)(dp - 1 - (x)))
#define T0_PUSH(v)     do { *dp = (v); dp ++; } while (0)
#define T0_PUSHi(v)    do { *(int32_t *)dp = (v); dp ++; } while (0)
#define T0_RPOP()      (*-- rp)
#define T0_RPOPi()     (*(int32_t *)(-- rp))
#define T0_RPUSH(v)    do { *rp = (v); rp ++; } while (0)
#define T0_RPUSHi(v)   do { *(int32_t *)rp = (v); rp ++; } while (0)
#define T0_ROLL(x)     do { \
	size_t t0len = (size_t)(x); \
	uint32_t t0tmp = *(dp - 1 - t0len); \
	memmove(dp - t0len - 1, dp - t0len, t0len * sizeof *dp); \
	*(dp - 1) = t0tmp; \
} while (0)
#define T0_SWAP()      do { \
	uint32_t t0tmp = *(dp - 2); \
	*(dp - 2) = *(dp - 1); \
	*(dp - 1) = t0tmp; \
} while (0)
#define T0_ROT()       do { \
	uint32_t t0tmp = *(dp - 3); \
	*(dp - 3) = *(dp - 2); \
	*(dp - 2) = *(dp - 1); \
	*(dp - 1) = t0tmp; \
} while (0)
#define T0_NROT()       do { \
	uint32_t t0tmp = *(dp - 1); \
	*(dp - 1) = *(dp - 2); \
	*(dp - 2) = *(dp - 3); \
	*(dp - 3) = t0tmp; \
} while (0)
#define T0_PICK(x)      do { \
	uint32_t t0depth = (x); \
	T0_PUSH(T0_PEEK(t0depth)); \
} while (0)
#define T0_CO()         do { \
	goto t0_exit; \
} while (0)
#define T0_RET()        goto t0_next

	dp = ((t0_context *)t0ctx)->dp;
	rp = ((t0_context *)t0ctx)->rp;
	ip = ((t0_context *)t0ctx)->ip;
	goto t0_next;
	for (;;) {
		uint32_t t0x;

	t0_next:
		t0x = T0_NEXT(&ip);
		if (t0x < T0_INTERPRETED) {
			switch (t0x) {
				int32_t t0off;

			case 0: /* ret */
				t0x = T0_RPOP();
				rp -= (t0x >> 16);
				t0x &= 0xFFFF;
				if (t0x == 0) {
					ip = NULL;
					goto t0_exit;
				}
				ip = &t0_codeblock[t0x];
				break;
			case 1: /* literal constant */
				T0_PUSHi(t0_parse7E_signed(&ip));
				break;
			case 2: /* read local */
				T0_PUSH(T0_LOCAL(t0_parse7E_unsigned(&ip)));
				break;
			case 3: /* write local */
				T0_LOCAL(t0_parse7E_unsigned(&ip)) = T0_POP();
				break;
			case 4: /* jump */
				t0off = t0_parse7E_signed(&ip);
				ip += t0off;
				break;
			case 5: /* jump if */
				t0off = t0_parse7E_signed(&ip);
				if (T0_POP()) {
					ip += t0off;
				}
				break;
			case 6: /* jump if not */
				t0off = t0_parse7E_signed(&ip);
				if (!T0_POP()) {
					ip += t0off;
				}
				break;");

			SortedDictionary<int, string> nccode =
				new SortedDictionary<int, string>();
			foreach (string k in ccodeUni.Keys) {
				nccode[ccodeUni[k]] = k;
			}
			foreach (int sn in nccode.Keys) {
				tw.WriteLine(
@"			case {0}: {{
				/* {1} */
{2}
				}}
				break;", sn, ccodeNames[sn], nccode[sn]);
			}

			tw.WriteLine(
@"			}

		} else {
			T0_ENTER(ip, rp, t0x);
		}
	}
t0_exit:
	((t0_context *)t0ctx)->dp = dp;
	((t0_context *)t0ctx)->rp = rp;
	((t0_context *)t0ctx)->ip = ip;
}");

			/*
			 * Add the "postamblr" elements here. These are
			 * elements that may need access to the data
			 * block or code block, so they must occur after
			 * their definition.
			 */
			foreach (string pp in extraCodeDefer) {
				tw.WriteLine();
				tw.WriteLine("{0}", pp);
			}
		}

		int codeLen = 0;
		foreach (CodeElement ce in gcode) {
			codeLen += ce.GetLength(oneByteCode);
		}
		int dataBlockLen = 0;
		foreach (ConstData cd in blocks.Values) {
			dataBlockLen += cd.Length;
		}

		/*
		 * Write some statistics on produced code.
		 */
		Console.WriteLine("code length: {0,6} byte(s)", codeLen);
		Console.WriteLine("data length: {0,6} byte(s)", dataLen);
		Console.WriteLine("total words: {0} (interpreted: {1})",
			slotInterpreted + numInterpreted, numInterpreted);
	}

	internal Word Lookup(string name)
	{
		Word w = LookupNF(name);
		if (w != null) {
			return w;
		}
		throw new Exception(String.Format("No such word: '{0}'", name));
	}

	internal Word LookupNF(string name)
	{
		Word w;
		words.TryGetValue(name, out w);
		return w;
	}

	internal TValue StringToBlob(string s)
	{
		return new TValue(0, new TPointerBlob(this, s));
	}

	internal bool TryParseLiteral(string tt, out TValue tv)
	{
		tv = new TValue(0);
		if (tt.StartsWith("\"")) {
			tv = StringToBlob(tt.Substring(1));
			return true;
		}
		if (tt.StartsWith("`")) {
			tv = DecodeCharConst(tt.Substring(1));
			return true;
		}
		bool neg = false;
		if (tt.StartsWith("-")) {
			neg = true;
			tt = tt.Substring(1);
		} else if (tt.StartsWith("+")) {
			tt = tt.Substring(1);
		}
		uint radix = 10;
		if (tt.StartsWith("0x") || tt.StartsWith("0X")) {
			radix = 16;
			tt = tt.Substring(2);
		} else if (tt.StartsWith("0b") || tt.StartsWith("0B")) {
			radix = 2;
			tt = tt.Substring(2);
		}
		if (tt.Length == 0) {
			return false;
		}
		uint acc = 0;
		bool overflow = false;
		uint maxV = uint.MaxValue / radix;
		foreach (char c in tt) {
			int d = HexVal(c);
			if (d < 0 || d >= radix) {
				return false;
			}
			if (acc > maxV) {
				overflow = true;
			}
			acc *= radix;
			if ((uint)d > uint.MaxValue - acc) {
				overflow = true;
			}
			acc += (uint)d;
		}
		int x = (int)acc;
		if (neg) {
			if (acc > (uint)0x80000000) {
				overflow = true;
			}
			x = -x;
		}
		if (overflow) {
			throw new Exception(
				"invalid literal integer (overflow)");
		}
		tv = x;
		return true;
	}

	int ParseInteger(string tt)
	{
		TValue tv;
		if (!TryParseLiteral(tt, out tv)) {
			throw new Exception("not an integer: " + ToString());
		}
		return (int)tv;
	}

	void CheckCompiling()
	{
		if (!compiling) {
			throw new Exception("Not in compilation mode");
		}
	}

	static string EscapeCComment(string s)
	{
		StringBuilder sb = new StringBuilder();
		foreach (char c in s) {
			if (c >= 33 && c <= 126 && c != '%') {
				sb.Append(c);
			} else if (c < 0x100) {
				sb.AppendFormat("%{0:X2}", (int)c);
			} else if (c < 0x800) {
				sb.AppendFormat("%{0:X2}%{0:X2}",
					((int)c >> 6) | 0xC0,
					((int)c & 0x3F) | 0x80);
			} else {
				sb.AppendFormat("%{0:X2}%{0:X2}%{0:X2}",
					((int)c >> 12) | 0xE0,
					(((int)c >> 6) & 0x3F) | 0x80,
					((int)c & 0x3F) | 0x80);
			}
		}
		return sb.ToString().Replace("*/", "%2A/");
	}
}
