Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Library/Core/Blocks/Colour/ColourBlend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ColourBlend : Block
var colour1 = (await Values.EvaluateAsync("COLOUR1", context) ?? "").ToString();
var colour2 = (await Values.EvaluateAsync("COLOUR2", context) ?? "").ToString();

var ratio = System.Math.Min(System.Math.Max(await Values.EvaluateAsync<double>("RATIO", context), 0), 1);
var ratio = System.Math.Min(System.Math.Max(await Values.EvaluateDoubleAsync("RATIO", context), 0), 1);

if (string.IsNullOrWhiteSpace(colour1) || colour1.Length != 7)
return null!;
Expand Down
6 changes: 3 additions & 3 deletions Library/Core/Blocks/Controls/ControlsFor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class ControlsFor : Block
{
var variableName = Fields["VAR"];

var fromValue = await Values.EvaluateAsync<double>("FROM", context);
var toValue = await Values.EvaluateAsync<double>("TO", context);
var byValue = await Values.EvaluateAsync<double>("BY", context);
var fromValue = await Values.EvaluateDoubleAsync("FROM", context);
var toValue = await Values.EvaluateDoubleAsync("TO", context);
var byValue = await Values.EvaluateDoubleAsync("BY", context);

var statement = Statements.TryGet("DO");

Expand Down
2 changes: 1 addition & 1 deletion Library/Core/Blocks/Controls/ControlsRepeatExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class ControlsRepeatExt : Block
return await base.EvaluateAsync(context);

/* Number of times to execute the inner block. */
for (var i = await Values.EvaluateAsync<double>("TIMES", context); i-- > 0;)
for (var i = await Values.EvaluateDoubleAsync("TIMES", context); i-- > 0;)
{
/* Execute the inner block. */
context.Cancellation.ThrowIfCancellationRequested();
Expand Down
2 changes: 1 addition & 1 deletion Library/Core/Blocks/Lists/ListsRepeat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ListsRepeat : Block
protected override async Task<object?> EvaluateAsync(Context context)
{
var item = await Values.EvaluateAsync<object>("ITEM", context);
var num = await Values.EvaluateAsync<double>("NUM", context);
var num = await Values.EvaluateDoubleAsync("NUM", context);

var list = new List<object>();

Expand Down
23 changes: 5 additions & 18 deletions Library/Core/Blocks/Logic/LogicCompare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,10 @@ public class LogicCompare : Block

if (a == null || b == null) return Compare(opValue, a == null, b == null);

var tryInt = TryConvertToDoubleValues(a, b); // int => blockly always uses double
var tryInt = TryConvertToDoubleValues(a, b, context);
if (tryInt.canConvert)
return Compare(opValue, tryInt.aValue, tryInt.bValue);

var tryDouble = TryConvertValues<double>(a, b);
if (tryDouble.canConvert)
return Compare(opValue, tryDouble.aValue, tryDouble.bValue);

var tryString = TryConvertValues<string>(a, b);
if (tryString.canConvert)
return Compare(opValue, tryString.aValue, tryString.bValue);
Expand Down Expand Up @@ -137,20 +133,11 @@ private static (bool canConvert, T aValue, T bValue) TryConvertValues<T>(object
return (true, aResult, bResult);
}

private static (bool canConvert, double aValue, double bValue) TryConvertToDoubleValues(object a, object b)
private static (bool canConvert, double aValue, double bValue) TryConvertToDoubleValues(object a, object b, Context context)
{
double aResult;
if (a?.GetType() == typeof(double) || a?.GetType() == typeof(int))
aResult = (double)Convert.ChangeType(a, typeof(double));
else
return (false, default, default);
if (Values.TryConvertToDouble(a, context, out var aResult) && Values.TryConvertToDouble(b, context, out var bResult))
return (true, aResult, bResult);

double bResult;
if (b?.GetType() == typeof(double) || b?.GetType() == typeof(int))
bResult = (double)Convert.ChangeType(b, typeof(double));
else
return (false, default, default);

return (true, aResult, bResult);
return (false, default, default);
}
}
15 changes: 1 addition & 14 deletions Library/Core/Blocks/Math/MathOnList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,7 @@ public class MathOnList : Block
var op = Fields["OP"];
var list = await Values.EvaluateAsync<IEnumerable<object>>("LIST", context);

var doubleList = list.Select(raw =>
{
if (cvt == null) return (double)raw;

/* Run converter - report cast error on mismatch. */
try
{
return cvt.GetNumber(raw);
}
catch (Exception)
{
return (double)raw;
}
}).ToArray();
var doubleList = list.Select(raw => Values.TryConvertToDouble(raw, context, out var num) ? num : (double)raw).ToArray();

return op switch
{
Expand Down
2 changes: 1 addition & 1 deletion Library/Core/Blocks/Procedures/ProceduresCallNoReturn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ProceduresCallNoReturn : Block
if (!context.Functions.TryGetValue(name, out var statement))
throw new MissingMethodException($"Method ${name} not defined");

var funcContext = new Context(context);
var funcContext = new ProcedureContext(name, context);

var counter = 0;

Expand Down
2 changes: 1 addition & 1 deletion Library/Core/Blocks/Procedures/ProceduresCallReturn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class ProceduresCallReturn : ProceduresCallNoReturn
if (!context.Functions.TryGetValue(name, out var statement))
throw new MissingMethodException($"Method '{name}' not defined");

var funcContext = new Context(context);
var funcContext = new ProcedureContext(name, context);

var counter = 0;

Expand Down
4 changes: 2 additions & 2 deletions Library/Core/Blocks/Text/TextSubString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ public class TextSubstring : Block
{
"FIRST" => 0,
"LAST" => value.Length,
"FROM_START" => (int)await Values.EvaluateAsync<double>(at, context) - 1,
"FROM_END" => value.Length - ((int)await Values.EvaluateAsync<double>(at, context) - 1),
"FROM_START" => (int)await Values.EvaluateDoubleAsync(at, context) - 1,
"FROM_END" => value.Length - ((int)await Values.EvaluateDoubleAsync(at, context) - 1),
_ => throw new ArgumentException($"unknown choice {where}")
};

Expand Down
9 changes: 5 additions & 4 deletions Library/Core/Model/Block.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ public abstract class Block
/// <inheritdoc/>
protected virtual async Task<object?> EvaluateAsync(Context context)
{
/* Wait for debugger to allow execution - the current block has finished its work. */
await context.Engine.SingleStepAsync(this, context, ScriptDebuggerStopReason.Leave);

/* Always check for cancel before proceeding with the execution of the next block in chain. */
context.Cancellation.ThrowIfCancellationRequested();

Expand All @@ -81,8 +78,9 @@ public abstract class Block
/// Start a brand new block execution chain.
/// </summary>
/// <param name="context">Current operation context.</param>
/// <param name="isScript">Set when we are executing the full script.</param>
/// <returns>Result of the block if any.</returns>
public async Task<object?> EnterBlockAsync(Context context)
public async Task<object?> EnterBlockAsync(Context context, bool isScript = false)
{
/* Wait for debugger to allow execution - we enter a new chain of execution, e.g. calculating a value or control block. */
await context.Engine.SingleStepAsync(this, context, ScriptDebuggerStopReason.Enter);
Expand All @@ -109,6 +107,9 @@ public abstract class Block
/* Wait for debugger to allow execution - the current block as finished its work. */
await context.Engine.SingleStepAsync(this, context, ScriptDebuggerStopReason.Finish);

/* Script is done. */
if (isScript) await context.Engine.SingleStepAsync(this, context, ScriptDebuggerStopReason.Leave);

return result;
}
}
7 changes: 4 additions & 3 deletions Library/Core/Model/ProcedureContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ namespace BlocklyNet.Core.Model;
/// <summary>
/// Execution context of a function.
/// </summary>
/// <param name="name"></param>
/// <param name="parent"></param>
public class ProcedureContext(Context parent) : Context(parent)
public class ProcedureContext(string name, Context parent) : Context(parent)
{
/// <summary>
/// All parameters of the function.
/// Name of the procedure called.
/// </summary>
public readonly IDictionary<string, object> Parameters = new Dictionary<string, object>();
public string Name { get; } = name;
}

45 changes: 39 additions & 6 deletions Library/Core/Model/Values.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,26 +50,59 @@ public class Values : Entities<Value>
/// <param name="context">Operation context.</param>
/// <returns>Requested number.</returns>
public async Task<double> EvaluateDoubleAsync(string name, Context context)
=> (await EvaluateOptionalDoubleAsync(name, context)) ?? throw new ArgumentException($"value {name} not found");

/// <summary>
/// Get an optional number - may be auto-converted from some other type.
/// </summary>
/// <param name="name">Name of the parameter.</param>
/// <param name="context">Operation context.</param>
/// <returns>Requested number or null.</returns>
public async Task<double?> EvaluateOptionalDoubleAsync(string name, Context context)
{
/* Retrieve raw value. */
var raw = await EvaluateAsync(name, context) ?? throw new ArgumentException($"value {name} not found");
var raw = await EvaluateAsync(name, context, false);

/* Make it a double number. */
return raw == null ? null : TryConvertToDouble(raw, context, out var num) ? num : (double)raw;
}

/// <summary>
/// Try to convert a given value to double precision floating point number.
/// </summary>
/// <param name="raw">Raw value - can be anything.</param>
/// <param name="context">Current operating context.</param>
/// <param name="value">Raw value as number.</param>
/// <returns>Set if the value contains the requested number.</returns>
public static bool TryConvertToDouble(object raw, Context context, out double value)
{
/* Already a number - extend as needed, left out long because conversion is not always lossless. */
if (raw is double doubleNum) { value = doubleNum; return true; }
if (raw is float floatNum) { value = floatNum; return true; }
if (raw is uint uintNum) { value = uintNum; return true; }
if (raw is int intNum) { value = intNum; return true; }
if (raw is ushort ushortNum) { value = ushortNum; return true; }
if (raw is short shortNum) { value = shortNum; return true; }
if (raw is byte byteNum) { value = byteNum; return true; }
if (raw is sbyte sByteNum) { value = sByteNum; return true; }

/* Already a number. */
if (raw is double num) return num;
value = default;

/* Check for converter. */
var cvt = context.ServiceProvider.GetService<IDoubleExtractor>();

if (cvt == null) return (double)raw;
if (cvt == null) return false;

/* Run converter - report cast error on mismatch. */
try
{
return cvt.GetNumber(raw);
value = cvt.GetNumber(raw);

return true;
}
catch (Exception)
{
return (double)raw;
return false;
}
}

Expand Down
2 changes: 1 addition & 1 deletion Library/Core/Model/Workspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class Workspace : IFragment
/* Remember the result and report the last result afterwards. */
context.Cancellation.ThrowIfCancellationRequested();

returnValue = await exec.EnterBlockAsync(context);
returnValue = await exec.EnterBlockAsync(context, true);

/* Did this path. */
break;
Expand Down
2 changes: 1 addition & 1 deletion Library/Extensions/Delay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class Delay : Block
/// <inheritdoc/>
protected override async Task<object?> EvaluateAsync(Context context)
{
var delay = (int)await Values.EvaluateAsync<double>("DELAY", context);
var delay = (int)await Values.EvaluateDoubleAsync("DELAY", context);

if (delay > 0) await Task.Delay(delay, context.Cancellation);

Expand Down
2 changes: 1 addition & 1 deletion Library/Extensions/GetGroupStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class GetGroupStatus : Block
/// <inheritdoc/>
protected override async Task<object?> EvaluateAsync(Context context)
{
var index = (int)await Values.EvaluateAsync<double>("INDEX", context);
var index = (int)await Values.EvaluateDoubleAsync("INDEX", context);

return context.Engine.GetGroupStatus(index - 1);
}
Expand Down
2 changes: 1 addition & 1 deletion Library/Extensions/RequestUserInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class RequestUserInput : Block
/// <inheritdoc/>
protected override async Task<object?> EvaluateAsync(Context context)
{
var delay = await Values.EvaluateAsync<double?>("DELAY", context, false);
var delay = await Values.EvaluateOptionalDoubleAsync("DELAY", context);
var key = await Values.EvaluateAsync<string>("KEY", context);
var required = await Values.EvaluateAsync<bool?>("REQUIRED", context, false);
var secs = delay.GetValueOrDefault(0);
Expand Down
2 changes: 1 addition & 1 deletion Library/Extensions/RunParallel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class RunParallel : Block
{
/* Load array of scripts. */
var scripts = await Values.EvaluateAsync<IEnumerable>("SCRIPTS", context);
var leading = await Values.EvaluateAsync<double?>("LEADINGSCRIPT", context, false);
var leading = await Values.EvaluateOptionalDoubleAsync("LEADINGSCRIPT", context);

/* Request configuration for all scripts - allow empty array elements. */
var configs = scripts.Cast<StartScript>().ToList();
Expand Down
2 changes: 1 addition & 1 deletion Library/Extensions/SetProgress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public class SetProgress : Block
protected override async Task<object?> EvaluateAsync(Context context)
{
var script = context.Engine.MainScript as IGenericScript;
var progress = await Values.EvaluateAsync<double>("PROGRESS", context);
var progress = await Values.EvaluateDoubleAsync("PROGRESS", context);
var name = await Values.EvaluateAsync<string?>("NAME", context, false);

context.Engine.ReportProgress(
Expand Down
92 changes: 92 additions & 0 deletions Library/Scripting/Debugger/BreakpointList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Microsoft.Extensions.Logging;

namespace BlocklyNet.Scripting.Debugger;

/// <summary>
/// Management for the list of breakpoints. Implemention is thread-safe.
/// </summary>
internal class BreakpointList(ScriptDebugger debugger) : IScriptBreakpoints
{
/// <summary>
/// Current list.
/// </summary>
private readonly Dictionary<ScriptBreakpoint, ScriptBreakpoint> _breakpoints = [];

/// <inheritdoc/>
public IScriptBreakpoint? this[string scriptId, string blockId]
{
get
{
lock (_breakpoints)
return _breakpoints.TryGetValue(new ScriptBreakpoint(scriptId, blockId), out var bp) ? bp : null;
}
}

/// <inheritdoc/>
public bool BreakOnExceptions
{
get => _breakOnExceptions;
set
{
if (_breakOnExceptions == value) return;

_breakOnExceptions = value;

debugger.SomethingChanged();
}
}

private bool _breakOnExceptions;

/// <inheritdoc/>
public bool BreakOnEndOfScript
{
get => _breakOnEndOfScript;
set
{
if (_breakOnEndOfScript == value) return;

_breakOnEndOfScript = value;

debugger.SomethingChanged();
}
}

private bool _breakOnEndOfScript;

/// <inheritdoc/>
public void Add(string scriptId, string blockId, string? description = null)
{
var bp = new ScriptBreakpoint(scriptId, blockId, description, debugger);

lock (_breakpoints)
{
debugger.InternalLogger.LogTrace("{What} breakpoint {Breakpoint}", _breakpoints.ContainsKey(bp) ? "Updating" : "Adding", bp.ToString());

_breakpoints[bp] = bp;
}

debugger.SomethingChanged();
}

/// <inheritdoc/>
public IScriptBreakpoint[] GetAll()
{
lock (_breakpoints)
return [.. _breakpoints.Values];
}

/// <inheritdoc/>
public void Remove(string scriptId, string blockId)
{
var bp = new ScriptBreakpoint(scriptId, blockId);

debugger.InternalLogger.LogTrace("Removing breakpoint {Breakpoint}", bp.ToString());

lock (_breakpoints)
if (!_breakpoints.Remove(bp))
return;

debugger.SomethingChanged();
}
}
Loading