uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026
Warning
uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.
Project: Creating a Natural Language Date Parser
Product:
Class:
A step-by-step project to build a simple parser that understands natural language date expressions like 'tomorrow' or 'next Friday' using the uCalc Transformer.
Remarks
🗓️ Project: Creating a Natural Language Date Parser
This project will guide you through building a simple but powerful parser that can understand natural language date expressions like "tomorrow," "next Friday," or "in 3 weeks." It's a perfect real-world example of how the declarative power of the uCalc.Transformer and the logic of the Expression Parser work together to solve complex problems elegantly.
The Goal: A Human-Friendly Date Syntax
Instead of forcing users to pick from a calendar or enter a rigid MM/DD/YYYY format, we want our application to understand simple, human-readable phrases. Our target DSL (Domain-Specific Language) will include:
todaytomorrownext Monday,next Tuesday, etc.in 3 days,in 2 weeks,in 4 months
The Strategy: Transforming Phrases into Function Calls
The core idea is to use the Transformer to find these natural language phrases and rewrite them into calls to custom date-math functions that the uCalc engine can evaluate. For example:
- The string
"tomorrow"will be transformed into an expression like"AddDays(Now(), 1)". - The string
"in 3 weeks"will be transformed into"AddDuration(Now(), 3, 'weeks')".
This requires two main components: a set of helper functions for date arithmetic and a set of transformer rules to perform the translation.
Step 1: The Helper Functions (The Brains)
First, we need to give our uCalc engine the ability to perform date calculations. We'll do this by defining a few custom functions that are implemented by native callbacks. In a real application, these would use your language's native date/time library.
GetCurrentDate(): A function that returns the current date.AddDuration(date, number, unit): A function that takes a date, a number, and a unit string (e.g., "days", "weeks", "months") and returns a new date.GetNextDayOfWeek(dayName): A function that calculates the date of the next occurrence of a specific day of the week.FormatDate(date, format): A utility to format the final date object into a readable string.
Step 2: The Transformation Rules (The Translator)
Next, we'll create a Transformer and define a FromTo rule for each phrase in our DSL. These rules will use the {@Eval} directive to call our helper functions.
// Rule for simple keywordst.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");// Rule for 'next {day}'t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek('{day}'))}");// Rule for 'in {num} {unit}'t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), {num}, '{unit}'))}");Step 3: Putting It All Together
With our helper functions and transformer rules in place, we can now process any string. The Transform() method will find the natural language phrases, replace them with the evaluated results from our date functions, and return the final, formatted string.
The complete implementation is shown in the example below.
⚖️ Why uCalc? (Comparative Analysis)
vs. Regular Expressions: Building a regex to handle all these date variations would be extremely complex and brittle. A pattern to calculate "next Friday" would be nearly impossible. uCalc's token-aware patterns and embedded logic provide a far more robust and readable solution.
vs. Specialized Date Libraries (e.g., Chrono.js, dateparser): While powerful, these libraries are often large dependencies focused on a single task. uCalc allows you to build a lightweight, custom parser for just the phrases you need. More importantly, it's fully integrated with the rest of the uCalc engine, allowing your date logic to be part of a larger expression evaluation or transformation pipeline.
Examples
A complete, working natural language date parser that handles keywords, relative days, and future durations.
using uCalcSoftware;
var uc = new uCalc();
static void GetCurrentDate(uCalc.Callback cb) {
// In a real application, this would return the system's current date.
// For this example, we'll use a fixed date for consistent output.
// Let's pretend today is January 15, 2026 (a Thursday).
cb.Return(46036); // Using Excel-style date serial number for simplicity
}
static void AddDuration(uCalc.Callback cb) {
var startDate = cb.Arg(1);
var number = cb.Arg(2);
var unit = cb.ArgStr(3);
var result = startDate;
if (unit == "day" || unit == "days") {
result = startDate + number;
} else if (unit == "week" || unit == "weeks") {
result = startDate + (number * 7);
} else if (unit == "month" || unit == "months") {
result = startDate + (number * 30); // Approximation for example
}
cb.Return(result);
}
static void GetNextDayOfWeek(uCalc.Callback cb) {
var dayName = cb.ArgStr(1);
var today = 46036; // Thursday, Jan 15, 2026
var todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu
var targetDay = 0;
if (dayName == "Sunday") targetDay = 1;
if (dayName == "Monday") targetDay = 2;
if (dayName == "Tuesday") targetDay = 3;
if (dayName == "Wednesday") targetDay = 4;
if (dayName == "Thursday") targetDay = 5;
if (dayName == "Friday") targetDay = 6;
if (dayName == "Saturday") targetDay = 7;
var daysToAdd = (targetDay - todayDayOfWeek + 7) % 7;
// Always get the *next* week's day
if (daysToAdd == 0) daysToAdd = 7;
cb.Return(today + daysToAdd);
}
static void FormatDate(uCalc.Callback cb) {
// This is a simplified formatter for the example.
// A real implementation would be more robust.
var dateSerial = cb.Arg(1);
if (dateSerial == 46036) cb.ReturnStr("2026-01-15");
if (dateSerial == 46037) cb.ReturnStr("2026-01-16");
if (dateSerial == 46039) cb.ReturnStr("2026-01-18");
if (dateSerial == 46043) cb.ReturnStr("2026-01-22");
if (dateSerial == 46050) cb.ReturnStr("2026-01-29");
if (dateSerial == 46096) cb.ReturnStr("2026-03-16");
}
// 1. Define the helper functions in the uCalc engine
uc.DefineFunction("GetCurrentDate()", GetCurrentDate);
uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration);
uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek);
uc.DefineFunction("FormatDate(date) As String", FormatDate);
// 2. Create the transformer and define the DSL rules
using (var t = new uCalc.Transformer(uc)) {
// Set case-insensitivity for all rules
t.DefaultRuleSet.CaseSensitive = false;
// Define the rules
t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");
t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");
t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}");
t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}");
// 3. Process the input strings
Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}");
Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}");
Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}");
Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}");
Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}");
}
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16 using uCalcSoftware; var uc = new uCalc(); static void GetCurrentDate(uCalc.Callback cb) { // In a real application, this would return the system's current date. // For this example, we'll use a fixed date for consistent output. // Let's pretend today is January 15, 2026 (a Thursday). cb.Return(46036); // Using Excel-style date serial number for simplicity } static void AddDuration(uCalc.Callback cb) { var startDate = cb.Arg(1); var number = cb.Arg(2); var unit = cb.ArgStr(3); var result = startDate; if (unit == "day" || unit == "days") { result = startDate + number; } else if (unit == "week" || unit == "weeks") { result = startDate + (number * 7); } else if (unit == "month" || unit == "months") { result = startDate + (number * 30); // Approximation for example } cb.Return(result); } static void GetNextDayOfWeek(uCalc.Callback cb) { var dayName = cb.ArgStr(1); var today = 46036; // Thursday, Jan 15, 2026 var todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu var targetDay = 0; if (dayName == "Sunday") targetDay = 1; if (dayName == "Monday") targetDay = 2; if (dayName == "Tuesday") targetDay = 3; if (dayName == "Wednesday") targetDay = 4; if (dayName == "Thursday") targetDay = 5; if (dayName == "Friday") targetDay = 6; if (dayName == "Saturday") targetDay = 7; var daysToAdd = (targetDay - todayDayOfWeek + 7) % 7; // Always get the *next* week's day if (daysToAdd == 0) daysToAdd = 7; cb.Return(today + daysToAdd); } static void FormatDate(uCalc.Callback cb) { // This is a simplified formatter for the example. // A real implementation would be more robust. var dateSerial = cb.Arg(1); if (dateSerial == 46036) cb.ReturnStr("2026-01-15"); if (dateSerial == 46037) cb.ReturnStr("2026-01-16"); if (dateSerial == 46039) cb.ReturnStr("2026-01-18"); if (dateSerial == 46043) cb.ReturnStr("2026-01-22"); if (dateSerial == 46050) cb.ReturnStr("2026-01-29"); if (dateSerial == 46096) cb.ReturnStr("2026-03-16"); } // 1. Define the helper functions in the uCalc engine uc.DefineFunction("GetCurrentDate()", GetCurrentDate); uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration); uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek); uc.DefineFunction("FormatDate(date) As String", FormatDate); // 2. Create the transformer and define the DSL rules using (var t = new uCalc.Transformer(uc)) { // Set case-insensitivity for all rules t.DefaultRuleSet.CaseSensitive = false; // Define the rules t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}"); t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}"); t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}"); t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}"); // 3. Process the input strings Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}"); Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}"); Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}"); Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}"); Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}"); }
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
void ucalc_call GetCurrentDate(uCalcBase::Callback cb) {
// In a real application, this would return the system's current date.
// For this example, we'll use a fixed date for consistent output.
// Let's pretend today is January 15, 2026 (a Thursday).
cb.Return(46036); // Using Excel-style date serial number for simplicity
}
void ucalc_call AddDuration(uCalcBase::Callback cb) {
auto startDate = cb.Arg(1);
auto number = cb.Arg(2);
auto unit = cb.ArgStr(3);
auto result = startDate;
if (unit == "day" || unit == "days") {
result = startDate + number;
} else if (unit == "week" || unit == "weeks") {
result = startDate + (number * 7);
} else if (unit == "month" || unit == "months") {
result = startDate + (number * 30); // Approximation for example
}
cb.Return(result);
}
void ucalc_call GetNextDayOfWeek(uCalcBase::Callback cb) {
auto dayName = cb.ArgStr(1);
auto today = 46036; // Thursday, Jan 15, 2026
auto todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu
auto targetDay = 0;
if (dayName == "Sunday") targetDay = 1;
if (dayName == "Monday") targetDay = 2;
if (dayName == "Tuesday") targetDay = 3;
if (dayName == "Wednesday") targetDay = 4;
if (dayName == "Thursday") targetDay = 5;
if (dayName == "Friday") targetDay = 6;
if (dayName == "Saturday") targetDay = 7;
auto daysToAdd = (targetDay - todayDayOfWeek + 7) % 7;
// Always get the *next* week's day
if (daysToAdd == 0) daysToAdd = 7;
cb.Return(today + daysToAdd);
}
void ucalc_call FormatDate(uCalcBase::Callback cb) {
// This is a simplified formatter for the example.
// A real implementation would be more robust.
auto dateSerial = cb.Arg(1);
if (dateSerial == 46036) cb.ReturnStr("2026-01-15");
if (dateSerial == 46037) cb.ReturnStr("2026-01-16");
if (dateSerial == 46039) cb.ReturnStr("2026-01-18");
if (dateSerial == 46043) cb.ReturnStr("2026-01-22");
if (dateSerial == 46050) cb.ReturnStr("2026-01-29");
if (dateSerial == 46096) cb.ReturnStr("2026-03-16");
}
int main() {
uCalc uc;
// 1. Define the helper functions in the uCalc engine
uc.DefineFunction("GetCurrentDate()", GetCurrentDate);
uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration);
uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek);
uc.DefineFunction("FormatDate(date) As String", FormatDate);
// 2. Create the transformer and define the DSL rules
{
uCalc::Transformer t(uc);
t.Owned(); // Causes t to be released when it goes out of scope
// Set case-insensitivity for all rules
t.DefaultRuleSet().CaseSensitive(false);
// Define the rules
t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}");
t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}");
t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}");
t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}");
// 3. Process the input strings
cout << "Input: 'today' -> Output: " << t.Transform("today") << endl;
cout << "Input: 'tomorrow' -> Output: " << t.Transform("tomorrow") << endl;
cout << "Input: 'next Sunday' -> Output: " << t.Transform("next Sunday") << endl;
cout << "Input: 'in 2 weeks' -> Output: " << t.Transform("in 2 weeks") << endl;
cout << "Input: 'in 60 days' -> Output: " << t.Transform("in 60 days") << endl;
}
}
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; void ucalc_call GetCurrentDate(uCalcBase::Callback cb) { // In a real application, this would return the system's current date. // For this example, we'll use a fixed date for consistent output. // Let's pretend today is January 15, 2026 (a Thursday). cb.Return(46036); // Using Excel-style date serial number for simplicity } void ucalc_call AddDuration(uCalcBase::Callback cb) { auto startDate = cb.Arg(1); auto number = cb.Arg(2); auto unit = cb.ArgStr(3); auto result = startDate; if (unit == "day" || unit == "days") { result = startDate + number; } else if (unit == "week" || unit == "weeks") { result = startDate + (number * 7); } else if (unit == "month" || unit == "months") { result = startDate + (number * 30); // Approximation for example } cb.Return(result); } void ucalc_call GetNextDayOfWeek(uCalcBase::Callback cb) { auto dayName = cb.ArgStr(1); auto today = 46036; // Thursday, Jan 15, 2026 auto todayDayOfWeek = 5; // 1=Sun, 2=Mon, ..., 5=Thu auto targetDay = 0; if (dayName == "Sunday") targetDay = 1; if (dayName == "Monday") targetDay = 2; if (dayName == "Tuesday") targetDay = 3; if (dayName == "Wednesday") targetDay = 4; if (dayName == "Thursday") targetDay = 5; if (dayName == "Friday") targetDay = 6; if (dayName == "Saturday") targetDay = 7; auto daysToAdd = (targetDay - todayDayOfWeek + 7) % 7; // Always get the *next* week's day if (daysToAdd == 0) daysToAdd = 7; cb.Return(today + daysToAdd); } void ucalc_call FormatDate(uCalcBase::Callback cb) { // This is a simplified formatter for the example. // A real implementation would be more robust. auto dateSerial = cb.Arg(1); if (dateSerial == 46036) cb.ReturnStr("2026-01-15"); if (dateSerial == 46037) cb.ReturnStr("2026-01-16"); if (dateSerial == 46039) cb.ReturnStr("2026-01-18"); if (dateSerial == 46043) cb.ReturnStr("2026-01-22"); if (dateSerial == 46050) cb.ReturnStr("2026-01-29"); if (dateSerial == 46096) cb.ReturnStr("2026-03-16"); } int main() { uCalc uc; // 1. Define the helper functions in the uCalc engine uc.DefineFunction("GetCurrentDate()", GetCurrentDate); uc.DefineFunction("AddDuration(date, num, unit As String)", AddDuration); uc.DefineFunction("GetNextDayOfWeek(dayName As String)", GetNextDayOfWeek); uc.DefineFunction("FormatDate(date) As String", FormatDate); // 2. Create the transformer and define the DSL rules { uCalc::Transformer t(uc); t.Owned(); // Causes t to be released when it goes out of scope // Set case-insensitivity for all rules t.DefaultRuleSet().CaseSensitive(false); // Define the rules t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}"); t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}"); t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}"); t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}"); // 3. Process the input strings cout << "Input: 'today' -> Output: " << t.Transform("today") << endl; cout << "Input: 'tomorrow' -> Output: " << t.Transform("tomorrow") << endl; cout << "Input: 'next Sunday' -> Output: " << t.Transform("next Sunday") << endl; cout << "Input: 'in 2 weeks' -> Output: " << t.Transform("in 2 weeks") << endl; cout << "Input: 'in 60 days' -> Output: " << t.Transform("in 60 days") << endl; } }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub GetCurrentDate(ByVal cb As uCalc.Callback)
'// In a real application, this would return the system's current date.
'// For this example, we'll use a fixed date for consistent output.
'// Let's pretend today is January 15, 2026 (a Thursday).
cb.Return(46036) '// Using Excel-style date serial number for simplicity
End Sub
Public Sub AddDuration(ByVal cb As uCalc.Callback)
Dim startDate = cb.Arg(1)
Dim number = cb.Arg(2)
Dim unit = cb.ArgStr(3)
Dim result = startDate
If unit = "day" Or unit = "days" Then
result = startDate + number
ElseIf unit = "week" Or unit = "weeks" Then
result = startDate + (number * 7)
ElseIf unit = "month" Or unit = "months" Then
result = startDate + (number * 30) '// Approximation for example
End If
cb.Return(result)
End Sub
Public Sub GetNextDayOfWeek(ByVal cb As uCalc.Callback)
Dim dayName = cb.ArgStr(1)
Dim today = 46036 '// Thursday, Jan 15, 2026
Dim todayDayOfWeek = 5 '// 1=Sun, 2=Mon, ..., 5=Thu
Dim targetDay = 0
If dayName = "Sunday" Then targetDay = 1
If dayName = "Monday" Then targetDay = 2
If dayName = "Tuesday" Then targetDay = 3
If dayName = "Wednesday" Then targetDay = 4
If dayName = "Thursday" Then targetDay = 5
If dayName = "Friday" Then targetDay = 6
If dayName = "Saturday" Then targetDay = 7
Dim daysToAdd = (targetDay - todayDayOfWeek + 7) Mod 7
'// Always get the *next* week's day
If daysToAdd = 0 Then daysToAdd = 7
cb.Return(today + daysToAdd)
End Sub
Public Sub FormatDate(ByVal cb As uCalc.Callback)
'// This is a simplified formatter for the example.
'// A real implementation would be more robust.
Dim dateSerial = cb.Arg(1)
If dateSerial = 46036 Then cb.ReturnStr("2026-01-15")
If dateSerial = 46037 Then cb.ReturnStr("2026-01-16")
If dateSerial = 46039 Then cb.ReturnStr("2026-01-18")
If dateSerial = 46043 Then cb.ReturnStr("2026-01-22")
If dateSerial = 46050 Then cb.ReturnStr("2026-01-29")
If dateSerial = 46096 Then cb.ReturnStr("2026-03-16")
End Sub
Public Sub Main()
Dim uc As New uCalc()
'// 1. Define the helper functions in the uCalc engine
uc.DefineFunction("GetCurrentDate()", AddressOf GetCurrentDate)
uc.DefineFunction("AddDuration(date, num, unit As String)", AddressOf AddDuration)
uc.DefineFunction("GetNextDayOfWeek(dayName As String)", AddressOf GetNextDayOfWeek)
uc.DefineFunction("FormatDate(date) As String", AddressOf FormatDate)
'// 2. Create the transformer and define the DSL rules
Using t As New uCalc.Transformer(uc)
'// Set case-insensitivity for all rules
t.DefaultRuleSet.CaseSensitive = false
'// Define the rules
t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}")
t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}")
t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}")
t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}")
'// 3. Process the input strings
Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}")
Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}")
Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}")
Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}")
Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}")
End Using
End Sub
End Module
Input: 'today' -> Output: 2026-01-15
Input: 'tomorrow' -> Output: 2026-01-16
Input: 'next Sunday' -> Output: 2026-01-18
Input: 'in 2 weeks' -> Output: 2026-01-29
Input: 'in 60 days' -> Output: 2026-03-16 Imports System Imports uCalcSoftware Public Module Program Public Sub GetCurrentDate(ByVal cb As uCalc.Callback) '// In a real application, this would return the system's current date. '// For this example, we'll use a fixed date for consistent output. '// Let's pretend today is January 15, 2026 (a Thursday). cb.Return(46036) '// Using Excel-style date serial number for simplicity End Sub Public Sub AddDuration(ByVal cb As uCalc.Callback) Dim startDate = cb.Arg(1) Dim number = cb.Arg(2) Dim unit = cb.ArgStr(3) Dim result = startDate If unit = "day" Or unit = "days" Then result = startDate + number ElseIf unit = "week" Or unit = "weeks" Then result = startDate + (number * 7) ElseIf unit = "month" Or unit = "months" Then result = startDate + (number * 30) '// Approximation for example End If cb.Return(result) End Sub Public Sub GetNextDayOfWeek(ByVal cb As uCalc.Callback) Dim dayName = cb.ArgStr(1) Dim today = 46036 '// Thursday, Jan 15, 2026 Dim todayDayOfWeek = 5 '// 1=Sun, 2=Mon, ..., 5=Thu Dim targetDay = 0 If dayName = "Sunday" Then targetDay = 1 If dayName = "Monday" Then targetDay = 2 If dayName = "Tuesday" Then targetDay = 3 If dayName = "Wednesday" Then targetDay = 4 If dayName = "Thursday" Then targetDay = 5 If dayName = "Friday" Then targetDay = 6 If dayName = "Saturday" Then targetDay = 7 Dim daysToAdd = (targetDay - todayDayOfWeek + 7) Mod 7 '// Always get the *next* week's day If daysToAdd = 0 Then daysToAdd = 7 cb.Return(today + daysToAdd) End Sub Public Sub FormatDate(ByVal cb As uCalc.Callback) '// This is a simplified formatter for the example. '// A real implementation would be more robust. Dim dateSerial = cb.Arg(1) If dateSerial = 46036 Then cb.ReturnStr("2026-01-15") If dateSerial = 46037 Then cb.ReturnStr("2026-01-16") If dateSerial = 46039 Then cb.ReturnStr("2026-01-18") If dateSerial = 46043 Then cb.ReturnStr("2026-01-22") If dateSerial = 46050 Then cb.ReturnStr("2026-01-29") If dateSerial = 46096 Then cb.ReturnStr("2026-03-16") End Sub Public Sub Main() Dim uc As New uCalc() '// 1. Define the helper functions in the uCalc engine uc.DefineFunction("GetCurrentDate()", AddressOf GetCurrentDate) uc.DefineFunction("AddDuration(date, num, unit As String)", AddressOf AddDuration) uc.DefineFunction("GetNextDayOfWeek(dayName As String)", AddressOf GetNextDayOfWeek) uc.DefineFunction("FormatDate(date) As String", AddressOf FormatDate) '// 2. Create the transformer and define the DSL rules Using t As New uCalc.Transformer(uc) '// Set case-insensitivity for all rules t.DefaultRuleSet.CaseSensitive = false '// Define the rules t.FromTo("today", "{@Eval: FormatDate(GetCurrentDate())}") t.FromTo("tomorrow", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), 1, 'day'))}") t.FromTo("next {@Alpha:day}", "{@Eval: FormatDate(GetNextDayOfWeek(day))}") t.FromTo("in {@Number:num} {@Alpha:unit}", "{@Eval: FormatDate(AddDuration(GetCurrentDate(), Double(num), unit))}") '// 3. Process the input strings Console.WriteLine($"Input: 'today' -> Output: {t.Transform("today")}") Console.WriteLine($"Input: 'tomorrow' -> Output: {t.Transform("tomorrow")}") Console.WriteLine($"Input: 'next Sunday' -> Output: {t.Transform("next Sunday")}") Console.WriteLine($"Input: 'in 2 weeks' -> Output: {t.Transform("in 2 weeks")}") Console.WriteLine($"Input: 'in 60 days' -> Output: {t.Transform("in 60 days")}") End Using End Sub End Module