mirror of
https://github.com/libretro/RetroArch.git
synced 2024-12-01 04:00:32 +00:00
2124 lines
48 KiB
C#
2124 lines
48 KiB
C#
/*
|
|
* 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/");
|
|
}
|
|
}
|