feat: make erg_compiler available as a Python lib

This commit is contained in:
Shunsuke Shibayama 2023-11-15 01:14:02 +09:00
parent 22ccf4d870
commit 8b17c6cf6c
9 changed files with 152 additions and 4 deletions

1
Cargo.lock generated
View file

@ -135,6 +135,7 @@ version = "0.6.25"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_parser", "erg_parser",
"pyo3",
] ]
[[package]] [[package]]

View file

@ -40,15 +40,19 @@ els = ["erg_common/els"]
no_std = ["erg_common/no_std"] no_std = ["erg_common/no_std"]
full-repl = ["erg_common/full-repl"] full-repl = ["erg_common/full-repl"]
experimental = ["erg_common/experimental", "erg_parser/experimental"] experimental = ["erg_common/experimental", "erg_parser/experimental"]
pylib = ["dep:pyo3", "erg_common/pylib", "erg_parser/pylib"]
pylib_compiler = ["pylib"]
[dependencies] [dependencies]
erg_common = { workspace = true } erg_common = { workspace = true }
erg_parser = { workspace = true } erg_parser = { workspace = true }
pyo3 = { workspace = true, optional = true }
[build-dependencies] [build-dependencies]
erg_common = { workspace = true } erg_common = { workspace = true }
[lib] [lib]
crate-type = ["cdylib", "rlib"]
path = "lib.rs" path = "lib.rs"
[[bin]] [[bin]]

View file

@ -1,3 +1,35 @@
# The Erg compiler (codename: Centimetre) # 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. 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
```
### 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 <output wheel>
```

View file

@ -175,6 +175,23 @@ pub struct CompileError {
impl_display_and_error!(CompileError); impl_display_and_error!(CompileError);
impl From<std::io::Error> 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<ParserRunnerError> for CompileError { impl From<ParserRunnerError> for CompileError {
fn from(err: ParserRunnerError) -> Self { fn from(err: ParserRunnerError) -> Self {
Self { Self {
@ -201,6 +218,13 @@ impl From<CompileError> for ParserRunnerError {
} }
} }
#[cfg(feature = "pylib")]
impl std::convert::From<CompileError> for pyo3::PyErr {
fn from(err: CompileError) -> pyo3::PyErr {
pyo3::exceptions::PyOSError::new_err(err.to_string())
}
}
impl ErrorDisplay for CompileError { impl ErrorDisplay for CompileError {
fn core(&self) -> &ErrorCore { fn core(&self) -> &ErrorCore {
&self.core &self.core
@ -521,6 +545,12 @@ impl std::error::Error for CompileErrors {}
impl_stream!(CompileErrors, CompileError); impl_stream!(CompileErrors, CompileError);
impl From<std::io::Error> for CompileErrors {
fn from(value: std::io::Error) -> Self {
Self::from(vec![value.into()])
}
}
impl From<ParserRunnerErrors> for CompileErrors { impl From<ParserRunnerErrors> for CompileErrors {
fn from(err: ParserRunnerErrors) -> Self { fn from(err: ParserRunnerErrors) -> Self {
Self(err.into_iter().map(CompileError::from).collect()) Self(err.into_iter().map(CompileError::from).collect())
@ -557,6 +587,13 @@ impl From<CompileErrors> for ParseErrors {
} }
} }
#[cfg(feature = "pylib")]
impl std::convert::From<CompileErrors> for pyo3::PyErr {
fn from(errs: CompileErrors) -> pyo3::PyErr {
pyo3::exceptions::PyOSError::new_err(errs[0].to_string())
}
}
impl MultiErrorDisplay<CompileError> for CompileErrors {} impl MultiErrorDisplay<CompileError> for CompileErrors {}
impl fmt::Display for CompileErrors { impl fmt::Display for CompileErrors {

View file

@ -30,3 +30,67 @@ pub mod varinfo;
pub use build_hir::{GenericHIRBuilder, HIRBuilder}; pub use build_hir::{GenericHIRBuilder, HIRBuilder};
pub use erg_parser::build_ast::ASTBuilder; pub use erg_parser::build_ast::ASTBuilder;
pub use transpile::Transpiler; pub use transpile::Transpiler;
#[cfg(feature = "pylib")]
use pyo3::prelude::*;
#[cfg(feature = "pylib")]
#[pyfunction]
#[pyo3(name = "compile")]
fn _compile(py: Python<'_>, code: String) -> Result<PyObject, error::CompileErrors> {
use erg_common::config::ErgConfig;
use pyo3::types::{IntoPyDict, PyBytes};
let cfg = ErgConfig::string(code);
let mut compiler = Compiler::new(cfg);
let code = compiler
.compile_module()
.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())
}
#[cfg(feature = "pylib")]
#[pyfunction]
#[pyo3(name = "compile_file")]
fn _compile_file(py: Python<'_>, path: String) -> Result<PyObject, error::CompileErrors> {
let code = std::fs::read_to_string(path)?;
_compile(py, code)
}
#[cfg(feature = "pylib")]
#[pyfunction]
#[pyo3(name = "exec_module")]
fn _exec_module(py: Python<'_>, code: String) -> Result<PyObject, error::CompileErrors> {
use pyo3::types::IntoPyDict;
let code = _compile(py, code)?;
let module = pyo3::types::PyModule::new(py, "<erg>").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())
}
#[cfg(feature = "pylib")]
#[pyfunction]
#[pyo3(name = "__import__")]
fn _import(py: Python<'_>, name: String) -> Result<PyObject, error::CompileErrors> {
let path = format!("{name}.er");
let code = std::fs::read_to_string(path)?;
_exec_module(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_file, m)?)?;
m.add_function(wrap_pyfunction!(_exec_module, m)?)?;
m.add_function(wrap_pyfunction!(_import, m)?)?;
Ok(())
}

View file

@ -37,7 +37,7 @@ impl HIROptimizer {
fn eliminate_unused_def(&mut self, expr: &mut Expr) { fn eliminate_unused_def(&mut self, expr: &mut Expr) {
match expr { match expr {
Expr::Def(def) => { Expr::Def(def) => {
if def.sig.ident().is_discarded() { if def.sig.ident().is_discarded() || def.sig.vis().is_public() {
return; return;
} }
if self if self

View file

@ -19,6 +19,7 @@ pretty = ["erg_common/pretty"]
large_thread = ["erg_common/large_thread"] large_thread = ["erg_common/large_thread"]
experimental = ["erg_common/experimental"] experimental = ["erg_common/experimental"]
pylib = ["dep:pyo3", "erg_common/pylib"] pylib = ["dep:pyo3", "erg_common/pylib"]
pylib_parser = ["pylib"]
[dependencies] [dependencies]
erg_common = { workspace = true } erg_common = { workspace = true }

View file

@ -15,8 +15,17 @@ for chunk in module:
assert chunk.sig.inspect() == "x" assert chunk.sig.inspect() == "x"
``` ```
### Debug install ### Debug install (using venv)
```python ```python
maturin develop --features pylib 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 <output wheel>
``` ```

View file

@ -30,7 +30,7 @@ fn _parse(code: String) -> Result<ast::Module, error::ParseErrors> {
.map_err(|iart| iart.errors) .map_err(|iart| iart.errors)
} }
#[cfg(feature = "pylib")] #[cfg(feature = "pylib_parser")]
#[pymodule] #[pymodule]
fn erg_parser(py: Python<'_>, m: &PyModule) -> PyResult<()> { fn erg_parser(py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(_parse, m)?)?; m.add_function(wrap_pyfunction!(_parse, m)?)?;