1use std::{
2 collections::{BTreeMap, BTreeSet},
3 error::Error,
4 fmt,
5 fmt::Write,
6};
7
8use crate::{
9 LangSpec, NcsAuxCode, NcsInstruction, NcsOpcode, NcsReadError, Ndb, NdbFunction, NdbLine,
10 decode_ncs_instructions,
11};
12
13#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsAsmLine {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f, "NcsAsmLine",
"offset", &self.offset, "label", &self.label, "instruction",
&self.instruction, "extra", &&self.extra)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsAsmLine {
#[inline]
fn clone(&self) -> NcsAsmLine {
NcsAsmLine {
offset: ::core::clone::Clone::clone(&self.offset),
label: ::core::clone::Clone::clone(&self.label),
instruction: ::core::clone::Clone::clone(&self.instruction),
extra: ::core::clone::Clone::clone(&self.extra),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsAsmLine {
#[inline]
fn eq(&self, other: &NcsAsmLine) -> bool {
self.offset == other.offset && self.label == other.label &&
self.instruction == other.instruction &&
self.extra == other.extra
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsAsmLine {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<usize>;
let _: ::core::cmp::AssertParamIsEq<Option<String>>;
let _: ::core::cmp::AssertParamIsEq<String>;
}
}Eq)]
15pub struct NcsAsmLine {
16 pub offset: usize,
18 pub label: Option<String>,
20 pub instruction: String,
22 pub extra: String,
24}
25
26#[derive(#[automatically_derived]
impl ::core::fmt::Debug for DecodedAsmLine {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"DecodedAsmLine", "line", &self.line, "opcode", &self.opcode,
"jump_target", &&self.jump_target)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for DecodedAsmLine {
#[inline]
fn clone(&self) -> DecodedAsmLine {
DecodedAsmLine {
line: ::core::clone::Clone::clone(&self.line),
opcode: ::core::clone::Clone::clone(&self.opcode),
jump_target: ::core::clone::Clone::clone(&self.jump_target),
}
}
}Clone)]
27struct DecodedAsmLine {
28 line: NcsAsmLine,
29 opcode: NcsOpcode,
30 jump_target: Option<usize>,
31}
32
33#[derive(#[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::fmt::Debug for NcsDisassemblyOptions {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
let names: &'static _ =
&["internal_names", "max_string_length", "labels", "offsets",
"local_offsets", "source_weave"];
let values: &[&dyn ::core::fmt::Debug] =
&[&self.internal_names, &self.max_string_length, &self.labels,
&self.offsets, &self.local_offsets, &&self.source_weave];
::core::fmt::Formatter::debug_struct_fields_finish(f,
"NcsDisassemblyOptions", names, values)
}
}Debug, #[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::clone::Clone for NcsDisassemblyOptions {
#[inline]
fn clone(&self) -> NcsDisassemblyOptions {
let _: ::core::clone::AssertParamIsClone<bool>;
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::marker::Copy for NcsDisassemblyOptions { }Copy, #[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::cmp::PartialEq for NcsDisassemblyOptions {
#[inline]
fn eq(&self, other: &NcsDisassemblyOptions) -> bool {
self.internal_names == other.internal_names &&
self.labels == other.labels && self.offsets == other.offsets
&& self.local_offsets == other.local_offsets &&
self.source_weave == other.source_weave &&
self.max_string_length == other.max_string_length
}
}PartialEq, #[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::cmp::Eq for NcsDisassemblyOptions {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<bool>;
let _: ::core::cmp::AssertParamIsEq<usize>;
}
}Eq, #[automatically_derived]
#[allow(clippy::struct_excessive_bools)]
impl ::core::hash::Hash for NcsDisassemblyOptions {
#[inline]
fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
::core::hash::Hash::hash(&self.internal_names, state);
::core::hash::Hash::hash(&self.max_string_length, state);
::core::hash::Hash::hash(&self.labels, state);
::core::hash::Hash::hash(&self.offsets, state);
::core::hash::Hash::hash(&self.local_offsets, state);
::core::hash::Hash::hash(&self.source_weave, state)
}
}Hash)]
35#[allow(clippy::struct_excessive_bools)]
36pub struct NcsDisassemblyOptions {
37 pub internal_names: bool,
39 pub max_string_length: usize,
41 pub labels: bool,
43 pub offsets: bool,
45 pub local_offsets: bool,
47 pub source_weave: bool,
50}
51
52impl Default for NcsDisassemblyOptions {
53 fn default() -> Self {
54 Self {
55 internal_names: false,
56 max_string_length: 15,
57 labels: true,
58 offsets: true,
59 local_offsets: true,
60 source_weave: true,
61 }
62 }
63}
64
65#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsAsmError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
NcsAsmError::Read(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Read",
&__self_0),
NcsAsmError::InvalidExtra {
offset: __self_0,
opcode: __self_1,
auxcode: __self_2,
message: __self_3 } =>
::core::fmt::Formatter::debug_struct_field4_finish(f,
"InvalidExtra", "offset", __self_0, "opcode", __self_1,
"auxcode", __self_2, "message", &__self_3),
NcsAsmError::Parse { line: __self_0, message: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f, "Parse",
"line", __self_0, "message", &__self_1),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsAsmError {
#[inline]
fn clone(&self) -> NcsAsmError {
match self {
NcsAsmError::Read(__self_0) =>
NcsAsmError::Read(::core::clone::Clone::clone(__self_0)),
NcsAsmError::InvalidExtra {
offset: __self_0,
opcode: __self_1,
auxcode: __self_2,
message: __self_3 } =>
NcsAsmError::InvalidExtra {
offset: ::core::clone::Clone::clone(__self_0),
opcode: ::core::clone::Clone::clone(__self_1),
auxcode: ::core::clone::Clone::clone(__self_2),
message: ::core::clone::Clone::clone(__self_3),
},
NcsAsmError::Parse { line: __self_0, message: __self_1 } =>
NcsAsmError::Parse {
line: ::core::clone::Clone::clone(__self_0),
message: ::core::clone::Clone::clone(__self_1),
},
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsAsmError {
#[inline]
fn eq(&self, other: &NcsAsmError) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr &&
match (self, other) {
(NcsAsmError::Read(__self_0), NcsAsmError::Read(__arg1_0)) =>
__self_0 == __arg1_0,
(NcsAsmError::InvalidExtra {
offset: __self_0,
opcode: __self_1,
auxcode: __self_2,
message: __self_3 }, NcsAsmError::InvalidExtra {
offset: __arg1_0,
opcode: __arg1_1,
auxcode: __arg1_2,
message: __arg1_3 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1 &&
__self_2 == __arg1_2 && __self_3 == __arg1_3,
(NcsAsmError::Parse { line: __self_0, message: __self_1 },
NcsAsmError::Parse { line: __arg1_0, message: __arg1_1 }) =>
__self_0 == __arg1_0 && __self_1 == __arg1_1,
_ => unsafe { ::core::intrinsics::unreachable() }
}
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsAsmError {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<NcsReadError>;
let _: ::core::cmp::AssertParamIsEq<usize>;
let _: ::core::cmp::AssertParamIsEq<NcsOpcode>;
let _: ::core::cmp::AssertParamIsEq<NcsAuxCode>;
let _: ::core::cmp::AssertParamIsEq<String>;
}
}Eq)]
67pub enum NcsAsmError {
68 Read(NcsReadError),
70 InvalidExtra {
72 offset: usize,
74 opcode: NcsOpcode,
76 auxcode: NcsAuxCode,
78 message: String,
80 },
81 Parse {
83 line: usize,
85 message: String,
87 },
88}
89
90impl fmt::Display for NcsAsmError {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 match self {
93 Self::Read(error) => error.fmt(f),
94 Self::InvalidExtra {
95 offset,
96 opcode,
97 auxcode,
98 message,
99 } => f.write_fmt(format_args!("invalid {0}.{1} payload at byte {2}: {3}",
opcode.internal_name(), auxcode.internal_name(), offset, message))write!(
100 f,
101 "invalid {}.{} payload at byte {}: {}",
102 opcode.internal_name(),
103 auxcode.internal_name(),
104 offset,
105 message
106 ),
107 Self::Parse {
108 line,
109 message,
110 } => {
111 f.write_fmt(format_args!("invalid NCS asm on line {0}: {1}", line, message))write!(f, "invalid NCS asm on line {line}: {message}")
112 }
113 }
114 }
115}
116
117impl Error for NcsAsmError {}
118
119impl From<NcsReadError> for NcsAsmError {
120 fn from(value: NcsReadError) -> Self {
121 Self::Read(value)
122 }
123}
124
125impl NcsOpcode {
126 #[must_use]
128 pub fn internal_name(self) -> &'static str {
129 match self {
130 Self::Assignment => "ASSIGNMENT",
131 Self::RunstackAdd => "RUNSTACK_ADD",
132 Self::RunstackCopy => "RUNSTACK_COPY",
133 Self::Constant => "CONSTANT",
134 Self::ExecuteCommand => "EXECUTE_COMMAND",
135 Self::LogicalAnd => "LOGICAL_AND",
136 Self::LogicalOr => "LOGICAL_OR",
137 Self::InclusiveOr => "INCLUSIVE_OR",
138 Self::ExclusiveOr => "EXCLUSIVE_OR",
139 Self::BooleanAnd => "BOOLEAN_AND",
140 Self::Equal => "EQUAL",
141 Self::NotEqual => "NOT_EQUAL",
142 Self::Geq => "GEQ",
143 Self::Gt => "GT",
144 Self::Lt => "LT",
145 Self::Leq => "LEQ",
146 Self::ShiftLeft => "SHIFT_LEFT",
147 Self::ShiftRight => "SHIFT_RIGHT",
148 Self::UShiftRight => "USHIFT_RIGHT",
149 Self::Add => "ADD",
150 Self::Sub => "SUB",
151 Self::Mul => "MUL",
152 Self::Div => "DIV",
153 Self::Modulus => "MODULUS",
154 Self::Negation => "NEGATION",
155 Self::OnesComplement => "ONES_COMPLEMENT",
156 Self::ModifyStackPointer => "MODIFY_STACK_POINTER",
157 Self::StoreIp => "STORE_IP",
158 Self::Jmp => "JMP",
159 Self::Jsr => "JSR",
160 Self::Jz => "JZ",
161 Self::Ret => "RET",
162 Self::DeStruct => "DE_STRUCT",
163 Self::BooleanNot => "BOOLEAN_NOT",
164 Self::Decrement => "DECREMENT",
165 Self::Increment => "INCREMENT",
166 Self::Jnz => "JNZ",
167 Self::AssignmentBase => "ASSIGNMENT_BASE",
168 Self::RunstackCopyBase => "RUNSTACK_COPY_BASE",
169 Self::DecrementBase => "DECREMENT_BASE",
170 Self::IncrementBase => "INCREMENT_BASE",
171 Self::SaveBasePointer => "SAVE_BASE_POINTER",
172 Self::RestoreBasePointer => "RESTORE_BASE_POINTER",
173 Self::StoreState => "STORE_STATE",
174 Self::NoOperation => "NO_OPERATION",
175 }
176 }
177}
178
179impl NcsAuxCode {
180 #[must_use]
182 pub fn internal_name(self) -> &'static str {
183 match self {
184 Self::None => "NONE",
185 Self::TypeVoid => "TYPE_VOID",
186 Self::TypeCommand => "TYPE_COMMAND",
187 Self::TypeInteger => "TYPE_INTEGER",
188 Self::TypeFloat => "TYPE_FLOAT",
189 Self::TypeString => "TYPE_STRING",
190 Self::TypeObject => "TYPE_OBJECT",
191 Self::TypeEngst0 => "TYPE_ENGST0",
192 Self::TypeEngst1 => "TYPE_ENGST1",
193 Self::TypeEngst2 => "TYPE_ENGST2",
194 Self::TypeEngst3 => "TYPE_ENGST3",
195 Self::TypeEngst4 => "TYPE_ENGST4",
196 Self::TypeEngst5 => "TYPE_ENGST5",
197 Self::TypeEngst6 => "TYPE_ENGST6",
198 Self::TypeEngst7 => "TYPE_ENGST7",
199 Self::TypeEngst8 => "TYPE_ENGST8",
200 Self::TypeEngst9 => "TYPE_ENGST9",
201 Self::TypeTypeIntegerInteger => "TYPETYPE_INTEGER_INTEGER",
202 Self::TypeTypeFloatFloat => "TYPETYPE_FLOAT_FLOAT",
203 Self::TypeTypeObjectObject => "TYPETYPE_OBJECT_OBJECT",
204 Self::TypeTypeStringString => "TYPETYPE_STRING_STRING",
205 Self::TypeTypeStructStruct => "TYPETYPE_STRUCT_STRUCT",
206 Self::TypeTypeIntegerFloat => "TYPETYPE_INTEGER_FLOAT",
207 Self::TypeTypeFloatInteger => "TYPETYPE_FLOAT_INTEGER",
208 Self::TypeTypeEngst0Engst0 => "TYPETYPE_ENGST0_ENGST0",
209 Self::TypeTypeEngst1Engst1 => "TYPETYPE_ENGST1_ENGST1",
210 Self::TypeTypeEngst2Engst2 => "TYPETYPE_ENGST2_ENGST2",
211 Self::TypeTypeEngst3Engst3 => "TYPETYPE_ENGST3_ENGST3",
212 Self::TypeTypeEngst4Engst4 => "TYPETYPE_ENGST4_ENGST4",
213 Self::TypeTypeEngst5Engst5 => "TYPETYPE_ENGST5_ENGST5",
214 Self::TypeTypeEngst6Engst6 => "TYPETYPE_ENGST6_ENGST6",
215 Self::TypeTypeEngst7Engst7 => "TYPETYPE_ENGST7_ENGST7",
216 Self::TypeTypeEngst8Engst8 => "TYPETYPE_ENGST8_ENGST8",
217 Self::TypeTypeEngst9Engst9 => "TYPETYPE_ENGST9_ENGST9",
218 Self::TypeTypeVectorVector => "TYPETYPE_VECTOR_VECTOR",
219 Self::TypeTypeVectorFloat => "TYPETYPE_VECTOR_FLOAT",
220 Self::TypeTypeFloatVector => "TYPETYPE_FLOAT_VECTOR",
221 Self::EvalInplace => "EVAL_INPLACE",
222 Self::EvalPostplace => "EVAL_POSTPLACE",
223 }
224 }
225}
226
227impl NcsInstruction {
228 #[must_use]
230 pub fn canonical_name(&self, internal: bool) -> String {
231 let mut name = if internal {
232 self.opcode.internal_name().to_string()
233 } else {
234 self.opcode.canonical_name().to_string()
235 };
236 let aux = if internal {
237 Some(self.auxcode.internal_name())
238 } else {
239 self.auxcode.canonical_name()
240 };
241 if let Some(aux) = aux {
242 name.push('.');
243 name.push_str(aux);
244 }
245 name
246 }
247
248 pub fn extra_string(&self, max_string_length: usize) -> Result<String, NcsAsmError> {
255 extra_string_for_instruction(self, 0, None, &BTreeMap::new(), max_string_length)
256 }
257}
258
259pub fn disassemble_ncs(
265 bytes: &[u8],
266 langspec: Option<&LangSpec>,
267 options: NcsDisassemblyOptions,
268) -> Result<Vec<NcsAsmLine>, NcsAsmError> {
269 Ok(decode_asm_lines(bytes, langspec, options)?
270 .into_iter()
271 .map(|line| line.line)
272 .collect())
273}
274
275fn decode_asm_lines(
276 bytes: &[u8],
277 langspec: Option<&LangSpec>,
278 options: NcsDisassemblyOptions,
279) -> Result<Vec<DecodedAsmLine>, NcsAsmError> {
280 let instructions = decode_ncs_instructions(bytes)?;
281 let labels = if options.labels {
282 collect_jump_labels(&instructions)
283 } else {
284 BTreeMap::new()
285 };
286 let mut offset = 0usize;
287 let mut lines = Vec::with_capacity(instructions.len());
288
289 for instruction in instructions {
290 let jump_target = jump_target_for_instruction(&instruction, offset);
291 let extra = extra_string_for_instruction(
292 &instruction,
293 offset,
294 langspec,
295 &labels,
296 options.max_string_length,
297 )?;
298 lines.push(DecodedAsmLine {
299 line: NcsAsmLine {
300 offset,
301 label: labels.get(&offset).cloned(),
302 instruction: instruction.canonical_name(options.internal_names),
303 extra,
304 },
305 opcode: instruction.opcode,
306 jump_target,
307 });
308 offset += instruction.encoded_len();
309 }
310
311 Ok(lines)
312}
313
314pub fn render_ncs_disassembly(
320 bytes: &[u8],
321 langspec: Option<&LangSpec>,
322 options: NcsDisassemblyOptions,
323) -> Result<String, NcsAsmError> {
324 let lines = decode_asm_lines(bytes, langspec, options)?;
325 Ok(render_disassembly_lines(
326 &lines.into_iter().map(|line| line.line).collect::<Vec<_>>(),
327 options,
328 ))
329}
330
331pub fn render_ncs_disassembly_with_ndb(
337 bytes: &[u8],
338 langspec: Option<&LangSpec>,
339 ndb: Option<&Ndb>,
340 source_files: Option<&BTreeMap<String, Vec<String>>>,
341 options: NcsDisassemblyOptions,
342) -> Result<String, NcsAsmError> {
343 let lines = decode_asm_lines(bytes, langspec, options)?;
344 Ok(render_disassembly_with_ndb_lines(
345 &lines,
346 ndb,
347 source_files,
348 options,
349 ))
350}
351
352pub fn assemble_ncs_text(
358 text: &str,
359 langspec: Option<&LangSpec>,
360) -> Result<Vec<NcsInstruction>, NcsAsmError> {
361 let mut labels = BTreeMap::<String, usize>::new();
362 let mut pending_labels = Vec::<String>::new();
363 let mut parsed = Vec::<ParsedAsmInstruction>::new();
364
365 for (index, raw_line) in text.lines().enumerate() {
366 let line_number = index + 1;
367 let line = strip_asm_line(raw_line);
368 if line.is_empty() || is_function_header_line(line) {
369 continue;
370 }
371 if let Some(label) = line.strip_suffix(':') {
372 let label = label.trim();
373 if label.is_empty() {
374 return Err(NcsAsmError::Parse {
375 line: line_number,
376 message: "empty label".to_string(),
377 });
378 }
379 if labels.contains_key(label) || pending_labels.iter().any(|pending| pending == label) {
380 return Err(NcsAsmError::Parse {
381 line: line_number,
382 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("duplicate label {0:?}", label))
})format!("duplicate label {label:?}"),
383 });
384 }
385 pending_labels.push(label.to_string());
386 continue;
387 }
388
389 let line = strip_rendered_offsets(line);
390 let (instruction_name, extra) =
391 split_instruction_line(line).ok_or_else(|| NcsAsmError::Parse {
392 line: line_number,
393 message: "missing instruction mnemonic".to_string(),
394 })?;
395 let (opcode, auxcode) = parse_instruction_name(instruction_name, line_number)?;
396 let operand = parse_instruction_operand(opcode, auxcode, extra, langspec, line_number)?;
397 let instruction_index = parsed.len();
398 for label in pending_labels.drain(..) {
399 labels.insert(label, instruction_index);
400 }
401 parsed.push(ParsedAsmInstruction {
402 opcode,
403 auxcode,
404 operand,
405 line: line_number,
406 });
407 }
408
409 if !pending_labels.is_empty() {
410 return Err(NcsAsmError::Parse {
411 line: text.lines().count().max(1),
412 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("label {0:?} does not precede an instruction",
pending_labels.first().cloned().unwrap_or_default()))
})format!(
413 "label {:?} does not precede an instruction",
414 pending_labels.first().cloned().unwrap_or_default()
415 ),
416 });
417 }
418
419 let instruction_offsets = parsed
420 .iter()
421 .scan(0usize, |offset, instruction| {
422 let current = *offset;
423 *offset += instruction.encoded_len();
424 Some(current)
425 })
426 .collect::<Vec<_>>();
427
428 let label_offsets = labels
429 .into_iter()
430 .map(|(label, index)| {
431 let offset =
432 instruction_offsets
433 .get(index)
434 .copied()
435 .ok_or_else(|| NcsAsmError::Parse {
436 line: parsed.get(index).map_or(1, |entry| entry.line),
437 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("label {0:?} resolved past end of instruction stream",
label))
})format!("label {label:?} resolved past end of instruction stream"),
438 })?;
439 Ok((label, offset))
440 })
441 .collect::<Result<BTreeMap<_, _>, NcsAsmError>>()?;
442
443 parsed
444 .into_iter()
445 .zip(instruction_offsets)
446 .map(|(instruction, offset)| instruction.build(offset, &label_offsets))
447 .collect()
448}
449
450pub fn assemble_ncs_bytes(text: &str, langspec: Option<&LangSpec>) -> Result<Vec<u8>, NcsAsmError> {
457 Ok(crate::encode_ncs_instructions(&assemble_ncs_text(
458 text, langspec,
459 )?))
460}
461
462#[must_use]
464pub fn render_disassembly_lines(lines: &[NcsAsmLine], options: NcsDisassemblyOptions) -> String {
465 let mut rendered = Vec::new();
466
467 for line in lines {
468 if options.labels
469 && let Some(label) = &line.label
470 {
471 rendered.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}:", label))
})format!("{label}:"));
472 }
473
474 let mut row = String::new();
475 if options.offsets {
476 let _ = row.write_fmt(format_args!("{0:04}", line.offset))write!(row, "{:04}", line.offset);
477 row.push_str(": ");
478 }
479
480 row.push_str(&line.instruction);
481 if !line.extra.is_empty() {
482 row.push(' ');
483 row.push_str(&line.extra);
484 }
485 rendered.push(row);
486 }
487
488 rendered.join("\n")
489}
490
491fn render_disassembly_with_ndb_lines(
492 lines: &[DecodedAsmLine],
493 ndb: Option<&Ndb>,
494 source_files: Option<&BTreeMap<String, Vec<String>>>,
495 options: NcsDisassemblyOptions,
496) -> String {
497 let Some(ndb) = ndb else {
498 return render_disassembly_lines(
499 &lines
500 .iter()
501 .map(|line| line.line.clone())
502 .collect::<Vec<_>>(),
503 options,
504 );
505 };
506
507 let functions = sorted_functions(ndb);
508 let mut rendered = Vec::new();
509 let mut current_function: Option<usize> = None;
510 let mut last_source: Option<(usize, usize)> = None;
511
512 for decoded in lines {
513 let line = &decoded.line;
514 let function_index = functions
515 .iter()
516 .position(|function| line_in_function(line.offset, function));
517
518 if function_index != current_function {
519 current_function = function_index;
520 last_source = None;
521
522 if let Some(index) = current_function
523 && let Some(function) = functions.get(index)
524 {
525 if !rendered.is_empty() {
526 rendered.push(String::new());
527 }
528 rendered.push(render_function_header(function, ndb));
529 }
530 }
531
532 if options.labels
533 && let Some(label) = &line.label
534 {
535 rendered.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}:", label))
})format!("{label}:"));
536 }
537
538 let mut row = String::new();
539 if options.offsets {
540 let _ = row.write_fmt(format_args!("{0:04}", line.offset))write!(row, "{:04}", line.offset);
541 if options.local_offsets
542 && let Some(index) = current_function
543 && let Some(function) = functions.get(index)
544 {
545 let local = local_offset(line.offset, function);
546 row.push(' ');
547 let _ = row.write_fmt(format_args!("{0:04}", local))write!(row, "{local:04}");
548 }
549 row.push_str(": ");
550 }
551
552 row.push_str(&line.instruction);
553 let rendered_extra = render_ndb_aware_extra(decoded, &functions).unwrap_or(&line.extra);
554 if !rendered_extra.is_empty() {
555 row.push(' ');
556 row.push_str(rendered_extra);
557 }
558
559 if options.source_weave
560 && let Some((source_key, source_text)) =
561 source_line_for_offset(line.offset, ndb, source_files, &mut last_source)
562 && !source_text.is_empty()
563 {
564 row.push_str(" | ");
565 row.push_str(&source_key);
566 row.push_str(" | ");
567 row.push_str(&source_text);
568 }
569
570 rendered.push(row);
571 }
572
573 rendered.join("\n")
574}
575
576#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParsedAsmOperand {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
ParsedAsmOperand::None =>
::core::fmt::Formatter::write_str(f, "None"),
ParsedAsmOperand::Bytes(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Bytes",
&__self_0),
ParsedAsmOperand::Jump(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Jump",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ParsedAsmOperand {
#[inline]
fn clone(&self) -> ParsedAsmOperand {
match self {
ParsedAsmOperand::None => ParsedAsmOperand::None,
ParsedAsmOperand::Bytes(__self_0) =>
ParsedAsmOperand::Bytes(::core::clone::Clone::clone(__self_0)),
ParsedAsmOperand::Jump(__self_0) =>
ParsedAsmOperand::Jump(::core::clone::Clone::clone(__self_0)),
}
}
}Clone)]
577enum ParsedAsmOperand {
578 None,
579 Bytes(Vec<u8>),
580 Jump(AsmJumpTarget),
581}
582
583#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AsmJumpTarget {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
AsmJumpTarget::Offset(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Offset",
&__self_0),
AsmJumpTarget::Label(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Label",
&__self_0),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for AsmJumpTarget {
#[inline]
fn clone(&self) -> AsmJumpTarget {
match self {
AsmJumpTarget::Offset(__self_0) =>
AsmJumpTarget::Offset(::core::clone::Clone::clone(__self_0)),
AsmJumpTarget::Label(__self_0) =>
AsmJumpTarget::Label(::core::clone::Clone::clone(__self_0)),
}
}
}Clone)]
584enum AsmJumpTarget {
585 Offset(usize),
586 Label(String),
587}
588
589#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParsedAsmInstruction {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f,
"ParsedAsmInstruction", "opcode", &self.opcode, "auxcode",
&self.auxcode, "operand", &self.operand, "line", &&self.line)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for ParsedAsmInstruction {
#[inline]
fn clone(&self) -> ParsedAsmInstruction {
ParsedAsmInstruction {
opcode: ::core::clone::Clone::clone(&self.opcode),
auxcode: ::core::clone::Clone::clone(&self.auxcode),
operand: ::core::clone::Clone::clone(&self.operand),
line: ::core::clone::Clone::clone(&self.line),
}
}
}Clone)]
590struct ParsedAsmInstruction {
591 opcode: NcsOpcode,
592 auxcode: NcsAuxCode,
593 operand: ParsedAsmOperand,
594 line: usize,
595}
596
597impl ParsedAsmInstruction {
598 fn encoded_len(&self) -> usize {
599 2 + match &self.operand {
600 ParsedAsmOperand::None => 0,
601 ParsedAsmOperand::Bytes(bytes) => bytes.len(),
602 ParsedAsmOperand::Jump(_target) => 4,
603 }
604 }
605
606 fn build(
607 self,
608 offset: usize,
609 labels: &BTreeMap<String, usize>,
610 ) -> Result<NcsInstruction, NcsAsmError> {
611 let extra = match self.operand {
612 ParsedAsmOperand::None => Vec::new(),
613 ParsedAsmOperand::Bytes(bytes) => bytes,
614 ParsedAsmOperand::Jump(target) => {
615 let absolute_target = match target {
616 AsmJumpTarget::Offset(offset) => offset,
617 AsmJumpTarget::Label(label) => {
618 labels
619 .get(&label)
620 .copied()
621 .ok_or_else(|| NcsAsmError::Parse {
622 line: self.line,
623 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown jump label {0:?}", label))
})format!("unknown jump label {label:?}"),
624 })?
625 }
626 };
627 let relative = i64::try_from(absolute_target)
628 .ok()
629 .and_then(|target| i64::try_from(offset).ok().map(|origin| target - origin))
630 .and_then(|relative| i32::try_from(relative).ok())
631 .ok_or_else(|| NcsAsmError::Parse {
632 line: self.line,
633 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("jump target {0} is out of range for byte offset {1}",
absolute_target, offset))
})format!(
634 "jump target {absolute_target} is out of range for byte offset \
635 {offset}"
636 ),
637 })?;
638 relative.to_be_bytes().to_vec()
639 }
640 };
641
642 Ok(NcsInstruction {
643 opcode: self.opcode,
644 auxcode: self.auxcode,
645 extra,
646 })
647 }
648}
649
650fn render_ndb_aware_extra<'a>(
651 decoded: &'a DecodedAsmLine,
652 functions: &[&'a NdbFunction],
653) -> Option<&'a str> {
654 if decoded.opcode != NcsOpcode::Jsr {
655 return None;
656 }
657 let target = decoded.jump_target?;
658 functions
659 .iter()
660 .find(|function| line_in_function(target, function))
661 .map(|function| function.label.as_str())
662}
663
664fn extra_string_for_instruction(
665 instruction: &NcsInstruction,
666 offset: usize,
667 langspec: Option<&LangSpec>,
668 labels: &BTreeMap<usize, String>,
669 max_string_length: usize,
670) -> Result<String, NcsAsmError> {
671 let extra = instruction.extra.as_slice();
672 match instruction.opcode {
673 NcsOpcode::Constant => match instruction.auxcode {
674 NcsAuxCode::TypeString => {
675 let value = decode_prefixed_string(extra, offset, instruction)?;
676 Ok(truncate_nwasm_string(&value, max_string_length))
677 }
678 NcsAuxCode::TypeInteger => Ok(read_i32(extra, offset, instruction)?.to_string()),
679 NcsAuxCode::TypeObject => {
680 Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("0x{0:08X}",
read_i32(extra, offset, instruction)?))
})format!("0x{:08X}", read_i32(extra, offset, instruction)?))
681 }
682 NcsAuxCode::TypeFloat => Ok(read_f32(extra, offset, instruction)?.to_string()),
683 NcsAuxCode::TypeEngst2 => Ok(read_u32(extra, offset, instruction)?.to_string()),
684 NcsAuxCode::TypeEngst7 => {
685 let value = decode_prefixed_string(extra, offset, instruction)?;
686 Ok(value.escape_default().to_string())
687 }
688 _ => Ok(String::new()),
689 },
690 NcsOpcode::Jz | NcsOpcode::Jmp | NcsOpcode::Jsr | NcsOpcode::Jnz => {
691 let target = jump_target(offset, read_i32(extra, offset, instruction)?);
692 Ok(format_jump_target(instruction.opcode, target, labels))
693 }
694 NcsOpcode::StoreState => Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}",
read_i32_part(extra, 0, offset, instruction)?,
read_i32_part(extra, 4, offset, instruction)?))
})format!(
695 "{}, {}",
696 read_i32_part(extra, 0, offset, instruction)?,
697 read_i32_part(extra, 4, offset, instruction)?,
698 )),
699 NcsOpcode::ModifyStackPointer
700 | NcsOpcode::Increment
701 | NcsOpcode::Decrement
702 | NcsOpcode::IncrementBase
703 | NcsOpcode::DecrementBase => Ok(read_i32(extra, offset, instruction)?.to_string()),
704 NcsOpcode::ExecuteCommand => {
705 let builtin_id = read_u16_part(extra, 0, offset, instruction)?;
706 let argc = read_u8_part(extra, 2, offset, instruction)?;
707 let _ = langspec;
708 Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}", builtin_id, argc))
})format!("{builtin_id}, {argc}"))
709 }
710 NcsOpcode::RunstackCopy | NcsOpcode::RunstackCopyBase => Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}",
read_i32_part(extra, 0, offset, instruction)?,
read_i16_part(extra, 4, offset, instruction)?))
})format!(
711 "{}, {}",
712 read_i32_part(extra, 0, offset, instruction)?,
713 read_i16_part(extra, 4, offset, instruction)?,
714 )),
715 NcsOpcode::Assignment | NcsOpcode::AssignmentBase => Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}",
read_i32_part(extra, 0, offset, instruction)?,
read_u16_part(extra, 4, offset, instruction)?))
})format!(
716 "{}, {}",
717 read_i32_part(extra, 0, offset, instruction)?,
718 read_u16_part(extra, 4, offset, instruction)?,
719 )),
720 NcsOpcode::DeStruct => Ok(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}, {1}, {2}",
read_u16_part(extra, 0, offset, instruction)?,
read_u16_part(extra, 2, offset, instruction)?,
read_u16_part(extra, 4, offset, instruction)?))
})format!(
721 "{}, {}, {}",
722 read_u16_part(extra, 0, offset, instruction)?,
723 read_u16_part(extra, 2, offset, instruction)?,
724 read_u16_part(extra, 4, offset, instruction)?,
725 )),
726 NcsOpcode::Equal | NcsOpcode::NotEqual
727 if instruction.auxcode == NcsAuxCode::TypeTypeStructStruct =>
728 {
729 Ok(read_u16(extra, offset, instruction)?.to_string())
730 }
731 _ if extra.is_empty() => Ok(String::new()),
732 _ => Err(NcsAsmError::InvalidExtra {
733 offset,
734 opcode: instruction.opcode,
735 auxcode: instruction.auxcode,
736 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported extra payload {0:?}",
extra))
})format!("unsupported extra payload {extra:?}"),
737 }),
738 }
739}
740
741fn strip_asm_line(line: &str) -> &str {
742 line.split_once(" | ")
743 .map_or(line, |(head, _tail)| head)
744 .trim()
745}
746
747fn is_function_header_line(line: &str) -> bool {
748 line.ends_with(']') && line.contains('(') && line.contains("):")
749}
750
751fn strip_rendered_offsets(line: &str) -> &str {
752 let Some((prefix, rest)) = line.split_once(": ") else {
753 return line;
754 };
755 if prefix
756 .chars()
757 .all(|ch| ch.is_ascii_digit() || ch.is_ascii_whitespace())
758 && prefix.chars().any(|ch| ch.is_ascii_digit())
759 {
760 rest.trim_start()
761 } else {
762 line
763 }
764}
765
766fn split_instruction_line(line: &str) -> Option<(&str, &str)> {
767 let trimmed = line.trim();
768 if trimmed.is_empty() {
769 return None;
770 }
771 if let Some((instruction, rest)) = trimmed.split_once(char::is_whitespace) {
772 Some((instruction, rest.trim()))
773 } else {
774 Some((trimmed, ""))
775 }
776}
777
778fn parse_instruction_name(name: &str, line: usize) -> Result<(NcsOpcode, NcsAuxCode), NcsAsmError> {
779 let (opcode_name, aux_name) = name
780 .split_once('.')
781 .map_or((name, None), |(opcode, aux)| (opcode, Some(aux)));
782 let opcode = parse_opcode_name(opcode_name).ok_or_else(|| NcsAsmError::Parse {
783 line,
784 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown instruction mnemonic {0:?}",
opcode_name))
})format!("unknown instruction mnemonic {opcode_name:?}"),
785 })?;
786 let auxcode = if let Some(aux_name) = aux_name {
787 parse_aux_name(aux_name).ok_or_else(|| NcsAsmError::Parse {
788 line,
789 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown instruction auxcode {0:?}",
aux_name))
})format!("unknown instruction auxcode {aux_name:?}"),
790 })?
791 } else {
792 default_aux_for_opcode(opcode)
793 };
794 Ok((opcode, auxcode))
795}
796
797fn default_aux_for_opcode(opcode: NcsOpcode) -> NcsAuxCode {
798 match opcode {
799 NcsOpcode::Assignment
800 | NcsOpcode::AssignmentBase
801 | NcsOpcode::RunstackCopy
802 | NcsOpcode::RunstackCopyBase => NcsAuxCode::TypeVoid,
803 _ => NcsAuxCode::None,
804 }
805}
806
807fn parse_opcode_name(name: &str) -> Option<NcsOpcode> {
808 match name {
809 "CPDOWNSP" | "ASSIGNMENT" => Some(NcsOpcode::Assignment),
810 "RSADD" | "RUNSTACK_ADD" => Some(NcsOpcode::RunstackAdd),
811 "CPTOPSP" | "RUNSTACK_COPY" => Some(NcsOpcode::RunstackCopy),
812 "CONST" | "CONSTANT" => Some(NcsOpcode::Constant),
813 "ACTION" | "EXECUTE_COMMAND" => Some(NcsOpcode::ExecuteCommand),
814 "LOGAND" | "LOGICAL_AND" => Some(NcsOpcode::LogicalAnd),
815 "LOGOR" | "LOGICAL_OR" => Some(NcsOpcode::LogicalOr),
816 "INCOR" | "INCLUSIVE_OR" => Some(NcsOpcode::InclusiveOr),
817 "EXCOR" | "EXCLUSIVE_OR" => Some(NcsOpcode::ExclusiveOr),
818 "BOOLAND" | "BOOLEAN_AND" => Some(NcsOpcode::BooleanAnd),
819 "EQUAL" => Some(NcsOpcode::Equal),
820 "NEQUAL" | "NOT_EQUAL" => Some(NcsOpcode::NotEqual),
821 "GEQ" => Some(NcsOpcode::Geq),
822 "GT" => Some(NcsOpcode::Gt),
823 "LT" => Some(NcsOpcode::Lt),
824 "LEQ" => Some(NcsOpcode::Leq),
825 "SHLEFT" | "SHIFT_LEFT" => Some(NcsOpcode::ShiftLeft),
826 "SHRIGHT" | "SHIFT_RIGHT" => Some(NcsOpcode::ShiftRight),
827 "USHRIGHT" | "USHIFT_RIGHT" => Some(NcsOpcode::UShiftRight),
828 "ADD" => Some(NcsOpcode::Add),
829 "SUB" => Some(NcsOpcode::Sub),
830 "MUL" => Some(NcsOpcode::Mul),
831 "DIV" => Some(NcsOpcode::Div),
832 "MOD" | "MODULUS" => Some(NcsOpcode::Modulus),
833 "NEG" | "NEGATION" => Some(NcsOpcode::Negation),
834 "COMP" | "ONES_COMPLEMENT" => Some(NcsOpcode::OnesComplement),
835 "MOVSP" | "MODIFY_STACK_POINTER" => Some(NcsOpcode::ModifyStackPointer),
836 "STOREIP" | "STORE_IP" => Some(NcsOpcode::StoreIp),
837 "JMP" => Some(NcsOpcode::Jmp),
838 "JSR" => Some(NcsOpcode::Jsr),
839 "JZ" => Some(NcsOpcode::Jz),
840 "RET" => Some(NcsOpcode::Ret),
841 "DESTRUCT" | "DE_STRUCT" => Some(NcsOpcode::DeStruct),
842 "NOT" | "BOOLEAN_NOT" => Some(NcsOpcode::BooleanNot),
843 "DECSP" | "DECREMENT" => Some(NcsOpcode::Decrement),
844 "INCSP" | "INCREMENT" => Some(NcsOpcode::Increment),
845 "JNZ" => Some(NcsOpcode::Jnz),
846 "CPDOWNBP" | "ASSIGNMENT_BASE" => Some(NcsOpcode::AssignmentBase),
847 "CPTOPBP" | "RUNSTACK_COPY_BASE" => Some(NcsOpcode::RunstackCopyBase),
848 "DECBP" | "DECREMENT_BASE" => Some(NcsOpcode::DecrementBase),
849 "INCBP" | "INCREMENT_BASE" => Some(NcsOpcode::IncrementBase),
850 "SAVEBP" | "SAVE_BASE_POINTER" => Some(NcsOpcode::SaveBasePointer),
851 "RESTOREBP" | "RESTORE_BASE_POINTER" => Some(NcsOpcode::RestoreBasePointer),
852 "STORESTATE" | "STORE_STATE" => Some(NcsOpcode::StoreState),
853 "NOP" | "NO_OPERATION" => Some(NcsOpcode::NoOperation),
854 _ => None,
855 }
856}
857
858fn parse_aux_name(name: &str) -> Option<NcsAuxCode> {
859 match name {
860 "NONE" => Some(NcsAuxCode::None),
861 "TYPE_VOID" => Some(NcsAuxCode::TypeVoid),
862 "TYPE_COMMAND" => Some(NcsAuxCode::TypeCommand),
863 "I" | "TYPE_INTEGER" => Some(NcsAuxCode::TypeInteger),
864 "F" | "TYPE_FLOAT" => Some(NcsAuxCode::TypeFloat),
865 "S" | "TYPE_STRING" => Some(NcsAuxCode::TypeString),
866 "O" | "TYPE_OBJECT" => Some(NcsAuxCode::TypeObject),
867 "E0" | "TYPE_ENGST0" => Some(NcsAuxCode::TypeEngst0),
868 "E1" | "TYPE_ENGST1" => Some(NcsAuxCode::TypeEngst1),
869 "E2" | "TYPE_ENGST2" => Some(NcsAuxCode::TypeEngst2),
870 "E3" | "TYPE_ENGST3" => Some(NcsAuxCode::TypeEngst3),
871 "E4" | "TYPE_ENGST4" => Some(NcsAuxCode::TypeEngst4),
872 "E5" | "TYPE_ENGST5" => Some(NcsAuxCode::TypeEngst5),
873 "E6" | "TYPE_ENGST6" => Some(NcsAuxCode::TypeEngst6),
874 "E7" | "TYPE_ENGST7" => Some(NcsAuxCode::TypeEngst7),
875 "E8" | "TYPE_ENGST8" => Some(NcsAuxCode::TypeEngst8),
876 "E9" | "TYPE_ENGST9" => Some(NcsAuxCode::TypeEngst9),
877 "II" | "TYPETYPE_INTEGER_INTEGER" => Some(NcsAuxCode::TypeTypeIntegerInteger),
878 "FF" | "TYPETYPE_FLOAT_FLOAT" => Some(NcsAuxCode::TypeTypeFloatFloat),
879 "OO" | "TYPETYPE_OBJECT_OBJECT" => Some(NcsAuxCode::TypeTypeObjectObject),
880 "SS" | "TYPETYPE_STRING_STRING" => Some(NcsAuxCode::TypeTypeStringString),
881 "TT" | "TYPETYPE_STRUCT_STRUCT" => Some(NcsAuxCode::TypeTypeStructStruct),
882 "IF" | "TYPETYPE_INTEGER_FLOAT" => Some(NcsAuxCode::TypeTypeIntegerFloat),
883 "FI" | "TYPETYPE_FLOAT_INTEGER" => Some(NcsAuxCode::TypeTypeFloatInteger),
884 "E0E0" | "TYPETYPE_ENGST0_ENGST0" => Some(NcsAuxCode::TypeTypeEngst0Engst0),
885 "E1E1" | "TYPETYPE_ENGST1_ENGST1" => Some(NcsAuxCode::TypeTypeEngst1Engst1),
886 "E2E2" | "TYPETYPE_ENGST2_ENGST2" => Some(NcsAuxCode::TypeTypeEngst2Engst2),
887 "E3E3" | "TYPETYPE_ENGST3_ENGST3" => Some(NcsAuxCode::TypeTypeEngst3Engst3),
888 "E4E4" | "TYPETYPE_ENGST4_ENGST4" => Some(NcsAuxCode::TypeTypeEngst4Engst4),
889 "E5E5" | "TYPETYPE_ENGST5_ENGST5" => Some(NcsAuxCode::TypeTypeEngst5Engst5),
890 "E6E6" | "TYPETYPE_ENGST6_ENGST6" => Some(NcsAuxCode::TypeTypeEngst6Engst6),
891 "E7E7" | "TYPETYPE_ENGST7_ENGST7" => Some(NcsAuxCode::TypeTypeEngst7Engst7),
892 "E8E8" | "TYPETYPE_ENGST8_ENGST8" => Some(NcsAuxCode::TypeTypeEngst8Engst8),
893 "E9E9" | "TYPETYPE_ENGST9_ENGST9" => Some(NcsAuxCode::TypeTypeEngst9Engst9),
894 "VV" | "TYPETYPE_VECTOR_VECTOR" => Some(NcsAuxCode::TypeTypeVectorVector),
895 "VF" | "TYPETYPE_VECTOR_FLOAT" => Some(NcsAuxCode::TypeTypeVectorFloat),
896 "FV" | "TYPETYPE_FLOAT_VECTOR" => Some(NcsAuxCode::TypeTypeFloatVector),
897 "EVAL_INPLACE" => Some(NcsAuxCode::EvalInplace),
898 "EVAL_POSTPLACE" => Some(NcsAuxCode::EvalPostplace),
899 _ => None,
900 }
901}
902
903fn parse_instruction_operand(
904 opcode: NcsOpcode,
905 auxcode: NcsAuxCode,
906 extra: &str,
907 langspec: Option<&LangSpec>,
908 line: usize,
909) -> Result<ParsedAsmOperand, NcsAsmError> {
910 let extra = extra.trim();
911 match opcode {
912 NcsOpcode::Constant => parse_constant_operand(auxcode, extra, line),
913 NcsOpcode::Jmp | NcsOpcode::Jsr | NcsOpcode::Jz | NcsOpcode::Jnz => {
914 parse_jump_operand(extra, line)
915 }
916 NcsOpcode::StoreState => parse_store_state_operand(extra, line),
917 NcsOpcode::ModifyStackPointer
918 | NcsOpcode::Increment
919 | NcsOpcode::Decrement
920 | NcsOpcode::IncrementBase
921 | NcsOpcode::DecrementBase => parse_single_number_bytes::<i32>(extra, line),
922 NcsOpcode::ExecuteCommand => parse_action_operand(extra, langspec, line),
923 NcsOpcode::RunstackCopy | NcsOpcode::RunstackCopyBase => {
924 parse_runstack_copy_operand(extra, line)
925 }
926 NcsOpcode::Assignment | NcsOpcode::AssignmentBase => parse_assignment_operand(extra, line),
927 NcsOpcode::DeStruct => parse_destruct_operand(extra, line),
928 NcsOpcode::Equal | NcsOpcode::NotEqual if auxcode == NcsAuxCode::TypeTypeStructStruct => {
929 parse_single_number_bytes::<u16>(extra, line)
930 }
931 _ if extra.is_empty() => Ok(ParsedAsmOperand::None),
932 _ => Err(NcsAsmError::Parse {
933 line,
934 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("instruction {0} does not accept operands {1:?}",
opcode, extra))
})format!("instruction {opcode} does not accept operands {extra:?}"),
935 }),
936 }
937}
938
939fn parse_store_state_operand(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError> {
940 let parts = split_csv(extra);
941 let [first, second] = parts.as_slice() else {
942 return Err(NcsAsmError::Parse {
943 line,
944 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `a, b`, found {0:?}",
extra))
})format!("expected `a, b`, found {extra:?}"),
945 });
946 };
947 let first = parse_i32_like(first, line)?;
948 let second = parse_i32_like(second, line)?;
949 Ok(ParsedAsmOperand::Bytes(
950 [
951 first.to_be_bytes().as_slice(),
952 second.to_be_bytes().as_slice(),
953 ]
954 .concat(),
955 ))
956}
957
958fn parse_constant_operand(
959 auxcode: NcsAuxCode,
960 extra: &str,
961 line: usize,
962) -> Result<ParsedAsmOperand, NcsAsmError> {
963 match auxcode {
964 NcsAuxCode::TypeString | NcsAuxCode::TypeEngst7 => {
965 let value = unescape_nwasm_string(extra, line)?;
966 let length = u16::try_from(value.len()).map_err(|_error| NcsAsmError::Parse {
967 line,
968 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("string operand too long: {0} bytes",
value.len()))
})format!("string operand too long: {} bytes", value.len()),
969 })?;
970 Ok(ParsedAsmOperand::Bytes(
971 [length.to_be_bytes().as_slice(), value.as_bytes()].concat(),
972 ))
973 }
974 NcsAuxCode::TypeInteger => parse_single_number_bytes::<i32>(extra, line),
975 NcsAuxCode::TypeFloat => {
976 let value = extra.parse::<f32>().map_err(|error| NcsAsmError::Parse {
977 line,
978 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid float operand {0:?}: {1}",
extra, error))
})format!("invalid float operand {extra:?}: {error}"),
979 })?;
980 Ok(ParsedAsmOperand::Bytes(
981 value.to_bits().to_be_bytes().to_vec(),
982 ))
983 }
984 NcsAuxCode::TypeObject => {
985 let value = parse_i32_like(extra, line)?;
986 Ok(ParsedAsmOperand::Bytes(value.to_be_bytes().to_vec()))
987 }
988 NcsAuxCode::TypeEngst2 => {
989 let value = parse_u32_like(extra, line)?;
990 Ok(ParsedAsmOperand::Bytes(value.to_be_bytes().to_vec()))
991 }
992 _ if extra.is_empty() => Ok(ParsedAsmOperand::None),
993 _ => Err(NcsAsmError::Parse {
994 line,
995 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported CONST operand for auxcode {0:?}",
auxcode))
})format!("unsupported CONST operand for auxcode {auxcode:?}"),
996 }),
997 }
998}
999
1000fn parse_jump_operand(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError> {
1001 if extra.is_empty() {
1002 return Err(NcsAsmError::Parse {
1003 line,
1004 message: "missing jump target".to_string(),
1005 });
1006 }
1007 if let Some(offset) = parse_usize_like(extra) {
1008 return Ok(ParsedAsmOperand::Jump(AsmJumpTarget::Offset(offset)));
1009 }
1010 Ok(ParsedAsmOperand::Jump(AsmJumpTarget::Label(
1011 extra.to_string(),
1012 )))
1013}
1014
1015fn parse_action_operand(
1016 extra: &str,
1017 langspec: Option<&LangSpec>,
1018 line: usize,
1019) -> Result<ParsedAsmOperand, NcsAsmError> {
1020 let parts = split_csv(extra);
1021 match parts.as_slice() {
1022 [id, argc] => {
1023 let id = parse_u16_like(id, line)?;
1024 let argc = parse_u8_like(argc, line)?;
1025 Ok(ParsedAsmOperand::Bytes(
1026 [id.to_be_bytes().as_slice(), &[argc]].concat(),
1027 ))
1028 }
1029 [name] if !name.is_empty() => {
1030 let Some(spec) = langspec else {
1031 return Err(NcsAsmError::Parse {
1032 line,
1033 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("ACTION operand {0:?} requires langspec or explicit `id, argc`",
name))
})format!(
1034 "ACTION operand {name:?} requires langspec or explicit `id, argc`"
1035 ),
1036 });
1037 };
1038 let (builtin_id, function) = spec
1039 .functions
1040 .iter()
1041 .enumerate()
1042 .find(|(_index, function)| function.name == *name)
1043 .ok_or_else(|| NcsAsmError::Parse {
1044 line,
1045 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown ACTION builtin {0:?}",
name))
})format!("unknown ACTION builtin {name:?}"),
1046 })?;
1047 let argc =
1048 u8::try_from(function.parameters.len()).map_err(|_error| NcsAsmError::Parse {
1049 line,
1050 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("ACTION builtin {0:?} has too many parameters to infer argc",
name))
})format!(
1051 "ACTION builtin {name:?} has too many parameters to infer argc"
1052 ),
1053 })?;
1054 let builtin_id = u16::try_from(builtin_id).map_err(|_error| NcsAsmError::Parse {
1055 line,
1056 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("ACTION builtin index out of range for {0:?}",
name))
})format!("ACTION builtin index out of range for {name:?}"),
1057 })?;
1058 Ok(ParsedAsmOperand::Bytes(
1059 [builtin_id.to_be_bytes().as_slice(), &[argc]].concat(),
1060 ))
1061 }
1062 _ => Err(NcsAsmError::Parse {
1063 line,
1064 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid ACTION operand {0:?}",
extra))
})format!("invalid ACTION operand {extra:?}"),
1065 }),
1066 }
1067}
1068
1069fn parse_runstack_copy_operand(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError> {
1070 let parts = split_csv(extra);
1071 let [offset, size] = parts.as_slice() else {
1072 return Err(NcsAsmError::Parse {
1073 line,
1074 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `offset, size`, found {0:?}",
extra))
})format!("expected `offset, size`, found {extra:?}"),
1075 });
1076 };
1077 let offset = parse_i32_like(offset, line)?;
1078 let size = parse_i16_like(size, line)?;
1079 Ok(ParsedAsmOperand::Bytes(
1080 [
1081 offset.to_be_bytes().as_slice(),
1082 size.to_be_bytes().as_slice(),
1083 ]
1084 .concat(),
1085 ))
1086}
1087
1088fn parse_assignment_operand(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError> {
1089 let parts = split_csv(extra);
1090 let [offset, size] = parts.as_slice() else {
1091 return Err(NcsAsmError::Parse {
1092 line,
1093 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `offset, size`, found {0:?}",
extra))
})format!("expected `offset, size`, found {extra:?}"),
1094 });
1095 };
1096 let offset = parse_i32_like(offset, line)?;
1097 let size = parse_u16_like(size, line)?;
1098 Ok(ParsedAsmOperand::Bytes(
1099 [
1100 offset.to_be_bytes().as_slice(),
1101 size.to_be_bytes().as_slice(),
1102 ]
1103 .concat(),
1104 ))
1105}
1106
1107fn parse_destruct_operand(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError> {
1108 let parts = split_csv(extra);
1109 let [first, second, third] = parts.as_slice() else {
1110 return Err(NcsAsmError::Parse {
1111 line,
1112 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected `a, b, c`, found {0:?}",
extra))
})format!("expected `a, b, c`, found {extra:?}"),
1113 });
1114 };
1115 let first = parse_u16_like(first, line)?;
1116 let second = parse_u16_like(second, line)?;
1117 let third = parse_u16_like(third, line)?;
1118 Ok(ParsedAsmOperand::Bytes(
1119 [
1120 first.to_be_bytes().as_slice(),
1121 second.to_be_bytes().as_slice(),
1122 third.to_be_bytes().as_slice(),
1123 ]
1124 .concat(),
1125 ))
1126}
1127
1128fn parse_single_number_bytes<T>(extra: &str, line: usize) -> Result<ParsedAsmOperand, NcsAsmError>
1129where
1130 T: ParseAsmNumber,
1131{
1132 let value = T::parse(extra, line)?;
1133 Ok(ParsedAsmOperand::Bytes(value.to_be_bytes_vec()))
1134}
1135
1136fn split_csv(input: &str) -> Vec<&str> {
1137 input.split(',').map(str::trim).collect()
1138}
1139
1140trait ParseAsmNumber {
1141 fn parse(input: &str, line: usize) -> Result<Self, NcsAsmError>
1142 where
1143 Self: Sized;
1144 fn to_be_bytes_vec(self) -> Vec<u8>;
1145}
1146
1147impl ParseAsmNumber for i32 {
1148 fn parse(input: &str, line: usize) -> Result<Self, NcsAsmError> {
1149 parse_i32_like(input, line)
1150 }
1151
1152 fn to_be_bytes_vec(self) -> Vec<u8> {
1153 self.to_be_bytes().to_vec()
1154 }
1155}
1156
1157impl ParseAsmNumber for u16 {
1158 fn parse(input: &str, line: usize) -> Result<Self, NcsAsmError> {
1159 parse_u16_like(input, line)
1160 }
1161
1162 fn to_be_bytes_vec(self) -> Vec<u8> {
1163 self.to_be_bytes().to_vec()
1164 }
1165}
1166
1167fn parse_i32_like(input: &str, line: usize) -> Result<i32, NcsAsmError> {
1168 let input = input.trim();
1169 if let Some(hex) = input
1170 .strip_prefix("0x")
1171 .or_else(|| input.strip_prefix("0X"))
1172 {
1173 u32::from_str_radix(hex, 16)
1174 .map(|value| i32::from_be_bytes(value.to_be_bytes()))
1175 .map_err(|error| NcsAsmError::Parse {
1176 line,
1177 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid hex i32 operand {0:?}: {1}",
input, error))
})format!("invalid hex i32 operand {input:?}: {error}"),
1178 })
1179 } else {
1180 input.parse::<i32>().map_err(|error| NcsAsmError::Parse {
1181 line,
1182 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid i32 operand {0:?}: {1}",
input, error))
})format!("invalid i32 operand {input:?}: {error}"),
1183 })
1184 }
1185}
1186
1187fn parse_i16_like(input: &str, line: usize) -> Result<i16, NcsAsmError> {
1188 input
1189 .trim()
1190 .parse::<i16>()
1191 .map_err(|error| NcsAsmError::Parse {
1192 line,
1193 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid i16 operand {0:?}: {1}",
input, error))
})format!("invalid i16 operand {input:?}: {error}"),
1194 })
1195}
1196
1197fn parse_u16_like(input: &str, line: usize) -> Result<u16, NcsAsmError> {
1198 let input = input.trim();
1199 if let Some(hex) = input
1200 .strip_prefix("0x")
1201 .or_else(|| input.strip_prefix("0X"))
1202 {
1203 u16::from_str_radix(hex, 16).map_err(|error| NcsAsmError::Parse {
1204 line,
1205 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid hex u16 operand {0:?}: {1}",
input, error))
})format!("invalid hex u16 operand {input:?}: {error}"),
1206 })
1207 } else {
1208 input.parse::<u16>().map_err(|error| NcsAsmError::Parse {
1209 line,
1210 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid u16 operand {0:?}: {1}",
input, error))
})format!("invalid u16 operand {input:?}: {error}"),
1211 })
1212 }
1213}
1214
1215fn parse_u8_like(input: &str, line: usize) -> Result<u8, NcsAsmError> {
1216 input
1217 .trim()
1218 .parse::<u8>()
1219 .map_err(|error| NcsAsmError::Parse {
1220 line,
1221 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid u8 operand {0:?}: {1}",
input, error))
})format!("invalid u8 operand {input:?}: {error}"),
1222 })
1223}
1224
1225fn parse_u32_like(input: &str, line: usize) -> Result<u32, NcsAsmError> {
1226 let input = input.trim();
1227 if let Some(hex) = input
1228 .strip_prefix("0x")
1229 .or_else(|| input.strip_prefix("0X"))
1230 {
1231 u32::from_str_radix(hex, 16).map_err(|error| NcsAsmError::Parse {
1232 line,
1233 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid hex u32 operand {0:?}: {1}",
input, error))
})format!("invalid hex u32 operand {input:?}: {error}"),
1234 })
1235 } else {
1236 input.parse::<u32>().map_err(|error| NcsAsmError::Parse {
1237 line,
1238 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid u32 operand {0:?}: {1}",
input, error))
})format!("invalid u32 operand {input:?}: {error}"),
1239 })
1240 }
1241}
1242
1243fn parse_usize_like(input: &str) -> Option<usize> {
1244 let input = input.trim();
1245 input.parse::<usize>().ok().or_else(|| {
1246 input
1247 .strip_prefix("0x")
1248 .or_else(|| input.strip_prefix("0X"))
1249 .and_then(|hex| usize::from_str_radix(hex, 16).ok())
1250 })
1251}
1252
1253fn unescape_nwasm_string(input: &str, line: usize) -> Result<String, NcsAsmError> {
1254 let mut chars = input.chars().peekable();
1255 let mut output = String::new();
1256 while let Some(ch) = chars.next() {
1257 if ch != '\\' {
1258 output.push(ch);
1259 continue;
1260 }
1261 let escaped = chars.next().ok_or_else(|| NcsAsmError::Parse {
1262 line,
1263 message: "dangling string escape".to_string(),
1264 })?;
1265 match escaped {
1266 '\\' => output.push('\\'),
1267 '\'' => output.push('\''),
1268 '"' => output.push('"'),
1269 'n' => output.push('\n'),
1270 'r' => output.push('\r'),
1271 't' => output.push('\t'),
1272 '0' => output.push('\0'),
1273 'x' => {
1274 let hi = chars.next().ok_or_else(|| NcsAsmError::Parse {
1275 line,
1276 message: "incomplete \\xNN string escape".to_string(),
1277 })?;
1278 let lo = chars.next().ok_or_else(|| NcsAsmError::Parse {
1279 line,
1280 message: "incomplete \\xNN string escape".to_string(),
1281 })?;
1282 let value = u8::from_str_radix(&::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", hi, lo))
})format!("{hi}{lo}"), 16).map_err(|error| {
1283 NcsAsmError::Parse {
1284 line,
1285 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid \\x escape: {0}", error))
})format!("invalid \\x escape: {error}"),
1286 }
1287 })?;
1288 output.push(char::from(value));
1289 }
1290 'u' => {
1291 if chars.next() != Some('{') {
1292 return Err(NcsAsmError::Parse {
1293 line,
1294 message: "expected `\\u{...}` escape".to_string(),
1295 });
1296 }
1297 let mut digits = String::new();
1298 loop {
1299 let next = chars.next().ok_or_else(|| NcsAsmError::Parse {
1300 line,
1301 message: "unterminated `\\u{...}` escape".to_string(),
1302 })?;
1303 if next == '}' {
1304 break;
1305 }
1306 digits.push(next);
1307 }
1308 let value =
1309 u32::from_str_radix(&digits, 16).map_err(|error| NcsAsmError::Parse {
1310 line,
1311 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid unicode escape: {0}",
error))
})format!("invalid unicode escape: {error}"),
1312 })?;
1313 let ch = char::from_u32(value).ok_or_else(|| NcsAsmError::Parse {
1314 line,
1315 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("invalid unicode scalar value {0:#x}",
value))
})format!("invalid unicode scalar value {value:#x}"),
1316 })?;
1317 output.push(ch);
1318 }
1319 other => {
1320 return Err(NcsAsmError::Parse {
1321 line,
1322 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsupported string escape \\{0}",
other))
})format!("unsupported string escape \\{other}"),
1323 });
1324 }
1325 }
1326 }
1327 Ok(output)
1328}
1329
1330fn truncate_nwasm_string(value: &str, max_string_length: usize) -> String {
1331 let escaped = value
1332 .chars()
1333 .take(max_string_length)
1334 .flat_map(char::escape_default)
1335 .collect::<String>();
1336 if value.len() > max_string_length {
1337 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{1}..{0}", value.len(), escaped))
})format!("{escaped}..{}", value.len())
1338 } else {
1339 escaped
1340 }
1341}
1342
1343fn collect_jump_labels(instructions: &[NcsInstruction]) -> BTreeMap<usize, String> {
1344 let mut offset = 0usize;
1345 let mut targets = BTreeSet::new();
1346 let mut labels = BTreeMap::new();
1347
1348 for instruction in instructions {
1349 if let Some(target) = jump_target_for_instruction(instruction, offset) {
1350 targets.insert((instruction.opcode as u8, target));
1351 }
1352 offset += instruction.encoded_len();
1353 }
1354
1355 for (opcode, target) in targets {
1356 let prefix = if opcode == NcsOpcode::Jsr as u8 {
1357 "sub"
1358 } else {
1359 "loc"
1360 };
1361 labels
1362 .entry(target)
1363 .or_insert_with(|| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}_{1:04}", prefix, target))
})format!("{prefix}_{target:04}"));
1364 }
1365
1366 labels
1367}
1368
1369fn jump_target_for_instruction(instruction: &NcsInstruction, offset: usize) -> Option<usize> {
1370 if !#[allow(non_exhaustive_omitted_patterns)] match instruction.opcode {
NcsOpcode::Jmp | NcsOpcode::Jsr | NcsOpcode::Jz | NcsOpcode::Jnz => true,
_ => false,
}matches!(
1371 instruction.opcode,
1372 NcsOpcode::Jmp | NcsOpcode::Jsr | NcsOpcode::Jz | NcsOpcode::Jnz
1373 ) {
1374 return None;
1375 }
1376 read_i32(&instruction.extra, offset, instruction)
1377 .ok()
1378 .map(|relative| jump_target(offset, relative))
1379}
1380
1381fn jump_target(offset: usize, relative: i32) -> usize {
1382 let base = i64::try_from(offset).ok().unwrap_or(i64::MAX);
1383 let target = base.saturating_add(i64::from(relative));
1384 usize::try_from(target.max(0)).ok().unwrap_or(0)
1385}
1386
1387fn format_jump_target(
1388 opcode: NcsOpcode,
1389 target: usize,
1390 labels: &BTreeMap<usize, String>,
1391) -> String {
1392 if let Some(label) = labels.get(&target) {
1393 label.clone()
1394 } else if opcode == NcsOpcode::Jsr {
1395 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("sub_{0:04}", target))
})format!("sub_{target:04}")
1396 } else {
1397 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("loc_{0:04}", target))
})format!("loc_{target:04}")
1398 }
1399}
1400
1401fn sorted_functions(ndb: &Ndb) -> Vec<&NdbFunction> {
1402 let mut functions = ndb.functions.iter().collect::<Vec<_>>();
1403 functions.sort_by_key(|function| function.binary_start);
1404 functions
1405}
1406
1407fn line_in_function(offset: usize, function: &NdbFunction) -> bool {
1408 let start = function.binary_start.saturating_sub(13) as usize;
1409 let end = function.binary_end.saturating_sub(13) as usize;
1410 (start..end).contains(&offset)
1411}
1412
1413fn local_offset(offset: usize, function: &NdbFunction) -> usize {
1414 let start = function.binary_start.saturating_sub(13) as usize;
1415 offset.saturating_sub(start)
1416}
1417
1418fn render_function_header(function: &NdbFunction, ndb: &Ndb) -> String {
1419 let args = function
1420 .args
1421 .iter()
1422 .map(ToString::to_string)
1423 .collect::<Vec<_>>()
1424 .join(", ");
1425 let start = function.binary_start.saturating_sub(13);
1426 let end = function.binary_end.saturating_sub(13);
1427 let location = source_location_for_function(function, ndb).map_or_else(
1428 || " ".to_string(),
1429 |(file, line)| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" {1}.nss:{0} ",
line.saturating_sub(1), file))
})format!(" {file}.nss:{} ", line.saturating_sub(1)),
1430 );
1431
1432 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} {1}({2}):{3}[{4}:{5}]",
function.return_type, function.label, args, location, start,
end))
})format!(
1433 "{} {}({}):{}[{}:{}]",
1434 function.return_type, function.label, args, location, start, end
1435 )
1436}
1437
1438fn source_location_for_function(function: &NdbFunction, ndb: &Ndb) -> Option<(String, usize)> {
1439 ndb.lines
1440 .iter()
1441 .find(|line| line.binary_start == function.binary_start)
1442 .and_then(|line| {
1443 ndb.files
1444 .get(line.file_num)
1445 .map(|file| (file.name.clone(), line.line_num))
1446 })
1447}
1448
1449fn source_line_for_offset(
1450 offset: usize,
1451 ndb: &Ndb,
1452 source_files: Option<&BTreeMap<String, Vec<String>>>,
1453 last_source: &mut Option<(usize, usize)>,
1454) -> Option<(String, String)> {
1455 let line = unique_line_for_offset(offset, &ndb.lines)?;
1456 let key = (line.file_num, line.line_num);
1457 if last_source.as_ref() == Some(&key) {
1458 return None;
1459 }
1460 *last_source = Some(key);
1461
1462 let file = ndb.files.get(line.file_num)?;
1463 let source_files = source_files?;
1464 let lines = source_files.get(&file.name)?;
1465 let text = lines
1466 .get(line.line_num.saturating_sub(1))?
1467 .trim()
1468 .to_string();
1469 Some((
1470 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}.nss:{1}", file.name,
line.line_num.saturating_sub(1)))
})format!("{}.nss:{}", file.name, line.line_num.saturating_sub(1)),
1471 text,
1472 ))
1473}
1474
1475fn unique_line_for_offset(offset: usize, lines: &[NdbLine]) -> Option<&NdbLine> {
1476 let matches = lines
1477 .iter()
1478 .filter(|line| {
1479 let start = line.binary_start.saturating_sub(13) as usize;
1480 let end = line.binary_end.saturating_sub(13) as usize;
1481 (start..end).contains(&offset)
1482 })
1483 .collect::<Vec<_>>();
1484 if matches.len() == 1 {
1485 matches.into_iter().next()
1486 } else {
1487 None
1488 }
1489}
1490
1491fn decode_prefixed_string(
1492 extra: &[u8],
1493 offset: usize,
1494 instruction: &NcsInstruction,
1495) -> Result<String, NcsAsmError> {
1496 let length = usize::from(read_u16(extra, offset, instruction)?);
1497 let payload = extra
1498 .get(2..2 + length)
1499 .ok_or_else(|| NcsAsmError::InvalidExtra {
1500 offset,
1501 opcode: instruction.opcode,
1502 auxcode: instruction.auxcode,
1503 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected {0} string bytes",
length))
})format!("expected {length} string bytes"),
1504 })?;
1505 Ok(String::from_utf8_lossy(payload).to_string())
1506}
1507
1508fn read_u8_part(
1509 extra: &[u8],
1510 start: usize,
1511 offset: usize,
1512 instruction: &NcsInstruction,
1513) -> Result<u8, NcsAsmError> {
1514 extra
1515 .get(start)
1516 .copied()
1517 .ok_or_else(|| NcsAsmError::InvalidExtra {
1518 offset,
1519 opcode: instruction.opcode,
1520 auxcode: instruction.auxcode,
1521 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected byte at offset {0}",
start))
})format!("expected byte at offset {start}"),
1522 })
1523}
1524
1525fn read_u16(extra: &[u8], offset: usize, instruction: &NcsInstruction) -> Result<u16, NcsAsmError> {
1526 read_u16_part(extra, 0, offset, instruction)
1527}
1528
1529fn read_u16_part(
1530 extra: &[u8],
1531 start: usize,
1532 offset: usize,
1533 instruction: &NcsInstruction,
1534) -> Result<u16, NcsAsmError> {
1535 let bytes: [u8; 2] = extra
1536 .get(start..start + 2)
1537 .ok_or_else(|| NcsAsmError::InvalidExtra {
1538 offset,
1539 opcode: instruction.opcode,
1540 auxcode: instruction.auxcode,
1541 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 2 bytes at offset {0}",
start))
})format!("expected 2 bytes at offset {start}"),
1542 })?
1543 .try_into()
1544 .map_err(|_error| NcsAsmError::InvalidExtra {
1545 offset,
1546 opcode: instruction.opcode,
1547 auxcode: instruction.auxcode,
1548 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 2 bytes at offset {0}",
start))
})format!("expected 2 bytes at offset {start}"),
1549 })?;
1550 Ok(u16::from_be_bytes(bytes))
1551}
1552
1553fn read_i16_part(
1554 extra: &[u8],
1555 start: usize,
1556 offset: usize,
1557 instruction: &NcsInstruction,
1558) -> Result<i16, NcsAsmError> {
1559 let bytes: [u8; 2] = extra
1560 .get(start..start + 2)
1561 .ok_or_else(|| NcsAsmError::InvalidExtra {
1562 offset,
1563 opcode: instruction.opcode,
1564 auxcode: instruction.auxcode,
1565 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 2 bytes at offset {0}",
start))
})format!("expected 2 bytes at offset {start}"),
1566 })?
1567 .try_into()
1568 .map_err(|_error| NcsAsmError::InvalidExtra {
1569 offset,
1570 opcode: instruction.opcode,
1571 auxcode: instruction.auxcode,
1572 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 2 bytes at offset {0}",
start))
})format!("expected 2 bytes at offset {start}"),
1573 })?;
1574 Ok(i16::from_be_bytes(bytes))
1575}
1576
1577fn read_i32(extra: &[u8], offset: usize, instruction: &NcsInstruction) -> Result<i32, NcsAsmError> {
1578 read_i32_part(extra, 0, offset, instruction)
1579}
1580
1581fn read_i32_part(
1582 extra: &[u8],
1583 start: usize,
1584 offset: usize,
1585 instruction: &NcsInstruction,
1586) -> Result<i32, NcsAsmError> {
1587 let bytes: [u8; 4] = extra
1588 .get(start..start + 4)
1589 .ok_or_else(|| NcsAsmError::InvalidExtra {
1590 offset,
1591 opcode: instruction.opcode,
1592 auxcode: instruction.auxcode,
1593 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 4 bytes at offset {0}",
start))
})format!("expected 4 bytes at offset {start}"),
1594 })?
1595 .try_into()
1596 .map_err(|_error| NcsAsmError::InvalidExtra {
1597 offset,
1598 opcode: instruction.opcode,
1599 auxcode: instruction.auxcode,
1600 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("expected 4 bytes at offset {0}",
start))
})format!("expected 4 bytes at offset {start}"),
1601 })?;
1602 Ok(i32::from_be_bytes(bytes))
1603}
1604
1605fn read_u32(extra: &[u8], offset: usize, instruction: &NcsInstruction) -> Result<u32, NcsAsmError> {
1606 let bytes: [u8; 4] = extra
1607 .get(..4)
1608 .ok_or_else(|| NcsAsmError::InvalidExtra {
1609 offset,
1610 opcode: instruction.opcode,
1611 auxcode: instruction.auxcode,
1612 message: "expected 4 bytes".to_string(),
1613 })?
1614 .try_into()
1615 .map_err(|_error| NcsAsmError::InvalidExtra {
1616 offset,
1617 opcode: instruction.opcode,
1618 auxcode: instruction.auxcode,
1619 message: "expected 4 bytes".to_string(),
1620 })?;
1621 Ok(u32::from_be_bytes(bytes))
1622}
1623
1624fn read_f32(extra: &[u8], offset: usize, instruction: &NcsInstruction) -> Result<f32, NcsAsmError> {
1625 Ok(f32::from_bits(read_u32(extra, offset, instruction)?))
1626}
1627
1628#[cfg(test)]
1629mod tests {
1630 use std::collections::BTreeMap;
1631
1632 use super::{
1633 NcsAsmLine, NcsDisassemblyOptions, assemble_ncs_bytes, assemble_ncs_text, disassemble_ncs,
1634 render_ncs_disassembly, render_ncs_disassembly_with_ndb,
1635 };
1636 use crate::{
1637 BuiltinFunction, BuiltinType, LangSpec, NcsAuxCode, NcsInstruction, NcsOpcode, Ndb,
1638 NdbFile, NdbFunction, NdbLine, NdbType, encode_ncs_instructions,
1639 };
1640
1641 #[test]
1642 fn instruction_names_match_upstream_nwasm() {
1643 let instruction = NcsInstruction {
1644 opcode: NcsOpcode::LogicalAnd,
1645 auxcode: NcsAuxCode::TypeTypeIntegerInteger,
1646 extra: b"not-printed".to_vec(),
1647 };
1648
1649 assert_eq!(instruction.canonical_name(false), "LOGAND.II");
1650 assert_eq!(
1651 instruction.canonical_name(true),
1652 "LOGICAL_AND.TYPETYPE_INTEGER_INTEGER"
1653 );
1654 }
1655
1656 #[test]
1657 fn disassembles_action_names_with_langspec() -> Result<(), Box<dyn std::error::Error>> {
1658 let bytes = encode_ncs_instructions(&[
1659 NcsInstruction {
1660 opcode: NcsOpcode::ExecuteCommand,
1661 auxcode: NcsAuxCode::None,
1662 extra: [1_u16.to_be_bytes().as_slice(), &[2_u8]].concat(),
1663 },
1664 NcsInstruction {
1665 opcode: NcsOpcode::Ret,
1666 auxcode: NcsAuxCode::None,
1667 extra: Vec::new(),
1668 },
1669 ]);
1670 let spec = LangSpec {
1671 engine_num_structures: 0,
1672 engine_structures: Vec::new(),
1673 constants: Vec::new(),
1674 functions: vec![
1675 BuiltinFunction {
1676 name: "First".to_string(),
1677 return_type: BuiltinType::Void,
1678 parameters: Vec::new(),
1679 },
1680 BuiltinFunction {
1681 name: "DelayCommand".to_string(),
1682 return_type: BuiltinType::Void,
1683 parameters: Vec::new(),
1684 },
1685 ],
1686 };
1687
1688 let lines = disassemble_ncs(&bytes, Some(&spec), NcsDisassemblyOptions::default())?;
1689 assert_eq!(
1690 lines,
1691 vec![
1692 NcsAsmLine {
1693 offset: 0,
1694 label: None,
1695 instruction: "ACTION".to_string(),
1696 extra: "1, 2".to_string(),
1697 },
1698 NcsAsmLine {
1699 offset: 5,
1700 label: None,
1701 instruction: "RET".to_string(),
1702 extra: String::new(),
1703 },
1704 ]
1705 );
1706 Ok(())
1707 }
1708
1709 #[test]
1710 fn disassembles_string_constants_like_upstream() -> Result<(), Box<dyn std::error::Error>> {
1711 let extra = [5_u16.to_be_bytes().as_slice(), b"hello"].concat();
1712 let bytes = encode_ncs_instructions(&[NcsInstruction {
1713 opcode: NcsOpcode::Constant,
1714 auxcode: NcsAuxCode::TypeString,
1715 extra,
1716 }]);
1717 let lines = disassemble_ncs(&bytes, None, NcsDisassemblyOptions::default())?;
1718
1719 assert_eq!(lines.len(), 1);
1720 let line = lines
1721 .first()
1722 .expect("string disassembly should produce one line");
1723 assert_eq!(line.instruction, "CONST.S");
1724 assert_eq!(line.extra, "hello");
1725 Ok(())
1726 }
1727
1728 #[test]
1729 fn renders_jump_targets_with_labels() -> Result<(), Box<dyn std::error::Error>> {
1730 let bytes = encode_ncs_instructions(&[
1731 NcsInstruction {
1732 opcode: NcsOpcode::Jz,
1733 auxcode: NcsAuxCode::None,
1734 extra: 8_i32.to_be_bytes().to_vec(),
1735 },
1736 NcsInstruction {
1737 opcode: NcsOpcode::Ret,
1738 auxcode: NcsAuxCode::None,
1739 extra: Vec::new(),
1740 },
1741 NcsInstruction {
1742 opcode: NcsOpcode::Ret,
1743 auxcode: NcsAuxCode::None,
1744 extra: Vec::new(),
1745 },
1746 ]);
1747
1748 let rendered = render_ncs_disassembly(&bytes, None, NcsDisassemblyOptions::default())?;
1749 assert!(rendered.contains("0000: JZ loc_0008"));
1750 assert!(rendered.contains("loc_0008:"));
1751 Ok(())
1752 }
1753
1754 #[test]
1755 fn renders_function_headers_and_source_weaving_with_ndb()
1756 -> Result<(), Box<dyn std::error::Error>> {
1757 let bytes = encode_ncs_instructions(&[NcsInstruction {
1758 opcode: NcsOpcode::Ret,
1759 auxcode: NcsAuxCode::None,
1760 extra: Vec::new(),
1761 }]);
1762 let ndb = Ndb {
1763 files: vec![NdbFile {
1764 name: "test".to_string(),
1765 is_root: true,
1766 }],
1767 structs: Vec::new(),
1768 functions: vec![NdbFunction {
1769 label: "main".to_string(),
1770 binary_start: 13,
1771 binary_end: 15,
1772 return_type: NdbType::Void,
1773 args: Vec::new(),
1774 }],
1775 variables: Vec::new(),
1776 lines: vec![NdbLine {
1777 file_num: 0,
1778 line_num: 1,
1779 binary_start: 13,
1780 binary_end: 15,
1781 }],
1782 };
1783 let mut sources = BTreeMap::new();
1784 sources.insert("test".to_string(), vec!["void main() {}".to_string()]);
1785
1786 let rendered = render_ncs_disassembly_with_ndb(
1787 &bytes,
1788 None,
1789 Some(&ndb),
1790 Some(&sources),
1791 NcsDisassemblyOptions::default(),
1792 )?;
1793
1794 assert!(rendered.contains("v main(): test.nss:0 [0:2]"));
1795 assert!(rendered.contains("0000 0000: RET | test.nss:0 | void main() {}"));
1796 Ok(())
1797 }
1798
1799 #[test]
1800 fn renders_jsr_targets_as_function_labels_with_ndb() -> Result<(), Box<dyn std::error::Error>> {
1801 let bytes = encode_ncs_instructions(&[
1802 NcsInstruction {
1803 opcode: NcsOpcode::Jsr,
1804 auxcode: NcsAuxCode::None,
1805 extra: 6_i32.to_be_bytes().to_vec(),
1806 },
1807 NcsInstruction {
1808 opcode: NcsOpcode::Ret,
1809 auxcode: NcsAuxCode::None,
1810 extra: Vec::new(),
1811 },
1812 NcsInstruction {
1813 opcode: NcsOpcode::Ret,
1814 auxcode: NcsAuxCode::None,
1815 extra: Vec::new(),
1816 },
1817 ]);
1818 let ndb = Ndb {
1819 files: vec![NdbFile {
1820 name: "test".to_string(),
1821 is_root: true,
1822 }],
1823 structs: Vec::new(),
1824 functions: vec![NdbFunction {
1825 label: "helper".to_string(),
1826 binary_start: 19,
1827 binary_end: 21,
1828 return_type: NdbType::Void,
1829 args: Vec::new(),
1830 }],
1831 variables: Vec::new(),
1832 lines: Vec::new(),
1833 };
1834
1835 let rendered = render_ncs_disassembly_with_ndb(
1836 &bytes,
1837 None,
1838 Some(&ndb),
1839 None,
1840 NcsDisassemblyOptions::default(),
1841 )?;
1842
1843 assert!(rendered.contains("0000: JSR helper"));
1844 Ok(())
1845 }
1846
1847 #[test]
1848 fn assemble_roundtrips_rendered_disassembly_to_identical_bytes()
1849 -> Result<(), Box<dyn std::error::Error>> {
1850 let bytes = encode_ncs_instructions(&[
1851 NcsInstruction {
1852 opcode: NcsOpcode::Constant,
1853 auxcode: NcsAuxCode::TypeString,
1854 extra: [6_u16.to_be_bytes().as_slice(), b"hi\\n[]"].concat(),
1855 },
1856 NcsInstruction {
1857 opcode: NcsOpcode::AssignmentBase,
1858 auxcode: NcsAuxCode::TypeVoid,
1859 extra: [
1860 (-12_i32).to_be_bytes().as_slice(),
1861 4_u16.to_be_bytes().as_slice(),
1862 ]
1863 .concat(),
1864 },
1865 NcsInstruction {
1866 opcode: NcsOpcode::ExecuteCommand,
1867 auxcode: NcsAuxCode::None,
1868 extra: [7_u16.to_be_bytes().as_slice(), &[1_u8]].concat(),
1869 },
1870 NcsInstruction {
1871 opcode: NcsOpcode::Jsr,
1872 auxcode: NcsAuxCode::None,
1873 extra: 12_i32.to_be_bytes().to_vec(),
1874 },
1875 NcsInstruction {
1876 opcode: NcsOpcode::Equal,
1877 auxcode: NcsAuxCode::TypeTypeStructStruct,
1878 extra: 12_u16.to_be_bytes().to_vec(),
1879 },
1880 NcsInstruction {
1881 opcode: NcsOpcode::Ret,
1882 auxcode: NcsAuxCode::None,
1883 extra: Vec::new(),
1884 },
1885 NcsInstruction {
1886 opcode: NcsOpcode::Ret,
1887 auxcode: NcsAuxCode::None,
1888 extra: Vec::new(),
1889 },
1890 ]);
1891
1892 let rendered = render_ncs_disassembly(
1893 &bytes,
1894 None,
1895 NcsDisassemblyOptions {
1896 max_string_length: usize::MAX,
1897 ..NcsDisassemblyOptions::default()
1898 },
1899 )?;
1900 let reassembled = assemble_ncs_bytes(&rendered, None)?;
1901
1902 assert_eq!(reassembled, bytes);
1903 Ok(())
1904 }
1905
1906 #[test]
1907 fn assemble_accepts_internal_names_and_explicit_auxcodes()
1908 -> Result<(), Box<dyn std::error::Error>> {
1909 let text = "\
19100000: ASSIGNMENT_BASE.TYPE_VOID -8, 4
19110008: EXECUTE_COMMAND.NONE 1, 2
19120013: RET.NONE
1913";
1914 let instructions = assemble_ncs_text(text, None)?;
1915
1916 assert_eq!(instructions.len(), 3);
1917 let first = instructions.first().expect("expected first instruction");
1918 let second = instructions.get(1).expect("expected second instruction");
1919 assert_eq!(first.opcode, NcsOpcode::AssignmentBase);
1920 assert_eq!(first.auxcode, NcsAuxCode::TypeVoid);
1921 assert_eq!(second.opcode, NcsOpcode::ExecuteCommand);
1922 assert_eq!(second.auxcode, NcsAuxCode::None);
1923 Ok(())
1924 }
1925}