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    type Params = ();
45    
46    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
47        Document::join(self.items.iter().map(|i| i.as_document(&())), Document::Line)
48    }
49}
50
51/// Top-level items in MSIL.
52#[derive(Clone, Debug, PartialEq, Eq, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
54pub enum Item {
55    /// Assembly definition.
56    Assembly(Assembly),
57    /// Module definition.
58    Module(String),
59    /// Class definition.
60    Class(Class),
61    /// External assembly reference.
62    AssemblyExtern(String),
63}
64
65impl ToSource for Item {
66    fn to_source(&self, buffer: &mut SourceBuffer) {
67        match self {
68            Item::Assembly(a) => a.to_source(buffer),
69            Item::Module(m) => {
70                buffer.push(".module ");
71                buffer.push(m)
72            }
73            Item::Class(c) => c.to_source(buffer),
74            Item::AssemblyExtern(a) => {
75                buffer.push(".assembly extern ");
76                buffer.push(a);
77                buffer.push(" {}")
78            }
79        }
80    }
81}
82
83#[cfg(feature = "oak-pretty-print")]
84impl AsDocument for Item {
85    type Params = ();
86    
87    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
88        match self {
89            Item::Assembly(a) => a.as_document(&()),
90            Item::Module(m) => Document::Text(format!(".module {}", m).into()),
91            Item::Class(c) => c.as_document(&()),
92            Item::AssemblyExtern(a) => Document::Text(format!(".assembly extern {} {{}}", a).into()),
93        }
94    }
95}
96
97/// Assembly definition.
98#[derive(Clone, Debug, PartialEq, Eq, Hash)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub struct Assembly {
101    /// Name of the assembly.
102    pub name: String,
103    /// Span of the assembly definition.
104    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
105    pub span: Range<usize>,
106}
107
108impl ToSource for Assembly {
109    fn to_source(&self, buffer: &mut SourceBuffer) {
110        buffer.push(".assembly ");
111        buffer.push(&self.name);
112        buffer.push(" {}")
113    }
114}
115
116#[cfg(feature = "oak-pretty-print")]
117impl AsDocument for Assembly {
118    type Params = ();
119    
120    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
121        Document::Text(format!(".assembly {} {{}}", self.name).into())
122    }
123}
124
125/// Class definition.
126#[derive(Clone, Debug, PartialEq, Eq, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct Class {
129    /// Name of the class.
130    pub name: String,
131    /// Methods in the class.
132    pub methods: Vec<Method>,
133    /// Span of the class definition.
134    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
135    pub span: Range<usize>,
136}
137
138impl ToSource for Class {
139    fn to_source(&self, buffer: &mut SourceBuffer) {
140        buffer.push(".class public auto ansi beforefieldinit ");
141        buffer.push(&self.name);
142        buffer.push("\n{");
143        for method in &self.methods {
144            buffer.push("\n");
145            method.to_source(buffer)
146        }
147        buffer.push("\n}")
148    }
149}
150
151#[cfg(feature = "oak-pretty-print")]
152impl AsDocument for Class {
153    type Params = ();
154    
155    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
156        Document::Concat(vec![
157            Document::Text(format!(".class public auto ansi beforefieldinit {}", self.name).into()),
158            Document::Line,
159            Document::Text("{".into()),
160            Document::indent(Document::join(self.methods.iter().map(|m| m.as_document(&())), Document::Line)),
161            Document::Text("}".into()),
162        ])
163    }
164}
165
166/// Method definition.
167#[derive(Clone, Debug, PartialEq, Eq, Hash)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct Method {
170    /// Name of the method.
171    pub name: String,
172    /// Instructions in the method.
173    pub instructions: Vec<Instruction>,
174    /// Span of the method definition.
175    #[cfg_attr(feature = "serde", serde(with = "oak_core::serde_range"))]
176    pub span: Range<usize>,
177}
178
179impl ToSource for Method {
180    fn to_source(&self, buffer: &mut SourceBuffer) {
181        buffer.push(".method public hidebysig static void ");
182        buffer.push(&self.name);
183        buffer.push("() cil managed\n{");
184        if !self.instructions.is_empty() {
185            buffer.push("\n    .entrypoint");
186            for inst in &self.instructions {
187                buffer.push("\n    ");
188                inst.to_source(buffer)
189            }
190        }
191        buffer.push("\n}")
192    }
193}
194
195#[cfg(feature = "oak-pretty-print")]
196impl AsDocument for Method {
197    type Params = ();
198    
199    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
200        let mut body = vec![Document::Text(".entrypoint".into()), Document::Line];
201        body.extend(self.instructions.iter().map(|i| i.as_document(&())));
202
203        Document::Concat(vec![
204            Document::Text(format!(".method public hidebysig static void {}() cil managed", self.name).into()),
205            Document::Line,
206            Document::Text("{".into()),
207            Document::indent(Document::join(body, Document::Line)),
208            Document::Text("}".into()),
209        ])
210    }
211}
212
213/// MSIL instructions.
214#[derive(Clone, Debug, PartialEq, Eq, Hash)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216pub enum Instruction {
217    /// Simple instruction without parameters.
218    Simple(String),
219    /// Instruction with a string parameter (e.g., `ldstr`).
220    String(String),
221    /// Call instruction with a method name.
222    Call(String),
223}
224
225impl ToSource for Instruction {
226    fn to_source(&self, buffer: &mut SourceBuffer) {
227        match self {
228            Instruction::Simple(s) => buffer.push(s),
229            Instruction::String(s) => {
230                buffer.push("ldstr \"");
231                buffer.push(s);
232                buffer.push("\"")
233            }
234            Instruction::Call(s) => {
235                buffer.push("call ");
236                buffer.push(s)
237            }
238        }
239    }
240}
241
242#[cfg(feature = "oak-pretty-print")]
243impl AsDocument for Instruction {
244    type Params = ();
245    
246    fn as_document(&self, _params: &Self::Params) -> Document<'_> {
247        match self {
248            Instruction::Simple(s) => Document::Text(s.clone().into()),
249            Instruction::String(s) => Document::Text(format!("ldstr \"{}\"", s).into()),
250            Instruction::Call(s) => Document::Text(format!("call {}", s).into()),
251        }
252    }
253}