diff --git a/Cargo.toml b/Cargo.toml index dd9f1d0..dc44869 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustpython-compiler" +name = "rustpython-compiler-core" version = "0.1.2" description = "Compiler for python code into bytecode for the rustpython VM." authors = ["RustPython Team"] diff --git a/porcelain/Cargo.toml b/porcelain/Cargo.toml new file mode 100644 index 0000000..e07c746 --- /dev/null +++ b/porcelain/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rustpython-compiler" +version = "0.1.2" +description = "A usability wrapper around rustpython-parser and rustpython-compiler-core" +authors = ["RustPython Team"] +edition = "2018" + +[dependencies] +thiserror = "1.0" +rustpython-compiler-core = { path = ".." } +rustpython-parser = { path = "../../parser" } +rustpython-bytecode = { path = "../../bytecode" } diff --git a/porcelain/src/lib.rs b/porcelain/src/lib.rs new file mode 100644 index 0000000..bdf93ee --- /dev/null +++ b/porcelain/src/lib.rs @@ -0,0 +1,135 @@ +use rustpython_bytecode::bytecode::CodeObject; +use rustpython_compiler_core::{compile, symboltable}; +use rustpython_parser::{ast::Location, parser}; +use std::fmt; + +pub use compile::{CompileOpts, Mode}; +pub use symboltable::{Symbol, SymbolScope, SymbolTable, SymbolTableType}; + +#[derive(Debug, thiserror::Error)] +pub enum CompileErrorType { + #[error(transparent)] + Compile(#[from] rustpython_compiler_core::error::CompileErrorType), + #[error(transparent)] + Parse(#[from] rustpython_parser::error::ParseErrorType), +} + +#[derive(Debug, thiserror::Error)] +pub struct CompileError { + pub error: CompileErrorType, + pub statement: Option, + pub source_path: String, + pub location: Location, +} + +impl fmt::Display for CompileError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let loc = self.location; + if let Some(ref stmt) = self.statement { + // visualize the error when location and statement are provided + write!( + f, + "{}", + loc.visualize(stmt, &format_args!("{} at {}", self.error, loc)) + ) + } else { + write!(f, "{} at {}", self.error, loc) + } + } +} + +impl CompileError { + fn from_compile(error: rustpython_compiler_core::error::CompileError, source: &str) -> Self { + CompileError { + error: error.error.into(), + location: error.location, + source_path: error.source_path, + statement: get_statement(source, error.location), + } + } + fn from_parse( + error: rustpython_parser::error::ParseError, + source: &str, + source_path: String, + ) -> Self { + CompileError { + error: error.error.into(), + location: error.location, + source_path, + statement: get_statement(source, error.location), + } + } + fn from_symtable( + error: symboltable::SymbolTableError, + source: &str, + source_path: String, + ) -> Self { + Self::from_compile(error.into_compile_error(source_path), source) + } +} + +/// Compile a given sourcecode into a bytecode object. +pub fn compile( + source: &str, + mode: compile::Mode, + source_path: String, + opts: CompileOpts, +) -> Result { + macro_rules! try_parse { + ($x:expr) => { + match $x { + Ok(x) => x, + Err(e) => return Err(CompileError::from_parse(e, source, source_path)), + } + }; + } + let res = match mode { + compile::Mode::Exec => { + let ast = try_parse!(parser::parse_program(source)); + compile::compile_program(ast, source_path, opts) + } + compile::Mode::Eval => { + let statement = try_parse!(parser::parse_statement(source)); + compile::compile_statement_eval(statement, source_path, opts) + } + compile::Mode::Single => { + let ast = try_parse!(parser::parse_program(source)); + compile::compile_program_single(ast, source_path, opts) + } + }; + res.map_err(|e| CompileError::from_compile(e, source)) +} + +pub fn compile_symtable( + source: &str, + mode: compile::Mode, + source_path: &str, +) -> Result { + macro_rules! try_parse { + ($x:expr) => { + match $x { + Ok(x) => x, + Err(e) => return Err(CompileError::from_parse(e, source, source_path.to_owned())), + } + }; + } + let res = match mode { + compile::Mode::Exec | compile::Mode::Single => { + let ast = try_parse!(parser::parse_program(source)); + symboltable::make_symbol_table(&ast) + } + compile::Mode::Eval => { + let statement = try_parse!(parser::parse_statement(source)); + symboltable::statements_to_symbol_table(&statement) + } + }; + res.map_err(|e| CompileError::from_symtable(e, source, source_path.to_owned())) +} + +fn get_statement(source: &str, loc: Location) -> Option { + if loc.column() == 0 || loc.row() == 0 { + return None; + } + let line = source.split('\n').nth(loc.row() - 1)?.to_owned(); + Some(line + "\n") +}