1use camino::Utf8PathBuf;
2use ecow::EcoString;
3use gleam_compiler_core::ast::SrcSpan;
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct SourceContext {
7 path: Utf8PathBuf,
8 source: String,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct SourceSpan {
13 start: usize,
14 end: usize,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct PanicSite {
19 module: EcoString,
20 function: EcoString,
21 span: SourceSpan,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct EchoSite {
26 module: EcoString,
27 function: EcoString,
28 span: SourceSpan,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub struct HostCallSite {
33 module: EcoString,
34 function: EcoString,
35 span: SourceSpan,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub(crate) struct FunctionCallTarget<Function> {
40 function: Function,
41 site: HostCallSite,
42}
43
44impl SourceContext {
45 pub fn new(path: impl Into<Utf8PathBuf>, source: impl Into<String>) -> Self {
46 Self {
47 path: path.into(),
48 source: source.into(),
49 }
50 }
51
52 pub fn path(&self) -> &Utf8PathBuf {
53 &self.path
54 }
55
56 pub fn source(&self) -> &str {
57 &self.source
58 }
59
60 pub(crate) fn named_source(&self) -> miette::NamedSource<String> {
61 miette::NamedSource::new(self.path.as_str(), self.source.clone()).with_language("gleam")
62 }
63}
64
65impl SourceSpan {
66 pub fn new(start: usize, end: usize) -> Self {
67 Self { start, end }
68 }
69
70 pub fn start(&self) -> usize {
71 self.start
72 }
73
74 pub fn end(&self) -> usize {
75 self.end
76 }
77
78 pub fn len(&self) -> usize {
79 self.end.saturating_sub(self.start)
80 }
81
82 pub fn is_empty(&self) -> bool {
83 self.len() == 0
84 }
85
86 pub(crate) fn to_miette(self) -> miette::SourceSpan {
87 (self.start, self.len()).into()
88 }
89}
90
91impl From<SrcSpan> for SourceSpan {
92 fn from(span: SrcSpan) -> Self {
93 Self {
94 start: span.start as usize,
95 end: span.end as usize,
96 }
97 }
98}
99
100impl PanicSite {
101 pub fn new(module: EcoString, function: EcoString, span: SourceSpan) -> Self {
102 Self {
103 module,
104 function,
105 span,
106 }
107 }
108
109 #[cfg(test)]
110 pub(crate) fn unknown() -> Self {
111 Self {
112 module: "<unknown>".into(),
113 function: "<unknown>".into(),
114 span: SourceSpan::new(0, 0),
115 }
116 }
117
118 pub fn module(&self) -> &EcoString {
119 &self.module
120 }
121
122 pub fn function(&self) -> &EcoString {
123 &self.function
124 }
125
126 pub fn span(&self) -> SourceSpan {
127 self.span
128 }
129}
130
131impl EchoSite {
132 pub fn new(module: EcoString, function: EcoString, span: SourceSpan) -> Self {
133 Self {
134 module,
135 function,
136 span,
137 }
138 }
139
140 pub fn module(&self) -> &EcoString {
141 &self.module
142 }
143
144 pub fn function(&self) -> &EcoString {
145 &self.function
146 }
147
148 pub fn span(&self) -> SourceSpan {
149 self.span
150 }
151}
152
153impl HostCallSite {
154 pub fn new(module: EcoString, function: EcoString, span: SourceSpan) -> Self {
155 Self {
156 module,
157 function,
158 span,
159 }
160 }
161
162 pub fn module(&self) -> &EcoString {
163 &self.module
164 }
165
166 pub fn function(&self) -> &EcoString {
167 &self.function
168 }
169
170 pub fn span(&self) -> SourceSpan {
171 self.span
172 }
173
174 #[cfg(test)]
175 pub(crate) fn unknown() -> Self {
176 Self::new(
177 "<unknown>".into(),
178 "<unknown>".into(),
179 SourceSpan::new(0, 0),
180 )
181 }
182}
183
184impl<Function> FunctionCallTarget<Function> {
185 pub(crate) fn new(function: Function, site: HostCallSite) -> Self {
186 Self { function, site }
187 }
188
189 pub(crate) fn function(&self) -> &Function {
190 &self.function
191 }
192
193 pub(crate) fn site(&self) -> &HostCallSite {
194 &self.site
195 }
196}
197
198#[cfg(test)]
199impl From<crate::plan::FunctionInstantiation>
200 for FunctionCallTarget<crate::plan::FunctionInstantiation>
201{
202 fn from(function: crate::plan::FunctionInstantiation) -> Self {
203 Self::new(function, HostCallSite::unknown())
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::{EchoSite, HostCallSite, PanicSite, SourceContext, SourceSpan};
210 use gleam_compiler_core::ast::SrcSpan;
211
212 #[test]
213 fn source_context_preserves_path_and_source() {
214 let context = SourceContext::new("main.gleam", "pub fn main() { 1 }");
215
216 assert_eq!(context.path().as_str(), "main.gleam");
217 assert_eq!(context.source(), "pub fn main() { 1 }");
218 assert_eq!(context.named_source().name(), "main.gleam");
219 }
220
221 #[test]
222 fn source_span_converts_from_gleam_span() {
223 let span = SourceSpan::from(SrcSpan::new(3, 9));
224
225 assert_eq!(span.start(), 3);
226 assert_eq!(span.end(), 9);
227 assert_eq!(span.len(), 6);
228 assert!(!span.is_empty());
229 assert_eq!(span.to_miette(), (3, 6).into());
230
231 let empty = SourceSpan::new(4, 4);
232 assert_eq!(empty.len(), 0);
233 assert!(empty.is_empty());
234 }
235
236 #[test]
237 fn panic_site_preserves_module_function_and_span() {
238 let site = PanicSite::new("main".into(), "run".into(), SourceSpan::new(4, 8));
239
240 assert_eq!(site.module(), "main");
241 assert_eq!(site.function(), "run");
242 assert_eq!(site.span(), SourceSpan::new(4, 8));
243 }
244
245 #[test]
246 fn echo_site_preserves_module_function_and_span() {
247 let site = EchoSite::new("main".into(), "run".into(), SourceSpan::new(4, 8));
248
249 assert_eq!(site.module(), "main");
250 assert_eq!(site.function(), "run");
251 assert_eq!(site.span(), SourceSpan::new(4, 8));
252 }
253
254 #[test]
255 fn host_call_site_preserves_module_function_and_span() {
256 let site = HostCallSite::new("main".into(), "run".into(), SourceSpan::new(4, 8));
257
258 assert_eq!(site.module(), "main");
259 assert_eq!(site.function(), "run");
260 assert_eq!(site.span(), SourceSpan::new(4, 8));
261 }
262}