essential_asm_spec/
lib.rs

1//! A small crate that exports the [ASM_YAML] spec string and provides a
2//! structured [Tree] model representing its deserialized form.
3
4use serde::Deserialize;
5
6mod de;
7pub mod visit;
8
9/// The raw YAML specification string.
10pub const ASM_YAML: &str = include_str!("./../asm.yml");
11
12/// Operations are laid out in a rose tree.
13/// Nodes are ordered by their opcode, ensured during deserialisation.
14#[derive(Debug)]
15pub struct Tree(Vec<(String, Node)>);
16
17/// Each node of the tree can be an operation, or another group.
18#[derive(Debug)]
19pub enum Node {
20    Op(Op),
21    Group(Group),
22}
23
24/// A group of related operations and subgroups.
25#[derive(Debug, Deserialize)]
26pub struct Group {
27    pub description: String,
28    #[serde(rename = "group")]
29    pub tree: Tree,
30}
31
32/// A single operation.
33///
34/// For the meaning of each of these fields, refer to the `essential-asm-spec` crate README.
35#[derive(Debug, Deserialize)]
36pub struct Op {
37    pub opcode: u8,
38    pub description: String,
39    #[serde(default)]
40    pub short: String,
41    #[serde(default)]
42    pub panics: Vec<String>,
43    #[serde(default)]
44    pub num_arg_bytes: u8,
45    #[serde(default)]
46    pub stack_in: Vec<String>,
47    #[serde(default)]
48    pub stack_out: StackOut,
49}
50
51/// The stack output of an operation, either fixed or dynamic (dependent on a `stack_in` value).
52#[derive(Debug)]
53pub enum StackOut {
54    Fixed(Vec<String>),
55    Dynamic(StackOutDynamic),
56}
57
58/// The stack output size is dynamic, dependent on a `stack_in` value.
59#[derive(Debug, Deserialize)]
60pub struct StackOutDynamic {
61    pub elem: String,
62    pub len: String,
63}
64
65impl Node {
66    /// Get the opcode for the node.
67    ///
68    /// If the node is a group, this is the opcode of the first op.
69    fn opcode(&self) -> u8 {
70        match self {
71            Self::Op(op) => op.opcode,
72            Self::Group(group) => group.tree.first().unwrap().1.opcode(),
73        }
74    }
75}
76
77impl Default for StackOut {
78    fn default() -> Self {
79        Self::Fixed(vec![])
80    }
81}
82
83impl std::ops::Deref for Tree {
84    type Target = Vec<(String, Node)>;
85    fn deref(&self) -> &Self::Target {
86        &self.0
87    }
88}
89
90/// Deserialize the top-level op tree from the YAML.
91pub fn tree() -> Tree {
92    serde_yaml::from_str::<Tree>(ASM_YAML)
93        .expect("ASM_YAML is a const and should never fail to deserialize")
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_deserialize() {
102        // Panics internally on failure, but should never fail.
103        let tree = tree();
104        println!("{:#?}", tree);
105    }
106
107    #[test]
108    fn test_no_duplicate_opcodes() {
109        let tree = tree();
110        let mut opcodes = std::collections::BTreeSet::new();
111        super::visit::ops(&tree, &mut |name, op| {
112            assert!(
113                opcodes.insert(op.opcode),
114                "ASM YAML must not contain duplicate opcodes. \
115                Opcode `0x{:02X}` for {} already exists.",
116                op.opcode,
117                name.join(" "),
118            );
119        });
120    }
121
122    #[test]
123    fn test_visit_ordered_by_opcode() {
124        let tree = tree();
125        let mut last_opcode = 0;
126        super::visit::ops(&tree, &mut |_name, op| {
127            assert!(
128                last_opcode < op.opcode,
129                "Visit functions are expected to visit ops in opcode order.\n  \
130                last opcode: `0x{last_opcode:02X}`\n  \
131                this opcode: `0x{:02X}`",
132                op.opcode
133            );
134            last_opcode = op.opcode;
135        });
136    }
137}