1use crate::runtime::globals::HashMap;
2use crate::runtime::{CommandContext, Value};
3
4pub const FM_VOID: i64 = 0;
5pub const FM_INT: i64 = 10;
6pub const FM_INTLIST: i64 = 11;
7pub const FM_STR: i64 = 20;
8pub const FM_STRLIST: i64 = 21;
9pub const FM_LABEL: i64 = 30;
10
11pub fn ret_form_is_string(ret_form: i64) -> bool {
12 ret_form == FM_STR
13}
14
15pub fn ret_form_is_string_opt(ret_form: Option<i64>) -> bool {
16 matches!(ret_form, Some(rf) if ret_form_is_string(rf))
17}
18
19pub fn parse_element_chain(form_id: u32, args: &[Value]) -> Option<(usize, &[i32])> {
20 for (i, v) in args.iter().enumerate() {
21 if let Value::Element(ch) = v
22 && ch.first().copied() == Some(form_id as i32)
23 {
24 return Some((i, ch.as_slice()));
25 }
26 }
27 None
28}
29
30pub fn parse_element_chain_ctx<'a>(
31 ctx: &'a CommandContext,
32 form_id: u32,
33 args: &'a [Value],
34) -> Option<(usize, &'a [i32])> {
35 let vm_call = ctx.vm_call.as_ref()?;
36 if vm_call.element.first().copied() == Some(form_id as i32) {
37 return Some((args.len(), vm_call.element.as_slice()));
38 }
39 None
40}
41
42pub fn parse_current_element_chain<'a>(
43 ctx: &'a CommandContext,
44 args: &'a [Value],
45) -> Option<(usize, &'a [i32])> {
46 let vm_call = ctx.vm_call.as_ref()?;
47 Some((args.len(), vm_call.element.as_slice()))
48}
49
50pub fn script_args(args: &[Value], chain_pos: usize) -> &[Value] {
51 if chain_pos == args.len() {
52 args
53 } else if chain_pos > 1 {
54 &args[1..chain_pos]
55 } else {
56 &[]
57 }
58}
59
60pub fn current_op_from_chain(chain: &[i32]) -> Option<i32> {
61 chain.get(1).copied()
62}
63
64pub fn current_op_from_ctx_or_args(ctx: &CommandContext, _args: &[Value]) -> Option<i32> {
65 let vm_call = ctx.vm_call.as_ref()?;
66 current_op_from_chain(&vm_call.element)
67}
68
69pub fn params_without_op<'a>(_ctx: &CommandContext, args: &'a [Value]) -> &'a [Value] {
70 args
71}
72
73pub fn current_vm_meta(ctx: &CommandContext) -> (Option<i64>, Option<i64>) {
74 ctx.vm_call
75 .as_ref()
76 .map(|m| (Some(m.al_id), Some(m.ret_form)))
77 .unwrap_or((None, None))
78}
79
80pub fn infer_assign_and_ret_ctx(
81 ctx: &CommandContext,
82 _chain_pos: usize,
83 args: &[Value],
84) -> (Option<i64>, Option<i64>, Option<Value>) {
85 let (meta_al, meta_ret) = current_vm_meta(ctx);
86 let rhs = if meta_al == Some(1) {
87 args.first().cloned()
88 } else {
89 None
90 };
91 (meta_al, meta_ret, rhs)
92}
93
94fn int_map(ctx: &mut CommandContext, form_id: u32) -> &mut HashMap<i32, i64> {
95 ctx.globals.int_props.entry(form_id).or_default()
96}
97
98fn str_map(ctx: &mut CommandContext, form_id: u32) -> &mut HashMap<i32, String> {
99 ctx.globals.str_props.entry(form_id).or_default()
100}
101
102fn int_list(ctx: &mut CommandContext, form_id: u32) -> &mut Vec<i64> {
103 let fixed_len = fixed_int_list_len(ctx, form_id);
104 let initial_len = fixed_len.unwrap_or(0);
105 let list = ctx
106 .globals
107 .int_lists
108 .entry(form_id)
109 .or_insert_with(|| vec![0; initial_len]);
110 if let Some(fixed_len) = fixed_len
111 && list.len() < fixed_len
112 {
113 list.resize(fixed_len, 0);
114 }
115 list
116}
117
118fn str_list(ctx: &mut CommandContext, form_id: u32) -> &mut Vec<String> {
119 let fixed_len = fixed_str_list_len(ctx, form_id);
120 let initial_len = fixed_len.unwrap_or(0);
121 let list = ctx
122 .globals
123 .str_lists
124 .entry(form_id)
125 .or_insert_with(|| vec![String::new(); initial_len]);
126 if let Some(fixed_len) = fixed_len
127 && list.len() < fixed_len
128 {
129 list.resize_with(fixed_len, String::new);
130 }
131 list
132}
133
134fn fixed_int_list_len(ctx: &CommandContext, form_id: u32) -> Option<usize> {
135 super::int_list::fixed_default_len(ctx, form_id)
136}
137
138fn fixed_str_list_len(ctx: &CommandContext, form_id: u32) -> Option<usize> {
139 super::str_list::fixed_default_len(ctx, form_id)
140}
141
142fn ensure_int_index(ctx: &mut CommandContext, form_id: u32, index: usize) -> bool {
143 super::int_list::ensure_fixed_direct_index(ctx, form_id, index);
144 if let Some(fixed_len) = fixed_int_list_len(ctx, form_id) {
145 let list = int_list(ctx, form_id);
146 if index < list.len() {
147 return true;
148 }
149 log::error!(
150 "INTLIST reference index out of range: form_id={} index={} size={} configured_size={}",
151 form_id,
152 index,
153 list.len(),
154 fixed_len
155 );
156 return false;
157 }
158
159 let list = int_list(ctx, form_id);
160 if index < list.len() {
161 return true;
162 }
163 log::error!(
164 "INTLIST reference index out of range: form_id={} index={} size={}",
165 form_id,
166 index,
167 list.len()
168 );
169 false
170}
171
172fn ensure_str_index(ctx: &mut CommandContext, form_id: u32, index: usize) -> bool {
173 super::str_list::ensure_fixed_direct_index(ctx, form_id, index);
174 if let Some(fixed_len) = fixed_str_list_len(ctx, form_id) {
175 let list = str_list(ctx, form_id);
176 if index < list.len() {
177 return true;
178 }
179 log::error!(
180 "STRLIST reference index out of range: form_id={} index={} size={} configured_size={}",
181 form_id,
182 index,
183 list.len(),
184 fixed_len
185 );
186 return false;
187 }
188
189 let list = str_list(ctx, form_id);
190 if index < list.len() {
191 return true;
192 }
193 log::error!(
194 "STRLIST reference index out of range: form_id={} index={} size={}",
195 form_id,
196 index,
197 list.len()
198 );
199 false
200}
201
202fn prefers_string(ret_form: Option<i64>, rhs: Option<&Value>) -> bool {
203 ret_form_is_string_opt(ret_form) || matches!(rhs, Some(Value::Str(_)))
204}
205
206pub fn chain_key(parts: &[i32]) -> i32 {
207 let mut h: u32 = 0x811C_9DC5;
208 for &p in parts {
209 h ^= p as u32;
210 h = h.wrapping_mul(0x0100_0193);
211 }
212 h as i32
213}
214
215pub fn push_stored_or_default(
216 ctx: &mut CommandContext,
217 form_id: u32,
218 op: i32,
219 ret_form: Option<i64>,
220) {
221 if ret_form_is_string_opt(ret_form) {
222 let s = ctx
223 .globals
224 .str_props
225 .get(&form_id)
226 .and_then(|m| m.get(&op))
227 .cloned()
228 .unwrap_or_default();
229 ctx.push(Value::Str(s));
230 return;
231 }
232
233 if let Some(s) = ctx
234 .globals
235 .str_props
236 .get(&form_id)
237 .and_then(|m| m.get(&op))
238 .cloned()
239 {
240 ctx.push(Value::Str(s));
241 return;
242 }
243
244 let v = ctx
245 .globals
246 .int_props
247 .get(&form_id)
248 .and_then(|m| m.get(&op).copied())
249 .unwrap_or(0);
250 ctx.push(Value::Int(v));
251}
252
253pub fn store_or_push_prop(
254 ctx: &mut CommandContext,
255 form_id: u32,
256 prop_key: i32,
257 chain_pos: usize,
258 args: &[Value],
259) {
260 let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
261 if al_id == Some(1) {
262 if let Some(v) = rhs {
263 match v {
264 Value::Str(s) => {
265 str_map(ctx, form_id).insert(prop_key, s);
266 }
267 Value::Int(n) => {
268 int_map(ctx, form_id).insert(prop_key, n);
269 }
270 _ => {}
271 }
272 }
273 ctx.push(Value::Int(0));
274 return;
275 }
276
277 push_stored_or_default(ctx, form_id, prop_key, ret_form);
278}
279
280pub fn store_or_push_direct_prop(
281 ctx: &mut CommandContext,
282 form_id: u32,
283 prop_key: i32,
284 args: &[Value],
285 value_idx: usize,
286) {
287 if let Some(v) = args.get(value_idx).cloned() {
288 match v {
289 Value::Str(s) => {
290 str_map(ctx, form_id).insert(prop_key, s);
291 }
292 Value::Int(n) => {
293 int_map(ctx, form_id).insert(prop_key, n);
294 }
295 _ => {}
296 }
297 ctx.push(Value::Int(0));
298 return;
299 }
300
301 push_stored_or_default(ctx, form_id, prop_key, None);
302}
303
304pub fn store_or_push_indexed(
305 ctx: &mut CommandContext,
306 form_id: u32,
307 index: usize,
308 chain_pos: usize,
309 args: &[Value],
310) {
311 let (al_id, ret_form, rhs) = infer_assign_and_ret_ctx(ctx, chain_pos, args);
312 if al_id == Some(1) {
313 match rhs {
314 Some(Value::Str(s)) => {
315 if ensure_str_index(ctx, form_id, index) {
316 let list = str_list(ctx, form_id);
317 if let Some(dst) = list.get_mut(index) {
318 *dst = s;
319 }
320 }
321 }
322 Some(Value::Int(n)) if ensure_int_index(ctx, form_id, index) => {
323 let list = int_list(ctx, form_id);
324 if let Some(dst) = list.get_mut(index) {
325 *dst = n;
326 }
327 }
328 _ => {}
329 }
330 ctx.push(Value::Int(0));
331 return;
332 }
333
334 if prefers_string(ret_form, rhs.as_ref()) {
335 if !ensure_str_index(ctx, form_id, index) {
336 ctx.push(Value::Str(String::new()));
337 return;
338 }
339 let value = {
340 let list = str_list(ctx, form_id);
341 list.get(index).cloned().unwrap_or_default()
342 };
343 ctx.push(Value::Str(value));
344 } else {
345 if !ensure_int_index(ctx, form_id, index) {
346 ctx.push(Value::Int(0));
347 return;
348 }
349 let value = {
350 let list = int_list(ctx, form_id);
351 list.get(index).copied().unwrap_or(0)
352 };
353 ctx.push(Value::Int(value));
354 }
355}
356
357pub fn store_or_push_indexed_direct(
358 ctx: &mut CommandContext,
359 form_id: u32,
360 index: usize,
361 args: &[Value],
362 value_idx: usize,
363) {
364 if let Some(v) = args.get(value_idx).cloned() {
365 match v {
366 Value::Str(s) => {
367 if ensure_str_index(ctx, form_id, index) {
368 let list = str_list(ctx, form_id);
369 if let Some(dst) = list.get_mut(index) {
370 *dst = s;
371 }
372 }
373 }
374 Value::Int(n) if ensure_int_index(ctx, form_id, index) => {
375 let list = int_list(ctx, form_id);
376 if let Some(dst) = list.get_mut(index) {
377 *dst = n;
378 }
379 }
380 _ => {}
381 }
382 ctx.push(Value::Int(0));
383 return;
384 }
385
386 if fixed_str_list_len(ctx, form_id).is_some() {
387 if !ensure_str_index(ctx, form_id, index) {
388 ctx.push(Value::Str(String::new()));
389 return;
390 }
391 let value = ctx
392 .globals
393 .str_lists
394 .get(&form_id)
395 .and_then(|v| v.get(index))
396 .cloned()
397 .unwrap_or_default();
398 ctx.push(Value::Str(value));
399 return;
400 }
401
402 if !ensure_int_index(ctx, form_id, index) {
403 ctx.push(Value::Int(0));
404 return;
405 }
406 let value = ctx
407 .globals
408 .int_lists
409 .get(&form_id)
410 .and_then(|v| v.get(index).copied())
411 .unwrap_or(0);
412 ctx.push(Value::Int(value));
413}
414
415pub fn dispatch_stateful_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
416 if let Some((chain_pos, chain)) = parse_element_chain_ctx(ctx, form_id, args) {
417 if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
418 let Ok(index) = usize::try_from(chain[2]) else {
419 log::error!(
420 "list index is negative: form_id={} index={}",
421 form_id,
422 chain[2]
423 );
424 let (_, ret_form) = current_vm_meta(ctx);
425 if ret_form_is_string_opt(ret_form) {
426 ctx.push(Value::Str(String::new()));
427 } else {
428 ctx.push(Value::Int(0));
429 }
430 return;
431 };
432 if chain.len() == 3 {
433 store_or_push_indexed(ctx, form_id, index, chain_pos, args);
434 } else {
435 let key = chain_key(&chain[1..]);
436 store_or_push_prop(ctx, form_id, key, chain_pos, args);
437 }
438 return;
439 }
440
441 if chain.len() >= 2 {
442 let key = if chain.len() == 2 {
443 chain[1]
444 } else {
445 chain_key(&chain[1..])
446 };
447 store_or_push_prop(ctx, form_id, key, chain_pos, args);
448 return;
449 }
450 }
451
452 if let Some(op) = args.first().and_then(|v| v.as_i64()) {
453 if op == ctx.ids.elm_array as i64 {
454 let raw_index = args.get(1).and_then(|v| v.as_i64()).unwrap_or(0);
455 let Ok(index) = usize::try_from(raw_index) else {
456 log::error!(
457 "list index is negative: form_id={} index={}",
458 form_id,
459 raw_index
460 );
461 ctx.push(Value::Int(0));
462 return;
463 };
464 store_or_push_indexed_direct(ctx, form_id, index, args, 2);
465 } else {
466 store_or_push_direct_prop(ctx, form_id, op as i32, args, 1);
467 }
468 return;
469 }
470
471 ctx.push(Value::Int(0));
472}
473
474pub fn dispatch_generic_form(ctx: &mut CommandContext, form_id: u32, args: &[Value]) {
475 dispatch_stateful_form(ctx, form_id, args);
476}
477
478pub fn assign_to_chain(ctx: &mut CommandContext, chain: &[i32], value: Value) {
479 if chain.is_empty() {
480 return;
481 }
482 let form_id = chain[0].max(0) as u32;
483 if chain.len() >= 3 && chain[1] == ctx.ids.elm_array {
484 let raw_index = chain[2];
485 let Ok(index) = usize::try_from(raw_index) else {
486 log::error!(
487 "list reference index is negative: form_id={} index={}",
488 form_id,
489 raw_index
490 );
491 return;
492 };
493 match value {
494 Value::Str(s) => {
495 if !ensure_str_index(ctx, form_id, index) {
496 return;
497 }
498 let list = str_list(ctx, form_id);
499 if let Some(dst) = list.get_mut(index) {
500 *dst = s;
501 }
502 }
503 Value::Int(n) => {
504 if !ensure_int_index(ctx, form_id, index) {
505 return;
506 }
507 let list = int_list(ctx, form_id);
508 if let Some(dst) = list.get_mut(index) {
509 *dst = n;
510 }
511 }
512 _ => {}
513 }
514 return;
515 }
516
517 if chain.len() >= 2 {
518 let key = if chain.len() == 2 {
519 chain[1]
520 } else {
521 chain_key(&chain[1..])
522 };
523 match value {
524 Value::Str(s) => {
525 str_map(ctx, form_id).insert(key, s);
526 }
527 Value::Int(n) => {
528 int_map(ctx, form_id).insert(key, n);
529 }
530 _ => {}
531 }
532 }
533}