aboutsummaryrefslogtreecommitdiff
path: root/contrib/bearssl/T0/T0Comp.cs
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bearssl/T0/T0Comp.cs')
-rw-r--r--contrib/bearssl/T0/T0Comp.cs2123
1 files changed, 2123 insertions, 0 deletions
diff --git a/contrib/bearssl/T0/T0Comp.cs b/contrib/bearssl/T0/T0Comp.cs
new file mode 100644
index 000000000000..7a397f7cbc48
--- /dev/null
+++ b/contrib/bearssl/T0/T0Comp.cs
@@ -0,0 +1,2123 @@
+/*
+ * 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>
+
+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 = *(*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 = ((**p) >> 6) & 1;
+ x = (uint32_t)-neg;
+ for (;;) {
+ unsigned y;
+
+ y = *(*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[] = {");
+ 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[] = {");
+ 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[] = {");
+ 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[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) (*(*(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/");
+ }
+}