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