载入中...
搜索中...
未找到
SettlementRules.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cctype>
5#include <cmath>
6#include <cstdlib>
7#include <iomanip>
8#include <limits>
9#include <memory>
10#include <optional>
11#include <set>
12#include <sstream>
13#include <string_view>
14#include <utility>
15
16namespace eve::settlement {
17namespace {
18
19constexpr std::size_t kMaximumConditionSource = 4096;
20constexpr std::size_t kMaximumConditionNodes = 256;
21constexpr std::size_t kMaximumConditionDepth = 32;
22constexpr std::size_t kMaximumEvaluationSteps = 1024;
23
24enum class ExpressionType : std::uint8_t { Unknown, Boolean, Number, String };
25
26enum class ExpressionKind : std::uint8_t {
27 Literal,
28 Field,
29 HasTag,
30 Not,
31 Negate,
32 Add,
35 Divide,
36 Equal,
38 Less,
40 Greater,
42 And,
43 Or,
44 Minimum,
45 Maximum,
46 Clamp,
48};
49
50struct ExpressionNode {
51 ExpressionKind kind = ExpressionKind::Literal;
52 ExpressionType type = ExpressionType::Boolean;
53 std::uint16_t left = 0;
54 std::uint16_t right = 0;
55 std::uint16_t third = 0;
57 std::string name;
58};
59
60struct ExpressionProgram {
61 std::vector<ExpressionNode> nodes;
62 std::uint16_t root = 0;
63};
64
65enum class TokenKind : std::uint8_t { End, Identifier, Number, String, Symbol };
66
67struct Token {
68 TokenKind kind = TokenKind::End;
69 std::string text;
70 std::size_t line = 1;
71 std::size_t column = 1;
72};
73
74const char* typeName(ExpressionType type) noexcept {
75 switch (type) {
76 case ExpressionType::Unknown: return "Unknown";
77 case ExpressionType::Boolean: return "Boolean";
78 case ExpressionType::Number: return "Number";
79 case ExpressionType::String: return "String";
80 }
81 return "Unknown";
82}
83
84eve::Diagnostic expressionDiagnostic(eve::DiagnosticCode code, std::string message, std::string path,
85 const Token& token, std::string expected = {}, std::string actual = {}) {
86 eve::DiagnosticDetails details{{"line", std::to_string(token.line)}, {"column", std::to_string(token.column)}};
87 if (!expected.empty()) details.emplace_back("expected", std::move(expected));
88 if (!actual.empty()) details.emplace_back("actual", std::move(actual));
89 return eve::Diagnostic::error(code, message, path, details,
90 "settlement.condition");
91}
92
93eve::Result<std::vector<Token>> tokenize(std::string_view source, const std::string& path) {
94 std::vector<Token> tokens;
95 std::size_t offset = 0;
96 std::size_t line = 1;
97 std::size_t column = 1;
98 auto advance = [&](char value) {
99 ++offset;
100 if (value == '\n') {
101 ++line;
102 column = 1;
103 } else {
104 ++column;
105 }
106 };
107 while (offset < source.size()) {
108 const char value = source[offset];
109 if (value == ' ' || value == '\t' || value == '\r' || value == '\n') {
110 advance(value);
111 continue;
112 }
113 Token token;
114 token.line = line;
115 token.column = column;
116 if (std::isalpha(static_cast<unsigned char>(value)) || value == '_') {
117 token.kind = TokenKind::Identifier;
118 while (offset < source.size()) {
119 const char next = source[offset];
120 if (!std::isalnum(static_cast<unsigned char>(next)) && next != '_' && next != '.') break;
121 token.text.push_back(next);
122 advance(next);
123 }
124 tokens.push_back(std::move(token));
125 continue;
126 }
127 if (std::isdigit(static_cast<unsigned char>(value)) ||
128 (value == '.' && offset + 1 < source.size() &&
129 std::isdigit(static_cast<unsigned char>(source[offset + 1])))) {
130 token.kind = TokenKind::Number;
131 const std::size_t begin = offset;
132 char* end = nullptr;
133 const std::string remaining(source.substr(begin));
134 const double number = std::strtod(remaining.c_str(), &end);
135 if (end == remaining.c_str() || !std::isfinite(number))
136 return eve::Result<std::vector<Token>>::failure(
137 expressionDiagnostic(eve::DiagnosticCode::ParseError, "condition contains an invalid number", path,
138 token, "finite Number", token.text));
139 const auto length = static_cast<std::size_t>(end - remaining.c_str());
140 token.text.assign(source.substr(begin, length));
141 for (std::size_t index = 0; index < length; ++index) advance(source[begin + index]);
142 tokens.push_back(std::move(token));
143 continue;
144 }
145 if (value == '"') {
146 token.kind = TokenKind::String;
147 advance(value);
148 bool closed = false;
149 while (offset < source.size()) {
150 const char next = source[offset];
151 if (next == '"') {
152 advance(next);
153 closed = true;
154 break;
155 }
156 if (next == '\n' || next == '\r') break;
157 if (next == '\\') {
158 advance(next);
159 if (offset >= source.size()) break;
160 const char escaped = source[offset];
161 switch (escaped) {
162 case 'n': token.text.push_back('\n'); break;
163 case 'r': token.text.push_back('\r'); break;
164 case 't': token.text.push_back('\t'); break;
165 case '"': token.text.push_back('"'); break;
166 case '\\': token.text.push_back('\\'); break;
167 default:
168 return eve::Result<std::vector<Token>>::failure(expressionDiagnostic(
169 eve::DiagnosticCode::ParseError, "condition contains an unsupported string escape",
170 path, token, "supported escape", std::string("\\") + escaped));
171 }
172 advance(escaped);
173 continue;
174 }
175 token.text.push_back(next);
176 advance(next);
177 }
178 if (!closed)
179 return eve::Result<std::vector<Token>>::failure(
180 expressionDiagnostic(eve::DiagnosticCode::ParseError, "condition string is not terminated", path,
181 token, "closing quote"));
182 tokens.push_back(std::move(token));
183 continue;
184 }
185 token.kind = TokenKind::Symbol;
186 token.text.push_back(value);
187 if (offset + 1 < source.size()) {
188 const std::string_view pair = source.substr(offset, 2);
189 if (pair == "&&" || pair == "||" || pair == "==" || pair == "!=" || pair == "<=" || pair == ">=") {
190 token.text.push_back(source[offset + 1]);
191 advance(value);
192 advance(source[offset]);
193 tokens.push_back(std::move(token));
194 continue;
195 }
196 }
197 if (std::string_view("!<>+-*/(),").find(value) == std::string_view::npos)
198 return eve::Result<std::vector<Token>>::failure(
199 expressionDiagnostic(eve::DiagnosticCode::ParseError, "condition contains an unsupported character",
200 path, token, "condition operator", token.text));
201 advance(value);
202 tokens.push_back(std::move(token));
203 }
204 tokens.push_back(Token{TokenKind::End, {}, line, column});
205 return eve::Result<std::vector<Token>>::success(std::move(tokens));
206}
207
208class ExpressionParser {
209public:
210 ExpressionParser(std::vector<Token> tokens, std::string path)
211 : tokens_(std::move(tokens)), path_(std::move(path)) {}
212
214 const auto root = parseOr(0);
215 if (error_) return eve::Result<ExpressionProgram>::failure(std::move(*error_));
216 if (current().kind != TokenKind::End) {
217 fail("condition contains trailing input", "end of expression", current().text);
218 return eve::Result<ExpressionProgram>::failure(std::move(*error_));
219 }
220 if (nodes_[root].type != ExpressionType::Boolean) {
221 fail("condition root must be Boolean", "Boolean", typeName(nodes_[root].type));
222 return eve::Result<ExpressionProgram>::failure(std::move(*error_));
223 }
224 return eve::Result<ExpressionProgram>::success(ExpressionProgram{std::move(nodes_), root});
225 }
226
227private:
228 const Token& current() const noexcept { return tokens_[position_]; }
229
230 bool accept(std::string_view text) {
231 if (current().text != text) return false;
232 ++position_;
233 return true;
234 }
235
236 void expect(std::string_view text) {
237 if (!accept(text)) fail("condition expected '" + std::string(text) + "'", std::string(text), current().text);
238 }
239
240 void fail(std::string message, std::string expected = {}, std::string actual = {}) {
241 if (!error_)
242 error_ = expressionDiagnostic(eve::DiagnosticCode::ParseError, std::move(message), path_, current(),
243 std::move(expected), std::move(actual));
244 }
245
246 std::uint16_t add(ExpressionNode node) {
247 if (nodes_.size() >= kMaximumConditionNodes) {
248 fail("condition exceeds the node limit", std::to_string(kMaximumConditionNodes));
249 return 0;
250 }
251 nodes_.push_back(std::move(node));
252 return static_cast<std::uint16_t>(nodes_.size() - 1);
253 }
254
255 std::uint16_t binary(ExpressionKind kind, ExpressionType result, std::uint16_t left, std::uint16_t right,
256 ExpressionType operand, const Token& operation) {
257 if (error_) return 0;
258 bind(left, operand);
259 bind(right, operand);
260 if (nodes_[left].type != operand || nodes_[right].type != operand) {
261 error_ = expressionDiagnostic(
262 eve::DiagnosticCode::TypeMismatch, "condition operator has incompatible operands", path_, operation,
263 typeName(operand), std::string(typeName(nodes_[left].type)) + "," + typeName(nodes_[right].type));
264 return 0;
265 }
266 return add(ExpressionNode{kind, result, left, right});
267 }
268
269 void bind(std::uint16_t index, ExpressionType type) {
270 if (nodes_[index].type == ExpressionType::Unknown && nodes_[index].kind == ExpressionKind::Field)
271 nodes_[index].type = type;
272 }
273
274 std::uint16_t parseOr(std::size_t depth) {
275 auto left = parseAnd(depth + 1);
276 while (!error_ && current().text == "||") {
277 const auto operation = current();
278 ++position_;
279 left = binary(ExpressionKind::Or, ExpressionType::Boolean, left, parseAnd(depth + 1),
280 ExpressionType::Boolean, operation);
281 }
282 return left;
283 }
284
285 std::uint16_t parseAnd(std::size_t depth) {
286 auto left = parseEquality(depth + 1);
287 while (!error_ && current().text == "&&") {
288 const auto operation = current();
289 ++position_;
290 left = binary(ExpressionKind::And, ExpressionType::Boolean, left, parseEquality(depth + 1),
291 ExpressionType::Boolean, operation);
292 }
293 return left;
294 }
295
296 std::uint16_t parseEquality(std::size_t depth) {
297 auto left = parseComparison(depth + 1);
298 while (!error_ && (current().text == "==" || current().text == "!=")) {
299 const auto operation = current();
300 ++position_;
301 const auto right = parseComparison(depth + 1);
302 if (!error_ && nodes_[left].type == ExpressionType::Unknown &&
303 nodes_[right].type != ExpressionType::Unknown)
304 bind(left, nodes_[right].type);
305 if (!error_ && nodes_[right].type == ExpressionType::Unknown &&
306 nodes_[left].type != ExpressionType::Unknown)
307 bind(right, nodes_[left].type);
308 if (!error_ && nodes_[left].type == ExpressionType::Unknown &&
309 nodes_[right].type == ExpressionType::Unknown) {
310 error_ = expressionDiagnostic(eve::DiagnosticCode::TypeMismatch,
311 "condition cannot infer either equality operand type", path_, operation,
312 "one typed operand", "Unknown,Unknown");
313 return 0;
314 }
315 if (!error_ && nodes_[left].type != nodes_[right].type) {
316 error_ = expressionDiagnostic(eve::DiagnosticCode::TypeMismatch,
317 "condition equality operands must have the same type", path_, operation,
318 typeName(nodes_[left].type), typeName(nodes_[right].type));
319 return 0;
320 }
321 left = add(ExpressionNode{operation.text == "==" ? ExpressionKind::Equal : ExpressionKind::NotEqual,
322 ExpressionType::Boolean, left, right});
323 }
324 return left;
325 }
326
327 std::uint16_t parseComparison(std::size_t depth) {
328 auto left = parseAdditive(depth + 1);
329 while (!error_ &&
330 (current().text == "<" || current().text == "<=" || current().text == ">" || current().text == ">=")) {
331 const auto operation = current();
332 ++position_;
333 const auto right = parseAdditive(depth + 1);
334 ExpressionKind kind = ExpressionKind::Less;
335 if (operation.text == "<=")
336 kind = ExpressionKind::LessEqual;
337 else if (operation.text == ">")
338 kind = ExpressionKind::Greater;
339 else if (operation.text == ">=")
340 kind = ExpressionKind::GreaterEqual;
341 left = binary(kind, ExpressionType::Boolean, left, right, ExpressionType::Number, operation);
342 }
343 return left;
344 }
345
346 std::uint16_t parseAdditive(std::size_t depth) {
347 auto left = parseMultiplicative(depth + 1);
348 while (!error_ && (current().text == "+" || current().text == "-")) {
349 const auto operation = current();
350 ++position_;
351 left =
352 binary(operation.text == "+" ? ExpressionKind::Add : ExpressionKind::Subtract, ExpressionType::Number,
353 left, parseMultiplicative(depth + 1), ExpressionType::Number, operation);
354 }
355 return left;
356 }
357
358 std::uint16_t parseMultiplicative(std::size_t depth) {
359 auto left = parseUnary(depth + 1);
360 while (!error_ && (current().text == "*" || current().text == "/")) {
361 const auto operation = current();
362 ++position_;
363 const auto right = parseUnary(depth + 1);
364 if (!error_ && operation.text == "/" && nodes_[right].kind == ExpressionKind::Literal) {
365 if (const auto* value = nodes_[right].literal.getIf<double>(); value && *value == 0.0) {
366 error_ = expressionDiagnostic(eve::DiagnosticCode::InvalidArgument,
367 "condition contains constant division by zero", path_, operation,
368 "non-zero Number", "0");
369 return 0;
370 }
371 }
372 left = binary(operation.text == "*" ? ExpressionKind::Multiply : ExpressionKind::Divide,
373 ExpressionType::Number, left, right, ExpressionType::Number, operation);
374 }
375 return left;
376 }
377
378 std::uint16_t parseUnary(std::size_t depth) {
379 if (depth > kMaximumConditionDepth) {
380 fail("condition exceeds the nesting limit", std::to_string(kMaximumConditionDepth));
381 return 0;
382 }
383 if (current().text == "!" || current().text == "-") {
384 const auto operation = current();
385 ++position_;
386 const auto child = parseUnary(depth + 1);
387 const auto expected = operation.text == "!" ? ExpressionType::Boolean : ExpressionType::Number;
388 if (!error_) bind(child, expected);
389 if (!error_ && nodes_[child].type != expected) {
390 error_ = expressionDiagnostic(eve::DiagnosticCode::TypeMismatch,
391 "condition unary operator has an incompatible operand", path_, operation,
392 typeName(expected), typeName(nodes_[child].type));
393 return 0;
394 }
395 return add(
396 ExpressionNode{operation.text == "!" ? ExpressionKind::Not : ExpressionKind::Negate, expected, child});
397 }
398 return parsePrimary(depth + 1);
399 }
400
401 std::uint16_t parseFunction(std::string name, const Token& token, std::size_t depth) {
402 expect("(");
403 std::vector<std::uint16_t> arguments;
404 if (!error_ && current().text != ")") {
405 do {
406 arguments.push_back(parseOr(depth + 1));
407 } while (!error_ && accept(","));
408 }
409 expect(")");
410 if (error_) return 0;
411 if (name == "has_tag") {
412 if (arguments.size() != 1 || nodes_[arguments[0]].kind != ExpressionKind::Literal ||
413 nodes_[arguments[0]].type != ExpressionType::String) {
414 error_ = expressionDiagnostic(eve::DiagnosticCode::TypeMismatch, "has_tag expects one string literal",
415 path_, token, "has_tag(String literal)");
416 return 0;
417 }
418 ExpressionNode node{ExpressionKind::HasTag, ExpressionType::Boolean};
419 node.name = *nodes_[arguments[0]].literal.getIf<std::string>();
420 return add(std::move(node));
421 }
422 const std::size_t arity = name == "clamp" ? 3 : (name == "abs" ? 1 : 2);
423 if ((name != "min" && name != "max" && name != "clamp" && name != "abs") || arguments.size() != arity) {
424 error_ = expressionDiagnostic(eve::DiagnosticCode::ParseError,
425 "condition uses an unknown function or invalid arity", path_, token,
426 "min/max(2), clamp(3), abs(1), or has_tag(1)", name);
427 return 0;
428 }
429 for (const auto argument : arguments) {
430 bind(argument, ExpressionType::Number);
431 if (nodes_[argument].type != ExpressionType::Number) {
432 error_ =
433 expressionDiagnostic(eve::DiagnosticCode::TypeMismatch, "numeric function received a non-number",
434 path_, token, "Number", typeName(nodes_[argument].type));
435 return 0;
436 }
437 }
438 ExpressionNode node;
439 node.type = ExpressionType::Number;
440 node.left = arguments[0];
441 node.right = arguments.size() > 1 ? arguments[1] : 0;
442 node.third = arguments.size() > 2 ? arguments[2] : 0;
443 if (name == "min")
444 node.kind = ExpressionKind::Minimum;
445 else if (name == "max")
446 node.kind = ExpressionKind::Maximum;
447 else if (name == "clamp")
448 node.kind = ExpressionKind::Clamp;
449 else
450 node.kind = ExpressionKind::Absolute;
451 return add(std::move(node));
452 }
453
454 std::uint16_t parsePrimary(std::size_t depth) {
455 if (depth > kMaximumConditionDepth) {
456 fail("condition exceeds the nesting limit", std::to_string(kMaximumConditionDepth));
457 return 0;
458 }
459 const auto token = current();
460 if (accept("(")) {
461 const auto expression = parseOr(depth + 1);
462 expect(")");
463 return expression;
464 }
465 if (token.kind == TokenKind::Number) {
466 ++position_;
467 ExpressionNode node{ExpressionKind::Literal, ExpressionType::Number};
468 node.literal = eve::Value(std::strtod(token.text.c_str(), nullptr));
469 return add(std::move(node));
470 }
471 if (token.kind == TokenKind::String) {
472 ++position_;
473 ExpressionNode node{ExpressionKind::Literal, ExpressionType::String};
474 node.literal = eve::Value(token.text);
475 return add(std::move(node));
476 }
477 if (token.kind != TokenKind::Identifier) {
478 fail("condition expected a literal, field, function, or parenthesized expression", "expression",
479 token.text);
480 return 0;
481 }
482 ++position_;
483 if (token.text == "true" || token.text == "false") {
484 ExpressionNode node{ExpressionKind::Literal, ExpressionType::Boolean};
485 node.literal = eve::Value(token.text == "true");
486 return add(std::move(node));
487 }
488 if (current().text == "(") return parseFunction(token.text, token, depth + 1);
489 ExpressionNode node{ExpressionKind::Field, ExpressionType::Unknown};
490 node.name = token.text;
491 if (token.text == "kind" || token.text == "resource")
492 node.type = ExpressionType::String;
493 else if (token.text == "magnitude" || token.text == "tick")
494 node.type = ExpressionType::Number;
495 else if (!token.text.starts_with("context.") || token.text.size() == std::string_view("context.").size()) {
496 error_ = expressionDiagnostic(eve::DiagnosticCode::NotFound, "condition references an unknown field", path_,
497 token, "kind, resource, magnitude, tick, or context.<name>", token.text);
498 return 0;
499 }
500 return add(std::move(node));
501 }
502
503 std::vector<Token> tokens_;
504 std::string path_;
505 std::size_t position_ = 0;
506 std::vector<ExpressionNode> nodes_;
507 std::optional<eve::Diagnostic> error_;
508};
509
510struct EvaluatedValue {
511 ExpressionType type = ExpressionType::Boolean;
512 bool boolean = false;
513 double number = 0.0;
514 std::string string;
515};
516
517eve::Result<EvaluatedValue> evaluationFailure(eve::DiagnosticCode code, std::string message, std::string path) {
519 eve::Diagnostic::error(code, message, path, {}, "settlement.condition"));
520}
521
522const eve::Value* findContextValue(const eve::Value& root, std::string_view path) {
523 const eve::Value* current = &root;
524 while (!path.empty()) {
525 const auto split = path.find('.');
526 const auto part = path.substr(0, split);
527 if (part.empty() || !current->isObject()) return nullptr;
528 current = current->find(std::string(part));
529 if (!current) return nullptr;
530 if (split == std::string_view::npos) break;
531 path.remove_prefix(split + 1);
532 }
533 return current;
534}
535
536eve::Result<EvaluatedValue> evaluateNode(const ExpressionProgram& program, std::uint16_t index,
537 const SettlementRequest& request, std::size_t& steps) {
538 if (++steps > kMaximumEvaluationSteps)
539 return evaluationFailure(eve::DiagnosticCode::PreconditionViolation,
540 "condition exceeded the evaluation step limit", "when");
541 const auto& node = program.nodes[index];
542 if (node.kind == ExpressionKind::Literal) {
543 EvaluatedValue result;
544 result.type = node.type;
545 if (const auto* value = node.literal.getIf<bool>())
546 result.boolean = *value;
547 else if (const auto* value = node.literal.getIf<double>())
548 result.number = *value;
549 else if (const auto* value = node.literal.getIf<std::string>())
550 result.string = *value;
551 return eve::Result<EvaluatedValue>::success(std::move(result));
552 }
553 if (node.kind == ExpressionKind::HasTag) {
554 const bool present = std::find(request.tags.begin(), request.tags.end(), node.name) != request.tags.end();
555 EvaluatedValue result;
556 result.type = ExpressionType::Boolean;
557 result.boolean = present;
558 return eve::Result<EvaluatedValue>::success(std::move(result));
559 }
560 if (node.kind == ExpressionKind::Field) {
561 EvaluatedValue result;
562 result.type = node.type;
563 if (node.name == "kind")
564 result.string = request.kind;
565 else if (node.name == "resource")
566 result.string = request.resource;
567 else if (node.name == "magnitude")
568 result.number = request.magnitude;
569 else if (node.name == "tick")
570 result.number = static_cast<double>(request.tick.value());
571 else {
572 auto contextPath = std::string_view(node.name).substr(std::string_view("context.").size());
573 const auto* value = findContextValue(request.context, contextPath);
574 if (!value)
575 return evaluationFailure(eve::DiagnosticCode::NotFound, "condition context field is missing",
576 node.name);
577 if (node.type == ExpressionType::Boolean) {
578 const auto* typed = value->getIf<bool>();
579 if (!typed)
580 return evaluationFailure(eve::DiagnosticCode::TypeMismatch,
581 "condition context field is not Boolean", node.name);
582 result.boolean = *typed;
583 } else if (node.type == ExpressionType::String) {
584 const auto* typed = value->getIf<std::string>();
585 if (!typed)
586 return evaluationFailure(eve::DiagnosticCode::TypeMismatch, "condition context field is not String",
587 node.name);
588 result.string = *typed;
589 } else if (const auto* integer = value->getIf<std::int64_t>()) {
590 result.number = static_cast<double>(*integer);
591 } else if (const auto* real = value->getIf<double>(); real && std::isfinite(*real)) {
592 result.number = *real;
593 } else {
594 return evaluationFailure(eve::DiagnosticCode::TypeMismatch,
595 "condition context field is not a finite Number", node.name);
596 }
597 }
598 return eve::Result<EvaluatedValue>::success(std::move(result));
599 }
600 auto leftResult = evaluateNode(program, node.left, request, steps);
601 if (!leftResult) return leftResult;
602 auto left = std::move(leftResult).takeValue();
603 if (node.kind == ExpressionKind::Not) {
604 left.boolean = !left.boolean;
606 }
607 if (node.kind == ExpressionKind::Negate) {
608 left.number = -left.number;
610 }
611 if (node.kind == ExpressionKind::Absolute) {
612 left.number = std::abs(left.number);
614 }
615 if (node.kind == ExpressionKind::And && !left.boolean) return eve::Result<EvaluatedValue>::success(std::move(left));
616 if (node.kind == ExpressionKind::Or && left.boolean) return eve::Result<EvaluatedValue>::success(std::move(left));
617 auto rightResult = evaluateNode(program, node.right, request, steps);
618 if (!rightResult) return rightResult;
619 auto right = std::move(rightResult).takeValue();
620 EvaluatedValue result;
621 switch (node.kind) {
622 case ExpressionKind::Add:
623 result.type = ExpressionType::Number;
624 result.number = left.number + right.number;
625 break;
626 case ExpressionKind::Subtract:
627 result.type = ExpressionType::Number;
628 result.number = left.number - right.number;
629 break;
630 case ExpressionKind::Multiply:
631 result.type = ExpressionType::Number;
632 result.number = left.number * right.number;
633 break;
634 case ExpressionKind::Divide:
635 if (right.number == 0.0)
636 return evaluationFailure(eve::DiagnosticCode::InvalidArgument, "condition attempted division by zero",
637 "when");
638 result.type = ExpressionType::Number;
639 result.number = left.number / right.number;
640 break;
641 case ExpressionKind::Minimum:
642 result.type = ExpressionType::Number;
643 result.number = std::min(left.number, right.number);
644 break;
645 case ExpressionKind::Maximum:
646 result.type = ExpressionType::Number;
647 result.number = std::max(left.number, right.number);
648 break;
649 case ExpressionKind::Clamp: {
650 auto thirdResult = evaluateNode(program, node.third, request, steps);
651 if (!thirdResult) return thirdResult;
652 const auto third = std::move(thirdResult).takeValue();
653 if (right.number > third.number)
654 return evaluationFailure(eve::DiagnosticCode::InvalidArgument,
655 "condition clamp minimum exceeds maximum", "when");
656 result.type = ExpressionType::Number;
657 result.number = std::clamp(left.number, right.number, third.number);
658 break;
659 }
660 case ExpressionKind::Equal:
661 case ExpressionKind::NotEqual: {
662 result.type = ExpressionType::Boolean;
663 bool equal = false;
664 if (left.type == ExpressionType::Boolean)
665 equal = left.boolean == right.boolean;
666 else if (left.type == ExpressionType::Number)
667 equal = left.number == right.number;
668 else
669 equal = left.string == right.string;
670 result.boolean = node.kind == ExpressionKind::Equal ? equal : !equal;
671 break;
672 }
673 case ExpressionKind::Less:
674 result.type = ExpressionType::Boolean;
675 result.boolean = left.number < right.number;
676 break;
677 case ExpressionKind::LessEqual:
678 result.type = ExpressionType::Boolean;
679 result.boolean = left.number <= right.number;
680 break;
681 case ExpressionKind::Greater:
682 result.type = ExpressionType::Boolean;
683 result.boolean = left.number > right.number;
684 break;
685 case ExpressionKind::GreaterEqual:
686 result.type = ExpressionType::Boolean;
687 result.boolean = left.number >= right.number;
688 break;
689 case ExpressionKind::And:
690 result.type = ExpressionType::Boolean;
691 result.boolean = left.boolean && right.boolean;
692 break;
693 case ExpressionKind::Or:
694 result.type = ExpressionType::Boolean;
695 result.boolean = left.boolean || right.boolean;
696 break;
697 default:
698 return evaluationFailure(eve::DiagnosticCode::InvariantViolation,
699 "condition contains an invalid instruction", "when");
700 }
701 if (result.type == ExpressionType::Number && !std::isfinite(result.number))
702 return evaluationFailure(eve::DiagnosticCode::InvalidArgument, "condition produced a non-finite number",
703 "when");
704 return eve::Result<EvaluatedValue>::success(std::move(result));
705}
706
707std::string digestCondition(std::string_view source) {
708 std::uint64_t hash = 1469598103934665603ull;
709 constexpr std::string_view version = "settlement-condition-v1\n";
710 for (const char value : version) {
711 hash ^= static_cast<unsigned char>(value);
712 hash *= 1099511628211ull;
713 }
714 for (const char value : source) {
715 hash ^= static_cast<unsigned char>(value);
716 hash *= 1099511628211ull;
717 }
718 std::ostringstream stream;
719 stream << std::hex << std::setfill('0') << std::setw(16) << hash;
720 return stream.str();
721}
722
723struct CompiledExpression {
724 std::shared_ptr<const ExpressionProgram> program;
725 std::string digest;
726};
727
728eve::Result<CompiledExpression> compileExpression(std::string_view source, const std::string& path) {
729 if (source.size() > kMaximumConditionSource) {
730 Token token;
732 expressionDiagnostic(eve::DiagnosticCode::InvalidArgument, "condition exceeds the source length limit",
733 path, token, std::to_string(kMaximumConditionSource), std::to_string(source.size())));
734 }
735 auto tokenResult = tokenize(source, path);
736 if (!tokenResult) return eve::Result<CompiledExpression>::failure(tokenResult.status());
737 ExpressionParser parser(std::move(tokenResult).takeValue(), path);
738 auto programResult = parser.parse();
739 if (!programResult) return eve::Result<CompiledExpression>::failure(programResult.status());
740 CompiledExpression compiled;
741 compiled.program = std::make_shared<ExpressionProgram>(std::move(programResult).takeValue());
742 compiled.digest = digestCondition(source);
743 return eve::Result<CompiledExpression>::success(std::move(compiled));
744}
745
746bool contains(const std::vector<std::string>& values, const std::string& value) {
747 return std::find(values.begin(), values.end(), value) != values.end();
748}
749
750bool matches(const RuleFilter& filter, const SettlementRequest& request) {
751 if (!filter.kinds.empty() && !contains(filter.kinds, request.kind)) return false;
752 for (const auto& tag : filter.requiredTags)
753 if (!contains(request.tags, tag)) return false;
754 for (const auto& tag : filter.excludedTags)
755 if (contains(request.tags, tag)) return false;
756 return true;
757}
758
759eve::Result<void> applyRule(const SettlementRule& rule,
760 const std::function<eve::Result<bool>(const SettlementRequest&)>& condition,
761 const std::string& conditionDigest, SettlementContext& context) {
762 if (!matches(rule.filter, context.request()))
764 if (condition) {
765 auto accepted = condition(context.request());
766 if (!accepted) return eve::Result<void>::failure(accepted.status());
768 }
769 const double operand = rule.value + rule.valuePerExtraStack * static_cast<double>(rule.stacks - 1);
770 const double before = context.magnitude();
771 double removed = 0.0;
772 auto outcome = [&]() -> eve::Result<void> {
773 switch (rule.operation) {
774 case RuleOperation::Add: return context.setMagnitude(std::max(0.0, before + operand));
775 case RuleOperation::Multiply: return context.setMagnitude(before * operand);
777 removed = std::min(before, operand);
778 if (auto changed = context.setMagnitude(before - removed); !changed) return changed;
779 return context.addResisted(removed);
781 removed = before * operand;
782 if (auto changed = context.setMagnitude(before - removed); !changed) return changed;
783 return context.addResisted(removed);
785 removed = std::min(before, operand);
786 if (auto changed = context.setMagnitude(before - removed); !changed) return changed;
787 return context.addAbsorbed(removed);
788 case RuleOperation::ClampMaximum: return context.setClampMax(operand);
789 case RuleOperation::Critical: context.setCritical(operand != 0.0); return eve::Result<void>::success();
793 if (!context.request().source.isValid() || context.projectedResult().applied == 0.0)
795 const double magnitude = context.projectedResult().applied * operand;
796 if (!std::isfinite(magnitude))
798 eve::DiagnosticCode::InvariantViolation, "derived settlement magnitude overflowed",
799 "operation"));
800 SettlementRequest derived;
801 derived.source = rule.operation == RuleOperation::Reflect ? context.request().target
802 : context.request().source;
803 derived.target = context.request().source;
804 derived.kind = rule.operation == RuleOperation::Reflect ? "damage" : "heal";
805 derived.resource = context.request().resource;
806 derived.magnitude = magnitude;
807 derived.tick = context.request().tick;
808 derived.correlation = context.request().correlation;
809 derived.trigger = "rule:" + rule.id;
810 derived.tags.push_back(rule.operation == RuleOperation::Reflect ? "trigger:reflect"
811 : "trigger:lifesteal");
812 return context.emitDerived(std::move(derived));
813 }
814 }
816 "unknown settlement rule operation", "operation"));
817 }();
818 if (!outcome) return outcome;
819 if (context.request().trace == SettlementTraceLevel::Full) {
820 context.setStageDetail("rule", eve::Value(rule.id));
821 context.setStageDetail("source", eve::Value(rule.source));
822 context.setStageDetail("stacks", eve::Value(static_cast<std::int64_t>(rule.stacks)));
823 context.setStageDetail("operand", eve::Value(operand));
824 if (!conditionDigest.empty()) context.setStageDetail("condition_digest", eve::Value(conditionDigest));
825 }
827}
828
829} // namespace
830
831eve::Result<void> SettlementRuleSet::configure(std::vector<SettlementRule> rules) {
832 std::set<std::string> ids;
833 std::vector<ConditionFunction> conditions;
834 std::vector<std::string> conditionDigests;
835 conditions.reserve(rules.size());
836 conditionDigests.reserve(rules.size());
837 for (std::size_t index = 0; index < rules.size(); ++index) {
838 const auto& rule = rules[index];
839 const auto path = "rules[" + std::to_string(index) + "]";
840 if (rule.id.empty()) return eve::Result<void>::failure(
841 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule id must not be empty", path + ".id"));
842 if (!ids.insert(rule.id).second) return eve::Result<void>::failure(
843 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule ids must be unique", path + ".id"));
844 if (rule.stacks == 0) return eve::Result<void>::failure(
845 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule stack count must be positive", path + ".stacks"));
846 if (!std::isfinite(rule.value) || !std::isfinite(rule.valuePerExtraStack))
848 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule operands must be finite", path + ".value"));
849 if (rule.stage == StageKind::Validate || rule.stage == StageKind::Apply || rule.stage == StageKind::Event)
851 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule cannot mutate a terminal or validation phase", path + ".stage"));
852 if (rule.stage == StageKind::Decision && rule.operation != RuleOperation::Immune)
854 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "decision-stage rules currently support only immune outcomes", path + ".operation"));
855 if (rule.operation == RuleOperation::Immune && rule.stage != StageKind::Decision)
857 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "immune rules must run in the decision stage", path + ".stage"));
858 const bool triggerOperation =
859 rule.operation == RuleOperation::Lifesteal || rule.operation == RuleOperation::Reflect;
860 if (rule.stage == StageKind::Trigger && !triggerOperation)
862 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "trigger-stage rules require a derived-request operation", path + ".operation"));
863 if (triggerOperation && rule.stage != StageKind::Trigger)
865 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "derived-request rules must run in the trigger stage", path + ".stage"));
866 if ((rule.operation == RuleOperation::Multiply || rule.operation == RuleOperation::ResistFlat ||
867 rule.operation == RuleOperation::AbsorbFlat || rule.operation == RuleOperation::ClampMaximum ||
868 triggerOperation) &&
869 (rule.value < 0.0 || rule.valuePerExtraStack < 0.0))
871 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement rule operation requires non-negative operands", path + ".value"));
872 if (rule.operation == RuleOperation::ResistPercent &&
873 (rule.value < 0.0 || rule.value > 1.0 || rule.valuePerExtraStack < 0.0 ||
874 rule.value + rule.valuePerExtraStack * static_cast<double>(rule.stacks - 1) > 1.0))
876 eve::Diagnostic::error(eve::DiagnosticCode::InvalidArgument, "settlement resistance percentage must remain in [0,1]", path + ".value"));
877 if (rule.when.empty()) {
878 conditions.emplace_back();
879 conditionDigests.emplace_back();
880 } else {
881 auto compiled = compileExpression(rule.when, path + ".when");
882 if (!compiled) return eve::Result<void>::failure(compiled.status());
883 auto expression = std::move(compiled).takeValue();
884 auto program = std::move(expression.program);
885 conditions.emplace_back([program = std::move(program)](const SettlementRequest& request) {
886 std::size_t steps = 0;
887 auto evaluated = evaluateNode(*program, program->root, request, steps);
888 if (!evaluated) return eve::Result<bool>::failure(evaluated.status());
889 return eve::Result<bool>::success(std::move(evaluated).takeValue().boolean);
890 });
891 conditionDigests.push_back(std::move(expression.digest));
892 }
893 }
894 rules_ = std::move(rules);
895 conditions_ = std::move(conditions);
896 conditionDigests_ = std::move(conditionDigests);
898}
899
901 struct InstalledRule {
902 SettlementRule rule;
903 std::function<eve::Result<bool>(const SettlementRequest&)> condition;
904 std::string digest;
905 };
906 std::vector<InstalledRule> rules;
907 rules.reserve(rules_.size());
908 for (std::size_t index = 0; index < rules_.size(); ++index)
909 rules.push_back(InstalledRule{rules_[index], conditions_[index], conditionDigests_[index]});
910 std::sort(rules.begin(), rules.end(), [](const auto& left, const auto& right) {
911 const auto leftStage = static_cast<std::uint8_t>(left.rule.stage);
912 const auto rightStage = static_cast<std::uint8_t>(right.rule.stage);
913 if (leftStage != rightStage) return leftStage < rightStage;
914 if (left.rule.priority != right.rule.priority) return left.rule.priority < right.rule.priority;
915 return left.rule.id < right.rule.id;
916 });
917 for (auto& installed : rules) {
918 const auto stage = installed.rule.stage;
919 // Canonical policy stages use simple names (for example
920 // source_modifiers). The prefix deliberately sorts rules after the
921 // domain policy while Clamp's terminal boundary still sorts last.
922 const auto name = std::string("zz_rule.") + installed.rule.id;
923 const auto priority = installed.rule.priority;
924 auto added = pipeline.addStage(stage, name, priority,
925 [rule = std::move(installed.rule), condition = std::move(installed.condition),
926 digest = std::move(installed.digest)](SettlementContext& context) {
927 return applyRule(rule, condition, digest, context);
928 });
929 if (!added) return added;
930 }
932}
933
934} // namespace eve::settlement
ActionParameterOperation operation
double value
int root
Definition AnimSmr.cpp:119
int priority
float length
Definition CaveMesh.cpp:94
bool split
Definition CaveMesh.cpp:123
eve::Value condition
int column
std::map< std::string, Var > values
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
std::string message
DiagnosticCode code
const GltfImportRequest & request
std::unordered_map< const Graphics *, std::shared_ptr< Lifetime > > tokens
glm::uvec4 ids
HexVec3 left
HexVec3 right
std::string text
TokenKind kind
size_t offset
std::string name
std::vector< BvhNode > nodes
const std::string * tag
std::string path
Definition PlayHost.cpp:110
float begin
int steps
uint64_t token
const RoadNode * node
double number
int removed
std::string filter
double current
std::uint16_t third
std::string digest
eve::Value literal
std::string string
std::shared_ptr< const ExpressionProgram > program
Declarative, domain-neutral rules for buffs and gameplay effects.
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
const VegetationPresetContext & context
A structured explanation of a failed, degraded, or noteworthy result.
Definition Diagnostic.h:94
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
Value * find(const std::string &key) noexcept
Return an object member, or nullptr when absent.
Definition Value.cpp:124
Mutable calculation frame visible to policy and custom stage code.
Definition Settlement.h:338
Deterministically ordered, composable settlement pipeline.
Definition Settlement.h:542
eve::Result< void > addStage(StageKind kind, std::string name, int priority, StageFunction function)
Add one named custom stage; duplicate names are rejected.
eve::Result< void > configure(std::vector< SettlementRule > rules)
Atomically replace all rules after validating ids, operands and phases.
eve::Result< void > install(SettlementPipeline &pipeline) const
Install a stable snapshot as custom stages on a pipeline.
@ Number
Unsigned numeric literal; sign handling belongs to the parser.
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
ModelData parse(std::span< const uint8_t > bytes)
Parse.
WidgetDesc child(std::string id, std::vector< WidgetDesc > children, float width, float height)
Scrollable child region with an explicit size.
Definition Widget.cpp:635
std::vector< DiagnosticDetail > DiagnosticDetails
Owning collection of diagnostic details with stable insertion order.
Definition Diagnostic.h:88
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
@ TypeMismatch
A stable reference resolved to a different canonical domain type.
enum EVENGINE_API_FOUNDATION String
OT_STRING.
Definition Runtime.h:119
Domain-neutral input to one settlement operation.
Definition Settlement.h:107
One owning, serializable projection of a buff/effect contribution.