<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using squirrel.Nodes;

namespace squirrel
{
    public class Evaluator
    {
        private readonly INode _root;

        private static readonly Dictionary<string, BuiltinFunctionDelegate> BuiltinFunctions =
            new Dictionary<string, BuiltinFunctionDelegate>()
            {
                {"block", BuiltinBlock},
                {"def", BuiltinDef},
                {"add", BuiltinAdd},
                {"sub", BuiltinSub},
                {"mul", BuiltinMul},
                {"div", BuiltinDiv},
                {"eval", BuiltinEval},
                {"lambda", BuiltinLambda}
            };

        private delegate INode BuiltinFunctionDelegate(List<INode> args, Environment env);

        public Evaluator(INode root)
        {
            _root = root;
        }

        public INode Evaluate() => VisitNode(_root, new Environment());

        private static INode VisitNode(INode node, Environment env)
        {
            var methodName = $"Visit{node.GetType().Name}";
            var method = typeof(Evaluator).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
            return (INode) method.Invoke(null, new object[] {node, env});
        }

        private static INode VisitIntegerNode(IntegerNode node, Environment env) => node;

        private static INode VisitSymbolNode(SymbolNode node, Environment env)
        {
            while (true)
            {
                var nullable = env.Get(node.Value);

                if (nullable == null)
                {
                    return node;
                }

                if (nullable.GetType() != typeof(SymbolNode))
                {
                    return nullable;
                }

                node = (SymbolNode) nullable;
            }
        }

        private static INode VisitSymbolicExpressionNode(SymbolicExpressionNode node, Environment env)
        {
            if (node.Children.Count < 2)
            {
                throw new ArgumentException("symbolic expression must contain a symbol and at least one argument");
            }

            var visitedChildren = new List<INode>();
            foreach (var child in node.Children)
            {
                var visitedChild = VisitNode(child, env);
                if (visitedChild.GetType() == typeof(ErrorNode))
                {
                    return visitedChild;
                }
                visitedChildren.Add(visitedChild);
            }

            var head = visitedChildren.Head();
            var tail = visitedChildren.Tail();

            if (head.GetType() == typeof(SymbolNode))
            {
                return EvaluateBuiltinFunction((SymbolNode) head, tail, env);
            }

            if (head.GetType() == typeof(LambdaFunctionNode))
            {
                throw new NotImplementedException("evaluation of lambda functions is not implemented yet");
            }

            throw new ArgumentException("first element of symbolic expression must be a symbol or lambda function");
        }

        private static INode VisitQuotedExpressionNode(QuotedExpressionNode node, Environment env) => node;

        private static INode VisitLambdaFunctionNode(LambdaFunctionNode node, Environment env) => node;

        private static INode VisitErrorNode(ErrorNode node, Environment env) => node;

        private static INode EvaluateBuiltinFunction(SymbolNode head, List<INode> tail, Environment env)
        {
            var functionName = head.Value;

            if (!BuiltinFunctions.ContainsKey(functionName))
            {
                throw new ArgumentException($"function is not defined: {functionName}");
            }

            var function = BuiltinFunctions[functionName];
            var attr = function.GetMethodInfo().GetCustomAttribute<BuiltinFunctionAttribute>();

            if (attr == null)
            {
                return function.Invoke(tail, env);
            }

            var expectedCount = attr.ExpectedTypes.Length;
            var actualCount = tail.Count;

            if (actualCount != expectedCount)
            {
                return new ErrorNode($"function takes exactly {expectedCount} arguments ({actualCount} given)");
            }

            for (var i = 0; i < expectedCount; i++)
            {
                var expectedType = attr.ExpectedTypes[i];
                var actualType = tail[i].GetType();

                if (actualType != expectedType)
                {
                    return new ErrorNode($"expected argument of type {expectedType} but got type {actualType}");
                }
            }

            return function.Invoke(tail, env);
        }

        private static INode BuiltinBlock(List<INode> args, Environment env) => args[args.Count - 1];

        private static INode BuiltinDef(List<INode> args, Environment env)
        {
            var names = ((QuotedExpressionNode) (args.Head())).Children;<commit_msg>Implement evaluation of lambda functions<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using squirrel.Nodes;

namespace squirrel
{
    public class Evaluator
    {
        private readonly INode _root;

        private static readonly Dictionary<string, BuiltinFunctionDelegate> BuiltinFunctions =
            new Dictionary<string, BuiltinFunctionDelegate>()
            {
                {"block", BuiltinBlock},
                {"def", BuiltinDef},
                {"add", BuiltinAdd},
                {"sub", BuiltinSub},
                {"mul", BuiltinMul},
                {"div", BuiltinDiv},
                {"eval", BuiltinEval},
                {"lambda", BuiltinLambda}
            };

        private delegate INode BuiltinFunctionDelegate(List<INode> args, Environment env);

        public Evaluator(INode root)
        {
            _root = root;
        }

        public INode Evaluate() => VisitNode(_root, new Environment());

        private static INode VisitNode(INode node, Environment env)
        {
            var methodName = $"Visit{node.GetType().Name}";
            var method = typeof(Evaluator).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
            return (INode) method.Invoke(null, new object[] {node, env});
        }

        private static INode VisitIntegerNode(IntegerNode node, Environment env) => node;

        private static INode VisitSymbolNode(SymbolNode node, Environment env)
        {
            while (true)
            {
                var nullable = env.Get(node.Value);

                if (nullable == null)
                {
                    return node;
                }

                if (nullable.GetType() != typeof(SymbolNode))
                {
                    return nullable;
                }

                node = (SymbolNode) nullable;
            }
        }

        private static INode VisitSymbolicExpressionNode(SymbolicExpressionNode node, Environment env)
        {
            if (node.Children.Count < 2)
            {
                throw new ArgumentException("symbolic expression must contain a symbol and at least one argument");
            }

            var visitedChildren = new List<INode>();
            foreach (var child in node.Children)
            {
                var visitedChild = VisitNode(child, env);
                if (visitedChild.GetType() == typeof(ErrorNode))
                {
                    return visitedChild;
                }
                visitedChildren.Add(visitedChild);
            }

            var head = visitedChildren.Head();
            var tail = visitedChildren.Tail();

            if (head.GetType() == typeof(SymbolNode))
            {
                return EvaluateBuiltinFunction((SymbolNode) head, tail, env);
            }

            if (head.GetType() == typeof(LambdaFunctionNode))
            {
                return EvaluateLambdaFunction((LambdaFunctionNode) head, tail, new Environment(env));
            }

            throw new ArgumentException("first element of symbolic expression must be a symbol or lambda function");
        }

        private static INode VisitQuotedExpressionNode(QuotedExpressionNode node, Environment env) => node;

        private static INode VisitLambdaFunctionNode(LambdaFunctionNode node, Environment env) => node;

        private static INode VisitErrorNode(ErrorNode node, Environment env) => node;

        private static INode EvaluateBuiltinFunction(SymbolNode head, List<INode> tail, Environment env)
        {
            var functionName = head.Value;

            if (!BuiltinFunctions.ContainsKey(functionName))
            {
                throw new ArgumentException($"function is not defined: {functionName}");
            }

            var function = BuiltinFunctions[functionName];
            var attr = function.GetMethodInfo().GetCustomAttribute<BuiltinFunctionAttribute>();

            if (attr == null)
            {
                return function.Invoke(tail, env);
            }

            var expectedCount = attr.ExpectedTypes.Length;
            var actualCount = tail.Count;

            if (actualCount != expectedCount)
            {
                return new ErrorNode($"function takes exactly {expectedCount} arguments ({actualCount} given)");
            }

            for (var i = 0; i < expectedCount; i++)
            {
                var expectedType = attr.ExpectedTypes[i];
                var actualType = tail[i].GetType();

                if (actualType != expectedType)
                {
                    return new ErrorNode($"expected argument of type {expectedType} but got type {actualType}");
                }
            }

            return function.Invoke(tail, env);
        }

        private static INode EvaluateLambdaFunction(LambdaFunctionNode head, List<INode> tail, Environment env)
        {
            var expectedCount = head.Parameters.Children.Count;
            var actualCount = tail.Count;

            if (actualCount != expectedCount)
            {
                return new ErrorNode($"function takes exactly {expectedCount} arguments ({actualCount} given)");
            }

            for (var i = 0; i < expectedCount; i++)
            {
                var key = ((SymbolNode) head.Parameters.Children[i]).Value;
                var value = tail[i];
                env.Put(key, value);
            }

            return BuiltinEval(new List<INode>() {head.Body}, env);
        }

        private static INode BuiltinBlock(List<INode> args, Environment env) => args[args.Count - 1];

        private static INode BuiltinDef(List<INode> args, Environment env)
        {
            var names = ((QuotedExpressionNode) (args.Head())).Children;
{'fraction_non_alphanumeric': 0.08959750045947436, 'fraction_numerical': 0.0006432641058628928, 'mean_word_length': 1.7840880020465593, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1033141', 'n_tokens_mistral': 2646, 'n_tokens_neox': 2503, 'n_words': 697}