diff --git a/compiler/erg_compiler/lib.rs b/compiler/erg_compiler/lib.rs index d3013842..f8629da8 100644 --- a/compiler/erg_compiler/lib.rs +++ b/compiler/erg_compiler/lib.rs @@ -21,5 +21,6 @@ pub mod mod_cache; pub mod optimize; pub mod ownercheck; pub mod reorder; +pub mod transpile; pub mod ty; pub mod varinfo; diff --git a/compiler/erg_compiler/main.rs b/compiler/erg_compiler/main.rs index 66cd9105..cfb8190f 100644 --- a/compiler/erg_compiler/main.rs +++ b/compiler/erg_compiler/main.rs @@ -10,6 +10,7 @@ use erg_common::traits::Runnable; use erg_compiler::build_hir::HIRBuilder; use erg_compiler::lower::ASTLowerer; +use erg_compiler::transpile::Transpiler; use erg_compiler::ty::deserialize::Deserializer; use erg_compiler::Compiler; @@ -31,6 +32,9 @@ fn run() { "check" => { HIRBuilder::run(cfg); } + "transpile" => { + Transpiler::run(cfg); + } "compile" | "exec" => { Compiler::run(cfg); } diff --git a/compiler/erg_compiler/transpile.rs b/compiler/erg_compiler/transpile.rs new file mode 100644 index 00000000..80144636 --- /dev/null +++ b/compiler/erg_compiler/transpile.rs @@ -0,0 +1,233 @@ +use std::fs::File; +use std::io::Write; + +use erg_common::config::ErgConfig; +use erg_common::log; +use erg_common::traits::{Runnable, Stream}; +use erg_common::Str; + +use erg_parser::ast::ParamPattern; + +use crate::build_hir::HIRBuilder; +use crate::desugar_hir::HIRDesugarer; +use crate::error::{CompileError, CompileErrors}; +use crate::hir::{Accessor, Block, Call, Expr, Identifier, Params, Signature, HIR}; +use crate::link::Linker; +use crate::mod_cache::SharedModuleCache; + +#[derive(Debug, Clone)] +pub struct PyScript { + pub filename: Str, + pub code: String, +} + +/// Generates a `PyScript` from an String or other File inputs. +#[derive(Debug, Default)] +pub struct Transpiler { + pub cfg: ErgConfig, + builder: HIRBuilder, + mod_cache: SharedModuleCache, + script_generator: ScriptGenerator, +} + +impl Runnable for Transpiler { + type Err = CompileError; + type Errs = CompileErrors; + const NAME: &'static str = "Erg transpiler"; + + fn new(cfg: ErgConfig) -> Self { + let mod_cache = SharedModuleCache::new(); + let py_mod_cache = SharedModuleCache::new(); + Self { + builder: HIRBuilder::new_with_cache( + cfg.copy(), + "", + mod_cache.clone(), + py_mod_cache, + ), + script_generator: ScriptGenerator::new(), + mod_cache, + cfg, + } + } + + #[inline] + fn cfg(&self) -> &ErgConfig { + &self.cfg + } + + #[inline] + fn finish(&mut self) {} + + fn clear(&mut self) { + // self.builder.clear(); + } + + fn exec(&mut self) -> Result { + let path = self.input().filename().replace(".er", ".py"); + let script = self.transpile(self.input().read(), "exec")?; + let mut f = File::create(&path).unwrap(); + f.write_all(script.code.as_bytes()).unwrap(); + Ok(0) + } + + fn eval(&mut self, src: String) -> Result { + let script = self.transpile(src, "eval")?; + Ok(script.code) + } +} + +impl Transpiler { + pub fn transpile(&mut self, src: String, mode: &str) -> Result { + log!(info "the transpiling process has started."); + let hir = self.build_link_desugar(src, mode)?; + let script = self.script_generator.transpile(hir); + log!(info "code:\n{}", script.code); + log!(info "the transpiling process has completed"); + Ok(script) + } + + fn build_link_desugar(&mut self, src: String, mode: &str) -> Result { + let artifact = self + .builder + .build(src, mode) + .map_err(|artifact| artifact.errors)?; + let linker = Linker::new(&self.cfg, &self.mod_cache); + let hir = linker.link(artifact.hir); + Ok(HIRDesugarer::desugar(hir)) + } +} + +#[derive(Debug, Default)] +pub struct ScriptGenerator { + level: usize, +} + +impl ScriptGenerator { + pub const fn new() -> Self { + Self { level: 0 } + } + + pub fn transpile(&mut self, hir: HIR) -> PyScript { + let mut code = String::new(); + for chunk in hir.module.into_iter() { + code += &self.transpile_expr(chunk); + code.push('\n'); + } + PyScript { + filename: hir.name, + code, + } + } + + pub fn transpile_expr(&mut self, expr: Expr) -> String { + match expr { + Expr::Lit(lit) => lit.token.content.to_string(), + Expr::Call(call) => self.transpile_call(call), + Expr::Accessor(acc) => match acc { + Accessor::Ident(ident) => Self::transpile_ident(ident), + Accessor::Attr(attr) => { + format!( + "{}.{}", + self.transpile_expr(*attr.obj), + Self::transpile_ident(attr.ident) + ) + } + }, + Expr::Def(mut def) => match def.sig { + Signature::Var(var) => { + let mut code = format!("{} = ", Self::transpile_ident(var.ident)); + if def.body.block.len() > 1 { + todo!("transpile instant blocks") + } + let expr = def.body.block.remove(0); + code += &self.transpile_expr(expr); + code + } + Signature::Subr(subr) => { + let mut code = format!( + "def {}({}):\n", + Self::transpile_ident(subr.ident), + self.transpile_params(subr.params) + ); + code += &self.transpile_block(def.body.block); + code + } + }, + other => todo!("transpile {other}"), + } + } + + fn transpile_call(&mut self, mut call: Call) -> String { + match call.obj.local_name() { + Some("assert") => { + let mut code = format!("assert {}", self.transpile_expr(call.args.remove(0))); + if let Some(msg) = call.args.try_remove(0) { + code += &format!(", {}", self.transpile_expr(msg)); + } + code + } + _ => self.transpile_simple_call(call), + } + } + + fn transpile_simple_call(&mut self, mut call: Call) -> String { + let mut code = self.transpile_expr(*call.obj); + code.push('('); + while let Some(arg) = call.args.try_remove_pos(0) { + code += &self.transpile_expr(arg.expr); + code.push(','); + } + while let Some(arg) = call.args.try_remove_kw(0) { + code += &format!("{}={},", arg.keyword, self.transpile_expr(arg.expr)); + } + code.push(')'); + code + } + + fn transpile_ident(ident: Identifier) -> String { + if let Some(py_name) = ident.vi.py_name { + py_name.to_string() + } else if ident.dot.is_some() { + ident.name.into_token().content.to_string() + } else { + let name = ident.name.into_token().content; + let name = name.replace('!', "__erg_proc__"); + let name = name.replace('$', "__erg_shared__"); + format!("__{name}") + } + } + + fn transpile_params(&mut self, params: Params) -> String { + let mut code = String::new(); + for non_default in params.non_defaults { + let ParamPattern::VarName(param) = non_default.pat else { todo!() }; + code += &format!("{},", param.into_token().content); + } + for default in params.defaults { + let ParamPattern::VarName(param) = default.sig.pat else { todo!() }; + code += &format!( + "{}={},", + param.into_token().content, + self.transpile_expr(default.default_val) + ); + } + code + } + + fn transpile_block(&mut self, block: Block) -> String { + self.level += 1; + let mut code = String::new(); + let last = block.len() - 1; + for (i, chunk) in block.into_iter().enumerate() { + code += &" ".repeat(self.level); + if i == last { + code += "return "; + } + code += &self.transpile_expr(chunk); + code.push('\n'); + } + self.level -= 1; + code + } +} diff --git a/src/main.rs b/src/main.rs index 1d7a226e..363f2201 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use erg_parser::ParserRunner; use erg_compiler::build_hir::HIRBuilder; use erg_compiler::lower::ASTLowerer; +use erg_compiler::transpile::Transpiler; use erg_compiler::ty::deserialize::Deserializer; use erg_compiler::Compiler; @@ -37,6 +38,9 @@ fn run() { "compile" => { Compiler::run(cfg); } + "transpile" => { + Transpiler::run(cfg); + } "exec" => { DummyVM::run(cfg); }