Skip to content
ncalcPublic

About

Experimental NCalc rewrite around C# unions and extensibility.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

NCalcU

Warning

NCalcU is experimental and under active development, it will also not release until .NET 11 and C# 15 are officially released. Feedback is very welcome.

NCalcU is to NCalc what the Wii U is to the Wii. Basically the same name and ideas but different architecture.

It keeps the simplicity of evaluating expressions while redesigning the core around immutability, and extensibility.

For comparison, the original NCalc AST is mutable and the parser is not extensible.

The U stands for two core ideas:

  • Unions: expressions, values, nodes, results, and errors are modeled using C# union types.
  • (U)you: U can replace or extend the parser, evaluator, functions, operators, and execution behavior.

Evaluation

using NCalc;

var result = new Expression("1 + 2 * 3").Evaluate(); // 7L
var asyncResult = await new Expression("Sqrt(9) + 1").EvaluateAsync(); // 4d

An Expression parses lazily and retains its immutable AST. Reuse it with different contexts. For one-off string or already-parsed AST evaluation, use LogicalExpressionEvaluator.Default.Evaluate(...) or EvaluateAsync(...).

Options, values, and functions

Following Fluid's options and function conventions, configuration belongs in reusable options and per-execution data belongs in a context. Functions are delegates receiving arguments and a context, returning ValueTask<object?>.

using NCalc.Evaluation;

var options = new EvaluationOptions();
options.Values["tax"] = 1.2m;
options.Functions["double"] = async (arguments, context) =>
{
    arguments.RequireCount(1);
    var value = await arguments.AtAsync(0);
    return Convert.ToDecimal(value, context.Options.Culture) * 2;
};
options.Functions["answer"] = (arguments, context) =>
{
    arguments.RequireCount(0);
    return ValueTask.FromResult<object?>(42L);
};

var expression = new Expression("double(price) * tax", options);
var context = new EvaluationContext(options).SetValue("price", 10m);
var result = await expression.EvaluateAsync(context); // 24m

Evaluation extensions

var options = new EvaluationOptions 
{
    Parser = new CustomOperatorParser()
};
options.BinaryOperators["@@"] = async (operands, context) =>
{
    var left = Convert.ToDecimal(await operands.AtAsync(0), context.Options.Culture);
    var right = Convert.ToDecimal(await operands.AtAsync(1), context.Options.Culture);
    return left + right;
};

var result = new Expression("2 @@ 3", options).Evaluate(); // 5m

Semantics and errors

var result = await LogicalExpressionEvaluator.Default.TryEvaluateAsync("1 / 0");
var message = result switch
{
    LiteralValue value => $"Result: {value.Value}",
    ExpressionError error => $"Error: {error.Message}"
};

Parsing

using NCalc;
using NCalc.Parser;

var result = LogicalExpressionParser.Default.Parse("price * quantity ?? 0");
if (result is LogicalExpression expression)
{
    // Inspect expression.Value or match the union cases.
}
else if (result is Parlot.ParseError error)
{
    Console.WriteLine($"{error.Message} at {error.Position}");
}

Custom parsing

Override CreatePrimaryParser to add literal or unary syntax, or CreateExpressionParser to add operator levels.

using Parlot.Fluent;

// Add a new primary form (a quoted identifier).
public sealed class QuotedIdentifierParser : DisallowSingleEqualsParser
{
    protected override Parser<LogicalExpression> CreatePrimaryParser(
        Parser<LogicalExpression> expression)
        => DelimitedIdentifier("'{", "}'").Or(base.CreatePrimaryParser(expression));
}

var parser = new QuotedIdentifierParser();
parser.Parse("f('{Example}', x + 1)");

Custom operators

public sealed class CustomOperatorParser : LogicalExpressionParser
{
    protected override Parser<LogicalExpression> CreateExpressionParser(
        Parser<LogicalExpression> expression)
    {
        var standard = base.CreateExpressionParser(expression);
        return BinaryLevel(standard, Terms.Text("@@"));
    }
}

About

Experimental NCalc rewrite around C# unions and extensibility.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages