Skip to main content

cuda_assembler/
lib.rs

1/*!
2# cuda-assembler
3
4Text-to-bytecode assembler for agent instruction sets.
5
6Supports: labels, comments, data directives, confidence annotations,
7and the full 80-opcode agent instruction set from cuda-instruction-set.
8
9Usage (pseudocode, not Rust):
10```text
11  MOVI R0, 42        ; R0 = 42
12  CONF R0, 0.95      ; R0 confidence = 0.95
13  LABEL start:
14  ADDI R1, R0, 1
15  JNZ R1, start
16  TELL R0, "hello"   ; A2A broadcast
17  HALT
18```
19*/
20
21use std::collections::HashMap;
22use std::fmt;
23
24/// Opcode enum matching cuda-instruction-set
25#[derive(Clone, Copy, Debug, PartialEq)]
26pub enum Op {
27    // Control flow
28    Nop=0x00, Halt=0x01, Jmp=0x02, Jz=0x03, Jnz=0x04, Je=0x05, Jne=0x06,
29    Call=0x07, Ret=0x08,
30    // Arithmetic (confidence-preserving)
31    CAdd=0x10, CSub=0x11, CMul=0x12, CDiv=0x13, CMod=0x14, CNeg=0x15,
32    CInc=0x16, CDec=0x17, CMin=0x18, CMax=0x19, CAbs=0x1A,
33    // Comparison
34    Cmp=0x20, CLt=0x21, CLe=0x22, CEq=0x23, CGt=0x24, CGe=0x25,
35    // Bitwise
36    And=0x30, Or=0x31, Xor=0x32, Not=0x33, Shl=0x34, Shr=0x35,
37    // Float
38    FCvt=0x38, FNeg=0x39, FAdd=0x3A, FSub=0x3B, FMul=0x3C, FDiv=0x3D,
39    // Stack
40    Push=0x40, Pop=0x41, Dup=0x42, Swap=0x43,
41    // Memory
42    Load=0x48, Store=0x49, LoadF=0x4A, StoreF=0x4B, MAlloc=0x4C, MFree=0x4D,
43    // Confidence
44    Conf=0x50, Fuse=0x51, Drop=0x52, Trust=0x53, Gate=0x54,
45    // Immediate
46    MovI=0x58, AddI=0x59,
47    // A2A
48    Tell=0x60, Ask=0x61, Broadcast=0x62, Listen=0x63, Delegate=0x64,
49    // Instinct (biological)
50    InstinctAct=0x68, InstinctQ=0x69,
51    // Gene (biological)
52    GeneExpr=0x6A, EnzymeBind=0x6B, RnaTrans=0x6C, ProteinFold=0x6D,
53    // Membrane (biological)
54    MembraneChk=0x6E, Quarantine=0x6F,
55    // Energy (biological)
56    AtpGen=0x70, AtpConsume=0x71, AtpQ=0x72, AtpTransfer=0x73,
57    ApoptosisChk=0x74, ApoptosisTrigger=0x75,
58    CircadianSet=0x76, CircadianGet=0x77,
59    // System
60    SysCall=0x78, Debug=0x79, Yield=0x7A,
61}
62
63impl Op {
64    fn from_name(name: &str) -> Option<Self> {
65        let m = [
66            ("NOP", Op::Nop), ("HALT", Op::Halt), ("JMP", Op::Jmp), ("JZ", Op::Jz),
67            ("JNZ", Op::Jnz), ("JE", Op::Je), ("JNE", Op::Jne), ("CALL", Op::Call),
68            ("RET", Op::Ret),
69            ("CADD", Op::CAdd), ("CSUB", Op::CSub), ("CMUL", Op::CMul), ("CDIV", Op::CDiv),
70            ("CMOD", Op::CMod), ("CNEG", Op::CNeg), ("CINC", Op::CInc), ("CDEC", Op::CDec),
71            ("CMIN", Op::CMin), ("CMAX", Op::CMax), ("CABS", Op::CAbs),
72            ("CMP", Op::Cmp), ("CLT", Op::CLt), ("CLE", Op::CLe), ("CEQ", Op::CEq),
73            ("CGT", Op::CGt), ("CGE", Op::CGe),
74            ("AND", Op::And), ("OR", Op::Or), ("XOR", Op::Xor), ("NOT", Op::Not),
75            ("SHL", Op::Shl), ("SHR", Op::Shr),
76            ("FCVT", Op::FCvt), ("FNEG", Op::FNeg), ("FADD", Op::FAdd), ("FSUB", Op::FSub),
77            ("FMUL", Op::FMul), ("FDIV", Op::FDiv),
78            ("PUSH", Op::Push), ("POP", Op::Pop), ("DUP", Op::Dup), ("SWAP", Op::Swap),
79            ("LOAD", Op::Load), ("STORE", Op::Store), ("LOADF", Op::LoadF),
80            ("STOREF", Op::StoreF), ("MALLOC", Op::MAlloc), ("MFREE", Op::MFree),
81            ("CONF", Op::Conf), ("FUSE", Op::Fuse), ("DROP", Op::Drop), ("TRUST", Op::Trust),
82            ("GATE", Op::Gate),
83            ("MOVI", Op::MovI), ("ADDI", Op::AddI),
84            ("TELL", Op::Tell), ("ASK", Op::Ask), ("BROADCAST", Op::Broadcast),
85            ("LISTEN", Op::Listen), ("DELEGATE", Op::Delegate),
86            ("INSTINCT_ACT", Op::InstinctAct), ("INSTINCT_Q", Op::InstinctQ),
87            ("GENE_EXPR", Op::GeneExpr), ("ENZYME_BIND", Op::EnzymeBind),
88            ("RNA_TRANS", Op::RnaTrans), ("PROTEIN_FOLD", Op::ProteinFold),
89            ("MEMBRANE_CHK", Op::MembraneChk), ("QUARANTINE", Op::Quarantine),
90            ("ATP_GEN", Op::AtpGen), ("ATP_CONSUME", Op::AtpConsume), ("ATP_Q", Op::AtpQ),
91            ("ATP_TRANSFER", Op::AtpTransfer),
92            ("APOPTOSIS_CHK", Op::ApoptosisChk), ("APOPTOSIS_TRIGGER", Op::ApoptosisTrigger),
93            ("CIRCADIAN_SET", Op::CircadianSet), ("CIRCADIAN_GET", Op::CircadianGet),
94            ("SYSCALL", Op::SysCall), ("DEBUG", Op::Debug), ("YIELD", Op::Yield),
95        ];
96        m.iter().find(|(n,_)| n.eq_ignore_ascii_case(name)).map(|(_,o)| *o)
97    }
98
99    fn format(self) -> &'static str {
100        match self {
101            Op::Nop => "NOP", Op::Halt => "HALT", Op::Jmp => "JMP", Op::Jz => "JZ",
102            Op::Jnz => "JNZ", Op::Je => "JE", Op::Jne => "JNE", Op::Call => "CALL",
103            Op::Ret => "RET", Op::CAdd => "CADD", Op::CSub => "CSUB", Op::CMul => "CMUL",
104            Op::CDiv => "CDIV", Op::CMod => "CMOD", Op::CNeg => "CNEG", Op::CInc => "CINC",
105            Op::CDec => "CDEC", Op::CMin => "CMIN", Op::CMax => "CMAX", Op::CAbs => "CABS",
106            Op::Cmp => "CMP", Op::CLt => "CLT", Op::CLe => "CLE", Op::CEq => "CEQ",
107            Op::CGt => "CGT", Op::CGe => "CGE", Op::And => "AND", Op::Or => "OR",
108            Op::Xor => "XOR", Op::Not => "NOT", Op::Shl => "SHL", Op::Shr => "SHR",
109            Op::FCvt => "FCVT", Op::FNeg => "FNEG", Op::FAdd => "FADD", Op::FSub => "FSUB",
110            Op::FMul => "FMUL", Op::FDiv => "FDIV", Op::Push => "PUSH", Op::Pop => "POP",
111            Op::Dup => "DUP", Op::Swap => "SWAP", Op::Load => "LOAD", Op::Store => "STORE",
112            Op::LoadF => "LOADF", Op::StoreF => "STOREF", Op::MAlloc => "MALLOC",
113            Op::MFree => "MFREE", Op::Conf => "CONF", Op::Fuse => "FUSE", Op::Drop => "DROP",
114            Op::Trust => "TRUST", Op::Gate => "GATE", Op::MovI => "MOVI", Op::AddI => "ADDI",
115            Op::Tell => "TELL", Op::Ask => "ASK", Op::Broadcast => "BROADCAST",
116            Op::Listen => "LISTEN", Op::Delegate => "DELEGATE",
117            Op::InstinctAct => "INSTINCT_ACT", Op::InstinctQ => "INSTINCT_Q",
118            Op::GeneExpr => "GENE_EXPR", Op::EnzymeBind => "ENZYME_BIND",
119            Op::RnaTrans => "RNA_TRANS", Op::ProteinFold => "PROTEIN_FOLD",
120            Op::MembraneChk => "MEMBRANE_CHK", Op::Quarantine => "QUARANTINE",
121            Op::AtpGen => "ATP_GEN", Op::AtpConsume => "ATP_CONSUME", Op::AtpQ => "ATP_Q",
122            Op::AtpTransfer => "ATP_TRANSFER", Op::ApoptosisChk => "APOPTOSIS_CHK",
123            Op::ApoptosisTrigger => "APOPTOSIS_TRIGGER", Op::CircadianSet => "CIRCADIAN_SET",
124            Op::CircadianGet => "CIRCADIAN_GET", Op::SysCall => "SYSCALL",
125            Op::Debug => "DEBUG", Op::Yield => "YIELD",
126        }
127    }
128}
129
130/// Register name to index
131fn parse_reg(s: &str) -> Option<u8> {
132    let s = s.trim().trim_end_matches(',');
133    if s.starts_with('R') || s.starts_with('r') {
134        s[1..].parse::<u8>().ok().filter(|&r| r <= 15)
135    } else {
136        None
137    }
138}
139
140/// Assembler error
141#[derive(Debug)]
142pub enum AsmError {
143    UnknownOpcode(String),
144    BadRegister(String),
145    BadImmediate(String),
146    UndefinedLabel(String),
147    DuplicateLabel(String),
148    ParseError(String),
149}
150
151impl fmt::Display for AsmError {
152    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
153        match self {
154            AsmError::UnknownOpcode(s) => write!(f, "unknown opcode: {}", s),
155            AsmError::BadRegister(s) => write!(f, "bad register: {}", s),
156            AsmError::BadImmediate(s) => write!(f, "bad immediate: {}", s),
157            AsmError::UndefinedLabel(s) => write!(f, "undefined label: {}", s),
158            AsmError::DuplicateLabel(s) => write!(f, "duplicate label: {}", s),
159            AsmError::ParseError(s) => write!(f, "parse error: {}", s),
160        }
161    }
162}
163
164/// Two-pass assembler
165pub struct Assembler {
166    labels: HashMap<String, usize>,
167    data: Vec<u8>,
168}
169
170impl Assembler {
171    pub fn new() -> Self { Assembler { labels: HashMap::new(), data: vec![] } }
172
173    /// Assemble source text into bytecode
174    #[allow(unused_variables)]
175    pub fn assemble(&mut self, source: &str) -> Result<Vec<u8>, Vec<AsmError>> {
176        let mut errors = vec![];
177        let lines: Vec<&str> = source.lines().collect();
178
179        // Pass 1: collect labels and estimate sizes
180        let mut pc = 0usize;
181        for line in &lines {
182            let line = line.split(';').next().unwrap_or("").trim();
183            if line.is_empty() { continue; }
184            if line.to_uppercase().starts_with("LABEL ") || line.ends_with(':') {
185                // Handle both "LABEL foo:" and "foo:" formats
186                let upper = line.to_uppercase();
187                let name = if upper.starts_with("LABEL ") {
188                    line[6..].trim().trim_end_matches(':').trim()
189                } else {
190                    line.trim_end_matches(':').trim()
191                };
192                if self.labels.contains_key(name) {
193                    errors.push(AsmError::DuplicateLabel(name.to_string()));
194                }
195                self.labels.insert(name.to_string(), pc);
196                continue;
197            }
198            if line.to_uppercase().starts_with("DATA ") {
199                // Parse data bytes
200                let bytes_str = &line[5..];
201                let count = bytes_str.split(',').filter(|s| !s.trim().is_empty()).count();
202                pc += count;
203                continue;
204            }
205            let size = self.instr_size(line);
206            if size == 0 && !line.is_empty() {
207                if errors.len() < 10 { errors.push(AsmError::ParseError(format!("unknown: {}", line))); }
208            }
209            pc += size;
210        }
211        if !errors.is_empty() { return Err(errors); }
212
213        // Pass 2: emit bytecode
214        self.data = vec![];
215        for line in &lines {
216            let line = line.split(';').next().unwrap_or("").trim();
217            if line.is_empty() { continue; }
218            if line.to_uppercase().starts_with("LABEL ") || line.ends_with(':') { continue; }
219            if line.to_uppercase().starts_with("DATA ") {
220                self.emit_data(&line[5..]);
221                continue;
222            }
223            self.emit_instruction(line, &mut errors);
224        }
225        if !errors.is_empty() { return Err(errors); }
226        Ok(self.data.clone())
227    }
228
229    fn instr_size(&self, line: &str) -> usize {
230        let parts: Vec<&str> = line.split_whitespace().collect();
231        if parts.is_empty() { return 0; }
232        let op_name = parts[0].to_uppercase();
233        let op = match Op::from_name(&op_name) {
234            Some(o) => o,
235            None => return 0,
236        };
237        match op {
238            // No operands: 1 byte
239            Op::Nop | Op::Halt | Op::Ret | Op::Yield | Op::Swap => 1,
240            // Single register: 2 bytes (op + rd)
241            Op::CInc | Op::CDec | Op::CNeg | Op::CAbs | Op::Not |
242            Op::Push | Op::Pop | Op::Dup |
243            Op::InstinctQ | Op::AtpQ | Op::CircadianGet |
244            Op::ApoptosisChk | Op::ApoptosisTrigger | Op::Debug => 2,
245            // Two registers: 3 bytes (op + rd + rs1)
246            Op::CAdd | Op::CSub | Op::CMul | Op::CDiv | Op::CMod |
247            Op::CMin | Op::CMax | Op::Cmp | Op::CLt | Op::CLe | Op::CEq | Op::CGt | Op::CGe |
248            Op::And | Op::Or | Op::Xor | Op::Shl | Op::Shr |
249            Op::FCvt | Op::FNeg | Op::FAdd | Op::FSub | Op::FMul | Op::FDiv |
250            Op::Load | Op::Store | Op::LoadF | Op::StoreF |
251            Op::Conf | Op::Fuse | Op::Trust | Op::Gate |
252            Op::GeneExpr | Op::EnzymeBind | Op::RnaTrans | Op::ProteinFold |
253            Op::MembraneChk | Op::Quarantine |
254            Op::AtpGen | Op::AtpConsume | Op::AtpTransfer |
255            Op::CircadianSet | Op::InstinctAct |
256            Op::Tell | Op::Ask | Op::Listen | Op::Delegate => 3,
257            // Register + immediate: 4 bytes (op + rd + imm16)
258            Op::MovI | Op::AddI | Op::MAlloc | Op::Drop | Op::Jz | Op::Jnz |
259            Op::Je | Op::Jne => 4,
260            // Jump: 4 bytes (op + flags + offset16)
261            Op::Jmp | Op::Call => 4,
262            // Broadcast: 5+ bytes (op + rd + len16 + bytes)
263            Op::Broadcast => 5,
264            // System: 3 bytes
265            Op::SysCall => 3,
266            Op::MFree => 2,
267        }
268    }
269
270    fn emit_data(&mut self, s: &str) {
271        for part in s.split(',') {
272            let trimmed = part.trim();
273            if let Ok(b) = trimmed.parse::<u8>() { self.data.push(b); }
274        }
275    }
276
277    fn emit_instruction(&mut self, line: &str, errors: &mut Vec<AsmError>) {
278        let parts: Vec<&str> = line.split_whitespace().collect();
279        if parts.is_empty() { return; }
280        let op = match Op::from_name(parts[0]) {
281            Some(o) => o,
282            None => { errors.push(AsmError::UnknownOpcode(parts[0].to_string())); return; }
283        };
284
285        let get_reg = |idx: usize, errs: &mut Vec<AsmError>| -> u8 {
286            if idx >= parts.len() { errs.push(AsmError::BadRegister("missing".into())); return 0; }
287            parse_reg(parts[idx]).unwrap_or_else(|| { errs.push(AsmError::BadRegister(parts[idx].to_string())); 0 })
288        };
289
290        let get_imm = |idx: usize, errs: &mut Vec<AsmError>| -> i16 {
291            if idx >= parts.len() { errs.push(AsmError::BadImmediate("missing".into())); return 0; }
292            // Try as label first
293            if let Some(addr) = self.labels.get(parts[idx]) {
294                // Will be patched later — for now store 0
295                return 0;
296            }
297            // Parse as number
298            let s = parts[idx].trim_end_matches(',');
299            s.parse::<i16>().unwrap_or_else(|_| {
300                // Try hex
301                if s.starts_with("0x") { i16::from_str_radix(&s[2..], 16).unwrap_or(0) }
302                else { 0 }
303            })
304        };
305
306        let get_imm_from_label = |idx: usize, errs: &mut Vec<AsmError>, labels: &HashMap<String,usize>, data_len: usize| -> i16 {
307            if idx >= parts.len() { return 0; }
308            let s = parts[idx].trim_end_matches(',');
309            if let Some(&addr) = labels.get(s) {
310                // offset from current PC (after this instruction)
311                let instr_end = data_len + 4; // jumps are 4 bytes
312                let offset = addr as isize - instr_end as isize;
313                return offset as i16;
314            }
315            s.parse::<i16>().unwrap_or_else(|_| {
316                if s.starts_with("0x") { i16::from_str_radix(&s[2..], 16).unwrap_or(0) }
317                else { 0 }
318            })
319        };
320        let labels_clone = self.labels.clone();
321
322        self.data.push(op as u8);
323
324        match op {
325            Op::Nop | Op::Halt | Op::Ret | Op::Yield | Op::Swap => {}
326            Op::CInc | Op::CDec | Op::CNeg | Op::CAbs | Op::Not |
327            Op::Push | Op::Pop | Op::Dup | Op::MFree |
328            Op::InstinctQ | Op::AtpQ | Op::CircadianGet |
329            Op::ApoptosisChk | Op::ApoptosisTrigger | Op::Debug => {
330                self.data.push(get_reg(1, errors));
331            }
332            Op::CAdd | Op::CSub | Op::CMul | Op::CDiv | Op::CMod |
333            Op::CMin | Op::CMax | Op::Cmp | Op::CLt | Op::CLe | Op::CEq | Op::CGt | Op::CGe |
334            Op::And | Op::Or | Op::Xor | Op::Shl | Op::Shr |
335            Op::FCvt | Op::FNeg | Op::FAdd | Op::FSub | Op::FMul | Op::FDiv |
336            Op::Load | Op::Store | Op::LoadF | Op::StoreF |
337            Op::Conf | Op::Fuse | Op::Trust | Op::Gate |
338            Op::GeneExpr | Op::EnzymeBind | Op::RnaTrans | Op::ProteinFold |
339            Op::MembraneChk | Op::Quarantine |
340            Op::AtpGen | Op::AtpConsume | Op::AtpTransfer |
341            Op::CircadianSet | Op::InstinctAct |
342            Op::Tell | Op::Ask | Op::Listen | Op::Delegate => {
343                self.data.push(get_reg(1, errors));
344                self.data.push(get_reg(2, errors));
345            }
346            Op::MovI | Op::AddI | Op::MAlloc | Op::Drop => {
347                self.data.push(get_reg(1, errors));
348                let imm = get_imm_from_label(2, errors, &labels_clone, self.data.len());
349                self.data.push((imm & 0xFF) as u8);
350                self.data.push(((imm >> 8) & 0xFF) as u8);
351            }
352            Op::Jz | Op::Jnz | Op::Je | Op::Jne => {
353                self.data.push(get_reg(1, errors));
354                let imm = get_imm_from_label(2, errors, &labels_clone, self.data.len());
355                self.data.push((imm & 0xFF) as u8);
356                self.data.push(((imm >> 8) & 0xFF) as u8);
357            }
358            Op::Jmp | Op::Call => {
359                self.data.push(0); // flags/unused
360                let imm = get_imm_from_label(1, errors, &labels_clone, self.data.len());
361                self.data.push((imm & 0xFF) as u8);
362                self.data.push(((imm >> 8) & 0xFF) as u8);
363            }
364            Op::Broadcast => {
365                self.data.push(get_reg(1, errors));
366                // For simplicity, emit empty payload
367                self.data.push(0); self.data.push(0); // len
368            }
369            Op::SysCall => {
370                let n = get_imm_from_label(1, errors, &labels_clone, self.data.len());
371                self.data.push(n as u8);
372                self.data.push(0);
373            }
374        }
375    }
376
377    /// Disassemble bytecode back to text
378    pub fn disassemble(&self, bytecode: &[u8]) -> String {
379        let mut out = String::new();
380        let mut pc = 0;
381        while pc < bytecode.len() {
382            let op_byte = bytecode[pc];
383            // Find matching op
384            let op = Self::op_from_byte(op_byte);
385            let name = op.map(|o| o.format()).unwrap_or("???");
386            out.push_str(&format!("{:04x}: {}\n", pc, name));
387            pc += match op {
388                None => 1,
389                Some(o) => self.op_size(o),
390            };
391        }
392        out
393    }
394
395    fn op_from_byte(b: u8) -> Option<Op> {
396        // Match by discriminant
397        match b {
398            0x00 => Some(Op::Nop), 0x01 => Some(Op::Halt), 0x02 => Some(Op::Jmp),
399            0x03 => Some(Op::Jz), 0x04 => Some(Op::Jnz), 0x05 => Some(Op::Je),
400            0x06 => Some(Op::Jne), 0x07 => Some(Op::Call), 0x08 => Some(Op::Ret),
401            0x10 => Some(Op::CAdd), 0x11 => Some(Op::CSub), 0x12 => Some(Op::CMul),
402            0x13 => Some(Op::CDiv), 0x14 => Some(Op::CMod), 0x15 => Some(Op::CNeg),
403            0x16 => Some(Op::CInc), 0x17 => Some(Op::CDec), 0x18 => Some(Op::CMin),
404            0x19 => Some(Op::CMax), 0x1A => Some(Op::CAbs),
405            0x20 => Some(Op::Cmp), 0x21 => Some(Op::CLt), 0x22 => Some(Op::CLe),
406            0x23 => Some(Op::CEq), 0x24 => Some(Op::CGt), 0x25 => Some(Op::CGe),
407            0x30 => Some(Op::And), 0x31 => Some(Op::Or), 0x32 => Some(Op::Xor),
408            0x33 => Some(Op::Not), 0x34 => Some(Op::Shl), 0x35 => Some(Op::Shr),
409            0x38 => Some(Op::FCvt), 0x39 => Some(Op::FNeg), 0x3A => Some(Op::FAdd),
410            0x3B => Some(Op::FSub), 0x3C => Some(Op::FMul), 0x3D => Some(Op::FDiv),
411            0x40 => Some(Op::Push), 0x41 => Some(Op::Pop), 0x42 => Some(Op::Dup),
412            0x43 => Some(Op::Swap),
413            0x48 => Some(Op::Load), 0x49 => Some(Op::Store), 0x4A => Some(Op::LoadF),
414            0x4B => Some(Op::StoreF), 0x4C => Some(Op::MAlloc), 0x4D => Some(Op::MFree),
415            0x50 => Some(Op::Conf), 0x51 => Some(Op::Fuse), 0x52 => Some(Op::Drop),
416            0x53 => Some(Op::Trust), 0x54 => Some(Op::Gate),
417            0x58 => Some(Op::MovI), 0x59 => Some(Op::AddI),
418            0x60 => Some(Op::Tell), 0x61 => Some(Op::Ask), 0x62 => Some(Op::Broadcast),
419            0x63 => Some(Op::Listen), 0x64 => Some(Op::Delegate),
420            0x68 => Some(Op::InstinctAct), 0x69 => Some(Op::InstinctQ),
421            0x6A => Some(Op::GeneExpr), 0x6B => Some(Op::EnzymeBind),
422            0x6C => Some(Op::RnaTrans), 0x6D => Some(Op::ProteinFold),
423            0x6E => Some(Op::MembraneChk), 0x6F => Some(Op::Quarantine),
424            0x70 => Some(Op::AtpGen), 0x71 => Some(Op::AtpConsume),
425            0x72 => Some(Op::AtpQ), 0x73 => Some(Op::AtpTransfer),
426            0x74 => Some(Op::ApoptosisChk), 0x75 => Some(Op::ApoptosisTrigger),
427            0x76 => Some(Op::CircadianSet), 0x77 => Some(Op::CircadianGet),
428            0x78 => Some(Op::SysCall), 0x79 => Some(Op::Debug), 0x7A => Some(Op::Yield),
429            _ => None,
430        }
431    }
432
433    fn op_size(&self, op: Op) -> usize {
434        // Simplified — just return 1 for unknown
435        let dummy = format!("{} R0", op.format());
436        self.instr_size(&dummy).max(1)
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::*;
443
444    #[test]
445    fn test_simple_program() {
446        let mut asm = Assembler::new();
447        let src = "MOVI R0, 42\nCINC R0\nHALT";
448        let bc = asm.assemble(src).unwrap();
449        assert!(!bc.is_empty());
450        assert_eq!(bc[0], Op::MovI as u8);
451        assert_eq!(bc[4], Op::CInc as u8);
452        assert_eq!(bc[6], Op::Halt as u8);
453    }
454
455    #[test]
456    fn test_labels() {
457        let mut asm = Assembler::new();
458        let src = "LABEL start:\nMOVI R0, 10\nLABEL end:\nHALT";
459        let bc = asm.assemble(src).unwrap();
460        assert_eq!(asm.labels["start"], 0);
461        assert_eq!(asm.labels["end"], 4); // MOVI is 4 bytes
462        assert_eq!(bc[4], Op::Halt as u8);
463    }
464
465    #[test]
466    fn test_jump_to_label() {
467        let mut asm = Assembler::new();
468        let src = "LABEL loop:\nCDEC R0\nJNZ R0, loop\nHALT";
469        let bc = asm.assemble(src).unwrap();
470        assert!(bc.len() > 0);
471    }
472
473    #[test]
474    fn test_comments() {
475        let mut asm = Assembler::new();
476        let src = "; this is a comment\nNOP ; inline comment\nHALT";
477        let bc = asm.assemble(src).unwrap();
478        assert_eq!(bc[0], Op::Nop as u8);
479        assert_eq!(bc[1], Op::Halt as u8);
480        assert_eq!(bc.len(), 2);
481    }
482
483    #[test]
484    fn test_data_directive() {
485        let mut asm = Assembler::new();
486        let src = "DATA 1, 2, 3, 42\nHALT";
487        let bc = asm.assemble(src).unwrap();
488        assert_eq!(&bc[0..4], &[1, 2, 3, 42]);
489        assert_eq!(bc[4], Op::Halt as u8);
490    }
491
492    #[test]
493    fn test_confidence_ops() {
494        let mut asm = Assembler::new();
495        let src = "CONF R0, R1\nFUSE R0, R1\nTRUST R0, R1";
496        let bc = asm.assemble(src).unwrap();
497        assert_eq!(bc[0], Op::Conf as u8);
498        assert_eq!(bc[3], Op::Fuse as u8);
499        assert_eq!(bc[6], Op::Trust as u8);
500    }
501
502    #[test]
503    fn test_biological_ops() {
504        let mut asm = Assembler::new();
505        let src = "INSTINCT_ACT R0, R1\nGENE_EXPR R0, R1\nATP_GEN R0, R1\nMEMBRANE_CHK R0, R1";
506        let bc = asm.assemble(src).unwrap();
507        assert_eq!(bc[0], 0x68); // INSTINCT_ACT
508        assert_eq!(bc[3], 0x6A); // GENE_EXPR
509        assert_eq!(bc[6], 0x70); // ATP_GEN
510        assert_eq!(bc[9], 0x6E); // MEMBRANE_CHK
511    }
512
513    #[test]
514    fn test_a2a_ops() {
515        let mut asm = Assembler::new();
516        let src = "TELL R0, R1\nASK R0, R1\nBROADCAST R0";
517        let bc = asm.assemble(src).unwrap();
518        assert_eq!(bc[0], 0x60);
519        assert_eq!(bc[3], 0x61);
520        assert_eq!(bc[6], 0x62);
521    }
522
523    #[test]
524    fn test_arithmetic() {
525        let mut asm = Assembler::new();
526        let src = "MOVI R0, 10\nMOVI R1, 3\nCADD R0, R1\nCDIV R0, R1";
527        let bc = asm.assemble(src).unwrap();
528        assert_eq!(bc[0], Op::MovI as u8);
529        assert_eq!(bc[4], Op::MovI as u8);
530        assert_eq!(bc[8], Op::CAdd as u8);
531        assert_eq!(bc[11], Op::CDiv as u8);
532    }
533
534    #[test]
535    fn test_case_insensitive() {
536        let mut asm = Assembler::new();
537        let bc1 = asm.assemble("NOP\nhalt").unwrap();
538        let mut asm2 = Assembler::new();
539        let bc2 = asm2.assemble("nop\nHALT").unwrap();
540        assert_eq!(bc1, bc2);
541    }
542
543    #[test]
544    fn test_unknown_opcode() {
545        let mut asm = Assembler::new();
546        let result = asm.assemble("FOOBAR R0, R1");
547        assert!(result.is_err());
548    }
549
550    #[test]
551    fn test_disassemble() {
552        let mut asm = Assembler::new();
553        let bc = asm.assemble("NOP\nMOVI R0, 42\nHALT").unwrap();
554        let out = asm.disassemble(&bc);
555        assert!(out.contains("NOP"));
556        assert!(out.contains("MOVI"));
557        assert!(out.contains("HALT"));
558    }
559
560    #[test]
561    fn test_stack_ops() {
562        let mut asm = Assembler::new();
563        let src = "PUSH R0\nPUSH R1\nSWAP\nPOP R0";
564        let bc = asm.assemble(src).unwrap();
565        assert_eq!(bc[0], 0x40); // PUSH
566        assert_eq!(bc[2], 0x40); // PUSH
567        assert_eq!(bc[4], 0x43); // SWAP
568        assert_eq!(bc[5], 0x41); // POP
569    }
570
571    #[test]
572    fn test_memory_ops() {
573        let mut asm = Assembler::new();
574        let src = "MOVI R0, 0\nMOVI R1, 42\nSTORE R1, R0";
575        let bc = asm.assemble(src).unwrap();
576        assert_eq!(bc[8], Op::Store as u8);
577    }
578
579    #[test]
580    fn test_call_ret() {
581        let mut asm = Assembler::new();
582        let src = "LABEL func:\nRET\nCALL func\nHALT";
583        let bc = asm.assemble(src).unwrap();
584        assert_eq!(bc[0], Op::Ret as u8);
585        assert_eq!(bc[1], Op::Call as u8);
586    }
587}