Skip to main content

typr_core/processes/transpiling/
mod.rs

1pub mod translatable;
2
3use crate::components::context::config::Environment;
4use crate::components::context::Context;
5use crate::components::error_message::help_data::HelpData;
6use crate::components::language::format_backtick;
7use crate::components::language::function_lang::Function;
8use crate::components::language::operators::Op;
9use crate::components::language::set_related_type_if_variable;
10use crate::components::language::var::Var;
11use crate::components::language::Lang;
12use crate::components::language::ModulePosition;
13use crate::components::r#type::array_type::ArrayType;
14use crate::components::r#type::function_type::FunctionType;
15use crate::components::r#type::Type;
16use crate::processes::transpiling::translatable::Translatable;
17use crate::processes::type_checking::type_comparison::reduce_type;
18use crate::processes::type_checking::typing;
19use translatable::RTranslatable;
20
21#[cfg(not(feature = "wasm"))]
22use std::fs::File;
23#[cfg(not(feature = "wasm"))]
24use std::io::Write;
25#[cfg(not(feature = "wasm"))]
26use std::path::PathBuf;
27
28use std::cell::RefCell;
29use std::collections::HashMap;
30
31// Thread-local storage for generated files (used in WASM mode)
32thread_local! {
33    static GENERATED_FILES: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
34}
35
36/// Register a generated file (used for WASM mode to capture file outputs)
37pub fn register_generated_file(path: &str, content: &str) {
38    GENERATED_FILES.with(|files| {
39        files
40            .borrow_mut()
41            .insert(path.to_string(), content.to_string());
42    });
43}
44
45/// Get all generated files
46pub fn get_generated_files() -> HashMap<String, String> {
47    GENERATED_FILES.with(|files| files.borrow().clone())
48}
49
50/// Clear all generated files
51pub fn clear_generated_files() {
52    GENERATED_FILES.with(|files| {
53        files.borrow_mut().clear();
54    });
55}
56
57/// Write a file - in native mode writes to filesystem, in WASM mode stores in memory
58#[cfg(not(feature = "wasm"))]
59fn write_output_file(path: &str, content: &str) -> Result<(), String> {
60    use std::fs;
61
62    // Also register in memory for consistency
63    register_generated_file(path, content);
64
65    let path_buf = PathBuf::from(path);
66    if let Some(parent) = path_buf.parent() {
67        fs::create_dir_all(parent).map_err(|e| e.to_string())?;
68    }
69    let mut file = File::create(&path_buf).map_err(|e| e.to_string())?;
70    file.write_all(content.as_bytes())
71        .map_err(|e| e.to_string())?;
72    Ok(())
73}
74
75#[cfg(feature = "wasm")]
76fn write_output_file(path: &str, content: &str) -> Result<(), String> {
77    register_generated_file(path, content);
78    Ok(())
79}
80
81pub trait ToSome {
82    fn to_some(self) -> Option<Self>
83    where
84        Self: Sized;
85}
86
87impl<T: Sized> ToSome for T {
88    fn to_some(self) -> Option<Self> {
89        Some(self)
90    }
91}
92
93trait AndIf {
94    fn and_if<F>(self, condition: F) -> Option<Self>
95    where
96        F: Fn(Self) -> bool,
97        Self: Sized;
98}
99
100impl<T: Clone> AndIf for T {
101    fn and_if<F>(self, condition: F) -> Option<Self>
102    where
103        F: Fn(Self) -> bool,
104    {
105        if condition(self.clone()) {
106            Some(self)
107        } else {
108            None
109        }
110    }
111}
112
113const JS_HEADER: &str = "";
114
115fn condition_to_if(var: &Var, typ: &Type, context: &Context) -> String {
116    format!(
117        "any(class({}) == c({}))",
118        var.get_name(),
119        context.get_class(typ)
120    )
121}
122
123fn to_if_statement(
124    var: Var,
125    exp: Lang,
126    branches: &[(Type, Box<Lang>)],
127    context: &Context,
128) -> String {
129    let res = branches
130        .iter()
131        .map(|(typ, body)| (condition_to_if(&var, typ, context), body))
132        .enumerate()
133        .map(|(id, (cond, body))| {
134            if id == 0 {
135                format!("if ({}) {{ \n {} \n }}", cond, body.to_r(context).0)
136            } else {
137                format!("else if ({}) {{ \n {} \n }}", cond, body.to_r(context).0)
138            }
139        })
140        .collect::<Vec<_>>()
141        .join(" ");
142    format!(
143        "{{\n {} <- {} \n {}\n}}",
144        var.get_name(),
145        exp.to_r(context).0,
146        res
147    )
148}
149
150impl RTranslatable<(String, Context)> for Lang {
151    fn to_r(&self, cont: &Context) -> (String, Context) {
152        let result = match self {
153            Lang::Bool(b, _) => {
154                let (typ, _, _) = typing(cont, self).to_tuple();
155                let anotation = cont.get_type_anotation(&typ);
156                (
157                    format!("{} |> {}", b.to_string().to_uppercase(), anotation),
158                    cont.clone(),
159                )
160            }
161            Lang::Number(n, _) => {
162                let (typ, _, _) = typing(cont, self).to_tuple();
163                let anotation = cont.get_type_anotation(&typ);
164                (format!("{} |> {}", n, anotation), cont.clone())
165            }
166            Lang::Integer(i, _) => {
167                let (typ, _, _) = typing(cont, self).to_tuple();
168                let anotation = cont.get_type_anotation(&typ);
169                (format!("{}L |> {}", i, anotation), cont.clone())
170            }
171            Lang::Char(s, _) => {
172                let (typ, _, _) = typing(cont, self).to_tuple();
173                let anotation = cont.get_type_anotation(&typ);
174                (format!("'{}' |> {}", s, anotation), cont.clone())
175            }
176            Lang::Operator(Op::Dot(_), e1, e2, _) | Lang::Operator(Op::Pipe(_), e1, e2, _) => {
177                let e1 = (**e1).clone();
178                let e2 = (**e2).clone();
179                match e2.clone() {
180                    Lang::Variable(_, _, _, _) => Translatable::from(cont.clone())
181                        .to_r(&e2)
182                        .add("[['")
183                        .to_r(&e1)
184                        .add("']]")
185                        .into(),
186                    Lang::Record(fields, _) => {
187                        let at = fields[0].clone();
188                        Translatable::from(cont.clone())
189                            .add("within(")
190                            .to_r(&e2)
191                            .add(", { ")
192                            .add(&at.get_argument())
193                            .add(" <- ")
194                            .to_r(&at.get_value())
195                            .add(" })")
196                            .into()
197                    }
198                    Lang::FunctionApp(var, v, h) => {
199                        let v = [e1].iter().chain(v.iter()).cloned().collect();
200                        Lang::FunctionApp(var, v, h).to_r(cont)
201                    }
202                    _ => Translatable::from(cont.clone())
203                        .to_r(&e2)
204                        .add("[[")
205                        .add("]]")
206                        .to_r(&e1)
207                        .into(),
208                }
209            }
210            Lang::Operator(Op::Dollar(_), e1, e2, _) => {
211                let e1 = (**e1).clone();
212                let e2 = (**e2).clone();
213                let t1 = typing(cont, &e1).value;
214                let val = match (t1.clone(), e2.clone()) {
215                    (Type::Vec(vtype, _, _, _), Lang::Variable(name, _, _, _))
216                        if vtype.is_array() =>
217                    {
218                        format!("vec_apply(get, {}, typed_vec('{}'))", e1.to_r(cont).0, name)
219                    }
220                    (_, Lang::Variable(name, _, _, _)) => format!("{}${}", e1.to_r(cont).0, name),
221                    _ => format!("{}${}", e1.to_r(cont).0, e2.to_r(cont).0),
222                    //_ => panic!("Dollar operation not yet implemented for {:?}", e2)
223                };
224                (val, cont.clone())
225            }
226            Lang::Operator(op, e1, e2, _) => {
227                let op_str = format!(" {} ", op.to_string());
228                Translatable::from(cont.clone())
229                    .to_r(e1)
230                    .add(&op_str)
231                    .to_r(e2)
232                    .into()
233            }
234            Lang::Scope(exps, _) => Translatable::from(cont.clone())
235                .add("{\n")
236                .join(exps, "\n")
237                .add("\n}")
238                .into(),
239            Lang::Function(args, _, body, _) => {
240                let fn_type = FunctionType::try_from(typing(cont, self).value.clone()).unwrap();
241                let output_conversion = cont.get_type_anotation(&fn_type.get_return_type());
242                let res = (output_conversion == "")
243                    .then_some("".to_string())
244                    .unwrap_or(" |> ".to_owned() + &output_conversion);
245                (
246                    format!(
247                        "(function({}) {}{}) |> {}",
248                        args.iter().map(|x| x.to_r()).collect::<Vec<_>>().join(", "),
249                        body.to_r(cont).0,
250                        res,
251                        cont.get_type_anotation(&fn_type.into())
252                    ),
253                    cont.clone(),
254                )
255            }
256            Lang::Variable(_, _, _, _) => {
257                //Here we only keep the variable name, the path and the type
258                let var = Var::from_language(self.clone()).unwrap();
259                let name = if var.contains("__") {
260                    var.replace("__", ".").get_name()
261                } else {
262                    var.display_type(cont).get_name()
263                };
264                ((&name).to_string(), cont.clone())
265            }
266            Lang::FunctionApp(exp, vals, _) => {
267                let var = Var::try_from(exp.clone()).unwrap();
268
269                let (exp_str, cont1) = exp.to_r(cont);
270                let fn_t = FunctionType::try_from(
271                    cont1
272                        .get_type_from_variable(&var)
273                        .expect(&format!("variable {} don't have a related type", var)),
274                )
275                .map(|ft| ft.adjust_nb_parameters(vals.len()))
276                .unwrap();
277                let new_args = fn_t
278                    .get_param_types()
279                    .into_iter()
280                    .map(|arg| reduce_type(&cont1, &arg))
281                    .collect::<Vec<_>>();
282                let new_vals = vals
283                    .into_iter()
284                    .zip(new_args.iter())
285                    .map(set_related_type_if_variable)
286                    .collect::<Vec<_>>();
287                let (args, current_cont) = Translatable::from(cont1).join(&new_vals, ", ").into();
288                Var::from_language(*exp.clone())
289                    .map(|var| {
290                        let name = var.get_name();
291                        let new_name = if &name[0..1] == "%" {
292                            format!("`{}`", name.replace("__", "."))
293                        } else {
294                            name.replace("__", ".")
295                        };
296                        (format!("{}({})", new_name, args), current_cont.clone())
297                    })
298                    .unwrap_or((format!("{}({})", exp_str, args), current_cont))
299            }
300            Lang::VecFunctionApp(exp, vals, _) => {
301                let var = Var::try_from(exp.clone()).unwrap();
302                let name = var.get_name();
303                let str_vals = vals
304                    .iter()
305                    .map(|x| x.to_r(cont).0)
306                    .collect::<Vec<_>>()
307                    .join(", ");
308                if name == "reduce" {
309                    (format!("vec_reduce({})", str_vals), cont.clone())
310                } else if cont.is_an_untyped_function(&name) {
311                    let name = name.replace("__", ".");
312                    let new_name = if &name[0..1] == "%" {
313                        format!("`{}`", name)
314                    } else {
315                        name.to_string()
316                    };
317                    let s = format!("{}({})", new_name, str_vals);
318                    (s, cont.clone())
319                } else {
320                    let (exp_str, cont1) = exp.to_r(cont);
321                    let fn_t = FunctionType::try_from(
322                        cont1
323                            .get_type_from_variable(&var)
324                            .expect(&format!("variable {} don't have a related type", var)),
325                    )
326                    .unwrap();
327                    let new_args = fn_t
328                        .get_param_types()
329                        .into_iter()
330                        .map(|arg| reduce_type(&cont1, &arg))
331                        .collect::<Vec<_>>();
332                    let new_vals = vals
333                        .into_iter()
334                        .zip(new_args.iter())
335                        .map(set_related_type_if_variable)
336                        .collect::<Vec<_>>();
337                    let (args, current_cont) =
338                        Translatable::from(cont1).join(&new_vals, ", ").into();
339                    Var::from_language(*exp.clone())
340                        .map(|var| {
341                            let name = var.get_name();
342                            let new_name = if &name[0..1] == "%" {
343                                format!("`{}`", name.replace("__", "."))
344                            } else {
345                                name.replace("__", ".")
346                            };
347                            (
348                                format!("vec_apply({}, {})", new_name, args),
349                                current_cont.clone(),
350                            )
351                        })
352                        .unwrap_or((format!("vec_apply({}, {})", exp_str, args), current_cont))
353                }
354            }
355            Lang::ArrayIndexing(exp, val, _) => {
356                let (exp_str, _) = exp.to_r(cont);
357                let (val_str, _) = val.to_simple_r(cont);
358                let (typ, _, _) = typing(&cont, exp).to_tuple();
359                let res = match typ {
360                    Type::Vec(_, _, _, _) => format!("{}[[{}]]", exp_str, val_str),
361                    _ => "".to_string(),
362                };
363                (res, cont.clone())
364            }
365            Lang::GenFunc(func, _, _) => (
366                format!("function(x, ...) UseMethod('{}')", func.to_string()),
367                cont.clone(),
368            ),
369            Lang::Let(expr, ttype, body, _) => {
370                let (body_str, new_cont) = body.to_r(cont);
371                let new_name = format_backtick(expr.clone().to_r(cont).0);
372
373                let (r_code, _new_name2) = Function::try_from((**body).clone())
374                    .map(|_| {
375                        let related_type = typing(cont, expr).value;
376                        let method = match cont.get_environment() {
377                            Environment::Project => format!(
378                                "#' @method {}\n",
379                                new_name.replace(".", " ").replace("`", "")
380                            ),
381                            _ => "".to_string(),
382                        };
383                        match related_type {
384                            Type::Empty(_) => {
385                                (format!("{} <- {}", new_name, body_str), new_name.clone())
386                            }
387                            Type::Any(_) | Type::Generic(_, _) => (
388                                format!("{}.default <- {}", new_name, body_str),
389                                new_name.clone(),
390                            ),
391                            _ => (
392                                format!("{}{} <- {}", method, new_name, body_str),
393                                new_name.clone(),
394                            ),
395                        }
396                    })
397                    .unwrap_or((format!("{} <- {}", new_name, body_str), new_name));
398                let code = if !ttype.is_empty() {
399                    let _ = new_cont.get_type_anotation(ttype);
400                    format!("{}\n", r_code)
401                } else {
402                    r_code + "\n"
403                };
404                (code, new_cont)
405            }
406            Lang::Array(_v, _h) => {
407                let typ = self.typing(cont).value;
408
409                let _dimension = ArrayType::try_from(typ.clone())
410                    .unwrap()
411                    .get_shape()
412                    .map(|sha| format!("c({})", sha))
413                    .unwrap_or(format!("c(0)"));
414
415                let array = &self
416                    .linearize_array()
417                    .iter()
418                    .map(|lang| lang.to_r(&cont).0)
419                    .collect::<Vec<_>>()
420                    .join(", ")
421                    .and_if(|lin_array| lin_array != "")
422                    //.map(|lin_array| format!("concat({}, dim = {})", lin_array, dimension))
423                    .map(|lin_array| format!("typed_vec({})", lin_array))
424                    .unwrap_or("logical(0)".to_string());
425
426                (
427                    format!("{} |> {}", array, cont.get_type_anotation(&typ)),
428                    cont.to_owned(),
429                )
430            }
431            Lang::Record(args, _) => {
432                let (body, current_cont) = Translatable::from(cont.clone())
433                    .join_arg_val(args, ",\n ")
434                    .into();
435                let (typ, _, _) = typing(cont, self).to_tuple();
436                let anotation = cont.get_type_anotation(&typ);
437                cont.get_classes(&typ)
438                    .map(|_| format!("list({}) |> {}", body, anotation))
439                    .unwrap_or(format!("list({}) |> {}", body, anotation))
440                    .to_some()
441                    .map(|s| (s, current_cont))
442                    .unwrap()
443            }
444            Lang::If(cond, exp, els, _) if els == &Box::new(Lang::Empty(HelpData::default())) => {
445                Translatable::from(cont.clone())
446                    .add("if(")
447                    .to_r(cond)
448                    .add(") {\n")
449                    .to_r(exp)
450                    .add(" \n}")
451                    .into()
452            }
453            Lang::If(cond, exp, els, _) => Translatable::from(cont.clone())
454                .add("if(")
455                .to_r(cond)
456                .add(") {\n")
457                .to_r(exp)
458                .add(" \n} else ")
459                .to_r(els)
460                .into(),
461            Lang::Tuple(vals, _) => Translatable::from(cont.clone())
462                .add("struct(list(")
463                .join(vals, ", ")
464                .add("), 'Tuple')")
465                .into(),
466            Lang::Assign(var, exp, _) => Translatable::from(cont.clone())
467                .to_r(var)
468                .add(" <- ")
469                .to_r(exp)
470                .into(),
471            Lang::Comment(txt, _) => ("#".to_string() + txt, cont.clone()),
472            Lang::Tag(s, t, _) => {
473                let (t_str, new_cont) = t.to_r(cont);
474                let (typ, _, _) = typing(cont, self).to_tuple();
475                let class = cont.get_class(&typ);
476                cont.get_classes(&typ)
477                    .map(|res| {
478                        format!(
479                            "struct(list('{}', {}), c('Tag', {}, {}))",
480                            s, t_str, class, res
481                        )
482                    })
483                    .unwrap_or(format!(
484                        "struct(list('{}', {}), c('Tag', {}))",
485                        s, t_str, class
486                    ))
487                    .to_some()
488                    .map(|s| (s, new_cont))
489                    .unwrap()
490            }
491            Lang::Empty(_) => ("NA".to_string(), cont.clone()),
492            Lang::ModuleDecl(name, _) => (format!("{} <- new.env()", name), cont.clone()),
493            Lang::Lines(exps, _) => Translatable::from(cont.clone()).join(exps, "\n").into(),
494            Lang::Return(exp, _) => Translatable::from(cont.clone())
495                .add("return ")
496                .to_r(exp)
497                .into(),
498            Lang::Lambda(bloc, _) => (
499                format!("function(x) {{ {} }}", bloc.to_r(cont).0),
500                cont.clone(),
501            ),
502            Lang::VecBlock(bloc, _) => (bloc.to_string(), cont.clone()),
503            Lang::Library(name, _) => (format!("library({})", name), cont.clone()),
504            Lang::Match(exp, var, branches, _) => (
505                to_if_statement(var.clone(), (**exp).clone(), branches, cont),
506                cont.clone(),
507            ),
508            Lang::Exp(exp, _) => (exp.clone(), cont.clone()),
509            Lang::ForLoop(var, iterator, body, _) => Translatable::from(cont.clone())
510                .add("for (")
511                .to_r_safe(var)
512                .add(" in ")
513                .to_r_safe(iterator)
514                .add(") {\n")
515                .to_r_safe(body)
516                .add("\n}")
517                .into(),
518            Lang::RFunction(vars, body, _) => Translatable::from(cont.clone())
519                .add("function (")
520                .join(vars, ", ")
521                .add(") \n")
522                .add(&body)
523                .add("\n")
524                .into(),
525            Lang::Signature(_, _, _) => ("".to_string(), cont.clone()),
526            Lang::Alias(_, _, _, _) => ("".to_string(), cont.clone()),
527            Lang::KeyValue(k, v, _) => (format!("{} = {}", k, v.to_r(cont).0), cont.clone()),
528            Lang::Vector(vals, _) => {
529                let res = "c(".to_string()
530                    + &vals
531                        .iter()
532                        .map(|x| x.to_r(cont).0)
533                        .collect::<Vec<_>>()
534                        .join(", ")
535                    + ")";
536                (res, cont.to_owned())
537            }
538            Lang::Not(exp, _) => (format!("!{}", exp.to_r(cont).0), cont.clone()),
539            Lang::Sequence(vals, _) => {
540                let res = if vals.len() > 0 {
541                    "c(".to_string()
542                        + &vals
543                            .iter()
544                            .map(|x| "list(".to_string() + &x.to_r(cont).0 + ")")
545                            .collect::<Vec<_>>()
546                            .join(", ")
547                        + ")"
548                } else {
549                    "c(list())".to_string()
550                };
551                (res, cont.to_owned())
552            }
553            Lang::TestBlock(body, h) => {
554                let file_name = h
555                    .get_file_data()
556                    .map(|(name, _)| format!("test-{}", name))
557                    .unwrap_or_else(|| "test-unknown".to_string())
558                    .replace("TypR/", "")
559                    .replace(".ty", ".R");
560
561                let file_path = format!("tests/testthat/{}", file_name);
562                let content = body.to_r(cont).0;
563
564                let _ = write_output_file(&file_path, &content);
565                ("".to_string(), cont.clone())
566            }
567            Lang::JSBlock(exp, _id, _h) => {
568                let js_cont = Context::default(); //TODO get js context from memory
569                let res = exp.to_js(&js_cont).0;
570                (format!("'{}{}'", JS_HEADER, res), cont.clone())
571            }
572            Lang::WhileLoop(condition, body, _) => (
573                format!(
574                    "while ({}) {{\n{}\n}}",
575                    condition.to_r(cont).0,
576                    body.to_r(cont).0
577                ),
578                cont.clone(),
579            ),
580            Lang::Break(_) => ("break".to_string(), cont.clone()),
581            Lang::Module(name, body, position, config, _) => {
582                let name = if (name == "main") && (config.environment == Environment::Project) {
583                    "a_main"
584                } else {
585                    name
586                };
587                let content = body
588                    .iter()
589                    .map(|lang| lang.to_r(cont).0)
590                    .collect::<Vec<_>>()
591                    .join("\n");
592                match (position, config.environment) {
593                    (ModulePosition::Internal, _) => (content, cont.clone()),
594                    // In WASM mode, inline all external modules instead of writing files
595                    (ModulePosition::External, Environment::Wasm) => {
596                        let file_path = format!("{}.R", name);
597                        let _ = write_output_file(&file_path, &content);
598                        // Return the content directly, no source() call
599                        (content, cont.clone())
600                    }
601                    (ModulePosition::External, Environment::StandAlone)
602                    | (ModulePosition::External, Environment::Repl) => {
603                        let file_path = format!("{}.R", name);
604                        let _ = write_output_file(&file_path, &content);
605                        (format!("source('{}')", file_path), cont.clone())
606                    }
607                    (ModulePosition::External, Environment::Project) => {
608                        let file_path = format!("R/{}.R", name);
609                        let _ = write_output_file(&file_path, &content);
610                        ("".to_string(), cont.clone())
611                    }
612                }
613            }
614            _ => {
615                println!("This language structure won't transpile: {:?}", self);
616                ("".to_string(), cont.clone())
617            }
618        };
619
620        result
621    }
622}