Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎉 Add custom function support #45

Merged
merged 2 commits into from
Jul 28, 2024
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 DoodleDigits.App/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "doodledigits.app",
"productName": "Doodle Digits",
"version": "2.1.1",
"version": "2.2.0",
"description": "An inline calculator where you write equations into a large textbox and the answers are given to you as you type.",
"main": ".webpack/main",
"scripts": {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 7 additions & 7 deletions DoodleDigits.App/static/doodledigits/_framework/blazor.boot.json
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
{
"mainAssemblyName": "DoodleDigits.JsInterop.dll",
"resources": {
"hash": "sha256-Fj9zCBinIWZZy9ryeUn+4FdTmiQ2xx6Y4EP2nOxGN5o=",
"hash": "sha256-ys0AOAa9O9rPPTaqg5+Gf8Qs5q7bm12HvPMzqpIwhtg=",
"jsModuleNative": {
"dotnet.native.js": "sha256-Q7sxjQWiJTLXMuRexctKySXuaDMq13wiJfh1vEo21xM="
},
"jsModuleRuntime": {
"dotnet.runtime.js": "sha256-WdSX3HQvnBYF0KJLZoOyHvTzMHetaob6PV0Kn2K+QXw="
},
"wasmNative": {
"dotnet.native.wasm": "sha256-qcd+mt4t2zXua6xBIYQKom5xyvDTWNjRbrY752SHa1U="
"dotnet.native.wasm": "sha256-LG3n8IVFUCy4xOBfyYFipb5mrnSssmUUklltpoCPf5c="
},
"icu": {
"icudt_CJK.dat": "sha256-SZLtQnRc0JkwqHab0VUVP7T3uBPSeYzxzDnpxPpUnHk=",
"icudt_EFIGS.dat": "sha256-8fItetYY8kQ0ww6oxwTLiT3oXlBwHKumbeP2pRF4yTc=",
"icudt_no_CJK.dat": "sha256-L7sV7NEYP37/Qr2FPCePo5cJqRgTXRwGHuwF5Q+0Nfs="
},
"assembly": {
"DoodleDigits.Core.wasm": "sha256-C+D9xk1oizwoH1tRWvLo9NTktfOxDJXs1VEONp5FAfY=",
"DoodleDigits.JsInterop.wasm": "sha256-zMDrZWPYkbczpQq6rHxWaD/EEnpiZC9TAPP30Tt5fLY=",
"DoodleDigits.Core.wasm": "sha256-bA0jMnhSjetDKq5fG40sMonV2o0H9Vd/T4vPotWKlmA=",
"DoodleDigits.JsInterop.wasm": "sha256-bxlcYFD9coBFXkCxIOeyF+HWeb3iupqa1Hd3PxNvzsg=",
"MathNet.Numerics.wasm": "sha256-kvNuJoKWciXhHWnTIf4yrOMhOGweMGmPfGdoXZj4ScY=",
"Rationals.wasm": "sha256-/EB0pSTa6uVEaldigQ2JaHS2FgJondqT/LXExuCPLPE=",
"System.Collections.Concurrent.wasm": "sha256-K8seTnpe72PIv3asamZqLl+c1rJfBtvjjICYHgB/GqU=",
"System.Collections.Concurrent.wasm": "sha256-ViZN2hOsYaBvhdAq9s+GrUjSlRakB7KkYOxsuRFA3BA=",
"System.Collections.wasm": "sha256-82omAELZPmOKeCtqhA0FrTuBkiVlCRyDJph6eA/rvrw=",
"System.ComponentModel.Primitives.wasm": "sha256-olnz3OgDJ8VYNg0haYR5fiAhl4RBjdWyWgRhwKYE7cc=",
"System.Linq.wasm": "sha256-EJXA+Xj2RYz1LGQSkA0dqEwQII7jlrn+kXznVm/OpvY=",
"System.Linq.wasm": "sha256-v+itANDvsG8rIChupC9nZJ0ijO5sTFl6ODBznCtJIqI=",
"System.Memory.wasm": "sha256-xqx+1FmYotufJoTdaOlG1TwQbNWM0fVj/lQZ+y+i3Kc=",
"System.Private.CoreLib.wasm": "sha256-ebRI1gikTuNJb3I6LDjV51Z39KqV//5B3ZXOzMTuyXE=",
"System.Private.CoreLib.wasm": "sha256-3o4h+BUy9KnSaVZweqU2iu5nNr+T+cd9++/rEj4SL6c=",
"System.Runtime.InteropServices.JavaScript.wasm": "sha256-Scu8f+rUcyHWC9cmsMmL37oc+oeSRwqeAtwamnWpKfY=",
"System.Runtime.Numerics.wasm": "sha256-J/Q4EML6d4o7OlCIEu13FpOQLQ2QgZaTdk2c+D93bQs=",
"System.Text.Encodings.Web.wasm": "sha256-/ipcKkJyTDGrXZ6CzgQYljZdyCaNwA1Yv88LwzBWqEE=",
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion DoodleDigits/DoodleDigits.Core/DoodleDigits.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<PropertyGroup>
<PackageId>DoodleDigits.Core</PackageId>
<Version>1.5.0</Version>
<Version>1.6.0</Version>
<Authors>Anton Bergåker</Authors>
<Title>Doodle Digits Core</Title>
<Description>
Expand Down
123 changes: 92 additions & 31 deletions DoodleDigits/DoodleDigits.Core/Execution/Executor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using DoodleDigits.Core.Functions;
using DoodleDigits.Core.Functions.Implementations.Binary;
using DoodleDigits.Core.Parsing.Ast;
using DoodleDigits.Core.Parsing.Ast.AmbiguousNodes;
using DoodleDigits.Core.Utilities;
using Rationals;

Expand Down Expand Up @@ -35,8 +36,8 @@ public ExecutionResult Execute(AstNode root, CalculatorSettings settings) {
_results.Clear();
var context = new ExecutorContext(settings, _constants);

if (root is ExpressionList list) {
foreach (Expression expression in list.Expressions) {
if (root is NodeList list) {
foreach (Expression expression in list.Nodes) {
_results.Add(new ResultValue(Calculate(expression, context), expression.Position));
}
} else if (root is Expression ex) {
Expand All @@ -58,50 +59,80 @@ private Value Calculate(Expression expression, ExecutorContext context) {
UnaryOperation uo => CalculateUnary(uo, context),
NumberLiteral nl => CalculateNumber(nl, context),
Identifier id => CalculateIdentifier(id, context),
Function f => CalculateFunction(f, context),
FunctionCall f => CalculateFunction(f, context),
Comparison ec => CalculateComparison(ec, context),
BaseCast bc => CalculateBaseCast(bc, context),
VectorDeclaration vd => CalculateVector(vd, context),
AmbiguousNode an => CalculateAmbiguousNode(an, context),
ErrorNode error => new UndefinedValue(UndefinedValue.UndefinedType.Error),
_ => throw new Exception("Expression not handled for " + expression.GetType()),
};
}

private Value CalculateFunction(Function function, ExecutorContext context) {
if (_functions.TryGetValue(function.Identifier, out var functionData) == false) {
_results.Add(new ResultError($"Unknown function: {function.Identifier}", function.Position));
return new UndefinedValue(UndefinedValue.UndefinedType.Error);
private Value CalculateFunction(FunctionCall function, ExecutorContext context) {
bool CheckParameterCount(Range parameterCount) {
int minParameters = parameterCount.Start.Value;
int maxParameters = parameterCount.End.GetOffset(int.MaxValue);


if (function.Arguments.Length < minParameters ||
function.Arguments.Length > maxParameters) {
string errorMessage;
if (minParameters == maxParameters) {
errorMessage = $"Function needs {minParameters} parameters";
} else if (maxParameters == int.MaxValue) {
errorMessage = $"Function needs at least {minParameters} parameters";
} else {
errorMessage = $"Function needs between {minParameters} and {maxParameters} parameters";
}

_results.Add(new ResultError(errorMessage, function.Position));
return false;
}
return true;
}

int minParameters = functionData.ParameterCount.Start.Value;
int maxParameters = functionData.ParameterCount.End.GetOffset(int.MaxValue);
// Built in function
if (_functions.TryGetValue(function.Identifier, out var functionData)) {
if (CheckParameterCount(functionData.ParameterCount) == false) {
return new UndefinedValue(UndefinedValue.UndefinedType.Error);
}
return functionData.Function(function.Arguments.Select(x => Calculate(x, context)).ToArray(), context, function);
}

// User function
if (context.TryGetVariable(function.Identifier, out var foundVariable) && foundVariable is FunctionValue functionValue) {
var count = functionValue.ArgumentNames.Length;
if (CheckParameterCount(count..count) == false) {
return new UndefinedValue(UndefinedValue.UndefinedType.Error);
}

var variables = new List<(string Identifier, Value Value)>();
for (int i = 0; i < function.Arguments.Length; i++) {
variables.Add((functionValue.ArgumentNames[i], Calculate(function.Arguments[i], context)));
}

if (function.Arguments.Length < minParameters ||
function.Arguments.Length > maxParameters) {
string errorMessage;
if (minParameters == maxParameters) {
errorMessage = $"Function needs {minParameters} parameters";
} else if (maxParameters == int.MaxValue) {
errorMessage = $"Function needs at least {minParameters} parameters";
} else {
errorMessage = $"Function needs between {minParameters} and {maxParameters} parameters";
context.PushVariableStack();
foreach (var variable in variables) {
context.AddVariable(variable.Identifier, variable.Value);
}
var result = Calculate(functionValue.Implementation, context);
if (result.TriviallyAchieved) {
result = result.Clone(false);
}
context.PopVariableStack();
return result;
}

_results.Add(new ResultError(errorMessage, function.Position));
return new UndefinedValue(UndefinedValue.UndefinedType.Error);
}

return functionData.Function(function.Arguments.Select(x => Calculate(x, context)).ToArray(), context, function);
_results.Add(new ResultError($"Unknown function: {function.Identifier}", function.Position));
return new UndefinedValue(UndefinedValue.UndefinedType.Error);
}

private Value CalculateIdentifier(Identifier identifier, ExecutorContext context) {
if (context.Constants.TryGetValue(identifier.Value, out Value? constantValue)) {
return constantValue;
}

if (context.Variables.TryGetValue(identifier.Value, out Value? variableValue)) {
return variableValue;
private Value CalculateIdentifier(Identifier identifier, ExecutorContext context) {
if (context.TryGetVariable(identifier.Value, out Value? variable)) {
return variable;
}

_results.Add(new ResultError("Unknown identifier", identifier.Position));
Expand Down Expand Up @@ -145,8 +176,7 @@ private Value CalculateComparison(Comparison comparison, ExecutorContext context

Value? CalculateExpression(Expression expression) {
if (expression is Identifier identifier) {
if (context.Variables.ContainsKey(identifier.Value) == false &&
context.Constants.ContainsKey(identifier.Value) == false) {
if (context.TryGetVariable(identifier.Value, out _) == false) {
return null;
}
}
Expand All @@ -172,7 +202,7 @@ private Value CalculateComparison(Comparison comparison, ExecutorContext context
}
Identifier value = (Identifier)comparison.Expressions[i];

context.Variables[value.Value] = calculatedResult.Clone(false);
context.AddVariable(value.Value, calculatedResult.Clone(false));
}

return calculatedResult;
Expand Down Expand Up @@ -267,4 +297,35 @@ private Value CalculateVector(VectorDeclaration vectorDeclaration, ExecutorConte

return val;
}

private Value CalculateFunctionDeclaration(FunctionDeclaration function, ExecutorContext context) {
var functionValue = new FunctionValue(function.Identifier, function.ArgumentNames, function.Implementation, false);
context.AddVariable(function.Identifier, functionValue);
return functionValue;
}

private Value CalculateAmbiguousNode(AmbiguousNode node, ExecutorContext context) {
return node switch {
FunctionCallOrMultiplication fcom => CalculateFunctionCallOrMultiplication(fcom, context),
FunctionDeclarationOrEquals fdoe => CalculateFunctionDeclarationOrEquals(fdoe, context),
_ => new UndefinedValue(UndefinedValue.UndefinedType.Error)
};
}

private Value CalculateFunctionDeclarationOrEquals(FunctionDeclarationOrEquals node, ExecutorContext context) {
// Not used, free to become a function I guess
if (context.TryGetVariable(node.FunctionDeclaration.Identifier, out _) == false) {
return CalculateFunctionDeclaration(node.FunctionDeclaration, context);
}
return CalculateComparison(node.EqualsComparison, context);
}

private Value CalculateFunctionCallOrMultiplication(FunctionCallOrMultiplication node, ExecutorContext context) {
if (context.TryGetVariable(node.Function.Identifier, out var value)) {
if (value is FunctionValue function) {
return CalculateFunction(node.Function, context);
}
}
return CalculateBinary(node.Multiplication, context);
}
}
42 changes: 36 additions & 6 deletions DoodleDigits/DoodleDigits.Core/Execution/ExecutorContext.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
using DoodleDigits.Core.Execution.Results;
using DoodleDigits.Core.Execution.ValueTypes;
using System.Diagnostics.CodeAnalysis;

namespace DoodleDigits.Core.Execution;

public class ExecutorContext {
private readonly Dictionary<string, Value> _constants;
private readonly Dictionary<string, Value> _variables;
private readonly List<Dictionary<string, Value>> _variables;

private readonly List<Result> _results;

public IReadOnlyDictionary<string, Value> Constants => _constants;
public Dictionary<string, Value> Variables => _variables;

public IReadOnlyList<Result> Results => _results;
public CalculatorSettings Settings { get; internal set; }

public ExecutorContext(CalculatorSettings settings, Dictionary<string, Value> constants) {
Settings = settings;
this._constants = constants;
this._variables = new();
_variables = new();

PushVariableStack();

foreach (var constant in constants) {
AddVariable(constant.Key, constant.Value);
}
this._results = new();
}

Expand All @@ -28,4 +33,29 @@ public void Clear() {
_results.Clear();
_variables.Clear();
}

public bool TryGetVariable(string identifier, [MaybeNullWhen(false)] out Value value) {
for (int i = _variables.Count - 1; i >= 0; i--) {
var stack = _variables[i];
if (stack.TryGetValue(identifier, out var foundValue)) {
value = foundValue;
return true;
}
}

value = null;
return false;
}

public void AddVariable(string identifier, Value value) {
_variables[^1].Add(identifier, value);
}

public void PushVariableStack() {
_variables.Add(new());
}

public void PopVariableStack() {
_variables.RemoveAt(_variables.Count-1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@


using DoodleDigits.Core.Parsing.Ast;

namespace DoodleDigits.Core.Execution.ValueTypes;
public class FunctionValue : Value {
public string Identifier { get; }
public string[] ArgumentNames { get; }
public Expression Implementation { get; }

public FunctionValue(string identifier, string[] argumentNames, Expression implementation, bool triviallyAchieved) : base(triviallyAchieved) {
Identifier = identifier;
ArgumentNames = argumentNames;
Implementation = implementation;
}

public override Value Clone(bool? triviallyAchieved = null) {
return new FunctionValue(Identifier, ArgumentNames, Implementation, triviallyAchieved ?? TriviallyAchieved);
}

public override bool Equals(Value? other) {
if (other is not FunctionValue otherFunction) {
return false;
}
return otherFunction.Implementation.Equals(Implementation);
}

public override int GetHashCode() {
return Implementation.GetHashCode();
}

public override string ToString() {
return Implementation.ToString();
}
}
10 changes: 5 additions & 5 deletions DoodleDigits/DoodleDigits.Core/Functions/FunctionLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ public class FunctionData {

public readonly FunctionExpectedType ExpectedType;

public readonly Func<Value[], ExecutorContext, Function, Value> Function;
public readonly Func<Value[], ExecutorContext, FunctionCall, Value> Function;

public readonly Range ParameterCount;

public FunctionData(string[] names, FunctionExpectedType type, Range parameterCount, Func<Value[], ExecutorContext, Function, Value> function) {
public FunctionData(string[] names, FunctionExpectedType type, Range parameterCount, Func<Value[], ExecutorContext, FunctionCall, Value> function) {
Names = names;
ExpectedType = type;
this.ParameterCount = parameterCount;
Function = function;
}


public FunctionData(string[] names, FunctionExpectedType type, Func<Value, ExecutorContext, Function, Value> function) : this(
public FunctionData(string[] names, FunctionExpectedType type, Func<Value, ExecutorContext, FunctionCall, Value> function) : this(
names, type, 1..1, (parameters, context, nodes) => function(parameters[0], context, nodes)
) { }

public FunctionData(string[] names, FunctionExpectedType type, Func<Value, Value, ExecutorContext, Function, Value> function) : this(
public FunctionData(string[] names, FunctionExpectedType type, Func<Value, Value, ExecutorContext, FunctionCall, Value> function) : this(
names, type, 2..2, (parameters, context, nodes) => function(parameters[0], parameters[1], context, nodes)
) { }

public FunctionData(string[] names, FunctionExpectedType type, Func<Value, Value, Value, ExecutorContext, Function, Value> function) : this(
public FunctionData(string[] names, FunctionExpectedType type, Func<Value, Value, Value, ExecutorContext, FunctionCall, Value> function) : this(
names, type, 3..3, (parameters, context, nodes) => function(parameters[0], parameters[1], parameters[2], context, nodes)
) { }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ public static Value Equals(Value lhs, Value rhs, ExecutorContext context, Binary
}
}

{
// Vector
if (lhs is MatrixValue matrixLhs && rhs is MatrixValue matrixRhs) {
return new BooleanValue(matrixLhs.Equals(matrixRhs));
}
}

{
// Fallback real
if (lhs is IConvertibleToReal ctrLhs && rhs is IConvertibleToReal ctrRhs) {
Expand All @@ -54,7 +61,7 @@ public static Value Equals(Value lhs, Value rhs, ExecutorContext context, Binary
}
}

return new UndefinedValue(UndefinedValue.UndefinedType.Error);
return new BooleanValue(false, triviallyAchieved: false, BooleanValue.PresentationForm.FromComparison);
}


Expand Down
Loading
Loading