From 0f982887a3d8cb0eeb8e5b506552081f3ca5863d Mon Sep 17 00:00:00 2001 From: boris Date: Sun, 21 Jun 2026 00:20:56 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BC=93=E5=AD=98=E5=B9=B3=E5=8F=B0=E7=AD=96?= =?UTF-8?q?=E7=95=A5=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=85=83=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fidc-core/src/platform_expr_strategy.rs | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/crates/fidc-core/src/platform_expr_strategy.rs b/crates/fidc-core/src/platform_expr_strategy.rs index f746320..635cbab 100644 --- a/crates/fidc-core/src/platform_expr_strategy.rs +++ b/crates/fidc-core/src/platform_expr_strategy.rs @@ -511,6 +511,12 @@ pub struct PlatformExprStrategy { /// 命中计数与未命中计数,便于在 unit test 中验证缓存生效;非生产指标。 cache_hits: RefCell, cache_misses: RefCell, + normalized_prelude: String, + prelude_identifier_candidates: BTreeSet, + prelude_declared_identifiers: BTreeSet, + prelude_numeric_constants: HashMap, + compact_stock_filter_expr: String, + stock_filter_quote_usage: StockFilterQuoteUsage, } #[derive(Debug, Clone, PartialEq)] @@ -589,6 +595,15 @@ impl PlatformExprStrategy { engine.register_fn("trim", |value: &str| value.trim().to_string()); engine.register_fn("strlen", |value: &str| value.chars().count() as i64); engine.register_fn("code_number", code_number_value); + let normalized_prelude = Self::normalize_prelude_for_eval(&config.prelude); + let prelude_identifier_candidates = + Self::extract_identifier_candidates(&normalized_prelude); + let prelude_declared_identifiers = Self::declared_prelude_identifiers(&config.prelude); + let prelude_numeric_constants = Self::parse_prelude_numeric_constants(&normalized_prelude); + let normalized_stock_filter_expr = Self::normalize_expr(&config.stock_filter_expr); + let compact_stock_filter_expr = Self::compact_expr(&normalized_stock_filter_expr); + let stock_filter_quote_usage = + Self::stock_filter_quote_usage_for_expr(&normalized_stock_filter_expr); Self { config, engine, @@ -599,6 +614,12 @@ impl PlatformExprStrategy { compiled_cache: RefCell::new(HashMap::new()), cache_hits: RefCell::new(0), cache_misses: RefCell::new(0), + normalized_prelude, + prelude_identifier_candidates, + prelude_declared_identifiers, + prelude_numeric_constants, + compact_stock_filter_expr, + stock_filter_quote_usage, } } @@ -3024,9 +3045,8 @@ impl PlatformExprStrategy { position: Option<&PositionExpressionState>, ) -> Result { let normalized_expr = Self::normalize_expr(expr); - let prelude = Self::normalize_prelude_for_eval(&self.config.prelude); let normalized_identifiers = Self::extract_identifier_candidates(&normalized_expr); - let prelude_identifiers = Self::extract_identifier_candidates(&prelude); + let prelude_identifiers = &self.prelude_identifier_candidates; let include_day_factors = normalized_identifiers.contains("day_factors") || normalized_identifiers.contains("day_factor") || prelude_identifiers.contains("day_factors"); @@ -3046,11 +3066,10 @@ impl PlatformExprStrategy { ); let expanded_expr = self.expand_runtime_helpers(ctx, day, stock, &normalized_expr, &mut scope)?; - let prelude_declared_identifiers = Self::declared_prelude_identifiers(&self.config.prelude); if let Some(item) = stock { for identifier in Self::extract_identifier_candidates(&expanded_expr) { if Self::is_reserved_scope_name(identifier.as_str()) - || prelude_declared_identifiers.contains(&identifier) + || self.prelude_declared_identifiers.contains(&identifier) || (!day.available_factor_names.contains(&identifier) && !day.available_text_factor_names.contains(&identifier)) { @@ -3064,11 +3083,11 @@ impl PlatformExprStrategy { } } } - let mut script_parts = Vec::new(); - if !prelude.trim().is_empty() { - script_parts.push(prelude); + let mut script_parts = Vec::with_capacity(2); + if !self.normalized_prelude.trim().is_empty() { + script_parts.push(self.normalized_prelude.as_str()); } - script_parts.push(expanded_expr); + script_parts.push(expanded_expr.as_str()); let script = script_parts.join("\n"); self.eval_with_cache(&mut scope, &script) } @@ -3114,7 +3133,12 @@ impl PlatformExprStrategy { } fn prelude_numeric_constant(&self, name: &str) -> Option { - for line in Self::normalize_prelude_for_eval(&self.config.prelude).lines() { + self.prelude_numeric_constants.get(name).copied() + } + + fn parse_prelude_numeric_constants(normalized_prelude: &str) -> HashMap { + let mut constants = HashMap::new(); + for line in normalized_prelude.lines() { let trimmed = line.trim(); let Some(body) = trimmed.strip_prefix("let ") else { continue; @@ -3122,15 +3146,12 @@ impl PlatformExprStrategy { let Some((lhs, rhs)) = body.split_once('=') else { continue; }; - if lhs.trim() != name { - continue; - } let rhs = rhs.trim().trim_end_matches(';').trim(); if let Ok(value) = rhs.parse::() { - return Some(value); + constants.insert(lhs.trim().to_string(), value); } } - None + constants } fn normalize_prelude_for_eval(prelude: &str) -> String { @@ -5642,7 +5663,7 @@ impl PlatformExprStrategy { _day: &DayExpressionState, stock: &StockExpressionState, ) -> Option { - let compact = Self::compact_expr(&Self::normalize_expr(&self.config.stock_filter_expr)); + let compact = self.compact_stock_filter_expr.as_str(); let ma_ratio = self.prelude_numeric_constant("ma_ratio").unwrap_or(1.0); if compact == "stock_ma_short>stock_ma_mid*ma_ratio&&stock_ma_mid>stock_ma_long" { return Some( @@ -5654,7 +5675,7 @@ impl PlatformExprStrategy { ); } - let mut filter_body = compact.as_str(); + let mut filter_body = compact; let requires_min_listed_days = if let Some(rest) = filter_body.strip_prefix("listed_days>=min_listed_days&&") { filter_body = rest; @@ -6157,7 +6178,10 @@ impl PlatformExprStrategy { } fn stock_filter_quote_usage(&self) -> StockFilterQuoteUsage { - let expr = Self::normalize_runtime_field_aliases(&self.config.stock_filter_expr); + self.stock_filter_quote_usage + } + + fn stock_filter_quote_usage_for_expr(expr: &str) -> StockFilterQuoteUsage { let mut usage = StockFilterQuoteUsage::DailyOnly; for name in Self::extract_identifier_candidates(&expr) { match name.as_str() {