siglus_scene_vm/runtime/forms/
str_list.rs1use 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}