Skip to main content

vm/
assembler.rs

1use std::collections::HashMap;
2
3use crate::debug_info::DebugInfoBuilder;
4use crate::{OpCode, Program, Value};
5
6pub struct BytecodeBuilder {
7    code: Vec<u8>,
8}
9
10#[derive(Debug)]
11pub enum AssemblerError {
12    DuplicateLabel(String),
13    UnknownLabel(String),
14}
15
16impl std::fmt::Display for AssemblerError {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            AssemblerError::DuplicateLabel(label) => write!(f, "duplicate label '{label}'"),
20            AssemblerError::UnknownLabel(label) => write!(f, "unknown label '{label}'"),
21        }
22    }
23}
24
25impl std::error::Error for AssemblerError {}
26
27struct Fixup {
28    at: usize,
29    label: String,
30}
31
32pub struct Assembler {
33    code: Vec<u8>,
34    constants: Vec<Value>,
35    int_constants: HashMap<i64, u32>,
36    float_constants: HashMap<u64, u32>,
37    bool_constants: HashMap<bool, u32>,
38    string_constants: HashMap<String, u32>,
39    bytes_constants: HashMap<Vec<u8>, u32>,
40    labels: HashMap<String, u32>,
41    fixups: Vec<Fixup>,
42    debug: DebugInfoBuilder,
43}
44
45impl Default for Assembler {
46    fn default() -> Self {
47        Self::new()
48    }
49}
50
51impl Assembler {
52    pub fn new() -> Self {
53        Self {
54            code: Vec::new(),
55            constants: Vec::new(),
56            int_constants: HashMap::new(),
57            float_constants: HashMap::new(),
58            bool_constants: HashMap::new(),
59            string_constants: HashMap::new(),
60            bytes_constants: HashMap::new(),
61            labels: HashMap::new(),
62            fixups: Vec::new(),
63            debug: DebugInfoBuilder::new(),
64        }
65    }
66
67    pub fn position(&self) -> u32 {
68        self.code.len() as u32
69    }
70
71    pub fn label(&mut self, name: &str) -> Result<(), AssemblerError> {
72        if self.labels.contains_key(name) {
73            return Err(AssemblerError::DuplicateLabel(name.to_string()));
74        }
75        let pos = self.position();
76        self.labels.insert(name.to_string(), pos);
77        Ok(())
78    }
79
80    pub fn set_source(&mut self, source: String) {
81        self.debug.set_source(source);
82    }
83
84    pub fn mark_line(&mut self, line: u32) {
85        let offset = self.code.len() as u32;
86        self.debug.mark_line(offset, line);
87    }
88
89    pub fn add_function(&mut self, name: String, args: Vec<String>) {
90        self.debug.add_function(name, args);
91    }
92
93    pub fn add_local(&mut self, name: String, index: u8) {
94        self.debug.add_local(name, index);
95    }
96
97    pub fn add_local_with_range(
98        &mut self,
99        name: String,
100        index: u8,
101        declared_line: Option<u32>,
102        last_line: Option<u32>,
103    ) {
104        self.debug
105            .add_local_with_range(name, index, declared_line, last_line);
106    }
107
108    pub fn add_constant(&mut self, value: Value) -> u32 {
109        match value {
110            Value::Int(number) => {
111                if let Some(index) = self.int_constants.get(&number).copied() {
112                    return index;
113                }
114                let index = self.constants.len() as u32;
115                self.constants.push(Value::Int(number));
116                self.int_constants.insert(number, index);
117                index
118            }
119            Value::Float(number) => {
120                let bits = number.to_bits();
121                if let Some(index) = self.float_constants.get(&bits).copied() {
122                    return index;
123                }
124                let index = self.constants.len() as u32;
125                self.constants.push(Value::Float(number));
126                self.float_constants.insert(bits, index);
127                index
128            }
129            Value::Bool(flag) => {
130                if let Some(index) = self.bool_constants.get(&flag).copied() {
131                    return index;
132                }
133                let index = self.constants.len() as u32;
134                self.constants.push(Value::Bool(flag));
135                self.bool_constants.insert(flag, index);
136                index
137            }
138            Value::String(text) => {
139                if let Some(index) = self.string_constants.get(text.as_str()).copied() {
140                    return index;
141                }
142                let index = self.constants.len() as u32;
143                self.constants.push(Value::String(text.clone()));
144                self.string_constants.insert(text.as_ref().clone(), index);
145                index
146            }
147            Value::Bytes(bytes) => {
148                if let Some(index) = self.bytes_constants.get(bytes.as_ref()).copied() {
149                    return index;
150                }
151                let index = self.constants.len() as u32;
152                self.constants.push(Value::Bytes(bytes.clone()));
153                self.bytes_constants.insert(bytes.as_ref().clone(), index);
154                index
155            }
156            other => {
157                let index = self.constants.len() as u32;
158                self.constants.push(other);
159                index
160            }
161        }
162    }
163
164    pub fn push_const(&mut self, value: Value) -> u32 {
165        let index = self.add_constant(value);
166        self.ldc(index);
167        index
168    }
169
170    pub fn finish_program(mut self) -> Result<Program, AssemblerError> {
171        for fixup in self.fixups.drain(..) {
172            let target = self
173                .labels
174                .get(&fixup.label)
175                .copied()
176                .ok_or_else(|| AssemblerError::UnknownLabel(fixup.label.clone()))?;
177            let bytes = target.to_le_bytes();
178            self.code[fixup.at..fixup.at + 4].copy_from_slice(&bytes);
179        }
180        Ok(Program::with_debug(
181            self.constants,
182            self.code,
183            self.debug.finish(),
184        ))
185    }
186
187    pub fn nop(&mut self) {
188        self.emit_opcode(OpCode::Nop);
189    }
190
191    pub fn ret(&mut self) {
192        self.emit_opcode(OpCode::Ret);
193    }
194
195    pub fn ldc(&mut self, index: u32) {
196        self.emit_opcode(OpCode::Ldc);
197        self.emit_u32(index);
198    }
199
200    pub fn add(&mut self) {
201        self.emit_opcode(OpCode::Add);
202    }
203
204    pub fn sub(&mut self) {
205        self.emit_opcode(OpCode::Sub);
206    }
207
208    pub fn mul(&mut self) {
209        self.emit_opcode(OpCode::Mul);
210    }
211
212    pub fn div(&mut self) {
213        self.emit_opcode(OpCode::Div);
214    }
215
216    pub fn modulo(&mut self) {
217        self.emit_opcode(OpCode::Mod);
218    }
219
220    pub fn and(&mut self) {
221        self.emit_opcode(OpCode::And);
222    }
223
224    pub fn or(&mut self) {
225        self.emit_opcode(OpCode::Or);
226    }
227
228    pub fn neg(&mut self) {
229        self.emit_opcode(OpCode::Neg);
230    }
231
232    pub fn not(&mut self) {
233        self.emit_opcode(OpCode::Not);
234    }
235
236    pub fn ceq(&mut self) {
237        self.emit_opcode(OpCode::Ceq);
238    }
239
240    pub fn clt(&mut self) {
241        self.emit_opcode(OpCode::Clt);
242    }
243
244    pub fn cgt(&mut self) {
245        self.emit_opcode(OpCode::Cgt);
246    }
247
248    pub fn br(&mut self, target: u32) {
249        self.emit_opcode(OpCode::Br);
250        self.emit_u32(target);
251    }
252
253    pub fn br_label(&mut self, label: &str) {
254        self.emit_opcode(OpCode::Br);
255        let at = self.code.len();
256        self.emit_u32(0);
257        self.fixups.push(Fixup {
258            at,
259            label: label.to_string(),
260        });
261    }
262
263    pub fn brfalse(&mut self, target: u32) {
264        self.emit_opcode(OpCode::Brfalse);
265        self.emit_u32(target);
266    }
267
268    pub fn brfalse_label(&mut self, label: &str) {
269        self.emit_opcode(OpCode::Brfalse);
270        let at = self.code.len();
271        self.emit_u32(0);
272        self.fixups.push(Fixup {
273            at,
274            label: label.to_string(),
275        });
276    }
277
278    pub fn pop(&mut self) {
279        self.emit_opcode(OpCode::Pop);
280    }
281
282    pub fn dup(&mut self) {
283        self.emit_opcode(OpCode::Dup);
284    }
285
286    pub fn ldloc(&mut self, index: u8) {
287        self.emit_opcode(OpCode::Ldloc);
288        self.emit_u8(index);
289    }
290
291    pub fn stloc(&mut self, index: u8) {
292        self.emit_opcode(OpCode::Stloc);
293        self.emit_u8(index);
294    }
295
296    pub fn call(&mut self, index: u16, argc: u8) {
297        self.emit_opcode(OpCode::Call);
298        self.emit_u16(index);
299        self.emit_u8(argc);
300    }
301
302    pub fn shl(&mut self) {
303        self.emit_opcode(OpCode::Shl);
304    }
305
306    pub fn shr(&mut self) {
307        self.emit_opcode(OpCode::Shr);
308    }
309
310    pub fn lshr(&mut self) {
311        self.emit_opcode(OpCode::Lshr);
312    }
313
314    fn emit_opcode(&mut self, opcode: OpCode) {
315        self.code.push(opcode as u8);
316    }
317
318    fn emit_u8(&mut self, value: u8) {
319        self.code.push(value);
320    }
321
322    fn emit_u16(&mut self, value: u16) {
323        self.code.extend_from_slice(&value.to_le_bytes());
324    }
325
326    fn emit_u32(&mut self, value: u32) {
327        self.code.extend_from_slice(&value.to_le_bytes());
328    }
329}
330
331impl Default for BytecodeBuilder {
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337impl BytecodeBuilder {
338    pub fn new() -> Self {
339        Self { code: Vec::new() }
340    }
341
342    pub fn position(&self) -> u32 {
343        self.code.len() as u32
344    }
345
346    pub fn finish(self) -> Vec<u8> {
347        self.code
348    }
349
350    pub fn nop(&mut self) {
351        self.emit_opcode(OpCode::Nop);
352    }
353
354    pub fn ret(&mut self) {
355        self.emit_opcode(OpCode::Ret);
356    }
357
358    pub fn ldc(&mut self, index: u32) {
359        self.emit_opcode(OpCode::Ldc);
360        self.emit_u32(index);
361    }
362
363    pub fn add(&mut self) {
364        self.emit_opcode(OpCode::Add);
365    }
366
367    pub fn sub(&mut self) {
368        self.emit_opcode(OpCode::Sub);
369    }
370
371    pub fn mul(&mut self) {
372        self.emit_opcode(OpCode::Mul);
373    }
374
375    pub fn div(&mut self) {
376        self.emit_opcode(OpCode::Div);
377    }
378
379    pub fn modulo(&mut self) {
380        self.emit_opcode(OpCode::Mod);
381    }
382
383    pub fn and(&mut self) {
384        self.emit_opcode(OpCode::And);
385    }
386
387    pub fn or(&mut self) {
388        self.emit_opcode(OpCode::Or);
389    }
390
391    pub fn neg(&mut self) {
392        self.emit_opcode(OpCode::Neg);
393    }
394
395    pub fn not(&mut self) {
396        self.emit_opcode(OpCode::Not);
397    }
398
399    pub fn ceq(&mut self) {
400        self.emit_opcode(OpCode::Ceq);
401    }
402
403    pub fn clt(&mut self) {
404        self.emit_opcode(OpCode::Clt);
405    }
406
407    pub fn cgt(&mut self) {
408        self.emit_opcode(OpCode::Cgt);
409    }
410
411    pub fn br(&mut self, target: u32) {
412        self.emit_opcode(OpCode::Br);
413        self.emit_u32(target);
414    }
415
416    pub fn brfalse(&mut self, target: u32) {
417        self.emit_opcode(OpCode::Brfalse);
418        self.emit_u32(target);
419    }
420
421    pub fn pop(&mut self) {
422        self.emit_opcode(OpCode::Pop);
423    }
424
425    pub fn dup(&mut self) {
426        self.emit_opcode(OpCode::Dup);
427    }
428
429    pub fn ldloc(&mut self, index: u8) {
430        self.emit_opcode(OpCode::Ldloc);
431        self.emit_u8(index);
432    }
433
434    pub fn stloc(&mut self, index: u8) {
435        self.emit_opcode(OpCode::Stloc);
436        self.emit_u8(index);
437    }
438
439    pub fn call(&mut self, index: u16, argc: u8) {
440        self.emit_opcode(OpCode::Call);
441        self.emit_u16(index);
442        self.emit_u8(argc);
443    }
444
445    pub fn shl(&mut self) {
446        self.emit_opcode(OpCode::Shl);
447    }
448
449    pub fn shr(&mut self) {
450        self.emit_opcode(OpCode::Shr);
451    }
452
453    pub fn lshr(&mut self) {
454        self.emit_opcode(OpCode::Lshr);
455    }
456
457    fn emit_opcode(&mut self, opcode: OpCode) {
458        self.code.push(opcode as u8);
459    }
460
461    fn emit_u8(&mut self, value: u8) {
462        self.code.push(value);
463    }
464
465    fn emit_u16(&mut self, value: u16) {
466        self.code.extend_from_slice(&value.to_le_bytes());
467    }
468
469    fn emit_u32(&mut self, value: u32) {
470        self.code.extend_from_slice(&value.to_le_bytes());
471    }
472}
473
474#[derive(Debug, Clone, PartialEq, Eq)]
475pub struct AsmParseError {
476    pub line: usize,
477    pub message: String,
478}
479
480impl std::fmt::Display for AsmParseError {
481    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
482        write!(f, "line {}: {}", self.line, self.message)
483    }
484}
485
486impl std::error::Error for AsmParseError {}
487
488#[derive(Clone, Copy, Debug, PartialEq, Eq)]
489enum AsmSection {
490    Data,
491    Code,
492}
493
494pub fn assemble(source: &str) -> Result<Program, AsmParseError> {
495    let mut assembler = Assembler::new();
496    assembler.set_source(source.to_string());
497    let mut consts: HashMap<String, u32> = HashMap::new();
498    let mut locals: HashMap<String, u8> = HashMap::new();
499    let mut next_local: u8 = 0;
500    let mut section = AsmSection::Code;
501
502    for (line_idx, raw_line) in source.lines().enumerate() {
503        let line_no = line_idx + 1;
504        let line = strip_comments(raw_line).trim();
505        if line.is_empty() {
506            continue;
507        }
508
509        if line.ends_with(':') {
510            return Err(AsmParseError {
511                line: line_no,
512                message: "label definitions must use '.label NAME'".to_string(),
513            });
514        }
515
516        if let Some(rest) = line.strip_prefix('.') {
517            let mut parts = rest.split_whitespace();
518            let directive = parts.next().unwrap_or("").to_ascii_lowercase();
519            match directive.as_str() {
520                "data" => {
521                    section = AsmSection::Data;
522                }
523                "code" => {
524                    section = AsmSection::Code;
525                }
526                "label" => {
527                    let name = next_token(&mut parts, line_no, "label name")?;
528                    if section != AsmSection::Code {
529                        return Err(AsmParseError {
530                            line: line_no,
531                            message: "labels are only valid in code section".to_string(),
532                        });
533                    }
534                    assembler.label(name).map_err(|err| AsmParseError {
535                        line: line_no,
536                        message: format!("label error: {err:?}"),
537                    })?;
538                }
539                "const" => {
540                    let name = next_token(&mut parts, line_no, "const name")?;
541                    if consts.contains_key(name) {
542                        return Err(AsmParseError {
543                            line: line_no,
544                            message: format!("duplicate const '{name}'"),
545                        });
546                    }
547                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
548                    if rest.is_empty() {
549                        return Err(AsmParseError {
550                            line: line_no,
551                            message: "missing const value".to_string(),
552                        });
553                    }
554                    let value = parse_literal(rest, line_no)?;
555                    let index = assembler.add_constant(value);
556                    consts.insert(name.to_string(), index);
557                }
558                "local" => {
559                    let name = next_token(&mut parts, line_no, "local name")?;
560                    if locals.contains_key(name) {
561                        return Err(AsmParseError {
562                            line: line_no,
563                            message: format!("duplicate local '{name}'"),
564                        });
565                    }
566
567                    let index = if let Some(token) = parts.next() {
568                        parse_u8(token, line_no)?
569                    } else {
570                        let index = next_local;
571                        next_local = next_local.checked_add(1).ok_or(AsmParseError {
572                            line: line_no,
573                            message: "local index overflow".to_string(),
574                        })?;
575                        index
576                    };
577                    locals.insert(name.to_string(), index);
578                }
579                other => {
580                    return Err(AsmParseError {
581                        line: line_no,
582                        message: format!("unknown directive '.{other}'"),
583                    });
584                }
585            }
586
587            if parts.next().is_some() {
588                return Err(AsmParseError {
589                    line: line_no,
590                    message: "unexpected extra tokens".to_string(),
591                });
592            }
593            continue;
594        }
595
596        let mut parts = line.split_whitespace();
597        let op = parts.next().ok_or_else(|| AsmParseError {
598            line: line_no,
599            message: "missing opcode".to_string(),
600        })?;
601        let op = op.to_ascii_lowercase();
602
603        if section == AsmSection::Data {
604            match op.as_str() {
605                "const" => {
606                    let name = next_token(&mut parts, line_no, "const name")?;
607                    if consts.contains_key(name) {
608                        return Err(AsmParseError {
609                            line: line_no,
610                            message: format!("duplicate const '{name}'"),
611                        });
612                    }
613                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
614                    if rest.is_empty() {
615                        return Err(AsmParseError {
616                            line: line_no,
617                            message: "missing const value".to_string(),
618                        });
619                    }
620                    let value = parse_literal(rest, line_no)?;
621                    let index = assembler.add_constant(value);
622                    consts.insert(name.to_string(), index);
623                }
624                "string" => {
625                    let name = next_token(&mut parts, line_no, "string name")?;
626                    if consts.contains_key(name) {
627                        return Err(AsmParseError {
628                            line: line_no,
629                            message: format!("duplicate const '{name}'"),
630                        });
631                    }
632                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
633                    if rest.is_empty() {
634                        return Err(AsmParseError {
635                            line: line_no,
636                            message: "missing string literal".to_string(),
637                        });
638                    }
639                    let value = Value::string(parse_string_literal(rest, line_no)?);
640                    let index = assembler.add_constant(value);
641                    consts.insert(name.to_string(), index);
642                }
643                other => {
644                    return Err(AsmParseError {
645                        line: line_no,
646                        message: format!("unexpected opcode '{other}' in data section"),
647                    });
648                }
649            }
650            continue;
651        }
652
653        assembler.mark_line(line_no as u32);
654        let mut check_extra = true;
655        let opcode = OpCode::parse_mnemonic(op.as_str()).ok_or_else(|| AsmParseError {
656            line: line_no,
657            message: format!("unknown opcode '{op}'"),
658        })?;
659        match opcode {
660            OpCode::Nop => assembler.nop(),
661            OpCode::Ret => assembler.ret(),
662            OpCode::Ldc => {
663                check_extra = false;
664                let rest = rest_after_n_tokens(line, 1).unwrap_or("");
665                if rest.is_empty() {
666                    return Err(AsmParseError {
667                        line: line_no,
668                        message: "missing ldc literal".to_string(),
669                    });
670                }
671                if let Some(&index) = consts.get(rest) {
672                    assembler.ldc(index);
673                } else {
674                    assembler.push_const(parse_literal(rest, line_no)?);
675                }
676            }
677            OpCode::Add => assembler.add(),
678            OpCode::Sub => assembler.sub(),
679            OpCode::Mul => assembler.mul(),
680            OpCode::Div => assembler.div(),
681            OpCode::Neg => assembler.neg(),
682            OpCode::Not => assembler.not(),
683            OpCode::Ceq => assembler.ceq(),
684            OpCode::Clt => assembler.clt(),
685            OpCode::Cgt => assembler.cgt(),
686            OpCode::Br => {
687                let target = next_token(&mut parts, line_no, "jump target")?;
688                if target.parse::<u32>().is_ok() {
689                    return Err(AsmParseError {
690                        line: line_no,
691                        message: "numeric jump targets are not supported".to_string(),
692                    });
693                }
694                assembler.br_label(target);
695            }
696            OpCode::Brfalse => {
697                let target = next_token(&mut parts, line_no, "jump target")?;
698                if target.parse::<u32>().is_ok() {
699                    return Err(AsmParseError {
700                        line: line_no,
701                        message: "numeric jump targets are not supported".to_string(),
702                    });
703                }
704                assembler.brfalse_label(target);
705            }
706            OpCode::Pop => assembler.pop(),
707            OpCode::Dup => assembler.dup(),
708            OpCode::Ldloc => {
709                let token = next_token(&mut parts, line_no, "local index")?;
710                let index = if let Ok(value) = token.parse::<u8>() {
711                    value
712                } else {
713                    *locals.get(token).ok_or(AsmParseError {
714                        line: line_no,
715                        message: format!("unknown local '{token}'"),
716                    })?
717                };
718                assembler.ldloc(index);
719            }
720            OpCode::Stloc => {
721                let token = next_token(&mut parts, line_no, "local index")?;
722                let index = if let Ok(value) = token.parse::<u8>() {
723                    value
724                } else {
725                    *locals.get(token).ok_or(AsmParseError {
726                        line: line_no,
727                        message: format!("unknown local '{token}'"),
728                    })?
729                };
730                assembler.stloc(index);
731            }
732            OpCode::Call => {
733                let index = parse_u16(next_token(&mut parts, line_no, "call id")?, line_no)?;
734                let argc = parse_u8(next_token(&mut parts, line_no, "arg count")?, line_no)?;
735                assembler.call(index, argc);
736            }
737            OpCode::Shl => assembler.shl(),
738            OpCode::Shr => assembler.shr(),
739            OpCode::Lshr => assembler.lshr(),
740            OpCode::Mod => assembler.modulo(),
741            OpCode::And => assembler.and(),
742            OpCode::Or => assembler.or(),
743        }
744
745        if check_extra && parts.next().is_some() {
746            return Err(AsmParseError {
747                line: line_no,
748                message: "unexpected extra tokens".to_string(),
749            });
750        }
751    }
752
753    assembler.finish_program().map_err(|err| AsmParseError {
754        line: 0,
755        message: format!("assembler error: {err:?}"),
756    })
757}
758
759fn strip_comments(line: &str) -> &str {
760    let hash_idx = line.find('#');
761    let slash_idx = line.find("//");
762    match (hash_idx, slash_idx) {
763        (Some(h), Some(s)) => &line[..h.min(s)],
764        (Some(h), None) => &line[..h],
765        (None, Some(s)) => &line[..s],
766        (None, None) => line,
767    }
768}
769
770fn next_token<'a>(
771    parts: &mut impl Iterator<Item = &'a str>,
772    line_no: usize,
773    what: &str,
774) -> Result<&'a str, AsmParseError> {
775    parts.next().ok_or_else(|| AsmParseError {
776        line: line_no,
777        message: format!("missing {what}"),
778    })
779}
780
781fn parse_u8(token: &str, line_no: usize) -> Result<u8, AsmParseError> {
782    token.parse::<u8>().map_err(|_| AsmParseError {
783        line: line_no,
784        message: format!("invalid u8 '{token}'"),
785    })
786}
787
788fn parse_u16(token: &str, line_no: usize) -> Result<u16, AsmParseError> {
789    token.parse::<u16>().map_err(|_| AsmParseError {
790        line: line_no,
791        message: format!("invalid u16 '{token}'"),
792    })
793}
794
795fn parse_f64(token: &str, line_no: usize, what: &str) -> Result<f64, AsmParseError> {
796    token.parse::<f64>().map_err(|_| AsmParseError {
797        line: line_no,
798        message: format!("invalid {what} '{token}'"),
799    })
800}
801
802fn parse_literal(token: &str, line_no: usize) -> Result<Value, AsmParseError> {
803    let token = token.trim();
804    if token.starts_with('"') {
805        return Ok(Value::string(parse_string_literal(token, line_no)?));
806    }
807    if token.eq_ignore_ascii_case("true") {
808        Ok(Value::Bool(true))
809    } else if token.eq_ignore_ascii_case("false") {
810        Ok(Value::Bool(false))
811    } else {
812        match token.parse::<i64>() {
813            Ok(value) => Ok(Value::Int(value)),
814            Err(_) => parse_f64(token, line_no, "const literal").map(Value::Float),
815        }
816    }
817}
818
819fn parse_string_literal(token: &str, line_no: usize) -> Result<String, AsmParseError> {
820    let mut chars = token.char_indices();
821    if chars.next().map(|(_, ch)| ch) != Some('"') {
822        return Err(AsmParseError {
823            line: line_no,
824            message: "string literal must start with '\"'".to_string(),
825        });
826    }
827
828    let mut out = String::new();
829    let mut escaped = false;
830    let mut end_idx = None;
831
832    for (idx, ch) in chars {
833        if escaped {
834            let mapped = match ch {
835                'n' => '\n',
836                'r' => '\r',
837                't' => '\t',
838                '\\' => '\\',
839                '"' => '"',
840                '0' => '\0',
841                other => {
842                    return Err(AsmParseError {
843                        line: line_no,
844                        message: format!("invalid escape '\\{other}'"),
845                    });
846                }
847            };
848            out.push(mapped);
849            escaped = false;
850            continue;
851        }
852
853        match ch {
854            '\\' => escaped = true,
855            '"' => {
856                end_idx = Some(idx);
857                break;
858            }
859            other => out.push(other),
860        }
861    }
862
863    let Some(end_idx) = end_idx else {
864        return Err(AsmParseError {
865            line: line_no,
866            message: "unterminated string literal".to_string(),
867        });
868    };
869
870    if token[end_idx + 1..].trim().is_empty() {
871        Ok(out)
872    } else {
873        Err(AsmParseError {
874            line: line_no,
875            message: "unexpected trailing characters after string literal".to_string(),
876        })
877    }
878}
879
880fn rest_after_n_tokens(line: &str, n: usize) -> Option<&str> {
881    let mut count = 0;
882    let mut in_token = false;
883    let mut end_idx = 0;
884    for (idx, ch) in line.char_indices() {
885        if ch.is_whitespace() {
886            if in_token {
887                in_token = false;
888                count += 1;
889                if count == n {
890                    end_idx = idx;
891                    break;
892                }
893            }
894        } else if !in_token {
895            in_token = true;
896        }
897    }
898
899    if in_token {
900        count += 1;
901        end_idx = line.len();
902    }
903
904    if count < n {
905        None
906    } else {
907        Some(line[end_idx..].trim_start())
908    }
909}