mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-12-24 02:31:44 +03:00
Additional validation for program scope and imported files
This commit is contained in:
parent
8f2a6fdb27
commit
e15e8f05d6
@ -344,6 +344,7 @@ pub trait ProgramReconstructor: StatementReconstructor {
|
||||
.into_iter()
|
||||
.map(|(i, f)| (i, self.reconstruct_function(f)))
|
||||
.collect(),
|
||||
span: input.span,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
use crate::{Function, Identifier, Mapping, Struct};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use leo_span::Span;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
@ -35,6 +36,8 @@ pub struct ProgramScope {
|
||||
pub mappings: IndexMap<Identifier, Mapping>,
|
||||
/// A map from function names to function definitions.
|
||||
pub functions: IndexMap<Identifier, Function>,
|
||||
/// The span associated with the program scope.
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProgramScope {
|
||||
|
@ -51,6 +51,8 @@ pub struct Compiler<'a> {
|
||||
pub input_ast: Option<InputAst>,
|
||||
/// Compiler options on some optional output files.
|
||||
output_options: OutputOptions,
|
||||
/// Whether or not we are compiling an imported program.
|
||||
is_import: bool,
|
||||
}
|
||||
|
||||
impl<'a> Compiler<'a> {
|
||||
@ -62,6 +64,7 @@ impl<'a> Compiler<'a> {
|
||||
main_file_path: PathBuf,
|
||||
output_directory: PathBuf,
|
||||
output_options: Option<OutputOptions>,
|
||||
is_import: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
handler,
|
||||
@ -72,6 +75,7 @@ impl<'a> Compiler<'a> {
|
||||
ast: Ast::new(Program::default()),
|
||||
input_ast: None,
|
||||
output_options: output_options.unwrap_or_default(),
|
||||
is_import,
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +101,20 @@ impl<'a> Compiler<'a> {
|
||||
// Use the parser to construct the abstract syntax tree (ast).
|
||||
self.ast = leo_parser::parse_ast(self.handler, &prg_sf.src, prg_sf.start_pos)?;
|
||||
|
||||
// TODO: Check that the program name matches the file name.
|
||||
// If the program is imported, then check that the name of its program scope matches the file name.
|
||||
// Note that parsing enforces that there is exactly one program scope in a file.
|
||||
// TODO: Clean up check.
|
||||
let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
|
||||
let program_scope_name =
|
||||
with_session_globals(|s| program_scope.name.name.as_str(s, |string| string.to_string()));
|
||||
if self.is_import && program_scope_name != self.program_name {
|
||||
return Err(CompilerError::imported_program_name_does_not_match_filename(
|
||||
program_scope_name,
|
||||
self.program_name.clone(),
|
||||
program_scope.name.span,
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if self.output_options.initial_ast {
|
||||
self.write_ast_to_json("initial_ast.json")?;
|
||||
|
@ -60,6 +60,7 @@ fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_> {
|
||||
ssa_ast: true,
|
||||
flattened_ast: true,
|
||||
}),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,9 @@ impl ParserContext<'_> {
|
||||
let mut imports = IndexMap::new();
|
||||
let mut program_scopes = IndexMap::new();
|
||||
|
||||
// TODO: Condense logic
|
||||
// TODO: Remove restrictions on multiple program scopes
|
||||
let mut parsed_program_scope = false;
|
||||
|
||||
while self.has_next() {
|
||||
match &self.token.token {
|
||||
Token::Import => {
|
||||
@ -36,12 +38,25 @@ impl ParserContext<'_> {
|
||||
imports.insert(id, import);
|
||||
}
|
||||
Token::Program => {
|
||||
match parsed_program_scope {
|
||||
// Only one program scope is allowed per file.
|
||||
true => return Err(ParserError::only_one_program_scope_is_allowed(self.token.span).into()),
|
||||
false => {
|
||||
parsed_program_scope = true;
|
||||
let program_scope = self.parse_program_scope()?;
|
||||
program_scopes.insert((program_scope.name, program_scope.network), program_scope);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => return Err(Self::unexpected_item(&self.token).into()),
|
||||
}
|
||||
}
|
||||
|
||||
// Requires that at least one program scope is present.
|
||||
if !parsed_program_scope {
|
||||
return Err(ParserError::missing_program_scope(self.token.span).into());
|
||||
}
|
||||
|
||||
Ok(Program {
|
||||
imports,
|
||||
program_scopes,
|
||||
@ -109,18 +124,22 @@ impl ParserContext<'_> {
|
||||
/// Parsers a program scope `program foo.aleo { ... }`.
|
||||
fn parse_program_scope(&mut self) -> Result<ProgramScope> {
|
||||
// Parse `program` keyword.
|
||||
let _start = self.expect(&Token::Program)?;
|
||||
let start = self.expect(&Token::Program)?;
|
||||
|
||||
// Parse the program name.
|
||||
let name = self.expect_identifier()?;
|
||||
|
||||
// Parse the program network.
|
||||
self.expect(&Token::Dot)?;
|
||||
|
||||
let network = self.expect_identifier()?;
|
||||
|
||||
// Check that the program network is valid.
|
||||
if network.name != sym::aleo {
|
||||
return Err(ParserError::invalid_network(network.span).into());
|
||||
}
|
||||
|
||||
// Parse `{`.
|
||||
let _open = self.expect(&Token::LeftCurly)?;
|
||||
self.expect(&Token::LeftCurly)?;
|
||||
|
||||
// Parse the body of the program scope.
|
||||
let mut functions = IndexMap::new();
|
||||
@ -148,7 +167,7 @@ impl ParserContext<'_> {
|
||||
}
|
||||
|
||||
// Parse `}`.
|
||||
let _close = self.expect(&Token::RightCurly)?;
|
||||
let end = self.expect(&Token::RightCurly)?;
|
||||
|
||||
Ok(ProgramScope {
|
||||
name,
|
||||
@ -156,6 +175,7 @@ impl ParserContext<'_> {
|
||||
functions,
|
||||
structs,
|
||||
mappings,
|
||||
span: start + end,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,7 @@ impl ProgramScopeConsumer for StaticSingleAssigner {
|
||||
.into_iter()
|
||||
.map(|(i, f)| (i, self.consume_function(f)))
|
||||
.collect(),
|
||||
span: input.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,4 +63,11 @@ create_messages!(
|
||||
msg: format!("Program name `{program_name}` should match file name `{file_name}`"),
|
||||
help: None,
|
||||
}
|
||||
|
||||
@formatted
|
||||
imported_program_name_does_not_match_filename {
|
||||
args: (program_scope_name: impl Display, file_name: impl Display),
|
||||
msg: format!("The program scope `{program_scope_name}` does not match its filename `{file_name}`."),
|
||||
help: None,
|
||||
}
|
||||
);
|
||||
|
@ -302,7 +302,7 @@ create_messages!(
|
||||
@backtraced
|
||||
source_directory_can_contain_only_one_file {
|
||||
args: (),
|
||||
msg: "The `src/` directory can contain only one file.".to_string(),
|
||||
msg: "The `src/` directory can contain only one file and must be named `main.leo`.".to_string(),
|
||||
help: None,
|
||||
}
|
||||
);
|
||||
|
@ -250,4 +250,25 @@ create_messages!(
|
||||
msg: "The keyword `circuit` is deprecated.",
|
||||
help: Some("Use `struct` instead.".to_string()),
|
||||
}
|
||||
|
||||
@formatted
|
||||
only_one_program_scope_is_allowed {
|
||||
args: (),
|
||||
msg: "Only one program scope is allowed in a Leo file.",
|
||||
help: None,
|
||||
}
|
||||
|
||||
@formatted
|
||||
missing_program_scope {
|
||||
args: (),
|
||||
msg: "Missing a program scope in a Leo file.",
|
||||
help: Some("Add a program scope of the form: `program <name>.aleo { ... }` to the Leo file.".to_string()),
|
||||
}
|
||||
|
||||
@formatted
|
||||
invalid_network {
|
||||
args: (),
|
||||
msg: "Invalid network identifier. The only supported identifier is `aleo`.",
|
||||
help: None,
|
||||
}
|
||||
);
|
||||
|
@ -137,6 +137,7 @@ impl Command for Build {
|
||||
&build_directory,
|
||||
&handler,
|
||||
self.compiler_options.clone(),
|
||||
false,
|
||||
)?);
|
||||
}
|
||||
|
||||
@ -157,6 +158,7 @@ impl Command for Build {
|
||||
&build_imports_directory,
|
||||
&handler,
|
||||
self.compiler_options.clone(),
|
||||
true,
|
||||
)?);
|
||||
}
|
||||
}
|
||||
@ -197,6 +199,8 @@ impl Command for Build {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiles a Leo file in the `src/` directory.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn compile_leo_file(
|
||||
file_path: PathBuf,
|
||||
_package_path: &Path,
|
||||
@ -205,6 +209,7 @@ fn compile_leo_file(
|
||||
build: &Path,
|
||||
handler: &Handler,
|
||||
options: BuildOptions,
|
||||
is_import: bool,
|
||||
) -> Result<IndexMap<Symbol, Struct>> {
|
||||
// Construct the Leo file name with extension `foo.leo`.
|
||||
let file_name = file_path
|
||||
@ -218,13 +223,14 @@ fn compile_leo_file(
|
||||
.ok_or_else(PackageError::failed_to_get_file_name)?;
|
||||
|
||||
// Create a new instance of the Leo compiler.
|
||||
let mut program = Compiler::new(
|
||||
let mut compiler = Compiler::new(
|
||||
program_name.to_string(),
|
||||
program_id.network().to_string(),
|
||||
handler,
|
||||
file_path.clone(),
|
||||
outputs.to_path_buf(),
|
||||
Some(options.into()),
|
||||
is_import,
|
||||
);
|
||||
|
||||
// TODO: Temporarily removing checksum files. Need to redesign this scheme.
|
||||
@ -258,7 +264,7 @@ fn compile_leo_file(
|
||||
|
||||
// if checksum_differs {
|
||||
// Compile the Leo program into Aleo instructions.
|
||||
let (symbol_table, instructions) = program.compile_and_generate_instructions()?;
|
||||
let (symbol_table, instructions) = compiler.compile_and_generate_instructions()?;
|
||||
|
||||
// Create the path to the Aleo file.
|
||||
let mut aleo_file_path = build.to_path_buf();
|
||||
|
@ -18,6 +18,7 @@ use crate::parse_file_paths;
|
||||
|
||||
use leo_errors::{PackageError, Result};
|
||||
|
||||
use crate::source::MAIN_FILENAME;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs,
|
||||
@ -59,7 +60,7 @@ impl SourceDirectory {
|
||||
pub fn check_files(paths: &[PathBuf]) -> Result<()> {
|
||||
match paths.len() {
|
||||
0 => Err(PackageError::empty_source_directory().into()),
|
||||
1 => Ok(()),
|
||||
1 if paths[0].as_path().ends_with(MAIN_FILENAME) => Ok(()),
|
||||
_ => Err(PackageError::source_directory_can_contain_only_one_file().into()),
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +80,7 @@ fn new_compiler(handler: &Handler) -> Compiler<'_> {
|
||||
PathBuf::from(String::new()),
|
||||
PathBuf::from(String::new()),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user