Skip to main content

siglus_scene_vm/runtime/forms/
str_list.rs

1use anyhow::Result;
2
3use crate::runtime::{CommandContext, Value};
4
5use super::codes::{str_list_op, str_op};
6use super::prop_access;
7
8fn ensure_len(v: &mut Vec<String>, idx: usize) {
9    if v.len() <= idx {
10        v.resize_with(idx + 1, String::new);
11    }
12}
13
14fn display_width_char(ch: char) -> usize {
15    if ch.is_ascii() {
16        1
17    } else {
18        2
19    }
20}
21
22fn display_width_str(s: &str) -> usize {
23    s.chars().map(display_width_char).sum()
24}
25
26fn left_by_display_width(s: &str, limit: usize) -> String {
27    let mut width = 0usize;
28    let mut out = String::new();
29    for ch in s.chars() {
30        let w = display_width_char(ch);
31        if width + w > limit {
32            break;
33        }
34        width += w;
35        out.push(ch);
36    }
37    out
38}
39
40fn right_by_display_width(s: &str, limit: usize) -> String {
41    let mut width = 0usize;
42    let mut out: Vec<char> = Vec::new();
43    for ch in s.chars().rev() {
44        let w = display_width_char(ch);
45        if width + w > limit {
46            break;
47        }
48        width += w;
49        out.push(ch);
50    }
51    out.into_iter().rev().collect()
52}
53
54fn mid_by_display_width(s: &str, start_width: usize, len_width: Option<usize>) -> String {
55    let mut width = 0usize;
56    let mut out = String::new();
57    for ch in s.chars() {
58        let ch_width = display_width_char(ch);
59        if width >= start_width {
60            if let Some(limit) = len_width {
61                if display_width_str(&out) + ch_width > limit {
62                    break;
63                }
64            }
65            out.push(ch);
66        }
67        width += ch_width;
68    }
69    out
70}
71
72fn lower_ascii(s: &str) -> String {
73    s.chars().map(|c| c.to_ascii_lowercase()).collect()
74}
75
76fn upper_ascii(s: &str) -> String {
77    s.chars().map(|c| c.to_ascii_uppercase()).collect()
78}
79
80fn default_for_ret_form(ret_form: i64) -> Value {
81    if prop_access::ret_form_is_string(ret_form) {
82        Value::Str(String::new())
83    } else {
84        Value::Int(0)
85    }
86}
87
88fn parse_chain<'a>(
89    ctx: &'a CommandContext,
90    form_id: u32,
91    args: &'a [Value],
92) -> Option<(usize, &'a [i32])> {
93    prop_access::parse_element_chain_ctx(ctx, form_id, args)
94}
95
96fn collect_params<'a>(chain_pos: usize, args: &'a [Value]) -> &'a [Value] {
97    prop_access::script_args(args, chain_pos)
98}
99
100fn execute_str_op(current: &mut String, op: i32, params: &[Value], al_id: Option<i64>) -> Value {
101    match op {
102        str_op::UPPER => Value::Str(upper_ascii(current)),
103        str_op::LOWER => Value::Str(lower_ascii(current)),
104        str_op::CNT => Value::Int(current.chars().count() as i64),
105        str_op::LEN => Value::Int(display_width_str(current) as i64),
106        str_op::LEFT => {
107            let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
108            Value::Str(current.chars().take(len).collect())
109        }
110        str_op::LEFT_LEN => {
111            let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
112            Value::Str(left_by_display_width(current, len))
113        }
114        str_op::RIGHT => {
115            let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
116            let total = current.chars().count();
117            let start = total.saturating_sub(len);
118            Value::Str(current.chars().skip(start).collect())
119        }
120        str_op::RIGHT_LEN => {
121            let len = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
122            Value::Str(right_by_display_width(current, len))
123        }
124        str_op::MID => {
125            let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
126            if al_id.unwrap_or(0) == 0 || params.len() <= 1 {
127                Value::Str(current.chars().skip(start).collect())
128            } else {
129                let len = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
130                Value::Str(current.chars().skip(start).take(len).collect())
131            }
132        }
133        str_op::MID_LEN => {
134            let start = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
135            let len = if al_id.unwrap_or(0) == 0 || params.len() <= 1 {
136                None
137            } else {
138                Some(params.get(1).and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize)
139            };
140            Value::Str(mid_by_display_width(current, start, len))
141        }
142        str_op::SEARCH => {
143            let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
144            let hay = lower_ascii(current);
145            let needle = lower_ascii(needle);
146            Value::Int(hay.find(&needle).map(|v| v as i64).unwrap_or(-1))
147        }
148        str_op::SEARCH_LAST => {
149            let needle = params.first().and_then(|v| v.as_str()).unwrap_or("");
150            let hay = lower_ascii(current);
151            let needle = lower_ascii(needle);
152            Value::Int(hay.rfind(&needle).map(|v| v as i64).unwrap_or(-1))
153        }
154        str_op::GET_CODE => {
155            let pos = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
156            let code = current.chars().nth(pos).map(|c| c as i64).unwrap_or(-1);
157            Value::Int(code)
158        }
159        str_op::TONUM => Value::Int(current.parse::<i64>().unwrap_or(0)),
160        _ => Value::Str(current.clone()),
161    }
162}
163
164pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
165    let Some((chain_pos, chain_ref)) = parse_chain(ctx, form_id, args) else {
166        ctx.push(Value::Str(String::new()));
167        return Ok(true);
168    };
169    let chain = chain_ref.to_vec();
170
171    let params = collect_params(chain_pos, args);
172    let (al_id, ret_form) = crate::runtime::forms::prop_access::current_vm_meta(ctx);
173    let (_, _, rhs_meta) =
174        crate::runtime::forms::prop_access::infer_assign_and_ret_ctx(ctx, chain_pos, args);
175    let rhs_cmd = if al_id == Some(1) {
176        rhs_meta.clone()
177    } else {
178        None
179    };
180    let rhs_prop = rhs_meta;
181
182    if chain.len() >= 3
183        && (chain[1] == ctx.ids.elm_array || chain[1] == crate::runtime::forms::codes::ELM_ARRAY)
184    {
185        let idx = chain[2].max(0) as usize;
186        let out = {
187            let list = ctx
188                .globals
189                .str_lists
190                .entry(form_id)
191                .or_insert_with(Vec::new);
192            ensure_len(list, idx);
193            let current = &mut list[idx];
194
195            if chain.len() == 3 {
196                if let Some(Value::Str(rhs)) = rhs_prop.or(rhs_cmd) {
197                    *current = rhs;
198                    Value::Int(0)
199                } else {
200                    Value::Str(current.clone())
201                }
202            } else {
203                let op = chain[3];
204                execute_str_op(current, op, params, al_id)
205            }
206        };
207        ctx.push(out);
208        return Ok(true);
209    }
210
211    if chain.len() >= 2 {
212        match chain[1] {
213            str_list_op::INIT => {
214                ctx.globals
215                    .str_lists
216                    .entry(form_id)
217                    .or_insert_with(Vec::new)
218                    .clear();
219                ctx.push(Value::Int(0));
220                return Ok(true);
221            }
222            str_list_op::RESIZE => {
223                let n = params.first().and_then(|v| v.as_i64()).unwrap_or(0).max(0) as usize;
224                ctx.globals
225                    .str_lists
226                    .entry(form_id)
227                    .or_insert_with(Vec::new)
228                    .resize_with(n, String::new);
229                ctx.push(Value::Int(0));
230                return Ok(true);
231            }
232            str_list_op::GET_SIZE => {
233                let n = ctx
234                    .globals
235                    .str_lists
236                    .entry(form_id)
237                    .or_insert_with(Vec::new)
238                    .len() as i64;
239                ctx.push(Value::Int(n));
240                return Ok(true);
241            }
242            _ => {}
243        }
244    }
245
246    ctx.push(default_for_ret_form(ret_form.unwrap_or(20)));
247    Ok(true)
248}