Skip to main content

behavior_contracts/
primitives.rs

1//! primitives — codegen が直呼びできる安定 primitive 公開面(bc#36 / typed-codegen.md §3.4)。
2//!
3//! A0 = 公開面の確定(emitter 生成ロジックは A1/#37)。生成コードが「細粒度 primitive を
4//! 呼ぶ」ための callable surface。
5//!
6//! 最重要規律: **演算子意味論は再実装しない**、かつ **codegen は IR を出力しない**(bc no-ir
7//! invariant)。各 native primitive は被演算子を**評価済み Value** で受け、意味論 SSoT の
8//! [`crate::expr`] value-core(`arith`/`compare`/`make_obj`/… — `evaluate` の各アームと**同一実装**)
9//! へ委譲する。よって意味論(INT_OVERFLOW / PRECISION_LOSS / MOD_ZERO / NULL_REF / TYPE_MISMATCH /
10//! FORBIDDEN_KEY / code-point 順 等)は `evaluate` と完全一致(実装が一本なので divergence 不能)。
11//! 短絡(and/or/cond/coalesce)は生成コードが native 制御フローで綴じ、bool 検査だけ primitive を使う。
12//!
13//! 生 IR をそのまま実行する経路(`ir` exec surface / execute_bundle)は `crate::expr::evaluate` を
14//! 直接使う(primitives 面は経由しない)。
15//!
16//! 加えて run_plan / run_plan_parallel の bounded 並行 / skip・unproduced / hydration helper を
17//! 再エクスポートまたは薄く公開する(いずれも既存実装を SSoT として共有)。
18
19use crate::expr::{ExprFailure, ExprFailureCode};
20use crate::plan::RelationKind;
21use crate::value::Value;
22
23/// primitive の返却型(`crate::expr::evaluate` と同一)。
24pub type R = Result<Value, ExprFailure>;
25
26// ── (a) 演算子/構築/リテラルの codegen callable surface ───────────────────────
27// bc no-ir invariant: codegen は IR を出力しない。かつてここには「単一キー IR ノードを組んで
28// `evaluate` に委譲する」薄いラッパ(ref/obj/add/…/eval_expr)が並んでいたが、それは生成コードに
29// インタプリタを再導入する松葉杖だった。撤去済み。callable surface は下記 (f)/(f0) の *_native
30// のみ(被演算子を評価済み Value で受け、`crate::expr::*` value-core = evaluate と同一実装へ委譲)。
31// 生 IR の実行が要る経路(`ir` exec surface / execute_bundle)は `crate::expr::evaluate` を直接使う。
32
33// ── (f0) 演算子/構築/リテラルのネイティブ面(bc no-ir invariant)──────────────────
34//
35// 直線 emitter (A2 rust) は被演算子を native emit → `?` で評価済み Value を得て、下の native
36// primitive に **値のまま**渡す。これらは `evaluate`(インタプリタ)も `json!`(IR ノード
37// 構築)も**呼ばない**。意味論(overflow / finite / code-point 順 / MOD_ZERO / FORBIDDEN_KEY /
38// short-circuit の bool 検査 等)は SSoT の `crate::expr::*` value-core を Value 直で呼ぶ
39// ——core は `evaluate` と**実装が一本**なので divergence しない。短絡(and/or/coalesce/cond)は
40// 生成コード側が native 制御フローで表現し、bool 検査だけ `require_bool_native` を使う。
41
42/// add / sub / mul(被演算子は評価済み Value)。
43pub fn add_native(a: Value, b: Value) -> R {
44    crate::expr::arith("add", &a, &b)
45}
46pub fn sub_native(a: Value, b: Value) -> R {
47    crate::expr::arith("sub", &a, &b)
48}
49pub fn mul_native(a: Value, b: Value) -> R {
50    crate::expr::arith("mul", &a, &b)
51}
52pub fn neg_native(a: Value) -> R {
53    crate::expr::neg(&a)
54}
55pub fn div_native(a: Value, b: Value) -> R {
56    crate::expr::div(&a, &b)
57}
58pub fn mod_native(a: Value, b: Value) -> R {
59    crate::expr::rem(&a, &b)
60}
61pub fn eq_native(a: Value, b: Value) -> R {
62    crate::expr::eq_ne("eq", &a, &b)
63}
64pub fn ne_native(a: Value, b: Value) -> R {
65    crate::expr::eq_ne("ne", &a, &b)
66}
67pub fn lt_native(a: Value, b: Value) -> R {
68    crate::expr::compare("lt", &a, &b)
69}
70pub fn le_native(a: Value, b: Value) -> R {
71    crate::expr::compare("le", &a, &b)
72}
73pub fn gt_native(a: Value, b: Value) -> R {
74    crate::expr::compare("gt", &a, &b)
75}
76pub fn ge_native(a: Value, b: Value) -> R {
77    crate::expr::compare("ge", &a, &b)
78}
79pub fn len_native(a: Value) -> R {
80    crate::expr::len(&a)
81}
82/// not(bool 検査 → 反転)。
83pub fn not_native(a: Value) -> R {
84    Ok(Value::Bool(!crate::expr::require_bool(&a, "not")?))
85}
86
87/// obj 構築(キーは静的・順序保存で codegen が渡す。`__proto__` は FORBIDDEN_KEY)。
88pub fn obj_native(pairs: Vec<(String, Value)>) -> R {
89    crate::expr::make_obj(pairs)
90}
91/// arr 構築(要素は評価済み Value)。
92pub fn arr_native(elems: Vec<Value>) -> R {
93    Ok(Value::Arr(elems))
94}
95
96/// `{int:"…"}` リテラル。
97pub fn int_lit_native(s: &str) -> R {
98    crate::expr::int_lit(s)
99}
100/// `{float:n}` リテラル。
101pub fn float_lit_native(n: f64) -> R {
102    crate::expr::float_lit(n)
103}
104/// bare number リテラル(§2.3 分類)。
105pub fn number_lit_native(n: f64) -> R {
106    crate::expr::number_lit(n)
107}
108/// bare スカラリテラル(null / bool / string)。型付き `R` を返すので生成コードで型注釈不要。
109pub fn null_native() -> R {
110    Ok(Value::Null)
111}
112pub fn bool_native(b: bool) -> R {
113    Ok(Value::Bool(b))
114}
115pub fn str_native(s: &str) -> R {
116    Ok(Value::Str(s.to_string()))
117}
118
119/// 短絡の bool 検査(and/or/cond/not の生成 native 制御フローが Value→bool に使う)。
120pub fn require_bool_native(v: Value, ctx: &str) -> Result<bool, ExprFailure> {
121    crate::expr::require_bool(&v, ctx)
122}
123
124// ── (f) 脱解釈ネイティブ面(Phase B — typed-codegen.md §4.1)─────────────────────
125//
126// 単純式(`ref`/`refOpt`/`concat`)は**式ツリーを `evaluate` で解釈しない**。直線 emitter
127// (A2 rust) は各 port を下の native helper 直呼び+ネイティブ被演算子へ展開する。よって
128// これらの関数は `evaluate`(インタプリタ)も `json!`(IR ノード構築)も**呼ばない**——
129// scope walk と `String` 連結を直接行う。意味論(NULL_REF / MISSING_PROP / UNKNOWN_BINDING /
130// TYPE_MISMATCH / refOpt の null 伝播 / code-point 連結)は [`crate::expr::evaluate`] の
131// `ref`/`refOpt`/`concat` アームと**同一**になるよう写し取る(同じ ExprFailureCode を返す)。
132// 対照的に overflow/短絡系(add/sub/…/and/or/cond/coalesce)は fail-closed 意味論を SSoT に
133// 一本化するため上の `evaluate` 委譲 primitive を使い続ける(native 化しない)。
134
135fn expr_fail(code: ExprFailureCode, message: String) -> ExprFailure {
136    ExprFailure {
137        code,
138        message,
139        detail: None,
140    }
141}
142
143/// `ref` のネイティブ実装(`evaluate` 非経由)。scope→field を直接歩く。
144/// 中間 null は `NULL_REF`(`evaluate` の `ref` アームと同一)。
145pub fn ref_native(path: &[&str], scope: &[(String, Value)]) -> R {
146    ref_walk(path, scope, false)
147}
148
149/// `refOpt` のネイティブ実装(`evaluate` 非経由)。中間 null は null 伝播。
150pub fn ref_opt_native(path: &[&str], scope: &[(String, Value)]) -> R {
151    ref_walk(path, scope, true)
152}
153
154/// scope 束縛 head → object field を native に歩く(`eval_ref` の写像)。`optional=true` は
155/// 中間 null で null を返す(refOpt)、false は `NULL_REF`(ref)。
156fn ref_walk(path: &[&str], scope: &[(String, Value)], optional: bool) -> R {
157    let op = if optional { "refOpt" } else { "ref" };
158    if path.is_empty() {
159        return Err(expr_fail(
160            ExprFailureCode::InvalidNode,
161            format!("{op} expects a non-empty string path"),
162        ));
163    }
164    let head = path[0];
165    let mut cur: Value = match scope.iter().find(|(k, _)| k == head) {
166        Some((_, v)) => v.clone(),
167        None => {
168            return Err(expr_fail(
169                ExprFailureCode::UnknownBinding,
170                format!("unknown binding: {head}"),
171            ))
172        }
173    };
174    for &seg in &path[1..] {
175        match cur {
176            Value::Null => {
177                if optional {
178                    return Ok(Value::Null);
179                }
180                return Err(expr_fail(
181                    ExprFailureCode::NullRef,
182                    format!("null intermediate at .{seg} (use ?.)"),
183                ));
184            }
185            Value::Obj(ref pairs) => match pairs.iter().find(|(k, _)| k == seg) {
186                Some((_, v)) => cur = v.clone(),
187                None => {
188                    return Err(expr_fail(
189                        ExprFailureCode::MissingProp,
190                        format!("missing property .{seg}"),
191                    ))
192                }
193            },
194            ref other => {
195                return Err(expr_fail(
196                    ExprFailureCode::TypeMismatch,
197                    format!("cannot access .{seg} on {}", other.type_name()),
198                ))
199            }
200        }
201    }
202    Ok(cur)
203}
204
205/// `concat` のネイティブ実装(`evaluate` 非経由)。既にネイティブ評価済みの parts を
206/// `String::push_str` で連結する。非 string 部分は `TYPE_MISMATCH`(`evaluate` の `concat`
207/// アームと同一)。arity<2 は不正 IR(`InvalidNode`)。parts は emitter が各要素を再帰的に
208/// native emit した Value 列(=式ツリー解釈が消えている)。
209pub fn concat_native(parts: &[Value]) -> R {
210    if parts.len() < 2 {
211        return Err(expr_fail(
212            ExprFailureCode::InvalidNode,
213            format!("concat expects >= 2 args, got {}", parts.len()),
214        ));
215    }
216    let mut s = String::new();
217    for p in parts {
218        match p {
219            Value::Str(part) => s.push_str(part),
220            other => {
221                return Err(expr_fail(
222                    ExprFailureCode::TypeMismatch,
223                    format!(
224                        "concat: strings only (got {}; no implicit toString)",
225                        other.type_name()
226                    ),
227                ))
228            }
229        }
230    }
231    Ok(Value::Str(s))
232}
233
234// ── (c) bounded 並行プリミティブ(順序保存)─────────────────────────────────────
235/// 入力順を保存し in-flight を `concurrency` で上限する bounded 並行 map。
236///
237/// execution-plan.md §4 の determinism 規律(output[i]=worker(items[i]) / 完了順非依存 /
238/// bounded dispatch)を単体 primitive として公開する。runtime は同期のため
239/// [`std::thread::scope`] の scoped worker で実装する([`crate::plan::run_plan_parallel`] と同一機構)。
240///
241/// `worker` は `Fn + Sync`(並行呼び出し安全)を要求する。結果は index 固定スロットに書くため
242/// 完了順に依存しない(逐次 map と同一の結果列)。
243pub fn map_with_concurrency<T, U, F>(items: &[T], concurrency: i64, worker: F) -> Vec<U>
244where
245    T: Sync,
246    U: Send,
247    F: Fn(&T, usize) -> U + Sync,
248{
249    let n = items.len();
250    let limit = concurrency.max(1) as usize;
251    if limit <= 1 || n <= 1 {
252        return items
253            .iter()
254            .enumerate()
255            .map(|(i, it)| worker(it, i))
256            .collect();
257    }
258    // 各スロットは Option<U>。worker は disjoint な index にしか書かないので競合しない。
259    let mut slots: Vec<Option<U>> = (0..n).map(|_| None).collect();
260    let cursor = std::sync::atomic::AtomicUsize::new(0);
261    let workers = limit.min(n);
262    let worker_ref = &worker;
263    let items_ref = items;
264    {
265        // 生ポインタ経由で disjoint スロットへ書き込む(各 index は 1 worker のみが触る)。
266        struct SlotsPtr<U>(*mut Option<U>);
267        unsafe impl<U: Send> Sync for SlotsPtr<U> {}
268        let base = SlotsPtr(slots.as_mut_ptr());
269        std::thread::scope(|s| {
270            for _ in 0..workers {
271                let cursor = &cursor;
272                let base = &base;
273                s.spawn(move || loop {
274                    let i = cursor.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
275                    if i >= n {
276                        break;
277                    }
278                    let out = worker_ref(&items_ref[i], i);
279                    // SAFETY: index i はこの worker だけが取得した(AtomicUsize で一意)ので
280                    // slots[i] への書き込みは disjoint・データ競合なし。
281                    unsafe {
282                        *base.0.add(i) = Some(out);
283                    }
284                });
285            }
286        });
287    }
288    slots
289        .into_iter()
290        .map(|o| o.expect("all slots filled"))
291        .collect()
292}
293
294// ── (d) skip / unproduced-value helper ────────────────────────────────────────
295/// 未生成 Port(Skip)の表現値。single→Null / connection→空 connection。
296pub fn unproduced_value(kind: Option<RelationKind>) -> Value {
297    match kind {
298        Some(RelationKind::Connection) => skip_connection(),
299        _ => Value::Null,
300    }
301}
302/// single relation の未生成 = Null。
303pub fn skip_single() -> Value {
304    Value::Null
305}
306/// connection relation の未生成 = {items:[],cursor:null}。
307pub fn skip_connection() -> Value {
308    Value::Obj(vec![
309        ("items".to_string(), Value::Arr(vec![])),
310        ("cursor".to_string(), Value::Null),
311    ])
312}
313
314// ── (e) hydration / relation helper ───────────────────────────────────────────
315/// behavior.rs の map.into と同一規律の zip-attach helper。
316///
317/// `over` の各要素へ `key` で `values[k]` を書き戻した augment 済みリストを返す
318/// (over と同じ長さ・順序、親不変=コピー、guard skip 要素は無変更で pass-through)。
319/// `kept_idx` は augment 対象の over index(昇順)。augment 対象が object でなければ Err。
320pub fn hydrate_into(
321    over: &[Value],
322    key: &str,
323    kept_idx: &[usize],
324    values: &[Value],
325) -> Result<Vec<Value>, String> {
326    let mut augmented: Vec<Value> = Vec::with_capacity(over.len());
327    let mut k = 0usize;
328    for (i, el) in over.iter().enumerate() {
329        if k < kept_idx.len() && kept_idx[k] == i {
330            match el {
331                Value::Obj(pairs) => {
332                    let mut next: Vec<(String, Value)> = pairs.clone();
333                    // 既存 key を上書き(無ければ追記)。宣言順を保つ。
334                    if let Some(slot) = next.iter_mut().find(|(kk, _)| kk == key) {
335                        slot.1 = values[k].clone();
336                    } else {
337                        next.push((key.to_string(), values[k].clone()));
338                    }
339                    augmented.push(Value::Obj(next));
340                }
341                _ => {
342                    return Err(format!(
343                        "hydrate_into: element {i} is not an object (into requires object elements)"
344                    ))
345                }
346            }
347            k += 1;
348        } else {
349            augmented.push(el.clone());
350        }
351    }
352    Ok(augmented)
353}