Merge pull request #1591 from AleoHQ/import-tree

[WIP] Refactor / Simplify imports in the AST
This commit is contained in:
Collin Chin 2022-02-02 14:51:23 -08:00 committed by GitHub
commit 9febc01c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 168 additions and 528 deletions

View File

@ -1,58 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::PackageOrPackages;
use leo_span::{Span, Symbol};
use serde::{Deserialize, Serialize};
use std::fmt;
/// Represents an import statement in a Leo program.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct ImportStatement {
/// The package or packages to import.
pub package_or_packages: PackageOrPackages,
/// The span, excluding the `;`.
pub span: Span,
}
impl ImportStatement {
/// Returns the the package file name of the self import statement.
pub fn get_file_name(&self) -> Symbol {
match self.package_or_packages {
PackageOrPackages::Package(ref package) => package.name.name,
PackageOrPackages::Packages(ref packages) => packages.name.name,
}
}
}
impl ImportStatement {
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "import {};", self.package_or_packages)
}
}
impl fmt::Display for ImportStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Debug for ImportStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}

View File

@ -1,76 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::Identifier;
use leo_span::{sym, Span};
use serde::{Deserialize, Serialize};
use std::fmt;
/// An import of `symbol` possibly renamed to `alias`,
/// e.g., `symbol` or `symbol as alias`.
///
/// This is the leaf of an import tree.
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ImportSymbol {
pub symbol: Identifier,
/// The name, if any, to import `symbol` as.
/// If not specified, `symbol` is the name it is imported under.
pub alias: Option<Identifier>,
/// The span including `symbol` and possibly `as alias`.
pub span: Span,
}
impl fmt::Display for ImportSymbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.alias.is_some() {
write!(f, "{} as {}", self.symbol, self.alias.as_ref().unwrap())
} else {
write!(f, "{}", self.symbol)
}
}
}
// TODO (collin): remove this
impl fmt::Debug for ImportSymbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.alias.is_some() {
write!(f, "{} as {}", self.symbol, self.alias.as_ref().unwrap())
} else {
write!(f, "{}", self.symbol)
}
}
}
impl ImportSymbol {
/// Creates a glob `*` import.
pub fn star(span: &Span) -> Self {
Self {
symbol: Identifier {
name: sym::Star,
span: span.clone(),
},
alias: None,
span: span.clone(),
}
}
/// Is this a glob import?
pub fn is_star(&self) -> bool {
self.symbol.name == sym::Star
}
}

View File

@ -14,20 +14,106 @@
// 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/>.
pub mod import;
pub use import::*;
use crate::Identifier;
use leo_span::{Span, Symbol};
pub mod import_symbol;
pub use import_symbol::*;
use serde::{Deserialize, Serialize};
use std::fmt;
pub mod package;
pub use package::*;
/// Represents an import statement in a Leo program.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct ImportStatement {
/// The tree specifying what items or packages to import.
pub tree: ImportTree,
/// The span, excluding the `;`.
pub span: Span,
}
pub mod packages;
pub use packages::*;
impl ImportStatement {
/// Returns the the package file name of the self import statement.
pub fn get_file_name(&self) -> Symbol {
self.tree.base.first().unwrap().name
}
}
pub mod package_or_packages;
pub use package_or_packages::*;
impl fmt::Display for ImportStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "import {};", self.tree)
}
}
pub mod package_access;
pub use package_access::*;
impl fmt::Debug for ImportStatement {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
/// An import tree specifies item(s) to import.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct ImportTree {
/// A path to the base item or package to import or import from.
/// The list is always non-empty.
pub base: Vec<Identifier>,
/// Specifies the kind of import and the meaning of `base`.
/// This includes plain imports, renames, globs (`*`), and nested imports.
pub kind: ImportTreeKind,
/// The span for the import excluding `import` and `;`.
pub span: Span,
}
impl fmt::Display for ImportTree {
/// Formats `self` to `f`.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// Format the path.
for (i, part) in self.base.iter().enumerate() {
write!(f, "{}", part)?;
if i < self.base.len() - 1 {
write!(f, ".")?;
}
}
// Format the kind.
match &self.kind {
ImportTreeKind::Glob { .. } => write!(f, ".*"),
ImportTreeKind::Leaf { alias: None } => Ok(()),
ImportTreeKind::Leaf { alias: Some(alias) } => write!(f, "as {}", alias),
ImportTreeKind::Nested { tree } => {
write!(f, ".(")?;
for (i, node) in tree.iter().enumerate() {
write!(f, "{}", node)?;
if i < tree.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
}
}
}
impl fmt::Debug for ImportTree {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
/// Specifies the import kind and the meaning of `base`.
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum ImportTreeKind {
/// A glob import `*`.
Glob {
/// The span for the `*`.
span: Span,
},
/// A leaf package to import.
Leaf {
/// When specified, the package is imported under a different name.
/// Otherwise, the `base` name is used as in the `ImportTree`.
alias: Option<Identifier>,
},
/// A nested import of items or sub-packages.
Nested {
/// The sub-tree specifying what to import from the `base`.
tree: Vec<ImportTree>,
},
}

View File

@ -1,51 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::{common::Identifier, PackageAccess};
use leo_span::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
/// A package import specification.
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Package {
/// The base package to import `access` from.
pub name: Identifier,
/// A specification of what to import from `name`.
pub access: PackageAccess,
/// The span including the `name` and the `access`.
pub span: Span,
}
impl Package {
/// Formats `self` to `f`.
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.name, self.access)
}
}
impl fmt::Display for Package {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Debug for Package {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}

View File

@ -1,92 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::{ImportSymbol, Node, Package, Packages};
use leo_span::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum PackageAccess {
/// A glob import `*`.
Star {
/// The span for the `*`.
span: Span,
},
/// A subpackage to import.
SubPackage(Box<Package>),
/// A leaf package to import.
Symbol(ImportSymbol),
/// Several subpackages to import.
// FIXME(Centril): This structure seems convoluted and unclear.
// Refactor and simplify the types to:
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/struct.UseTree.html.
Multiple(Packages),
}
impl Node for PackageAccess {
fn span(&self) -> &Span {
match self {
PackageAccess::Star { span } => span,
PackageAccess::SubPackage(package) => &package.span,
PackageAccess::Symbol(package) => &package.span,
PackageAccess::Multiple(package) => &package.span,
}
}
fn set_span(&mut self, span: Span) {
match self {
PackageAccess::Star { span } => *span = span.clone(),
PackageAccess::SubPackage(package) => package.span = span,
PackageAccess::Symbol(package) => package.span = span,
PackageAccess::Multiple(package) => package.span = span,
}
}
}
impl PackageAccess {
/// Formats `self` to `f`.
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PackageAccess::Star { .. } => write!(f, "*"),
PackageAccess::SubPackage(ref package) => write!(f, "{}", package),
PackageAccess::Symbol(ref symbol) => write!(f, "{}", symbol),
PackageAccess::Multiple(ref packages) => {
write!(f, "(")?;
for (i, access) in packages.accesses.iter().enumerate() {
write!(f, "{}", access)?;
if i < packages.accesses.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
}
}
}
impl fmt::Debug for PackageAccess {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Display for PackageAccess {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}

View File

@ -1,77 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::{Node, Package, Packages};
use leo_span::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
/// A specification of what packages to import.
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub enum PackageOrPackages {
/// Instruction to import a single package or item.
Package(Package),
/// Instruction to import a packages or items with a common prefix.
Packages(Packages),
}
impl PackageOrPackages {
/// Formats `self` to `f`.
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PackageOrPackages::Package(ref package) => write!(f, "{}", package),
PackageOrPackages::Packages(ref packages) => {
write!(f, "(")?;
for (i, access) in packages.accesses.iter().enumerate() {
write!(f, "{}", access)?;
if i < packages.accesses.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
}
}
}
impl fmt::Debug for PackageOrPackages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Display for PackageOrPackages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl Node for PackageOrPackages {
fn span(&self) -> &Span {
match self {
PackageOrPackages::Package(package) => &package.span,
PackageOrPackages::Packages(packages) => &packages.span,
}
}
fn set_span(&mut self, span: Span) {
match self {
PackageOrPackages::Package(package) => package.span = span,
PackageOrPackages::Packages(packages) => packages.span = span,
}
}
}

View File

@ -1,58 +0,0 @@
// Copyright (C) 2019-2022 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 <https://www.gnu.org/licenses/>.
use crate::{common::Identifier, PackageAccess};
use leo_span::Span;
use serde::{Deserialize, Serialize};
use std::fmt;
/// Import of `name.(accesses)`, that is, several sub-packages or items within `name`.
#[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct Packages {
/// The common package that `accesses` are contained within.
pub name: Identifier,
/// The packages or items to import within the package `name`.
pub accesses: Vec<PackageAccess>,
/// The entire span for `name.(accesses)`.
pub span: Span,
}
impl Packages {
/// Formats `self` to `f`.
fn format(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.(", self.name)?;
for (i, access) in self.accesses.iter().enumerate() {
write!(f, "{}", access)?;
if i < self.accesses.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")
}
}
impl fmt::Display for Packages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}
impl fmt::Debug for Packages {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.format(f)
}
}

View File

@ -496,27 +496,33 @@ impl<R: ReconstructingReducer> ReconstructingDirector<R> {
self.reducer.reduce_function_input(input, new)
}
pub fn reduce_package_or_packages(&mut self, package_or_packages: &PackageOrPackages) -> Result<PackageOrPackages> {
let new = match package_or_packages {
PackageOrPackages::Package(package) => PackageOrPackages::Package(Package {
name: self.reduce_identifier(&package.name)?,
access: package.access.clone(),
span: package.span.clone(),
}),
PackageOrPackages::Packages(packages) => PackageOrPackages::Packages(Packages {
name: self.reduce_identifier(&packages.name)?,
accesses: packages.accesses.clone(),
span: packages.span.clone(),
}),
pub fn reduce_import_tree(&mut self, tree: &ImportTree) -> Result<ImportTree> {
let new = ImportTree {
base: tree
.base
.iter()
.map(|i| self.reduce_identifier(i))
.collect::<Result<_>>()?,
kind: match &tree.kind {
ImportTreeKind::Glob { .. } | ImportTreeKind::Leaf { alias: None } => tree.kind.clone(),
ImportTreeKind::Leaf { alias: Some(alias) } => {
let alias = self.reduce_identifier(alias)?;
ImportTreeKind::Leaf { alias: Some(alias) }
}
ImportTreeKind::Nested { tree } => ImportTreeKind::Nested {
tree: tree.iter().map(|n| self.reduce_import_tree(n)).collect::<Result<_>>()?,
},
},
span: tree.span.clone(),
};
self.reducer.reduce_package_or_packages(package_or_packages, new)
self.reducer.reduce_import_tree(tree, new)
}
pub fn reduce_import_statement(&mut self, import: &ImportStatement) -> Result<ImportStatement> {
let package_or_packages = self.reduce_package_or_packages(&import.package_or_packages)?;
self.reducer.reduce_import_statement(import, package_or_packages)
let tree = self.reduce_import_tree(&import.tree)?;
self.reducer.reduce_import_statement(import, tree)
}
pub fn reduce_import(&mut self, identifier: &[Symbol], import: &Program) -> Result<(Vec<Symbol>, Program)> {

View File

@ -421,21 +421,13 @@ pub trait ReconstructingReducer {
Ok(new)
}
fn reduce_package_or_packages(
&mut self,
_package_or_packages: &PackageOrPackages,
new: PackageOrPackages,
) -> Result<PackageOrPackages> {
fn reduce_import_tree(&mut self, _tree: &ImportTree, new: ImportTree) -> Result<ImportTree> {
Ok(new)
}
fn reduce_import_statement(
&mut self,
import: &ImportStatement,
package_or_packages: PackageOrPackages,
) -> Result<ImportStatement> {
fn reduce_import_statement(&mut self, import: &ImportStatement, tree: ImportTree) -> Result<ImportStatement> {
Ok(ImportStatement {
package_or_packages,
tree,
span: import.span.clone(),
})
}

View File

@ -425,6 +425,8 @@ impl<'a> ParserContext<'a> {
trailing = false;
break;
}
trailing = true;
}
// Parse closing delimiter.

View File

@ -18,7 +18,7 @@ use super::*;
use crate::KEYWORD_TOKENS;
use leo_errors::{ParserError, Result};
use leo_span::{sym, Span};
use leo_span::sym;
impl ParserContext<'_> {
///
@ -146,64 +146,6 @@ impl ParserContext<'_> {
Ok(name)
}
/// Returns a vector of [`PackageAccess`] AST nodes if the next tokens represent package access
/// expressions within an import statement.
pub fn parse_package_accesses(&mut self, start: &Span) -> Result<(Vec<PackageAccess>, Span)> {
let (out, _, end) = self.parse_paren_comma_list(|p| p.parse_package_access().map(Some))?;
if out.is_empty() {
self.emit_err(ParserError::invalid_import_list(&end));
}
Ok((out, start + &end))
}
///
/// Returns a [`PackageAccess`] AST node if the next tokens represent a package access expression
/// within an import statement.
///
pub fn parse_package_access(&mut self) -> Result<PackageAccess> {
if let Some(SpannedToken { span, .. }) = self.eat(Token::Mul) {
Ok(PackageAccess::Star { span })
} else {
let mut name = self.expect_ident()?;
// Allow dashes in the accessed members (should only be used for directories).
// If imported member does not exist, code will fail on ASG level.
if let Token::Minus = self.peek_token().as_ref() {
let span = self.expect(Token::Minus)?;
name.span = name.span + span;
let next = self.expect_ident()?;
name.span = name.span + next.span;
name.name = Symbol::intern(&format!("{}-{}", name.name, next.name));
}
if self.peek_token().as_ref() == &Token::Dot {
self.backtrack(SpannedToken {
token: Token::Ident(name.name),
span: name.span,
});
Ok(match self.parse_package_path()? {
PackageOrPackages::Package(p) => PackageAccess::SubPackage(Box::new(p)),
PackageOrPackages::Packages(p) => PackageAccess::Multiple(p),
})
} else if self.eat(Token::As).is_some() {
let alias = self.expect_ident()?;
Ok(PackageAccess::Symbol(ImportSymbol {
span: &name.span + &alias.span,
symbol: name,
alias: Some(alias),
}))
} else {
Ok(PackageAccess::Symbol(ImportSymbol {
span: name.span.clone(),
symbol: name,
alias: None,
}))
}
}
}
/// Returns an [`Identifier`] AST node if the next tokens represent a valid package name.
pub fn parse_package_name(&mut self) -> Result<Identifier> {
// Build the package name, starting with valid characters up to a dash `-` (Token::Minus).
@ -257,33 +199,57 @@ impl ParserContext<'_> {
Ok(base)
}
///
/// Returns a [`PackageOrPackages`] AST node if the next tokens represent a valid package import
/// Returns an [`ImportTree`] AST node if the next tokens represent a valid package import
/// with accesses.
///
pub fn parse_package_path(&mut self) -> Result<PackageOrPackages> {
let name = self.parse_package_name()?;
self.expect(Token::Dot)?;
if self.peek_is_left_par() {
let (accesses, span) = self.parse_package_accesses(&name.span)?;
Ok(PackageOrPackages::Packages(Packages { span, name, accesses }))
} else {
let access = self.parse_package_access()?;
let span = &name.span + access.span();
Ok(PackageOrPackages::Package(Package { span, name, access }))
fn parse_import_tree(&mut self) -> Result<ImportTree> {
// Parse the first part of the path.
let first_name = self.parse_package_name()?;
let start = first_name.span.clone();
let mut base = vec![first_name];
let make = |base, end, kind| {
let span = start + end;
ImportTree { base, span, kind }
};
// Paths are separated by `.`s.
while self.eat(Token::Dot).is_some() {
if self.peek_is_left_par() {
// Encountered `.(`, so we have a nested import. Recurse!
let (tree, _, end) = self.parse_paren_comma_list(|p| p.parse_import_tree().map(Some))?;
if tree.is_empty() {
self.emit_err(ParserError::invalid_import_list(&end));
}
return Ok(make(base, end, ImportTreeKind::Nested { tree }));
} else if let Some(SpannedToken { span, .. }) = self.eat(Token::Mul) {
// Encountered `.*`, so we have a glob import.
return Ok(make(base, span.clone(), ImportTreeKind::Glob { span }));
}
// Parse another path segment.
base.push(self.parse_package_name()?);
}
let (end, alias) = if self.eat(Token::As).is_some() {
// Encountered `as`, so interpret as `path as rename`.
let alias = self.expect_ident()?;
(alias.span.clone(), Some(alias))
} else {
(base.last().unwrap().span.clone(), None)
};
Ok(make(base, end, ImportTreeKind::Leaf { alias }))
}
///
/// Returns a [`ImportStatement`] AST node if the next tokens represent an import statement.
///
pub fn parse_import_statement(&mut self) -> Result<ImportStatement> {
self.expect(Token::Import)?;
let package_or_packages = self.parse_package_path()?;
let tree = self.parse_import_tree()?;
self.expect(Token::Semicolon)?;
Ok(ImportStatement {
span: package_or_packages.span().clone(),
package_or_packages,
span: tree.span.clone(),
tree,
})
}