From b475bd449515a8fe7697d3253c2926d278898d3c Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Wed, 13 Feb 2019 18:10:39 +0100 Subject: [PATCH] Added support for coroutines --- .../ScriptFunction/ScriptFunction.cs | 2 + .../ScriptMethodInfoFunction.cs | 32 ++- .../ScriptFunction/ScriptRuntimeFunction.cs | 42 +++- Upsilon/Binder/Binder.cs | 13 +- .../BoundFunctionExpression.cs | 4 +- Upsilon/Binder/BoundKind.cs | 1 + .../BoundStatements/BoundBlockStatement.cs | 17 ++ .../BoundStatements/BoundBreakStatement.cs | 7 + .../BoundExpressionStatement.cs | 8 + .../BoundFunctionAssignmentStatement.cs | 7 + .../BoundGenericForStatement.cs | 53 +++++ .../BoundStatements/BoundIfStatement.cs | 21 ++ .../BoundMultiAssignmentStatement.cs | 7 + .../BoundNumericForStatement.cs | 59 ++++++ .../BoundStatements/BoundReturnStatement.cs | 8 + Upsilon/Binder/BoundStatements/BoundScript.cs | 6 + .../Binder/BoundStatements/BoundStatement.cs | 4 + .../BoundTableAssigmentStatement.cs | 7 + .../BoundVariableAssignment.cs | 7 + .../BoundStatements/BoundWhileStatement.cs | 29 +++ .../BoundStatements/BoundYieldStatement.cs | 62 ++++++ .../UnboundFunctionExpression.cs | 2 +- .../VariableSymbols/FunctionVariableSymbol.cs | 6 +- .../InternalFunctionVariableSymbol.cs | 9 +- .../ScriptFunctionVariableSymbol.cs | 5 +- Upsilon/Evaluator/EvaluationState.cs | 2 +- Upsilon/Evaluator/Evaluator.cs | 24 +++ Upsilon/Evaluator/Script.cs | 13 ++ Upsilon/Executor.cs | 7 + .../FunctionExpressionSyntax.cs | 4 +- Upsilon/Parser/Parser.cs | 46 ++++- .../StatementSyntax/YieldStatementSyntax.cs | 23 +++ Upsilon/Parser/SyntaxKeyWords.cs | 4 + Upsilon/Parser/SyntaxKind.cs | 5 +- Upsilon/StandardLibraries/StaticScope.cs | 7 +- UpsilonTests/GeneralTests/CoroutineTests.cs | 185 ++++++++++++++++++ 36 files changed, 703 insertions(+), 35 deletions(-) create mode 100644 Upsilon/Binder/BoundStatements/BoundYieldStatement.cs create mode 100644 Upsilon/Parser/StatementSyntax/YieldStatementSyntax.cs create mode 100644 UpsilonTests/GeneralTests/CoroutineTests.cs diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs index ffec4a7..2df7038 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptFunction.cs @@ -1,3 +1,4 @@ +using System.Collections; using Upsilon.Evaluator; using Upsilon.Text; @@ -17,5 +18,6 @@ namespace Upsilon.BaseTypes.ScriptFunction } public abstract ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span); + public abstract IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span); } } \ No newline at end of file diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs index 4597aee..34387c8 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptMethodInfoFunction.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -47,7 +48,31 @@ namespace Upsilon.BaseTypes.ScriptFunction public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) { - var objects = new List(); + var result = Execute(diagnostics, variables, script, scope, span); + if (_directTypeManipulation) + return (ScriptType)result; + return result.ToScriptType(); + + } + + public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, + TextSpan span) + { + var result = Execute(diagnostics, variables, script, scope, span); + if (result is IEnumerator enumerator) + { + while (enumerator.MoveNext()) + { + yield return enumerator.Current; + } + yield break; + } + yield return result; + } + + private object Execute(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) + { + var objects = new List(); if (_passScriptReference) objects.Add(script); if (_passScopeReference) @@ -129,10 +154,7 @@ namespace Upsilon.BaseTypes.ScriptFunction if (e.InnerException != null) throw e.InnerException; throw; } - if (_directTypeManipulation) - return (ScriptType)result; - return result.ToScriptType(); - + return result; } } } \ No newline at end of file diff --git a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs index e1c087d..35ee8f8 100644 --- a/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs +++ b/Upsilon/BaseTypes/ScriptFunction/ScriptRuntimeFunction.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -33,6 +33,22 @@ namespace Upsilon.BaseTypes.ScriptFunction return option.Run(diagnostics, variables, script, scope, span); } + public override IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, + TextSpan span) + { + var option = GetValidOption(variables); + if (option == null) + { + throw new EvaluationException(script.FileName, + $"No valid function found", span); + } + var coroutine = option.RunCoroutine(diagnostics, variables, script, scope, span); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + } + public ScriptRuntimeFunctionOption GetValidOption(object[] variables) { if (variables == null) @@ -163,7 +179,7 @@ namespace Upsilon.BaseTypes.ScriptFunction EvaluationScope = scope; } - public EvaluationScope EvaluationScope { get; set; } + public EvaluationScope EvaluationScope { get; private set; } public BoundBlockStatement Block { get; } public ImmutableArray Parameters { get; } @@ -188,6 +204,28 @@ namespace Upsilon.BaseTypes.ScriptFunction Block.Evaluate(innerScope, diagnostics, ref state); return state.ReturnValue; } + + public IEnumerator RunCoroutine(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope outerScope, TextSpan span) + { + var innerScope = new EvaluationScope(EvaluationScope); + var state = new EvaluationState() + { + Script = script, + }; + if (Parameters != null) + { + for (var i = 0; i < Parameters.Length; i++) + { + var parameterVariable = Parameters[i]; + var parameterValue = variables[i]; + innerScope.CreateLocal(parameterVariable.VariableSymbol, parameterValue); + } + } + + return Block.EvaluateCoroutine(innerScope, diagnostics, state); + } + + } } } \ No newline at end of file diff --git a/Upsilon/Binder/Binder.cs b/Upsilon/Binder/Binder.cs index 999a8ff..cfb58bc 100644 --- a/Upsilon/Binder/Binder.cs +++ b/Upsilon/Binder/Binder.cs @@ -92,7 +92,8 @@ namespace Upsilon.Binder return BindGenericForStatement((GenericForStatementSyntax) s); case SyntaxKind.WhileStatement: return BindWhileStatement((WhileStatementSyntax) s); - + case SyntaxKind.YieldStatement: + return BindYieldStatement((YieldStatementSyntax) s); case SyntaxKind.BreakStatement: return new BoundBreakStatement(s.Span); } @@ -685,7 +686,7 @@ namespace Upsilon.Binder var returnType = Scope.ReturnType; Scope = Scope.ParentScope; var func = new BoundFunctionExpression(parameters.ToImmutable(), (BoundBlockStatement) block, e.Span, - innerScope, returnType); + innerScope, returnType, e.IsCoroutine); return func; } else @@ -719,7 +720,8 @@ namespace Upsilon.Binder if (!Scope.TryGetVariable(name, !isLocal, out var variable)) { - var functionVariable = new ScriptFunctionVariableSymbol(name, isLocal, parameters.ToImmutable(), func.ReturnType) + var functionVariable = new ScriptFunctionVariableSymbol(name, isLocal, parameters.ToImmutable(), func.ReturnType + , func.IsCoroutine) { CommentValue = commentData.ToArray() }; @@ -1072,5 +1074,10 @@ namespace Upsilon.Binder return new BoundWhileStatement(condition, block, e.Span); } + private BoundYieldStatement BindYieldStatement(YieldStatementSyntax yieldStatementSyntax) + { + var expression = BindExpression(yieldStatementSyntax.Expression); + return new BoundYieldStatement(expression, yieldStatementSyntax.Span); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs index 9f59955..1931185 100644 --- a/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs +++ b/Upsilon/Binder/BoundExpressions/BoundFunctionExpression.cs @@ -13,14 +13,16 @@ namespace Upsilon.Binder { public ImmutableArray Parameters { get; } public BoundBlockStatement Block { get; set; } + public bool IsCoroutine { get; } public BoundFunctionExpression(ImmutableArray parameters, BoundBlockStatement block, - TextSpan span, BoundScope scope, Type returnType) : base(span) + TextSpan span, BoundScope scope, Type returnType, bool isCoroutine) : base(span) { Parameters = parameters; Block = block; Scope = scope; ReturnType = returnType; + IsCoroutine = isCoroutine; } public override BoundKind Kind => BoundKind.BoundFunctionExpression; diff --git a/Upsilon/Binder/BoundKind.cs b/Upsilon/Binder/BoundKind.cs index 2fdb4ed..ebad1ff 100644 --- a/Upsilon/Binder/BoundKind.cs +++ b/Upsilon/Binder/BoundKind.cs @@ -31,5 +31,6 @@ namespace Upsilon.Binder BoundGenericForStatement, BoundBreakStatement, BoundWhileStatement, + BoundYieldStatement } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundBlockStatement.cs b/Upsilon/Binder/BoundStatements/BoundBlockStatement.cs index 2c59126..9b439da 100644 --- a/Upsilon/Binder/BoundStatements/BoundBlockStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundBlockStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using Upsilon.Evaluator; @@ -32,5 +33,21 @@ namespace Upsilon.Binder return; } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + foreach (var statement in Statements) + { + var coroutine = statement.EvaluateCoroutine(scope, diagnostics, state); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + if (state.Returned) + yield break; + if (state.HasBroken) + yield break; + } + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundBreakStatement.cs b/Upsilon/Binder/BoundStatements/BoundBreakStatement.cs index 970cdfd..07901f9 100644 --- a/Upsilon/Binder/BoundStatements/BoundBreakStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundBreakStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.Evaluator; using Upsilon.Text; @@ -21,5 +22,11 @@ namespace Upsilon.Binder { state.HasBroken = true; } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + state.HasBroken = true; + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundExpressionStatement.cs b/Upsilon/Binder/BoundStatements/BoundExpressionStatement.cs index 5145bcb..242bcf6 100644 --- a/Upsilon/Binder/BoundStatements/BoundExpressionStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundExpressionStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.Evaluator; using Upsilon.Text; @@ -24,5 +25,12 @@ namespace Upsilon.Binder var value = Expression.Evaluate(scope, diagnostics, ref state); state.LastValue = value; } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + var value = Expression.Evaluate(scope, diagnostics, ref state); + state.LastValue = value; + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs index e1fcd51..c4050a3 100644 --- a/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundFunctionAssignmentStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.BaseTypes.ScriptFunction; using Upsilon.Binder.VariableSymbols; @@ -51,5 +52,11 @@ namespace Upsilon.Binder } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + Evaluate(scope, diagnostics, ref state); + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundGenericForStatement.cs b/Upsilon/Binder/BoundStatements/BoundGenericForStatement.cs index ac954a0..409f264 100644 --- a/Upsilon/Binder/BoundStatements/BoundGenericForStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundGenericForStatement.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using Upsilon.BaseTypes; @@ -81,5 +82,57 @@ namespace Upsilon.Binder } } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope outerScope, Diagnostics diagnostics, EvaluationState outerState) + { + var innerScope = new EvaluationScope(outerScope); + var innerState = new EvaluationState() + { + Script = outerState.Script, + }; + + var enumeratorObject = BoundEnumerableExpression.Evaluate(outerScope, diagnostics, ref innerState); + 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 != BaseTypes.Type.Table) + { + throw new Exception($"Can't assign result value with type '{current.Type}' to multiple values"); + } + + var table = (SimpleScriptTable)current; + if (Variables[0].VariableSymbol.Name != "_") + innerScope.CreateLocal(Variables[0].VariableSymbol, table[0].ToScriptType()); + if (Variables[1].VariableSymbol.Name != "_") + innerScope.CreateLocal(Variables[1].VariableSymbol, table[1]); + + var coroutine = Block.EvaluateCoroutine(innerScope, diagnostics, innerState); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + if (innerState.HasBroken || innerState.Returned) + { + if (innerState.Returned) + { + outerState.Returned = true; + outerState.ReturnValue = innerState.ReturnValue; + } + break; + } + } + } + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundIfStatement.cs b/Upsilon/Binder/BoundStatements/BoundIfStatement.cs index f5b9897..afe3dc0 100644 --- a/Upsilon/Binder/BoundStatements/BoundIfStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundIfStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.BaseTypes; using Upsilon.Evaluator; @@ -57,6 +58,21 @@ namespace Upsilon.Binder ElseStatement?.Evaluate(scope, diagnostics, ref state); } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + var condition = Condition.Expression.Evaluate(scope, diagnostics, ref state); + if ((ScriptBoolean) condition) + { + return Block.EvaluateCoroutine(scope, diagnostics, state); + } + if (NextElseIf != null) + { + return NextElseIf.EvaluateCoroutine(scope, diagnostics, state); + } + + return ElseStatement?.EvaluateCoroutine(scope, diagnostics, state); + } } public class BoundElseStatement : BoundStatement @@ -79,5 +95,10 @@ namespace Upsilon.Binder { Block.Evaluate(scope, diagnostics, ref state); } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + return Block.EvaluateCoroutine(scope, diagnostics, state); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs index f3fc1e8..214f54a 100644 --- a/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundMultiAssignmentStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using Upsilon.BaseTypes.Number; @@ -52,5 +53,11 @@ namespace Upsilon.Binder } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + Evaluate(scope, diagnostics, ref state); + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs b/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs index 3716547..62b0e01 100644 --- a/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundNumericForStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.BaseTypes.Number; using Upsilon.Binder.VariableSymbols; @@ -84,5 +85,63 @@ namespace Upsilon.Binder } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope outerScope, Diagnostics diagnostics, EvaluationState outerState) + { + var innerScope = new EvaluationScope(outerScope); + var innerState = new EvaluationState() + { + Script = outerState.Script, + }; + + var startVal = (ScriptNumberLong) BoundStart.Evaluate(innerScope, diagnostics, ref innerState); + innerScope.CreateLocal(Variable, startVal); + var stopVal = (ScriptNumberLong)BoundStop.Evaluate(innerScope, diagnostics, ref innerState); + long step = 1; + if (BoundStep != null) + { + var stepVal = (ScriptNumberLong) BoundStep.Evaluate(innerScope, diagnostics, ref innerState); + step = stepVal.Value; + } + + if (step > 0) + { + for (; startVal.Value <= stopVal.Value; startVal.Value = startVal.Value + step) + { + var coroutine = Block.EvaluateCoroutine(innerScope, diagnostics, innerState); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + if (innerState.HasBroken) + break; + if (innerState.Returned) + { + outerState.Returned = true; + outerState.ReturnValue = innerState.ReturnValue; + yield break; + } + } + } + else if (step < 0) + { + for (; startVal.Value >= stopVal.Value; startVal.Value = startVal.Value + step) + { + var coroutine = Block.EvaluateCoroutine(innerScope, diagnostics, innerState); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + if (innerState.HasBroken) + break; + if (innerState.Returned) + { + outerState.Returned = true; + outerState.ReturnValue = innerState.ReturnValue; + yield break; + } + } + } + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs b/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs index 46ff7af..f3b46d5 100644 --- a/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundReturnStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.Evaluator; using Upsilon.Text; @@ -25,5 +26,12 @@ namespace Upsilon.Binder state.Returned = true; } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + state.ReturnValue = Expression?.Evaluate(scope, diagnostics, ref state); + state.Returned = true; + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundScript.cs b/Upsilon/Binder/BoundStatements/BoundScript.cs index 2646257..41ebe48 100644 --- a/Upsilon/Binder/BoundStatements/BoundScript.cs +++ b/Upsilon/Binder/BoundStatements/BoundScript.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.Evaluator; using Upsilon.Text; @@ -31,5 +32,10 @@ namespace Upsilon.Binder { Statement.Evaluate(scope, diagnostics, ref state); } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + return Statement.EvaluateCoroutine(scope, diagnostics, state); + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundStatement.cs b/Upsilon/Binder/BoundStatements/BoundStatement.cs index c9c59fd..f6b1885 100644 --- a/Upsilon/Binder/BoundStatements/BoundStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using Upsilon.Evaluator; using Upsilon.Text; @@ -12,5 +13,8 @@ namespace Upsilon.Binder internal bool HasBreakpoint { get; set; } internal abstract void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state); + + internal abstract IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, + EvaluationState state); } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs b/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs index a2a9f0a..6078f94 100644 --- a/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundTableAssigmentStatement.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using Upsilon.BaseTypes; using Upsilon.BaseTypes.ScriptTypeInterfaces; @@ -51,5 +52,11 @@ namespace Upsilon.Binder } indexable.Set(diagnostics, Span, index.ToString().ToScriptType(), value); } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + Evaluate(scope, diagnostics, ref state); + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundVariableAssignment.cs b/Upsilon/Binder/BoundStatements/BoundVariableAssignment.cs index 104d7fa..3aa3ad9 100644 --- a/Upsilon/Binder/BoundStatements/BoundVariableAssignment.cs +++ b/Upsilon/Binder/BoundStatements/BoundVariableAssignment.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.Evaluator; using Upsilon.Text; @@ -38,5 +39,11 @@ namespace Upsilon.Binder } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + Evaluate(scope, diagnostics, ref state); + yield break; + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundWhileStatement.cs b/Upsilon/Binder/BoundStatements/BoundWhileStatement.cs index 6b261ab..c886324 100644 --- a/Upsilon/Binder/BoundStatements/BoundWhileStatement.cs +++ b/Upsilon/Binder/BoundStatements/BoundWhileStatement.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Collections.Generic; using Upsilon.BaseTypes; using Upsilon.Evaluator; @@ -49,5 +50,33 @@ namespace Upsilon.Binder } } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope outerScope, Diagnostics diagnostics, EvaluationState outerState) + { + var innerScope = new EvaluationScope(outerScope); + var innerState = new EvaluationState() + { + Script = outerState.Script + }; + + + while ((ScriptBoolean) Condition.Evaluate(innerScope, diagnostics, ref innerState)) + { + var coroutine = Block.EvaluateCoroutine(innerScope, diagnostics, innerState); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + if (innerState.HasBroken || innerState.Returned) + { + if (innerState.Returned) + { + outerState.Returned = true; + outerState.ReturnValue = innerState.ReturnValue; + } + break; + } + } + } } } \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs b/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs new file mode 100644 index 0000000..18912e2 --- /dev/null +++ b/Upsilon/Binder/BoundStatements/BoundYieldStatement.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Upsilon.BaseTypes; +using Upsilon.BaseTypes.ScriptFunction; +using Upsilon.Evaluator; +using Upsilon.Exceptions; +using Upsilon.Text; +using Type = Upsilon.BaseTypes.Type; + +namespace Upsilon.Binder +{ + public class BoundYieldStatement : BoundStatement + { + public BoundExpression Expression { get; } + + public BoundYieldStatement(BoundExpression expression, TextSpan span) : base(span) + { + Expression = expression; + } + + public override BoundKind Kind => BoundKind.BoundYieldStatement; + public override IEnumerable GetChildren() + { + yield return Expression; + } + + internal override void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state) + { + throw new ScriptRuntimeException(state.Script.FileName, + "Yielding in a function that's not executed as a coroutine is not possible.", Span, + diagnostics.ScriptString.GetLine(Span.StartLine)); + } + + internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state) + { + if (Expression.Kind == BoundKind.BoundFunctionCallExpression) + { + var functionCall = (BoundFunctionCallExpression) Expression; + var variable = functionCall.Identifier.Evaluate(scope, diagnostics, ref state); + if (!(variable is ScriptFunction function)) + { + throw new EvaluationException(state.Script.FileName, $"Variable is not a function.", functionCall.Identifier.Span); + } + var ls = new List(); + foreach (var t in functionCall.Parameters) + { + var evaluate = t.Evaluate(scope, diagnostics, ref state); + ls.Add(evaluate); + } + var coroutine = function.RunCoroutine(diagnostics, ls.ToArray(), state.Script, scope, Span); + while (coroutine.MoveNext()) + { + yield return coroutine.Current; + } + yield break; + } + var value = Expression.Evaluate(scope, diagnostics, ref state); + yield return value; + } + } +} \ No newline at end of file diff --git a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs index ab9ba58..d4f7b1e 100644 --- a/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs +++ b/Upsilon/Binder/BoundStatements/UnboundFunctionExpression.cs @@ -10,7 +10,7 @@ namespace Upsilon.Binder { public UnboundFunctionExpression(ImmutableArray parameters, BlockStatementSyntax unboundBlock, TextSpan span, BoundScope scope, string name) - : base(parameters, null, span, scope, BaseTypes.Type.Unknown) + : base(parameters, null, span, scope, BaseTypes.Type.Unknown, false) { UnboundBlock = unboundBlock; Name = name; diff --git a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs index 3d81545..d41571e 100644 --- a/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/FunctionVariableSymbol.cs @@ -5,13 +5,15 @@ using Upsilon.BaseTypes; namespace Upsilon.Binder.VariableSymbols { - public abstract class FunctionVariableSymbol : VariableSymbol + public abstract class FunctionVariableSymbol : VariableSymbol { public List FunctionOption { get; protected set; } = new List(); + public bool IsCoroutine { get; } - public FunctionVariableSymbol(string name, bool local, Type resultType) + public FunctionVariableSymbol(string name, bool local, bool isCoroutine) : base(name, Type.Function, local) { + IsCoroutine = isCoroutine; } public abstract bool ValidateParameters( diff --git a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs index 68540f9..a394494 100644 --- a/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/InternalFunctionVariableSymbol.cs @@ -11,14 +11,15 @@ namespace Upsilon.Binder.VariableSymbols public class InternalFunctionVariableSymbol : FunctionVariableSymbol { public InternalFunctionVariableSymbol(string name, bool local, TypeContainer resultType, - InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType) - : base(name, local, resultType) + InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType, bool isCoroutine) + : base(name, local, isCoroutine) { FunctionOption.Add(new InternalFunctionVariableOption(resultType, functionParameters, overrideResultType)); } - public InternalFunctionVariableSymbol(string name, bool local, Type resultType, List options) - : base(name, local, resultType) + public InternalFunctionVariableSymbol(string name, bool local, List options, + bool isCoroutine) + : base(name, local, isCoroutine) { FunctionOption = options; } diff --git a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs index b689c4d..7b65b2d 100644 --- a/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs +++ b/Upsilon/Binder/VariableSymbols/ScriptFunctionVariableSymbol.cs @@ -7,8 +7,9 @@ namespace Upsilon.Binder.VariableSymbols { public class ScriptFunctionVariableSymbol : FunctionVariableSymbol { - public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray parameters, Type resultType) - : base(name, local, resultType) + public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray parameters, Type resultType, + bool isCoroutine) + : base(name, local, isCoroutine) { FunctionOption.Add(new ScriptFunctionVariableOption(resultType, parameters)); } diff --git a/Upsilon/Evaluator/EvaluationState.cs b/Upsilon/Evaluator/EvaluationState.cs index 90af54b..195b2ad 100644 --- a/Upsilon/Evaluator/EvaluationState.cs +++ b/Upsilon/Evaluator/EvaluationState.cs @@ -2,7 +2,7 @@ using Upsilon.BaseTypes; namespace Upsilon.Evaluator { - internal struct EvaluationState + internal class EvaluationState { public Script Script; public bool Returned; diff --git a/Upsilon/Evaluator/Evaluator.cs b/Upsilon/Evaluator/Evaluator.cs index c624e20..fcf2189 100644 --- a/Upsilon/Evaluator/Evaluator.cs +++ b/Upsilon/Evaluator/Evaluator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Linq; using Upsilon.BaseTypes; using Upsilon.BaseTypes.ScriptFunction; @@ -82,5 +83,28 @@ namespace Upsilon.Evaluator var result = option.Run(_diagnostics, parameters?.Select(x => x.ToScriptType()).ToArray(), _script, Scope, new TextSpan()); return result; } + + public IEnumerator EvaluateCoroutine(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 option = function.GetValidOption(parameters); + if (option == null) + { + if (parameters == null) + { + throw new EvaluationException(_script.FileName, + $"No function found with name '{functionName}' and no parameters.", + e.Span); + } + throw new EvaluationException(_script.FileName, + $"No function found with name '{functionName}' and available parameters {string.Join(", ", parameters.Select(x => $"{x.GetType().Name}"))}", + e.Span); + } + return option.RunCoroutine(_diagnostics, parameters?.Select(x => x.ToScriptType()).ToArray(), _script, Scope, new TextSpan()); + } } } \ No newline at end of file diff --git a/Upsilon/Evaluator/Script.cs b/Upsilon/Evaluator/Script.cs index 2818ba1..14f477d 100644 --- a/Upsilon/Evaluator/Script.cs +++ b/Upsilon/Evaluator/Script.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using Upsilon.BaseTypes; @@ -114,6 +115,18 @@ namespace Upsilon.Evaluator return Convert(Evaluator.Evaluate(Bind(), functionName, parameters)); } + public IEnumerator EvaluateFunctionCoroutine(string functionName, object[] parameters = null) + { + var coroutine = Evaluator.EvaluateCoroutine(Bind(), functionName, parameters); + while (coroutine.MoveNext()) + { + var current = coroutine.Current; + if (current is ScriptType scriptType) + yield return scriptType.ToCSharpObject(); + yield return current; + } + } + private static T Convert(ScriptType t) { var result = UpsilonBinder.Default.ChangeType(t, typeof(T), CultureInfo.InvariantCulture); diff --git a/Upsilon/Executor.cs b/Upsilon/Executor.cs index a361573..cb3cba8 100644 --- a/Upsilon/Executor.cs +++ b/Upsilon/Executor.cs @@ -1,3 +1,4 @@ +using System.Collections; using Upsilon.Binder; using Upsilon.Evaluator; @@ -89,5 +90,11 @@ namespace Upsilon var script = ParseInputAndEvaluate(input, options); return script.EvaluateFunction(function, parameters); } + + public static IEnumerator EvaluateFunctionCoroutine(string input, string function, object[] parameters = null, ScriptOptions options = null) + { + var script = ParseInputAndEvaluate(input, options); + return script.EvaluateFunctionCoroutine(function, parameters); + } } } \ No newline at end of file diff --git a/Upsilon/Parser/ExpressionSyntax/FunctionExpressionSyntax.cs b/Upsilon/Parser/ExpressionSyntax/FunctionExpressionSyntax.cs index e5cc313..d4e4768 100644 --- a/Upsilon/Parser/ExpressionSyntax/FunctionExpressionSyntax.cs +++ b/Upsilon/Parser/ExpressionSyntax/FunctionExpressionSyntax.cs @@ -12,10 +12,11 @@ namespace Upsilon.Parser public SyntaxToken CloseParenthesis { get; } public BlockStatementSyntax Block { get; } public SyntaxToken EndToken { get; } + public bool IsCoroutine { get; } public FunctionExpressionSyntax(SyntaxToken functionToken, SyntaxToken openParenthesis, ImmutableArray parameters, SyntaxToken closeParenthesis, - BlockStatementSyntax block, SyntaxToken endToken) + BlockStatementSyntax block, SyntaxToken endToken, bool isCoroutine) { FunctionToken = functionToken; OpenParenthesis = openParenthesis; @@ -23,6 +24,7 @@ namespace Upsilon.Parser CloseParenthesis = closeParenthesis; Block = block; EndToken = endToken; + IsCoroutine = isCoroutine; Span = TextSpan.Between(FunctionToken.Span, endToken.Span); } diff --git a/Upsilon/Parser/Parser.cs b/Upsilon/Parser/Parser.cs index c2ccb97..45fb5d5 100644 --- a/Upsilon/Parser/Parser.cs +++ b/Upsilon/Parser/Parser.cs @@ -79,11 +79,19 @@ namespace Upsilon.Parser } if (Current.Kind == SyntaxKind.FunctionKeyword && Next.Kind != SyntaxKind.OpenParenthesis) { - return ParseFunctionAssignmentStatement(); + return ParseFunctionAssignmentStatement(SyntaxKind.FunctionKeyword); } if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.FunctionKeyword) { - return ParseFunctionAssignmentStatement(); + return ParseFunctionAssignmentStatement(SyntaxKind.FunctionKeyword); + } + if (Current.Kind == SyntaxKind.CoroutineKeyword && Next.Kind != SyntaxKind.OpenParenthesis) + { + return ParseFunctionAssignmentStatement(SyntaxKind.CoroutineKeyword); + } + if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == SyntaxKind.CoroutineKeyword) + { + return ParseFunctionAssignmentStatement(SyntaxKind.CoroutineKeyword); } if (Current.Kind == SyntaxKind.ForKeyword) { @@ -97,6 +105,10 @@ namespace Upsilon.Parser { return new BreakStatementSyntax(NextToken()); } + if (Current.Kind == SyntaxKind.YieldKeyword) + { + return ParseYieldStatement(); + } return ParseExpressionStatement(); } @@ -210,9 +222,9 @@ namespace Upsilon.Parser } - private ExpressionSyntax ParseFunctionExpression() + private ExpressionSyntax ParseFunctionExpression(SyntaxKind openKeyword) { - var functionToken = MatchToken(SyntaxKind.FunctionKeyword); + var functionToken = MatchToken(openKeyword); var openParenthesis = MatchToken(SyntaxKind.OpenParenthesis); var variableBuilder = ImmutableArray.CreateBuilder(); SyntaxToken current = null; @@ -239,11 +251,13 @@ namespace Upsilon.Parser var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword}); var endToken = MatchToken(SyntaxKind.EndKeyword); + + var isCoroutine = openKeyword == SyntaxKind.CoroutineKeyword; return new FunctionExpressionSyntax(functionToken, openParenthesis, - variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken); + variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken, isCoroutine); } - private StatementSyntax ParseFunctionAssignmentStatement() + private StatementSyntax ParseFunctionAssignmentStatement(SyntaxKind identifyingKeyword) { SyntaxToken localToken = null; string[] commentData = null; @@ -252,7 +266,7 @@ namespace Upsilon.Parser localToken = NextToken(); commentData = localToken.CommentData; } - var functionToken = MatchToken(SyntaxKind.FunctionKeyword); + var functionToken = MatchToken(identifyingKeyword); if (commentData == null) { commentData = functionToken.CommentData; @@ -278,8 +292,10 @@ namespace Upsilon.Parser var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword}); var endToken = MatchToken(SyntaxKind.EndKeyword); + + var isCoroutine = identifyingKeyword == SyntaxKind.CoroutineKeyword; var functionExpression = new FunctionExpressionSyntax(functionToken, openParenthesis, - variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken); + variableBuilder.ToImmutable(), closeParenthesis, (BlockStatementSyntax) block, endToken, isCoroutine); return new FunctionAssignmentStatementSyntax(localToken, (IdentifierToken) identifier, functionExpression) { CommentData = commentData @@ -309,12 +325,24 @@ namespace Upsilon.Parser return new ReturnStatementSyntax(returnToken, expression); } + private StatementSyntax ParseYieldStatement() + { + var yieldToken = MatchToken(SyntaxKind.YieldKeyword); + var expression = ParseExpression(); + return new YieldStatementSyntax(yieldToken, expression); + } + + private ExpressionSyntax ParseExpression() { ExpressionSyntax expression; if (Current.Kind == SyntaxKind.FunctionKeyword && Next.Kind == SyntaxKind.OpenParenthesis) { - expression = ParseFunctionExpression(); + expression = ParseFunctionExpression(SyntaxKind.FunctionKeyword); + } + else if (Current.Kind == SyntaxKind.CoroutineKeyword && Next.Kind == SyntaxKind.OpenParenthesis) + { + expression = ParseFunctionExpression(SyntaxKind.FunctionKeyword); } else { diff --git a/Upsilon/Parser/StatementSyntax/YieldStatementSyntax.cs b/Upsilon/Parser/StatementSyntax/YieldStatementSyntax.cs new file mode 100644 index 0000000..bd9c403 --- /dev/null +++ b/Upsilon/Parser/StatementSyntax/YieldStatementSyntax.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace Upsilon.Parser +{ + public class YieldStatementSyntax : StatementSyntax + { + public SyntaxToken YieldToken { get; } + public ExpressionSyntax Expression { get; } + + public YieldStatementSyntax(SyntaxToken yieldToken, ExpressionSyntax expression) + { + YieldToken = yieldToken; + Expression = expression; + } + + public override SyntaxKind Kind => SyntaxKind.YieldStatement; + public override IEnumerable ChildNodes() + { + yield return YieldToken; + yield return Expression; + } + } +} \ No newline at end of file diff --git a/Upsilon/Parser/SyntaxKeyWords.cs b/Upsilon/Parser/SyntaxKeyWords.cs index 0d11134..ca95448 100644 --- a/Upsilon/Parser/SyntaxKeyWords.cs +++ b/Upsilon/Parser/SyntaxKeyWords.cs @@ -44,6 +44,10 @@ namespace Upsilon.Parser return SyntaxKind.DoKeyword; case "break": return SyntaxKind.BreakKeyword; + case "yield": + return SyntaxKind.YieldKeyword; + case "coroutine": + return SyntaxKind.CoroutineKeyword; default: return SyntaxKind.Identifier; } diff --git a/Upsilon/Parser/SyntaxKind.cs b/Upsilon/Parser/SyntaxKind.cs index 9b9520a..9593024 100644 --- a/Upsilon/Parser/SyntaxKind.cs +++ b/Upsilon/Parser/SyntaxKind.cs @@ -56,6 +56,8 @@ namespace Upsilon.Parser InKeyword, DoKeyword, BreakKeyword, + YieldKeyword, + CoroutineKeyword, Identifier, Parameter, @@ -90,6 +92,7 @@ namespace Upsilon.Parser NumericForStatement, BreakStatement, GenericForStatement, - WhileStatement + WhileStatement, + YieldStatement, } } \ No newline at end of file diff --git a/Upsilon/StandardLibraries/StaticScope.cs b/Upsilon/StandardLibraries/StaticScope.cs index 8b64aa7..81414b2 100644 --- a/Upsilon/StandardLibraries/StaticScope.cs +++ b/Upsilon/StandardLibraries/StaticScope.cs @@ -64,7 +64,7 @@ namespace Upsilon.StandardLibraries var derivedType = DeriveValidTypes(typeInfo.Type); return new InternalFunctionVariableSymbol.InternalFunctionParameter(func.Key, derivedType, typeInfo.IsOptional); - }).ToArray(), func.Value.MethodInfoFunction.Method.GetMethods()[0].Attribute.OverrideReturnType) + }).ToArray(), func.Value.MethodInfoFunction.Method.GetMethods()[0].Attribute.OverrideReturnType, false) { CommentValue = func.Value.CommentValue?.Split('\n') }; @@ -139,7 +139,7 @@ namespace Upsilon.StandardLibraries } var result = genericParameters[genericParameters.Length - 1].GetScriptType(); - return new InternalFunctionVariableSymbol(name, true, result, parameters.ToArray(), null); + return new InternalFunctionVariableSymbol(name, true, Type.Nil, parameters.ToArray(), null, false); } private static VariableSymbol BuildActionVariableSymbol(string name, System.Type type) @@ -147,7 +147,8 @@ namespace Upsilon.StandardLibraries var genericParameters = type.GetGenericArguments(); return new InternalFunctionVariableSymbol(name, true, Type.Nil, genericParameters.Select(DeriveValidTypes).Select(t => - new InternalFunctionVariableSymbol.InternalFunctionParameter(name, t, false)).ToArray(), null); + new InternalFunctionVariableSymbol.InternalFunctionParameter(name, t, false)).ToArray(), null, + false); } public static TypeContainer DeriveValidTypes(System.Type type) diff --git a/UpsilonTests/GeneralTests/CoroutineTests.cs b/UpsilonTests/GeneralTests/CoroutineTests.cs new file mode 100644 index 0000000..0c75637 --- /dev/null +++ b/UpsilonTests/GeneralTests/CoroutineTests.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Upsilon; +using Upsilon.BaseTypes.UserData; +using Xunit; + +namespace UpsilonTests.GeneralTests +{ + public class CoroutineTests + { + [Fact] + public void BasicCoroutineTest() + { + const string input = @" +coroutine testCoroutine() + yield 5 + yield 1000 + yield ""test"" + yield nil +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine"); + var count = 0; + while (coroutine.MoveNext()) + { + var value = coroutine.Current; + switch (count) + { + case 0: + Assert.Equal(5L, value); + break; + case 1: + Assert.Equal(1000L, value); + break; + case 2: + Assert.Equal("test", value); + break; + case 3: + Assert.Null(value); + break; + } + count++; + } + Assert.Equal(4, count); + } + + [Fact] + public void EmptyCoroutineTest() + { + const string input = @" +coroutine testCoroutine() +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine"); + var count = 0; + while (coroutine.MoveNext()) + { + count++; + } + Assert.Equal(0, count); + } + + [Fact] + public void NestedCoroutineTest() + { + const string input = @" +coroutine testCoroutine() + for i = 0, 9 do + yield nil + end +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine"); + var count = 0; + var objArray = new List(); + while (coroutine.MoveNext()) + { + objArray.Add(coroutine.Current); + count++; + } + Assert.Equal(10, count); + Assert.All(objArray, Assert.Null); + } + + [Theory] + [InlineData(9)] + [InlineData(20)] + [InlineData(2)] + public void FunctionNestedCoroutine(int max) + { + var input = @" +coroutine lowerCoroutine(number max) + for i = 0, max do + yield nil + end +end + +coroutine testCoroutine() + yield lowerCoroutine(" + max + @") +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine"); + var count = 0; + var objArray = new List(); + while (coroutine.MoveNext()) + { + objArray.Add(coroutine.Current); + count++; + } + Assert.Equal(max + 1, count); + Assert.All(objArray, Assert.Null); + } + + [Fact] + public void CoroutineReturnTest() + { + const string input = @" +coroutine testCoroutine() + yield 5 + yield 1000 + return + yield ""test"" + yield nil +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine"); + var count = 0; + while (coroutine.MoveNext()) + { + var value = coroutine.Current; + switch (count) + { + case 0: + Assert.Equal(5L, value); + break; + case 1: + Assert.Equal(1000L, value); + break; + } + count++; + } + Assert.Equal(2, count); + } + + public class TestClass + { + public IEnumerator TestCoroutine() + { + yield return 5L; + yield return 1000L; + } + } + + [Fact] + public void CSharpCoroutineCallTest() + { + UserDataTypeHandler.LoadType(); + + const string input = @" +coroutine testCoroutine(enumerator) + yield enumerator.TestCoroutine() +end +"; + var coroutine = Executor.EvaluateFunctionCoroutine(input, "testCoroutine", new[] {new TestClass()}); + var count = 0; + while (coroutine.MoveNext()) + { + var value = coroutine.Current; + switch (count) + { + case 0: + Assert.Equal(5L, value); + break; + case 1: + Assert.Equal(1000L, value); + break; + } + count++; + } + Assert.Equal(2, count); + } + + } +} \ No newline at end of file