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:
10```
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    pub fn assemble(&mut self, source: &str) -> Result<Vec<u8>, Vec<AsmError>> {
175        let mut errors = vec![];
176        let lines: Vec<&str> = source.lines().collect();
177
178        // Pass 1: collect labels and estimate sizes
179        let mut pc = 0usize;
180        for line in &lines {
181            let line = line.split(';').next().unwrap_or("").trim();
182            if line.is_empty() { continue; }
183            if line.to_uppercase().starts_with("LABEL ") || line.ends_with(':') {
184                // Handle both "LABEL foo:" and "foo:" formats
185                let upper = line.to_uppercase();
186                let name = if upper.starts_with("LABEL ") {
187                    line[6..].trim().trim_end_matches(':').trim()
188                } else {
189                    line.trim_end_matches(':').trim()
190                };
191                if self.labels.contains_key(name) {
192                    errors.push(AsmError::DuplicateLabel(name.to_string()));
193                }
194                self.labels.insert(name.to_string(), pc);
195                continue;
196            }
197            if line.to_uppercase().starts_with("DATA ") {
198                // Parse data bytes
199                let bytes_str = &line[5..];
200                let count = bytes_str.split(',').filter(|s| !s.trim().is_empty()).count();
201                pc += count;
202                continue;
203            }
204            let size = self.instr_size(line);
205            if size == 0 && !line.is_empty() {
206                if errors.len() < 10 { errors.push(AsmError::ParseError(format!("unknown: {}", line))); }
207            }
208            pc += size;
209        }
210        if !errors.is_empty() { return Err(errors); }
211
212        // Pass 2: emit bytecode
213        self.data = vec![];
214        for line in &lines {
215            let line = line.split(';').next().unwrap_or("").trim();
216            if line.is_empty() { continue; }
217            if line.to_uppercase().starts_with("LABEL ") || line.ends_with(':') { continue; }
218            if line.to_uppercase().starts_with("DATA ") {
219                self.emit_data(&line[5..]);
220                continue;
221            }
222            self.emit_instruction(line, &mut errors);
223        }
224        if !errors.is_empty() { return Err(errors); }
225        Ok(self.data.clone())
226    }
227
228    fn instr_size(&self, line: &str) -> usize {
229        let parts: Vec<&str> = line.split_whitespace().collect();
230        if parts.is_empty() { return 0; }
231        let op_name = parts[0].to_uppercase();
232        let op = match Op::from_name(&op_name) {
233            Some(o) => o,
234            None => return 0,
235        };
236        match op {
237            // No operands: 1 byte
238            Op::Nop | Op::Halt | Op::Ret | Op::Yield | Op::Swap => 1,
239            // Single register: 2 bytes (op + rd)
240            Op::CInc | Op::CDec | Op::CNeg | Op::CAbs | Op::Not |
241            Op::Push | Op::Pop | Op::Dup |
242            Op::InstinctQ | Op::AtpQ | Op::CircadianGet |
243            Op::ApoptosisChk | Op::ApoptosisTrigger | Op::Debug => 2,
244            // Two registers: 3 bytes (op + rd + rs1)
245            Op::CAdd | Op::CSub | Op::CMul | Op::CDiv | Op::CMod |
246            Op::CMin | Op::CMax | Op::Cmp | Op::CLt | Op::CLe | Op::CEq | Op::CGt | Op::CGe |
247            Op::And | Op::Or | Op::Xor | Op::Shl | Op::Shr |
248            Op::FCvt | Op::FNeg | Op::FAdd | Op::FSub | Op::FMul | Op::FDiv |
249            Op::Load | Op::Store | Op::LoadF | Op::StoreF |
250            Op::Conf | Op::Fuse | Op::Trust | Op::Gate |
251            Op::GeneExpr | Op::EnzymeBind | Op::RnaTrans | Op::ProteinFold |
252            Op::MembraneChk | Op::Quarantine |
253            Op::AtpGen | Op::AtpConsume | Op::AtpTransfer |
254            Op::CircadianSet | Op::InstinctAct |
255            Op::Tell | Op::Ask | Op::Listen | Op::Delegate => 3,
256            // Register + immediate: 4 bytes (op + rd + imm16)
257            Op::MovI | Op::AddI | Op::MAlloc | Op::Drop | Op::Jz | Op::Jnz |
258            Op::Je | Op::Jne => 4,
259            // Jump: 4 bytes (op + flags + offset16)
260            Op::Jmp | Op::Call => 4,
261            // Broadcast: 5+ bytes (op + rd + len16 + bytes)
262            Op::Broadcast => 5,
263            // System: 3 bytes
264            Op::SysCall => 3,
265            Op::MFree => 2,
266        }
267    }
268
269    fn emit_data(&mut self, s: &str) {
270        for part in s.split(',') {
271            let trimmed = part.trim();
272            if let Ok(b) = trimmed.parse::<u8>() { self.data.push(b); }
273        }
274    }
275
276    fn emit_instruction(&mut self, line: &str, errors: &mut Vec<AsmError>) {
277        let parts: Vec<&str> = line.split_whitespace().collect();
278        if parts.is_empty() { return; }
279        let op = match Op::from_name(parts[0]) {
280            Some(o) => o,
281            None => { errors.push(AsmError::UnknownOpcode(parts[0].to_string())); return; }
282        };
283
284        let get_reg = |idx: usize, errs: &mut Vec<AsmError>| -> u8 {
285            if idx >= parts.len() { errs.push(AsmError::BadRegister("missing".into())); return 0; }
286            parse_reg(parts[idx]).unwrap_or_else(|| { errs.push(AsmError::BadRegister(parts[idx].to_string())); 0 })
287        };
288
289        let get_imm = |idx: usize, errs: &mut Vec<AsmError>| -> i16 {
290            if idx >= parts.len() { errs.push(AsmError::BadImmediate("missing".into())); return 0; }
291            // Try as label first
292            if let Some(&addr) = self.labels.get(parts[idx]) {
293                // Will be patched later — for now store 0
294                return 0;
295            }
296            // Parse as number
297            let s = parts[idx].trim_end_matches(',');
298            s.parse::<i16>().unwrap_or_else(|_| {
299                // Try hex
300                if s.starts_with("0x") { i16::from_str_radix(&s[2..], 16).unwrap_or(0) }
301                else { 0 }
302            })
303        };
304
305        let get_imm_from_label = |idx: usize, errs: &mut Vec<AsmError>, labels: &HashMap<String,usize>, data_len: usize| -> i16 {
306            if idx >= parts.len() { return 0; }
307            let s = parts[idx].trim_end_matches(',');
308            if let Some(&addr) = labels.get(s) {
309                // offset from current PC (after this instruction)
310                let instr_end = data_len + 4; // jumps are 4 bytes
311                let offset = addr as isize - instr_end as isize;
312                return offset as i16;
313            }
314            s.parse::<i16>().unwrap_or_else(|_| {
315                if s.starts_with("0x") { i16::from_str_radix(&s[2..], 16).unwrap_or(0) }
316                else { 0 }
317            })
318        };
319        let labels_clone = self.labels.clone();
320
321        self.data.push(op as u8);
322
323        match op {
324            Op::Nop | Op::Halt | Op::Ret | Op::Yield | Op::Swap => {}
325            Op::CInc | Op::CDec | Op::CNeg | Op::CAbs | Op::Not |
326            Op::Push | Op::Pop | Op::Dup | Op::MFree |
327            Op::InstinctQ | Op::AtpQ | Op::CircadianGet |
328            Op::ApoptosisChk | Op::ApoptosisTrigger | Op::Debug => {
329                self.data.push(get_reg(1, errors));
330            }
331            Op::CAdd | Op::CSub | Op::CMul | Op::CDiv | Op::CMod |
332            Op::CMin | Op::CMax | Op::Cmp | Op::CLt | Op::CLe | Op::CEq | Op::CGt | Op::CGe |
333            Op::And | Op::Or | Op::Xor | Op::Shl | Op::Shr |
334            Op::FCvt | Op::FNeg | Op::FAdd | Op::FSub | Op::FMul | Op::FDiv |
335            Op::Load | Op::Store | Op::LoadF | Op::StoreF |
336            Op::Conf | Op::Fuse | Op::Trust | Op::Gate |
337            Op::GeneExpr | Op::EnzymeBind | Op::RnaTrans | Op::ProteinFold |
338            Op::MembraneChk | Op::Quarantine |
339            Op::AtpGen | Op::AtpConsume | Op::AtpTransfer |
340            Op::CircadianSet | Op::InstinctAct |
341            Op::Tell | Op::Ask | Op::Listen | Op::Delegate => {
342                self.data.push(get_reg(1, errors));
343                self.data.push(get_reg(2, errors));
344            }
345            Op::MovI | Op::AddI | Op::MAlloc | Op::Drop => {
346                self.data.push(get_reg(1, errors));
347                let imm = get_imm_from_label(2, errors, &labels_clone, self.data.len());
348                self.data.push((imm & 0xFF) as u8);
349                self.data.push(((imm >> 8) & 0xFF) as u8);
350            }
351            Op::Jz | Op::Jnz | Op::Je | Op::Jne => {
352                self.data.push(get_reg(1, errors));
353                let imm = get_imm_from_label(2, errors, &labels_clone, self.data.len());
354                self.data.push((imm & 0xFF) as u8);
355                self.data.push(((imm >> 8) & 0xFF) as u8);
356            }
357            Op::Jmp | Op::Call => {
358                self.data.push(0); // flags/unused
359                let imm = get_imm_from_label(1, errors, &labels_clone, self.data.len());
360                self.data.push((imm & 0xFF) as u8);
361                self.data.push(((imm >> 8) & 0xFF) as u8);
362            }
363            Op::Broadcast => {
364                self.data.push(get_reg(1, errors));
365                // For simplicity, emit empty payload
366                self.data.push(0); self.data.push(0); // len
367            }
368            Op::SysCall => {
369                let n = get_imm_from_label(1, errors, &labels_clone, self.data.len());
370                self.data.push(n as u8);
371                self.data.push(0);
372            }
373        }
374    }
375
376    /// Disassemble bytecode back to text
377    pub fn disassemble(&self, bytecode: &[u8]) -> String {
378        let mut out = String::new();
379        let mut pc = 0;
380        while pc < bytecode.len() {
381            let op_byte = bytecode[pc];
382            // Find matching op
383            let op = Self::op_from_byte(op_byte);
384            let name = op.map(|o| o.format()).unwrap_or("???");
385            out.push_str(&format!("{:04x}: {}\n", pc, name));
386            pc += match op {
387                None => 1,
388                Some(o) => self.op_size(o),
389            };
390        }
391        out
392    }
393
394    fn op_from_byte(b: u8) -> Option<Op> {
395        // Match by discriminant
396        match b {
397            0x00 => Some(Op::Nop), 0x01 => Some(Op::Halt), 0x02 => Some(Op::Jmp),
398            0x03 => Some(Op::Jz), 0x04 => Some(Op::Jnz), 0x05 => Some(Op::Je),
399            0x06 => Some(Op::Jne), 0x07 => Some(Op::Call), 0x08 => Some(Op::Ret),
400            0x10 => Some(Op::CAdd), 0x11 => Some(Op::CSub), 0x12 => Some(Op::CMul),
401            0x13 => Some(Op::CDiv), 0x14 => Some(Op::CMod), 0x15 => Some(Op::CNeg),
402            0x16 => Some(Op::CInc), 0x17 => Some(Op::CDec), 0x18 => Some(Op::CMin),
403            0x19 => Some(Op::CMax), 0x1A => Some(Op::CAbs),
404            0x20 => Some(Op::Cmp), 0x21 => Some(Op::CLt), 0x22 => Some(Op::CLe),
405            0x23 => Some(Op::CEq), 0x24 => Some(Op::CGt), 0x25 => Some(Op::CGe),
406            0x30 => Some(Op::And), 0x31 => Some(Op::Or), 0x32 => Some(Op::Xor),
407            0x33 => Some(Op::Not), 0x34 => Some(Op::Shl), 0x35 => Some(Op::Shr),
408            0x38 => Some(Op::FCvt), 0x39 => Some(Op::FNeg), 0x3A => Some(Op::FAdd),
409            0x3B => Some(Op::FSub), 0x3C => Some(Op::FMul), 0x3D => Some(Op::FDiv),
410            0x40 => Some(Op::Push), 0x41 => Some(Op::Pop), 0x42 => Some(Op::Dup),
411            0x43 => Some(Op::Swap),
412            0x48 => Some(Op::Load), 0x49 => Some(Op::Store), 0x4A => Some(Op::LoadF),
413            0x4B => Some(Op::StoreF), 0x4C => Some(Op::MAlloc), 0x4D => Some(Op::MFree),
414            0x50 => Some(Op::Conf), 0x51 => Some(Op::Fuse), 0x52 => Some(Op::Drop),
415            0x53 => Some(Op::Trust), 0x54 => Some(Op::Gate),
416            0x58 => Some(Op::MovI), 0x59 => Some(Op::AddI),
417            0x60 => Some(Op::Tell), 0x61 => Some(Op::Ask), 0x62 => Some(Op::Broadcast),
418            0x63 => Some(Op::Listen), 0x64 => Some(Op::Delegate),
419            0x68 => Some(Op::InstinctAct), 0x69 => Some(Op::InstinctQ),
420            0x6A => Some(Op::GeneExpr), 0x6B => Some(Op::EnzymeBind),
421            0x6C => Some(Op::RnaTrans), 0x6D => Some(Op::ProteinFold),
422            0x6E => Some(Op::MembraneChk), 0x6F => Some(Op::Quarantine),
423            0x70 => Some(Op::AtpGen), 0x71 => Some(Op::AtpConsume),
424            0x72 => Some(Op::AtpQ), 0x73 => Some(Op::AtpTransfer),
425            0x74 => Some(Op::ApoptosisChk), 0x75 => Some(Op::ApoptosisTrigger),
426            0x76 => Some(Op::CircadianSet), 0x77 => Some(Op::CircadianGet),
427            0x78 => Some(Op::SysCall), 0x79 => Some(Op::Debug), 0x7A => Some(Op::Yield),
428            _ => None,
429        }
430    }
431
432    fn op_size(&self, op: Op) -> usize {
433        // Simplified — just return 1 for unknown
434        let dummy = format!("{} R0", op.format());
435        self.instr_size(&dummy).max(1)
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn test_simple_program() {
445        let mut asm = Assembler::new();
446        let src = "MOVI R0, 42\nCINC R0\nHALT";
447        let bc = asm.assemble(src).unwrap();
448        assert!(!bc.is_empty());
449        assert_eq!(bc[0], Op::MovI as u8);
450        assert_eq!(bc[4], Op::CInc as u8);
451        assert_eq!(bc[6], Op::Halt as u8);
452    }
453
454    #[test]
455    fn test_labels() {
456        let mut asm = Assembler::new();
457        let src = "LABEL start:\nMOVI R0, 10\nLABEL end:\nHALT";
458        let bc = asm.assemble(src).unwrap();
459        assert_eq!(asm.labels["start"], 0);
460        assert_eq!(asm.labels["end"], 4); // MOVI is 4 bytes
461        assert_eq!(bc[4], Op::Halt as u8);
462    }
463
464    #[test]
465    fn test_jump_to_label() {
466        let mut asm = Assembler::new();
467        let src = "LABEL loop:\nCDEC R0\nJNZ R0, loop\nHALT";
468        let bc = asm.assemble(src).unwrap();
469        assert!(bc.len() > 0);
470    }
471
472    #[test]
473    fn test_comments() {
474        let mut asm = Assembler::new();
475        let src = "; this is a comment\nNOP ; inline comment\nHALT";
476        let bc = asm.assemble(src).unwrap();
477        assert_eq!(bc[0], Op::Nop as u8);
478        assert_eq!(bc[1], Op::Halt as u8);
479        assert_eq!(bc.len(), 2);
480    }
481
482    #[test]
483    fn test_data_directive() {
484        let mut asm = Assembler::new();
485        let src = "DATA 1, 2, 3, 42\nHALT";
486        let bc = asm.assemble(src).unwrap();
487        assert_eq!(&bc[0..4], &[1, 2, 3, 42]);
488        assert_eq!(bc[4], Op::Halt as u8);
489    }
490
491    #[test]
492    fn test_confidence_ops() {
493        let mut asm = Assembler::new();
494        let src = "CONF R0, R1\nFUSE R0, R1\nTRUST R0, R1";
495        let bc = asm.assemble(src).unwrap();
496        assert_eq!(bc[0], Op::Conf as u8);
497        assert_eq!(bc[3], Op::Fuse as u8);
498        assert_eq!(bc[6], Op::Trust as u8);
499    }
500
501    #[test]
502    fn test_biological_ops() {
503        let mut asm = Assembler::new();
504        let src = "INSTINCT_ACT R0, R1\nGENE_EXPR R0, R1\nATP_GEN R0, R1\nMEMBRANE_CHK R0, R1";
505        let bc = asm.assemble(src).unwrap();
506        assert_eq!(bc[0], 0x68); // INSTINCT_ACT
507        assert_eq!(bc[3], 0x6A); // GENE_EXPR
508        assert_eq!(bc[6], 0x70); // ATP_GEN
509        assert_eq!(bc[9], 0x6E); // MEMBRANE_CHK
510    }
511
512    #[test]
513    fn test_a2a_ops() {
514        let mut asm = Assembler::new();
515        let src = "TELL R0, R1\nASK R0, R1\nBROADCAST R0";
516        let bc = asm.assemble(src).unwrap();
517        assert_eq!(bc[0], 0x60);
518        assert_eq!(bc[3], 0x61);
519        assert_eq!(bc[6], 0x62);
520    }
521
522    #[test]
523    fn test_arithmetic() {
524        let mut asm = Assembler::new();
525        let src = "MOVI R0, 10\nMOVI R1, 3\nCADD R0, R1\nCDIV R0, R1";
526        let bc = asm.assemble(src).unwrap();
527        assert_eq!(bc[0], Op::MovI as u8);
528        assert_eq!(bc[4], Op::MovI as u8);
529        assert_eq!(bc[8], Op::CAdd as u8);
530        assert_eq!(bc[11], Op::CDiv as u8);
531    }
532
533    #[test]
534    fn test_case_insensitive() {
535        let mut asm = Assembler::new();
536        let bc1 = asm.assemble("NOP\nhalt").unwrap();
537        let mut asm2 = Assembler::new();
538        let bc2 = asm2.assemble("nop\nHALT").unwrap();
539        assert_eq!(bc1, bc2);
540    }
541
542    #[test]
543    fn test_unknown_opcode() {
544        let mut asm = Assembler::new();
545        let result = asm.assemble("FOOBAR R0, R1");
546        assert!(result.is_err());
547    }
548
549    #[test]
550    fn test_disassemble() {
551        let mut asm = Assembler::new();
552        let bc = asm.assemble("NOP\nMOVI R0, 42\nHALT").unwrap();
553        let out = asm.disassemble(&bc);
554        assert!(out.contains("NOP"));
555        assert!(out.contains("MOVI"));
556        assert!(out.contains("HALT"));
557    }
558
559    #[test]
560    fn test_stack_ops() {
561        let mut asm = Assembler::new();
562        let src = "PUSH R0\nPUSH R1\nSWAP\nPOP R0";
563        let bc = asm.assemble(src).unwrap();
564        assert_eq!(bc[0], 0x40); // PUSH
565        assert_eq!(bc[2], 0x40); // PUSH
566        assert_eq!(bc[4], 0x43); // SWAP
567        assert_eq!(bc[5], 0x41); // POP
568    }
569
570    #[test]
571    fn test_memory_ops() {
572        let mut asm = Assembler::new();
573        let src = "MOVI R0, 0\nMOVI R1, 42\nSTORE R1, R0";
574        let bc = asm.assemble(src).unwrap();
575        assert_eq!(bc[8], Op::Store as u8);
576    }
577
578    #[test]
579    fn test_call_ret() {
580        let mut asm = Assembler::new();
581        let src = "LABEL func:\nRET\nCALL func\nHALT";
582        let bc = asm.assemble(src).unwrap();
583        assert_eq!(bc[0], Op::Ret as u8);
584        assert_eq!(bc[1], Op::Call as u8);
585    }
586}