diff --git a/Cargo.lock b/Cargo.lock index cece206388..0c92b96fea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "leo-core" -version = "1.0.1" +version = "1.0.3" dependencies = [ "leo-gadgets", "leo-typed", @@ -1287,6 +1287,14 @@ dependencies = [ "thiserror", ] +[[package]] +name = "leo-imports" +version = "1.0.3" +dependencies = [ + "leo-typed", + "thiserror", +] + [[package]] name = "leo-input" version = "1.0.3" @@ -1319,6 +1327,7 @@ dependencies = [ "leo-compiler", "leo-core", "leo-gadgets", + "leo-imports", "leo-input", "leo-package", "leo-state", diff --git a/Cargo.toml b/Cargo.toml index 618495c59a..d15e9019e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ members = [ "compiler", "core", "gadgets", + "imports", "input", "linter", "package", @@ -49,6 +50,10 @@ version = "1.0.1" path = "./gadgets" version = "1.0.3" +[dependencies.leo-imports] +path = "./imports" +version = "1.0.3" + [dependencies.leo-input] path = "./input" version = "1.0.3" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 80b22634d2..58041b4253 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -23,7 +23,7 @@ version = "1.0.3" [dependencies.leo-core] path = "../core" -version = "1.0.1" +version = "1.0.3" [dependencies.leo-gadgets] path = "../gadgets" diff --git a/core/Cargo.toml b/core/Cargo.toml index 111ce74559..aa59743d09 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-core" -version = "1.0.1" +version = "1.0.3" authors = [ "The Aleo Team " ] description = "Core package dependencies of the Leo programming language" homepage = "https://aleo.org" diff --git a/imports/Cargo.toml b/imports/Cargo.toml new file mode 100644 index 0000000000..2c0c98154a --- /dev/null +++ b/imports/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "leo-imports" +version = "1.0.3" +authors = [ "The Aleo Team "] +description = "Import parser for Leo program package dependencies" +homepage = "https://aleo.org" +respository = "https://github.com/AleoHQ/leo" +keywords = [ + "aleo", + "cryptography", + "leo", + "programming-language", + "zero-knowledge" +] +categories = [ "cryptography::cryptocurrencies", "web-programming" ] +include = ["Cargo.toml", "src", "README.md", "LICENSE.md" ] +license = "GPL-3.0" +edition = "2018" + +[dependencies.leo-typed] +path = "../typed" +version = "1.0.3" + +[dependencies.thiserror] +version = "1.0" \ No newline at end of file diff --git a/imports/src/core_package.rs b/imports/src/core_package.rs new file mode 100644 index 0000000000..2873eb4346 --- /dev/null +++ b/imports/src/core_package.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2019-2020 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{errors::ImportError, ImportParser}; +use leo_typed::Package; + +pub static CORE_PACKAGE_NAME: &str = "core"; + +impl ImportParser { + // import a core package into scope + pub fn parse_core_package(&mut self, package: &Package) -> Result<(), ImportError> { + self.insert_core_package(package); + Ok(()) + } +} diff --git a/imports/src/import_parser.rs b/imports/src/import_parser.rs new file mode 100644 index 0000000000..c870f8670f --- /dev/null +++ b/imports/src/import_parser.rs @@ -0,0 +1,70 @@ +// Copyright (C) 2019-2020 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::errors::ImportError; +use leo_typed::{Package, Program}; + +use std::{collections::HashMap, env::current_dir}; + +/// Parses all relevant import files for a program. +/// Stores compiled program structs. +#[derive(Clone)] +pub struct ImportParser { + imports: HashMap, + core_packages: Vec, +} + +impl ImportParser { + pub fn new() -> Self { + Self { + imports: HashMap::new(), + core_packages: vec![], + } + } + + pub(crate) fn insert_import(&mut self, file_name: String, program: Program) { + // todo: handle conflicting versions for duplicate imports here + let _res = self.imports.insert(file_name, program); + } + + pub(crate) fn insert_core_package(&mut self, package: &Package) { + let _res = self.core_packages.push(package.clone()); + } + + pub fn get_import(&self, file_name: &String) -> Option<&Program> { + self.imports.get(file_name) + } + + pub fn core_packages(&self) -> &Vec { + &self.core_packages + } + + pub fn parse(program: &Program) -> Result { + let mut imports = Self::new(); + + // Find all imports relative to current directory + let path = current_dir().map_err(|error| ImportError::current_directory_error(error))?; + + // Parse each imported file + program + .imports + .iter() + .map(|import| imports.parse_package(path.clone(), &import.package)) + .collect::, ImportError>>()?; + + Ok(imports) + } +} diff --git a/imports/src/lib.rs b/imports/src/lib.rs new file mode 100644 index 0000000000..4cc48bcdc2 --- /dev/null +++ b/imports/src/lib.rs @@ -0,0 +1,28 @@ +// Copyright (C) 2019-2020 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +/// The import parser creates a hashmap of import program names -> import program structs +pub mod core_package; +pub use self::core_package::*; + +pub mod parse_symbol; +pub use self::parse_symbol::*; + +pub mod import_parser; +pub use self::import_parser::*; + +pub mod parse_package; +pub use self::parse_package::*; diff --git a/imports/src/parse_package.rs b/imports/src/parse_package.rs new file mode 100644 index 0000000000..3247cb5c29 --- /dev/null +++ b/imports/src/parse_package.rs @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2020 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{errors::ImportError, ImportParser, CORE_PACKAGE_NAME}; +use leo_typed::{Package, PackageAccess}; + +use std::{fs, fs::DirEntry, path::PathBuf}; + +static SOURCE_FILE_EXTENSION: &str = ".leo"; +static SOURCE_DIRECTORY_NAME: &str = "src/"; +static IMPORTS_DIRECTORY_NAME: &str = "imports/"; + +impl ImportParser { + // bring one or more import symbols into scope for the current constrained program + // we will recursively traverse sub packages here until we find the desired symbol + pub fn parse_package_access(&mut self, entry: &DirEntry, access: &PackageAccess) -> Result<(), ImportError> { + tracing::debug!("import {:?}", entry.path()); + + match access { + PackageAccess::Star(span) => self.parse_import_star(entry, span), + PackageAccess::Symbol(symbol) => self.parse_import_symbol(entry, symbol), + PackageAccess::SubPackage(package) => self.parse_package(entry.path(), package), + PackageAccess::Multiple(accesses) => { + for access in accesses { + self.parse_package_access(entry, access)?; + } + + Ok(()) + } + } + } + + pub fn parse_package(&mut self, mut path: PathBuf, package: &Package) -> Result<(), ImportError> { + let error_path = path.clone(); + let package_name = package.name.clone(); + + // Fetch a core package + let core_package = package_name.name.eq(CORE_PACKAGE_NAME); + + // Trim path if importing from another file + if path.is_file() { + path.pop(); + } + + // Search for package name in local directory + let mut source_directory = path.clone(); + source_directory.push(SOURCE_DIRECTORY_NAME); + + // Search for package name in `imports` directory + let mut imports_directory = path.clone(); + imports_directory.push(IMPORTS_DIRECTORY_NAME); + + // Read from local `src` directory or the current path + if source_directory.exists() { + path = source_directory + } + + let entries = fs::read_dir(path) + .map_err(|error| ImportError::directory_error(error, package_name.span.clone(), error_path.clone()))? + .into_iter() + .collect::, std::io::Error>>() + .map_err(|error| ImportError::directory_error(error, package_name.span.clone(), error_path.clone()))?; + + let matched_source_entry = entries.into_iter().find(|entry| { + entry + .file_name() + .to_os_string() + .into_string() + .unwrap() + .trim_end_matches(SOURCE_FILE_EXTENSION) + .eq(&package_name.name) + }); + + if core_package { + // Enforce core library package access + self.parse_core_package(&package) + } else if imports_directory.exists() { + let entries = fs::read_dir(imports_directory) + .map_err(|error| ImportError::directory_error(error, package_name.span.clone(), error_path.clone()))? + .into_iter() + .collect::, std::io::Error>>() + .map_err(|error| ImportError::directory_error(error, package_name.span.clone(), error_path.clone()))?; + + let matched_import_entry = entries + .into_iter() + .find(|entry| entry.file_name().into_string().unwrap().eq(&package_name.name)); + + match (matched_source_entry, matched_import_entry) { + (Some(_), Some(_)) => Err(ImportError::conflicting_imports(package_name)), + (Some(source_entry), None) => self.parse_package_access(&source_entry, &package.access), + (None, Some(import_entry)) => self.parse_package_access(&import_entry, &package.access), + (None, None) => Err(ImportError::unknown_package(package_name)), + } + } else { + // Enforce local package access with no found imports directory + match matched_source_entry { + Some(source_entry) => self.parse_package_access(&source_entry, &package.access), + None => Err(ImportError::unknown_package(package_name)), + } + } + } +} diff --git a/imports/src/parse_symbol.rs b/imports/src/parse_symbol.rs new file mode 100644 index 0000000000..0d8f869b4f --- /dev/null +++ b/imports/src/parse_symbol.rs @@ -0,0 +1,124 @@ +// Copyright (C) 2019-2020 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use crate::{errors::ImportError, ImportParser}; +use leo_ast::LeoAst; +use leo_typed::{ImportSymbol, Program, Span}; + +use std::{ffi::OsString, fs::DirEntry, path::PathBuf}; + +static LIBRARY_FILE: &str = "src/lib.leo"; +static FILE_EXTENSION: &str = "leo"; + +fn parse_import_file(entry: &DirEntry, span: &Span) -> Result { + // make sure the given entry is file + let file_type = entry + .file_type() + .map_err(|error| ImportError::directory_error(error, span.clone(), entry.path()))?; + let file_name = entry + .file_name() + .to_os_string() + .into_string() + .map_err(|_| ImportError::convert_os_string(span.clone()))?; + + let mut file_path = entry.path().to_path_buf(); + if file_type.is_dir() { + file_path.push(LIBRARY_FILE); + + if !file_path.exists() { + return Err(ImportError::expected_lib_file( + format!("{:?}", file_path.as_path()), + span.clone(), + )); + } + } + + // Builds the abstract syntax tree. + let program_string = &LeoAst::load_file(&file_path)?; + let ast = &LeoAst::new(&file_path, &program_string)?; + + // Generates the Leo program from file. + Ok(Program::from(&file_name, ast.as_repr())) +} + +impl ImportParser { + pub fn parse_import_star(&mut self, entry: &DirEntry, span: &Span) -> Result<(), ImportError> { + let path = entry.path(); + let is_dir = path.is_dir(); + let is_leo_file = path + .extension() + .map_or(false, |ext| ext.eq(&OsString::from(FILE_EXTENSION))); + + let mut package_path = path.to_path_buf(); + package_path.push(LIBRARY_FILE); + + let is_package = is_dir && package_path.exists(); + + // import * can only be invoked on a package with a library file or a leo file + if is_package || is_leo_file { + // Generate aleo program from file + let program = parse_import_file(entry, &span)?; + + // Store program's imports in imports hashmap + program + .imports + .iter() + .map(|import| self.parse_package(entry.path(), &import.package)) + .collect::, ImportError>>()?; + + // Store program in imports hashmap + let file_name_path = PathBuf::from(entry.file_name()); + let file_name = file_name_path + .file_stem() + .unwrap() + .to_os_string() + .into_string() + .unwrap(); // the file exists so these will not fail + + self.insert_import(file_name, program); + + Ok(()) + } else { + // importing * from a directory or non-leo file in `package/src/` is illegal + Err(ImportError::star(entry.path().to_path_buf(), span.clone())) + } + } + + pub fn parse_import_symbol(&mut self, entry: &DirEntry, symbol: &ImportSymbol) -> Result<(), ImportError> { + // Generate aleo program from file + let program = parse_import_file(entry, &symbol.span)?; + + // Store program's imports in imports hashmap + program + .imports + .iter() + .map(|import| self.parse_package(entry.path(), &import.package)) + .collect::, ImportError>>()?; + + // Store program in imports hashmap + let file_name_path = PathBuf::from(entry.file_name()); + let file_name = file_name_path + .file_stem() + .unwrap() + .to_os_string() + .into_string() + .unwrap(); // the file exists so these will not fail + + self.insert_import(file_name, program); + + Ok(()) + } +}