Skip to main content

oak_msil/ast/
mod.rs

1#![doc = include_str!("readme.md")]
2use core::range::Range;
3use oak_core::source::{SourceBuffer, ToSource};
4#[cfg(feature = "oak-pretty-print")]
5use oak_pretty_print::{AsDocument, Document};
6
7/// MSIL Typed AST Root.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct MsilTypedRoot<'a> {
10    red: oak_core::RedNode<'a, crate::language::MsilLanguage>,
11}
12
13impl<'a> oak_core::tree::TypedNode<'a> for MsilTypedRoot<'a> {
14    type Language = crate::language::MsilLanguage;
15
16    fn cast(node: oak_core::RedNode<'a, Self::Language>) -> Option<Self> {
17        if node.kind::<crate::parser::element_type::MsilElementType>() == crate::parser::element_type::MsilElementType::Root { Some(Self { red: node }) } else { None }
18    }
19
20    fn green(&self) -> &oak_core::GreenNode<'a, Self::Language> {
21        self.red.green()
22    }
23}
24
25/// Root node of the MSIL AST.
26#[derive(Clone, Debug, PartialEq, Eq, Hash)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct MsilRoot {
29    /// List of items (directives, classes, methods, etc.).
30    pub items: Vec<Item>,
31}
32
33impl ToSource for MsilRoot {
34    fn to_source(&self, buffer: &mut SourceBuffer) {
35        for item in &self.items {
36            item.to_source(buffer);
37            buffer.push("\n")
38        }
39    }
40}
41
42#[cfg(feature = "oak-pretty-print")]
43impl AsDocument for MsilRoot {
44    fn as_document(&self) -> Document<'_> {
45        Document::join(self.items.iter().map(|i| i.as_document()), Document::Line)
46    }
47}
48
49/// Top-level items in MSIL.
50#[derive(Clone, Debug, PartialEq, Eq, Hash)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub enum Item {
53    /// Assembly definition.
54    Assembly(Assembly),
55    /// Module definition.
56    Module(String),
57    /// Class definition.
58    Class(Class),
59    /// External assembly reference.
60    AssemblyExtern(String),
61}
62
63impl ToSource for Item {
64    fn to_source(&self, buffer: &mut SourceBuffer) {
65        match self {
66            Item::Assembly(a) => a.to_source(buffer),
67            Item::Module(m) => {
68                buffer.push(".module ");
69                buffer.push(m)
70            }
71            Item::Class(c) => c.to_source(buffer),
72            Item::AssemblyExtern(a) => {
73                buffer.push(".assembly extern ");
74                buffer.push(a);
75                buffer.push(" {}")
76            }
77        }
78    }
79}
80
81#[cfg(feature = "oak-pretty-print")]
82impl AsDocument for Item {
83    fn as_document(&self) -> Document<'_> {
84        match self {
85            Item::Assembly(a) => a.as_document(),
86            Item::Module(m) => Document::Text(format!(".module {}", m).into()),
87            Item::Class(c) => c.as_document(),
88            Item::AssemblyExtern(a) => Document::Text(format!(".assembly extern {} {{}}", a).into()),
89        }
90    }
91}
92
93/// Assembly definition.
94#[derive(Clone, Debug, PartialEq, Eq, Hash)]
95#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
96pub struct Assembly {
97    /// Name of the assembly.
98    pub name: String,
99    /// Span of the assembly definition.
100    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
101    pub span: Range<usize>,
102}
103
104impl ToSource for Assembly {
105    fn to_source(&self, buffer: &mut SourceBuffer) {
106        buffer.push(".assembly ");
107        buffer.push(&self.name);
108        buffer.push(" {}")
109    }
110}
111
112#[cfg(feature = "oak-pretty-print")]
113impl AsDocument for Assembly {
114    fn as_document(&self) -> Document<'_> {
115        Document::Text(format!(".assembly {} {{}}", self.name).into())
116    }
117}
118
119/// Class definition.
120#[derive(Clone, Debug, PartialEq, Eq, Hash)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct Class {
123    /// Name of the class.
124    pub name: String,
125    /// Methods in the class.
126    pub methods: Vec<Method>,
127    /// Span of the class definition.
128    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
129    pub span: Range<usize>,
130}
131
132impl ToSource for Class {
133    fn to_source(&self, buffer: &mut SourceBuffer) {
134        buffer.push(".class public auto ansi beforefieldinit ");
135        buffer.push(&self.name);
136        buffer.push("\n{");
137        for method in &self.methods {
138            buffer.push("\n");
139            method.to_source(buffer)
140        }
141        buffer.push("\n}")
142    }
143}
144
145#[cfg(feature = "oak-pretty-print")]
146impl AsDocument for Class {
147    fn as_document(&self) -> Document<'_> {
148        Document::Concat(vec![
149            Document::Text(format!(".class public auto ansi beforefieldinit {}", self.name).into()),
150            Document::Line,
151            Document::Text("{".into()),
152            Document::indent(Document::join(self.methods.iter().map(|m| m.as_document()), Document::Line)),
153            Document::Text("}".into()),
154        ])
155    }
156}
157
158/// Method definition.
159#[derive(Clone, Debug, PartialEq, Eq, Hash)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
161pub struct Method {
162    /// Name of the method.
163    pub name: String,
164    /// Instructions in the method.
165    pub instructions: Vec<Instruction>,
166    /// Span of the method definition.
167    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
168    pub span: Range<usize>,
169}
170
171impl ToSource for Method {
172    fn to_source(&self, buffer: &mut SourceBuffer) {
173        buffer.push(".method public hidebysig static void ");
174        buffer.push(&self.name);
175        buffer.push("() cil managed\n{");
176        if !self.instructions.is_empty() {
177            buffer.push("\n    .entrypoint");
178            for inst in &self.instructions {
179                buffer.push("\n    ");
180                inst.to_source(buffer)
181            }
182        }
183        buffer.push("\n}")
184    }
185}
186
187#[cfg(feature = "oak-pretty-print")]
188impl AsDocument for Method {
189    fn as_document(&self) -> Document<'_> {
190        let mut body = vec![Document::Text(".entrypoint".into()), Document::Line];
191        body.extend(self.instructions.iter().map(|i| i.as_document()));
192
193        Document::Concat(vec![
194            Document::Text(format!(".method public hidebysig static void {}() cil managed", self.name).into()),
195            Document::Line,
196            Document::Text("{".into()),
197            Document::indent(Document::join(body, Document::Line)),
198            Document::Text("}".into()),
199        ])
200    }
201}
202
203/// MSIL instructions.
204#[derive(Clone, Debug, PartialEq, Eq, Hash)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub enum Instruction {
207    /// Simple instruction without parameters.
208    Simple(String),
209    /// Instruction with a string parameter (e.g., `ldstr`).
210    String(String),
211    /// Call instruction with a method name.
212    Call(String),
213}
214
215impl ToSource for Instruction {
216    fn to_source(&self, buffer: &mut SourceBuffer) {
217        match self {
218            Instruction::Simple(s) => buffer.push(s),
219            Instruction::String(s) => {
220                buffer.push("ldstr \"");
221                buffer.push(s);
222                buffer.push("\"")
223            }
224            Instruction::Call(s) => {
225                buffer.push("call ");
226                buffer.push(s)
227            }
228        }
229    }
230}
231
232#[cfg(feature = "oak-pretty-print")]
233impl AsDocument for Instruction {
234    fn as_document(&self) -> Document<'_> {
235        match self {
236            Instruction::Simple(s) => Document::Text(s.clone().into()),
237            Instruction::String(s) => Document::Text(format!("ldstr \"{}\"", s).into()),
238            Instruction::Call(s) => Document::Text(format!("call {}", s).into()),
239        }
240    }
241}