This nuget package provides extension functions for NCalc.
The NCalc documentation can be found here (source code) and here (good explanation of built-in functions).
Detailed per-function documentation is maintained in Documentation/README.md and generated pages under Documentation/.
Use RELEASE_NOTES.md to maintain upcoming changes under ## Unreleased.
- Run
Publish.ps1when the next publish should use the version currently reported by NBGV and no extra release-notes commit is needed. - Run
PublishWithReleaseNote.ps1when you want to finalizeRELEASE_NOTES.mdfirst. It stamps the current## Unreleasedsection with the next package version and date, creates a fresh## Unreleasedsection, commits that change, pushesmain, verifies the NBGV increment, and then callsPublish.ps1. PublishWithReleaseNote.ps1expectsmain, fetchesorigin/main, and only allows a clean working tree or changes limited to RELEASE_NOTES.md.
When using ExtendedExpression, you can:
- use "//" comments
- use "/* */" multi-line comments for documentation
- split expressions over multiple lines
- include empty lines
- indent lines
- define typed parameters and answers directly in comments using formats like:
// parameterName:type:value,// parameterName:type, and// answer:type:value
For example:
// This calculates the presence of a needle in a haystack
contains(
// Complicated haystack creation
'haystack ' + 'containing needle',
// The needle we are looking for
'needle'
)
You can provide typed parameter definitions in comment lines using either of these formats:
// parameterName:type:value
// parameterName:type
The legacy // parameterName:TypeName:value format is still supported.
You can also provide a typed answer using:
// answer:type:value
// answer:type
Examples:
// count:int:5// customerName:string:Ada// optionalName:string?:null// answer:string?
null is reserved as the explicit null marker for typed definitions. A literal string value of "null" is not supported in this format.
Multi-line documentation blocks (if used) should use markdown format:
// theAnswer:int:42
// theAnswer2:int:2
// theAnswer3:string:Blah
// theAnswer4:int:23
// answer:int:44
/*
# The answer plus one plus one
This adds one twice to theAnswer, which is a 32-bit integer in this example.
*/
theAnswer + 1 + 1
Supported simple types:
string,string?bool,bool?byte,byte?sbyte,sbyte?short,short?ushort,ushort?int,int?uint,uint?long,long?ulong,ulong?float,float?double,double?decimal,decimal?DateTime,DateTime?Guid,Guid?
Equivalent CLR type names such as System.Int32 and System.String? are also supported.
Example:
using PanoramicData.NCalcExtensions;
var expression = new ExtendedExpression("""
// count:int:5
// multiplier:decimal:2.5
// answer:decimal:12.5
/*
# Calculate total amount
Multiplies the count by the multiplier value.
*/
count * multiplier
""");
var result = expression.Evaluate(); // Returns: 12.5 (5 * 2.5)ExtendedExpression parses the input once at construction time and exposes the results as properties:
var expression = new ExtendedExpression("""
// x:int:10
// answer:int:11
/*
# My expression
Adds one to x.
*/
x + 1
""");
// Access parsed metadata
string? docs = expression.Documentation; // "# My expression\nAdds one to x."
var parameters = expression.ParameterDefinitions; // { "x": TypedDefinition.FromLiteral("int", "10") }
var answer = expression.AnswerDefinition; // TypedDefinition.FromLiteral("int", "11")
var hasExpectedAnswer = expression.HasExpectedAnswer; // true
var hasExpectedAnswerValue = expression.HasExpectedAnswerValue; // true
var expectedAnswer = expression.ExpectedAnswer; // 11
var comments = expression.Comments; // any non-parameter // comment lines
var doc = expression.Document; // full ExtendedExpressionDocument
// Access a cached SimpleExtendedExpression for repeated evaluation
var simple = expression.SimpleExtendedExpression; // lazy, same options/culture
var result = simple.Evaluate(); // 11The SimpleExtendedExpression accessor is created lazily on first access and reuses the same parsed document, so there is no double-parsing cost.
For advanced use cases where you need to:
- Extract documentation and parameters separately
- Reuse the same expression multiple times
- Cache tidied expressions for better performance
Use ExtendedExpressionDocumentParser to parse the expression once, and then use SimpleExtendedExpression for evaluation:
using PanoramicData.NCalcExtensions;
// Step 1: Parse once (including documentation and parameter extraction)
var multilineExpression = """
// x:int:10
// y:int:5
/*
# Add two numbers
This demonstrates adding two pre-defined parameters.
*/
x + y
""";
var document = ExtendedExpressionDocumentParser.Parse(multilineExpression);
// Step 2: Access parsed information
Console.WriteLine("Documentation: " + document.Documentation);
Console.WriteLine("Parameters: " + string.Join(", ", document.Parameters.Keys));
// Step 3: Create a SimpleExtendedExpression for evaluation
// (no re-parsing overhead)
var expression = new SimpleExtendedExpression(document.TidiedExpression, document);
var result = expression.Evaluate(); // Returns: 15The parsed document contains:
- OriginalExpression: The original multi-line input
- TidiedExpression: The cleaned expression ready for evaluation
- Documentation: Extracted from
/* */comments (markdown format) - Parameters: Dictionary of typed definitions from
// name:typeor// name:type:valuelines - Answer: Optional typed definition from
// answer:typeor// answer:type:value - Comments: Regular comment lines (not parameter definitions)
Use SimpleExtendedExpression for performance-critical scenarios where you:
- Have already parsed the expression with
ExtendedExpressionDocumentParser - Need to evaluate the same expression multiple times
- Want to avoid the overhead of re-parsing
The main difference from ExtendedExpression:
- Takes a pre-tidied expression (no parsing/tidying overhead)
- Can be created from an
ExtendedExpressionDocumentfor automatic parameter setup - Still supports all extension functions
- Lightweight and optimized for repeated evaluations
Input contract: SimpleExtendedExpression requires a pre-tidied expression. The constructor throws FormatException if:
- The expression contains newline characters (
\nor\r) — useExtendedExpressionorExtendedExpressionDocumentParserto tidy multi-line input first - The expression (after trimming whitespace) starts with
//or/*— comment markers must be stripped before constructing aSimpleExtendedExpression
Starting with version 5.8, this library has migrated from CoreCLR-NCalc to the NCalcSync package.
This brings improved performance, better maintainability, and bug fixes, but requires awareness when upgrading.
Version numbers align with the NCalcSync package for clarity.
- NuGet Package Dependency: The underlying NCalc implementation changed from
CoreCLR-NCalctoNCalcSync - Target Framework: Now targets .NET 9 only (previously supported .NET Standard 2.0)
- Constructor Options:
ExtendedExpressionnow provides two constructors for different use cases
For most users, no code changes are required. The default constructor continues to work with sensible defaults:
using PanoramicData.NCalcExtensions;
var expression = new ExtendedExpression("1 + 1");
var result = expression.Evaluate(); // Returns: 2The default constructor automatically applies these ExpressionOptions:
ExpressionOptions.NoCache- Expressions are not cached (safer for dynamic expressions)ExpressionOptions.NoStringTypeCoercion- Prevents automatic string-to-number conversionsExpressionOptions.StrictTypeMatching- Enforces type safety (e.g.,1 != '1')
If you need custom ExpressionOptions, use the overloaded constructor:
using PanoramicData.NCalcExtensions;
using NCalc;
using System.Globalization;
var expression = new ExtendedExpression(
"1 + 1",
ExpressionOptions.None, // Or combine options with |
CultureInfo.InvariantCulture
);
var result = expression.Evaluate();The ExpressionOptions enum from NCalcSync provides fine-grained control over expression evaluation:
None: Default NCalc behavior (allows caching, type coercion, etc.)NoCache: Disables expression caching (recommended for dynamic expressions)NoStringTypeCoercion: Prevents implicit string-to-number conversionsStrictTypeMatching: Enforces strict type comparisonsIgnoreCase: Makes string comparisons case-insensitiveAllowNullParameter: Allows null parameter valuesIterateParameters: Enables parameter iteration in expressions
For complete documentation on ExpressionOptions, see the NCalcSync documentation.
The CoreCLR-NCalc package is no longer actively maintained. Migrating to NCalcSync provides:
- Active maintenance and bug fixes
- Better performance
- Improved standard compliance
- Enhanced type safety options
The generated documentation index is now on the dedicated page:
That page contains the per-function indexes and the example links to NCalc101.
using PanoramicData.NCalcExtensions;
...
var calculation = "lastIndexOf('abcdefg', 'def')";
var nCalcExpression = new ExtendedExpression(calculation);
if (nCalcExpression.HasErrors())
{
throw new FormatException($"Could not evaluate expression: '{calculation}' due to {nCalcExpression.Error}.");
}
return nCalcExpression.Evaluate();Note that we regularly extend this library to add new functions. For those that further extend the functions, it is important to avoid future potential namespace conflicts. For this reason, we guarantee that we will never use the underscore character in any future function name and we recommend that any functions that you add DO include an underscore. For example, if you had a project called "My Project", we suggest that you name your functions as follows:
- mp_myFunction1()
- mp_myFunction2()
Pull requests are welcome! Please submit your requests for new functions in the form of pull requests.