1use std::{error::Error, fmt};
2
3use crate::{
4 CodegenError, CompileArtifacts, CompileError, CompileOptions, DEFAULT_LANGSPEC_SCRIPT_NAME,
5 LangSpec, LangSpecError, OptimizationLevel, PreprocessError, Script, ScriptResolver,
6 SourceBundle, SourceError, SourceLoadOptions, compile_script, compile_script_with_source_map,
7 graphviz::render_script_graphviz, load_langspec, load_source_bundle, parse_source_bundle,
8};
9
10#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompilerSessionOptions {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f,
"CompilerSessionOptions", "langspec_script_name",
&self.langspec_script_name, "source_load", &self.source_load,
"compile", &self.compile, "emit_debug", &&self.emit_debug)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for CompilerSessionOptions {
#[inline]
fn clone(&self) -> CompilerSessionOptions {
CompilerSessionOptions {
langspec_script_name: ::core::clone::Clone::clone(&self.langspec_script_name),
source_load: ::core::clone::Clone::clone(&self.source_load),
compile: ::core::clone::Clone::clone(&self.compile),
emit_debug: ::core::clone::Clone::clone(&self.emit_debug),
}
}
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for CompilerSessionOptions {
#[inline]
fn eq(&self, other: &CompilerSessionOptions) -> bool {
self.emit_debug == other.emit_debug &&
self.langspec_script_name == other.langspec_script_name &&
self.source_load == other.source_load &&
self.compile == other.compile
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for CompilerSessionOptions {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<String>;
let _: ::core::cmp::AssertParamIsEq<SourceLoadOptions>;
let _: ::core::cmp::AssertParamIsEq<CompileOptions>;
let _: ::core::cmp::AssertParamIsEq<bool>;
}
}Eq)]
12pub struct CompilerSessionOptions {
13 pub langspec_script_name: String,
15 pub source_load: SourceLoadOptions,
17 pub compile: CompileOptions,
19 pub emit_debug: bool,
21}
22
23impl Default for CompilerSessionOptions {
24 fn default() -> Self {
25 Self {
26 langspec_script_name: DEFAULT_LANGSPEC_SCRIPT_NAME.to_string(),
27 source_load: SourceLoadOptions::default(),
28 compile: CompileOptions::default(),
29 emit_debug: true,
30 }
31 }
32}
33
34#[derive(#[automatically_derived]
impl ::core::fmt::Debug for CompilerSessionError {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
CompilerSessionError::LangSpec(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"LangSpec", &__self_0),
CompilerSessionError::Preprocess(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Preprocess", &__self_0),
CompilerSessionError::Source(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Source",
&__self_0),
CompilerSessionError::Compile(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Compile", &__self_0),
}
}
}Debug)]
36pub enum CompilerSessionError {
37 LangSpec(LangSpecError),
39 Preprocess(PreprocessError),
41 Source(SourceError),
43 Compile(CompileError),
45}
46
47impl fmt::Display for CompilerSessionError {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Self::LangSpec(error) => error.fmt(f),
51 Self::Preprocess(error) => error.fmt(f),
52 Self::Source(error) => error.fmt(f),
53 Self::Compile(error) => error.fmt(f),
54 }
55 }
56}
57
58impl Error for CompilerSessionError {}
59
60impl From<LangSpecError> for CompilerSessionError {
61 fn from(value: LangSpecError) -> Self {
62 Self::LangSpec(value)
63 }
64}
65
66impl From<SourceError> for CompilerSessionError {
67 fn from(value: SourceError) -> Self {
68 Self::Source(value)
69 }
70}
71
72impl From<PreprocessError> for CompilerSessionError {
73 fn from(value: PreprocessError) -> Self {
74 Self::Preprocess(value)
75 }
76}
77
78impl From<CompileError> for CompilerSessionError {
79 fn from(value: CompileError) -> Self {
80 Self::Compile(value)
81 }
82}
83
84pub struct CompilerSession<'a> {
86 resolver: &'a dyn ScriptResolver,
87 options: CompilerSessionOptions,
88 cached_langspec: Option<LangSpec>,
89}
90
91#[derive(#[automatically_derived]
impl ::core::fmt::Debug for PreparedScript {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f,
"PreparedScript", "langspec", &self.langspec, "bundle",
&self.bundle, "script", &&self.script)
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for PreparedScript {
#[inline]
fn clone(&self) -> PreparedScript {
PreparedScript {
langspec: ::core::clone::Clone::clone(&self.langspec),
bundle: ::core::clone::Clone::clone(&self.bundle),
script: ::core::clone::Clone::clone(&self.script),
}
}
}Clone)]
92pub(crate) struct PreparedScript {
93 pub(crate) langspec: LangSpec,
94 pub(crate) bundle: SourceBundle,
95 pub(crate) script: Script,
96}
97
98impl fmt::Debug for CompilerSession<'_> {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 f.debug_struct("CompilerSession")
101 .field("options", &self.options)
102 .field("has_cached_langspec", &self.cached_langspec.is_some())
103 .finish()
104 }
105}
106
107impl<'a> CompilerSession<'a> {
108 #[must_use]
110 pub fn new(resolver: &'a dyn ScriptResolver) -> Self {
111 Self::with_options(resolver, CompilerSessionOptions::default())
112 }
113
114 #[must_use]
116 pub fn with_options(resolver: &'a dyn ScriptResolver, options: CompilerSessionOptions) -> Self {
117 Self {
118 resolver,
119 options,
120 cached_langspec: None,
121 }
122 }
123
124 #[must_use]
126 pub fn options(&self) -> &CompilerSessionOptions {
127 &self.options
128 }
129
130 #[must_use]
132 pub fn generate_debugger_output(&self) -> bool {
133 self.options.emit_debug
134 }
135
136 pub fn set_generate_debugger_output(&mut self, state: bool) {
138 self.options.emit_debug = state;
139 }
140
141 #[must_use]
143 pub fn optimization_level(&self) -> OptimizationLevel {
144 self.options.compile.optimization
145 }
146
147 pub fn set_optimization_level(&mut self, optimization: OptimizationLevel) {
149 self.options.compile.optimization = optimization;
150 }
151
152 #[must_use]
154 pub fn source_load_options(&self) -> SourceLoadOptions {
155 self.options.source_load
156 }
157
158 pub fn set_source_load_options(&mut self, options: SourceLoadOptions) {
160 self.options.source_load = options;
161 self.cached_langspec = None;
162 }
163
164 #[must_use]
166 pub fn langspec_script_name(&self) -> &str {
167 &self.options.langspec_script_name
168 }
169
170 pub fn set_langspec_script_name(&mut self, script_name: impl Into<String>) {
172 self.options.langspec_script_name = script_name.into();
173 self.cached_langspec = None;
174 }
175
176 pub fn compile_script_name(
183 &mut self,
184 script_name: &str,
185 ) -> Result<CompileArtifacts, CompilerSessionError> {
186 let prepared = self.prepare_script_name(script_name)?;
187 self.compile_prepared(&prepared)
188 .map_err(CompilerSessionError::from)
189 }
190
191 pub fn render_graphviz_for_script_name(
198 &mut self,
199 script_name: &str,
200 ) -> Result<String, CompilerSessionError> {
201 let prepared = self.prepare_script_name(script_name)?;
202 Ok(render_script_graphviz(
203 &prepared.script,
204 Some(&prepared.bundle.source_map),
205 ))
206 }
207
208 fn ensure_langspec_loaded(&mut self) -> Result<&LangSpec, CompilerSessionError> {
209 if self.cached_langspec.is_none() {
210 let langspec = load_langspec(
211 self.resolver,
212 &self.options.langspec_script_name,
213 self.options.source_load,
214 )?;
215 self.cached_langspec = Some(langspec);
216 }
217 self.cached_langspec.as_ref().ok_or_else(|| {
218 CompilerSessionError::Source(SourceError::resolver(
219 "failed to cache langspec after successful load",
220 ))
221 })
222 }
223
224 pub(crate) fn prepare_script_name(
225 &mut self,
226 script_name: &str,
227 ) -> Result<PreparedScript, CompilerSessionError> {
228 let langspec = self.ensure_langspec_loaded()?.clone();
229 let bundle = load_source_bundle(self.resolver, script_name, self.options.source_load)?;
230 let script = parse_source_bundle(&bundle, Some(&langspec)).map_err(|error| {
231 CompilerSessionError::Compile(CompileError::Codegen(CodegenError {
232 span: None,
233 message: ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("failed to parse source bundle during compile: {0}",
error))
})format!("failed to parse source bundle during compile: {error}"),
234 }))
235 })?;
236 Ok(PreparedScript {
237 langspec,
238 bundle,
239 script,
240 })
241 }
242
243 pub(crate) fn compile_prepared(
244 &self,
245 prepared: &PreparedScript,
246 ) -> Result<CompileArtifacts, CompileError> {
247 if self.options.emit_debug {
248 compile_script_with_source_map(
249 &prepared.script,
250 &prepared.bundle.source_map,
251 prepared.bundle.root_id,
252 Some(&prepared.langspec),
253 self.options.compile,
254 )
255 } else {
256 compile_script(
257 &prepared.script,
258 Some(&prepared.langspec),
259 self.options.compile,
260 )
261 }
262 }
263}
264
265#[cfg(test)]
266mod tests {
267 use super::CompilerSession;
268 use crate::{InMemoryScriptResolver, OptimizationLevel};
269
270 #[test]
271 fn compiler_session_reuses_langspec_and_toggles_debug_output()
272 -> Result<(), Box<dyn std::error::Error>> {
273 let mut resolver = InMemoryScriptResolver::new();
274 resolver.insert_source("nwscript", "void PrintInteger(int n);");
275 resolver.insert_source("main", "void main() { PrintInteger(42); }");
276
277 let mut session = CompilerSession::new(&resolver);
278 let first = session.compile_script_name("main")?;
279 assert!(!first.ncs.is_empty());
280 assert!(first.ndb.is_some());
281
282 session.set_generate_debugger_output(false);
283 let second = session.compile_script_name("main")?;
284 assert!(!second.ncs.is_empty());
285 assert!(second.ndb.is_none());
286 Ok(())
287 }
288
289 #[test]
290 fn compiler_session_updates_optimization_without_recreation()
291 -> Result<(), Box<dyn std::error::Error>> {
292 let mut resolver = InMemoryScriptResolver::new();
293 resolver.insert_source("nwscript", "void PrintInteger(int n);");
294 resolver.insert_source("main", "void main() { PrintInteger(42); }");
295
296 let mut session = CompilerSession::new(&resolver);
297 session.set_optimization_level(OptimizationLevel::O1);
298 let artifacts = session.compile_script_name("main")?;
299 assert!(!artifacts.ncs.is_empty());
300 assert!(artifacts.ndb.is_none());
301 Ok(())
302 }
303}