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 call_value(&mut self, argc: u8) {
303        self.emit_opcode(OpCode::CallValue);
304        self.emit_u8(argc);
305    }
306
307    pub fn shl(&mut self) {
308        self.emit_opcode(OpCode::Shl);
309    }
310
311    pub fn shr(&mut self) {
312        self.emit_opcode(OpCode::Shr);
313    }
314
315    pub fn lshr(&mut self) {
316        self.emit_opcode(OpCode::Lshr);
317    }
318
319    fn emit_opcode(&mut self, opcode: OpCode) {
320        self.code.push(opcode as u8);
321    }
322
323    fn emit_u8(&mut self, value: u8) {
324        self.code.push(value);
325    }
326
327    fn emit_u16(&mut self, value: u16) {
328        self.code.extend_from_slice(&value.to_le_bytes());
329    }
330
331    fn emit_u32(&mut self, value: u32) {
332        self.code.extend_from_slice(&value.to_le_bytes());
333    }
334}
335
336impl Default for BytecodeBuilder {
337    fn default() -> Self {
338        Self::new()
339    }
340}
341
342impl BytecodeBuilder {
343    pub fn new() -> Self {
344        Self { code: Vec::new() }
345    }
346
347    pub fn position(&self) -> u32 {
348        self.code.len() as u32
349    }
350
351    pub fn finish(self) -> Vec<u8> {
352        self.code
353    }
354
355    pub fn nop(&mut self) {
356        self.emit_opcode(OpCode::Nop);
357    }
358
359    pub fn ret(&mut self) {
360        self.emit_opcode(OpCode::Ret);
361    }
362
363    pub fn ldc(&mut self, index: u32) {
364        self.emit_opcode(OpCode::Ldc);
365        self.emit_u32(index);
366    }
367
368    pub fn add(&mut self) {
369        self.emit_opcode(OpCode::Add);
370    }
371
372    pub fn sub(&mut self) {
373        self.emit_opcode(OpCode::Sub);
374    }
375
376    pub fn mul(&mut self) {
377        self.emit_opcode(OpCode::Mul);
378    }
379
380    pub fn div(&mut self) {
381        self.emit_opcode(OpCode::Div);
382    }
383
384    pub fn modulo(&mut self) {
385        self.emit_opcode(OpCode::Mod);
386    }
387
388    pub fn and(&mut self) {
389        self.emit_opcode(OpCode::And);
390    }
391
392    pub fn or(&mut self) {
393        self.emit_opcode(OpCode::Or);
394    }
395
396    pub fn neg(&mut self) {
397        self.emit_opcode(OpCode::Neg);
398    }
399
400    pub fn not(&mut self) {
401        self.emit_opcode(OpCode::Not);
402    }
403
404    pub fn ceq(&mut self) {
405        self.emit_opcode(OpCode::Ceq);
406    }
407
408    pub fn clt(&mut self) {
409        self.emit_opcode(OpCode::Clt);
410    }
411
412    pub fn cgt(&mut self) {
413        self.emit_opcode(OpCode::Cgt);
414    }
415
416    pub fn br(&mut self, target: u32) {
417        self.emit_opcode(OpCode::Br);
418        self.emit_u32(target);
419    }
420
421    pub fn brfalse(&mut self, target: u32) {
422        self.emit_opcode(OpCode::Brfalse);
423        self.emit_u32(target);
424    }
425
426    pub fn pop(&mut self) {
427        self.emit_opcode(OpCode::Pop);
428    }
429
430    pub fn dup(&mut self) {
431        self.emit_opcode(OpCode::Dup);
432    }
433
434    pub fn ldloc(&mut self, index: u8) {
435        self.emit_opcode(OpCode::Ldloc);
436        self.emit_u8(index);
437    }
438
439    pub fn stloc(&mut self, index: u8) {
440        self.emit_opcode(OpCode::Stloc);
441        self.emit_u8(index);
442    }
443
444    pub fn call(&mut self, index: u16, argc: u8) {
445        self.emit_opcode(OpCode::Call);
446        self.emit_u16(index);
447        self.emit_u8(argc);
448    }
449
450    pub fn call_value(&mut self, argc: u8) {
451        self.emit_opcode(OpCode::CallValue);
452        self.emit_u8(argc);
453    }
454
455    pub fn shl(&mut self) {
456        self.emit_opcode(OpCode::Shl);
457    }
458
459    pub fn shr(&mut self) {
460        self.emit_opcode(OpCode::Shr);
461    }
462
463    pub fn lshr(&mut self) {
464        self.emit_opcode(OpCode::Lshr);
465    }
466
467    fn emit_opcode(&mut self, opcode: OpCode) {
468        self.code.push(opcode as u8);
469    }
470
471    fn emit_u8(&mut self, value: u8) {
472        self.code.push(value);
473    }
474
475    fn emit_u16(&mut self, value: u16) {
476        self.code.extend_from_slice(&value.to_le_bytes());
477    }
478
479    fn emit_u32(&mut self, value: u32) {
480        self.code.extend_from_slice(&value.to_le_bytes());
481    }
482}
483
484#[derive(Debug, Clone, PartialEq, Eq)]
485pub struct AsmParseError {
486    pub line: usize,
487    pub message: String,
488}
489
490impl std::fmt::Display for AsmParseError {
491    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492        write!(f, "line {}: {}", self.line, self.message)
493    }
494}
495
496impl std::error::Error for AsmParseError {}
497
498#[derive(Clone, Copy, Debug, PartialEq, Eq)]
499enum AsmSection {
500    Data,
501    Code,
502}
503
504pub fn assemble(source: &str) -> Result<Program, AsmParseError> {
505    let mut assembler = Assembler::new();
506    assembler.set_source(source.to_string());
507    let mut consts: HashMap<String, u32> = HashMap::new();
508    let mut locals: HashMap<String, u8> = HashMap::new();
509    let mut next_local: u8 = 0;
510    let mut section = AsmSection::Code;
511
512    for (line_idx, raw_line) in source.lines().enumerate() {
513        let line_no = line_idx + 1;
514        let line = strip_comments(raw_line).trim();
515        if line.is_empty() {
516            continue;
517        }
518
519        if line.ends_with(':') {
520            return Err(AsmParseError {
521                line: line_no,
522                message: "label definitions must use '.label NAME'".to_string(),
523            });
524        }
525
526        if let Some(rest) = line.strip_prefix('.') {
527            let mut parts = rest.split_whitespace();
528            let directive = parts.next().unwrap_or("").to_ascii_lowercase();
529            match directive.as_str() {
530                "data" => {
531                    section = AsmSection::Data;
532                }
533                "code" => {
534                    section = AsmSection::Code;
535                }
536                "label" => {
537                    let name = next_token(&mut parts, line_no, "label name")?;
538                    if section != AsmSection::Code {
539                        return Err(AsmParseError {
540                            line: line_no,
541                            message: "labels are only valid in code section".to_string(),
542                        });
543                    }
544                    assembler.label(name).map_err(|err| AsmParseError {
545                        line: line_no,
546                        message: format!("label error: {err:?}"),
547                    })?;
548                }
549                "const" => {
550                    let name = next_token(&mut parts, line_no, "const name")?;
551                    if consts.contains_key(name) {
552                        return Err(AsmParseError {
553                            line: line_no,
554                            message: format!("duplicate const '{name}'"),
555                        });
556                    }
557                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
558                    if rest.is_empty() {
559                        return Err(AsmParseError {
560                            line: line_no,
561                            message: "missing const value".to_string(),
562                        });
563                    }
564                    let value = parse_literal(rest, line_no)?;
565                    let index = assembler.add_constant(value);
566                    consts.insert(name.to_string(), index);
567                }
568                "local" => {
569                    let name = next_token(&mut parts, line_no, "local name")?;
570                    if locals.contains_key(name) {
571                        return Err(AsmParseError {
572                            line: line_no,
573                            message: format!("duplicate local '{name}'"),
574                        });
575                    }
576
577                    let index = if let Some(token) = parts.next() {
578                        parse_u8(token, line_no)?
579                    } else {
580                        let index = next_local;
581                        next_local = next_local.checked_add(1).ok_or(AsmParseError {
582                            line: line_no,
583                            message: "local index overflow".to_string(),
584                        })?;
585                        index
586                    };
587                    locals.insert(name.to_string(), index);
588                }
589                other => {
590                    return Err(AsmParseError {
591                        line: line_no,
592                        message: format!("unknown directive '.{other}'"),
593                    });
594                }
595            }
596
597            if parts.next().is_some() {
598                return Err(AsmParseError {
599                    line: line_no,
600                    message: "unexpected extra tokens".to_string(),
601                });
602            }
603            continue;
604        }
605
606        let mut parts = line.split_whitespace();
607        let op = parts.next().ok_or_else(|| AsmParseError {
608            line: line_no,
609            message: "missing opcode".to_string(),
610        })?;
611        let op = op.to_ascii_lowercase();
612
613        if section == AsmSection::Data {
614            match op.as_str() {
615                "const" => {
616                    let name = next_token(&mut parts, line_no, "const name")?;
617                    if consts.contains_key(name) {
618                        return Err(AsmParseError {
619                            line: line_no,
620                            message: format!("duplicate const '{name}'"),
621                        });
622                    }
623                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
624                    if rest.is_empty() {
625                        return Err(AsmParseError {
626                            line: line_no,
627                            message: "missing const value".to_string(),
628                        });
629                    }
630                    let value = parse_literal(rest, line_no)?;
631                    let index = assembler.add_constant(value);
632                    consts.insert(name.to_string(), index);
633                }
634                "string" => {
635                    let name = next_token(&mut parts, line_no, "string name")?;
636                    if consts.contains_key(name) {
637                        return Err(AsmParseError {
638                            line: line_no,
639                            message: format!("duplicate const '{name}'"),
640                        });
641                    }
642                    let rest = rest_after_n_tokens(line, 2).unwrap_or("");
643                    if rest.is_empty() {
644                        return Err(AsmParseError {
645                            line: line_no,
646                            message: "missing string literal".to_string(),
647                        });
648                    }
649                    let value = Value::string(parse_string_literal(rest, line_no)?);
650                    let index = assembler.add_constant(value);
651                    consts.insert(name.to_string(), index);
652                }
653                other => {
654                    return Err(AsmParseError {
655                        line: line_no,
656                        message: format!("unexpected opcode '{other}' in data section"),
657                    });
658                }
659            }
660            continue;
661        }
662
663        assembler.mark_line(line_no as u32);
664        let mut check_extra = true;
665        let opcode = OpCode::parse_mnemonic(op.as_str()).ok_or_else(|| AsmParseError {
666            line: line_no,
667            message: format!("unknown opcode '{op}'"),
668        })?;
669        match opcode {
670            OpCode::Nop => assembler.nop(),
671            OpCode::Ret => assembler.ret(),
672            OpCode::Ldc => {
673                check_extra = false;
674                let rest = rest_after_n_tokens(line, 1).unwrap_or("");
675                if rest.is_empty() {
676                    return Err(AsmParseError {
677                        line: line_no,
678                        message: "missing ldc literal".to_string(),
679                    });
680                }
681                if let Some(&index) = consts.get(rest) {
682                    assembler.ldc(index);
683                } else {
684                    assembler.push_const(parse_literal(rest, line_no)?);
685                }
686            }
687            OpCode::Add => assembler.add(),
688            OpCode::Sub => assembler.sub(),
689            OpCode::Mul => assembler.mul(),
690            OpCode::Div => assembler.div(),
691            OpCode::Neg => assembler.neg(),
692            OpCode::Not => assembler.not(),
693            OpCode::Ceq => assembler.ceq(),
694            OpCode::Clt => assembler.clt(),
695            OpCode::Cgt => assembler.cgt(),
696            OpCode::Br => {
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.br_label(target);
705            }
706            OpCode::Brfalse => {
707                let target = next_token(&mut parts, line_no, "jump target")?;
708                if target.parse::<u32>().is_ok() {
709                    return Err(AsmParseError {
710                        line: line_no,
711                        message: "numeric jump targets are not supported".to_string(),
712                    });
713                }
714                assembler.brfalse_label(target);
715            }
716            OpCode::Pop => assembler.pop(),
717            OpCode::Dup => assembler.dup(),
718            OpCode::Ldloc => {
719                let token = next_token(&mut parts, line_no, "local index")?;
720                let index = if let Ok(value) = token.parse::<u8>() {
721                    value
722                } else {
723                    *locals.get(token).ok_or(AsmParseError {
724                        line: line_no,
725                        message: format!("unknown local '{token}'"),
726                    })?
727                };
728                assembler.ldloc(index);
729            }
730            OpCode::Stloc => {
731                let token = next_token(&mut parts, line_no, "local index")?;
732                let index = if let Ok(value) = token.parse::<u8>() {
733                    value
734                } else {
735                    *locals.get(token).ok_or(AsmParseError {
736                        line: line_no,
737                        message: format!("unknown local '{token}'"),
738                    })?
739                };
740                assembler.stloc(index);
741            }
742            OpCode::Call => {
743                let index = parse_u16(next_token(&mut parts, line_no, "call id")?, line_no)?;
744                let argc = parse_u8(next_token(&mut parts, line_no, "arg count")?, line_no)?;
745                assembler.call(index, argc);
746            }
747            OpCode::CallValue => {
748                let argc = parse_u8(next_token(&mut parts, line_no, "arg count")?, line_no)?;
749                assembler.call_value(argc);
750            }
751            OpCode::Shl => assembler.shl(),
752            OpCode::Shr => assembler.shr(),
753            OpCode::Lshr => assembler.lshr(),
754            OpCode::Mod => assembler.modulo(),
755            OpCode::And => assembler.and(),
756            OpCode::Or => assembler.or(),
757        }
758
759        if check_extra && parts.next().is_some() {
760            return Err(AsmParseError {
761                line: line_no,
762                message: "unexpected extra tokens".to_string(),
763            });
764        }
765    }
766
767    assembler.finish_program().map_err(|err| AsmParseError {
768        line: 0,
769        message: format!("assembler error: {err:?}"),
770    })
771}
772
773fn strip_comments(line: &str) -> &str {
774    let hash_idx = line.find('#');
775    let slash_idx = line.find("//");
776    match (hash_idx, slash_idx) {
777        (Some(h), Some(s)) => &line[..h.min(s)],
778        (Some(h), None) => &line[..h],
779        (None, Some(s)) => &line[..s],
780        (None, None) => line,
781    }
782}
783
784fn next_token<'a>(
785    parts: &mut impl Iterator<Item = &'a str>,
786    line_no: usize,
787    what: &str,
788) -> Result<&'a str, AsmParseError> {
789    parts.next().ok_or_else(|| AsmParseError {
790        line: line_no,
791        message: format!("missing {what}"),
792    })
793}
794
795fn parse_u8(token: &str, line_no: usize) -> Result<u8, AsmParseError> {
796    token.parse::<u8>().map_err(|_| AsmParseError {
797        line: line_no,
798        message: format!("invalid u8 '{token}'"),
799    })
800}
801
802fn parse_u16(token: &str, line_no: usize) -> Result<u16, AsmParseError> {
803    token.parse::<u16>().map_err(|_| AsmParseError {
804        line: line_no,
805        message: format!("invalid u16 '{token}'"),
806    })
807}
808
809fn parse_f64(token: &str, line_no: usize, what: &str) -> Result<f64, AsmParseError> {
810    token.parse::<f64>().map_err(|_| AsmParseError {
811        line: line_no,
812        message: format!("invalid {what} '{token}'"),
813    })
814}
815
816fn parse_literal(token: &str, line_no: usize) -> Result<Value, AsmParseError> {
817    let token = token.trim();
818    if token.starts_with('"') {
819        return Ok(Value::string(parse_string_literal(token, line_no)?));
820    }
821    if token.eq_ignore_ascii_case("true") {
822        Ok(Value::Bool(true))
823    } else if token.eq_ignore_ascii_case("false") {
824        Ok(Value::Bool(false))
825    } else {
826        match token.parse::<i64>() {
827            Ok(value) => Ok(Value::Int(value)),
828            Err(_) => parse_f64(token, line_no, "const literal").map(Value::Float),
829        }
830    }
831}
832
833fn parse_string_literal(token: &str, line_no: usize) -> Result<String, AsmParseError> {
834    let mut chars = token.char_indices();
835    if chars.next().map(|(_, ch)| ch) != Some('"') {
836        return Err(AsmParseError {
837            line: line_no,
838            message: "string literal must start with '\"'".to_string(),
839        });
840    }
841
842    let mut out = String::new();
843    let mut escaped = false;
844    let mut end_idx = None;
845
846    for (idx, ch) in chars {
847        if escaped {
848            let mapped = match ch {
849                'n' => '\n',
850                'r' => '\r',
851                't' => '\t',
852                '\\' => '\\',
853                '"' => '"',
854                '0' => '\0',
855                other => {
856                    return Err(AsmParseError {
857                        line: line_no,
858                        message: format!("invalid escape '\\{other}'"),
859                    });
860                }
861            };
862            out.push(mapped);
863            escaped = false;
864            continue;
865        }
866
867        match ch {
868            '\\' => escaped = true,
869            '"' => {
870                end_idx = Some(idx);
871                break;
872            }
873            other => out.push(other),
874        }
875    }
876
877    let Some(end_idx) = end_idx else {
878        return Err(AsmParseError {
879            line: line_no,
880            message: "unterminated string literal".to_string(),
881        });
882    };
883
884    if token[end_idx + 1..].trim().is_empty() {
885        Ok(out)
886    } else {
887        Err(AsmParseError {
888            line: line_no,
889            message: "unexpected trailing characters after string literal".to_string(),
890        })
891    }
892}
893
894fn rest_after_n_tokens(line: &str, n: usize) -> Option<&str> {
895    let mut count = 0;
896    let mut in_token = false;
897    let mut end_idx = 0;
898    for (idx, ch) in line.char_indices() {
899        if ch.is_whitespace() {
900            if in_token {
901                in_token = false;
902                count += 1;
903                if count == n {
904                    end_idx = idx;
905                    break;
906                }
907            }
908        } else if !in_token {
909            in_token = true;
910        }
911    }
912
913    if in_token {
914        count += 1;
915        end_idx = line.len();
916    }
917
918    if count < n {
919        None
920    } else {
921        Some(line[end_idx..].trim_start())
922    }
923}