galvan_transpiler/
lib.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::iter;
4
5use convert_case::{Case, Casing};
6use derive_more::{Deref, Display, From};
7use itertools::Itertools;
8use thiserror::Error;
9
10use galvan_ast::*;
11use galvan_files::{FileError, Source};
12use galvan_into_ast::{AstError, SegmentAst, SourceIntoAst};
13use galvan_resolver::{LookupError, Scope};
14
15use builtins::builtin_fns;
16
17static SUPPRESS_WARNINGS: &str = "#![allow(warnings, unused)]";
18
19// TODO: Maybe use something like https://crates.io/crates/ruast to generate the Rust code in a more reliable way
20
21/// Name of the generated rust module that exports all public items from all galvan files in this crate
22#[macro_export]
23macro_rules! galvan_module {
24    () => {
25        "galvan_module"
26    };
27    ($ext:literal) => {
28        concat!("galvan_module.", $ext)
29    };
30}
31
32mod builtins;
33mod error;
34#[cfg(feature = "exec")]
35pub mod exec;
36
37mod cast;
38mod context;
39mod mapping;
40mod sanitize;
41
42pub use error::{Diagnostic, DiagnosticSeverity, ErrorCollector, Span, TranspilerError};
43
44#[derive(Debug, Error)]
45pub enum TranspileError {
46    #[error(transparent)]
47    Ast(#[from] AstError),
48    #[error(transparent)]
49    Lookup(#[from] LookupError),
50    #[error(transparent)]
51    File(#[from] FileError),
52}
53
54fn transpile_sources(sources: Vec<Source>) -> Result<Vec<TranspileOutput>, TranspileError> {
55    let asts = sources
56        .into_iter()
57        .map(|s| s.try_into_ast())
58        .collect::<Result<Vec<_>, _>>()?;
59
60    transpile_asts(asts)
61}
62
63fn transpile_asts(asts: Vec<Ast>) -> Result<Vec<TranspileOutput>, TranspileError> {
64    let segmented = asts.segmented()?;
65    let builtins = builtins();
66    let predefined = predefined_from(&builtins, builtin_fns());
67    let lookup = Context::new(builtins).with(&predefined)?.with(&segmented)?;
68    let mut scope = Scope::default();
69    scope.set_lookup(lookup.lookup.clone());
70
71    transpile_segmented(&segmented, &lookup, &mut scope)
72}
73
74struct TypeFileContent<'a> {
75    pub ty: &'a TypeDecl,
76    pub fns: Vec<&'a FnDecl>,
77}
78
79struct ExtensionFileContent<'a> {
80    pub elem: &'a TypeElement,
81    pub fns: Vec<&'a FnDecl>,
82}
83
84fn transpile_segmented(
85    segmented: &SegmentedAsts,
86    ctx: &Context,
87    scope: &mut Scope,
88) -> Result<Vec<TranspileOutput>, TranspileError> {
89    let mut main_errors = ErrorCollector::new();
90    #[derive(Hash, PartialEq, Eq, Deref, From, Display)]
91    struct ModuleName(Box<str>);
92    fn module_name(ident: &TypeIdent) -> ModuleName {
93        ident.as_str().to_case(Case::Snake).into_boxed_str().into()
94    }
95
96    fn extension_module_name(ty: &TypeElement) -> ModuleName {
97        extension_name(ty)
98            .to_ascii_lowercase()
99            .into_boxed_str()
100            .into()
101    }
102
103    fn add_extension_module<'a>(
104        extensions: &mut HashMap<ModuleName, ExtensionFileContent<'a>>,
105        func: &'a ToplevelItem<FnDecl>,
106        elem: &'a TypeElement,
107    ) {
108        let content = extensions
109            .entry(extension_module_name(elem))
110            .or_insert_with(|| ExtensionFileContent {
111                elem,
112                fns: Vec::new(),
113            });
114        content.fns.push(&func.item);
115    }
116
117    let mut type_files: HashMap<ModuleName, TypeFileContent> = HashMap::new();
118
119    for ty in &segmented.types {
120        if let Some(duplicate) = type_files.insert(
121            module_name(ty.ident()),
122            TypeFileContent {
123                ty,
124                fns: Vec::new(),
125            },
126        ) {
127            panic!(
128                "File collision for types: {} and {}",
129                ty.item.ident(),
130                duplicate.ty.ident()
131            );
132        }
133    }
134
135    let mut toplevel_functions = Vec::new();
136    let mut extensions: HashMap<ModuleName, ExtensionFileContent> = HashMap::new();
137    for func in &segmented.functions {
138        if let Some(receiver) = func.signature.receiver() {
139            let elem = &receiver.param_type;
140            let TypeElement::Plain(ty) = elem else {
141                add_extension_module(&mut extensions, func, elem);
142                continue;
143            };
144            match type_files.get_mut(&module_name(&ty.ident)) {
145                Some(content) => content.fns.push(&func.item),
146                None => {
147                    add_extension_module(&mut extensions, func, elem);
148                }
149            }
150        } else {
151            toplevel_functions.push(func);
152        }
153    }
154
155    let type_files = type_files;
156    let toplevel_functions = toplevel_functions
157        .iter()
158        .map(|func| func.transpile(ctx, scope, &mut main_errors))
159        .collect::<Vec<_>>()
160        .join("\n\n");
161    let toplevel_functions = toplevel_functions.trim();
162
163    let tests = transpile_tests(segmented, ctx, scope, &mut main_errors);
164
165    let modules = type_files
166        .keys()
167        .chain(extensions.keys())
168        .map(|id| sanitize_name(id))
169        .map(|mod_name| format!("mod {mod_name};\npub use self::{mod_name}::*;"))
170        .collect::<Vec<_>>()
171        .join("\n");
172    let modules = modules.trim();
173
174    let main = segmented
175        .main
176        .as_ref()
177        .map(|main| {
178            let main_errors_ref = &mut main_errors;
179            transpile!(
180                ctx,
181                scope,
182                main_errors_ref,
183                "pub(crate) fn __main__() {}",
184                main.body
185            )
186        })
187        .unwrap_or_default();
188
189    let lib = TranspileOutput {
190        file_name: galvan_module!("rs").into(),
191        content: format!(
192            "extern crate galvan; #[allow(unused_imports)] pub(crate) use ::galvan::std::*;\n pub(crate) mod {} {{\n{}\nuse crate::*;\n{}\n}}",
193            galvan_module!(),
194            SUPPRESS_WARNINGS,
195            [modules, toplevel_functions, &main, &tests].join("\n\n")
196        )
197        .into(),
198    };
199
200    let type_files = type_files
201        .iter()
202        .map(|(k, v)| TranspileOutput {
203            file_name: format!("{k}.rs").into(),
204            content: [
205                "use crate::*;",
206                &v.ty.transpile(ctx, scope, &mut main_errors),
207                &transpile_member_functions(v.ty.ident(), &v.fns, ctx, scope, &mut main_errors),
208            ]
209            .join("\n\n")
210            .trim()
211            .into(),
212        })
213        .collect_vec();
214
215    let extension_files = extensions
216        .iter()
217        .map(|(k, v)| TranspileOutput {
218            file_name: format!("{k}.rs").into(),
219            content: [
220                "use crate::*;",
221                &transpile_extension_functions(v.elem, &v.fns, ctx, scope, &mut main_errors),
222            ]
223            .join("\n\n")
224            .trim()
225            .into(),
226        })
227        .collect_vec();
228
229    // Output any collected warnings
230    for diagnostic in main_errors.diagnostics() {
231        match diagnostic.severity {
232            DiagnosticSeverity::Error => {
233                println!("cargo::error={}", diagnostic.message);
234                std::process::exit(1);
235            }
236            DiagnosticSeverity::Warning => {
237                println!("cargo::warning={}", diagnostic.message);
238            }
239            _ => {}
240        }
241    }
242
243    Ok(type_files
244        .into_iter()
245        .chain(extension_files.into_iter())
246        .chain(iter::once(lib))
247        .collect())
248}
249
250fn transpile_tests(
251    segmented_asts: &SegmentedAsts,
252    ctx: &Context,
253    scope: &mut Scope,
254    errors: &mut ErrorCollector,
255) -> String {
256    fn test_name<'a>(desc: &Option<StringLiteral>) -> Cow<'a, str> {
257        desc.as_ref().map_or("test".into(), |desc| {
258            let snake = desc
259                .as_str()
260                .trim_matches('\"')
261                .to_case(Case::Snake)
262                .replace(|c: char| !c.is_ascii_alphanumeric(), "_");
263
264            let snake = if snake.starts_with(|c: char| c.is_ascii_digit()) {
265                format!("test_{}", snake)
266            } else {
267                snake
268            };
269
270            if snake.ends_with(|c: char| c.is_ascii_digit()) {
271                format!("{}_", snake).into()
272            } else {
273                snake.into()
274            }
275        })
276    }
277
278    let mut by_name: HashMap<Cow<'_, str>, Vec<&TestDecl>> = HashMap::new();
279    for test in &segmented_asts.tests {
280        by_name
281            .entry(test_name(&test.item.name))
282            .or_default()
283            .push(&test.item);
284    }
285
286    let resolved_tests = by_name
287        .iter()
288        .flat_map(|(name, tests)| {
289            if tests.len() == 1 {
290                vec![(name.clone(), tests[0])]
291            } else {
292                tests
293                    .iter()
294                    .enumerate()
295                    .map(|(i, &test)| (Cow::from(format!("{}_{}", name, i)), test))
296                    .collect_vec()
297            }
298        })
299        .collect_vec();
300
301    if resolved_tests.is_empty() {
302        return "".into();
303    }
304
305    let test_mod = "#[cfg(test)]\nmod tests {\nuse crate::*;\n".to_owned()
306        + resolved_tests
307            .iter()
308            .map(|t| t.transpile(ctx, scope, errors))
309            .collect::<Vec<_>>()
310            .join("\n\n")
311            .as_str()
312        + "\n}";
313
314    test_mod
315}
316
317fn transpile_member_functions(
318    ty: &TypeIdent,
319    fns: &[&FnDecl],
320    ctx: &Context,
321    scope: &mut Scope,
322    errors: &mut ErrorCollector,
323) -> String {
324    if fns.is_empty() {
325        return "".into();
326    }
327
328    let transpiled_fns = fns
329        .iter()
330        .map(|f| f.transpile(ctx, scope, errors))
331        .collect::<Vec<_>>()
332        .join("\n\n");
333    transpile!(ctx, scope, errors, "impl {} {{\n{transpiled_fns}\n}}", ty)
334}
335
336fn transpile_extension_functions(
337    ty: &TypeElement,
338    fns: &[&FnDecl],
339    ctx: &Context,
340    scope: &mut Scope,
341    errors: &mut ErrorCollector,
342) -> String {
343    debug_assert_ne!(fns.len(), 0, "Extension functions should not be empty");
344    if fns
345        .iter()
346        .find(|f| f.signature.visibility.kind != VisibilityKind::Inherited)
347        .is_some()
348    {
349        // TODO: Add proper error handling for invalid member function visibility
350        return String::new();
351    }
352
353    let trait_name = extension_name(&ty);
354    let fn_signatures = fns
355        .iter()
356        .map(|f| FnSignature {
357            visibility: Visibility::private(),
358            ..f.signature.clone()
359        })
360        .map(|s| s.transpile(ctx, scope, errors))
361        .collect::<Vec<_>>()
362        .join(";\n")
363        + ";";
364    let transpiled_fns = fns
365        .iter()
366        .map(|f| f.transpile(ctx, scope, errors))
367        .map(|s| s.strip_prefix("pub(crate) ").unwrap().to_owned())
368        .collect::<Vec<_>>()
369        .join("\n\n");
370
371    transpile! {ctx, scope, errors,
372        "
373        pub trait {trait_name} {{
374            {fn_signatures}
375        }}
376
377        impl {trait_name} for {} {{
378            {transpiled_fns}
379        }}
380        ", ty
381    }
382}
383
384fn extension_name(ty: &TypeElement) -> String {
385    fn escaped_name(ty: &TypeElement) -> String {
386        match ty {
387            TypeElement::Plain(ty) => ty.ident.as_str().to_case(Case::UpperCamel),
388            TypeElement::Tuple(ty) => format!(
389                "Tuple_{}",
390                ty.elements
391                    .iter()
392                    .map(escaped_name)
393                    .collect::<Vec<_>>()
394                    .join("_")
395            ),
396            TypeElement::Result(ty) => format!(
397                "Result_{}_{}",
398                escaped_name(&ty.success),
399                ty.error.as_ref().map_or("".into(), escaped_name)
400            ),
401            TypeElement::Optional(ty) => format!("Option_{}_Ext", escaped_name(&ty.inner)),
402            TypeElement::Dictionary(ty) => {
403                format!("Dict_{}_{}", escaped_name(&ty.key), escaped_name(&ty.value))
404            }
405            TypeElement::OrderedDictionary(ty) => format!(
406                "OrderedDict_{}_{}",
407                escaped_name(&ty.key),
408                escaped_name(&ty.value)
409            ),
410            TypeElement::Array(ty) => format!("Array_{}", escaped_name(&ty.elements)),
411            TypeElement::Set(ty) => format!("Set_{}", escaped_name(&ty.elements)),
412            TypeElement::Generic(_ty) => todo!("Generics are not supported yet!"),
413            TypeElement::Void(_) => format!("Void"),
414            TypeElement::Infer(_) => format!("Infer"),
415            TypeElement::Never(_) => format!("Never"),
416        }
417    }
418
419    escaped_name(ty) + "_Ext"
420}
421
422pub struct TranspileOutput {
423    pub file_name: Box<str>,
424    pub content: Box<str>,
425}
426
427pub struct TranspileErrors<'t> {
428    pub source: Source,
429    pub errors: &'t [TranspileError],
430}
431
432impl TranspileErrors<'_> {
433    pub fn is_empty(&self) -> bool {
434        self.errors.is_empty()
435    }
436}
437
438pub fn transpile(sources: Vec<Source>) -> Result<Vec<TranspileOutput>, TranspileError> {
439    transpile_sources(sources)
440}
441
442mod transpile_item;
443mod type_inference;
444
445trait Transpile {
446    fn transpile(&self, ctx: &Context, scope: &mut Scope, errors: &mut ErrorCollector) -> String;
447}
448
449trait Punctuated {
450    fn punctuation() -> &'static str;
451}
452
453mod macros {
454    macro_rules! transpile {
455        ($ctx:ident, $scope:ident, $errors:ident, $string:expr, $($items:expr),*$(,)?) => {
456            format!($string, $(($items).transpile($ctx, $scope, $errors)),*)
457        };
458
459        // Temporary backward compatibility - creates a local temp ErrorCollector
460        ($ctx:ident, $scope:ident, $string:expr, $($items:expr),*$(,)?) => {
461            {
462                let mut _temp_errors = crate::ErrorCollector::new();
463                format!($string, $(($items).transpile($ctx, $scope, &mut _temp_errors)),*)
464            }
465        };
466    }
467
468    macro_rules! impl_transpile {
469        ($ty:ty, $string:expr, $($field:tt),*$(,)?) => {
470            impl crate::Transpile for $ty {
471                fn transpile(&self, _ctx: &crate::Context, _scope: &mut crate::Scope, _errors: &mut crate::ErrorCollector) -> String {
472                    crate::macros::transpile!(_ctx, _scope, _errors, $string, $(self.$field),*)
473                }
474            }
475        };
476
477        // Temporary backward compatibility
478        ($ty:ty, $old_signature:expr, $string:expr, $($field:tt),*$(,)?) => {
479            impl crate::Transpile for $ty {
480                fn transpile(&self, _ctx: &crate::Context, _scope: &mut crate::Scope, _errors: &mut crate::ErrorCollector) -> String {
481                    crate::macros::transpile!(_ctx, _scope, _errors, $string, $(self.$field),*)
482                }
483            }
484        };
485    }
486
487    #[allow(unused_macros)]
488    macro_rules! impl_transpile_fn {
489        ($ty:ty, $string:expr, $($fun:ident),*$(,)?) => {
490            impl crate::Transpile for $ty {
491                fn transpile(&self, ctx: &crate::Context, scope: &mut crate::Scope) -> String {
492                    crate::macros::transpile!(ctx, scope, $string, $(self.$fun()),*)
493                }
494            }
495        };
496    }
497
498    macro_rules! impl_transpile_match {
499        ($ty:ty, $($case:pat_param => ($($args:expr),+)),+$(,)?) => {
500            impl crate::Transpile for $ty {
501                #[deny(bindings_with_variant_name)]
502                #[deny(unreachable_patterns)]
503                #[deny(non_snake_case)]
504                fn transpile(&self, ctx: &crate::Context, scope: &mut crate::Scope, errors: &mut crate::ErrorCollector) -> String {
505                    use $ty::*;
506                    match self {
507                        $($case => crate::macros::transpile!(ctx, scope, errors, $($args),+),)+
508                    }
509                }
510            }
511        };
512    }
513
514    macro_rules! impl_transpile_variants {
515        ($ty:ty; $($case:ident$(,)?)+) => {
516            impl crate::Transpile for $ty {
517                #[deny(bindings_with_variant_name)]
518                #[deny(unreachable_patterns)]
519                #[deny(non_snake_case)]
520                fn transpile(&self, ctx: &crate::Context, scope: &mut crate::Scope, errors: &mut crate::ErrorCollector) -> String {
521                    use $ty::*;
522                    match self {
523                        $($case(inner) => inner.transpile(ctx, scope, errors),)+
524                    }
525                }
526            }
527        };
528    }
529
530    macro_rules! punct {
531        ($string:expr, $($ty:ty),+) => {
532            $(impl Punctuated for $ty {
533                fn punctuation() -> &'static str {
534                    $string
535                }
536            })+
537            $(impl Punctuated for &$ty {
538                fn punctuation() -> &'static str {
539                    $string
540                }
541            })+
542        };
543    }
544
545    pub(crate) use {
546        impl_transpile, impl_transpile_match, impl_transpile_variants, punct, transpile,
547    };
548}
549
550use crate::builtins::builtins;
551use crate::context::{predefined_from, Context};
552use crate::macros::transpile;
553use crate::sanitize::sanitize_name;
554use macros::punct;
555
556punct!(
557    ", ",
558    TypeElement,
559    TupleTypeMember,
560    Param,
561    ConstructorCallArg,
562    ClosureParameter,
563    DictLiteralElement
564);
565punct!(",\n", StructTypeMember, EnumTypeMember);
566punct!("\n\n", RootItem, FnDecl);
567punct!(";\n", Statement);
568
569impl<T> Transpile for Vec<T>
570where
571    T: Transpile + Punctuated,
572{
573    fn transpile(&self, ctx: &Context, scope: &mut Scope, errors: &mut ErrorCollector) -> String {
574        self.as_slice().transpile(ctx, scope, errors)
575    }
576}
577
578impl<T> Transpile for [T]
579where
580    T: Transpile + Punctuated,
581{
582    fn transpile(&self, ctx: &Context, scope: &mut Scope, errors: &mut ErrorCollector) -> String {
583        let punct = T::punctuation();
584        self.iter()
585            .map(|e| e.transpile(ctx, scope, errors))
586            .reduce(|acc, e| format!("{acc}{punct}{e}"))
587            .unwrap_or_else(String::new)
588    }
589}
590
591impl<T> Transpile for Option<Vec<T>>
592where
593    T: Transpile + Punctuated,
594{
595    fn transpile(&self, ctx: &Context, scope: &mut Scope, errors: &mut ErrorCollector) -> String {
596        self.as_ref()
597            .map_or_else(String::new, |v| v.transpile(ctx, scope, errors))
598    }
599}
600
601impl Transpile for &str {
602    fn transpile(
603        &self,
604        _ctx: &Context,
605        _scope: &mut Scope,
606        _errors: &mut ErrorCollector,
607    ) -> String {
608        self.to_string()
609    }
610}
611
612impl Transpile for String {
613    fn transpile(
614        &self,
615        _ctx: &Context,
616        _scope: &mut Scope,
617        _errors: &mut ErrorCollector,
618    ) -> String {
619        self.to_owned()
620    }
621}
622
623impl<T> Transpile for Box<T>
624where
625    T: Transpile,
626{
627    fn transpile(&self, ctx: &Context, scope: &mut Scope, errors: &mut ErrorCollector) -> String {
628        self.as_ref().transpile(ctx, scope, errors)
629    }
630}