Przykład prostego interpretera własnego języka – podstawy
Artykuł opisujący tworzenie interpretera w C#. Może znaleźć zastosowanie w programach, które pozwalają użytkownikowi na częściowe zaprogramowanie operacji np. obliczeniowych w prostym quasi-języku programowania.
***
Wstęp
Artykuł jest odpowiedzią na pojawiające się czasem pytanie na forum "Jak napisać własny język programowania w C#?". Czy język C# jest do tego właściwym wyborem? Należałoby się zapytać o zastosowanie takiego rozwiązania.
Można spotkać programy, w których użytkownikowi umożliwia się opisanie pewnego zbioru operacji np. obliczeń za pomocą programów w jakimś stworzonym do tego celu prostym języku, który nie należy do grupy języków używanych w inżynierii oprogramowania.
Artykuł nawiązuje do takiego zastosowania.
Nie można zapomnieć, że musi być możliwość dwukierunkowego przekazywania danych pomiędzy interpreterem a językiem C#. Podstawowe elementy takiej integracji zostały tu pokazane.
Co prawda jest pewien własny język, ale raczej przeznaczony do pisania krótkich podprogramów, czy też makr i nie jest to język, który wymagałby kompilatora, ale musi być możliwość diagnostyki błędów. W poniższym kodzie rozwiązano to przez tworzenie logu operacji. Log jest przeznaczony dla programisty C# i należy zdecydować, jakie komunikaty będzie otrzymywał użytkownik.
Stos wartości i typy danych
W rozwiązaniu interpreter posiada własny stos wartości (danych). Typem danych występujących na stosie są obiekty klasy Value, która zawiera wariant typu int i typu string, plus flagę informującą o typie tej danej.
Znajomość typu danej jest konieczna, aby sprawdzać, czy wykonywana operacja jest dopuszczalna. Np. można dodawać liczby int i łączyć stringi, a nie można tego zrobić przy różnych typach argumentów (to zasada przyjęta akurat tutaj, choć istnieją języki, w których takie operacje są możliwe).
Podprogramy i stos powrotów
Własny język wyposażono w możliwość definiowania podprogramów. Wywołania podprogramów wykonuje C#, więc interpreter nie ma własnego stosu powrotów do miejsca, gdzie nastąpiło wywołanie podprogramu.
Słowniki
Interpreter posiada dwa słowniki tj. słownik funkcji predefiniowanych, odpowiednika słów kluczowych języka oraz słownik funkcji definiowanych przez użytkownika. Funkcje predefiniowane należy napisać w C#. Funkcje użytkownika trafiają do słownika po analizie kodu programu we własnym języku przez interpreter.
Elementy integracji z CSharp
Istnieje możliwość odczytania dowolnej wartości na stosie interpretera jak z tablicy (Peek), wpisania liczby lub stringa na szczyt stosu (Push) i zdjęcia wartości ze szczytu stosu (Pop).
Kod w CSharp
** Kod klasy Interpreter zawierający podstawową funkcję interpretującą Interpret **
#define SHOWDEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace MyInterpreter
{
partial class Interpreter
{
static List<string> Words; //lista słów w programie użytkownika
//słownik funkcji języka
static Dictionary<string, Func<bool>> LanguageFunctionDictionary;
//słownik funkcji zdefiniowanych w programie
static Dictionary<string, List<string>> UserFunctionDictionary;
static Stack<Value> ValueStack; //stos wartości Value - są to warości int oraz string
//z polem typu
static uint ValueStackCapacity; //pojemność stosu wartości
static char StringDelimiter = '"'; //obustronny separator wartości string
//wprawdzie słowa zaczynające i kończące definicję podprogramu
//są również słowami kluczowymi, jednak ze względu na swoją specyfikę
//nie znalazły się w słowniku LanguageFunctionDictionary
//było to prostsze rozwiązanie
static string DefinitionBeginIndicator = ":"; //słowo rozpoczynające definicję nowej funkcji
static string DefinitionEndIndicator = ";"; //słowo kończące definicję nowej funkcji
uint RecursionLevelCountAllowed; //dopuszczaln ilość
//wywołań podprogramów
public Interpreter(uint stackCapacity, uint recursionLevelCountAllowed)
{
ValueStackCapacity = stackCapacity;
RecursionLevelCountAllowed = recursionLevelCountAllowed;
ValueStack = new Stack<Value>((int)ValueStackCapacity);
LanguageFunctionDictionary = new Dictionary<string, Func<bool>>
{
{ "+", LanguageFunction.AddConcat },
{ "/", LanguageFunction.DivMod },
{ "DROP", LanguageFunction.Drop },
{ "-", LanguageFunction.Substract }
//tu można wpisać do slownika inne slowa kluczowe wlasnego języka
//należy opisać ich zachowanie w klasie LanguageFunction
};
UserFunctionDictionary = new Dictionary<string, List<string>>();
}
public bool Interpret(string s)
{
bool result = true;
#if SHOWDEBUG
Debug.WriteLine(Texts.MainScript + '\n' + s);
#endif
Value v = new Value(Value.Type.str_, 0, s);
Words = v.strVal.ToList();
if (UserFunction.GetDefinition())
{
#if SHOWDEBUG
Debug.WriteLine(Texts.ReturnStackEntry + " 0");
#endif
for (int wordIdx = 0; wordIdx < Words.Count; wordIdx++)
{
result = Interpret(wordIdx, Words, 0);
if (!result)
{
break;
}
}
}
return result;
}
bool Interpret(int wordIdx, List<string> words, int recursionLevel)
{
bool result = true;
try
{
if (wordIdx < words.Count)
{
int u;
//jeżeli w programie wystąpi wartość int, jest wstawiana na stos
if (Value.IsInt(words[wordIdx], out u))
{
if (ValueStack.Count < ValueStackCapacity)
{
ValueStack.Push(new Value(Value.Type.int_, u, null));
#if SHOWDEBUG
Utils.DisplayStack();
#endif
return result;
}
else
{
result = false;
}
}
else
{
string s;
//jeżeli w programie wystąpi wartość string, jest wstawiana na stos
if (Value.IsStr(words[wordIdx], out s))
{
if (ValueStack.Count < ValueStackCapacity)
{
ValueStack.Push(new Value(Value.Type.str_, 0, s));
#if SHOWDEBUG
Utils.DisplayStack();
#endif
return result;
}
else
{
result = false;
}
}
else
{
Func<bool> f;
//jeżeli w programie wystąpi słowo kluczowe, jest wykonywana
//funkcja ze słownika programu
if (Value.IsLanguageFunc(words[wordIdx], out f))
{
if (f != null)
{
if (f())
{
#if SHOWDEBUG
Utils.DisplayStack();
#endif
return result;
}
else
{
result = false;
}
}
else
{
result = false;
}
}
else
{
List<string> l;
//jeżeli w programie wystąpi nazwa funkcji zdefiniowanej w programie
//użytkownika, jest wykonywana funkcja ze słownika użytkownika
if (Value.IsUserFunc(words[wordIdx], out l))
{
for (int sIdx = 0; sIdx < l.Count; sIdx++)
{
#if SHOWDEBUG
Debug.WriteLine(Texts.ReturnStackEntry + ' ' + "level"
+ ' ' + (recursionLevel + 1).ToString() + ' ' + "of" + ' '
+ (RecursionLevelCountAllowed).ToString());
#endif
if (recursionLevel + 1 <= RecursionLevelCountAllowed)
{
result = Interpret(sIdx, l, recursionLevel + 1);
}
else
{
#if SHOWDEBUG
Debug.WriteLine(Texts.ReturnStackEntry + ' ' + "denied");
#endif
result = false;
}
if (!result)
{
break;
}
}
}
else
{
result = false;
}
}
}
}
}
else
{
#if SHOWDEBUG
Debug.WriteLine(Texts.EndOfScrip + '\n');
#endif
}
}
catch
{
result = false;
}
return result;
}
//interfejsy funkcji do operacji na stosie interpretera przez program c#
public bool TryPeek(int offset, out int x)
{
return (new StackAccess()).TryPeek(offset, out x);
}
public bool TryPeek(int offset, out string x)
{
return (new StackAccess()).TryPeek(offset, out x);
}
public bool TryPop(out int x)
{
return (new StackAccess()).TryPop(out x);
}
public bool TryPop(out string x)
{
return (new StackAccess()).TryPop(out x);
}
public bool TryPush(int x)
{
return (new StackAccess()).TryPush(x);
}
public bool TryPush(string x)
{
return (new StackAccess()).TryPush(x);
}
}
}
** Kod klasy Value uzupełniony o rozpoznawanie typów danych **
#define SHOWDEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace MyInterpreter
{
partial class Interpreter
{
class Value
{
public enum Type
{
int_,
str_,
unknown_
}
public Type type;
public int intVal;
public List<string> strVal;
public Value(Type t, int i, string s)
{
type = t;
intVal = i;
if (s != null)
{
s = Regex.Replace(s, @"\s+", " ");
strVal = s.Trim().Split((new List<char>{ ' ' }).ToArray()).ToList();
}
else
{
strVal = null;
}
}
//sprawdzenie, czy słowo występujące w programie
//jest wartością int
public static bool IsInt(string word, out int i)
{
bool result = true;
i = 0;
if (!int.TryParse(word, out i))
{
result = false;
}
#if SHOWDEBUG
if (result)
{
Debug.WriteLine(word.ToString() + ' ' + MethodInfo.GetCurrentMethod().Name);
}
#endif
return result;
}
//sprawdzenie, czy słowo występujące w programie
//jest wartością string
public static bool IsStr(string word, out string s)
{
bool result = true;
s = null;
if (word == "\"\"")
{
s = string.Empty;
}
else
{
if (word.Length > 2)
{
s = word.Substring(1, word.Length - 2);
result = word.First() == StringDelimiter && word.Last() == StringDelimiter && !s.Contains(StringDelimiter);
}
else
{
result = false;
}
}
#if SHOWDEBUG
if (result)
{
Debug.WriteLine(word.ToString() + ' ' + MethodInfo.GetCurrentMethod().Name);
}
#endif
return result;
}
//sprawdzenie, czy słowo występujące w programie
//jest nazwą funkcji predefiniowanej
public static bool IsLanguageFunc(string word, out Func<bool> f)
{
bool result = true;
f = null;
if (LanguageFunctionDictionary.ContainsKey(word))
{
f = LanguageFunctionDictionary[word];
}
else
{
result = false;
}
#if SHOWDEBUG
if (result)
{
Debug.WriteLine(word.ToString() + ' ' + MethodInfo.GetCurrentMethod().Name);
}
#endif
return result;
}
//sprawdzenie, czy słowo występujące w programie
//jest nazwą funkcji zdefiniowanej przez użytkownika
public static bool IsUserFunc(string word, out List<string> l)
{
bool result = true;
l = null;
if (UserFunctionDictionary.ContainsKey(word))
{
l = UserFunctionDictionary[word];
}
else
{
result = false;
}
#if SHOWDEBUG
if (result)
{
Debug.WriteLine(word.ToString() + ' ' + MethodInfo.GetCurrentMethod().Name);
}
#endif
return result;
}
}
}
}
** Kod funkcji predefiniowanych **
#define SHOWDEBUG
using System.Diagnostics;
using System.Reflection;
namespace MyInterpreter
{
partial class Interpreter
{
//klasa zawierająca definicje slów kluczowych wlasnego języka
//poszczególne funckcje wymagają odpowiednich typów parametrów
//w zależności od tego, czy parametry są odpowiednie, zwracają true lub false
class LanguageFunction
{
//funkcja dodawania liczb lub konkatenacji stringów
//funkcja zdejmuje ze stosu dwie dane i zwraca na stos jedną daną
//tj. wynik dodawania lub konkatenacji
public static bool AddConcat()
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Call + ' ' + MethodInfo.GetCurrentMethod().Name);
#endif
bool result = true;
if (ValueStack.Count >= 2)
{
Value v2 = ValueStack.ToArray()[0];
Value v1 = ValueStack.ToArray()[1];
if (v1.type == Value.Type.int_ && v2.type == Value.Type.int_)
{
ValueStack.Pop();
ValueStack.Pop();
long sum = v1.intVal + v2.intVal;
if (sum <= int.MaxValue && sum >= int.MinValue)
{
ValueStack.Push(new Value(Value.Type.int_, (int)sum, null));
}
else
{
result = false;
}
}
else if (v1.type == Value.Type.str_ && v2.type == Value.Type.str_)
{
ValueStack.Pop();
ValueStack.Pop();
v1.strVal.AddRange(v2.strVal);
ValueStack.Push(new Value(Value.Type.str_, 0, Utils.ConvertToStr(v1.strVal)));
}
else
{
result = false;
}
}
else
{
result = false;
}
return result;
}
//funkcja dzielenia calkowitego zwracająca część całkowitą i resztę
//funkcja zdejmuje ze stosu dwie dane calkowite
//i wstawia na stos dwie dane z wynikiem dzielenia całkowitego
public static bool DivMod()
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Call + ' ' + MethodInfo.GetCurrentMethod().Name);
#endif
bool result = false;
if (ValueStack.Count >= 2)
{
Value v2 = ValueStack.ToArray()[0];
Value v1 = ValueStack.ToArray()[1];
if (v1.type == Value.Type.int_ && v2.type == Value.Type.int_)
{
ValueStack.Pop();
ValueStack.Pop();
if (v2.intVal != 0)
{
ValueStack.Push(new Value(Value.Type.int_, v1.intVal / v2.intVal, null));
ValueStack.Push(new Value(Value.Type.int_, v1.intVal % v2.intVal, null));
result = true;
}
else
{
result = false;
}
}
else
{
result = false;
}
}
return result;
}
//funkcja zdejmująca wartość ze szczytu stosu bez dalszych działań
public static bool Drop()
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Call + ' ' + MethodInfo.GetCurrentMethod().Name);
#endif
bool result = true;
if (ValueStack.Count >= 1)
{
ValueStack.Pop();
}
else
{
result = false;
}
return result;
}
//funkcja odejmowania
//funkcja zdejmuje ze stosu dwa argumenty
//i wstawia na stos wynik odejmowania
public static bool Substract()
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Call + ' ' + MethodInfo.GetCurrentMethod().Name);
#endif
bool result = true;
if (ValueStack.Count >= 2)
{
Value v2 = ValueStack.ToArray()[0];
Value v1 = ValueStack.ToArray()[1];
if (v1.type == Value.Type.int_ && v2.type == Value.Type.int_)
{
ValueStack.Pop();
ValueStack.Pop();
long sub = v1.intVal - v2.intVal;
if (sub <= int.MaxValue && sub >= int.MinValue)
{
ValueStack.Push(new Value(Value.Type.int_, (int)sub, null));
}
else
{
result = false;
}
}
else
{
result = false;
}
}
return result;
}
}
}
}
** Kod umożliwiający definiowanie funkcji we własnym języku **
#define SHOWDEBUG
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace MyInterpreter
{
partial class Interpreter
{
//klasa opisująca sposób interpretacji nowych funkcji
//są one następnie wpisywane do słownika funkcji użytkownika
class UserFunction
{
public static bool GetDefinition()
{
#if SHOWDEBUG
Debug.WriteLine(Texts.InitialCallOf + ' ' + MethodInfo.GetCurrentMethod().Name);
#endif
bool result = true;
int beginCount = Words.Count(x => x == DefinitionBeginIndicator);
int endCount = Words.Count(x => x == DefinitionEndIndicator);
if (beginCount == endCount)
{
while (beginCount > 0)
{
int idxBegin = Words.IndexOf(DefinitionBeginIndicator);
int idxFuncName = idxBegin + 1;
int idxFuncBody = idxBegin + 2;
int idxEnd = Words.IndexOf(DefinitionEndIndicator);
if (idxEnd > idxFuncBody)
{
List<string> body = Words.GetRange((int)idxFuncBody, (int)idxEnd
- (int)idxFuncBody);
if (!body.Contains(DefinitionBeginIndicator))
{
string key = Words[(int)idxFuncName];
UserFunctionDictionary.Add(key, body);
#if SHOWDEBUG
Debug.WriteLine(Texts.ScriptOfNewSubroutine + ' ' + key
+ '\n' + Utils.ConvertToStr(body));
#endif
Words.RemoveRange((int)idxBegin, (int)idxEnd - (int)idxBegin + 1);
#if SHOWDEBUG
Debug.WriteLine(Texts.MainScriptChange + '\n'
+ Utils.ConvertToStr(Words));
#endif
beginCount = Words.Count(x => x == DefinitionBeginIndicator);
endCount = Words.Count(x => x == DefinitionEndIndicator);
}
else
{
result = false;
break;
}
}
else
{
result = false;
break;
}
}
}
else
{
result = false;
}
#if SHOWDEBUG
if (result)
{
Debug.WriteLine(MethodInfo.GetCurrentMethod().Name + ' '
+ Texts.EndsWithSuccess);
}
else
{
Debug.WriteLine(MethodInfo.GetCurrentMethod().Name + ' '
+ Texts.Fails);
}
#endif
return result;
}
}
}
}
** Minimalny zbiór funkcji umożliwiających wymianę danych z C# **
#define SHOWDEBUG
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
namespace MyInterpreter
{
partial class Interpreter
{
//klasa integrująca program we własnym języku z programem w c# przez stos interpretera
//
//funkcje TryPeek odczytują daną z dowolnej pozycji stosu bez zdjmowania danej ze stosu
//
//funkcje TryPush pozwalają na wstawienie danej na szczyt stosu interpreter
//
//funkcje TryPop odczytują daną ze szczytu stosu interpretera i zdejmują ją ze stosu
class StackAccess
{
public bool TryPeek(int offset, out int u)
{
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
bool result = true;
u = 0;
object o;
Value.Type t;
result = TryPeek(offset, out o, out t);
result = result && t == Value.Type.int_;
if (result)
{
result = int.TryParse(o.ToString(), out u);
#if SHOWDEBUG
Debug.WriteLine(Texts.Returns + ' ' + Value.Type.int_.ToString() + ' '
+ u.ToString());
#endif
}
else
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Fails);
#endif
}
return result;
}
public bool TryPeek(int offset, out string s)
{
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
bool result = true;
s = null;
object o;
Value.Type t;
result = TryPeek(offset, out o, out t);
result = result && t == Value.Type.str_;
if (result)
{
s = o.ToString();
#if SHOWDEBUG
Debug.WriteLine(Texts.Returns + ' ' + Value.Type.str_.ToString()
+ ' ' + '"' + s.ToString() + '"');
}
else
{
Debug.WriteLine(Texts.Fails);
#endif
}
return result;
}
bool TryPeek(int offset, out object o, out Value.Type t)
{
bool result = true;
o = null;
t = Value.Type.unknown_;
List<string> l = null;
if (offset < ValueStack.Count)
{
Value word = ValueStack.ToArray()[offset];
switch (word.type)
{
case Value.Type.int_:
o = word.intVal;
t = Value.Type.int_;
break;
case Value.Type.str_:
l = word.strVal;
o = Utils.ConvertToStr(l);
t = Value.Type.str_;
break;
}
}
else
{
result = false;
}
return result;
}
public bool TryPop(out int u)
{
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
bool result = true;
u = 0;
object o;
Value.Type t;
result = TryPop(out o, out t);
result = result && t == Value.Type.int_;
if (result)
{
result = int.TryParse(o.ToString(), out u);
#if SHOWDEBUG
Debug.WriteLine(Texts.Returns + ' '
+ Value.Type.int_.ToString() + ' ' + u.ToString());
#endif
}
else
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Fails);
#endif
}
#if SHOWDEBUG
Utils.DisplayStack();
#endif
return result;
}
public bool TryPop(out string s)
{
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
bool result = true;
s = null;
object o;
Value.Type t;
result = TryPop(out o, out t);
result = result && t == Value.Type.str_;
if (result)
{
s = o.ToString();
#if SHOWDEBUG
Debug.WriteLine(Texts.Returns + ' ' + Value.Type.str_.ToString()
+ ' ' + '"' + s.ToString() + '"');
#endif
}
else
{
#if SHOWDEBUG
Debug.WriteLine(Texts.Fails);
#endif
}
#if SHOWDEBUG
Utils.DisplayStack();
#endif
return result;
}
bool TryPop(out object o, out Value.Type t)
{
bool result = true;
o = null;
t = Value.Type.unknown_;
List<string> l = null;
if (ValueStack.Count > 0)
{
Value word = ValueStack.Pop();
switch (word.type)
{
case Value.Type.int_:
o = word.intVal;
t = Value.Type.int_;
break;
case Value.Type.str_:
l = word.strVal;
o = Utils.ConvertToStr(l);
t = Value.Type.str_;
break;
}
}
else
{
result = false;
}
return result;
}
public bool TryPush(int x)
{
bool result = false;
if (ValueStack.Count < ValueStackCapacity)
{
ValueStack.Push(new Value(Value.Type.int_, x, null));
result = true;
}
else
{
result = false;
}
#if SHOWDEBUG
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
if (result)
{
Debug.WriteLine(Texts.Success);
}
else
{
Debug.WriteLine(Texts.Fail);
}
Utils.DisplayStack();
#endif
return result;
}
public bool TryPush(string x)
{
bool result = false;
if (ValueStack.Count < ValueStackCapacity)
{
ValueStack.Push(new Value(Value.Type.str_, 0, x));
result = true;
}
else
{
result = false;
}
#if SHOWDEBUG
Debug.WriteLine(Texts.UserCall + ' ' + MethodInfo.GetCurrentMethod().Name);
if (result)
{
Debug.WriteLine(Texts.Success);
}
else
{
Debug.WriteLine(Texts.Fail);
}
Utils.DisplayStack();
#endif
return result;
}
}
}
}
** Pomocnicza klasa zawierająca teksty wpisywane do logu **
namespace MyInterpreter
{
//klasa zawierająca teksty
//wpisywane do logu
class Texts
{
enum Language
{
En = 0,
Pl = 1
}
static Language DefaultLanguage = Language.En;
public static string ReturnStackEntry
{ get { return returnStackEntry[(int)DefaultLanguage]; } }
public static string EndOfScrip
{ get { return endOfScrip[(int)DefaultLanguage]; } }
public static string MainScript
{ get { return mainScript[(int)DefaultLanguage]; } }
public static string Stack
{ get { return stack[(int)DefaultLanguage]; } }
public static string Empty
{ get { return empty[(int)DefaultLanguage]; } }
public static string Null
{ get { return null_[(int)DefaultLanguage]; } }
public static string InitialCallOf
{ get { return initialCallOf[(int)DefaultLanguage]; } }
public static string ScriptOfNewSubroutine
{ get { return scriptOfNewSubroutine[(int)DefaultLanguage]; } }
public static string MainScriptChange
{ get { return mainScriptChange[(int)DefaultLanguage]; } }
public static string EndsWithSuccess
{ get { return endsWithSuccess[(int)DefaultLanguage]; } }
public static string Fails
{ get { return fails[(int)DefaultLanguage]; } }
public static string UserCall
{ get { return userCall[(int)DefaultLanguage]; } }
public static string Returns
{ get { return returns[(int)DefaultLanguage]; } }
public static string Fail
{ get { return fail[(int)DefaultLanguage]; } }
public static string Call
{ get { return call[(int)DefaultLanguage]; } }
public static string Success
{ get { return success[(int)DefaultLanguage]; } }
//poniżej podano tłumaczenia/opisy tekstów logu do debuggowania
//nastąpiło wywołanie funkcji przez program lub funkcję
static string[] returnStackEntry = { "return stack entry", "" };
//koniec programu
static string[] endOfScrip = { "end of script", "" };
//program główny
static string[] mainScript = { "main script", "" };
//stos
static string[] stack = { "stack", "" };
//pusty stos
static string[] empty = { "empty", "" };
//wartość null
static string[] null_ = { "null", "" };
//początek wywołania podprogramu
static string[] initialCallOf = { "initial call of", "" };
//utworzenie nowej funkcji
static string[] scriptOfNewSubroutine = { "script of new subroutine", "" };
//zmiana treści programu
static string[] mainScriptChange = { "main script change", "" };
//operacja zakończona sukcesem
static string[] endsWithSuccess = { "ends with success", "" };
//operacja zakończona niepowodzeniem
static string[] fails = { "fails", "" };
//wywołanie użytkownia jednej z funkcji integrujących
static string[] userCall = { "user call", "" };
//zwracana wartość
static string[] returns = { "returns", "" };
//niepowodzenie operacji
static string[] fail = { "fail", "" };
//wywołanie funcji
static string[] call = { "call", "" };
//sukces operacji
static string[] success = { "success", "" };
}
}
** Pomocnicza klasa ułatwiająca wpisywanie wartości ze stosu do logu **
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace MyInterpreter
{
partial class Interpreter
{
class Utils
{
//konwersja listy stringów na jeden string rozdzielony spacjami
public static string ConvertToStr(List<string> strings)
{
if (strings != null)
{
string s1 = string.Empty;
foreach (string s2 in strings)
{
s1 += s2 + ' ';
}
return s1.Trim();
}
return null;
}
//wyświetlenie zawartości stosu
public static void DisplayStack()
{
Debug.Write(Texts.Stack + ' ');
if (ValueStack.Count == 0)
{
Debug.Write(Texts.Empty);
}
foreach (Value v in ValueStack.ToArray().Reverse())
{
if (v.type == Value.Type.int_)
{
Debug.Write(v.intVal.ToString() + ' ');
}
else if (v.type == Value.Type.str_)
{
string s = ConvertToStr(v.strVal);
if (s != null)
{
Debug.Write(StringDelimiter.ToString() + s
+ StringDelimiter + ' ');
}
else
{
Debug.Write(StringDelimiter.ToString() + Texts.Null
+ StringDelimiter + ' ');
}
}
}
Debug.Write('\n');
}
}
}
}
Przykład użycia
** Program **
using System;
using MyInterpreter;
//przykładowy kod progamu we własnym języku
//zintegrowany przez stos tego języka
//z programem w języku c#
namespace Example
{
class Program
{
static void Main()
{
//utworzenie interpretera ze stosem pojemności 32 danych typu Value
//i jednym poziomem wywołań zdefiniowanych w kodzie programu funkcji
//(mogą to być funkcje rekurencyjne)
Interpreter i = new Interpreter(32, 1);
//przykładowy program korzystający z funkcji predefiniowanych w tym artykule
//oraz definiujący własne funkcje użytkownika i je wywołujący
string UserProgram = "10 : AP \"Artur\" \"Protasewicz\" ; : x 1 + AP ; 20 \t\n 3 / DROP x +";
i.Interpret(UserProgram);
#region Elementy integracji
int intVal;
string strVal;
//próba odczytania danej int z pozycji szczyt stosu minus jeden
Console.WriteLine("TryPeek(1, int)");
if (i.TryPeek(1, out intVal))
{
Console.WriteLine("odczytano " + intVal);
}
else
{
Console.WriteLine("niepowodzenie");
}
//próba odczytania danej string ze szczytu stosu
Console.WriteLine("TryPeek(0, str)");
if (i.TryPeek(0, out strVal))
{
Console.WriteLine("odczytano " + strVal);
}
else
{
Console.WriteLine("niepowodzenie");
}
//próba wpisania na szczyt stosu stringa "John"
Console.WriteLine("TryPush(str)");
if (i.TryPush("John"))
{
Console.WriteLine("wstawiono " + "John");
}
else
{
Console.WriteLine("niepowodzenie");
}
//próba wpisania na szczyt stosu liczby 100
Console.WriteLine("TryPush(int)");
if (i.TryPush(100))
{
Console.WriteLine("wstawiono " + 100);
}
else
{
Console.WriteLine("niepowodzenie");
}
//próba zdjęcia ze szczytu stosu liczby 100
Console.WriteLine("TryPop(int)");
if (i.TryPop(out intVal))
{
Console.WriteLine("zdjęto " + intVal);
}
else
{
Console.WriteLine("niepowodzenie");
}
//próba zdjęcia ze szczytu stosu stringa "John"
Console.WriteLine("TryPop(str)");
if (i.TryPop(out strVal))
{
Console.WriteLine("zdjęto " + strVal);
}
else
{
Console.WriteLine("niepowodzenie");
}
#endregion
Console.ReadKey();
}
}
}
** Log powyższego programu **
/*
main script
10 : AP "Artur" "Protasewicz" ; : x 1 + AP ; 20
3 / DROP x +
initial call of GetDefinition
script of new subroutine AP
"Artur" "Protasewicz"
main script change
10 : x 1 + AP ; 20 3 / DROP x +
script of new subroutine x
1 + AP
main script change
10 20 3 / DROP x +
GetDefinition ends with success
return stack entry 0
10 IsInt
stack 10
20 IsInt
stack 10 20
3 IsInt
stack 10 20 3
/ IsLanguageFunc
call DivMod
stack 10 6 2
DROP IsLanguageFunc
call Drop
stack 10 6
x IsUserFunc
return stack entry level 1 of 1
1 IsInt
stack 10 6 1
return stack entry level 1 of 1
+ IsLanguageFunc
call AddConcat
stack 10 7
return stack entry level 1 of 1
AP IsUserFunc
return stack entry level 2 of 1
return stack entry denied
user call TryPeek
returns int_ 10
user call TryPeek
fails
user call TryPush
success
stack 10 7 "John"
user call TryPush
success
stack 10 7 "John" 100
user call TryPop
returns int_ 100
stack 10 7 "John"
user call TryPop
returns str_ "John"
stack 10 7
*/