1
//! Types and procedures that represents a command line argument,
2
//! or collections of command line arguments
3

            
4
use std::borrow::Cow;
5
use std::ops::Deref;
6
use std::path::Path;
7
use std::{borrow::Borrow, ffi::OsStr, path::PathBuf};
8

            
9
use serde::{Deserialize, Serialize};
10

            
11
use crate::project_model::compiler::LanguageLevel;
12

            
13
/// Wrapper type for represent and storing a command line argument
14
749
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
15
296
pub struct Argument<'a>(Cow<'a, str>);
16

            
17
impl<'a> Argument<'a> {
18
    #[inline(always)]
19
    pub fn value(&self) -> &Cow<'a, str> {
20
        &self.0
21
    }
22

            
23
    #[inline(always)]
24
    pub fn is_empty(&self) -> bool {
25
        self.0.is_empty()
26
    }
27
}
28

            
29
impl<'a> From<&'a str> for Argument<'a> {
30
32
    fn from(value: &'a str) -> Self {
31
32
        Self(Cow::Borrowed(value))
32
32
    }
33
}
34

            
35
impl<'a> From<&'a String> for Argument<'a> {
36
    fn from(value: &'a String) -> Self {
37
        Self(Cow::Borrowed(value))
38
    }
39
}
40

            
41
impl<'a> From<Cow<'a, str>> for Argument<'a> {
42
5
    fn from(value: Cow<'a, str>) -> Self {
43
5
        Self(value)
44
5
    }
45
}
46

            
47
impl<'a> From<&Cow<'a, str>> for Argument<'a> {
48
1
    fn from(value: &Cow<'a, str>) -> Self {
49
1
        Self(value.clone())
50
1
    }
51
}
52

            
53
impl<'a> From<String> for Argument<'a> {
54
49
    fn from(value: String) -> Argument<'a> {
55
49
        Self(Cow::Owned(value))
56
49
    }
57
}
58

            
59
impl<'a> From<&'a Path> for Argument<'a> {
60
    fn from(value: &'a Path) -> Self {
61
        Self::from(value.to_string_lossy())
62
    }
63
}
64

            
65
impl<'a> From<PathBuf> for Argument<'a> {
66
33
    fn from(value: PathBuf) -> Self {
67
33
        Self::from(format!("{}", value.display()))
68
33
    }
69
}
70

            
71
impl<'a> From<&PathBuf> for Argument<'a> {
72
10
    fn from(value: &PathBuf) -> Self {
73
10
        Self::from(format!("{}", value.display()))
74
10
    }
75
}
76

            
77
impl<'a> From<LanguageLevel> for Argument<'a> {
78
    fn from(value: LanguageLevel) -> Self {
79
        Self::from(value.as_ref().to_string())
80
    }
81
}
82

            
83
impl<'a> Borrow<str> for Argument<'a> {
84
    fn borrow(&self) -> &str {
85
        &self.0
86
    }
87
}
88

            
89
impl<'a> AsRef<OsStr> for Argument<'a> {
90
131
    fn as_ref(&self) -> &OsStr {
91
131
        OsStr::new(self.0.as_ref())
92
131
    }
93
}
94

            
95
impl<'a> AsRef<str> for Argument<'a> {
96
    fn as_ref(&self) -> &str {
97
        &self.0
98
    }
99
}
100

            
101
impl<'a> core::fmt::Display for Argument<'a> {
102
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103
        write!(f, "{}", self.0)
104
    }
105
}
106

            
107
/// Strong type for represent a linear collection of [`Argument`]
108
121
#[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)]
109
48
pub struct Arguments<'a>(Vec<Argument<'a>>);
110

            
111
impl<'a> core::fmt::Display for Arguments<'a> {
112
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113
        let len = self.0.len();
114
        self.0.iter().enumerate().try_for_each(|(i, arg)| {
115
            if i == len - 1 {
116
                write!(f, "{}", arg)
117
            } else {
118
                write!(f, "{} ", arg)
119
            }
120
        })
121
    }
122
}
123

            
124
impl<'a> Arguments<'a> {
125
    /// Wraps an existing [`std::vec::Vec`] of [`Argument`]
126
2
    pub fn from_vec(vec: Vec<Argument<'a>>) -> Self {
127
2
        Self(vec)
128
2
    }
129

            
130
    /// Returns a new collection of [`Argument`] with the specified capacity
131
    pub fn with_capacity(cap: usize) -> Self {
132
        Self(Vec::with_capacity(cap))
133
    }
134

            
135
    /// Creates and stores a new [`Argument`] to the end of this collection
136
    /// from any type *T* that can be coerced into an [`Argument`] type
137
49
    pub fn push<T>(&mut self, val: T)
138
    where
139
        T: Into<Argument<'a>>,
140
    {
141
49
        self.0.push(val.into())
142
49
    }
143

            
144
    /// Given an optional, adds the inner value if there's Some(<[Argument]>)
145
1
    pub fn push_opt<T: Into<Argument<'a>>>(&mut self, arg: Option<T>) {
146
1
        if let Some(val) = arg {
147
1
            self.0.push(val.into())
148
        }
149
1
    }
150

            
151
    /// Extends the underlying collection from an Iterator of [`Argument`]
152
3
    pub fn extend(&mut self, iter: impl IntoIterator<Item = Argument<'a>>) {
153
3
        self.0.extend(iter);
154
3
    }
155

            
156
    /// Extends the underlying collection given a slice of [`Argument`]
157
2
    pub fn extend_from_slice(&mut self, slice: &'a [Argument]) {
158
2
        self.0.extend_from_slice(slice);
159
2
    }
160

            
161
    /// Extends the underlying collection given a slice of any type that is convertible to [`Argument`]
162
    /// and implements [`Clone`]
163
    pub fn extend_from_to_argument_slice<F>(&mut self, slice: &'a [F])
164
    where
165
        F: Into<Argument<'a>> + Clone,
166
    {
167
        self.0.extend(
168
            slice
169
                .iter()
170
                .map(|arg| <F as Into<Argument>>::into(arg.clone())),
171
        );
172
    }
173

            
174
8
    pub fn as_slice(&self) -> &[Argument] {
175
8
        &self.0
176
8
    }
177

            
178
    /// Clears the contained values of the wrapped [`std::vec::Vec`]
179
    pub fn clear(&mut self) {
180
        self.0.clear()
181
    }
182
}
183

            
184
impl<'a> Deref for Arguments<'a> {
185
    type Target = [Argument<'a>];
186

            
187
98
    fn deref(&self) -> &Self::Target {
188
98
        &self.0
189
98
    }
190
}
191

            
192
impl<'a> IntoIterator for Arguments<'a> {
193
    type Item = Argument<'a>;
194
    type IntoIter = std::vec::IntoIter<Self::Item>;
195

            
196
3
    fn into_iter(self) -> Self::IntoIter {
197
3
        self.0.into_iter()
198
3
    }
199
}
200

            
201
impl<'a> IntoIterator for &Arguments<'a> {
202
    type Item = Argument<'a>;
203
    type IntoIter = std::vec::IntoIter<Self::Item>;
204

            
205
15
    fn into_iter(self) -> Self::IntoIter {
206
15
        self.0.clone().into_iter()
207
15
    }
208
}
209

            
210
impl<'a> FromIterator<Argument<'a>> for Arguments<'a> {
211
    fn from_iter<I: IntoIterator<Item = Argument<'a>>>(iter: I) -> Self {
212
        let mut vec = Vec::new();
213
        for item in iter {
214
            vec.push(item);
215
        }
216
        Arguments(vec)
217
    }
218
}
219

            
220
impl<'a> FromIterator<&'a Argument<'a>> for Arguments<'a> {
221
19
    fn from_iter<I: IntoIterator<Item = &'a Argument<'a>>>(iter: I) -> Arguments<'a> {
222
19
        let mut vec = Vec::new();
223
164
        for item in iter {
224
145
            vec.push(item.clone());
225
        }
226
19
        Arguments(vec)
227
19
    }
228
}
229

            
230
/// Isolated module to storing custom procedures to easily create and add new command line arguments
231
/// or flags specific to Clang, that otherwise, will be bloating the main procedures with a lot
232
/// of cognitive complexity
233
pub mod clang_args {
234
    use std::path::Path;
235

            
236
    use crate::{
237
        cache::ZorkCache,
238
        domain::{
239
            commands::command_lines::SourceCommandLine, translation_unit::TranslationUnitStatus,
240
        },
241
        project_model::compiler::{CppCompiler, StdLibMode},
242
        utils::constants,
243
    };
244

            
245
    use super::*;
246

            
247
    /// Generates a module mapping command line argument for Clang.
248
    ///
249
    // The Windows variant is a Zork++ feature to allow the users to write `import std;`
250
    // under -std=c++20 with clang linking against GCC with
251
    // some MinGW installation or similar
252
1
    pub(crate) fn implicit_module_map<'a>(out_dir: &Path) -> Cow<'a, str> {
253
1
        if std::env::consts::OS.eq("windows") {
254
            Cow::Owned(format!(
255
                "-fmodule-map-file={}",
256
                out_dir
257
                    .join(constants::ZORK)
258
                    .join(constants::dir_names::INTRINSICS)
259
                    .join("zork.modulemap")
260
                    .display()
261
            ))
262
        } else {
263
1
            Cow::Borrowed("-fimplicit-module-maps")
264
        }
265
1
    }
266

            
267
1
    pub(crate) fn add_prebuilt_module_path(out_dir: &Path) -> String {
268
1
        format!(
269
            "-fprebuilt-module-path={}",
270
1
            out_dir
271
                .join(constants::compilers::CLANG)
272
                .join(constants::dir_names::MODULES)
273
                .join(constants::dir_names::INTERFACES)
274
                .display()
275
        )
276
1
    }
277

            
278
5
    pub(crate) fn add_direct_module_interfaces_dependencies<'a>(
279
        dependencies: &[Cow<str>],
280
        out_dir: &Path,
281
        clang_major_version: i32,
282
    ) -> Arguments<'a> {
283
5
        let compiler = CppCompiler::CLANG;
284

            
285
5
        let mut args = Arguments::default();
286
9
        dependencies.iter().for_each(|ifc_dep| {
287
12
            let mut module_file_path = out_dir
288
4
                .join(compiler.as_ref())
289
                .join(constants::dir_names::MODULES)
290
                .join(constants::dir_names::INTERFACES)
291
4
                .join::<&str>(ifc_dep)
292
                .display()
293
4
                .to_string();
294
4
            module_file_path.push('.');
295
4
            module_file_path.push_str(compiler.get_typical_bmi_extension());
296

            
297
4
            let argument = if clang_major_version > 15 {
298
3
                format!("-fmodule-file={}={}", ifc_dep, module_file_path)
299
            } else {
300
1
                format!("-fmodule-file={}", module_file_path)
301
            };
302

            
303
4
            args.push(argument);
304
4
        });
305

            
306
5
        args
307
5
    }
308

            
309
    pub(crate) fn generate_std_cmd<'a>(
310
        cache: &mut ZorkCache<'a>,
311
        stdlib_mode: StdLibMode,
312
    ) -> SourceCommandLine<'a> {
313
        let clang_metadata = &cache.compilers_metadata.clang;
314

            
315
        let mut args = Arguments::default();
316
        args.push("-Wno-reserved-module-identifier");
317
        args.push("--precompile");
318

            
319
        let (filename, byproduct) = match stdlib_mode {
320
            StdLibMode::Cpp => (String::from("std.cppm"), &clang_metadata.stdlib_pcm),
321
            StdLibMode::CCompat => {
322
                // std.compat re-exports std, to it must be explicitly referenced
323
                args.push(format!(
324
                    "-fmodule-file=std={}",
325
                    clang_metadata.stdlib_pcm.display()
326
                ));
327
                (String::from("std.compat.cppm"), &clang_metadata.ccompat_pcm)
328
            }
329
        };
330

            
331
        let input_file = clang_metadata.libcpp_path.join(&filename);
332

            
333
        // TODO: GENERAL TODO: chain for every SCL the scl.path() as the input file and byprduct as
334
        // the output, so we can avoid to held them twice, in arguments and in their respective
335
        // struct fields
336

            
337
        // The input file
338
        args.push(input_file);
339

            
340
        // The output file
341
        args.push("-o");
342
        args.push(byproduct);
343

            
344
        SourceCommandLine {
345
            directory: clang_metadata.libcpp_path.clone(),
346
            filename,
347
            args,
348
            status: TranslationUnitStatus::PendingToBuild,
349
            byproduct: byproduct.into(),
350
        }
351
    }
352

            
353
    #[cfg(test)]
354
    mod clang_args_tests {
355
        use crate::domain::commands::arguments::Arguments;
356
        use std::{borrow::Cow, path::Path};
357

            
358
        #[test] // fixed since v0.11.2
359
2
        fn test_clang_add_direct_module_ifc_deps() {
360
1
            let args = super::add_direct_module_interfaces_dependencies(
361
                &[Cow::Borrowed("math.numbers")],
362
1
                Path::new("out"),
363
                19,
364
            );
365

            
366
1
            assert_eq!(
367
                args,
368
2
                Arguments::from_vec(vec![
369
1
                    "-fmodule-file=math.numbers=out/clang/modules/interfaces/math.numbers.pcm"
370
                        .into()
371
                ])
372
            );
373

            
374
1
            let args = super::add_direct_module_interfaces_dependencies(
375
                &[Cow::Borrowed("math.numbers")],
376
1
                Path::new("out"),
377
                15,
378
            );
379
1
            assert_eq!(
380
                args,
381
2
                Arguments::from_vec(vec![
382
1
                    "-fmodule-file=out/clang/modules/interfaces/math.numbers.pcm".into()
383
                ])
384
            )
385
2
        }
386
    }
387
}
388

            
389
pub mod msvc_args {
390
    use crate::cache::ZorkCache;
391
    use crate::domain::commands::command_lines::SourceCommandLine;
392
    use crate::domain::translation_unit::TranslationUnit;
393
    use crate::project_model::compiler::StdLibMode;
394

            
395
    use super::Arguments;
396

            
397
    pub(crate) fn generate_std_cmd<'a>(
398
        cache: &ZorkCache<'a>,
399
        stdlib_mode: StdLibMode,
400
    ) -> SourceCommandLine<'a> {
401
        let mut arguments = Arguments::default();
402
        let msvc = &cache.compilers_metadata.msvc;
403

            
404
        let (stdlib_sf, stdlib_bmi_path, stdlib_obj_path) = if stdlib_mode.eq(&StdLibMode::Cpp) {
405
            (
406
                &msvc.vs_stdlib_path,
407
                &msvc.stdlib_bmi_path,
408
                &msvc.stdlib_obj_path,
409
            )
410
        } else {
411
            // std.compat re-exports std
412
            arguments.push("/reference");
413
            arguments.push(cache.compilers_metadata.msvc.stdlib_bmi_path.clone());
414

            
415
            (
416
                &msvc.vs_ccompat_stdlib_path,
417
                &msvc.ccompat_stdlib_bmi_path,
418
                &msvc.ccompat_stdlib_obj_path,
419
            )
420
        };
421

            
422
        arguments.push(stdlib_sf.path());
423
        arguments.push("/ifcOutput");
424
        arguments.push(format! {
425
            "{}", stdlib_bmi_path.display()
426
        });
427
        arguments.push(format! {
428
            "/Fo{}", stdlib_obj_path.display()
429
        });
430

            
431
        SourceCommandLine::new(stdlib_sf, arguments, stdlib_obj_path.to_path_buf())
432
    }
433
}