Last active
April 7, 2024 22:00
-
-
Save tluyben/16ee2645c4c8aed813005d51488d5c6a to your computer and use it in GitHub Desktop.
A minimal Forth implementation in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Minimal .NET Forth implementation. Just an experiment. Do not use for anything serious. | |
* by Tycho Luyben (https://github.com/tluyben) | |
* | |
* The only 'primitive' (built-in) is an foreign function interface word which allows you to define | |
* whatever is needed, for example: | |
* | |
* hello System.String System.Console.WriteLine ffi | |
* | |
* will print hello. | |
* | |
* ffi references must include the complete package name and function, aka | |
* | |
* hello string Console.WriteLine ffi | |
* | |
* will not work. | |
* | |
* Words can be excaped to prevent execution: | |
* | |
* 5 4 `+ | |
* | |
* Stack: 5 4 + | |
* | |
* Note the + on the stack is not executed. | |
* | |
* To execute it ; run the exec word. | |
* | |
*/ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
namespace TinyForths | |
{ | |
// This is the runtime, this is all that's needed for the language | |
public class Forth1 | |
{ | |
// base ENVironment | |
public static Stack<string> St = new Stack<string>(); | |
public static Dictionary<string, string> Ws = new Dictionary<string, string>(); | |
// foreign function interface, only supports static methods for ease | |
public static void FFI() | |
{ | |
var tf = St.Pop(); // total package.class.function name | |
var i = tf.LastIndexOf('.'); | |
if (i < 0) throw new EntryPointNotFoundException($"FFI with {tf} has no package"); | |
// find the Type | |
var cls = tf.Substring(0, i); | |
Type t = AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetType(cls) != null).GetType(cls); | |
if (t == null) throw new EntryPointNotFoundException($"Cannot resolve type {cls}"); | |
// find the method | |
var ts = St.Pop().Split(new char[] { ',' }).Where(tt=>tt!="void").Select(tt => Type.GetType(tt)).ToArray(); // parameter types | |
var f = t.GetMethod(tf.Substring(i + 1), ts); // get the static method | |
var o = ts.Select(p => (object)(p == typeof(string) ? St.Pop() : Convert.ChangeType(St.Pop(), p))).ToArray(); // set the input params | |
// run the (static) method | |
f.Invoke(null, o); | |
} | |
// base execution | |
public static void EC(string s) | |
{ | |
//Console.WriteLine($"executing {s} with stack {string.Join(" ",St.ToArray())} "); | |
s.Replace("\n", " ") | |
.Split(null).Select(s1 => s1.Trim()).Where(s1 => s1 != "") // tokenizer | |
.ToList().ForEach(s1 => | |
{ | |
// the only built-in word | |
if (s1[0] != '`' && s1 == "ffi") FFI(); | |
else // this is a defined word | |
if (s1[0] != '`' && Ws.ContainsKey(s1)) EC(Ws[s1]); | |
else // the rest we add on the stack | |
{ | |
if (s1[0] == '`') s1 = s1.Substring(1); | |
St.Push(s1); | |
} | |
}); // execution | |
} | |
} | |
// Create some internals to make the language usable | |
public static class ForthBasicInternals | |
{ | |
static string PopOff(int arity) | |
{ | |
var ar = new string[arity]; | |
while (arity-- > 0) ar[arity] = Forth1.St.Pop(); | |
return String.Join(" ", ar); | |
} | |
// define words | |
public static void Define(string name, int arity) | |
{ | |
Forth1.Ws[name] = PopOff(arity); | |
} | |
public static void Add(int a, int b) | |
{ | |
Forth1.St.Push((a + b).ToString()); | |
} | |
public static void Mult(int a, int b) | |
{ | |
Forth1.St.Push((a * b).ToString()); | |
} | |
public static void Dup(string w) | |
{ | |
Forth1.St.Push(w); | |
Forth1.St.Push(w); | |
} | |
public static void Drop(string w) | |
{ | |
} | |
public static void Swap(string w1, string w2) | |
{ | |
Forth1.St.Push(w1); | |
Forth1.St.Push(w2); | |
} | |
public static void Print(string w) | |
{ | |
if (Forth1.Ws.ContainsKey(w)) | |
{ | |
Console.WriteLine(Forth1.Ws[w]); | |
} | |
else | |
{ | |
Console.WriteLine(w); | |
} | |
} | |
public static void PrintStack() | |
{ | |
Console.WriteLine(string.Join(" ", Forth1.St.Reverse().ToArray())); | |
} | |
public static void Exec(string w) | |
{ | |
Forth1.EC(w); | |
} | |
public static void If(int condArity, int thenArity, int elseArity) | |
{ | |
var _cond = PopOff(condArity); | |
var _then = PopOff(thenArity); | |
var _else = PopOff(elseArity); | |
Forth1.EC(_cond); | |
if (Forth1.St.Pop() == "true") | |
Forth1.EC(_then); | |
else | |
Forth1.EC(_else); | |
} | |
public static void Eq(int a1, int a2) | |
{ | |
Forth1.St.Push(a1 == a2 ? "true" : "false"); | |
} | |
} | |
public class Forth1Console | |
{ | |
public static void Main(string[] args) | |
{ | |
// define some library calls | |
Forth1.EC("System.String,System.Int32 TinyForths.ForthBasicInternals.Define `ffi 3 def System.String,System.Int32 TinyForths.ForthBasicInternals.Define ffi"); | |
Forth1.EC("System.String System.Console.WriteLine `ffi 3 . def"); | |
Forth1.EC("void TinyForths.ForthBasicInternals.PrintStack `ffi 3 .s def"); | |
Forth1.EC("System.String TinyForths.ForthBasicInternals.Exec `ffi 3 exec def"); | |
Forth1.EC("System.String TinyForths.ForthBasicInternals.Dup `ffi 3 dup def"); | |
Forth1.EC("System.String TinyForths.ForthBasicInternals.Drop `ffi 3 drop def"); | |
Forth1.EC("System.Int32,System.Int32 TinyForths.ForthBasicInternals.Eq `ffi 3 = def"); | |
Forth1.EC("System.String,System.String TinyForths.ForthBasicInternals.Swap `ffi 3 swap def"); | |
Forth1.EC("System.Int32,System.Int32 TinyForths.ForthBasicInternals.Add `ffi 3 + def"); | |
Forth1.EC("System.Int32,System.Int32 TinyForths.ForthBasicInternals.Mult `ffi 3 * def"); | |
Forth1.EC("System.Int32,System.Int32,System.Int32 TinyForths.ForthBasicInternals.If `ffi 3 if def"); | |
// redefine print to allow to print words | |
Forth1.EC("System.String TinyForths.ForthBasicInternals.Print `ffi 3 `. def"); | |
// have some fun | |
Forth1.EC("hello . world ."); | |
Forth1.EC("2 3 + . 9 11 + . 6 66 * . x y z .s 7 3 * .s dup .s drop swap .s"); | |
Forth1.EC("swap 3 `* .s exec .s"); | |
Forth1.EC("hello world 2 helloworld def `helloworld ."); | |
Forth1.EC("5 5 = 5 6 = .s"); | |
Forth1.EC("5 cheese meat 5 `= 1 1 2 if .s"); | |
Forth1.EC("5 cheese `. meat `. 6 `= 2 2 2 if"); | |
// repl | |
while (true) | |
{ | |
var s = Console.ReadLine().Trim(); | |
if (s == "") continue; | |
if (s == "\\e") break; | |
Forth1.EC(s); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment