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