Design and Implementation of a Calculation DSL and Engine
The article presents a domain‑specific language that mimics Excel formulas, a stack‑based parser and recursive engine for evaluating calculations, and a multi‑layer architecture—including a dynamic priority scheduler—to efficiently resolve field dependencies, improve maintainability, and enable monitoring across large data systems.
In large data systems, massive calculation requirements often lead developers to embed complex logic directly in code, resulting in bloated and hard‑to‑maintain implementations. To address this, a domain‑specific language (DSL) is proposed to define calculation rules as an independent, reusable service.
What is a DSL? A domain‑specific language focuses on a particular application domain, unlike general‑purpose languages. It abstracts common patterns (e.g., SQL) to improve development efficiency.
Designing a Calculation DSL The DSL mirrors Excel’s calculation capabilities. For example, to compute the amount by which actual expenses exceed a budget:
IF(C2 > B2, IMSUB(C2, B2), 0) // C2: actual expense, B2: budget, IMSUB: subtractionCorresponding DSL syntax uses placeholders for fields:
IF({budget} > {actual_expenses}, IMSUB({budget}, {actual_expenses}), 0)Advantages of the DSL
Similar to Excel, reducing learning cost.
Formal definition makes the DSL more standardized.
Well‑designed DSL facilitates future extensions.
Calculation Engine Implementation
DSL Parsing A stack‑based parser handles nested DSL expressions. Example parsing code:
$dsl = 'IF({budget}>={actual_expenses},IMSUB({budget},{actual_expenses}),0,1)';
$stack = [];
$result = [];
$comparisonOperators = ['<', '>', '&', '|', '='];
$placeholders = [',', '(', ')'];
for ($index = 0; $index < strlen($dsl); $index++) {
$key = $dsl[$index];
switch ($key) {
case '}':
$variable = '';
while (true) {
$item = array_pop($stack);
if ($item === '{') break;
$variable = $item . $variable;
}
$result[] = $this->getVariable($variable);
break;
case '(':
$method = '';
while (true) {
$item = array_pop($stack);
if (is_null($item)) break;
$method = $item . $method;
}
$result[] = $method;
$result[] = $key;
break;
case in_array($key, $placeholders):
$variable = '';
while (true) {
$item = array_pop($stack);
if (is_null($item)) break;
$variable = $item . $variable;
}
if ($variable != '') $result[] = $variable;
$result[] = $key;
break;
case in_array($key, $comparisonOperators):
if ($dsl[$index + 1] == '=') {
$result[] = $key . '=';
$index++;
} else {
$result[] = $key;
}
break;
default:
$stack[] = $key;
break;
}
}Data Structuring After parsing, the DSL yields a tree‑like structure that can be populated with actual values from a predefined data model.
Recursive Calculation Each node may represent a function, a placeholder, or a constant. Recursion evaluates nested functions:
/**
* IF function core logic
*/
public function calculate() {
// Evaluate comparison result
if ($this->getComparativeResult()) {
return $this->getResult($this->params[1]); // true branch
} else {
return $this->getResult($this->params[2]); // false branch
}
}
/**
* Get calculation result
*/
public function getResult($params) {
// If the parameter is a function, recurse
if (is_array($params)) {
return (new Calculate($params))->calculate(); // recursive call
}
// Otherwise return the literal value
return $params;
}Architecture Overview
The system consists of five layers: Application, Communication, DSL Parsing, Core Calculation, and Persistence/Messaging. The DSL parsing layer and core calculation layer together form the calculation engine.
Project Integration
The architecture is divided into five tiers, with the upper application layer exposing APIs, the communication layer handling data exchange, the DSL parsing layer performing validation and structuring, the core calculation layer executing the logic, and the final layer persisting results and publishing messages.
Problems and Reflections
Efficiency Gains Are Slow Parallel execution does not linearly improve performance; beyond a certain concurrency level, gains diminish due to hidden dependencies.
Computation Dependencies Some fields depend on the results of others (e.g., B depends on A, C depends on A and B), creating bottlenecks.
Solution: Optimal Scheduling
The proposed strategy assigns priority levels based on dependency depth (e.g., A and D have priority 0, B and E priority 1, C priority 2). Fields with the same priority are computed in parallel, minimizing resource usage.
Dynamic Priority Algorithm When a field finishes, dependent fields are re‑evaluated and may be promoted to a higher priority for immediate execution.
Summary
The architecture now includes a strategy allocation layer between DSL parsing and core calculation. This layer schedules tasks based on dynamic priorities, handles events, and ensures efficient resource utilization. Monitoring is added at each stage (parsing, strategy allocation, calculation, persistence) to detect and log errors promptly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent Cloud Developer
Official Tencent Cloud community account that brings together developers, shares practical tech insights, and fosters an influential tech exchange community.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
