在表达式预检阶段校验值类型
This commit is contained in:
@@ -1348,6 +1348,7 @@ impl PlatformExprStrategy {
|
|||||||
/// This validates syntax only; identifiers and runtime values are resolved
|
/// This validates syntax only; identifiers and runtime values are resolved
|
||||||
/// later against the point-in-time execution scope.
|
/// later against the point-in-time execution scope.
|
||||||
pub fn validate_expression_syntax(&self) -> Result<(), BacktestError> {
|
pub fn validate_expression_syntax(&self) -> Result<(), BacktestError> {
|
||||||
|
self.validate_expression_value_kinds()?;
|
||||||
let normalized_prelude = Self::normalize_prelude_for_eval(&self.config.prelude);
|
let normalized_prelude = Self::normalize_prelude_for_eval(&self.config.prelude);
|
||||||
let mut expressions = vec![
|
let mut expressions = vec![
|
||||||
(
|
(
|
||||||
@@ -1565,6 +1566,88 @@ impl PlatformExprStrategy {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn trim_outer_parentheses_for_expression_literal(expression: &str) -> &str {
|
||||||
|
let bytes = expression.as_bytes();
|
||||||
|
let mut start = 0usize;
|
||||||
|
let mut end = bytes.len();
|
||||||
|
while start < end && bytes[start].is_ascii_whitespace() {
|
||||||
|
start += 1;
|
||||||
|
}
|
||||||
|
while end > start && bytes[end - 1].is_ascii_whitespace() {
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
if end <= start + 1 || bytes[start] != b'(' || bytes[end - 1] != b')' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
start += 1;
|
||||||
|
end -= 1;
|
||||||
|
while start < end && bytes[start].is_ascii_whitespace() {
|
||||||
|
start += 1;
|
||||||
|
}
|
||||||
|
while end > start && bytes[end - 1].is_ascii_whitespace() {
|
||||||
|
end -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&expression[start..end]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_single_string_literal_expression(expression: &str) -> bool {
|
||||||
|
let expression = Self::trim_outer_parentheses_for_expression_literal(expression).trim();
|
||||||
|
let Some(quote) = expression.chars().next() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if quote != '"' && quote != '\'' {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut escaped = false;
|
||||||
|
for (index, character) in expression.char_indices().skip(1) {
|
||||||
|
if escaped {
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if character == '\\' {
|
||||||
|
escaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if character == quote {
|
||||||
|
return expression[index + character.len_utf8()..].trim().is_empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_expression_value_kinds(&self) -> Result<(), BacktestError> {
|
||||||
|
if Self::is_single_string_literal_expression(&self.config.stock_filter_expr) {
|
||||||
|
return Err(BacktestError::Execution(
|
||||||
|
"field=stock_filter_expr must be a boolean expression; received a string literal"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
for (field, expression) in [
|
||||||
|
("refresh_rate_expr", self.config.refresh_rate_expr.as_str()),
|
||||||
|
("market_cap_lower_expr", self.config.market_cap_lower_expr.as_str()),
|
||||||
|
("market_cap_upper_expr", self.config.market_cap_upper_expr.as_str()),
|
||||||
|
("selection_limit_expr", self.config.selection_limit_expr.as_str()),
|
||||||
|
(
|
||||||
|
"selection_candidate_limit_expr",
|
||||||
|
self.config.selection_candidate_limit_expr.as_str(),
|
||||||
|
),
|
||||||
|
("buy_scale_expr", self.config.buy_scale_expr.as_str()),
|
||||||
|
("exposure_expr", self.config.exposure_expr.as_str()),
|
||||||
|
("stop_loss_expr", self.config.stop_loss_expr.as_str()),
|
||||||
|
("take_profit_expr", self.config.take_profit_expr.as_str()),
|
||||||
|
("rank_expr", self.config.rank_expr.as_str()),
|
||||||
|
] {
|
||||||
|
if Self::is_single_string_literal_expression(expression) {
|
||||||
|
return Err(BacktestError::Execution(format!(
|
||||||
|
"field={field} must be a numeric expression; received a string literal"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// 用 AST 缓存执行 script。命中:直接走 eval_ast_with_scope;未命中:先
|
/// 用 AST 缓存执行 script。命中:直接走 eval_ast_with_scope;未命中:先
|
||||||
/// engine.compile,再插入缓存,再 eval_ast_with_scope。任何编译/执行错误
|
/// engine.compile,再插入缓存,再 eval_ast_with_scope。任何编译/执行错误
|
||||||
/// 都按字符串包装为 BacktestError::Execution。
|
/// 都按字符串包装为 BacktestError::Execution。
|
||||||
|
|||||||
Reference in New Issue
Block a user