Merge pull request #1774 from AleoHQ/feature/create-symbol-table

[Impl] Symbol Table
This commit is contained in:
Collin Chin 2022-05-02 09:22:17 -07:00 committed by GitHub
commit cd606ac578
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
633 changed files with 1846 additions and 4383 deletions

1
.gitattributes vendored
View File

@ -2,3 +2,4 @@
*.leo text eol=lf
*.out text eol=lf
*.rs text eol=lf
*.in text eol=lf

28
Cargo.lock generated
View File

@ -1220,26 +1220,18 @@ dependencies = [
"smallvec",
]
[[package]]
name = "leo-ast-passes"
version = "1.5.3"
dependencies = [
"indexmap",
"leo-ast",
"leo-errors",
"leo-parser",
"leo-span",
]
[[package]]
name = "leo-compiler"
version = "1.5.3"
dependencies = [
"leo-ast",
"leo-ast-passes",
"leo-errors",
"leo-parser",
"leo-passes",
"leo-span",
"leo-test-framework",
"serde",
"serde_yaml",
"sha2",
]
@ -1273,6 +1265,7 @@ dependencies = [
"leo-compiler",
"leo-errors",
"leo-package",
"leo-span",
"notify",
"rand",
"rand_core",
@ -1325,6 +1318,17 @@ dependencies = [
"tracing",
]
[[package]]
name = "leo-passes"
version = "1.5.3"
dependencies = [
"indexmap",
"leo-ast",
"leo-errors",
"leo-parser",
"leo-span",
]
[[package]]
name = "leo-span"
version = "1.5.3"

View File

@ -46,10 +46,17 @@ version = "1.5.3"
path = "./leo/package"
version = "1.5.3"
[dependencies.leo-span]
path = "./leo/span"
version = "1.5.3"
[dependencies.snarkvm-utilities]
git = "https://github.com/AleoHQ/snarkVM.git"
rev = "51633e2"
[dependencies.backtrace]
version = "0.3.65"
[dependencies.clap]
version = "3.1"
@ -59,18 +66,12 @@ version = "0.5.1"
[dependencies.colored]
version = "2.0"
[dependencies.backtrace]
version = "0.3.65"
[dependencies.sys-info]
version = "0.9.1"
[dependencies.console]
version = "0.15.0"
[dependencies.dirs]
version = "4.0.0"
[dependencies.console]
version = "0.15.0"
[dependencies.indexmap]
version = "1.8"
features = ["serde"]
@ -105,6 +106,9 @@ version = "1.0"
[dependencies.structopt]
version = "0.3"
[dependencies.sys-info]
version = "0.9.1"
[dependencies.toml]
version = "0.5"

View File

@ -18,9 +18,6 @@ license = "GPL-3.0"
edition = "2021"
rust-version = "1.56"
[dependencies]
smallvec = { version = "1.8.0", features = ["serde"] }
[dependencies.leo-errors]
path = "../../leo/errors"
version = "1.5.3"
@ -41,6 +38,10 @@ features = [ "derive", "rc" ]
version = "1.0"
features = [ "preserve_order" ]
[dependencies.smallvec]
version = "1.8.0"
features = ["serde"]
[dev-dependencies.criterion]
version = "0.3"

View File

@ -14,8 +14,10 @@
// 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::{normalize_json_value, remove_key_from_json};
use super::*;
use leo_errors::AstError;
use leo_errors::{AstError, Result};
/// Input data which includes [`ProgramInput`] and [`ProgramState`].
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
@ -24,16 +26,44 @@ pub struct Input {
pub program_state: ProgramState,
}
/// A raw unprocessed input or state file data. Used for future conversion
/// into [`ProgramInput`] or [`ProgramState`].
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ParsedInputFile {
pub sections: Vec<Section>,
}
impl Input {
/// Serializes the ast into a JSON string.
pub fn to_json_string(&self) -> Result<String> {
Ok(serde_json::to_string_pretty(&self).map_err(|e| AstError::failed_to_convert_ast_to_json_string(&e))?)
}
}
/// A raw unprocessed input or state file data. Used for future conversion
/// into [`ProgramInput`] or [`ProgramState`].
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct InputAst {
pub sections: Vec<Section>,
}
impl InputAst {
/// Serializes the `Input` into a JSON Value.
pub fn to_json_value(&self) -> Result<serde_json::Value> {
Ok(serde_json::to_value(&self).map_err(|e| AstError::failed_to_convert_ast_to_json_value(&e))?)
}
/// Serializes the `Input` into a JSON value and removes keys from object mappings before writing to a file.
pub fn to_json_file_without_keys(
&self,
mut path: std::path::PathBuf,
file_name: &str,
excluded_keys: &[&str],
) -> Result<()> {
path.push(file_name);
let file = std::fs::File::create(&path).map_err(|e| AstError::failed_to_create_ast_json_file(&path, &e))?;
let writer = std::io::BufWriter::new(file);
let mut value = self.to_json_value().unwrap();
for key in excluded_keys {
value = remove_key_from_json(value, key);
}
value = normalize_json_value(value);
Ok(serde_json::to_writer_pretty(writer, &value)
.map_err(|e| AstError::failed_to_write_ast_to_json_file(&path, &e))?)
}
}

View File

@ -14,7 +14,7 @@
// 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::{CharValue, Expression, GroupValue, IntegerType, Node, Type, ValueExpression};
use crate::{CharValue, Expression, GroupValue, IntegerType, Node, Type, UnaryOperation, ValueExpression};
use leo_errors::{InputError, LeoError, ParserError, Result};
use serde::{Deserialize, Serialize};
@ -59,6 +59,9 @@ impl TryFrom<(Type, Expression)> for InputValue {
}
}
}
(type_, Expression::Unary(unary)) if unary.op == UnaryOperation::Negate => {
InputValue::try_from((type_, *unary.inner))?
}
(_type_, expr) => return Err(InputError::illegal_expression(&expr, expr.span()).into()),
})
}

View File

@ -23,9 +23,9 @@ pub struct ProgramInput {
pub registers: Definitions,
}
impl TryFrom<ParsedInputFile> for ProgramInput {
impl TryFrom<InputAst> for ProgramInput {
type Error = LeoError;
fn try_from(input: ParsedInputFile) -> Result<Self> {
fn try_from(input: InputAst) -> Result<Self> {
let mut main = IndexMap::new();
let mut registers = IndexMap::new();

View File

@ -22,9 +22,9 @@ pub struct ProgramState {
pub state: Definitions,
}
impl TryFrom<ParsedInputFile> for ProgramState {
impl TryFrom<InputAst> for ProgramState {
type Error = LeoError;
fn try_from(input: ParsedInputFile) -> Result<Self> {
fn try_from(input: InputAst) -> Result<Self> {
let mut state = IndexMap::new();
for section in input.sections {

View File

@ -40,9 +40,6 @@ pub use self::groups::*;
pub mod input;
pub use self::input::*;
pub mod pass;
pub use self::pass::*;
pub mod passes;
pub use self::passes::*;
@ -147,7 +144,7 @@ impl AsRef<Program> for Ast {
}
/// Helper function to recursively filter keys from AST JSON
fn remove_key_from_json(value: serde_json::Value, key: &str) -> serde_json::Value {
pub(crate) fn remove_key_from_json(value: serde_json::Value, key: &str) -> serde_json::Value {
match value {
serde_json::Value::Object(map) => serde_json::Value::Object(
map.into_iter()
@ -168,7 +165,7 @@ fn remove_key_from_json(value: serde_json::Value, key: &str) -> serde_json::Valu
/// 1. Remove empty object mappings from JSON arrays
/// 2. If there are two elements in a JSON array and one is an empty object
/// mapping and the other is not, then lift up the one that isn't
fn normalize_json_value(value: serde_json::Value) -> serde_json::Value {
pub(crate) fn normalize_json_value(value: serde_json::Value) -> serde_json::Value {
match value {
serde_json::Value::Array(vec) => {
let orig_length = vec.len();

View File

@ -29,84 +29,84 @@ impl Default for VisitResult {
}
}
pub trait ExpressionVisitor {
fn visit_expression(&mut self, _input: &Expression) -> VisitResult {
pub trait ExpressionVisitor<'a> {
fn visit_expression(&mut self, _input: &'a Expression) -> VisitResult {
Default::default()
}
fn visit_identifer(&mut self, _input: &Identifier) -> VisitResult {
fn visit_identifier(&mut self, _input: &'a Identifier) -> VisitResult {
Default::default()
}
fn visit_value(&mut self, _input: &ValueExpression) -> VisitResult {
fn visit_value(&mut self, _input: &'a ValueExpression) -> VisitResult {
Default::default()
}
fn visit_binary(&mut self, _input: &BinaryExpression) -> VisitResult {
fn visit_binary(&mut self, _input: &'a BinaryExpression) -> VisitResult {
Default::default()
}
fn visit_unary(&mut self, _input: &UnaryExpression) -> VisitResult {
fn visit_unary(&mut self, _input: &'a UnaryExpression) -> VisitResult {
Default::default()
}
fn visit_ternary(&mut self, _input: &TernaryExpression) -> VisitResult {
fn visit_ternary(&mut self, _input: &'a TernaryExpression) -> VisitResult {
Default::default()
}
fn visit_call(&mut self, _input: &CallExpression) -> VisitResult {
fn visit_call(&mut self, _input: &'a CallExpression) -> VisitResult {
Default::default()
}
fn visit_err(&mut self, _input: &ErrExpression) -> VisitResult {
fn visit_err(&mut self, _input: &'a ErrExpression) -> VisitResult {
Default::default()
}
}
pub trait StatementVisitor {
fn visit_statement(&mut self, _input: &Statement) -> VisitResult {
pub trait StatementVisitor<'a> {
fn visit_statement(&mut self, _input: &'a Statement) -> VisitResult {
Default::default()
}
fn visit_return(&mut self, _input: &ReturnStatement) -> VisitResult {
fn visit_return(&mut self, _input: &'a ReturnStatement) -> VisitResult {
Default::default()
}
fn visit_definition(&mut self, _input: &DefinitionStatement) -> VisitResult {
fn visit_definition(&mut self, _input: &'a DefinitionStatement) -> VisitResult {
Default::default()
}
fn visit_assign(&mut self, _input: &AssignStatement) -> VisitResult {
fn visit_assign(&mut self, _input: &'a AssignStatement) -> VisitResult {
Default::default()
}
fn visit_conditional(&mut self, _input: &ConditionalStatement) -> VisitResult {
fn visit_conditional(&mut self, _input: &'a ConditionalStatement) -> VisitResult {
Default::default()
}
fn visit_iteration(&mut self, _input: &IterationStatement) -> VisitResult {
fn visit_iteration(&mut self, _input: &'a IterationStatement) -> VisitResult {
Default::default()
}
fn visit_console(&mut self, _input: &ConsoleStatement) -> VisitResult {
fn visit_console(&mut self, _input: &'a ConsoleStatement) -> VisitResult {
Default::default()
}
fn visit_expression_statement(&mut self, _input: &ExpressionStatement) -> VisitResult {
fn visit_expression_statement(&mut self, _input: &'a ExpressionStatement) -> VisitResult {
Default::default()
}
fn visit_block(&mut self, _input: &Block) -> VisitResult {
fn visit_block(&mut self, _input: &'a Block) -> VisitResult {
Default::default()
}
}
pub trait ProgramVisitor {
fn visit_program(&mut self, _input: &Program) -> VisitResult {
pub trait ProgramVisitor<'a> {
fn visit_program(&mut self, _input: &'a Program) -> VisitResult {
Default::default()
}
fn visit_function(&mut self, _input: &Function) -> VisitResult {
fn visit_function(&mut self, _input: &'a Function) -> VisitResult {
Default::default()
}
}

View File

@ -18,25 +18,28 @@
//! It implements default methods for each node to be made
//! given the type of node its visiting.
// temporary till we use in another pr.
#![allow(dead_code)]
use std::marker::PhantomData;
use crate::*;
pub struct VisitorDirector<V: ExpressionVisitor> {
pub struct VisitorDirector<'a, V: ExpressionVisitor<'a>> {
visitor: V,
lifetime: PhantomData<&'a ()>,
}
impl<V: ExpressionVisitor> VisitorDirector<V> {
impl<'a, V: ExpressionVisitor<'a>> VisitorDirector<'a, V> {
pub fn new(visitor: V) -> Self {
Self { visitor }
Self {
visitor,
lifetime: PhantomData,
}
}
pub fn visitor(self) -> V {
self.visitor
}
pub fn visit_expression(&mut self, input: &Expression) {
pub fn visit_expression(&mut self, input: &'a Expression) {
if let VisitResult::VisitChildren = self.visitor.visit_expression(input) {
match input {
Expression::Identifier(_) => {}
@ -50,20 +53,20 @@ impl<V: ExpressionVisitor> VisitorDirector<V> {
}
}
pub fn visit_binary(&mut self, input: &BinaryExpression) {
pub fn visit_binary(&mut self, input: &'a BinaryExpression) {
if let VisitResult::VisitChildren = self.visitor.visit_binary(input) {
self.visit_expression(&input.left);
self.visit_expression(&input.right);
}
}
pub fn visit_unary(&mut self, input: &UnaryExpression) {
pub fn visit_unary(&mut self, input: &'a UnaryExpression) {
if let VisitResult::VisitChildren = self.visitor.visit_unary(input) {
self.visit_expression(&input.inner);
}
}
pub fn visit_ternary(&mut self, input: &TernaryExpression) {
pub fn visit_ternary(&mut self, input: &'a TernaryExpression) {
if let VisitResult::VisitChildren = self.visitor.visit_ternary(input) {
self.visit_expression(&input.condition);
self.visit_expression(&input.if_true);
@ -71,16 +74,18 @@ impl<V: ExpressionVisitor> VisitorDirector<V> {
}
}
pub fn visit_call(&mut self, input: &CallExpression) {
pub fn visit_call(&mut self, input: &'a CallExpression) {
if let VisitResult::VisitChildren = self.visitor.visit_call(input) {
self.visit_expression(&input.function);
input.arguments.iter().for_each(|expr| self.visit_expression(expr));
for expr in input.arguments.iter() {
self.visit_expression(expr);
}
}
}
}
impl<V: ExpressionVisitor + StatementVisitor> VisitorDirector<V> {
fn visit_statement(&mut self, input: &Statement) {
impl<'a, V: ExpressionVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V> {
pub fn visit_statement(&mut self, input: &'a Statement) {
if let VisitResult::VisitChildren = self.visitor.visit_statement(input) {
match input {
Statement::Return(stmt) => self.visit_return(stmt),
@ -95,25 +100,25 @@ impl<V: ExpressionVisitor + StatementVisitor> VisitorDirector<V> {
}
}
fn visit_return(&mut self, input: &ReturnStatement) {
pub fn visit_return(&mut self, input: &'a ReturnStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_return(input) {
self.visit_expression(&input.expression);
}
}
fn visit_definition(&mut self, input: &DefinitionStatement) {
pub fn visit_definition(&mut self, input: &'a DefinitionStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_definition(input) {
self.visit_expression(&input.value);
}
}
fn visit_assign(&mut self, input: &AssignStatement) {
pub fn visit_assign(&mut self, input: &'a AssignStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_assign(input) {
self.visit_expression(&input.value);
}
}
fn visit_conditional(&mut self, input: &ConditionalStatement) {
pub fn visit_conditional(&mut self, input: &'a ConditionalStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_conditional(input) {
self.visit_expression(&input.condition);
self.visit_block(&input.block);
@ -123,7 +128,7 @@ impl<V: ExpressionVisitor + StatementVisitor> VisitorDirector<V> {
}
}
fn visit_iteration(&mut self, input: &IterationStatement) {
pub fn visit_iteration(&mut self, input: &'a IterationStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_iteration(input) {
self.visit_expression(&input.start);
self.visit_expression(&input.stop);
@ -131,7 +136,7 @@ impl<V: ExpressionVisitor + StatementVisitor> VisitorDirector<V> {
}
}
fn visit_console(&mut self, input: &ConsoleStatement) {
pub fn visit_console(&mut self, input: &'a ConsoleStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_console(input) {
if let ConsoleFunction::Assert(expr) = &input.function {
self.visit_expression(expr);
@ -139,30 +144,31 @@ impl<V: ExpressionVisitor + StatementVisitor> VisitorDirector<V> {
}
}
fn visit_expression_statement(&mut self, input: &ExpressionStatement) {
pub fn visit_expression_statement(&mut self, input: &'a ExpressionStatement) {
if let VisitResult::VisitChildren = self.visitor.visit_expression_statement(input) {
self.visit_expression(&input.expression);
}
}
fn visit_block(&mut self, input: &Block) {
pub fn visit_block(&mut self, input: &'a Block) {
if let VisitResult::VisitChildren = self.visitor.visit_block(input) {
input.statements.iter().for_each(|stmt| self.visit_statement(stmt));
for stmt in input.statements.iter() {
self.visit_statement(stmt);
}
}
}
}
impl<V: ExpressionVisitor + ProgramVisitor + StatementVisitor> VisitorDirector<V> {
fn visit_program(&mut self, input: &Program) {
impl<'a, V: ExpressionVisitor<'a> + ProgramVisitor<'a> + StatementVisitor<'a>> VisitorDirector<'a, V> {
pub fn visit_program(&mut self, input: &'a Program) {
if let VisitResult::VisitChildren = self.visitor.visit_program(input) {
input
.functions
.values()
.for_each(|function| self.visit_function(function));
for function in input.functions.values() {
self.visit_function(function);
}
}
}
fn visit_function(&mut self, input: &Function) {
pub fn visit_function(&mut self, input: &'a Function) {
if let VisitResult::VisitChildren = self.visitor.visit_function(input) {
self.visit_block(&input.block);
}

View File

@ -22,24 +22,36 @@ rust-version = "1.56.1"
path = "../ast"
version = "1.5.3"
[dependencies.leo-ast-passes]
path = "../ast-passes"
version = "1.5.3"
[dependencies.leo-errors]
path = "../../leo/errors"
version = "1.5.3"
[dependencies.leo-passes]
path = "../passes"
version = "1.5.3"
[dependencies.leo-parser]
path = "../parser"
version = "1.5.3"
[dependencies.leo-span]
[dependencies.sha2]
version = "0.10"
[dev-dependencies.leo-span]
path = "../../leo/span"
version = "1.5.3"
[dependencies.sha2]
version = "0.10"
[dev-dependencies.leo-test-framework]
path = "../../tests/test-framework"
version = "1.4.0"
[dev-dependencies.serde]
version = "1.0.136"
features = ["derive"]
[dev-dependencies.serde_yaml]
version = "0.8.23"
[features]
default = [ ]

View File

@ -22,38 +22,41 @@
#![allow(clippy::upper_case_acronyms)]
#![doc = include_str!("../README.md")]
pub use leo_ast::Ast;
#[cfg(test)]
mod test;
use leo_ast::Program;
pub use leo_ast::{Ast, InputAst};
use leo_errors::emitter::Handler;
use leo_errors::{CompilerError, Result};
use leo_span::symbol::create_session_if_not_set_then;
pub use leo_passes::SymbolTable;
use leo_passes::*;
use sha2::{Digest, Sha256};
use std::fs;
use std::path::PathBuf;
#[derive(Clone)]
/// The primary entry point of the Leo compiler.
pub struct Compiler<'a> {
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
input_file_path: PathBuf,
pub ast: Ast,
pub input_ast: Option<InputAst>,
}
impl<'a> Compiler<'a> {
///
/// Returns a new Leo compiler.
///
pub fn new(
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
input_file_path: PathBuf,
) -> Self {
pub fn new(handler: &'a Handler, main_file_path: PathBuf, output_directory: PathBuf) -> Self {
Self {
handler,
main_file_path,
output_directory,
input_file_path,
ast: Ast::new(Program::new("Initial".to_string())),
input_ast: None,
}
}
@ -73,28 +76,8 @@ impl<'a> Compiler<'a> {
Ok(format!("{:x}", hash))
}
///
/// Runs the compiler stages.
///
fn compiler_stages(self) -> Result<leo_ast::Ast> {
//load the input file if it exists.
let _input_ast = if self.input_file_path.exists() {
let input_string = fs::read_to_string(&self.input_file_path)
.map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
Some(leo_parser::parse_input(
self.handler,
self.input_file_path.to_str().unwrap_or_default(),
input_string,
)?)
} else {
None
};
// 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))?;
// 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,
@ -102,20 +85,58 @@ impl<'a> Compiler<'a> {
program_string,
)?;
// Write the AST snapshot post parsing.
ast.to_json_file_without_keys(self.output_directory, "initial_ast.json", &["span"])?;
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;
Ok(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))?;
self.parse_program_from_string(&program_string)
}
/// Parses and stores the input file, constructs a syntax tree, and generates a program input.
pub fn parse_input_from_string(&mut self, input_file_path: PathBuf, input_string: &str) -> Result<()> {
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"])?;
self.input_ast = Some(input_ast);
Ok(())
}
/// Parses and stores the input file, constructs a syntax tree, and generates a program input.
pub fn parse_input(&mut self, input_file_path: PathBuf) -> Result<()> {
// 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.parse_input_from_string(input_file_path, &input_string)?;
}
Ok(())
}
///
/// Runs the compiler stages.
///
fn compiler_stages(&self) -> Result<SymbolTable<'_>> {
let symbol_table = CreateSymbolTable::do_pass((&self.ast, self.handler))?;
Ok(symbol_table)
}
///
/// Returns a compiled Leo program.
///
pub fn compile(self) -> Result<leo_ast::Ast> {
create_session_if_not_set_then(|_| self.compiler_stages())
pub fn compile(&self) -> Result<SymbolTable<'_>> {
self.compiler_stages()
}
}

View File

@ -0,0 +1,224 @@
// 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 std::{
cell::RefCell,
fmt, fs,
path::{Path, PathBuf},
rc::Rc,
};
use crate::Compiler;
use leo_errors::{
emitter::{Buffer, Emitter, Handler},
LeoError, LeoWarning,
};
use leo_passes::SymbolTable;
use leo_span::symbol::create_session_if_not_set_then;
use leo_test_framework::{
runner::{Namespace, ParseType, Runner},
Test,
};
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_> {
let output_dir = PathBuf::from("/tmp/output/");
fs::create_dir_all(output_dir.clone()).unwrap();
Compiler::new(handler, main_file_path, output_dir)
}
fn parse_program<'a>(
handler: &'a Handler,
program_string: &str,
cwd: Option<PathBuf>,
) -> Result<Compiler<'a>, LeoError> {
let mut compiler = new_compiler(handler, cwd.unwrap_or_else(|| "compiler-test".into()));
compiler.parse_program_from_string(program_string)?;
Ok(compiler)
}
fn hash_content(content: &str) -> String {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
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 {
fn parse_type(&self) -> ParseType {
ParseType::Whole
}
fn run_test(&self, test: Test) -> Result<Value, String> {
let buf = BufferEmitter(Rc::default(), Rc::default());
let handler = Handler::new(Box::new(buf.clone()));
create_session_if_not_set_then(|_| run_test(test, &handler, &buf).map_err(|()| buf.0.take().to_string()))
}
}
#[derive(Deserialize, PartialEq, Serialize)]
struct OutputItem {
pub initial_input_ast: String,
pub symbol_table: String,
}
#[derive(Deserialize, PartialEq, Serialize)]
struct CompileOutput {
pub output: Vec<OutputItem>,
pub initial_ast: String,
}
/// Get the path of the `input_file` given in `input` into `list`.
fn get_input_file_paths(list: &mut Vec<PathBuf>, test: &Test, input: &Value) {
let input_file: PathBuf = test.path.parent().expect("no test parent dir").into();
if input.as_str().is_some() {
let mut input_file = input_file;
input_file.push(input.as_str().expect("input_file was not a string or array"));
list.push(input_file.clone());
}
}
/// Collect and return all inputs, if possible.
fn collect_all_inputs(test: &Test) -> Result<Vec<PathBuf>, String> {
let mut list = vec![];
if let Some(input) = test.config.get("input_file") {
get_input_file_paths(&mut list, test, input);
}
Ok(list)
}
fn compile_and_process<'a>(
parsed: &'a mut Compiler<'a>,
input_file_path: PathBuf,
) -> Result<SymbolTable<'a>, LeoError> {
parsed.parse_input(input_file_path)?;
parsed.compiler_stages()
}
// Errors used in this module.
enum LeoOrString {
Leo(LeoError),
String(String),
}
impl fmt::Display for LeoOrString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Leo(x) => x.fmt(f),
Self::String(x) => x.fmt(f),
}
}
}
/// A buffer used to emit errors into.
#[derive(Clone)]
struct BufferEmitter(Rc<RefCell<Buffer<LeoOrString>>>, Rc<RefCell<Buffer<LeoWarning>>>);
impl Emitter for BufferEmitter {
fn emit_err(&mut self, err: LeoError) {
self.0.borrow_mut().push(LeoOrString::Leo(err));
}
fn last_emited_err(&self) -> Option<LeoError> {
let temp = &*self.0.borrow();
temp.last_entry().map(|entry| match entry {
LeoOrString::Leo(err) => err.clone(),
_ => unimplemented!(),
})
}
fn emit_warning(&mut self, warning: leo_errors::LeoWarning) {
self.1.borrow_mut().push(warning);
}
}
fn buffer_if_err<T>(buf: &BufferEmitter, res: Result<T, String>) -> Result<T, ()> {
res.map_err(|err| buf.0.borrow_mut().push(LeoOrString::String(err)))
}
fn run_test(test: Test, handler: &Handler, err_buf: &BufferEmitter) -> Result<Value, ()> {
// Check for CWD option:
// ``` cwd: import ```
// When set, uses different working directory for current file.
// If not, uses file path as current working directory.
let cwd = test.config.get("cwd").map(|val| {
let mut cwd = test.path.clone();
cwd.pop();
cwd.join(&val.as_str().unwrap())
});
let parsed = handler.extend_if_error(parse_program(handler, &test.content, cwd))?;
// (name, content)
let inputs = buffer_if_err(err_buf, collect_all_inputs(&test))?;
let mut output_items = Vec::with_capacity(inputs.len());
for input in inputs {
let mut parsed = parsed.clone();
let symbol_table = handler.extend_if_error(compile_and_process(&mut parsed, input))?;
let initial_input_ast = hash_file("/tmp/output/inital_input_ast.json");
output_items.push(OutputItem {
initial_input_ast,
symbol_table: hash_content(&symbol_table.to_string()),
});
}
let initial_ast = hash_file("/tmp/output/initial_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.");
}
let final_output = CompileOutput {
output: output_items,
initial_ast,
};
Ok(serde_yaml::to_value(&final_output).expect("serialization failed"))
}
struct TestRunner;
impl Runner for TestRunner {
fn resolve_namespace(&self, name: &str) -> Option<Box<dyn Namespace>> {
Some(match name {
"Compile" => Box::new(CompileNamespace),
_ => return None,
})
}
}
#[test]
pub fn compiler_tests() {
leo_test_framework::run_tests(&TestRunner, "compiler");
}

View File

@ -23,9 +23,6 @@ name = "leo_ast"
path = "benches/leo_ast.rs"
harness = false
[dependencies]
smallvec = "1.8"
[dependencies.leo-ast]
path = "../ast"
version = "1.5.3"
@ -52,6 +49,9 @@ version = "1.3.0"
version = "1.0"
features = [ "derive" ]
[dependencies.smallvec]
version = "1.8"
[dependencies.tracing]
version = "0.1"

View File

@ -20,7 +20,7 @@ use leo_errors::{ParserError, Result};
impl ParserContext<'_> {
/// Returns a [`ParsedInputFile`] struct filled with the data acquired in the file.
pub fn parse_input(&mut self) -> Result<ParsedInputFile> {
pub fn parse_input(&mut self) -> Result<InputAst> {
let mut sections = Vec::new();
while self.has_next() {
@ -31,7 +31,7 @@ impl ParserContext<'_> {
}
}
Ok(ParsedInputFile { sections })
Ok(InputAst { sections })
}
/// Parses particular section in the Input or State file.
@ -67,7 +67,7 @@ impl ParserContext<'_> {
self.expect(&Token::Colon)?;
let (type_, span) = self.parse_non_ident_types()?;
self.expect(&Token::Assign)?;
let value = self.parse_primary_expression()?;
let value = self.parse_unary_expression()?;
self.expect(&Token::Semicolon)?;
Ok(Definition {

View File

@ -57,7 +57,7 @@ pub fn parse(handler: &Handler, path: &str, source: &str) -> Result<Program> {
}
/// Parses an input file at the given file `path` and `source` code text.
pub fn parse_input<T: AsRef<str>, Y: AsRef<str>>(handler: &Handler, path: T, source: Y) -> Result<ParsedInputFile> {
pub fn parse_input<T: AsRef<str>, Y: AsRef<str>>(handler: &Handler, path: T, source: Y) -> Result<InputAst> {
let mut tokens = ParserContext::new(handler, crate::tokenize(path.as_ref(), source.as_ref())?);
tokens.parse_input()

View File

@ -1,5 +1,5 @@
[package]
name = "leo-ast-passes"
name = "leo-passes"
version = "1.5.3"
authors = [ "The Aleo Team <hello@aleo.org>" ]
description = "The Leo programming language"
@ -39,7 +39,3 @@ version = "1.5.3"
[dependencies.leo-span]
path = "../../leo/span"
version = "1.5.3"
#[dependencies.leo-stdlib]
#path = "../stdlib"
#version = "1.5.3"

View File

@ -25,3 +25,9 @@ pub use canonicalization::*;
// until we migrate stdlib and then import resolution.
/* pub mod import_resolution;
pub use import_resolution::*; */
pub mod pass;
pub use self::pass::*;
pub mod symbol_table;
pub use symbol_table::*;

View File

@ -14,10 +14,10 @@
// 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::{Ast, Program};
use leo_errors::Result;
/// A pass consuming a `Program` and possibly returning an `Ast`.
pub trait AstPass {
fn do_pass(self, ast: Program) -> Result<Ast>;
pub trait Pass<'a> {
type Input;
type Output;
fn do_pass(input: Self::Input) -> Self::Output;
}

View File

@ -0,0 +1,50 @@
// 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 leo_ast::*;
use leo_errors::emitter::Handler;
use crate::SymbolTable;
pub struct CreateSymbolTable<'a> {
symbol_table: SymbolTable<'a>,
handler: &'a Handler,
}
impl<'a> CreateSymbolTable<'a> {
pub fn new(handler: &'a Handler) -> Self {
Self {
symbol_table: SymbolTable::default(),
handler,
}
}
pub fn symbol_table(self) -> SymbolTable<'a> {
self.symbol_table
}
}
impl<'a> ExpressionVisitor<'a> for CreateSymbolTable<'a> {}
impl<'a> StatementVisitor<'a> for CreateSymbolTable<'a> {}
impl<'a> ProgramVisitor<'a> for CreateSymbolTable<'a> {
fn visit_function(&mut self, input: &'a Function) -> VisitResult {
if let Err(err) = self.symbol_table.insert_fn(input.name(), input) {
self.handler.emit_err(err);
}
VisitResult::SkipChildren
}
}

View File

@ -0,0 +1,42 @@
// 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/>.
pub mod create;
pub use create::*;
pub mod table;
pub use table::*;
pub mod variable_symbol;
pub use variable_symbol::*;
use crate::Pass;
use leo_ast::{Ast, VisitorDirector};
use leo_errors::{emitter::Handler, Result};
impl<'a> Pass<'a> for CreateSymbolTable<'a> {
type Input = (&'a Ast, &'a Handler);
type Output = Result<SymbolTable<'a>>;
fn do_pass((ast, handler): Self::Input) -> Self::Output {
let mut visitor = VisitorDirector::new(CreateSymbolTable::new(handler));
visitor.visit_program(ast.as_repr());
handler.last_err()?;
Ok(visitor.visitor().symbol_table())
}
}

View File

@ -0,0 +1,68 @@
// 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 std::fmt::Display;
use leo_ast::Function;
use leo_errors::{AstError, Result};
use leo_span::Symbol;
use indexmap::IndexMap;
use crate::VariableSymbol;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SymbolTable<'a> {
/// Functions represents the name of each function mapped to the Ast's function definition.
/// This field is populated at a first pass.
functions: IndexMap<Symbol, &'a Function>,
/// Variables represents functions variable definitions and input variables.
/// This field is not populated till necessary.
variables: VariableSymbol<'a>,
}
impl<'a> SymbolTable<'a> {
pub fn check_shadowing(&self, symbol: &Symbol) -> Result<()> {
if let Some(function) = self.functions.get(symbol) {
Err(AstError::shadowed_function(symbol, &function.span).into())
} else {
self.variables.check_shadowing(symbol)?;
Ok(())
}
}
pub fn clear_variables(&mut self) {
self.variables.clear();
}
pub fn insert_fn(&mut self, symbol: Symbol, function: &'a Function) -> Result<()> {
self.check_shadowing(&symbol)?;
self.functions.insert(symbol, function);
Ok(())
}
}
impl<'a> Display for SymbolTable<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "SymbolTable")?;
for func in self.functions.values() {
write!(f, "{func}")?;
}
write!(f, "{}", self.variables)
}
}

View File

@ -0,0 +1,91 @@
// 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 std::fmt::Display;
use indexmap::IndexMap;
use leo_ast::{DefinitionStatement, FunctionInput, FunctionInputVariable};
use leo_errors::{AstError, Result};
use leo_span::Symbol;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct VariableSymbol<'a> {
/// The parent scope of variables if it exists.
/// For example if we are in a if block inside a function.
/// The parent would be the functions variables and inputs.
/// This field is populated as necessary.
parent: Option<Box<VariableSymbol<'a>>>,
/// The input variables defined in a scope.
/// This field is populated as necessary.
inputs: IndexMap<Symbol, &'a FunctionInputVariable>,
/// The variables defined in a scope.
/// This field is populated as necessary.
variables: IndexMap<Symbol, &'a DefinitionStatement>,
}
impl<'a> VariableSymbol<'a> {
pub fn new(parent: Option<Box<VariableSymbol<'a>>>, inputs: Vec<&'a FunctionInput>) -> Self {
Self {
parent,
inputs: inputs
.iter()
.map(|input| {
let inner = input.get_variable();
(inner.identifier.name, inner)
})
.collect(),
variables: IndexMap::new(),
}
}
pub fn check_shadowing(&self, symbol: &Symbol) -> Result<()> {
if let Some(input) = self.inputs.get(symbol) {
Err(AstError::shadowed_function_input(symbol, &input.span).into())
} else if let Some(var) = self.variables.get(symbol) {
Err(AstError::shadowed_variable(symbol, &var.span).into())
} else if let Some(parent) = &self.parent {
parent.check_shadowing(symbol)
} else {
Ok(())
}
}
pub fn clear(&mut self) {
self.parent = None;
self.inputs.clear();
self.variables.clear();
}
}
impl<'a> Display for VariableSymbol<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "VariableSymbol")?;
self.parent
.as_ref()
.map(|parent| write!(f, "parent {parent}"))
.transpose()?;
for input in self.inputs.values() {
write!(f, "{input}")?;
}
for var in self.variables.values() {
write!(f, "{var}")?;
}
Ok(())
}
}

Some files were not shown because too many files have changed in this diff Show More