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 
*/

0 komentarzy