1
0
Fork 0
mirror of synced 2025-09-24 04:40:05 +00:00
ZoKrates/zokrates_core/src/compile.rs
2019-06-11 11:12:37 +02:00

249 lines
6.7 KiB
Rust

//! Module containing the complete compilation pipeline.
//!
//! @file compile.rs
//! @author Thibaut Schaeffer <thibaut@schaeff.fr>
//! @date 2018
use crate::absy::{Module, ModuleId, Program};
use crate::flatten::Flattener;
use crate::imports::{self, Importer};
use crate::ir;
use crate::optimizer::Optimize;
use crate::semantics::{self, Checker};
use crate::static_analysis::Analyse;
use std::collections::HashMap;
use std::fmt;
use std::io;
use std::io::BufRead;
use zokrates_field::field::Field;
use zokrates_pest_ast as pest;
#[derive(Debug)]
pub struct CompileErrors(Vec<CompileError>);
impl From<CompileError> for CompileErrors {
fn from(e: CompileError) -> CompileErrors {
CompileErrors(vec![e])
}
}
impl fmt::Display for CompileErrors {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
self.0
.iter()
.map(|e| format!("{}", e))
.collect::<Vec<_>>()
.join("\n\n")
)
}
}
#[derive(Debug)]
pub enum CompileErrorInner {
ParserError(pest::Error),
ImportError(imports::Error),
SemanticError(semantics::Error),
ReadError(io::Error),
}
impl CompileErrorInner {
pub fn with_context(self, context: &Option<String>) -> CompileError {
CompileError {
value: self,
context: context.clone(),
}
}
}
#[derive(Debug)]
pub struct CompileError {
context: Option<String>,
value: CompileErrorInner,
}
impl CompileErrors {
pub fn with_context(self, context: Option<String>) -> Self {
CompileErrors(
self.0
.into_iter()
.map(|e| CompileError {
context: context.clone(),
..e
})
.collect(),
)
}
}
impl fmt::Display for CompileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let context = match self.context {
Some(ref x) => x.clone(),
None => "???".to_string(),
};
write!(f, "{}:{}", context, self.value)
}
}
impl From<pest::Error> for CompileErrorInner {
fn from(error: pest::Error) -> Self {
CompileErrorInner::ParserError(error)
}
}
impl From<imports::Error> for CompileErrorInner {
fn from(error: imports::Error) -> Self {
CompileErrorInner::ImportError(error)
}
}
impl From<io::Error> for CompileErrorInner {
fn from(error: io::Error) -> Self {
CompileErrorInner::ReadError(error)
}
}
impl From<semantics::Error> for CompileErrorInner {
fn from(error: semantics::Error) -> Self {
CompileErrorInner::SemanticError(error)
}
}
impl fmt::Display for CompileErrorInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = match *self {
CompileErrorInner::ParserError(ref e) => format!("{}", e),
CompileErrorInner::SemanticError(ref e) => format!("{}", e),
CompileErrorInner::ReadError(ref e) => format!("{}", e),
CompileErrorInner::ImportError(ref e) => format!("{}", e),
};
write!(f, "{}", res)
}
}
pub fn compile<T: Field, R: BufRead, S: BufRead, E: Into<imports::Error>>(
reader: &mut R,
location: Option<String>,
resolve_option: Option<fn(&Option<String>, &String) -> Result<(S, String, String), E>>,
) -> Result<ir::Prog<T>, CompileErrors> {
let mut source = String::new();
reader.read_to_string(&mut source).unwrap();
let compiled = compile_program(&source, location.clone(), resolve_option)?;
// check semantics
let typed_ast = Checker::check(compiled).map_err(|errors| {
CompileErrors(
errors
.into_iter()
.map(|e| CompileErrorInner::from(e).with_context(&location))
.collect(),
)
})?;
// analyse (unroll and constant propagation)
let typed_ast = typed_ast.analyse();
// flatten input program
let program_flattened = Flattener::flatten(typed_ast);
// analyse (constant propagation after call resolution)
let program_flattened = program_flattened.analyse();
Ok(ir::Prog::from(program_flattened).optimize())
}
pub fn compile_program<'ast, T: Field, S: BufRead, E: Into<imports::Error>>(
source: &'ast str,
location: Option<String>,
resolve_option: Option<fn(&Option<String>, &String) -> Result<(S, String, String), E>>,
) -> Result<Program<T>, CompileErrors> {
let mut modules = HashMap::new();
let main = compile_module(&source, location.clone(), resolve_option, &mut modules)?;
let location = location.unwrap_or("???".to_string());
modules.insert(location.clone(), main);
Ok(Program {
main: location,
modules,
})
}
pub fn compile_module<'ast, T: Field, S: BufRead, E: Into<imports::Error>>(
source: &'ast str,
location: Option<String>,
resolve_option: Option<fn(&Option<String>, &String) -> Result<(S, String, String), E>>,
modules: &mut HashMap<ModuleId, Module<'ast, T>>,
) -> Result<Module<'ast, T>, CompileErrors> {
let ast = pest::generate_ast(&source)
.map_err(|e| CompileErrors::from(CompileErrorInner::from(e).with_context(&location)))?;
let module_without_imports: Module<T> = Module::from(ast);
Importer::new().apply_imports(
module_without_imports,
location.clone(),
resolve_option,
modules,
)
}
#[cfg(test)]
mod test {
use super::*;
use std::io::{BufReader, Empty};
use zokrates_field::field::FieldPrime;
#[test]
fn no_resolver_with_imports() {
let mut r = BufReader::new(
r#"
import "./path/to/file" as foo
def main() -> (field):
return foo()
"#
.as_bytes(),
);
let res: Result<ir::Prog<FieldPrime>, CompileErrors> = compile(
&mut r,
Some(String::from("./path/to/file")),
None::<
fn(
&Option<String>,
&String,
) -> Result<(BufReader<Empty>, String, String), io::Error>,
>,
);
assert!(res
.unwrap_err()
.to_string()
.contains(&"Can't resolve import without a resolver"));
}
#[test]
fn no_resolver_without_imports() {
let mut r = BufReader::new(
r#"
def main() -> (field):
return 1
"#
.as_bytes(),
);
let res: Result<ir::Prog<FieldPrime>, CompileErrors> = compile(
&mut r,
Some(String::from("./path/to/file")),
None::<
fn(
&Option<String>,
&String,
) -> Result<(BufReader<Empty>, String, String), io::Error>,
>,
);
assert!(res.is_ok());
}
}