diff --git a/.gitignore b/.gitignore index 20acc9932..36bd8fdb8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ /.idea/ /timeit.dat **/__pycache__/ +**/.venv # Nix .direnv diff --git a/Cargo.lock b/Cargo.lock index 0957c6169..fc79abda2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,7 @@ dependencies = [ "backtrace-on-stack-overflow", "crossterm", "parking_lot", + "pyo3", "thread_local", ] @@ -134,6 +135,7 @@ version = "0.6.25" dependencies = [ "erg_common", "erg_parser", + "pyo3", ] [[package]] @@ -141,6 +143,8 @@ name = "erg_parser" version = "0.6.25" dependencies = [ "erg_common", + "erg_proc_macros", + "pyo3", "unicode-xid", ] @@ -168,6 +172,12 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "idna" version = "0.4.0" @@ -178,6 +188,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + [[package]] name = "itoa" version = "1.0.9" @@ -234,6 +250,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -276,7 +301,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] @@ -332,6 +357,67 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pyo3" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset 0.9.0", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "quote" version = "1.0.33" @@ -468,6 +554,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" + [[package]] name = "thread_local" version = "1.1.7" @@ -520,6 +612,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "url" version = "2.4.1" diff --git a/Cargo.toml b/Cargo.toml index 11fb78afe..2eb98aa1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,6 +71,7 @@ erg_parser = { version = "0.6.25", path = "./crates/erg_parser" } erg_compiler = { version = "0.6.25", path = "./crates/erg_compiler" } els = { version = "0.1.37", path = "./crates/els" } erg_proc_macros = { version = "0.6.25", path = "./crates/erg_proc_macros" } +pyo3 = { version = "0.20", features = ["extension-module"] } [dependencies] erg_common = { workspace = true } diff --git a/crates/erg_common/Cargo.toml b/crates/erg_common/Cargo.toml index 01505b99f..6f865f063 100644 --- a/crates/erg_common/Cargo.toml +++ b/crates/erg_common/Cargo.toml @@ -24,6 +24,7 @@ gal = [] no_std = [] full-repl = ["dep:crossterm"] experimental = [] +pylib = ["dep:pyo3"] [target.'cfg(unix)'.dependencies] backtrace-on-stack-overflow = { version = "0.3.0", optional = true } @@ -32,6 +33,7 @@ backtrace-on-stack-overflow = { version = "0.3.0", optional = true } crossterm = { optional = true, version = "0.25.0" } parking_lot = "0.12" thread_local = "1.1" +pyo3 = { workspace = true, optional = true } [lib] path = "lib.rs" diff --git a/crates/erg_common/error.rs b/crates/erg_common/error.rs index 5857b7404..fbf705872 100644 --- a/crates/erg_common/error.rs +++ b/crates/erg_common/error.rs @@ -16,6 +16,9 @@ use crate::style::THEME; use crate::traits::{Locational, Stream}; use crate::{impl_display_from_debug, switch_lang}; +#[cfg(feature = "pylib")] +use pyo3::prelude::*; + /// This includes not only Error but also Warning, Exception /// Numbering of this is not specifically related to ErrFmt.errno(). #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -278,6 +281,48 @@ impl fmt::Display for Location { } } +#[cfg(feature = "pylib")] +impl FromPyObject<'_> for Location { + fn extract(ob: &'_ PyAny) -> PyResult { + if let Ok(s) = ob.extract::() { + Ok(s.parse::().unwrap()) + } else if let Ok(s) = ob.extract::() { + Ok(Location::Line(s)) + } else if let Ok((l, r)) = ob.extract::<(u32, u32)>() { + Ok(Location::LineRange(l, r)) + } else if let Ok((lb, cb, le, ce)) = ob.extract::<(u32, u32, u32, u32)>() { + Ok(Location::Range { + ln_begin: lb, + col_begin: cb, + ln_end: le, + col_end: ce, + }) + } else { + Err(PyErr::new::(format!( + "expected Into, but got {:?}", + ob.get_type().name()? + ))) + } + } +} + +#[cfg(feature = "pylib")] +impl IntoPy for Location { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::Line(l) => (l, py.None(), l, py.None()).into_py(py), + Self::LineRange(lb, le) => (lb, py.None(), le, py.None()).into_py(py), + Self::Range { + ln_begin, + col_begin, + ln_end, + col_end, + } => (ln_begin, col_begin, ln_end, col_end).into_py(py), + Self::Unknown => (py.None(), py.None(), py.None(), py.None()).into_py(py), + } + } +} + impl std::str::FromStr for Location { type Err = (); fn from_str(s: &str) -> Result { @@ -328,7 +373,7 @@ impl Ord for Location { } impl PartialOrd for Location { - #[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] + #[allow(clippy::non_canonical_partial_ord_impl)] fn partial_cmp(&self, other: &Location) -> Option { if self.is_unknown() || other.is_unknown() { None diff --git a/crates/erg_common/set.rs b/crates/erg_common/set.rs index 0bafc73af..188dc3a57 100644 --- a/crates/erg_common/set.rs +++ b/crates/erg_common/set.rs @@ -7,6 +7,9 @@ use std::iter::FromIterator; use crate::fxhash::FxHashSet; use crate::{debug_fmt_iter, fmt_iter}; +#[cfg(feature = "pylib")] +use pyo3::{FromPyObject, IntoPy, PyAny, PyObject, Python}; + #[macro_export] macro_rules! set { () => { $crate::set::Set::new() }; @@ -22,6 +25,25 @@ pub struct Set { elems: FxHashSet, } +#[cfg(feature = "pylib")] +impl> IntoPy for Set { + fn into_py(self, py: Python<'_>) -> PyObject { + self.elems.into_py(py) + } +} + +#[cfg(feature = "pylib")] +impl<'source, T> FromPyObject<'source> for Set +where + T: Hash + Eq + FromPyObject<'source>, +{ + fn extract(ob: &'source PyAny) -> pyo3::PyResult { + Ok(Set { + elems: ob.extract::>()?, + }) + } +} + // Use `fast_eq` for faster comparisons impl PartialEq for Set { fn eq(&self, other: &Set) -> bool { diff --git a/crates/erg_common/str.rs b/crates/erg_common/str.rs index 4d9036118..db2802bf0 100644 --- a/crates/erg_common/str.rs +++ b/crates/erg_common/str.rs @@ -3,6 +3,9 @@ use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::{Add, Deref}; +#[cfg(feature = "pylib")] +use pyo3::{FromPyObject, IntoPy, PyAny, PyObject, Python}; + pub type ArcStr = std::sync::Arc; /// Used to hold an immutable string. @@ -14,6 +17,21 @@ pub enum Str { Static(&'static str), } +#[cfg(feature = "pylib")] +impl FromPyObject<'_> for Str { + fn extract(ob: &PyAny) -> pyo3::PyResult { + let s = ob.extract::()?; + Ok(Str::Rc(s.into())) + } +} + +#[cfg(feature = "pylib")] +impl IntoPy for Str { + fn into_py(self, py: Python<'_>) -> PyObject { + (&self[..]).into_py(py) + } +} + impl PartialEq for Str { #[inline] fn eq(&self, other: &Str) -> bool { diff --git a/crates/erg_common/traits.rs b/crates/erg_common/traits.rs index 2e4d2df65..40427518a 100644 --- a/crates/erg_common/traits.rs +++ b/crates/erg_common/traits.rs @@ -769,7 +769,7 @@ pub trait Runnable: Sized + Default + New { let indent = vm.indent(); if vm.now_block.len() > 1 { output.write_all(instance.ps2().as_bytes()).unwrap(); - output.write_all(indent.as_str().as_bytes()).unwrap(); + output.write_all(indent.as_bytes()).unwrap(); output.flush().unwrap(); } else { output.write_all(instance.ps1().as_bytes()).unwrap(); diff --git a/crates/erg_compiler/Cargo.toml b/crates/erg_compiler/Cargo.toml index 34a73905f..640e9e436 100644 --- a/crates/erg_compiler/Cargo.toml +++ b/crates/erg_compiler/Cargo.toml @@ -40,15 +40,19 @@ els = ["erg_common/els"] no_std = ["erg_common/no_std"] full-repl = ["erg_common/full-repl"] experimental = ["erg_common/experimental", "erg_parser/experimental"] +pylib = ["dep:pyo3", "erg_common/pylib", "erg_parser/pylib"] +pylib_compiler = ["pylib"] [dependencies] erg_common = { workspace = true } erg_parser = { workspace = true } +pyo3 = { workspace = true, optional = true } [build-dependencies] erg_common = { workspace = true } [lib] +crate-type = ["cdylib", "rlib"] path = "lib.rs" [[bin]] diff --git a/crates/erg_compiler/README.md b/crates/erg_compiler/README.md index ba46d26e9..d1d9076d1 100644 --- a/crates/erg_compiler/README.md +++ b/crates/erg_compiler/README.md @@ -1,3 +1,50 @@ # The Erg compiler (codename: Centimetre) The overall structure is described in detail in [architecture.md(English)](../../doc/EN/compiler/architecture.md).For other language translations of architecture.md, please check them out by yourself. + +## Use `erg_compiler` as a Python library + +`erg_compiler` can be built as a Python library by using pyo3/maturin. + +### Example + +```python +import erg_compiler + +module = erg_compiler.exec_module(".i = 1") +# foo.er: +# .bar = 1 +foo = erg_compiler.__import__("foo") +assert module.i == 1 +assert foo.bar == 1 +``` + +```python +import erg_compiler +erg_parser = erg_compiler.erg_parser +erg_ast = erg_compiler.erg_parser.ast + +module = erg_parser.parse(".i = 1") +d = module.pop() +d.sig = erg_ast.VarSignature.new(erg_ast.Identifier.public("j"), None) +module.push(d) +ast = erg_ast.AST.new("test", module) +code = erg_compiler.compile_ast(ast) +exec(code) +assert j == 1 +``` + +### Debug install (using venv) + +```python +python -m venv .venv +source .venv/bin/activate +maturin develop --features pylib_compiler +``` + +### Release install + +```python +maturin build -i python --release --features pylib_compiler +pip install +``` diff --git a/crates/erg_compiler/compile.rs b/crates/erg_compiler/compile.rs index fc2420825..b052a2a19 100644 --- a/crates/erg_compiler/compile.rs +++ b/crates/erg_compiler/compile.rs @@ -8,9 +8,9 @@ use erg_common::dict::Dict; use erg_common::error::MultiErrorDisplay; use erg_common::log; use erg_common::traits::{ExitStatus, New, Runnable, Stream}; -use erg_parser::ast::VarName; +use erg_parser::ast::{VarName, AST}; -use crate::artifact::{CompleteArtifact, ErrorArtifact}; +use crate::artifact::{Buildable, CompleteArtifact, ErrorArtifact}; use crate::build_package::PackageBuilder; use crate::codegen::PyCodeGenerator; use crate::context::{Context, ContextProvider}; @@ -244,6 +244,19 @@ impl Compiler { Ok(CompleteArtifact::new(codeobj, arti.warns)) } + pub fn compile_ast( + &mut self, + ast: AST, + mode: &str, + ) -> Result, ErrorArtifact> { + log!(info "the compiling process has started."); + let arti = self.build_link_desugar_optimize_ast(ast, mode)?; + let codeobj = self.code_generator.emit(arti.object); + log!(info "code object:\n{}", codeobj.code_info(Some(self.code_generator.py_version))); + log!(info "the compiling process has completed"); + Ok(CompleteArtifact::new(codeobj, arti.warns)) + } + pub fn compile_module(&mut self) -> Result, ErrorArtifact> { let src = self.cfg.input.read(); self.compile(src, "exec") @@ -276,6 +289,19 @@ impl Compiler { Ok(CompleteArtifact::new(hir, artifact.warns)) } + fn build_link_desugar_optimize_ast( + &mut self, + ast: AST, + mode: &str, + ) -> Result { + let artifact = self.builder.build_from_ast(ast, mode)?; + let linker = HIRLinker::new(&self.cfg, &self.shared.mod_cache); + let hir = linker.link(artifact.object); + let hir = HIRDesugarer::desugar(hir); + let hir = HIROptimizer::optimize(self.cfg.clone(), self.shared.clone(), hir); + Ok(CompleteArtifact::new(hir, artifact.warns)) + } + pub fn initialize_generator(&mut self) { self.code_generator.initialize(); } diff --git a/crates/erg_compiler/context/initialize/mod.rs b/crates/erg_compiler/context/initialize/mod.rs index 8f65cc4f5..7c0a081be 100644 --- a/crates/erg_compiler/context/initialize/mod.rs +++ b/crates/erg_compiler/context/initialize/mod.rs @@ -1158,7 +1158,7 @@ impl Context { self.cfg.inherit(path.to_path_buf()), self.shared().clone(), ); - let eval_t = func1(Type::Str, Type::Obj); + let eval_t = func1(Str | mono(BYTES) | Code, Obj); ctx.register_builtin_erg_impl( "pyeval", eval_t, @@ -1172,6 +1172,20 @@ impl Context { let block = Block::new(vec![eval]); let body = DefBody::new(Token::DUMMY, block, DefId(0)); let eval = Def::new(sig, body); + let exec_t = func1(Str | mono(BYTES) | Code, NoneType); + ctx.register_builtin_erg_impl( + "pyexec", + exec_t, + Mutability::Immutable, + Visibility::BUILTIN_PUBLIC, + ); + let pyexec = Identifier::public("pyexec"); + let sig = VarSignature::new(pyexec.clone(), None); + let sig = Signature::Var(sig); + let exec = Expr::Accessor(Accessor::Ident(Identifier::public("exec"))); + let block = Block::new(vec![exec]); + let body = DefBody::new(Token::DUMMY, block, DefId(0)); + let exec = Def::new(sig, body); let T = type_q("T"); let perform_t = func1(proc0(T.clone()), T).quantify(); ctx.register_builtin_erg_impl( @@ -1199,7 +1213,7 @@ impl Context { let block = Block::new(vec![Expr::Call(call)]); let body = DefBody::new(Token::DUMMY, block, DefId(0)); let perform = Def::new(sig, body); - let module = Module::new(vec![Expr::Def(eval), Expr::Def(perform)]); + let module = Module::new(vec![Expr::Def(eval), Expr::Def(exec), Expr::Def(perform)]); let hir = HIR::new("unsound".into(), module); let ctx = ModuleContext::new(ctx, dict! {}); self.mod_cache().register(path, None, Some(hir), ctx); diff --git a/crates/erg_compiler/error/mod.rs b/crates/erg_compiler/error/mod.rs index 2994d09d7..58caf2c83 100644 --- a/crates/erg_compiler/error/mod.rs +++ b/crates/erg_compiler/error/mod.rs @@ -175,6 +175,23 @@ pub struct CompileError { impl_display_and_error!(CompileError); +impl From for CompileError { + fn from(value: std::io::Error) -> Self { + Self { + core: Box::new(ErrorCore::new( + vec![SubMessage::only_loc(Location::Unknown)], + value.to_string(), + 0, + IoError, + Location::Unknown, + )), + input: Input::str("".into()), + caused_by: "".to_owned(), + theme: THEME, + } + } +} + impl From for CompileError { fn from(err: ParserRunnerError) -> Self { Self { @@ -201,6 +218,13 @@ impl From for ParserRunnerError { } } +#[cfg(feature = "pylib")] +impl std::convert::From for pyo3::PyErr { + fn from(err: CompileError) -> pyo3::PyErr { + pyo3::exceptions::PyOSError::new_err(err.to_string()) + } +} + impl ErrorDisplay for CompileError { fn core(&self) -> &ErrorCore { &self.core @@ -521,6 +545,12 @@ impl std::error::Error for CompileErrors {} impl_stream!(CompileErrors, CompileError); +impl From for CompileErrors { + fn from(value: std::io::Error) -> Self { + Self::from(vec![value.into()]) + } +} + impl From for CompileErrors { fn from(err: ParserRunnerErrors) -> Self { Self(err.into_iter().map(CompileError::from).collect()) @@ -557,6 +587,13 @@ impl From for ParseErrors { } } +#[cfg(feature = "pylib")] +impl std::convert::From for pyo3::PyErr { + fn from(errs: CompileErrors) -> pyo3::PyErr { + pyo3::exceptions::PyOSError::new_err(errs[0].to_string()) + } +} + impl MultiErrorDisplay for CompileErrors {} impl fmt::Display for CompileErrors { diff --git a/crates/erg_compiler/lib.rs b/crates/erg_compiler/lib.rs index c59371fa1..9d919e5c9 100644 --- a/crates/erg_compiler/lib.rs +++ b/crates/erg_compiler/lib.rs @@ -30,3 +30,143 @@ pub mod varinfo; pub use build_hir::{GenericHIRBuilder, HIRBuilder}; pub use erg_parser::build_ast::ASTBuilder; pub use transpile::Transpiler; + +#[cfg(feature = "pylib")] +use pyo3::prelude::*; +#[cfg(feature = "pylib")] +use pyo3::types::{IntoPyDict, PyBytes}; + +/// compile(code: str, mode: str) -> code +/// -- +/// +/// compile an Erg code as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "compile")] +fn _compile(py: Python<'_>, code: String, mode: &str) -> Result { + use erg_common::{config::ErgConfig, traits::Runnable}; + let cfg = ErgConfig::string(code); + let mut compiler = Compiler::new(cfg); + let src = compiler.cfg_mut().input.read(); + let code = compiler + .compile(src, mode) + .map(|art| art.object) + .map_err(|iart| iart.errors)?; + let bytes = code.into_bytes(py.version().parse().unwrap()); + let dict = [("bytes", PyBytes::new(py, &bytes))].into_py_dict(py); + py.run("import marshal", None, None).unwrap(); + let code = py.eval("marshal.loads(bytes)", None, Some(dict)).unwrap(); + Ok(code.into()) +} + +/// compile_ast(ast: erg_parser.AST, mode: str) -> code +/// -- +/// +/// compile an Erg AST as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "compile_ast")] +fn _compile_ast( + py: Python<'_>, + ast: erg_parser::ast::AST, + mode: &str, +) -> Result { + use erg_common::config::ErgConfig; + let cfg = ErgConfig::default(); + let mut compiler = Compiler::new(cfg); + let code = compiler + .compile_ast(ast, mode) + .map(|art| art.object) + .map_err(|iart| iart.errors)?; + let bytes = code.into_bytes(py.version().parse().unwrap()); + let dict = [("bytes", PyBytes::new(py, &bytes))].into_py_dict(py); + py.run("import marshal", None, None).unwrap(); + Ok(py + .eval("marshal.loads(bytes)", None, Some(dict)) + .unwrap() + .into()) +} + +/// compile_file(path: str) -> code +/// -- +/// +/// compile an Erg file as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "compile_file")] +fn _compile_file(py: Python<'_>, path: String) -> Result { + let code = std::fs::read_to_string(&path).unwrap_or_else(|err| panic!("{err}, path: {path}")); + _compile(py, code, "exec") +} + +/// exec(code: str) -> module +/// -- +/// +/// compile and execute an Erg code as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "exec")] +fn _exec(py: Python<'_>, code: String) -> Result { + let code = _compile(py, code, "exec")?; + let module = pyo3::types::PyModule::new(py, "").unwrap(); + let dic = [("code", code), ("dict", PyObject::from(module.dict()))].into_py_dict(py); + py.run("exec(code, dict)", None, Some(dic)).unwrap(); + Ok(module.into()) +} + +/// exec_ast(ast: erg_parser.AST) -> module +/// -- +/// +/// compile and execute an Erg AST as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "exec_ast")] +fn _exec_ast(py: Python<'_>, ast: erg_parser::ast::AST) -> Result { + let code = _compile_ast(py, ast, "exec")?; + let module = pyo3::types::PyModule::new(py, "").unwrap(); + let dic = [("code", code), ("dict", PyObject::from(module.dict()))].into_py_dict(py); + py.run("exec(code, dict)", None, Some(dic)).unwrap(); + Ok(module.into()) +} + +/// __import__(name: str) -> module +/// -- +/// +/// import an Erg module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "__import__")] +fn _import(py: Python<'_>, name: String) -> Result { + let path = format!("{name}.er"); + let code = std::fs::read_to_string(&path).unwrap_or_else(|err| panic!("{err}, path: {path}")); + _exec(py, code) +} + +#[cfg(feature = "pylib")] +#[pymodule] +fn erg_compiler(py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_compile, m)?)?; + m.add_function(wrap_pyfunction!(_compile_ast, m)?)?; + m.add_function(wrap_pyfunction!(_compile_file, m)?)?; + m.add_function(wrap_pyfunction!(_exec, m)?)?; + m.add_function(wrap_pyfunction!(_exec_ast, m)?)?; + m.add_function(wrap_pyfunction!(_import, m)?)?; + + use crate::erg_parser::erg_parser; + let parser = PyModule::new(py, "erg_parser")?; + erg_parser(py, parser)?; + m.add_submodule(parser)?; + + py.run( + "\ +import sys +sys.modules['erg_compiler.erg_parser'] = erg_parser +sys.modules['erg_compiler.erg_parser.ast'] = erg_parser.ast +sys.modules['erg_compiler.erg_parser.expr'] = erg_parser.expr +", + None, + Some(m.dict()), + )?; + + Ok(()) +} diff --git a/crates/erg_compiler/lib/external/erg_compiler.d/__init__.d.er b/crates/erg_compiler/lib/external/erg_compiler.d/__init__.d.er new file mode 100644 index 000000000..506cc4767 --- /dev/null +++ b/crates/erg_compiler/lib/external/erg_compiler.d/__init__.d.er @@ -0,0 +1,8 @@ +.erg_parser = pyimport "erg_parser" + +.compile: (code: Str, mode: Str) -> Code +.compile_ast: (ast: .erg_parser.ast.AST, mode: Str) -> Code +.compile_file: (path: Str) -> Code +.exec: (code: Str) -> Module +.exec_ast: (ast: .erg_parser.ast.AST) -> Module +.__import__: (name: Str) -> Module diff --git a/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/__init__.d.er b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/__init__.d.er new file mode 100644 index 000000000..f01d88a92 --- /dev/null +++ b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/__init__.d.er @@ -0,0 +1,4 @@ +.ast = pyimport "./ast" +.expr = pyimport "./expr" + +.parse: (code: Str) -> .ast.Module diff --git a/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/ast.d.er b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/ast.d.er new file mode 100644 index 000000000..847f61350 --- /dev/null +++ b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/ast.d.er @@ -0,0 +1,38 @@ +.Token: ClassType +.TokenKind: ClassType + +.Literal: ClassType +.VarName: ClassType +.Identifier: ClassType +.Attribute: ClassType +.TupleAttribute: ClassType +.Subscript: ClassType +.TypeApp: ClassType +.NormalArray: ClassType +.NormalTuple: ClassType +.NormalDict: ClassType +.NormalSet: ClassType +.NormalRecord: ClassType +.BinOp: ClassType +.UnaryOp: ClassType +.Call: ClassType +.Args: ClassType +.Block: ClassType +.DataPack: ClassType +.Lambda: ClassType +.TypeAscription: ClassType +.VarSignature: ClassType +.SubrSignature: ClassType +.Def: ClassType +.Methods: ClassType +.ClassDef: ClassType +.PatchDef: ClassType +.ReDef: ClassType +.Compound: ClassType +.InlineModule: ClassType +.Dummy: ClassType +.Module: ClassType + +.AST: ClassType +.AST. + new: (name: Str, mod: .Module) -> .AST diff --git a/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/expr.d.er b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/expr.d.er new file mode 100644 index 000000000..7ce9abdf0 --- /dev/null +++ b/crates/erg_compiler/lib/external/erg_compiler.d/erg_parser.d/expr.d.er @@ -0,0 +1,20 @@ +.Literal: ClassType +.NormalArray: ClassType +.NormalTuple: ClassType +.NormalDict: ClassType +.NormalSet: ClassType +.NormalRecord: ClassType +.BinOp: ClassType +.UnaryOp: ClassType +.Call: ClassType +.DataPack: ClassType +.Lambda: ClassType +.TypeAscription: ClassType +.Def: ClassType +.Methods: ClassType +.ClassDef: ClassType +.PatchDef: ClassType +.ReDef: ClassType +.Compound: ClassType +.InlineModule: ClassType +.Dummy: ClassType diff --git a/crates/erg_compiler/lib/external/erg_compiler.d/package.er b/crates/erg_compiler/lib/external/erg_compiler.d/package.er new file mode 100644 index 000000000..e69de29bb diff --git a/crates/erg_compiler/link_hir.rs b/crates/erg_compiler/link_hir.rs index 441bedbac..84866ac8f 100644 --- a/crates/erg_compiler/link_hir.rs +++ b/crates/erg_compiler/link_hir.rs @@ -491,7 +491,7 @@ impl<'a> HIRLinker<'a> { if !mod_path .canonicalize() .unwrap() - .starts_with(&dir.canonicalize().unwrap()) + .starts_with(dir.canonicalize().unwrap()) { dir = PathBuf::new(); } diff --git a/crates/erg_compiler/lower.rs b/crates/erg_compiler/lower.rs index 4f9813dbd..d5a76329c 100644 --- a/crates/erg_compiler/lower.rs +++ b/crates/erg_compiler/lower.rs @@ -954,8 +954,8 @@ impl GenericASTLowerer { if let (ast::Expr::Accessor(ast::Accessor::Ident(ident)), None, Some(lhs), Some(rhs)) = ( call.obj.as_ref(), &call.attr_name, - call.args.nth_or_key(0, "object"), - call.args.nth_or_key(1, "classinfo"), + &call.args.nth_or_key(0, "object"), + &call.args.nth_or_key(1, "classinfo"), ) { self.get_bin_guard_type(ident.name.token(), lhs, rhs) } else { diff --git a/crates/erg_compiler/optimize.rs b/crates/erg_compiler/optimize.rs index bd45be25a..ecf779cd4 100644 --- a/crates/erg_compiler/optimize.rs +++ b/crates/erg_compiler/optimize.rs @@ -37,7 +37,7 @@ impl HIROptimizer { fn eliminate_unused_def(&mut self, expr: &mut Expr) { match expr { Expr::Def(def) => { - if def.sig.ident().is_discarded() { + if def.sig.ident().is_discarded() || def.sig.vis().is_public() { return; } if self diff --git a/crates/erg_compiler/tests/example.py b/crates/erg_compiler/tests/example.py new file mode 100644 index 000000000..97417ce12 --- /dev/null +++ b/crates/erg_compiler/tests/example.py @@ -0,0 +1,12 @@ +import erg_compiler +erg_parser = erg_compiler.erg_parser +erg_ast = erg_compiler.erg_parser.ast + +module = erg_parser.parse(".i = 1") +d = module.pop() +d.sig = erg_ast.VarSignature.new(erg_ast.Identifier.public("j"), None) +module.push(d) +ast = erg_ast.AST.new("test", module) +code = erg_compiler.compile_ast(ast) +exec(code) +assert j == 1 diff --git a/crates/erg_compiler/ty/mod.rs b/crates/erg_compiler/ty/mod.rs index 89a58d3be..a9220c1b3 100644 --- a/crates/erg_compiler/ty/mod.rs +++ b/crates/erg_compiler/ty/mod.rs @@ -330,7 +330,10 @@ impl LimitedDisplay for SubrType { pt.typ().limited_fmt(f, limit - 1)?; } if let Some(kw_var_params) = &self.kw_var_params { - if !self.non_default_params.is_empty() || !self.default_params.is_empty() { + if !self.non_default_params.is_empty() + || !self.default_params.is_empty() + || self.var_params.is_some() + { write!(f, ", ")?; } write!(f, "**")?; diff --git a/crates/erg_parser/Cargo.toml b/crates/erg_parser/Cargo.toml index 2fab8236f..9027d3e9c 100644 --- a/crates/erg_parser/Cargo.toml +++ b/crates/erg_parser/Cargo.toml @@ -18,12 +18,17 @@ unicode = ["erg_common/unicode"] pretty = ["erg_common/pretty"] large_thread = ["erg_common/large_thread"] experimental = ["erg_common/experimental"] +pylib = ["dep:pyo3", "erg_common/pylib"] +pylib_parser = ["pylib"] [dependencies] erg_common = { workspace = true } +erg_proc_macros = { workspace = true } unicode-xid = "0.2.4" +pyo3 = { workspace = true, optional = true } [lib] +crate-type = ["cdylib", "rlib"] path = "lib.rs" [[bin]] diff --git a/crates/erg_parser/README.md b/crates/erg_parser/README.md index 4d18423fd..2b7188af0 100644 --- a/crates/erg_parser/README.md +++ b/crates/erg_parser/README.md @@ -1,5 +1,31 @@ # Erg parser -## Why isn't this module but crate? +## Use `erg_parser` as a Python library -For maintainability. This crate has tests. +`erg_parser` can be built as a Python library by using pyo3/maturin. + +### Example + +```python +import erg_parser + +module = erg_parser.parse("x = 1") +for chunk in module: + if isinstance(chunk, erg_parser.expr.Def): + assert chunk.sig.inspect() == "x" +``` + +### Debug install (using venv) + +```python +python -m venv .venv +source .venv/bin/activate +maturin develop --features pylib_parser +``` + +### Release install + +```python +maturin build -i python --release --features pylib_parser +pip install +``` diff --git a/crates/erg_parser/ast.rs b/crates/erg_parser/ast.rs index ee547473b..6443db779 100644 --- a/crates/erg_parser/ast.rs +++ b/crates/erg_parser/ast.rs @@ -19,7 +19,144 @@ use erg_common::{fmt_vec_split_with, Str}; use crate::token::{Token, TokenKind, EQUAL}; +#[cfg(not(feature = "pylib"))] +use erg_proc_macros::staticmethod as to_owned; +#[cfg(feature = "pylib")] +use erg_proc_macros::to_owned; +#[cfg(not(feature = "pylib"))] +use erg_proc_macros::{getter, pyclass, pymethods, pyo3, setter, staticmethod}; +#[cfg(feature = "pylib")] +use pyo3::prelude::*; + +macro_rules! impl_into_py_for_enum { + ($Enum: ident; $($Variant: ident $(,)?)*) => { + #[cfg(feature = "pylib")] + impl IntoPy for $Enum { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + $(Self::$Variant(v) => v.into_py(py),)* + } + } + } + }; +} + +macro_rules! impl_from_py_for_enum { + ($Ty: ty; $($Variant: ident ($inner: ident) $(,)*)*) => { + #[cfg(feature = "pylib")] + impl FromPyObject<'_> for $Ty { + fn extract(ob: &PyAny) -> PyResult { + $(if let Ok(extracted) = ob.extract::<$inner>() { + return Ok(Self::$Variant(extracted)); + } else)* { + Err(PyErr::new::( + format!("expected one of {:?}, but got {}", &[$(stringify!($Variant),)*], ob.get_type().name()?), + )) + } + } + } + }; + ($Ty: ty; $($Variant: ident $(,)*)*) => { + #[cfg(feature = "pylib")] + impl FromPyObject<'_> for $Ty { + fn extract(ob: &PyAny) -> PyResult { + $(if let Ok(extracted) = ob.extract::<$Variant>() { + return Ok(Self::$Variant(extracted)); + } else)* { + Err(PyErr::new::( + format!("expected one of {:?}, but got {}", &[$(stringify!($Variant),)*], ob.get_type().name()?), + )) + } + } + } + }; +} + +macro_rules! impl_py_iter { + ($Ty: ident <$inner: ident>, $Iter: ident, 0) => { + #[cfg(feature = "pylib")] + #[pyclass] + struct $Iter { + inner: std::vec::IntoIter<$inner>, + } + + #[cfg(feature = "pylib")] + #[pymethods] + impl $Iter { + #[allow(clippy::self_named_constructors)] + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<$inner> { + slf.inner.next() + } + } + + #[cfg(feature = "pylib")] + #[pymethods] + impl $Ty { + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = $Iter { + inner: slf.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + + #[pyo3(name = "pop")] + fn _pop(mut slf: PyRefMut<'_, Self>) -> Option<$inner> { + slf.0.pop() + } + + #[pyo3(name = "push")] + fn _push(mut slf: PyRefMut<'_, Self>, item: $inner) { + slf.0.push(item); + } + + #[pyo3(name = "remove")] + fn _remove(mut slf: PyRefMut<'_, Self>, idx: usize) -> $inner { + slf.0.remove(idx) + } + + #[pyo3(name = "insert")] + fn _insert(mut slf: PyRefMut<'_, Self>, idx: usize, item: $inner) { + slf.0.insert(idx, item); + } + } + }; + ($Ty: ident <$inner: ident>, $Iter: ident) => { + #[cfg(feature = "pylib")] + #[pyclass] + struct $Iter { + inner: std::vec::IntoIter<$inner>, + } + + #[cfg(feature = "pylib")] + #[pymethods] + impl $Iter { + #[allow(clippy::self_named_constructors)] + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<$inner> { + slf.inner.next() + } + } + + #[cfg(feature = "pylib")] + #[pymethods] + impl $Ty { + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = $Iter { + inner: slf.clone().into_iter(), + }; + Py::new(slf.py(), iter) + } + } + }; +} + /// Some Erg functions require additional operation by the compiler. +#[pyclass] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OperationKind { Import, @@ -36,6 +173,7 @@ pub enum OperationKind { Cast, } +#[pymethods] impl OperationKind { pub const fn is_erg_import(&self) -> bool { matches!(self, Self::Import) @@ -65,6 +203,7 @@ pub fn fmt_lines<'a, T: NestedDisplay + 'a>( /// リテラルに実際の値が格納された構造体(定数畳み込み用) /// ArrayやDictはまた別に +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Literal { pub token: Token, @@ -87,20 +226,26 @@ impl From for Literal { } impl Literal { - pub const fn new(token: Token) -> Self { + pub fn str(s: impl Into, line: u32) -> Self { + let token = Token::new_fake(TokenKind::StrLit, s, line, 0, 0); Self { token } } +} - pub fn nat(n: usize, line: u32) -> Self { - let token = Token::new_fake(TokenKind::NatLit, Str::from(n.to_string()), line, 0, 0); +#[pymethods] +impl Literal { + #[staticmethod] + pub const fn new(token: Token) -> Self { Self { token } } - pub fn str(s: impl Into, line: u32) -> Self { - let token = Token::new_fake(TokenKind::StrLit, s, line, 0, 0); + #[staticmethod] + pub fn nat(n: usize, line: u32) -> Self { + let token = Token::new_fake(TokenKind::NatLit, Str::from(n.to_string()), line, 0, 0); Self { token } } + #[staticmethod] pub fn bool(b: bool, line: u32) -> Self { let b = if b { "True" } else { "False" }; let token = Token::new_fake(TokenKind::BoolLit, b, line, 0, 0); @@ -118,6 +263,7 @@ impl Literal { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PosArg { pub expr: Expr, @@ -138,6 +284,7 @@ impl PosArg { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct KwArg { pub keyword: Token, @@ -170,6 +317,7 @@ impl KwArg { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Args { pos_args: Vec, @@ -226,18 +374,6 @@ impl Args { } } - pub fn pos_only(pos_arg: Vec, paren: Option<(Token, Token)>) -> Self { - Self::new(pos_arg, None, vec![], None, paren) - } - - pub fn single(pos_args: PosArg) -> Self { - Self::pos_only(vec![pos_args], None) - } - - pub fn empty() -> Self { - Self::new(vec![], None, vec![], None, None) - } - // for replacing to hir::Args #[allow(clippy::type_complexity)] pub fn deconstruct( @@ -258,6 +394,66 @@ impl Args { ) } + pub fn into_iters( + self, + ) -> ( + impl IntoIterator, + impl IntoIterator, + ) { + (self.pos_args.into_iter(), self.kw_args.into_iter()) + } + + pub fn extend_pos(&mut self, iter: I) + where + I: IntoIterator, + { + self.pos_args.extend(iter); + } + + pub fn pos_args(&self) -> &[PosArg] { + &self.pos_args[..] + } +} + +#[pymethods] +impl Args { + #[getter] + #[pyo3(name = "pos_args")] + fn _pos_args(&self) -> Vec { + self.pos_args.clone() + } + + #[getter] + pub fn var_args(&self) -> Option { + self.var_args.as_ref().map(|x| *x.clone()) + } + + #[getter] + #[pyo3(name = "kw_args")] + fn _kw_args(&self) -> Vec { + self.kw_args.clone() + } + + #[getter] + pub fn kw_var_args(&self) -> Option { + self.kw_var_args.as_ref().map(|x| *x.clone()) + } + + #[staticmethod] + pub fn pos_only(pos_arg: Vec, paren: Option<(Token, Token)>) -> Self { + Self::new(pos_arg, None, vec![], None, paren) + } + + #[staticmethod] + pub fn single(pos_args: PosArg) -> Self { + Self::pos_only(vec![pos_args], None) + } + + #[staticmethod] + pub fn empty() -> Self { + Self::new(vec![], None, vec![], None, None) + } + pub fn is_empty(&self) -> bool { self.pos_args.is_empty() && self.kw_args.is_empty() } @@ -270,10 +466,7 @@ impl Args { self.kw_args.is_empty() } - pub fn pos_args(&self) -> &[PosArg] { - &self.pos_args[..] - } - + #[to_owned] pub fn kw_args(&self) -> &[KwArg] { &self.kw_args[..] } @@ -282,26 +475,10 @@ impl Args { self.pos_args.contains(pa) } - pub fn into_iters( - self, - ) -> ( - impl IntoIterator, - impl IntoIterator, - ) { - (self.pos_args.into_iter(), self.kw_args.into_iter()) - } - pub fn push_pos(&mut self, arg: PosArg) { self.pos_args.push(arg); } - pub fn extend_pos(&mut self, iter: I) - where - I: IntoIterator, - { - self.pos_args.extend(iter); - } - pub fn remove_pos(&mut self, index: usize) -> PosArg { self.pos_args.remove(index) } @@ -326,6 +503,7 @@ impl Args { self.paren = Some(paren); } + #[to_owned(cloned)] pub fn get_left_or_key(&self, key: &str) -> Option<&Expr> { if !self.pos_args.is_empty() { self.pos_args.get(0).map(|a| &a.expr) @@ -340,6 +518,7 @@ impl Args { } } + #[to_owned(cloned)] pub fn nth_or_key(&self, nth: usize, key: &str) -> Option<&Expr> { if !self.pos_args.is_empty() { self.pos_args.get(nth).map(|a| &a.expr) @@ -355,6 +534,7 @@ impl Args { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Attribute { pub obj: Box, @@ -374,6 +554,19 @@ impl NestedDisplay for Attribute { impl_display_from_nested!(Attribute); impl_locational!(Attribute, obj, ident); +#[pymethods] +impl Attribute { + #[getter] + pub fn obj(&self) -> Expr { + self.obj.as_ref().clone() + } + + #[getter] + pub fn ident(&self) -> Identifier { + self.ident.clone() + } +} + impl Attribute { pub fn new(obj: Expr, ident: Identifier) -> Self { Self { @@ -383,6 +576,7 @@ impl Attribute { } } +#[pyclass] /// e.g. obj.0, obj.1 #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TupleAttribute { @@ -403,6 +597,19 @@ impl NestedDisplay for TupleAttribute { impl_display_from_nested!(TupleAttribute); impl_locational!(TupleAttribute, obj, index); +#[pymethods] +impl TupleAttribute { + #[getter] + pub fn obj(&self) -> Expr { + self.obj.as_ref().clone() + } + + #[getter] + pub fn index(&self) -> Literal { + self.index.clone() + } +} + impl TupleAttribute { pub fn new(obj: Expr, index: Literal) -> Self { Self { @@ -412,6 +619,7 @@ impl TupleAttribute { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Subscript { pub obj: Box, @@ -432,6 +640,19 @@ impl NestedDisplay for Subscript { impl_display_from_nested!(Subscript); impl_locational!(Subscript, obj, r_sqbr); +#[pymethods] +impl Subscript { + #[getter] + pub fn obj(&self) -> Expr { + self.obj.as_ref().clone() + } + + #[getter] + pub fn index(&self) -> Expr { + self.index.as_ref().clone() + } +} + impl Subscript { pub fn new(obj: Expr, index: Expr, r_sqbr: Token) -> Self { Self { @@ -448,6 +669,32 @@ pub enum TypeAppArgsKind { Args(Args), } +#[cfg(feature = "pylib")] +impl IntoPy for TypeAppArgsKind { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::SubtypeOf(ty) => ty.into_py(py), + Self::Args(args) => args.into_py(py), + } + } +} + +#[cfg(feature = "pylib")] +impl FromPyObject<'_> for TypeAppArgsKind { + fn extract(ob: &PyAny) -> PyResult { + if let Ok(ty) = ob.extract::() { + Ok(Self::SubtypeOf(Box::new(ty))) + } else if let Ok(args) = ob.extract::() { + Ok(Self::Args(args)) + } else { + Err(PyErr::new::(format!( + "expected one of {:?}", + &["TypeSpecWithOp", "Args"] + ))) + } + } +} + impl NestedDisplay for TypeAppArgsKind { fn fmt_nest(&self, f: &mut std::fmt::Formatter<'_>, _level: usize) -> std::fmt::Result { match self { @@ -460,6 +707,7 @@ impl NestedDisplay for TypeAppArgsKind { impl_display_from_nested!(TypeAppArgsKind); impl_locational_for_enum!(TypeAppArgsKind; SubtypeOf, Args); +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TypeAppArgs { pub l_vbar: Token, @@ -487,6 +735,7 @@ impl TypeAppArgs { } /// f|T := Int| +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TypeApp { pub obj: Box, @@ -527,6 +776,8 @@ pub enum Accessor { impl_nested_display_for_enum!(Accessor; Ident, Attr, TupleAttr, Subscr, TypeApp); impl_display_from_nested!(Accessor); impl_locational_for_enum!(Accessor; Ident, Attr, TupleAttr, Subscr, TypeApp); +impl_into_py_for_enum!(Accessor; Ident, Attr, TupleAttr, Subscr, TypeApp); +impl_from_py_for_enum!(Accessor; Ident(Identifier), Attr(Attribute), TupleAttr(TupleAttribute), Subscr(Subscript), TypeApp(TypeApp)); impl Accessor { pub const fn local(symbol: Token) -> Self { @@ -591,6 +842,7 @@ impl Accessor { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NormalArray { pub l_sqbr: Token, @@ -609,6 +861,14 @@ impl NestedDisplay for NormalArray { impl_display_from_nested!(NormalArray); impl_locational!(NormalArray, l_sqbr, elems, r_sqbr); +#[pymethods] +impl NormalArray { + #[pyo3(name = "get")] + fn _get(&self, index: usize) -> Option { + self.get(index).cloned() + } +} + impl NormalArray { pub const fn new(l_sqbr: Token, r_sqbr: Token, elems: Args) -> Self { Self { @@ -623,6 +883,7 @@ impl NormalArray { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayWithLength { pub l_sqbr: Token, @@ -651,6 +912,7 @@ impl ArrayWithLength { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayComprehension { pub l_sqbr: Token, @@ -707,6 +969,8 @@ pub enum Array { impl_nested_display_for_enum!(Array; Normal, WithLength, Comprehension); impl_display_for_enum!(Array; Normal, WithLength, Comprehension); impl_locational_for_enum!(Array; Normal, WithLength, Comprehension); +impl_into_py_for_enum!(Array; Normal, WithLength, Comprehension); +impl_from_py_for_enum!(Array; Normal(NormalArray), WithLength(ArrayWithLength), Comprehension(ArrayComprehension)); impl Array { pub fn get(&self, index: usize) -> Option<&Expr> { @@ -717,6 +981,7 @@ impl Array { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NormalTuple { pub elems: Args, @@ -754,6 +1019,8 @@ pub enum Tuple { impl_nested_display_for_enum!(Tuple; Normal); impl_display_for_enum!(Tuple; Normal); impl_locational_for_enum!(Tuple; Normal); +impl_into_py_for_enum!(Tuple; Normal); +impl_from_py_for_enum!(Tuple; Normal(NormalTuple)); impl Tuple { pub fn paren(&self) -> Option<&(Token, Token)> { @@ -763,6 +1030,7 @@ impl Tuple { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyValue { pub key: Expr, @@ -784,6 +1052,7 @@ impl KeyValue { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct NormalDict { pub l_brace: Token, @@ -810,6 +1079,7 @@ impl NormalDict { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct DictComprehension { l_brace: Token, @@ -864,6 +1134,8 @@ pub enum Dict { impl_nested_display_for_enum!(Dict; Normal, Comprehension); impl_display_for_enum!(Dict; Normal, Comprehension); impl_locational_for_enum!(Dict; Normal, Comprehension); +impl_into_py_for_enum!(Dict; Normal, Comprehension); +impl_from_py_for_enum!(Dict; Normal(NormalDict), Comprehension(DictComprehension)); impl Dict { pub fn braces(&self) -> (&Token, &Token) { @@ -884,7 +1156,9 @@ pub enum ClassAttr { impl_nested_display_for_enum!(ClassAttr; Def, Decl, Doc); impl_display_for_enum!(ClassAttr; Def, Decl, Doc); impl_locational_for_enum!(ClassAttr; Def, Decl, Doc); +impl_into_py_for_enum!(ClassAttr; Def, Decl, Doc); +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ClassAttrs(Vec); @@ -909,9 +1183,11 @@ impl From> for ClassAttrs { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RecordAttrs(Vec); +impl_py_iter!(RecordAttrs, DefIter); impl_stream!(RecordAttrs, Def); impl NestedDisplay for RecordAttrs { @@ -933,6 +1209,7 @@ impl From> for RecordAttrs { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct NormalRecord { pub l_brace: Token, @@ -979,6 +1256,19 @@ impl From for NormalRecord { } } +#[pymethods] +impl NormalRecord { + #[pyo3(name = "get")] + fn _get(&self, name: &str) -> Option { + self.get(name).cloned() + } + + #[pyo3(name = "keys")] + fn _keys(&self) -> Vec { + self.keys().cloned().collect() + } +} + impl NormalRecord { pub const fn new(l_brace: Token, r_brace: Token, attrs: RecordAttrs) -> Self { Self { @@ -1020,6 +1310,8 @@ pub enum Record { impl_nested_display_for_enum!(Record; Normal, Mixed); impl_display_for_enum!(Record; Normal, Mixed); impl_locational_for_enum!(Record; Normal, Mixed); +impl_into_py_for_enum!(Record; Normal, Mixed); +impl_from_py_for_enum!(Record; Normal(NormalRecord), Mixed(MixedRecord)); impl Record { pub const fn new_mixed(l_brace: Token, r_brace: Token, attrs: Vec) -> Self { @@ -1069,6 +1361,7 @@ impl Record { } /// Record can be defined with shorthend/normal mixed style, i.e. {x; y=expr; z; ...} +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MixedRecord { pub l_brace: Token, @@ -1089,6 +1382,19 @@ impl NestedDisplay for MixedRecord { impl_display_from_nested!(MixedRecord); impl_locational!(MixedRecord, l_brace, r_brace); +#[pymethods] +impl MixedRecord { + #[pyo3(name = "get")] + fn _get(&self, name: &str) -> Option { + self.get(name).cloned() + } + + #[pyo3(name = "keys")] + fn _keys(&self) -> Vec { + self.keys().cloned().collect() + } +} + impl MixedRecord { pub const fn new(l_brace: Token, r_brace: Token, attrs: Vec) -> Self { Self { @@ -1098,6 +1404,24 @@ impl MixedRecord { } } + pub fn get(&self, name: &str) -> Option<&RecordAttrOrIdent> { + for attr in self.attrs.iter() { + match attr { + RecordAttrOrIdent::Attr(def) => { + if def.sig.ident().is_some_and(|n| n.inspect() == name) { + return Some(attr); + } + } + RecordAttrOrIdent::Ident(ident) => { + if ident.inspect() == name { + return Some(attr); + } + } + } + } + None + } + pub fn keys(&self) -> impl Iterator { self.attrs.iter().filter_map(|attr| match attr { RecordAttrOrIdent::Attr(attr) => attr.sig.ident(), @@ -1115,6 +1439,8 @@ pub enum RecordAttrOrIdent { impl_nested_display_for_enum!(RecordAttrOrIdent; Attr, Ident); impl_display_for_enum!(RecordAttrOrIdent; Attr, Ident); impl_locational_for_enum!(RecordAttrOrIdent; Attr, Ident); +impl_into_py_for_enum!(RecordAttrOrIdent; Attr, Ident); +impl_from_py_for_enum!(RecordAttrOrIdent; Attr(Def), Ident(Identifier)); impl RecordAttrOrIdent { pub fn ident(&self) -> Option<&Identifier> { @@ -1125,6 +1451,7 @@ impl RecordAttrOrIdent { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct NormalSet { pub l_brace: Token, @@ -1149,6 +1476,14 @@ impl From for Expr { } } +#[pymethods] +impl NormalSet { + #[pyo3(name = "get")] + fn _get(&self, index: usize) -> Option { + self.get(index).cloned() + } +} + impl NormalSet { pub const fn new(l_brace: Token, r_brace: Token, elems: Args) -> Self { Self { @@ -1157,8 +1492,13 @@ impl NormalSet { elems, } } + + pub fn get(&self, index: usize) -> Option<&Expr> { + self.elems.pos_args.get(index).map(|a| &a.expr) + } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SetWithLength { pub l_brace: Token, @@ -1187,6 +1527,7 @@ impl SetWithLength { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SetComprehension { pub l_brace: Token, @@ -1242,7 +1583,10 @@ pub enum Set { impl_nested_display_for_enum!(Set; Normal, WithLength, Comprehension); impl_display_for_enum!(Set; Normal, WithLength, Comprehension); impl_locational_for_enum!(Set; Normal, WithLength, Comprehension); +impl_into_py_for_enum!(Set; Normal, WithLength, Comprehension); +impl_from_py_for_enum!(Set; Normal(NormalSet), WithLength(SetWithLength), Comprehension(SetComprehension)); +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BinOp { pub op: Token, @@ -1281,6 +1625,26 @@ impl BinOp { } } +#[pymethods] +impl BinOp { + pub fn lhs(&self) -> Expr { + self.args[0].as_ref().clone() + } + + pub fn rhs(&self) -> Expr { + self.args[1].as_ref().clone() + } + + pub fn set_lhs(&mut self, lhs: Expr) { + self.args[0] = Box::new(lhs); + } + + pub fn set_rhs(&mut self, rhs: Expr) { + self.args[1] = Box::new(rhs); + } +} + +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct UnaryOp { pub op: Token, @@ -1316,6 +1680,18 @@ impl UnaryOp { } } +#[pymethods] +impl UnaryOp { + pub fn value(&self) -> Expr { + self.args[0].as_ref().clone() + } + + pub fn set_value(&mut self, value: Expr) { + self.args[0] = Box::new(value); + } +} + +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Call { pub obj: Box, @@ -1383,6 +1759,39 @@ impl Locational for Call { } } +#[pymethods] +impl Call { + #[getter] + pub fn get_obj(&self) -> Expr { + self.obj.as_ref().clone() + } + + #[setter] + pub fn set_obj(&mut self, obj: Expr) { + self.obj = Box::new(obj); + } + + #[getter] + pub fn get_attr_name(&self) -> Option { + self.attr_name.clone() + } + + #[setter] + pub fn set_attr_name(&mut self, attr_name: Option) { + self.attr_name = attr_name; + } + + #[getter] + pub fn get_args(&self) -> Args { + self.args.clone() + } + + #[setter] + pub fn set_args(&mut self, args: Args) { + self.args = args; + } +} + impl Call { pub fn new(obj: Expr, attr_name: Option, args: Args) -> Self { Self { @@ -1415,6 +1824,7 @@ impl Call { } /// e.g. `Data::{x = 1; y = 2}` +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct DataPack { pub class: Box, @@ -1446,6 +1856,7 @@ impl DataPack { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Block(Vec); @@ -1468,7 +1879,9 @@ impl Locational for Block { } impl_stream!(Block, Expr); +impl_py_iter!(Block, BlockIter, 0); +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Dummy { pub loc: Option, @@ -1529,6 +1942,7 @@ impl Dummy { pub type ConstIdentifier = Identifier; +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstAttribute { pub obj: Box, @@ -1561,6 +1975,7 @@ impl ConstAttribute { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstTupleAttribute { tup: Box, @@ -1589,6 +2004,7 @@ impl ConstTupleAttribute { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstSubscript { obj: Box, @@ -1628,6 +2044,8 @@ pub enum ConstAccessor { impl_nested_display_for_enum!(ConstAccessor; Local, Attr, TupleAttr, Subscr); impl_display_from_nested!(ConstAccessor); impl_locational_for_enum!(ConstAccessor; Local, Attr, TupleAttr, Subscr); +impl_into_py_for_enum!(ConstAccessor; Local, Attr, TupleAttr, Subscr); +impl_from_py_for_enum!(ConstAccessor; Local(ConstIdentifier), Attr(ConstAttribute), TupleAttr(ConstTupleAttribute), Subscr(ConstSubscript)); impl ConstAccessor { pub const fn local(symbol: Token) -> Self { @@ -1665,6 +2083,8 @@ pub enum ConstArray { impl_nested_display_for_enum!(ConstArray; Normal, WithLength); impl_display_from_nested!(ConstArray); impl_locational_for_enum!(ConstArray; Normal, WithLength); +impl_into_py_for_enum!(ConstArray; Normal, WithLength); +impl_from_py_for_enum!(ConstArray; Normal(ConstNormalArray), WithLength(ConstArrayWithLength)); impl ConstArray { pub fn downgrade(self) -> Array { @@ -1675,6 +2095,7 @@ impl ConstArray { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstNormalArray { pub l_sqbr: Token, @@ -1711,6 +2132,7 @@ impl ConstNormalArray { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstArrayWithLength { pub l_sqbr: Token, @@ -1748,6 +2170,7 @@ impl ConstArrayWithLength { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstNormalSet { pub l_brace: Token, @@ -1778,6 +2201,7 @@ impl ConstNormalSet { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstSetComprehension { pub l_brace: Token, @@ -1845,6 +2269,8 @@ pub enum ConstSet { impl_nested_display_for_enum!(ConstSet; Normal, Comprehension); impl_display_from_nested!(ConstSet); impl_locational_for_enum!(ConstSet; Normal, Comprehension); +impl_into_py_for_enum!(ConstSet; Normal, Comprehension); +impl_from_py_for_enum!(ConstSet; Normal(ConstNormalSet), Comprehension(ConstSetComprehension)); impl ConstSet { pub fn downgrade(self) -> Set { @@ -1855,6 +2281,7 @@ impl ConstSet { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ConstKeyValue { pub key: ConstExpr, @@ -1880,6 +2307,7 @@ impl ConstKeyValue { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstDict { l_brace: Token, @@ -1914,6 +2342,7 @@ impl ConstDict { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstTuple { pub elems: ConstArgs, @@ -1938,6 +2367,7 @@ impl ConstTuple { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstBlock(Vec); @@ -1960,6 +2390,7 @@ impl Locational for ConstBlock { } impl_stream!(ConstBlock, ConstExpr); +impl_py_iter!(ConstBlock, ConstExprIter); impl ConstBlock { pub fn downgrade(self) -> Block { @@ -1967,6 +2398,7 @@ impl ConstBlock { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstDefBody { pub op: Token, @@ -1986,6 +2418,7 @@ impl ConstDefBody { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstDef { pub ident: ConstIdentifier, @@ -2011,6 +2444,7 @@ impl ConstDef { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstLambda { pub sig: Box, @@ -2043,6 +2477,7 @@ impl ConstLambda { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstRecord { pub l_brace: Token, @@ -2082,6 +2517,7 @@ impl ConstRecord { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstBinOp { pub op: Token, @@ -2112,6 +2548,7 @@ impl ConstBinOp { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstUnaryOp { pub op: Token, @@ -2142,6 +2579,7 @@ impl ConstUnaryOp { /// Application /// ex. `Vec Int` of `Option Vec Int` +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstApp { pub obj: Box, @@ -2194,6 +2632,7 @@ impl ConstApp { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstTypeAsc { pub expr: Box, @@ -2252,6 +2691,8 @@ pub enum ConstExpr { impl_nested_display_for_chunk_enum!(ConstExpr; Lit, Accessor, App, Array, Set, Dict, Tuple, Record, BinOp, UnaryOp, Def, Lambda, Set, TypeAsc); impl_display_from_nested!(ConstExpr); impl_locational_for_enum!(ConstExpr; Lit, Accessor, App, Array, Set, Dict, Tuple, Record, BinOp, UnaryOp, Def, Lambda, Set, TypeAsc); +impl_into_py_for_enum!(ConstExpr; Lit, Accessor, App, Array, Set, Dict, Tuple, Record, Def, Lambda, BinOp, UnaryOp, TypeAsc); +impl_from_py_for_enum!(ConstExpr; Lit(Literal), Accessor(ConstAccessor), App(ConstApp), Array(ConstArray), Set(ConstSet), Dict(ConstDict), Tuple(ConstTuple), Record(ConstRecord), Def(ConstDef), Lambda(ConstLambda), BinOp(ConstBinOp), UnaryOp(ConstUnaryOp), TypeAsc(ConstTypeAsc)); impl TryFrom<&ParamPattern> for ConstExpr { type Error = (); @@ -2297,6 +2738,7 @@ impl ConstExpr { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstPosArg { pub expr: ConstExpr, @@ -2316,6 +2758,7 @@ impl ConstPosArg { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstKwArg { pub keyword: Token, @@ -2336,6 +2779,7 @@ impl ConstKwArg { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ConstArgs { pos_args: Vec, @@ -2467,6 +2911,7 @@ impl ConstArgs { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct PolyTypeSpec { pub acc: ConstAccessor, @@ -2576,6 +3021,7 @@ impl PreDeclTypeSpec { } } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ParamTySpec { pub name: Option, @@ -2612,6 +3058,7 @@ impl ParamTySpec { } } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DefaultParamTySpec { pub param: ParamTySpec, @@ -2630,6 +3077,7 @@ impl DefaultParamTySpec { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SubrTypeSpec { pub bounds: TypeBoundSpecs, @@ -2703,6 +3151,7 @@ impl SubrTypeSpec { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ArrayTypeSpec { pub sqbrs: Option<(Token, Token)>, @@ -2736,6 +3185,7 @@ impl ArrayTypeSpec { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SetWithLenTypeSpec { pub ty: Box, @@ -2759,6 +3209,7 @@ impl SetWithLenTypeSpec { } } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TupleTypeSpec { pub parens: Option<(Token, Token)>, @@ -2789,6 +3240,7 @@ impl TupleTypeSpec { } } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct DictTypeSpec { pub braces: Option<(Token, Token)>, @@ -2825,6 +3277,7 @@ impl DictTypeSpec { } } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RecordTypeSpec { pub braces: Option<(Token, Token)>, @@ -2861,6 +3314,7 @@ impl RecordTypeSpec { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RefinementTypeSpec { pub var: Token, @@ -2929,6 +3383,14 @@ pub enum TypeSpec { Refinement(RefinementTypeSpec), } +// TODO: +#[cfg(feature = "pylib")] +impl IntoPy for TypeSpec { + fn into_py(self, py: Python<'_>) -> PyObject { + pyo3::types::PyNone::get(py).into() + } +} + impl fmt::Display for TypeSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -3035,6 +3497,7 @@ impl TypeSpec { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TypeSpecWithOp { pub op: Token, @@ -3124,6 +3587,7 @@ impl TypeBoundSpec { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TypeBoundSpecs(Vec); @@ -3141,6 +3605,7 @@ impl Locational for TypeBoundSpecs { /// デコレータは関数を返す関数オブジェクトならば何でも指定できる /// e.g. @(x -> x) +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Decorator(pub Expr); @@ -3159,6 +3624,7 @@ impl Decorator { } /// symbol as a left value +#[pyclass] #[derive(Debug, Clone, Eq)] pub struct VarName(Token); @@ -3208,24 +3674,33 @@ impl fmt::Display for VarName { } } +#[pymethods] impl VarName { - pub const fn new(symbol: Token) -> Self { - Self(symbol) + pub fn __repr__(&self) -> String { + format!("VarName({})", self) } - pub const fn from_static(symbol: &'static str) -> Self { - Self(Token::static_symbol(symbol)) + pub fn __str__(&self) -> String { + format!("VarName({})", self) + } + + #[staticmethod] + pub const fn new(symbol: Token) -> Self { + Self(symbol) } + #[staticmethod] #[allow(clippy::should_implement_trait)] pub fn from_str(symbol: Str) -> Self { Self(Token::from_str(TokenKind::Symbol, &symbol)) } + #[staticmethod] pub fn from_str_and_line(symbol: Str, line: u32) -> Self { Self(Token::new_fake(TokenKind::Symbol, symbol, line, 0, 0)) } + #[staticmethod] pub fn from_str_and_loc(symbol: Str, loc: Location) -> Self { Self(Token::new_with_loc(TokenKind::Symbol, symbol, loc)) } @@ -3256,6 +3731,12 @@ impl VarName { pub fn is_generated(&self) -> bool { self.0.content.starts_with('%') } +} + +impl VarName { + pub const fn from_static(symbol: &'static str) -> Self { + Self(Token::static_symbol(symbol)) + } pub const fn token(&self) -> &Token { &self.0 @@ -3283,6 +3764,7 @@ impl VarName { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Namespaces(Vec); @@ -3318,6 +3800,7 @@ pub enum VisRestriction { impl_locational_for_enum!(VisRestriction; Namespaces, SubtypeOf); impl_display_from_nested!(VisRestriction); +impl_into_py_for_enum!(VisRestriction; Namespaces, SubtypeOf); impl NestedDisplay for VisRestriction { fn fmt_nest(&self, f: &mut fmt::Formatter<'_>, _level: usize) -> fmt::Result { @@ -3337,6 +3820,19 @@ pub enum VisModifierSpec { Restricted(VisRestriction), } +#[cfg(feature = "pylib")] +impl IntoPy for VisModifierSpec { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::Private => py.None(), + Self::Auto => py.None(), + Self::Public(token) => token.into_py(py), + Self::ExplicitPrivate(token) => token.into_py(py), + Self::Restricted(rest) => rest.into_py(py), + } + } +} + impl NestedDisplay for VisModifierSpec { fn fmt_nest(&self, f: &mut fmt::Formatter<'_>, _level: usize) -> fmt::Result { match self { @@ -3380,6 +3876,7 @@ impl VisModifierSpec { } } +#[pyclass] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum AccessModifier { Private, // `::` @@ -3388,6 +3885,7 @@ pub enum AccessModifier { Force, // can access any identifiers } +#[pyclass(get_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Identifier { pub vis: VisModifierSpec, @@ -3415,18 +3913,57 @@ impl From for Expr { } } +#[pymethods] impl Identifier { - pub const fn new(vis: VisModifierSpec, name: VarName) -> Self { - Self { vis, name } + pub fn __repr__(&self) -> String { + format!("Identifier({})", self) } - pub fn static_public(name: &'static str) -> Self { - Self::new( - VisModifierSpec::Public(Token::from_str(TokenKind::Dot, ".")), - VarName::from_static(name), - ) + pub fn __str__(&self) -> String { + format!("Identifier({})", self) + } + + pub fn is_const(&self) -> bool { + self.name.is_const() + } + + pub fn is_discarded(&self) -> bool { + self.name.is_discarded() + } + + pub fn is_raw(&self) -> bool { + self.name.is_raw() + } + + pub fn acc_kind(&self) -> AccessModifier { + match &self.vis { + VisModifierSpec::Auto => AccessModifier::Auto, + VisModifierSpec::Public(_) => AccessModifier::Public, + VisModifierSpec::ExplicitPrivate(_) + | VisModifierSpec::Restricted(_) + | VisModifierSpec::Private => AccessModifier::Private, + } } + pub fn is_procedural(&self) -> bool { + self.name.is_procedural() + } + + pub fn trim_end_proc_mark(&mut self) { + self.name.trim_end_proc_mark(); + } + + #[pyo3(name = "inspect")] + fn _inspect(&self) -> Str { + self.name.inspect().clone() + } + + #[setter] + pub fn set_name(&mut self, name: VarName) { + self.name = name; + } + + #[staticmethod] pub fn public(name: Str) -> Self { Self::new( VisModifierSpec::Public(Token::from_str(TokenKind::Dot, ".")), @@ -3434,18 +3971,22 @@ impl Identifier { ) } + #[staticmethod] pub fn private(name: Str) -> Self { Self::new(VisModifierSpec::Private, VarName::from_str(name)) } + #[staticmethod] pub fn private_from_token(symbol: Token) -> Self { Self::new(VisModifierSpec::Private, VarName::new(symbol)) } + #[staticmethod] pub fn private_from_varname(name: VarName) -> Self { Self::new(VisModifierSpec::Private, name) } + #[staticmethod] pub fn private_with_line(name: Str, line: u32) -> Self { Self::new( VisModifierSpec::Private, @@ -3453,6 +3994,7 @@ impl Identifier { ) } + #[staticmethod] pub fn private_with_loc(name: Str, loc: Location) -> Self { Self::new( VisModifierSpec::Private, @@ -3460,6 +4002,7 @@ impl Identifier { ) } + #[staticmethod] pub fn public_with_line(dot: Token, name: Str, line: u32) -> Self { Self::new( VisModifierSpec::Public(dot), @@ -3467,6 +4010,7 @@ impl Identifier { ) } + #[staticmethod] pub fn public_with_loc(dot: Token, name: Str, loc: Location) -> Self { Self::new( VisModifierSpec::Public(dot), @@ -3474,48 +4018,33 @@ impl Identifier { ) } + #[staticmethod] pub fn public_from_token(dot: Token, symbol: Token) -> Self { Self::new(VisModifierSpec::Public(dot), VarName::new(symbol)) } + #[staticmethod] pub fn auto(name: Str) -> Self { Self::new(VisModifierSpec::Auto, VarName::from_str(name)) } +} - pub fn is_const(&self) -> bool { - self.name.is_const() - } - - pub fn is_discarded(&self) -> bool { - self.name.is_discarded() - } - - pub fn is_raw(&self) -> bool { - self.name.is_raw() +impl Identifier { + pub fn static_public(name: &'static str) -> Self { + Self::new( + VisModifierSpec::Public(Token::from_str(TokenKind::Dot, ".")), + VarName::from_static(name), + ) } - pub fn acc_kind(&self) -> AccessModifier { - match &self.vis { - VisModifierSpec::Auto => AccessModifier::Auto, - VisModifierSpec::Public(_) => AccessModifier::Public, - VisModifierSpec::ExplicitPrivate(_) - | VisModifierSpec::Restricted(_) - | VisModifierSpec::Private => AccessModifier::Private, - } + pub const fn new(vis: VisModifierSpec, name: VarName) -> Self { + Self { vis, name } } pub const fn inspect(&self) -> &Str { self.name.inspect() } - pub fn is_procedural(&self) -> bool { - self.name.is_procedural() - } - - pub fn trim_end_proc_mark(&mut self) { - self.name.trim_end_proc_mark(); - } - pub fn call1(self, arg: Expr) -> Call { Call::new( self.into(), @@ -3537,6 +4066,7 @@ impl Identifier { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarArrayPattern { l_sqbr: Token, @@ -3577,6 +4107,7 @@ impl VarArrayPattern { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarTuplePattern { pub(crate) paren: Option<(Token, Token)>, @@ -3619,6 +4150,7 @@ impl VarTuplePattern { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarRecordAttr { pub lhs: Identifier, @@ -3640,6 +4172,7 @@ impl VarRecordAttr { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarRecordAttrs { pub(crate) elems: Vec, @@ -3664,6 +4197,7 @@ impl VarRecordAttrs { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarRecordPattern { l_brace: Token, @@ -3689,6 +4223,7 @@ impl VarRecordPattern { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarDataPackPattern { pub class: TypeSpec, @@ -3743,6 +4278,8 @@ impl NestedDisplay for VarPattern { impl_display_from_nested!(VarPattern); impl_locational_for_enum!(VarPattern; Discard, Ident, Array, Tuple, Record, DataPack); +impl_into_py_for_enum!(VarPattern; Discard, Ident, Array, Tuple, Record, DataPack); +impl_from_py_for_enum!(VarPattern; Discard(Token), Ident(Identifier), Array(VarArrayPattern), Tuple(VarTuplePattern), Record(VarRecordPattern), DataPack(VarDataPackPattern)); impl VarPattern { pub const fn inspect(&self) -> Option<&Str> { @@ -3768,7 +4305,7 @@ impl VarPattern { } } - pub fn vis(&self) -> &VisModifierSpec { + pub const fn vis(&self) -> &VisModifierSpec { match self { Self::Ident(ident) => &ident.vis, // TODO: `[.x, .y]`? @@ -3776,7 +4313,7 @@ impl VarPattern { } } - pub fn ident(&self) -> Option<&Identifier> { + pub const fn ident(&self) -> Option<&Identifier> { match self { Self::Ident(ident) => Some(ident), _ => None, @@ -3784,6 +4321,7 @@ impl VarPattern { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct VarSignature { pub pat: VarPattern, @@ -3808,20 +4346,46 @@ impl Locational for VarSignature { } } +#[pymethods] impl VarSignature { - pub const fn new(pat: VarPattern, t_spec: Option) -> Self { - Self { pat, t_spec } + #[pyo3(name = "inspect")] + fn _inspect(&self) -> Option { + self.pat.inspect().cloned() } - pub const fn inspect(&self) -> Option<&Str> { - self.pat.inspect() + #[pyo3(name = "ident")] + fn _ident(&self) -> Option { + match &self.pat { + VarPattern::Ident(ident) => Some(ident), + _ => None, + } + .cloned() + } + + #[staticmethod] + pub const fn new(pat: VarPattern, t_spec: Option) -> Self { + Self { pat, t_spec } } pub fn is_const(&self) -> bool { self.pat.is_const() } - pub fn vis(&self) -> &VisModifierSpec { + pub fn __repr__(&self) -> String { + format!("VarSignature({})", self) + } + + pub fn __str__(&self) -> String { + format!("VarSignature({})", self) + } +} + +impl VarSignature { + pub const fn inspect(&self) -> Option<&Str> { + self.pat.inspect() + } + + pub const fn vis(&self) -> &VisModifierSpec { self.pat.vis() } @@ -3833,6 +4397,7 @@ impl VarSignature { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Vars { pub(crate) elems: Vec, @@ -3867,6 +4432,7 @@ impl Vars { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ParamArrayPattern { pub l_sqbr: Token, @@ -3918,7 +4484,9 @@ impl TryFrom<&ParamArrayPattern> for ConstExpr { } } +#[pymethods] impl ParamArrayPattern { + #[staticmethod] pub const fn new(l_sqbr: Token, elems: Params, r_sqbr: Token) -> Self { Self { l_sqbr, @@ -3935,6 +4503,7 @@ impl ParamArrayPattern { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ParamTuplePattern { pub elems: Params, @@ -3981,6 +4550,7 @@ impl ParamTuplePattern { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ParamRecordAttr { pub lhs: Identifier, @@ -4002,6 +4572,7 @@ impl ParamRecordAttr { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ParamRecordAttrs { pub(crate) elems: Vec, @@ -4030,6 +4601,7 @@ impl ParamRecordAttrs { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ParamRecordPattern { pub(crate) l_brace: Token, @@ -4071,6 +4643,8 @@ pub enum ParamPattern { RefMut(VarName), } +impl_into_py_for_enum!(ParamPattern; Discard, VarName, Lit, Array, Tuple, Record, Ref, RefMut); + impl NestedDisplay for ParamPattern { fn fmt_nest(&self, f: &mut fmt::Formatter<'_>, _level: usize) -> fmt::Result { match self { @@ -4142,6 +4716,7 @@ impl ParamPattern { } /// Once the default_value is set to Some, all subsequent values must be Some +#[pyclass(get_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct NonDefaultParamSignature { pub pat: ParamPattern, @@ -4185,6 +4760,7 @@ impl NonDefaultParamSignature { } /// Once the default_value is set to Some, all subsequent values must be Some +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct DefaultParamSignature { pub sig: NonDefaultParamSignature, @@ -4231,6 +4807,7 @@ impl NestedDisplay for GuardClause { } impl_display_from_nested!(GuardClause); +impl_into_py_for_enum!(GuardClause; Condition, Bind); impl Locational for GuardClause { fn loc(&self) -> Location { @@ -4241,6 +4818,7 @@ impl Locational for GuardClause { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Params { pub non_defaults: Vec, @@ -4310,6 +4888,34 @@ type RawParams = ( Option<(Token, Token)>, ); +#[pymethods] +impl Params { + #[getter] + pub fn non_defaults(&self) -> Vec { + self.non_defaults.clone() + } + + #[getter] + pub fn var_params(&self) -> Option { + self.var_params.as_deref().cloned() + } + + #[getter] + pub fn defaults(&self) -> Vec { + self.defaults.clone() + } + + #[getter] + pub fn kw_var_params(&self) -> Option { + self.kw_var_params.as_deref().cloned() + } + + #[getter] + pub fn guards(&self) -> Vec { + self.guards.clone() + } +} + impl Params { pub fn new( non_defaults: Vec, @@ -4370,6 +4976,7 @@ impl Params { } /// 引数を取るならTypeでもSubr扱い +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SubrSignature { pub decorators: HashSet, @@ -4442,6 +5049,7 @@ impl SubrSignature { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct LambdaSignature { pub bounds: TypeBoundSpecs, @@ -4507,6 +5115,7 @@ impl LambdaSignature { } } +#[pyclass(subclass)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DefId(pub usize); @@ -4516,6 +5125,7 @@ impl DefId { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Lambda { pub sig: LambdaSignature, @@ -4556,6 +5166,8 @@ pub enum Signature { impl_nested_display_for_chunk_enum!(Signature; Var, Subr); impl_display_from_nested!(Signature); impl_locational_for_enum!(Signature; Var, Subr); +impl_into_py_for_enum!(Signature; Var, Subr); +impl_from_py_for_enum!(Signature; Var(VarSignature), Subr(SubrSignature)); impl Signature { pub fn name_as_str(&self) -> Option<&Str> { @@ -4656,6 +5268,7 @@ impl Signature { } } +#[pyclass] #[derive(Debug, Clone, Copy)] pub enum AscriptionKind { TypeOf, @@ -4673,6 +5286,7 @@ impl AscriptionKind { /// type_ascription ::= expr ':' type /// | expr '<:' type /// | expr ':>' type +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct TypeAscription { pub expr: Box, @@ -4701,6 +5315,7 @@ impl TypeAscription { } } +#[pyclass] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum DefKind { Class, @@ -4750,6 +5365,7 @@ impl DefKind { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct DefBody { pub op: Token, @@ -4797,6 +5413,7 @@ impl DefBody { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Def { pub sig: Signature, @@ -4837,6 +5454,7 @@ impl Def { } /// This is not necessary for Erg syntax, but necessary for mapping ASTs in Python +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ReDef { pub attr: Accessor, @@ -4870,6 +5488,7 @@ impl ReDef { /// x = 1 /// f(a) = ... /// ``` +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Methods { pub id: DefId, @@ -4907,6 +5526,7 @@ impl Methods { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ClassDef { pub def: Def, @@ -4937,6 +5557,7 @@ impl ClassDef { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct PatchDef { pub def: Def, @@ -4967,6 +5588,7 @@ impl PatchDef { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Compound { pub exprs: Vec, @@ -5031,6 +5653,8 @@ impl_nested_display_for_chunk_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, impl_from_trait_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Compound, InlineModule, Dummy); impl_display_from_nested!(Expr); impl_locational_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Compound, InlineModule, Dummy); +impl_into_py_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Compound, InlineModule, Dummy); +impl_from_py_for_enum!(Expr; Literal, Accessor, Array, Tuple, Dict, Set, Record, BinOp, UnaryOp, Call, DataPack, Lambda, TypeAscription, Def, Methods, ClassDef, PatchDef, ReDef, Compound, InlineModule, Dummy); impl Expr { pub fn is_match_call(&self) -> bool { @@ -5252,6 +5876,7 @@ impl Expr { } } +#[pyclass] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Module(Block); @@ -5296,6 +5921,8 @@ impl FromIterator for Module { } } +impl_py_iter!(Module, ModuleIter, 0); + impl Module { pub const fn empty() -> Self { Self(Block::empty()) @@ -5313,6 +5940,7 @@ impl Module { } } +#[pyclass(get_all, set_all)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AST { pub name: Str, @@ -5328,7 +5956,9 @@ impl NestedDisplay for AST { impl_display_from_nested!(AST); impl_locational!(AST, module); +#[pymethods] impl AST { + #[staticmethod] pub const fn new(name: Str, module: Module) -> Self { Self { name, module } } @@ -5338,6 +5968,7 @@ impl AST { } } +#[pyclass] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlineModule { pub input: Input, @@ -5355,6 +5986,14 @@ impl NestedDisplay for InlineModule { impl_display_from_nested!(InlineModule); impl_locational!(InlineModule, ast); +#[pymethods] +impl InlineModule { + #[getter] + pub fn ast(&self) -> AST { + self.ast.clone() + } +} + impl InlineModule { pub const fn new(input: Input, ast: AST, import: Call) -> Self { Self { input, ast, import } diff --git a/crates/erg_parser/error.rs b/crates/erg_parser/error.rs index 64bd895c2..efe07a654 100644 --- a/crates/erg_parser/error.rs +++ b/crates/erg_parser/error.rs @@ -25,6 +25,13 @@ impl fmt::Display for LexError { impl std::error::Error for LexError {} +#[cfg(feature = "pylib")] +impl std::convert::From for pyo3::PyErr { + fn from(err: LexError) -> pyo3::PyErr { + pyo3::exceptions::PyOSError::new_err(err.to_string()) + } +} + impl From for LexError { fn from(core: ErrorCore) -> Self { Self(Box::new(core)) @@ -37,6 +44,7 @@ impl From for ErrorCore { } } +#[cfg_attr(feature = "pylib", pyo3::pyclass)] #[derive(Debug)] pub struct LexErrors(Vec); @@ -50,6 +58,13 @@ impl fmt::Display for LexErrors { impl std::error::Error for LexErrors {} +#[cfg(feature = "pylib")] +impl std::convert::From for pyo3::PyErr { + fn from(errs: LexErrors) -> pyo3::PyErr { + pyo3::exceptions::PyOSError::new_err(errs[0].to_string()) + } +} + const ERR: Color = THEME.colors.error; const WARN: Color = THEME.colors.warning; const HINT: Color = THEME.colors.hint; diff --git a/crates/erg_parser/lib.rs b/crates/erg_parser/lib.rs index ee1af26a6..9d30a8eff 100644 --- a/crates/erg_parser/lib.rs +++ b/crates/erg_parser/lib.rs @@ -17,3 +17,96 @@ pub mod visitor; pub use parse::{Parser, ParserRunner}; pub use visitor::ASTVisitor; + +#[cfg(feature = "pylib")] +use pyo3::prelude::*; + +/// parse(code: str) -> erg_parser.Module +/// -- +/// +/// parse an Erg code as a module at runtime +#[cfg(feature = "pylib")] +#[pyfunction] +#[pyo3(name = "parse")] +fn _parse(code: String) -> Result { + parse::SimpleParser::parse(code) + .map(|art| art.ast) + .map_err(|iart| iart.errors) +} + +#[cfg(feature = "pylib")] +#[cfg_attr(feature = "pylib_parser", pymodule)] +pub fn erg_parser(py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(_parse, m)?)?; + let expr = PyModule::new(py, "expr")?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + expr.add_class::()?; + m.add_submodule(expr)?; + + let ast = PyModule::new(py, "ast")?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + ast.add_class::()?; + m.add_submodule(ast)?; + + py.run( + "\ +import sys +sys.modules['erg_parser.ast'] = ast +sys.modules['erg_parser.expr'] = expr +", + None, + Some(m.dict()), + )?; + + Ok(()) +} diff --git a/crates/erg_parser/parse.rs b/crates/erg_parser/parse.rs index 445996cdf..72e3a5d62 100644 --- a/crates/erg_parser/parse.rs +++ b/crates/erg_parser/parse.rs @@ -102,6 +102,7 @@ pub trait Parsable: 'static { fn parse(code: String) -> Result>; } +#[cfg_attr(feature = "pylib", pyo3::pyclass)] pub struct SimpleParser {} impl Parsable for SimpleParser { @@ -353,11 +354,11 @@ impl Parser { ParseError::unclosed_error(line as usize, loc, closer, ty) } - fn skip_and_throw_invalid_seq_err( + fn skip_and_throw_invalid_seq_err( &mut self, caused_by: &str, errno: usize, - expected: &[S], + expected: &[impl std::fmt::Display], found: TokenKind, ) -> ParseError { log!(err "error caused by: {caused_by}"); diff --git a/crates/erg_parser/token.rs b/crates/erg_parser/token.rs index 074d071b0..942ec5770 100644 --- a/crates/erg_parser/token.rs +++ b/crates/erg_parser/token.rs @@ -14,7 +14,13 @@ use erg_common::traits::{DequeStream, Locational}; // use erg_common::typaram::OpKind; // use erg_common::value::ValueObj; +#[cfg(not(feature = "pylib"))] +use erg_proc_macros::pyclass; +#[cfg(feature = "pylib")] +use pyo3::prelude::*; + /// 意味論的名前と記号自体の名前が混在しているが、Pythonの名残である +#[pyclass] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pub enum TokenKind { @@ -188,6 +194,7 @@ pub enum TokenKind { use TokenKind::*; +#[pyclass] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TokenCategory { Symbol, @@ -333,6 +340,7 @@ impl From for BinOpCode { } } +#[pyclass(get_all, set_all)] #[derive(Clone, Eq)] pub struct Token { pub kind: TokenKind, @@ -547,6 +555,7 @@ impl Token { } } +#[pyclass] #[derive(Debug, Clone)] pub struct TokenStream(VecDeque); diff --git a/crates/erg_proc_macros/src/lib.rs b/crates/erg_proc_macros/src/lib.rs index aede09e57..7fa481386 100644 --- a/crates/erg_proc_macros/src/lib.rs +++ b/crates/erg_proc_macros/src/lib.rs @@ -1,7 +1,10 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{PathArguments, ReturnType, Type, TypePath}; +use syn::{ + punctuated::Punctuated, AngleBracketedGenericArguments, GenericArgument, PathArguments, + ReturnType, Type, TypePath, TypeReference, TypeSlice, +}; /// ```rust_ /// #[exec_new_thread] @@ -54,3 +57,139 @@ pub fn exec_new_thread(_attr: TokenStream, item: TokenStream) -> TokenStream { let item = quote! { #item_fn }; item.into() } + +/// dummy attribute +#[proc_macro_attribute] +pub fn pyo3(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn pyclass(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn pymethods(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn staticmethod(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn classmethod(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn getter(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +/// dummy attribute +#[proc_macro_attribute] +pub fn setter(_attr: TokenStream, item: TokenStream) -> TokenStream { + item +} + +fn args_to_owned(args: &PathArguments) -> PathArguments { + match args { + PathArguments::AngleBracketed(args) => { + let res = args + .args + .iter() + .map(|arg| match arg { + GenericArgument::Type(t) => GenericArgument::Type(type_to_owned(t)), + _ => arg.clone(), + }) + .collect::>(); + let mut punc = Punctuated::new(); + punc.extend(res); + let args = AngleBracketedGenericArguments { + colon2_token: args.colon2_token, + lt_token: args.lt_token, + args: punc, + gt_token: args.gt_token, + }; + PathArguments::AngleBracketed(args) + } + _ => args.clone(), + } +} + +fn type_to_owned(t: &Type) -> Type { + match t { + Type::Reference(TypeReference { elem, .. }) => match elem.as_ref() { + Type::Slice(TypeSlice { elem, .. }) => syn::parse_quote! { Vec<#elem> }, + Type::Path(TypePath { path, .. }) => { + match path.segments.first().unwrap().ident.to_string().as_str() { + "str" => syn::parse_quote! { String }, + _ => elem.as_ref().clone(), + } + } + _ => elem.as_ref().clone(), + }, + Type::Path(TypePath { qself, path }) => { + let mut segments = Punctuated::new(); + segments.extend(path.segments.iter().map(|seg| { + let mut seg = seg.clone(); + seg.arguments = args_to_owned(&seg.arguments); + seg + })); + let path = syn::Path { + leading_colon: path.leading_colon, + segments, + }; + Type::Path(TypePath { + qself: qself.to_owned(), + path, + }) + } + _ => t.clone(), + } +} + +/// ```rust +/// #[erg_proc_macros::to_owned] +/// fn foo(s: &str) -> &str { s } +/// ``` +/// ↓ ↓ +/// ```rust +/// fn foo(s: &str) -> String { let r = s; r.to_owned() } +/// ``` +#[proc_macro_attribute] +pub fn to_owned(attr: TokenStream, item: TokenStream) -> TokenStream { + let mut item_fn = syn::parse_macro_input!(item as syn::ItemFn); + let ReturnType::Type(_, out) = &item_fn.sig.output else { + todo!() + }; + let out = type_to_owned(out); + let block = item_fn.block; + let block = if attr + .into_iter() + .next() + .is_some_and(|attr| attr.to_string().as_str() == "cloned") + { + syn::parse_quote! {{ + let r = #block; + r.cloned() + }} + } else { + syn::parse_quote! {{ + let r = #block; + r.to_owned() + }} + }; + item_fn.block = Box::new(block); + item_fn.sig.output = syn::parse_quote! { -> #out }; + let item = quote! { #item_fn }; + item.into() +} diff --git a/examples/use_compiler.er b/examples/use_compiler.er new file mode 100644 index 000000000..0cac8e7e3 --- /dev/null +++ b/examples/use_compiler.er @@ -0,0 +1,27 @@ +unsound = import "unsound" + +erg_compiler = pyimport "erg_compiler" +erg_parser = pyimport "erg_compiler/erg_parser" +erg_ast = pyimport "erg_compiler/erg_parser/ast" + +mod = erg_parser.parse ".i = 1" +ast = erg_ast.AST.new "test", mod +test = erg_compiler.exec_ast ast +i = test.__dict__.get("i") +assert i in Int +assert i == 1 + +test2 = erg_compiler.exec ".i = 1" +i2 = test2.__dict__.get("i") +assert i2 in Int +assert i2 == i + +a11y = erg_compiler.__import__ "examples/a11y" +pub = a11y.__dict__.get("public") +assert pub in Str +assert pub == "this is a public variable" + +code = erg_compiler.compile "1 + 1", "eval" +i3 = unsound.pyeval code +assert i3 in Int +assert i3 == 2 diff --git a/tests/test.rs b/tests/test.rs index 01e0d73c2..20257f1a8 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -121,6 +121,16 @@ fn exec_empty_check() -> Result<(), ()> { expect_success("tests/should_ok/dyn_type_check.er", 0) } +#[test] +fn exec_erg_compiler() -> Result<(), ()> { + let py_command = opt_which_python().unwrap(); + if module_exists(&py_command, "erg_compiler") { + expect_success("examples/use_compiler.er", 0) + } else { + expect_compile_success("examples/use_compiler.er", 0) + } +} + #[test] fn exec_external() -> Result<(), ()> { let py_command = opt_which_python().unwrap();