using System; using System.Collections.Generic; using System.Threading; using Upsilon.BaseTypes; using Upsilon.BaseTypes.Number; using Upsilon.BaseTypes.ScriptFunction; using Upsilon.BaseTypes.ScriptTable; using Upsilon.BaseTypes.ScriptTypeInterfaces; using Upsilon.BaseTypes.UserData; using Upsilon.Binder; using Upsilon.Binder.VariableSymbols; using Upsilon.Evaluator.Debugging; using Upsilon.Exceptions; using Upsilon.Text; using Upsilon.Utilities; using Type = Upsilon.BaseTypes.Type; namespace Upsilon.Evaluator { internal class Evaluator : IDisposable { private readonly Script _script; private Diagnostics _diagnostics; private ScriptType _lastValue; private ScriptType _returnValue; internal EvaluationScope Scope { get; private set; } private bool HasReturned { get; set; } private bool HasBroken { get; set; } internal Evaluator(Diagnostics diagnostics, Dictionary variables, Script script) { _diagnostics = diagnostics; _script = script; Scope = new EvaluationScope(variables); } internal Evaluator(Diagnostics diagnostics, EvaluationScope parentScope, Script script) { _diagnostics = diagnostics; _script = script; Scope = new EvaluationScope(parentScope); } private Evaluator(Script script) { _script = script; } internal static Evaluator CreateWithSetScope(Diagnostics diagnostics, EvaluationScope scope, Script script) { return new Evaluator(script) {_diagnostics = diagnostics, Scope = scope}; } public void Dispose() { Scope.Dispose(); _lastValue = null; _returnValue = null; } public ScriptType Evaluate(BoundScript e) { if (DebugSession.DebuggerAttached && !string.IsNullOrWhiteSpace(e.FileName)) { var file = DebugSession.GetFileInfo(e.FileName); if (file != null) { foreach (var breakpoint in file.Breakpoints) { var debugStatement = e.GetBottomStatementAtPosition(breakpoint.Line, breakpoint.Position); debugStatement.HasBreakpoint = true; } } } EvaluateNode(e.Statement); if (_returnValue == null) return _lastValue; return _returnValue; } public ScriptType Evaluate(BoundScript e, string functionName, object[] parameters) { if (!Scope.TryGet(functionName, out var statement) || statement.Type != Type.Function) { throw new ArgumentException(($"Function '{functionName}' could not be found")); } var function = (ScriptRuntimeFunction) statement; var innerEvaluator = new Evaluator(_diagnostics, Scope, _script); if (parameters != null) { for (var index = 0; index < parameters.Length; index++) { object parameter; if (index < parameters.Length) { parameter = parameters[index]; } else { parameter = null; } UserDataVariableSymbol parameterSymbol; if (index < function.Parameters.Length) { parameterSymbol = (UserDataVariableSymbol)function.Parameters[index].VariableSymbol; } else { continue; } if (parameterSymbol.BoundTypeDefinition != null && parameter != null) { bool isCompatible = false; var parameterType = parameter.GetType(); foreach (var validType in parameterSymbol.BoundTypeDefinition.ValidInternalTypes) { if (validType.IsAssignableFrom(parameterType)) { isCompatible = true; break; } } if (!isCompatible) { throw new EvaluationException( $"Parameter '{parameterSymbol.Name}' of function '{functionName}' can't handle the given object with type '{parameterType}'"); } } var parameterConverted = parameter == null ? new ScriptNull() : parameter.ToScriptType(); innerEvaluator.Scope.CreateLocal(parameterSymbol, parameterConverted); } } var result = innerEvaluator.EvaluateNode(function.Block); return result; } internal ScriptType 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: case BoundKind.BoundNumericForStatement: case BoundKind.BoundGenericForStatement: case BoundKind.BoundBreakStatement: case BoundKind.BoundWhileStatement: 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; case BoundKind.BoundNumericForStatement: EvaluateNumericForStatement((BoundNumericForStatement) e); break; case BoundKind.BoundBreakStatement: HasBroken = true; return; case BoundKind.BoundGenericForStatement: EvaluateGenericForStatement((BoundGenericForStatement) e); break; case BoundKind.BoundWhileStatement: EvaluateWhileStatement((BoundWhileStatement) e); break; default: EvaluateExpressionStatement((BoundExpressionStatement) e); break; } } private void EvaluateExpressionStatement(BoundExpressionStatement e) { _lastValue = EvaluateExpression(e.Expression); } internal ScriptType 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(e.Kind.ToString()); } } private ScriptType 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 -((ScriptNumber)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 !(ScriptBoolean) 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; case BoundUnaryOperator.OperatorKind.Length: if (operand is ILengthType length) { return length.Length(); } goto default; default: throw new Exception("Invalid Unary Operator: " + e.Operator.Kind); } } private ScriptType EvaluateBinaryExpression(BoundBinaryExpression e) { if (e.Operator.Kind == BoundBinaryOperator.OperatorKind.Or) { var l = EvaluateExpression(e.LeftExpression); if (l.Type == Type.Boolean && ((ScriptBoolean)l).Value) return new ScriptBoolean(true); var r = EvaluateExpression(e.RightExpression); if (r.Type == Type.Boolean && ((ScriptBoolean)r).Value) return new ScriptBoolean(true); return new ScriptBoolean(false); } if (e.Operator.Kind == BoundBinaryOperator.OperatorKind.And) { var l = EvaluateExpression(e.LeftExpression); if (l.Type != Type.Boolean || !((ScriptBoolean)l).Value) return new ScriptBoolean(false); var r = EvaluateExpression(e.RightExpression); if (r.Type != Type.Boolean || !((ScriptBoolean)r).Value) return new ScriptBoolean(false); return new ScriptBoolean(true); } var left = EvaluateExpression(e.LeftExpression); var right = EvaluateExpression(e.RightExpression); switch (e.Operator.Kind) { case BoundBinaryOperator.OperatorKind.Addition: if (left.Type == Type.Number) { if (right.Type == Type.Number) { return ((ScriptNumber)left) + ((ScriptNumber)right); } } else if (left.Type == Type.String) { return ((ScriptString) 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 && right.Type == Type.Number) { return ((ScriptNumber)left) - ((ScriptNumber)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 && right.Type == Type.Number) { return ((ScriptNumber)left) * ((ScriptNumber)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 && right.Type == Type.Number) { return ((ScriptNumber)left) / ((ScriptNumber)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.Exponent: if (left.Type == Type.Number && right.Type == Type.Number) { return ScriptNumber.Exponent((ScriptNumber) left, (ScriptNumber) right); } goto default; case BoundBinaryOperator.OperatorKind.Remainder: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber)left) % ((ScriptNumber)right); } goto default; case BoundBinaryOperator.OperatorKind.Equality: return new ScriptBoolean(left.Equals(right)); case BoundBinaryOperator.OperatorKind.Inequality: return new ScriptBoolean(!left.Equals(right)); case BoundBinaryOperator.OperatorKind.Less: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber)left) < ((ScriptNumber)right); } ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); return new ScriptNull(); case BoundBinaryOperator.OperatorKind.LessEquals: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber)left) <= ((ScriptNumber)right); } ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); return new ScriptNull(); case BoundBinaryOperator.OperatorKind.Greater: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber)left) > ((ScriptNumber)right); } ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); return new ScriptNull(); case BoundBinaryOperator.OperatorKind.GreaterEquals: if (left.Type == Type.Number && right.Type == Type.Number) { return ((ScriptNumber)left) >= ((ScriptNumber)right); } ThrowException($"Can't find operator for types '{left.Type}' and '{right.Type}'", e.Span); return new ScriptNull(); default: throw new Exception("Invalid Binary Operator: " + e.Operator.Kind); } } private void ThrowException(string message, TextSpan location) { throw new ScriptRuntimeException(message, location, _diagnostics.ScriptString.GetSpan(location)); } private void EvaluateAssignmentStatement(BoundVariableAssignment e) { var val = EvaluateExpression(e.BoundExpression); if (e.IsLocalDefinition) { Scope.CreateLocal(e.Variable.VariableSymbol, val); } else { Scope.AssignToNearest(e.Variable.VariableSymbol, val); } _lastValue = val; } private void EvaluateMultiAssignmentStatement(BoundMultiAssignmentStatement e) { var val = EvaluateExpression(e.Assignment); if (val is IIndexable table) { 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 ScriptNumberLong(i + 1), Scope); if (variableSymbol.Local) Scope.CreateLocal(variableSymbol, value); else Scope.AssignToNearest(variableSymbol, value); } } else { _diagnostics.LogError($"Can't assign type '{val.Type}' to multiple variables.", e.Span); } _lastValue = val; } private ScriptType EvaluateVariableExpression(BoundVariableExpression e) { if (Scope.TryGet(e.Variable.VariableSymbol, out var val)) { return val; } throw new Exception($"Cannot find variable: '{e.Variable.VariableSymbol.Name}'"); } private readonly Stack _todoStatements = new Stack(); private void EvaluateBoundBlockStatement(BoundBlockStatement boundBlockStatement) { for (var index = boundBlockStatement.Statements.Length - 1; index >= 0; index--) { var s = boundBlockStatement.Statements[index]; _todoStatements.Push(s); } while (true) { if (DebugSession.Debugging) { Thread.Sleep(10); continue; } if (HasReturned) return; if (HasBroken) return; if (_todoStatements.Count == 0) return; var boundStatement = _todoStatements.Pop(); if (DebugSession.DebuggerAttached && boundStatement.HasBreakpoint) { DebugSession.TriggerBreakpoint(Scope); continue; } EvaluateStatement(boundStatement); } } private void EvaluateBoundIfStatement(BoundIfStatement boundBlockStatement) { var condition = EvaluateExpression(boundBlockStatement.Condition.Expression); if ((ScriptBoolean) condition) { EvaluateStatement(boundBlockStatement.Block); } else if (boundBlockStatement.NextElseIf != null) { EvaluateStatement(boundBlockStatement.NextElseIf); } else if (boundBlockStatement.ElseStatement != null) { EvaluateStatement(boundBlockStatement.ElseStatement.Block); } } private void EvaluateBoundFunctionAssigmentStatement(BoundFunctionAssignmentStatement e) { var func = EvaluateBoundFunctionStatement(e.Func); if (e.Variable.Local) Scope.CreateLocal(e.Variable, func); else Scope.AssignToNearest(e.Variable, func); } private ScriptType EvaluateBoundFunctionStatement(BoundFunctionExpression boundFunctionExpression) { var func = new ScriptRuntimeFunction(boundFunctionExpression.Parameters, boundFunctionExpression.Block, Scope); return func; } private ScriptType EvaluateUnboundFunctionStatement(UnboundFunctionExpression unboundFunctionExpression) { var func = new ScriptRuntimeFunction(unboundFunctionExpression.Parameters, unboundFunctionExpression.Block, Scope); return func; } private ScriptType EvaluateBoundFunctionCallExpression(BoundFunctionCallExpression boundFunctionCallExpression) { var variable = EvaluateExpression(boundFunctionCallExpression.Identifier); if (!(variable is ScriptFunction 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(), _script, Scope, boundFunctionCallExpression.Span); return val; } private void EvaluateReturnStatement(BoundReturnStatement b) { _returnValue = b.Expression == null ? null : EvaluateExpression(b.Expression); _lastValue = _returnValue; HasReturned = true; } private ScriptType EvaluateTableExpression(BoundTableExpression e) { var tableScope = EvaluationScope.CreateWithGetOnlyParent(Scope); var innerEvaluator = new Evaluator(_diagnostics, tableScope, _script); var currentPos = 1; var isNumerated = true; foreach (var boundStatement in e.Statements) { switch (boundStatement.Kind) { case BoundKind.BoundAssignmentStatement: case BoundKind.BoundFunctionExpression: case BoundKind.BoundFunctionAssignmentStatement: innerEvaluator.EvaluateNode(boundStatement); isNumerated = false; break; default: { innerEvaluator.EvaluateStatement(boundStatement); if (innerEvaluator._lastValue != null) tableScope.CreateLocal(new VariableSymbol(currentPos.ToString(), innerEvaluator._lastValue.Type, false), innerEvaluator._lastValue); innerEvaluator._lastValue = null; break; } } currentPos++; } if (isNumerated) { return new NumeratedScriptTable(tableScope); } return new GenericKeyedScriptTable(tableScope); } private ScriptType 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 ScriptType 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.ToScriptType(), scope); } private void EvaluateTableAssignmentStatement(BoundTableAssigmentStatement e) { ScriptType table; ScriptType 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.ToScriptType(); } var value = EvaluateExpression(e.Value); if (!(table is IIndexable indexable)) { throw new Exception("Cant assign to that"); } indexable.Set(_diagnostics, e.Span, index.ToString().ToScriptType(), value); } private void EvaluateNumericForStatement(BoundNumericForStatement e) { var innerEvaluator = new Evaluator(_diagnostics, Scope, _script); var startVal = (ScriptNumberLong)innerEvaluator.EvaluateExpression(e.BoundStart); innerEvaluator.Scope.CreateLocal(e.Variable, startVal); var stopVal = (ScriptNumberLong)innerEvaluator.EvaluateExpression(e.BoundStop); long step = 1; if (e.BoundStep != null) { var stepVal = (ScriptNumberLong)innerEvaluator.EvaluateExpression(e.BoundStep); step = stepVal.Value; } if (step > 0) { for (; startVal.Value <= stopVal.Value; startVal.Value = startVal.Value + step) { innerEvaluator.EvaluateBoundBlockStatement(e.Block); if (innerEvaluator.HasBroken) break; } } else if (step < 0) { for (; startVal.Value >= stopVal.Value; startVal.Value = startVal.Value + step) { innerEvaluator.EvaluateBoundBlockStatement(e.Block); if (innerEvaluator.HasBroken) break; } } } private void EvaluateGenericForStatement(BoundGenericForStatement e) { var innerEvaluator = new Evaluator(_diagnostics, Scope, _script); var enumeratorObject = EvaluateExpression(e.BoundEnumerableExpression); if (!(enumeratorObject is IIterable iterable)) { throw new Exception($"Can't iterate over object with type '{enumeratorObject.Type}'"); } using (var enumerator = iterable.GetScriptEnumerator()) { while (enumerator.MoveNext()) { var current = enumerator.Current; if (current == null) { throw new Exception($"Can't assign result value of nothing to multiple values"); } if (current.Type != Type.Table) { throw new Exception($"Can't assign result value with type '{current.Type}' to multiple values"); } var table = (SimpleScriptTable)current; if (e.Variables[0].Name != "_") innerEvaluator.Scope.CreateLocal(e.Variables[0], table[0].ToScriptType()); if (e.Variables[1].Name != "_") innerEvaluator.Scope.CreateLocal(e.Variables[1], table[1]); innerEvaluator.EvaluateBoundBlockStatement((BoundBlockStatement) e.Block); if (innerEvaluator.HasBroken || innerEvaluator.HasReturned) { if (innerEvaluator.HasReturned) { HasReturned = innerEvaluator.HasReturned; _returnValue = innerEvaluator._returnValue; } break; } } } } private void EvaluateWhileStatement(BoundWhileStatement e) { var innerEvaluator = new Evaluator(_diagnostics, Scope, _script); var block = (BoundBlockStatement) e.Block; while ((ScriptBoolean)innerEvaluator.EvaluateExpression(e.Condition)) { innerEvaluator.EvaluateBoundBlockStatement(block); if (innerEvaluator.HasBroken || innerEvaluator.HasReturned) { if (innerEvaluator.HasReturned) { HasReturned = innerEvaluator.HasReturned; _returnValue = innerEvaluator._returnValue; } break; } } } } }