//Rozdział 16.
//Interpreter

//Ewaluator wyrażeń numerycznych
//Leksykalizacja

public class Token
{
    public enum Type
    {
        Integer, Plus, Minus, Lparen, Rparen
    }
    public Type MyType;
    public string Text;
    public Token(Type type, string text)
    {
        MyType = type;
        Text = text;
    }
    public override string ToString()
    {
        return $"`{Text}`";
    }
}

static List<Token> Lex(string input)
{
    var result = new List<Token>();
    for (int i = 0; i < input.Length; i++)
    {
        switch (input[i])
        {
            case '+':
                result.Add(new Token(Token.Type.Plus, "+"));
                break;
            case '-':
                result.Add(new Token(Token.Type.Minus, "-"));
                break;
            case '(':
                result.Add(new Token(Token.Type.Lparen, "("));
                break;
            case ')':
                result.Add(new Token(Token.Type.Rparen, ")"));
                break;
            default:
                // todo
        }
    }
    return result;
}
Dictionary<BinaryOperation.Type, char>
var sb = new StringBuilder(input[i].ToString());
for (int j = i + 1; j < input.Length; ++j)
{
    if (char.IsDigit(input[j]))
    {
        sb.Append(input[j]);
        ++i;
    }
    else
    {
        result.Add(new Token(Token.Type.Integer, sb.ToString()));
        break;
    }
}

// Parsowanie

public interface IElement
{
    int Value { get; }
}

public class Integer : IElement
{
    public Integer(int value)
    {
        Value = value;
    }
    public int Value { get; }
}

public class BinaryOperation : IElement
{
    public enum Type
    {
        Addition,
        Subtraction
    }

    public Type MyType;
    public IElement Left, Right;

    public int Value
    {
        get
        {
            switch (MyType)
            {
                case Type.Addition:
                    return Left.Value + Right.Value;
                case Type.Subtraction:
                    return Left.Value - Right.Value;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
}

static IElement Parse(IReadOnlyList<Token> tokens)
{
    var result = new BinaryOperation();
    bool haveLHS = false;
    for (int i = 0; i < tokens.Count; i++)
    {
        var token = tokens[i];
        //sprawdzamy typ tokena
        switch (token.MyType)
        {
            //przetwarzamy po kolei każdy token
        }
    }
    return result;
}
 case Token.Type.Integer:
    var integer = new Integer(int.Parse(token.Text));
    if (!haveLHS)
    {
        result.Left = integer;
        haveLHS = true;
    } else
    {
        result.Right = integer;
    }
    break;
 case Token.Type.Plus:
    result.MyType = BinaryOperation.Type.Addition;
    break;
 case Token.Type.Minus:
    result.MyType = BinaryOperation.Type.Subtraction;
    break;
 case Token.Type.Lparen:
    int j = i;
    for (; j < tokens.Count; ++j)
        if (tokens[j].MyType == Token.Type.Rparen)
            break; //znalazłem to!
    //przetwarzanie podwyrażenia bez otwierania
    var subexpression = tokens.Skip(i+1).Take(j - i - 1).ToList();
    var element = Parse(subexpression);
    if (!haveLHS)
    {
        result.Left = element;
        haveLHS = true;
    } else result.Right = element;
    i = j; // przechodzimy dalej
    break;
// Wykorzystanie leksera i parsera

var input = "(13+4)-(12+1)";
var tokens = Lex(input);
WriteLine(string.Join("\t", tokens));
// `(` `13` `+` `4` `)` `-` `(` `12` `+` `1` `)`

var parsed = Parse(tokens);
WriteLine($"{input} = {parsed.Value}");
// (13-4)-(12+1) = -4

//Interpreter w paradygmacie funkcyjnym
 <math>
    <plus>
        <value>2</value>
        <value>3</value>
    </plus>
</math>
 type Expression =
    Math of Expression list
    | Plus of lhs:Expression * rhs:Expression
    | Value of value:string
 let cases = FSharpType.GetUnionCases (typeof<Expression>)
            |> Array.map(fun f ->
               (f.Name, FSharpValue.PreComputeUnionConstructor(f)))
            |> Map.ofArray
 let makeCase parameters =
    try
        let caseInfo = cases.Item name
        (caseInfo parameters) :?> Expression
    with
    | exp -> raise <| new Exception(String.Format("Nieudana próba stworzenia {0} : {1}", name, exp.Message))
 use stringReader = new StringReader(text)
use xmlReader = XmlReader.Create(stringReader)
let doc = XDocument.Load(xmlReader)
let parsed = recursiveBuild doc.Root
 let rec recursiveBuild (root:XElement) =
    let name = root.Name.LocalName |> makeCamelCase

    let makeCase parameters =
    // tak, jak wcześniej

    let elems = root.Elements() |> Seq.toArray
    let values = elems |> Array.map(fun f -> recursiveBuild f)
    if elems.Length = 0 then
        let rootValue = root.Value.Trim()
        makeCase [| box rootValue |]
    else
        try
            values |> Array.map box |> makeCase
        with
        | _ -> makeCase [| values |> Array.toList |]
 let rec eval expr =
    match expr with
    | Math m -> eval m.Head
    | Plus (lhs, rhs) -> eval lhs + eval rhs
    | Value v -> v |> int
 let parsed = recursiveBuild doc.Root
 printf "%s = %d" (print parsed) (eval parsed)
// (2+3) = 5
 type Expression =
    // tutaj składowe unii
    member self.Val =
        let rec eval expr =
            match expr with
            | Math m -> eval(m.Head)
            | Plus (lhs, rhs) -> eval lhs + eval rhs
            | Value v -> v |> int
        eval self
 
