refactor(joinir): Phase 92 P1 - 箱化モジュール化・レガシー削除
P1-1: ConditionalStep lowering を1箱に隔離 - 新規作成: src/mir/join_ir/lowering/common/conditional_step_emitter.rs - emit_conditional_step_update() を carrier_update_emitter.rs から移動 - Fail-Fast 不変条件チェック追加(then_delta != else_delta) - 副作用を減らしたクリーンなインターフェース - 包括的なテストスイート(3テスト) P1-0: 境界SSOTの固定 - routing.rs: skeleton 設定をrouting層から削除 - pattern2_with_break.rs: skeleton 取得をlower()内部に閉じ込め - parity_checker から skeleton を直接取得 - skeleton の使用を Pattern2 のみに限定 P1-2: escape recognizer をSSOTに戻す - escape_pattern_recognizer.rs: 未使用フィールド削除 - quote_char, escape_char 削除(使われていない) - 責務を cond/delta 抽出のみに限定 - pattern_recognizer.rs: デフォルト値を使用 P1-3: E2Eテスト作成(実行は後回し) - apps/tests/test_pattern5b_escape_minimal.hako 作成 - body-local 変数対応後に検証予定 テスト結果: - conditional_step_emitter tests: 3 passed - Pattern2 tests: 18 passed - Regression: 0 failures 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
41
apps/tests/test_pattern5b_escape_minimal.hako
Normal file
41
apps/tests/test_pattern5b_escape_minimal.hako
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Phase 92 P1-3: Pattern 5b (Escape Sequence Handling) E2E Test
|
||||||
|
//
|
||||||
|
// This test verifies that ConditionalStep is correctly used for
|
||||||
|
// escape sequence handling patterns.
|
||||||
|
//
|
||||||
|
// Pattern structure:
|
||||||
|
// - Loop with conditional increment
|
||||||
|
// - if condition { i = i + 2 } else { i = i + 1 }
|
||||||
|
// - ConditionalStep carrier update (P5b pattern)
|
||||||
|
//
|
||||||
|
// NOTE: This is a simplified version without body-local variables
|
||||||
|
// for easier initial testing. Full P5b pattern support (with ch variable)
|
||||||
|
// will be tested in Phase 92 P2+.
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local i
|
||||||
|
local len
|
||||||
|
local count
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
len = 10
|
||||||
|
count = 0
|
||||||
|
|
||||||
|
// Simple conditional increment pattern
|
||||||
|
// (not a full escape sequence handler, but tests ConditionalStep)
|
||||||
|
loop(i < len) {
|
||||||
|
// Conditional increment based on even/odd position
|
||||||
|
if (i % 2) == 0 {
|
||||||
|
i = i + 2 // then_delta (even position: skip 2)
|
||||||
|
} else {
|
||||||
|
i = i + 1 // else_delta (odd position: skip 1)
|
||||||
|
}
|
||||||
|
count = count + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print(i)
|
||||||
|
print(count)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,17 +12,20 @@
|
|||||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||||
|
|
||||||
/// Information about a detected escape skip pattern
|
/// Information about a detected escape skip pattern
|
||||||
|
///
|
||||||
|
/// Phase 92 P1-2: Responsibility limited to cond/delta extraction only.
|
||||||
|
/// Body-local variable handling (`ch`) should be done by canonicalizer/caller.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EscapeSkipPatternInfo {
|
pub struct EscapeSkipPatternInfo {
|
||||||
pub counter_name: String,
|
pub counter_name: String,
|
||||||
pub normal_delta: i64,
|
pub normal_delta: i64,
|
||||||
pub escape_delta: i64,
|
pub escape_delta: i64,
|
||||||
pub quote_char: char,
|
|
||||||
pub escape_char: char,
|
|
||||||
pub body_stmts: Vec<ASTNode>,
|
|
||||||
/// Phase 92 P0-3: The condition expression for conditional increment
|
/// Phase 92 P0-3: The condition expression for conditional increment
|
||||||
/// e.g., `ch == '\\'` for escape sequence handling
|
/// e.g., `ch == '\\'` for escape sequence handling
|
||||||
pub escape_cond: Box<ASTNode>,
|
pub escape_cond: Box<ASTNode>,
|
||||||
|
/// Body statements before break check (for reference)
|
||||||
|
/// Note: Caller should handle body-local variable extraction (e.g., `ch`)
|
||||||
|
pub body_stmts: Vec<ASTNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Detect escape sequence handling pattern in loop body
|
/// Detect escape sequence handling pattern in loop body
|
||||||
@ -71,10 +74,8 @@ pub fn detect_escape_skip_pattern(body: &[ASTNode]) -> Option<EscapeSkipPatternI
|
|||||||
counter_name,
|
counter_name,
|
||||||
normal_delta,
|
normal_delta,
|
||||||
escape_delta,
|
escape_delta,
|
||||||
quote_char: '"', // Default for JSON/CSV (Phase 91 MVP)
|
|
||||||
escape_char: '\\', // Default for JSON/CSV (Phase 91 MVP)
|
|
||||||
body_stmts,
|
|
||||||
escape_cond, // Phase 92 P0-3: Condition for JoinIR Select
|
escape_cond, // Phase 92 P0-3: Condition for JoinIR Select
|
||||||
|
body_stmts,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -717,18 +717,34 @@ pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternCo
|
|||||||
///
|
///
|
||||||
/// Wrapper around cf_loop_pattern2_with_break to match router signature
|
/// Wrapper around cf_loop_pattern2_with_break to match router signature
|
||||||
/// Phase 200-C: Pass fn_body to cf_loop_pattern2_with_break
|
/// Phase 200-C: Pass fn_body to cf_loop_pattern2_with_break
|
||||||
/// Phase 92 P0-3: Pass skeleton for ConditionalStep support
|
/// Phase 92 P1-0: Retrieve skeleton internally for ConditionalStep support
|
||||||
pub(crate) fn lower(
|
pub(crate) fn lower(
|
||||||
builder: &mut MirBuilder,
|
builder: &mut MirBuilder,
|
||||||
ctx: &super::router::LoopPatternContext,
|
ctx: &super::router::LoopPatternContext,
|
||||||
) -> Result<Option<ValueId>, String> {
|
) -> Result<Option<ValueId>, String> {
|
||||||
|
// Phase 92 P1-0: Retrieve skeleton from parity checker if dev mode enabled
|
||||||
|
// verify_router_parity is a method on MirBuilder, accessible directly
|
||||||
|
let skeleton = if crate::config::env::joinir_dev_enabled() {
|
||||||
|
let (_parity_result, skeleton_opt) = builder.verify_router_parity(
|
||||||
|
ctx.condition,
|
||||||
|
ctx.body,
|
||||||
|
ctx.func_name,
|
||||||
|
ctx,
|
||||||
|
);
|
||||||
|
// Note: parity check errors are already logged by verify_router_parity
|
||||||
|
// We only need the skeleton for ConditionalStep support
|
||||||
|
skeleton_opt
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
builder.cf_loop_pattern2_with_break_impl(
|
builder.cf_loop_pattern2_with_break_impl(
|
||||||
ctx.condition,
|
ctx.condition,
|
||||||
ctx.body,
|
ctx.body,
|
||||||
ctx.func_name,
|
ctx.func_name,
|
||||||
ctx.debug,
|
ctx.debug,
|
||||||
ctx.fn_body,
|
ctx.fn_body,
|
||||||
ctx.skeleton, // Phase 92 P0-3: Pass skeleton for ConditionalStep
|
skeleton.as_ref(), // Phase 92 P1-0: Pass skeleton reference
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -304,19 +304,10 @@ impl MirBuilder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Phase 137-4: Router parity verification (after ctx is created)
|
// Phase 137-4: Router parity verification (after ctx is created)
|
||||||
// Phase 92 P0-2: Get skeleton from canonicalizer for Option A
|
// Phase 92 P1-0: Skeleton setting removed - patterns retrieve skeleton internally if needed
|
||||||
let skeleton_holder: Option<crate::mir::loop_canonicalizer::LoopSkeleton>;
|
|
||||||
if crate::config::env::joinir_dev_enabled() {
|
if crate::config::env::joinir_dev_enabled() {
|
||||||
let (result, skeleton_opt) = self.verify_router_parity(condition, body, func_name, &ctx);
|
let (result, _skeleton_opt) = self.verify_router_parity(condition, body, func_name, &ctx);
|
||||||
result?;
|
result?;
|
||||||
skeleton_holder = skeleton_opt;
|
|
||||||
if skeleton_holder.is_some() {
|
|
||||||
// Phase 92 P0-2: Set skeleton reference in context (must use holder lifetime)
|
|
||||||
// Note: This is safe because skeleton_holder lives for the entire scope
|
|
||||||
ctx.skeleton = skeleton_holder.as_ref();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
skeleton_holder = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(result) = route_loop_pattern(self, &ctx)? {
|
if let Some(result) = route_loop_pattern(self, &ctx)? {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
|
//! CFG sanity checks and dispatcher helpers for MIR-based lowering.
|
||||||
|
|
||||||
pub mod case_a;
|
pub mod case_a;
|
||||||
|
pub mod conditional_step_emitter; // Phase 92 P1-1: ConditionalStep emission module
|
||||||
|
|
||||||
use crate::mir::loop_form::LoopForm;
|
use crate::mir::loop_form::LoopForm;
|
||||||
use crate::mir::query::{MirQuery, MirQueryBox};
|
use crate::mir::query::{MirQuery, MirQueryBox};
|
||||||
|
|||||||
259
src/mir/join_ir/lowering/common/conditional_step_emitter.rs
Normal file
259
src/mir/join_ir/lowering/common/conditional_step_emitter.rs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
//! Phase 92 P1-1: ConditionalStep Emitter Module
|
||||||
|
//!
|
||||||
|
//! Specialized emitter for conditional step updates in P5b (escape sequence) patterns.
|
||||||
|
//! Extracted from carrier_update_emitter.rs for improved modularity and single responsibility.
|
||||||
|
//!
|
||||||
|
//! # Design
|
||||||
|
//!
|
||||||
|
//! - **Single Responsibility**: Handles only ConditionalStep emission with Fail-Fast validation
|
||||||
|
//! - **Isolated Logic**: No side effects, pure JoinIR generation
|
||||||
|
//! - **Clean Interface**: Exports only `emit_conditional_step_update()`
|
||||||
|
//!
|
||||||
|
//! # Fail-Fast Invariants
|
||||||
|
//!
|
||||||
|
//! 1. `then_delta != else_delta` - ConditionalStep must have different deltas
|
||||||
|
//! 2. Condition must be pure expression (no side effects)
|
||||||
|
|
||||||
|
use crate::ast::ASTNode;
|
||||||
|
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||||
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||||
|
use crate::mir::join_ir::lowering::condition_lowerer::lower_condition_to_joinir;
|
||||||
|
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst, VarId};
|
||||||
|
use crate::mir::{MirType, ValueId};
|
||||||
|
|
||||||
|
/// Emit JoinIR instructions for conditional step update (Phase 92 P1-1)
|
||||||
|
///
|
||||||
|
/// Handles the P5b escape sequence pattern where carrier update depends on a condition:
|
||||||
|
/// ```text
|
||||||
|
/// if escape_cond { carrier = carrier + then_delta }
|
||||||
|
/// else { carrier = carrier + else_delta }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This generates:
|
||||||
|
/// 1. Lower condition expression to get cond_id
|
||||||
|
/// 2. Compute then_result = carrier + then_delta
|
||||||
|
/// 3. Compute else_result = carrier + else_delta
|
||||||
|
/// 4. JoinInst::Select { dst: carrier_new, cond: cond_id, then_val: then_result, else_val: else_result }
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `carrier_name` - Name of the carrier variable (e.g., "i", "pos")
|
||||||
|
/// * `carrier_param` - ValueId of the carrier parameter in JoinIR
|
||||||
|
/// * `cond_ast` - AST node for the condition expression (e.g., `ch == '\\'`)
|
||||||
|
/// * `then_delta` - Delta to add when condition is true
|
||||||
|
/// * `else_delta` - Delta to add when condition is false
|
||||||
|
/// * `alloc_value` - ValueId allocator closure
|
||||||
|
/// * `env` - ConditionEnv for variable resolution
|
||||||
|
/// * `instructions` - Output vector to append instructions to
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// ValueId of the computed update result (the dst of Select)
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns error if:
|
||||||
|
/// - `then_delta == else_delta` (Fail-Fast: invariant violation)
|
||||||
|
/// - Condition lowering fails (Fail-Fast: must be pure expression)
|
||||||
|
pub fn emit_conditional_step_update(
|
||||||
|
carrier_name: &str,
|
||||||
|
carrier_param: ValueId,
|
||||||
|
cond_ast: &ASTNode,
|
||||||
|
then_delta: i64,
|
||||||
|
else_delta: i64,
|
||||||
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
|
env: &ConditionEnv,
|
||||||
|
instructions: &mut Vec<JoinInst>,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
// Phase 92 P1-1: Fail-Fast check - then_delta must differ from else_delta
|
||||||
|
if then_delta == else_delta {
|
||||||
|
return Err(format!(
|
||||||
|
"ConditionalStep invariant violated: then_delta ({}) must differ from else_delta ({}) for carrier '{}'",
|
||||||
|
then_delta, else_delta, carrier_name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Lower the condition expression
|
||||||
|
let (cond_id, cond_insts) = lower_condition_to_joinir(cond_ast, alloc_value, env).map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"ConditionalStep invariant violated: condition must be pure expression for carrier '{}': {}",
|
||||||
|
carrier_name, e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
instructions.extend(cond_insts);
|
||||||
|
|
||||||
|
// Step 2: Compute then_result = carrier + then_delta
|
||||||
|
let then_const_id = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: then_const_id,
|
||||||
|
value: ConstValue::Integer(then_delta),
|
||||||
|
}));
|
||||||
|
let then_result = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: then_result,
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
lhs: carrier_param,
|
||||||
|
rhs: then_const_id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Step 3: Compute else_result = carrier + else_delta
|
||||||
|
let else_const_id = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: else_const_id,
|
||||||
|
value: ConstValue::Integer(else_delta),
|
||||||
|
}));
|
||||||
|
let else_result = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: else_result,
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
lhs: carrier_param,
|
||||||
|
rhs: else_const_id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Step 4: Emit Select instruction
|
||||||
|
let carrier_new: VarId = alloc_value();
|
||||||
|
instructions.push(JoinInst::Select {
|
||||||
|
dst: carrier_new,
|
||||||
|
cond: cond_id,
|
||||||
|
then_val: then_result,
|
||||||
|
else_val: else_result,
|
||||||
|
type_hint: Some(MirType::Integer), // Carrier is always Integer
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(carrier_new)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||||
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||||
|
|
||||||
|
fn test_env() -> ConditionEnv {
|
||||||
|
let mut env = ConditionEnv::new();
|
||||||
|
env.insert("ch".to_string(), ValueId(10));
|
||||||
|
env.insert("i".to_string(), ValueId(20));
|
||||||
|
env
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ch_eq_backslash() -> ASTNode {
|
||||||
|
ASTNode::BinaryOp {
|
||||||
|
operator: BinaryOperator::Equal,
|
||||||
|
left: Box::new(ASTNode::Variable {
|
||||||
|
name: "ch".to_string(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
right: Box::new(ASTNode::Literal {
|
||||||
|
value: LiteralValue::String("\\".to_string()),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_conditional_step_basic() {
|
||||||
|
// Test: if ch == "\\" { i = i + 2 } else { i = i + 1 }
|
||||||
|
let env = test_env();
|
||||||
|
let cond_ast = ch_eq_backslash();
|
||||||
|
|
||||||
|
let mut value_counter = 100u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_conditional_step_update(
|
||||||
|
"i",
|
||||||
|
ValueId(20), // carrier_param
|
||||||
|
&cond_ast,
|
||||||
|
2, // then_delta (escape: i + 2)
|
||||||
|
1, // else_delta (normal: i + 1)
|
||||||
|
&mut alloc_value,
|
||||||
|
&env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result_id = result.unwrap();
|
||||||
|
|
||||||
|
// Should generate:
|
||||||
|
// 1. Condition lowering instructions (Compare)
|
||||||
|
// 2. Const(2), BinOp(Add, i, 2) for then_result
|
||||||
|
// 3. Const(1), BinOp(Add, i, 1) for else_result
|
||||||
|
// 4. Select(cond, then_result, else_result)
|
||||||
|
assert!(instructions.len() >= 6); // At least 6 instructions
|
||||||
|
|
||||||
|
// Check Select instruction exists
|
||||||
|
let select_found = instructions.iter().any(|inst| matches!(inst, JoinInst::Select { .. }));
|
||||||
|
assert!(select_found, "Select instruction should be emitted");
|
||||||
|
|
||||||
|
assert!(result_id.0 >= 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fail_fast_equal_deltas() {
|
||||||
|
// Phase 92 P1-1: Test Fail-Fast when then_delta == else_delta
|
||||||
|
let env = test_env();
|
||||||
|
let cond_ast = ch_eq_backslash();
|
||||||
|
|
||||||
|
let mut value_counter = 200u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_conditional_step_update(
|
||||||
|
"i",
|
||||||
|
ValueId(20),
|
||||||
|
&cond_ast,
|
||||||
|
2, // then_delta
|
||||||
|
2, // else_delta (SAME! Should fail)
|
||||||
|
&mut alloc_value,
|
||||||
|
&env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(err.contains("ConditionalStep invariant violated"));
|
||||||
|
assert!(err.contains("then_delta"));
|
||||||
|
assert!(err.contains("must differ from else_delta"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fail_fast_invalid_condition() {
|
||||||
|
// Phase 92 P1-1: Test Fail-Fast when condition is not pure
|
||||||
|
let env = ConditionEnv::new(); // Empty env - ch will not be found
|
||||||
|
|
||||||
|
let cond_ast = ch_eq_backslash();
|
||||||
|
|
||||||
|
let mut value_counter = 300u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_conditional_step_update(
|
||||||
|
"i",
|
||||||
|
ValueId(20),
|
||||||
|
&cond_ast,
|
||||||
|
2, // then_delta
|
||||||
|
1, // else_delta
|
||||||
|
&mut alloc_value,
|
||||||
|
&env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(err.contains("ConditionalStep invariant violated"));
|
||||||
|
assert!(err.contains("condition must be pure expression"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -330,8 +330,8 @@ pub fn try_extract_escape_skip_pattern(
|
|||||||
info.counter_name,
|
info.counter_name,
|
||||||
info.normal_delta,
|
info.normal_delta,
|
||||||
info.escape_delta,
|
info.escape_delta,
|
||||||
info.quote_char,
|
'"', // Phase 92 P1-2: Default quote_char for JSON/CSV
|
||||||
info.escape_char,
|
'\\', // Phase 92 P1-2: Default escape_char for JSON/CSV
|
||||||
info.body_stmts,
|
info.body_stmts,
|
||||||
info.escape_cond, // Phase 92 P0-3: Condition for JoinIR Select
|
info.escape_cond, // Phase 92 P0-3: Condition for JoinIR Select
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user