Added support for coroutines

This commit is contained in:
Deukhoofd 2019-02-13 18:10:39 +01:00
parent 237f2fefd9
commit b475bd4495
No known key found for this signature in database
GPG Key ID: B4C087AC81641654
36 changed files with 703 additions and 35 deletions

View File

@ -1,3 +1,4 @@
using System.Collections;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; 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 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);
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -46,6 +47,30 @@ namespace Upsilon.BaseTypes.ScriptFunction
} }
public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span) public override ScriptType Run(Diagnostics diagnostics, ScriptType[] variables, Script script, EvaluationScope scope, TextSpan span)
{
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<object>(); var objects = new List<object>();
if (_passScriptReference) if (_passScriptReference)
@ -129,10 +154,7 @@ namespace Upsilon.BaseTypes.ScriptFunction
if (e.InnerException != null) throw e.InnerException; if (e.InnerException != null) throw e.InnerException;
throw; throw;
} }
if (_directTypeManipulation) return result;
return (ScriptType)result;
return result.ToScriptType();
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
@ -33,6 +33,22 @@ namespace Upsilon.BaseTypes.ScriptFunction
return option.Run(diagnostics, variables, script, scope, span); 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) public ScriptRuntimeFunctionOption GetValidOption(object[] variables)
{ {
if (variables == null) if (variables == null)
@ -163,7 +179,7 @@ namespace Upsilon.BaseTypes.ScriptFunction
EvaluationScope = scope; EvaluationScope = scope;
} }
public EvaluationScope EvaluationScope { get; set; } public EvaluationScope EvaluationScope { get; private set; }
public BoundBlockStatement Block { get; } public BoundBlockStatement Block { get; }
public ImmutableArray<BoundVariableSymbol> Parameters { get; } public ImmutableArray<BoundVariableSymbol> Parameters { get; }
@ -188,6 +204,28 @@ namespace Upsilon.BaseTypes.ScriptFunction
Block.Evaluate(innerScope, diagnostics, ref state); Block.Evaluate(innerScope, diagnostics, ref state);
return state.ReturnValue; 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);
}
} }
} }
} }

View File

@ -92,7 +92,8 @@ namespace Upsilon.Binder
return BindGenericForStatement((GenericForStatementSyntax) s); return BindGenericForStatement((GenericForStatementSyntax) s);
case SyntaxKind.WhileStatement: case SyntaxKind.WhileStatement:
return BindWhileStatement((WhileStatementSyntax) s); return BindWhileStatement((WhileStatementSyntax) s);
case SyntaxKind.YieldStatement:
return BindYieldStatement((YieldStatementSyntax) s);
case SyntaxKind.BreakStatement: case SyntaxKind.BreakStatement:
return new BoundBreakStatement(s.Span); return new BoundBreakStatement(s.Span);
} }
@ -685,7 +686,7 @@ namespace Upsilon.Binder
var returnType = Scope.ReturnType; var returnType = Scope.ReturnType;
Scope = Scope.ParentScope; Scope = Scope.ParentScope;
var func = new BoundFunctionExpression(parameters.ToImmutable(), (BoundBlockStatement) block, e.Span, var func = new BoundFunctionExpression(parameters.ToImmutable(), (BoundBlockStatement) block, e.Span,
innerScope, returnType); innerScope, returnType, e.IsCoroutine);
return func; return func;
} }
else else
@ -719,7 +720,8 @@ namespace Upsilon.Binder
if (!Scope.TryGetVariable(name, !isLocal, out var variable)) 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() CommentValue = commentData.ToArray()
}; };
@ -1072,5 +1074,10 @@ namespace Upsilon.Binder
return new BoundWhileStatement(condition, block, e.Span); return new BoundWhileStatement(condition, block, e.Span);
} }
private BoundYieldStatement BindYieldStatement(YieldStatementSyntax yieldStatementSyntax)
{
var expression = BindExpression(yieldStatementSyntax.Expression);
return new BoundYieldStatement(expression, yieldStatementSyntax.Span);
}
} }
} }

View File

@ -13,14 +13,16 @@ namespace Upsilon.Binder
{ {
public ImmutableArray<BoundVariableSymbol> Parameters { get; } public ImmutableArray<BoundVariableSymbol> Parameters { get; }
public BoundBlockStatement Block { get; set; } public BoundBlockStatement Block { get; set; }
public bool IsCoroutine { get; }
public BoundFunctionExpression(ImmutableArray<BoundVariableSymbol> parameters, BoundBlockStatement block, public BoundFunctionExpression(ImmutableArray<BoundVariableSymbol> parameters, BoundBlockStatement block,
TextSpan span, BoundScope scope, Type returnType) : base(span) TextSpan span, BoundScope scope, Type returnType, bool isCoroutine) : base(span)
{ {
Parameters = parameters; Parameters = parameters;
Block = block; Block = block;
Scope = scope; Scope = scope;
ReturnType = returnType; ReturnType = returnType;
IsCoroutine = isCoroutine;
} }
public override BoundKind Kind => BoundKind.BoundFunctionExpression; public override BoundKind Kind => BoundKind.BoundFunctionExpression;

View File

@ -31,5 +31,6 @@ namespace Upsilon.Binder
BoundGenericForStatement, BoundGenericForStatement,
BoundBreakStatement, BoundBreakStatement,
BoundWhileStatement, BoundWhileStatement,
BoundYieldStatement
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Upsilon.Evaluator; using Upsilon.Evaluator;
@ -32,5 +33,21 @@ namespace Upsilon.Binder
return; 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;
}
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; using Upsilon.Text;
@ -21,5 +22,11 @@ namespace Upsilon.Binder
{ {
state.HasBroken = true; state.HasBroken = true;
} }
internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state)
{
state.HasBroken = true;
yield break;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; using Upsilon.Text;
@ -24,5 +25,12 @@ namespace Upsilon.Binder
var value = Expression.Evaluate(scope, diagnostics, ref state); var value = Expression.Evaluate(scope, diagnostics, ref state);
state.LastValue = value; 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;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.BaseTypes.ScriptFunction; using Upsilon.BaseTypes.ScriptFunction;
using Upsilon.Binder.VariableSymbols; 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;
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Upsilon.BaseTypes; 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;
}
}
}
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
using Upsilon.Evaluator; using Upsilon.Evaluator;
@ -57,6 +58,21 @@ namespace Upsilon.Binder
ElseStatement?.Evaluate(scope, diagnostics, ref state); 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 public class BoundElseStatement : BoundStatement
@ -79,5 +95,10 @@ namespace Upsilon.Binder
{ {
Block.Evaluate(scope, diagnostics, ref state); Block.Evaluate(scope, diagnostics, ref state);
} }
internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state)
{
return Block.EvaluateCoroutine(scope, diagnostics, state);
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using Upsilon.BaseTypes.Number; 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;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.BaseTypes.Number; using Upsilon.BaseTypes.Number;
using Upsilon.Binder.VariableSymbols; 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;
}
}
}
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; using Upsilon.Text;
@ -25,5 +26,12 @@ namespace Upsilon.Binder
state.Returned = true; 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;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; using Upsilon.Text;
@ -31,5 +32,10 @@ namespace Upsilon.Binder
{ {
Statement.Evaluate(scope, diagnostics, ref state); Statement.Evaluate(scope, diagnostics, ref state);
} }
internal override IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics, EvaluationState state)
{
return Statement.EvaluateCoroutine(scope, diagnostics, state);
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; using Upsilon.Text;
@ -12,5 +13,8 @@ namespace Upsilon.Binder
internal bool HasBreakpoint { get; set; } internal bool HasBreakpoint { get; set; }
internal abstract void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state); internal abstract void Evaluate(EvaluationScope scope, Diagnostics diagnostics, ref EvaluationState state);
internal abstract IEnumerator EvaluateCoroutine(EvaluationScope scope, Diagnostics diagnostics,
EvaluationState state);
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
using Upsilon.BaseTypes.ScriptTypeInterfaces; using Upsilon.BaseTypes.ScriptTypeInterfaces;
@ -51,5 +52,11 @@ namespace Upsilon.Binder
} }
indexable.Set(diagnostics, Span, index.ToString().ToScriptType(), value); 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;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.Evaluator; using Upsilon.Evaluator;
using Upsilon.Text; 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;
}
} }
} }

View File

@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
using Upsilon.Evaluator; 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;
}
}
}
} }
} }

View File

@ -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<BoundNode> 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<ScriptType>();
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;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Upsilon.Binder
{ {
public UnboundFunctionExpression(ImmutableArray<BoundVariableSymbol> parameters, public UnboundFunctionExpression(ImmutableArray<BoundVariableSymbol> parameters,
BlockStatementSyntax unboundBlock, TextSpan span, BoundScope scope, string name) 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; UnboundBlock = unboundBlock;
Name = name; Name = name;

View File

@ -8,10 +8,12 @@ namespace Upsilon.Binder.VariableSymbols
public abstract class FunctionVariableSymbol : VariableSymbol public abstract class FunctionVariableSymbol : VariableSymbol
{ {
public List<FunctionVariableSymbolOption> FunctionOption { get; protected set; } = new List<FunctionVariableSymbolOption>(); public List<FunctionVariableSymbolOption> FunctionOption { get; protected set; } = new List<FunctionVariableSymbolOption>();
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) : base(name, Type.Function, local)
{ {
IsCoroutine = isCoroutine;
} }
public abstract bool ValidateParameters( public abstract bool ValidateParameters(

View File

@ -11,14 +11,15 @@ namespace Upsilon.Binder.VariableSymbols
public class InternalFunctionVariableSymbol : FunctionVariableSymbol public class InternalFunctionVariableSymbol : FunctionVariableSymbol
{ {
public InternalFunctionVariableSymbol(string name, bool local, TypeContainer resultType, public InternalFunctionVariableSymbol(string name, bool local, TypeContainer resultType,
InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType) InternalFunctionParameter[] functionParameters, MethodInfo overrideResultType, bool isCoroutine)
: base(name, local, resultType) : base(name, local, isCoroutine)
{ {
FunctionOption.Add(new InternalFunctionVariableOption(resultType, functionParameters, overrideResultType)); FunctionOption.Add(new InternalFunctionVariableOption(resultType, functionParameters, overrideResultType));
} }
public InternalFunctionVariableSymbol(string name, bool local, Type resultType, List<FunctionVariableSymbolOption> options) public InternalFunctionVariableSymbol(string name, bool local, List<FunctionVariableSymbolOption> options,
: base(name, local, resultType) bool isCoroutine)
: base(name, local, isCoroutine)
{ {
FunctionOption = options; FunctionOption = options;
} }

View File

@ -7,8 +7,9 @@ namespace Upsilon.Binder.VariableSymbols
{ {
public class ScriptFunctionVariableSymbol : FunctionVariableSymbol public class ScriptFunctionVariableSymbol : FunctionVariableSymbol
{ {
public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray<VariableSymbol> parameters, Type resultType) public ScriptFunctionVariableSymbol(string name, bool local, ImmutableArray<VariableSymbol> parameters, Type resultType,
: base(name, local, resultType) bool isCoroutine)
: base(name, local, isCoroutine)
{ {
FunctionOption.Add(new ScriptFunctionVariableOption(resultType, parameters)); FunctionOption.Add(new ScriptFunctionVariableOption(resultType, parameters));
} }

View File

@ -2,7 +2,7 @@ using Upsilon.BaseTypes;
namespace Upsilon.Evaluator namespace Upsilon.Evaluator
{ {
internal struct EvaluationState internal class EvaluationState
{ {
public Script Script; public Script Script;
public bool Returned; public bool Returned;

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Linq; using System.Linq;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
using Upsilon.BaseTypes.ScriptFunction; 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()); var result = option.Run(_diagnostics, parameters?.Select(x => x.ToScriptType()).ToArray(), _script, Scope, new TextSpan());
return result; 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());
}
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using Upsilon.BaseTypes; using Upsilon.BaseTypes;
@ -114,6 +115,18 @@ namespace Upsilon.Evaluator
return Convert(Evaluator.Evaluate(Bind(), functionName, parameters)); 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<T>(ScriptType t) private static T Convert<T>(ScriptType t)
{ {
var result = UpsilonBinder.Default.ChangeType(t, typeof(T), CultureInfo.InvariantCulture); var result = UpsilonBinder.Default.ChangeType(t, typeof(T), CultureInfo.InvariantCulture);

View File

@ -1,3 +1,4 @@
using System.Collections;
using Upsilon.Binder; using Upsilon.Binder;
using Upsilon.Evaluator; using Upsilon.Evaluator;
@ -89,5 +90,11 @@ namespace Upsilon
var script = ParseInputAndEvaluate(input, options); var script = ParseInputAndEvaluate(input, options);
return script.EvaluateFunction(function, parameters); 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);
}
} }
} }

View File

@ -12,10 +12,11 @@ namespace Upsilon.Parser
public SyntaxToken CloseParenthesis { get; } public SyntaxToken CloseParenthesis { get; }
public BlockStatementSyntax Block { get; } public BlockStatementSyntax Block { get; }
public SyntaxToken EndToken { get; } public SyntaxToken EndToken { get; }
public bool IsCoroutine { get; }
public FunctionExpressionSyntax(SyntaxToken functionToken, public FunctionExpressionSyntax(SyntaxToken functionToken,
SyntaxToken openParenthesis, ImmutableArray<ParameterToken> parameters, SyntaxToken closeParenthesis, SyntaxToken openParenthesis, ImmutableArray<ParameterToken> parameters, SyntaxToken closeParenthesis,
BlockStatementSyntax block, SyntaxToken endToken) BlockStatementSyntax block, SyntaxToken endToken, bool isCoroutine)
{ {
FunctionToken = functionToken; FunctionToken = functionToken;
OpenParenthesis = openParenthesis; OpenParenthesis = openParenthesis;
@ -23,6 +24,7 @@ namespace Upsilon.Parser
CloseParenthesis = closeParenthesis; CloseParenthesis = closeParenthesis;
Block = block; Block = block;
EndToken = endToken; EndToken = endToken;
IsCoroutine = isCoroutine;
Span = TextSpan.Between(FunctionToken.Span, endToken.Span); Span = TextSpan.Between(FunctionToken.Span, endToken.Span);
} }

View File

@ -79,11 +79,19 @@ namespace Upsilon.Parser
} }
if (Current.Kind == SyntaxKind.FunctionKeyword && Next.Kind != SyntaxKind.OpenParenthesis) if (Current.Kind == SyntaxKind.FunctionKeyword && Next.Kind != SyntaxKind.OpenParenthesis)
{ {
return ParseFunctionAssignmentStatement(); return ParseFunctionAssignmentStatement(SyntaxKind.FunctionKeyword);
} }
if (Current.Kind == SyntaxKind.LocalKeyword && Next.Kind == 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) if (Current.Kind == SyntaxKind.ForKeyword)
{ {
@ -97,6 +105,10 @@ namespace Upsilon.Parser
{ {
return new BreakStatementSyntax(NextToken()); return new BreakStatementSyntax(NextToken());
} }
if (Current.Kind == SyntaxKind.YieldKeyword)
{
return ParseYieldStatement();
}
return ParseExpressionStatement(); 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 openParenthesis = MatchToken(SyntaxKind.OpenParenthesis);
var variableBuilder = ImmutableArray.CreateBuilder<ParameterToken>(); var variableBuilder = ImmutableArray.CreateBuilder<ParameterToken>();
SyntaxToken current = null; SyntaxToken current = null;
@ -239,11 +251,13 @@ namespace Upsilon.Parser
var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis);
var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword}); var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword});
var endToken = MatchToken(SyntaxKind.EndKeyword); var endToken = MatchToken(SyntaxKind.EndKeyword);
var isCoroutine = openKeyword == SyntaxKind.CoroutineKeyword;
return new FunctionExpressionSyntax(functionToken, openParenthesis, 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; SyntaxToken localToken = null;
string[] commentData = null; string[] commentData = null;
@ -252,7 +266,7 @@ namespace Upsilon.Parser
localToken = NextToken(); localToken = NextToken();
commentData = localToken.CommentData; commentData = localToken.CommentData;
} }
var functionToken = MatchToken(SyntaxKind.FunctionKeyword); var functionToken = MatchToken(identifyingKeyword);
if (commentData == null) if (commentData == null)
{ {
commentData = functionToken.CommentData; commentData = functionToken.CommentData;
@ -278,8 +292,10 @@ namespace Upsilon.Parser
var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis); var closeParenthesis = MatchToken(SyntaxKind.CloseParenthesis);
var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword}); var block = ParseBlockStatement(new[] {SyntaxKind.EndKeyword});
var endToken = MatchToken(SyntaxKind.EndKeyword); var endToken = MatchToken(SyntaxKind.EndKeyword);
var isCoroutine = identifyingKeyword == SyntaxKind.CoroutineKeyword;
var functionExpression = new FunctionExpressionSyntax(functionToken, openParenthesis, 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) return new FunctionAssignmentStatementSyntax(localToken, (IdentifierToken) identifier, functionExpression)
{ {
CommentData = commentData CommentData = commentData
@ -309,12 +325,24 @@ namespace Upsilon.Parser
return new ReturnStatementSyntax(returnToken, expression); return new ReturnStatementSyntax(returnToken, expression);
} }
private StatementSyntax ParseYieldStatement()
{
var yieldToken = MatchToken(SyntaxKind.YieldKeyword);
var expression = ParseExpression();
return new YieldStatementSyntax(yieldToken, expression);
}
private ExpressionSyntax ParseExpression() private ExpressionSyntax ParseExpression()
{ {
ExpressionSyntax expression; ExpressionSyntax expression;
if (Current.Kind == SyntaxKind.FunctionKeyword && Next.Kind == SyntaxKind.OpenParenthesis) 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 else
{ {

View File

@ -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<SyntaxNode> ChildNodes()
{
yield return YieldToken;
yield return Expression;
}
}
}

View File

@ -44,6 +44,10 @@ namespace Upsilon.Parser
return SyntaxKind.DoKeyword; return SyntaxKind.DoKeyword;
case "break": case "break":
return SyntaxKind.BreakKeyword; return SyntaxKind.BreakKeyword;
case "yield":
return SyntaxKind.YieldKeyword;
case "coroutine":
return SyntaxKind.CoroutineKeyword;
default: default:
return SyntaxKind.Identifier; return SyntaxKind.Identifier;
} }

View File

@ -56,6 +56,8 @@ namespace Upsilon.Parser
InKeyword, InKeyword,
DoKeyword, DoKeyword,
BreakKeyword, BreakKeyword,
YieldKeyword,
CoroutineKeyword,
Identifier, Identifier,
Parameter, Parameter,
@ -90,6 +92,7 @@ namespace Upsilon.Parser
NumericForStatement, NumericForStatement,
BreakStatement, BreakStatement,
GenericForStatement, GenericForStatement,
WhileStatement WhileStatement,
YieldStatement,
} }
} }

View File

@ -64,7 +64,7 @@ namespace Upsilon.StandardLibraries
var derivedType = DeriveValidTypes(typeInfo.Type); var derivedType = DeriveValidTypes(typeInfo.Type);
return new InternalFunctionVariableSymbol.InternalFunctionParameter(func.Key, derivedType, return new InternalFunctionVariableSymbol.InternalFunctionParameter(func.Key, derivedType,
typeInfo.IsOptional); 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') CommentValue = func.Value.CommentValue?.Split('\n')
}; };
@ -139,7 +139,7 @@ namespace Upsilon.StandardLibraries
} }
var result = genericParameters[genericParameters.Length - 1].GetScriptType(); 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) private static VariableSymbol BuildActionVariableSymbol(string name, System.Type type)
@ -147,7 +147,8 @@ namespace Upsilon.StandardLibraries
var genericParameters = type.GetGenericArguments(); var genericParameters = type.GetGenericArguments();
return new InternalFunctionVariableSymbol(name, true, Type.Nil, return new InternalFunctionVariableSymbol(name, true, Type.Nil,
genericParameters.Select(DeriveValidTypes).Select(t => 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) public static TypeContainer DeriveValidTypes(System.Type type)

View File

@ -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<object>();
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<object>();
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<TestClass>();
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);
}
}
}