using System; using System.Collections.Generic; using System.Collections.Immutable; using Upsilon.BaseTypes; using Upsilon.BaseTypes.Number; using Upsilon.BaseTypes.UserData; using Upsilon.Binder; using Type = Upsilon.BaseTypes.Type; namespace Upsilon.Evaluator { internal class Evaluator { private readonly Diagnostics _diagnostics; private LuaType _lastValue; private LuaType _returnValue; internal EvaluationScope Scope { get; } private bool HasReturned { get; set; } internal Evaluator(Diagnostics diagnostics, Dictionary variables) { _diagnostics = diagnostics; Scope = new EvaluationScope(variables); } internal Evaluator(Diagnostics diagnostics, EvaluationScope parentScope) { _diagnostics = diagnostics; Scope = new EvaluationScope(parentScope); } public LuaType Evaluate(BoundScript e) { EvaluateNode(e.Statement); if (_returnValue == null) return _lastValue; return _returnValue; } public LuaType Evaluate(BoundScript e, string functionName, ImmutableArray parameters) { EvaluateNode(e.Statement); if (!Scope.TryGet(functionName, out var statement) || statement.Type != Type.Function) { throw new ArgumentException(($"Function '{functionName}' could not be found")); } var function = (LuaInternalFunction) statement; var innerEvaluator = new Evaluator(_diagnostics, Scope); for (var index = 0; index < parameters.Length; index++) { var parameter = parameters[index]; var parameterName = function.Parameters[index]; innerEvaluator.Scope.Set(parameterName, parameter); } var result = innerEvaluator.EvaluateNode(function.Block); return result; } internal LuaType EvaluateNode(BoundNode b) { switch (b.Kind) { case BoundKind.BoundScript: EvaluateStatement(((BoundScript)b).Statement); break; case BoundKind.BoundLiteralExpression: case BoundKind.BoundBinaryExpression: case BoundKind.BoundUnaryExpression: case BoundKind.VariableExpression: case BoundKind.BoundFunctionCallExpression: case BoundKind.BoundFunctionExpression: case BoundKind.BoundTableExpression: case BoundKind.BoundIndexExpression: case BoundKind.BoundFullstopIndexExpression: _lastValue = EvaluateExpression((BoundExpression) b); break; case BoundKind.BoundAssignmentStatement: case BoundKind.BoundExpressionStatement: case BoundKind.BoundBlockStatement: case BoundKind.BoundIfStatement: case BoundKind.BoundElseStatement: case BoundKind.BoundFunctionAssignmentStatement: case BoundKind.BoundPromise: case BoundKind.BoundReturnStatement: case BoundKind.BoundTableAssigmentStatement: case BoundKind.BoundMultiAssignmentStatement: EvaluateStatement((BoundStatement) b); break; default: throw new ArgumentOutOfRangeException(); } return _returnValue; } private void EvaluateStatement(BoundStatement e) { if (HasReturned) return; switch (e.Kind) { case BoundKind.BoundAssignmentStatement: EvaluateAssignmentStatement((BoundVariableAssignment) e); break; case BoundKind.BoundBlockStatement: EvaluateBoundBlockStatement((BoundBlockStatement) e); break; case BoundKind.BoundIfStatement: EvaluateBoundIfStatement((BoundIfStatement) e); break; case BoundKind.BoundReturnStatement: EvaluateReturnStatement((BoundReturnStatement) e); break; case BoundKind.BoundFunctionAssignmentStatement: EvaluateBoundFunctionAssigmentStatement((BoundFunctionAssignmentStatement) e); break; case BoundKind.BoundTableAssigmentStatement: EvaluateTableAssignmentStatement((BoundTableAssigmentStatement) e); break; case BoundKind.BoundMultiAssignmentStatement: EvaluateMultiAssignmentStatement((BoundMultiAssignmentStatement) e); break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; } } private void EvaluateExpressionStatement(BoundExpressionStatement e) { _lastValue = EvaluateExpression(e.Expression); } internal LuaType EvaluateExpression(BoundExpression e) { switch (e.Kind) { case BoundKind.BoundLiteralExpression: return ((BoundLiteralExpression) e).Value; case BoundKind.BoundBinaryExpression: return EvaluateBinaryExpression((BoundBinaryExpression) e); case BoundKind.BoundUnaryExpression: return EvaluateUnaryExpression((BoundUnaryExpression) e); case BoundKind.VariableExpression: return EvaluateVariableExpression((BoundVariableExpression) e); case BoundKind.BoundFunctionCallExpression: return EvaluateBoundFunctionCallExpression((BoundFunctionCallExpression) e); case BoundKind.BoundTableExpression: return EvaluateTableExpression((BoundTableExpression) e); case BoundKind.BoundIndexExpression: return EvaluateIndexExpression((BoundIndexExpression) e); case BoundKind.BoundFunctionExpression: return EvaluateBoundFunctionStatement((BoundFunctionExpression) e); case BoundKind.BoundPromise: return EvaluateUnboundFunctionStatement((UnboundFunctionExpression) e); case BoundKind.BoundFullstopIndexExpression: return EvaluateFullStopIndexExpression((BoundFullStopIndexExpression) e); default: throw new NotImplementedException(); } } private LuaType EvaluateUnaryExpression(BoundUnaryExpression e) { var operand = EvaluateExpression(e.InExpression); switch (e.Operator.Kind) { case BoundUnaryOperator.OperatorKind.Identity: return operand; case BoundUnaryOperator.OperatorKind.Negation: if (operand.Type == Type.Number) return -((Number)operand); else if (operand.Type == Type.UserData) { var ud = (GenericUserData) operand; var (type, failed) = ud.UnaryOperator(operand, OperatorType.UnaryNegation); if (failed) goto default; return type; } goto default; case BoundUnaryOperator.OperatorKind.LogicalNegation: if (operand.Type == Type.Boolean) return !(LuaBoolean) operand; else if (operand.Type == Type.UserData) { var ud = (GenericUserData) operand; var (type, failed) = ud.UnaryOperator(operand, OperatorType.LogicalNot); if (failed) goto default; return type; } goto default; default: throw new Exception("Invalid Unary Operator: " + e.Operator.Kind); } } private LuaType EvaluateBinaryExpression(BoundBinaryExpression e) { var left = EvaluateExpression(e.LeftExpression); var right = EvaluateExpression(e.RightExpression); switch (e.Operator.Kind) { case BoundBinaryOperator.OperatorKind.Addition: if (left.Type == Type.Number) return ((Number)left) + ((Number)right); else if (left.Type == Type.String) { return ((LuaString) left) + right; } else if (left.Type == Type.UserData) { var ud = (GenericUserData) left; var (type, failed) = ud.BinaryOperator(left, OperatorType.Addition, right); if (failed) goto default; return type; } goto default; case BoundBinaryOperator.OperatorKind.Subtraction: if (left.Type == Type.Number) { return ((Number)left) - ((Number)right); } else if (left.Type == Type.UserData) { var ud = (GenericUserData) left; var (type, failed) = ud.BinaryOperator(left, OperatorType.Subtraction, right); if (failed) goto default; return type; } goto default; case BoundBinaryOperator.OperatorKind.Multiplication: if (left.Type == Type.Number) { return ((Number)left) * ((Number)right); } else if (left.Type == Type.UserData) { var ud = (GenericUserData) left; var (type, failed) = ud.BinaryOperator(left, OperatorType.Multiplication, right); if (failed) goto default; return type; } goto default; case BoundBinaryOperator.OperatorKind.Division: if (left.Type == Type.Number) { return ((Number)left) / ((Number)right); } else if (left.Type == Type.UserData) { var ud = (GenericUserData) left; var (type, failed) = ud.BinaryOperator(left, OperatorType.Division, right); if (failed) goto default; return type; } goto default; case BoundBinaryOperator.OperatorKind.Equality: return new LuaBoolean(Equals(left, right)); case BoundBinaryOperator.OperatorKind.Inequality: return new LuaBoolean(!Equals(left, right)); default: throw new Exception("Invalid Binary Operator: " + e.Operator); } } private void EvaluateAssignmentStatement(BoundVariableAssignment e) { var val = EvaluateExpression(e.BoundExpression); if (e.Variable.Local) { Scope.Set(e.Variable, val); } else { Scope.SetGlobal(e.Variable, val); } _lastValue = val; } private void EvaluateMultiAssignmentStatement(BoundMultiAssignmentStatement e) { var val = EvaluateExpression(e.Assignment); if (val.Type == Type.Table) { var table = (LuaTable) val; for (var i = 0; i < e.Variables.Length; i++) { var variableSymbol = e.Variables[i]; if (variableSymbol == null) continue; if (variableSymbol.Name == "_") continue; var value = table.Get(_diagnostics, e.Span, new NumberLong(i + 1), Scope); if (variableSymbol.Local) Scope.Set(variableSymbol, value); else Scope.SetGlobal(variableSymbol, value); } } else { _diagnostics.LogError($"Can't assign type '{val.Type}' to multiple variables.", e.Span); } _lastValue = val; } private LuaType EvaluateVariableExpression(BoundVariableExpression e) { if (Scope.TryGet(e.Variable, out var val)) { return val; } throw new Exception($"Cannot find variable: '{e.Variable.Name}'"); } private void EvaluateBoundBlockStatement(BoundBlockStatement boundBlockStatement) { foreach (var boundStatement in boundBlockStatement.Statements) { if (HasReturned) return; EvaluateStatement(boundStatement); } } private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement) { var innerEvaluator = new Evaluator(_diagnostics, Scope); var condition = innerEvaluator.EvaluateExpression(boundBlockStatement.Condition.Expression); if ((LuaBoolean) condition) { innerEvaluator.EvaluateStatement(boundBlockStatement.Block); } else if (boundBlockStatement.NextElseIf != null) { innerEvaluator.EvaluateStatement(boundBlockStatement.NextElseIf); } else if (boundBlockStatement.ElseStatement != null) { innerEvaluator.EvaluateStatement(boundBlockStatement.ElseStatement.Block); } HasReturned = innerEvaluator.HasReturned; if (HasReturned) _returnValue = innerEvaluator._returnValue; _lastValue = innerEvaluator._lastValue; } private void EvaluateBoundFunctionAssigmentStatement(BoundFunctionAssignmentStatement e) { var func = EvaluateBoundFunctionStatement(e.Func); if (e.Variable.Local) Scope.Set(e.Variable, func); else Scope.SetGlobal(e.Variable, func); } private LuaType EvaluateBoundFunctionStatement(BoundFunctionExpression boundFunctionExpression) { var func = new LuaInternalFunction(boundFunctionExpression.Parameters, boundFunctionExpression.Block, Scope); return func; } private LuaType EvaluateUnboundFunctionStatement(UnboundFunctionExpression unboundFunctionExpression) { var func = new LuaInternalFunction(unboundFunctionExpression.Parameters, unboundFunctionExpression.Block, Scope); return func; } private LuaType EvaluateBoundFunctionCallExpression(BoundFunctionCallExpression boundFunctionCallExpression) { var variable = EvaluateExpression(boundFunctionCallExpression.Identifier); if (!(variable is LuaFunction function)) { throw new Exception($"Variable is not a function."); } var ls = new List(); foreach (var t in boundFunctionCallExpression.Parameters) { var evaluate = EvaluateExpression(t); ls.Add(evaluate); } var val = function.Run(_diagnostics, ls.ToArray()); return val; } private void EvaluateReturnStatement(BoundReturnStatement b) { _returnValue = EvaluateExpression(b.Expression); _lastValue = _returnValue; HasReturned = true; } private LuaType EvaluateTableExpression(BoundTableExpression e) { var tableScope = EvaluationScope.CreateWithGetOnlyParent(Scope); var innerEvaluator = new Evaluator(_diagnostics, tableScope); var currentPos = 1; foreach (var boundStatement in e.Statements) { switch (boundStatement.Kind) { case BoundKind.BoundAssignmentStatement: case BoundKind.BoundFunctionExpression: case BoundKind.BoundFunctionAssignmentStatement: innerEvaluator.EvaluateNode(boundStatement); break; default: { innerEvaluator.EvaluateStatement(boundStatement); if (innerEvaluator._lastValue != null) tableScope.Set(new VariableSymbol(currentPos.ToString(), innerEvaluator._lastValue.Type, false), innerEvaluator._lastValue); innerEvaluator._lastValue = null; break; } } currentPos++; } return new LuaTable(tableScope); } private LuaType EvaluateIndexExpression(BoundIndexExpression e) { var variable = EvaluateExpression(e.Identifier); if (!(variable is IIndexable indexable)) { throw new Exception("Variable is not indexable."); } var scope = Scope; if (variable is IScopeOwner scopeOwner) { scope = scopeOwner.EvaluationScope; } var indexer = EvaluateExpression(e.Index); return indexable.Get(_diagnostics, e.Span, indexer, scope); } private LuaType EvaluateFullStopIndexExpression(BoundFullStopIndexExpression e) { var variable = EvaluateExpression(e.Expression); if (!(variable is IIndexable indexable)) { throw new Exception("Variable is not indexable."); } var scope = Scope; if (variable is IScopeOwner scopeOwner) { scope = scopeOwner.EvaluationScope; } return indexable.Get(_diagnostics, e.Span, e.Index.ToLuaType(), scope); } private void EvaluateTableAssignmentStatement(BoundTableAssigmentStatement e) { LuaType table; LuaType index; if (e.TableIndexExpression.Kind == BoundKind.BoundIndexExpression) { table = EvaluateExpression(((BoundIndexExpression)e.TableIndexExpression).Identifier); index = EvaluateExpression(((BoundIndexExpression)e.TableIndexExpression).Index); } else { table = EvaluateExpression(((BoundFullStopIndexExpression)e.TableIndexExpression).Expression); index = ((BoundFullStopIndexExpression) e.TableIndexExpression).Index.ToLuaType(); } var value = EvaluateExpression(e.Value); if (!(table is IIndexable indexable)) { throw new Exception("Cant assign to that"); } indexable.Set(_diagnostics, e.Span, index.ToString().ToLuaType(), value); } } }