Exemplo n.º 1
0
 public Repl Register <T>(string name, T obj)
 {
     _global[name] = new NonCallable(obj);
     return(this);
 }
Exemplo n.º 2
0
        public object Execute(string command)
        {
            var stack  = new List <Token>();
            var levels = new List <Level>();

            // Kind a parser
            int GetPriority(string newToken)
            {
                switch (newToken)
                {
                case "=":
                    return(1);

                case "*":
                case "/":
                    return(4);

                case "+":
                case "-":
                    return(5);

                default:
                    return(15);
                }
            }

            void Reduce <T>(int offset, Func <Token, T> init, Dictionary <string, Func <T, Token, T> > reducers)
            {
                var result = init(stack[offset]);

                stack.RemoveAt(offset);

                while (offset < stack.Count)
                {
                    if (reducers.TryGetValue(stack[offset].AsString, out var reducer))
                    {
                        stack.RemoveAt(offset);
                        result = reducer(result, stack[offset]);
                        stack.RemoveAt(offset);
                    }
                    else
                    {
                        break;
                    }
                }

                stack.Insert(offset, new Token(TokenType.Scalar, result));
            }

            void GoDeep(TokenType type, int take, int priority)
            {
                while (levels.Count > 0 && levels[levels.Count - 1].Type != TokenType.Bracket && levels[levels.Count - 1].Priority < priority)
                {
                    GoUp();
                }

                if (levels.Count == 0 || levels[levels.Count - 1].Priority != priority)
                {
                    levels.Add(new Level(type, stack.Count - 1 - take, priority));
                }
            }

            void GoUp()
            {
                var last = levels[levels.Count - 1];

                switch (last.Type)
                {
                case TokenType.Bracket:
                    if (last.Offset > 0 && stack[last.Offset - 1].Type == TokenType.Identifier)
                    {
                        List <Token> args = stack[last.Offset + 1].Type != TokenType.Bracket
                                ? stack[last.Offset + 1].Value as List <Token> ?? new List <Token> {
                            stack[last.Offset + 1]
                        }
                                : new List <Token>();
                        var result = Call(stack[last.Offset - 1].AsString, args);

                        stack.RemoveRange(last.Offset - 1, stack.Count - last.Offset + 1);
                        stack.Insert(last.Offset - 1, new Token(TokenType.Scalar, result));
                    }
                    else
                    {
                        var value = GetValue(stack[last.Offset + 1]);
                        stack.RemoveRange(last.Offset, 3);
                        stack.Insert(last.Offset, new Token(TokenType.Scalar, value));
                    }
                    break;

                case TokenType.Delimiter:
                    Reduce(last.Offset, x => new List <Token> {
                        stack[last.Offset]
                    }, new Dictionary <string, Func <List <Token>, Token, List <Token> > >
                    {
                        [","] = (s, x) =>
                        {
                            s.Add(stack[last.Offset]);
                            return(s);
                        }
                    });
                    break;

                case TokenType.Operation:
                    if (stack[last.Offset + 1].AsString == "=")
                    {
                        // TODO implement right-to-left a = b = c
                        var nonCallable = (NonCallable)Get(stack[last.Offset].AsString);
                        if (nonCallable != null)
                        {
                            nonCallable.Value = GetValue(stack[last.Offset + 2]);
                        }
                        else
                        {
                            _global[stack[last.Offset].AsString] = nonCallable = new NonCallable(GetValue(stack[last.Offset + 2]));
                        }

                        stack.RemoveRange(last.Offset, 3);
                        stack.Insert(last.Offset, new Token(TokenType.Scalar, nonCallable));
                    }
                    else if (stack[last.Offset + 1].AsString == "+" || stack[last.Offset + 1].AsString == "-")
                    {
                        Reduce(last.Offset, x => Convert.ToDouble(GetValue(stack[last.Offset])), new Dictionary <string, Func <double, Token, double> >
                        {
                            ["+"] = (s, x) => s + Convert.ToDouble(GetValue(x)),
                            ["-"] = (s, x) => s - Convert.ToDouble(GetValue(x)),
                        });
                    }
                    else if (stack[last.Offset + 1].AsString == "*" || stack[last.Offset + 1].AsString == "/")
                    {
                        Reduce(last.Offset, x => (double)GetValue(stack[last.Offset]), new Dictionary <string, Func <double, Token, double> >
                        {
                            ["*"] = (s, x) => s * Convert.ToDouble(GetValue(x)),
                            ["/"] = (s, x) => s / Convert.ToDouble(GetValue(x)),
                        });
                    }
                    break;
                }
                levels.RemoveAt(levels.Count - 1);
            }

            // Kind a lexer
            var position = 0;
            var escaped  = false;

            char Take(int offset) => position + offset < command.Length ? command[position + offset] : '\0';

            bool IsScalarStart(char x) => x >= '0' && x <= '9' || x == '.' || x == '-';
            bool IsScalarCont(char x) => x >= '0' && x <= '9' || x == '.';
            bool IsIdentifierStart(char x) => x >= 'a' && x <= 'z' || x >= 'A' && x <= 'Z';
            bool IsIdentifierCont(char x) => IsIdentifierStart(x) || IsScalarCont(x);
            bool IsStringStart(char x) => x == '\"' || x == '\'';

            bool IsStringCont(char start, char x)
            {
                if (x != start || escaped)
                {
                    escaped = !escaped && x == '\\';
                    return(true);
                }
                return(false);
            }

            while (position < command.Length)
            {
                if (IsIdentifierStart(Take(0)))
                {
                    var length = 1;
                    while (IsIdentifierCont(Take(length)))
                    {
                        length += 1;
                    }

                    stack.Add(new Token(TokenType.Identifier, command.Substring(position, length)));
                    position += length;
                }
                else if (IsScalarStart(Take(0)))
                {
                    var length = 1;
                    while (IsScalarCont(Take(length)))
                    {
                        length += 1;
                    }

                    stack.Add(new Token(TokenType.Scalar, double.Parse(command.Substring(position, length))));
                    position += length;
                }
                else if (IsStringStart(Take(0)))
                {
                    var length = 1;
                    while (IsStringCont(Take(0), Take(length)))
                    {
                        length += 1;
                    }
                    length += 1;

                    stack.Add(new Token(TokenType.Scalar, command.Substring(position + 1, length - 2)));
                    position += length;
                }
                else if (Take(0) == '=' || Take(0) == '+' || Take(0) == '-' || Take(0) == '*' || Take(0) == '/')
                {
                    stack.Add(new Token(TokenType.Operation, command.Substring(position, 1)));
                    GoDeep(TokenType.Operation, 1, GetPriority(Take(0).ToString()));
                    position += 1;
                }
                else if (Take(0) == '(' || Take(0) == ')')
                {
                    stack.Add(new Token(TokenType.Bracket, command.Substring(position, 1)));
                    if (Take(0) == '(')
                    {
                        GoDeep(TokenType.Bracket, 0, 0);
                    }
                    else
                    {
                        while (levels[levels.Count - 1].Type != TokenType.Bracket)
                        {
                            GoUp();
                        }
                        GoUp();
                    }
                    position += 1;
                }
                else if (Take(0) == ',')
                {
                    stack.Add(new Token(TokenType.Delimiter, command.Substring(position, 1)));
                    GoDeep(TokenType.Delimiter, 1, 16);
                    position += 1;
                }
                else if (char.IsWhiteSpace(Take(0)))
                {
                    position += 1;
                }
                else
                {
                    throw new ArgumentException($"Unexpected symbol at position {position}: {command.Substring(0, position + 1)}");
                }
            }

            while (levels.Count > 0)
            {
                GoUp();
            }

            if (stack.Count == 0)
            {
                return(null);
            }

            return(GetValue(stack[0]));
        }