some clean up to compiler to be able to better test

This commit is contained in:
gluax 2022-04-26 15:15:47 -07:00
parent bd356e1f31
commit 0fc6d17289
4 changed files with 100 additions and 94 deletions

View File

@ -25,9 +25,11 @@
#[cfg(test)]
mod test;
pub use leo_ast::Ast;
use leo_ast::Program;
pub use leo_ast::{Ast, ParsedInputFile};
use leo_errors::emitter::Handler;
use leo_errors::{CompilerError, Result};
pub use leo_passes::SymbolTable;
use leo_passes::*;
use leo_span::symbol::create_session_if_not_set_then;
@ -41,7 +43,7 @@ pub struct Compiler<'a> {
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
input_file_path: Option<PathBuf>,
pub ast: Ast,
}
impl<'a> Compiler<'a> {
@ -53,7 +55,7 @@ impl<'a> Compiler<'a> {
handler,
main_file_path,
output_directory,
input_file_path: None,
ast: Ast::new(Program::new("Initial".to_string())),
}
}
@ -73,26 +75,28 @@ impl<'a> Compiler<'a> {
Ok(format!("{:x}", hash))
}
///
/// Runs the compiler stages.
///
fn compiler_stages(&self, program_string: &str) -> Result<leo_ast::Ast> {
//load the input file if it exists.
if let Some(input_file_path) = self.input_file_path.as_ref() {
let _input_ast = if input_file_path.exists() {
let input_string = fs::read_to_string(&input_file_path)
.map_err(|e| CompilerError::file_read_error(input_file_path.clone(), e))?;
Some(leo_parser::parse_input(
// Parses and stores a program file content from a string, constructs a syntax tree, and generates a program.
pub fn parse_program_from_string(&mut self, program_string: &str) -> Result<()> {
// Use the parser to construct the abstract syntax tree (ast).
let ast: leo_ast::Ast = leo_parser::parse_ast(
self.handler,
input_file_path.to_str().unwrap_or_default(),
input_string,
)?)
} else {
None
};
self.main_file_path.to_str().unwrap_or_default(),
program_string,
)?;
// Write the AST snapshot post parsing.
ast.to_json_file_without_keys(self.output_directory.clone(), "initial_ast.json", &["span"])?;
self.ast = ast;
Ok(())
}
/// Parses and stores the main program file, constructs a syntax tree, and generates a program.
pub fn parse_program(&mut self) -> Result<()> {
// Load the program file.
let program_string = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
// Use the parser to construct the abstract syntax tree (ast).
let ast: leo_ast::Ast = leo_parser::parse_ast(
self.handler,
@ -102,31 +106,43 @@ impl<'a> Compiler<'a> {
// Write the AST snapshot post parsing.
ast.to_json_file_without_keys(self.output_directory.clone(), "initial_ast.json", &["span"])?;
// Canonicalize the AST.
// ast = leo_ast_passes::Canonicalizer::do_pass(Default::default(), ast.into_repr())?;
// Write the AST snapshot post parsing
// ast.to_json_file_without_keys(self.output_directory, "canonicalization_ast.json", &["span"])?;
self.ast = ast;
let _symbol_table = CreateSymbolTable::do_pass((&ast, self.handler));
Ok(ast)
Ok(())
}
/// Parses and stores the main program file, constructs a syntax tree, and generates a program.
///
/// Parses and stores all programs imported by the main program file.
pub fn parse_program(&self) -> Result<Ast> {
// Load the program file.
let program_string = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
// Loads the given input file if it exists.
fn parse_input(&self, input_file_path: PathBuf) -> Result<Option<ParsedInputFile>> {
// Load the input file if it exists.
if input_file_path.exists() {
let input_string = fs::read_to_string(&input_file_path)
.map_err(|e| CompilerError::file_read_error(input_file_path.clone(), e))?;
self.compiler_stages(&program_string)
let input_ast =
leo_parser::parse_input(self.handler, input_file_path.to_str().unwrap_or_default(), input_string)?;
input_ast.to_json_file_without_keys(self.output_directory.clone(), "inital_input_ast.json", &["span"])?;
Ok(Some(input_ast))
} else {
Ok(None)
}
}
///
/// Runs the compiler stages.
///
fn compiler_stages(&mut self, input_file_path: PathBuf) -> Result<(Option<ParsedInputFile>, SymbolTable<'_>)> {
let input_ast = self.parse_input(input_file_path)?;
let symbol_table = CreateSymbolTable::do_pass((&self.ast, self.handler));
Ok((input_ast, symbol_table))
}
///
/// Returns a compiled Leo program.
///
pub fn compile(&self) -> Result<leo_ast::Ast> {
create_session_if_not_set_then(|_| self.parse_program())
pub fn compile(&mut self, input_file_path: PathBuf) -> Result<(Option<ParsedInputFile>, SymbolTable<'_>)> {
create_session_if_not_set_then(|_| self.compiler_stages(input_file_path))
}
}

View File

@ -28,6 +28,7 @@ use leo_errors::{
emitter::{Buffer, Emitter, Handler},
LeoError,
};
use leo_passes::SymbolTable;
use leo_span::symbol::create_session_if_not_set_then;
use leo_test_framework::{
runner::{Namespace, ParseType, Runner},
@ -49,22 +50,25 @@ fn parse_program<'a>(
cwd: Option<PathBuf>,
) -> Result<Compiler<'a>, LeoError> {
let mut compiler = new_compiler(handler, cwd.unwrap_or_else(|| "compiler-test".into()));
compiler.compile()?;
compiler.parse_program_from_string(program_string)?;
Ok(compiler)
}
fn hash_file(path: &str) -> String {
fn hash_content(content: &str) -> String {
use sha2::{Digest, Sha256};
let mut file = fs::File::open(&Path::new(path)).unwrap();
let mut hasher = Sha256::new();
std::io::copy(&mut file, &mut hasher).unwrap();
hasher.update(content);
let hash = hasher.finalize();
format!("{:x}", hash)
}
fn hash_file(path: &str) -> String {
let file = fs::read_to_string(&Path::new(path)).unwrap();
hash_content(&file)
}
struct CompileNamespace;
impl Namespace for CompileNamespace {
@ -85,6 +89,8 @@ impl Namespace for CompileNamespace {
#[derive(Deserialize, PartialEq, Serialize)]
struct OutputItem {
pub input_file: String,
pub initial_input_ast: String,
pub symbol_table: String,
}
#[derive(Deserialize, PartialEq, Serialize)]
@ -93,44 +99,18 @@ struct CompileOutput {
pub initial_ast: String,
}
type Input = (String, String);
type Input = (PathBuf, String);
/// Collect all inputs into `list` from `field`, if possible.
fn collect_inputs_list(list: &mut Vec<Input>, field: &[Value]) -> Result<(), String> {
for map in field {
for (name, value) in map.as_mapping().unwrap().iter() {
// Try to parse string from 'inputs' map, else fail
let value = if let serde_yaml::Value::String(value) = value {
value
} else {
return Err("Expected string in 'inputs' map".to_string());
};
list.push((name.as_str().unwrap().to_string(), value.clone()));
}
}
Ok(())
}
/// Read contents of `input_file` given in `input` into `list`.
fn read_input_file(list: &mut Vec<Input>, test: &Test, input: &Value) {
/// Get the path of the `input_file` given in `input` into `list`.
fn get_input_file_paths(list: &mut Vec<Input>, test: &Test, input: &Value) {
let input_file: PathBuf = test.path.parent().expect("no test parent dir").into();
if let Some(name) = input.as_str() {
if let Some(_) = input.as_str() {
let mut input_file = input_file;
input_file.push(input.as_str().expect("input_file was not a string or array"));
list.push((
name.to_string(),
input_file.clone(),
fs::read_to_string(&input_file).expect("failed to read test input file"),
));
} else if let Some(seq) = input.as_sequence() {
for name in seq {
let mut input_file = input_file.clone();
input_file.push(name.as_str().expect("input_file was not a string"));
list.push((
name.as_str().expect("input_file item was not a string").to_string(),
fs::read_to_string(&input_file).expect("failed to read test input file"),
));
}
}
}
@ -138,22 +118,19 @@ fn read_input_file(list: &mut Vec<Input>, test: &Test, input: &Value) {
fn collect_all_inputs(test: &Test) -> Result<Vec<Input>, String> {
let mut list = vec![];
if let Some(Value::Sequence(field)) = test.config.get("inputs") {
collect_inputs_list(&mut list, field)?;
if let Some(input) = test.config.get("input_file") {
get_input_file_paths(&mut list, &test, input);
}
if let Some(input) = test.config.get("input_file") {
read_input_file(&mut list, &test, input);
}
if list.is_empty() {
list.push(("empty".to_string(), "".to_string()));
}
Ok(list)
}
fn compile_and_process(parsed: Compiler<'_>, input: &Input) -> Result<(), LeoError> {
// ParsedInputFile
Ok(())
fn compile_and_process<'a>(
parsed: &'a mut Compiler<'a>,
input_file_path: PathBuf,
) -> Result<(Option<ParsedInputFile>, SymbolTable<'a>), LeoError> {
let compiled = parsed.compiler_stages(input_file_path)?;
Ok(compiled)
}
// Errors used in this module.
@ -212,14 +189,18 @@ fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Va
let mut output_items = Vec::with_capacity(inputs.len());
for input in inputs {
let parsed = parsed.clone();
handler.extend_if_error(compile_and_process(parsed, &input))?;
let mut parsed = parsed.clone();
let (_, symbol_table) = handler.extend_if_error(compile_and_process(&mut parsed, input.0))?;
let initial_input_ast = hash_file("/tmp/output/inital_input_ast.json");
output_items.push(OutputItem { input_file: input.0 });
output_items.push(OutputItem {
input_file: input.1,
initial_input_ast,
symbol_table: hash_content(&symbol_table.to_string()),
});
}
let initial_ast = hash_file("/tmp/output/initial_ast.json");
let initial_input = hash_file("/tmp/output/input_ast.json");
if fs::read_dir("/tmp/output").is_ok() {
fs::remove_dir_all(Path::new("/tmp/output")).expect("Error failed to clean up output dir.");

View File

@ -14,6 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use std::fmt::Display;
use leo_ast::Function;
use leo_errors::{AstError, Result};
use leo_span::Symbol;
@ -48,3 +50,9 @@ impl<'a> SymbolTable<'a> {
Ok(())
}
}
impl<'a> Display for SymbolTable<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}

View File

@ -15,7 +15,7 @@
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
use crate::{commands::Command, context::Context};
use leo_compiler::{Ast, Compiler};
use leo_compiler::{Ast, Compiler, ParsedInputFile};
use leo_errors::{CliError, Result};
use leo_package::{
inputs::InputFile,
@ -101,7 +101,7 @@ pub struct Build {
impl Command for Build {
type Input = ();
type Output = (Ast, bool);
type Output = (Option<ParsedInputFile>, Ast, bool);
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Build")
@ -180,13 +180,14 @@ impl Command for Build {
// Initialize error handler
let handler = leo_errors::emitter::Handler::default();
let program = Compiler::new(&handler, main_file_path, output_directory);
let mut program = Compiler::new(&handler, main_file_path, output_directory);
program.parse_program()?;
// Compute the current program checksum
let program_checksum = program.checksum()?;
// Compile the program
let program_compiled = program.compile()?;
let (input_ast, _) = program.compile(input_path.to_path_buf())?;
// Generate the program on the constraint system and verify correctness
{
@ -240,6 +241,6 @@ impl Command for Build {
tracing::info!("Complete");
Ok((program_compiled, checksum_differs))
Ok((input_ast, program.ast, checksum_differs))
}
}