#[macro_use]
extern crate pest_derive;
#[macro_use]
pub mod error;
mod asm_generation;
mod asm_lang;
mod build_config;
mod concurrent_slab;
pub mod constants;
mod control_flow_analysis;
mod ident;
pub mod parse_tree;
mod parser;
pub mod semantic_analysis;
mod span;
mod style;
pub mod type_engine;
use crate::asm_generation::checks::check_invalid_opcodes;
pub use crate::parse_tree::*;
pub use crate::parser::{HllParser, Rule};
use crate::{asm_generation::compile_ast_to_asm, error::*};
pub use asm_generation::{AbstractInstructionSet, FinalizedAsm, HllAsmSet};
pub use build_config::BuildConfig;
use control_flow_analysis::{ControlFlowGraph, Graph};
use pest::iterators::Pair;
use pest::Parser;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
pub use semantic_analysis::TreeType;
pub use semantic_analysis::TypedParseTree;
pub mod types;
pub(crate) mod utils;
pub use crate::parse_tree::{Declaration, Expression, UseStatement, WhileLoop};
pub use crate::span::Span;
pub use error::{CompileError, CompileResult, CompileWarning};
pub use ident::Ident;
pub use semantic_analysis::{Namespace, TypedDeclaration, TypedFunctionDeclaration};
pub use type_engine::TypeInfo;
#[derive(Debug)]
pub struct HllParseTree {
pub tree_type: TreeType,
pub tree: ParseTree,
}
#[derive(Debug)]
pub struct ParseTree {
pub root_nodes: Vec<AstNode>,
pub span: span::Span,
}
#[derive(Debug, Clone)]
pub struct AstNode {
pub content: AstNodeContent,
pub span: span::Span,
}
#[derive(Debug, Clone)]
pub enum AstNodeContent {
UseStatement(UseStatement),
ReturnStatement(ReturnStatement),
Declaration(Declaration),
Expression(Expression),
ImplicitReturnExpression(Expression),
WhileLoop(WhileLoop),
IncludeStatement(IncludeStatement),
}
impl ParseTree {
pub(crate) fn new(span: span::Span) -> Self {
ParseTree {
root_nodes: Vec::new(),
span,
}
}
pub(crate) fn push(&mut self, new_node: AstNode) {
self.root_nodes.push(new_node);
}
}
pub fn parse(input: Arc<str>, config: Option<&BuildConfig>) -> CompileResult<HllParseTree> {
let mut warnings: Vec<CompileWarning> = Vec::new();
let mut errors: Vec<CompileError> = Vec::new();
let mut parsed = match HllParser::parse(Rule::program, input.clone()) {
Ok(o) => o,
Err(e) => {
return err(
Vec::new(),
vec![CompileError::ParseFailure {
span: span::Span {
span: pest::Span::new(input, get_start(&e), get_end(&e)).unwrap(),
path: config.map(|config| config.path()),
},
err: e,
}],
)
}
};
let parsed_root = check!(
parse_root_from_pairs(parsed.next().unwrap().into_inner(), config),
return err(warnings, errors),
warnings,
errors
);
ok(parsed_root, warnings, errors)
}
pub enum CompilationResult {
Success {
asm: FinalizedAsm,
warnings: Vec<CompileWarning>,
},
Library {
name: Ident,
namespace: Box<Namespace>,
warnings: Vec<CompileWarning>,
},
Failure {
warnings: Vec<CompileWarning>,
errors: Vec<CompileError>,
},
}
pub enum CompileAstResult {
Success {
parse_tree: Box<TypedParseTree>,
tree_type: TreeType,
warnings: Vec<CompileWarning>,
},
Failure {
warnings: Vec<CompileWarning>,
errors: Vec<CompileError>,
},
}
pub enum BytecodeCompilationResult {
Success {
bytes: Vec<u8>,
warnings: Vec<CompileWarning>,
},
Library {
warnings: Vec<CompileWarning>,
},
Failure {
warnings: Vec<CompileWarning>,
errors: Vec<CompileError>,
},
}
pub fn extract_keyword(line: &str, rule: Rule) -> Option<String> {
if let Ok(pair) = HllParser::parse(rule, Arc::from(line)) {
Some(pair.as_str().trim().to_string())
} else {
None
}
}
fn get_start(err: &pest::error::Error<Rule>) -> usize {
match err.location {
pest::error::InputLocation::Pos(num) => num,
pest::error::InputLocation::Span((start, _)) => start,
}
}
fn get_end(err: &pest::error::Error<Rule>) -> usize {
match err.location {
pest::error::InputLocation::Pos(num) => num,
pest::error::InputLocation::Span((_, end)) => end,
}
}
pub(crate) struct InnerDependencyCompileResult {
name: Ident,
namespace: Namespace,
}
pub(crate) fn compile_inner_dependency(
input: Arc<str>,
initial_namespace: &Namespace,
build_config: BuildConfig,
dead_code_graph: &mut ControlFlowGraph,
dependency_graph: &mut HashMap<String, HashSet<String>>,
) -> CompileResult<InnerDependencyCompileResult> {
let mut warnings = Vec::new();
let mut errors = Vec::new();
let parse_tree = check!(
parse(input.clone(), Some(&build_config)),
return err(warnings, errors),
warnings,
errors
);
let library_name = match &parse_tree.tree_type {
TreeType::Library { name } => name,
TreeType::Contract | TreeType::Script | TreeType::Predicate => {
errors.push(CompileError::ImportMustBeLibrary {
span: span::Span {
span: pest::Span::new(input, 0, 0).unwrap(),
path: Some(build_config.path()),
},
});
return err(warnings, errors);
}
};
let typed_parse_tree = check!(
TypedParseTree::type_check(
parse_tree.tree,
initial_namespace.clone(),
&parse_tree.tree_type,
&build_config,
dead_code_graph,
dependency_graph,
),
return err(warnings, errors),
warnings,
errors
);
let graph = ControlFlowGraph::construct_return_path_graph(&typed_parse_tree);
errors.append(&mut graph.analyze_return_paths());
if let Err(e) = ControlFlowGraph::append_to_dead_code_graph(
&typed_parse_tree,
&parse_tree.tree_type,
dead_code_graph,
) {
errors.push(e)
};
ok(
InnerDependencyCompileResult {
name: library_name.clone(),
namespace: typed_parse_tree.into_namespace(),
},
warnings,
errors,
)
}
pub fn compile_to_ast(
input: Arc<str>,
initial_namespace: &Namespace,
build_config: &BuildConfig,
dependency_graph: &mut HashMap<String, HashSet<String>>,
) -> CompileAstResult {
let mut warnings = Vec::new();
let mut errors = Vec::new();
let parse_tree = check!(
parse(input, Some(build_config)),
return CompileAstResult::Failure { errors, warnings },
warnings,
errors
);
let mut dead_code_graph = ControlFlowGraph {
graph: Graph::new(),
entry_points: vec![],
namespace: Default::default(),
};
let typed_parse_tree = check!(
TypedParseTree::type_check(
parse_tree.tree,
initial_namespace.clone(),
&parse_tree.tree_type,
&build_config.clone(),
&mut dead_code_graph,
dependency_graph,
),
return CompileAstResult::Failure { errors, warnings },
warnings,
errors
);
let (mut l_warnings, mut l_errors) = perform_control_flow_analysis(
&typed_parse_tree,
&parse_tree.tree_type,
&mut dead_code_graph,
);
errors.append(&mut l_errors);
warnings.append(&mut l_warnings);
errors = dedup_unsorted(errors);
warnings = dedup_unsorted(warnings);
if !errors.is_empty() {
return CompileAstResult::Failure { errors, warnings };
}
CompileAstResult::Success {
parse_tree: Box::new(typed_parse_tree),
tree_type: parse_tree.tree_type,
warnings,
}
}
pub fn compile_to_asm(
input: Arc<str>,
initial_namespace: &Namespace,
build_config: BuildConfig,
dependency_graph: &mut HashMap<String, HashSet<String>>,
) -> CompilationResult {
match compile_to_ast(input, initial_namespace, &build_config, dependency_graph) {
CompileAstResult::Failure { warnings, errors } => {
CompilationResult::Failure { warnings, errors }
}
CompileAstResult::Success {
parse_tree,
tree_type,
mut warnings,
} => {
let mut errors = vec![];
match tree_type {
TreeType::Contract | TreeType::Script | TreeType::Predicate => {
let asm = check!(
compile_ast_to_asm(*parse_tree, &build_config),
return CompilationResult::Failure { errors, warnings },
warnings,
errors
);
if !errors.is_empty() {
return CompilationResult::Failure { errors, warnings };
}
CompilationResult::Success { asm, warnings }
}
TreeType::Library { name } => CompilationResult::Library {
warnings,
name,
namespace: Box::new(parse_tree.into_namespace()),
},
}
}
}
}
pub fn compile_to_bytecode(
input: Arc<str>,
initial_namespace: &Namespace,
build_config: BuildConfig,
dependency_graph: &mut HashMap<String, HashSet<String>>,
) -> BytecodeCompilationResult {
match compile_to_asm(input, initial_namespace, build_config, dependency_graph) {
CompilationResult::Success {
mut asm,
mut warnings,
} => {
let mut asm_res = asm.to_bytecode_mut();
warnings.append(&mut asm_res.warnings);
if asm_res.value.is_none() || !asm_res.errors.is_empty() {
BytecodeCompilationResult::Failure {
warnings,
errors: asm_res.errors,
}
} else {
BytecodeCompilationResult::Success {
bytes: asm_res.value.unwrap(),
warnings,
}
}
}
CompilationResult::Failure { warnings, errors } => {
BytecodeCompilationResult::Failure { warnings, errors }
}
CompilationResult::Library { warnings, .. } => {
BytecodeCompilationResult::Library { warnings }
}
}
}
fn perform_control_flow_analysis(
tree: &TypedParseTree,
tree_type: &TreeType,
dead_code_graph: &mut ControlFlowGraph,
) -> (Vec<CompileWarning>, Vec<CompileError>) {
match ControlFlowGraph::append_to_dead_code_graph(tree, tree_type, dead_code_graph) {
Ok(_) => (),
Err(e) => return (vec![], vec![e]),
}
let mut warnings = vec![];
let mut errors = vec![];
warnings.append(&mut dead_code_graph.find_dead_code());
let graph = ControlFlowGraph::construct_return_path_graph(tree);
errors.append(&mut graph.analyze_return_paths());
(warnings, errors)
}
fn parse_root_from_pairs(
input: impl Iterator<Item = Pair<Rule>>,
config: Option<&BuildConfig>,
) -> CompileResult<HllParseTree> {
let path = config.map(|config| config.dir_of_code.clone());
let mut warnings = Vec::new();
let mut errors = Vec::new();
let mut fuel_ast_opt = None;
for block in input {
let mut parse_tree = ParseTree::new(span::Span {
span: block.as_span(),
path: path.clone(),
});
let rule = block.as_rule();
let input = block.clone().into_inner();
let mut library_name = None;
for pair in input {
match pair.as_rule() {
Rule::non_var_decl => {
let decl = check!(
Declaration::parse_non_var_from_pair(pair.clone(), config),
continue,
warnings,
errors
);
parse_tree.push(AstNode {
content: AstNodeContent::Declaration(decl),
span: span::Span {
span: pair.as_span(),
path: path.clone(),
},
});
}
Rule::use_statement => {
let stmt = check!(
UseStatement::parse_from_pair(pair.clone(), config),
continue,
warnings,
errors
);
for entry in stmt {
parse_tree.push(AstNode {
content: AstNodeContent::UseStatement(entry.clone()),
span: span::Span {
span: pair.as_span(),
path: path.clone(),
},
});
}
}
Rule::library_name => {
let lib_pair = pair.into_inner().next().unwrap();
library_name = Some(check!(
Ident::parse_from_pair(lib_pair, config),
continue,
warnings,
errors
));
}
Rule::include_statement => {
let include_statement = check!(
IncludeStatement::parse_from_pair(pair.clone(), config),
continue,
warnings,
errors
);
parse_tree.push(AstNode {
content: AstNodeContent::IncludeStatement(include_statement),
span: span::Span {
span: pair.as_span(),
path: path.clone(),
},
});
}
_ => unreachable!("{:?}", pair.as_str()),
}
}
match rule {
Rule::contract => {
fuel_ast_opt = Some(HllParseTree {
tree_type: TreeType::Contract,
tree: parse_tree,
});
}
Rule::script => {
fuel_ast_opt = Some(HllParseTree {
tree_type: TreeType::Script,
tree: parse_tree,
});
}
Rule::predicate => {
fuel_ast_opt = Some(HllParseTree {
tree_type: TreeType::Predicate,
tree: parse_tree,
});
}
Rule::library => {
fuel_ast_opt = Some(HllParseTree {
tree_type: TreeType::Library {
name: library_name.expect(
"Safe unwrap, because the sway-core enforces the library keyword is \
followed by a name. This is an invariant",
),
},
tree: parse_tree,
});
}
Rule::EOI => (),
a => errors.push(CompileError::InvalidTopLevelItem(
a,
span::Span {
span: block.as_span(),
path: path.clone(),
},
)),
}
}
let fuel_ast = fuel_ast_opt.unwrap();
ok(fuel_ast, warnings, errors)
}
#[test]
fn test_basic_prog() {
let prog = parse(
r#"
contract;
enum yo
<T>
where
T: IsAThing
{
x: u32,
y: MyStruct<u32>
}
enum MyOtherSumType
{
x: u32,
y: MyStruct<u32>
}
struct MyStruct<T> {
field_name: u64,
other_field: T,
}
fn generic_function
<T>
(arg1: u64,
arg2: T)
->
T
where T: Display,
T: Debug {
let x: MyStruct =
MyStruct
{
field_name:
5
};
return
match
arg1
{
1
=> true,
_ => { return false; },
};
}
struct MyStruct {
test: string,
}
use stdlib::println;
trait MyTrait {
// interface points
fn myfunc(x: int) -> unit;
} {
// methods
fn calls_interface_fn(x: int) -> unit {
// declare a byte
let x = 0b10101111;
let mut y = 0b11111111;
self.interface_fn(x);
}
}
pub fn prints_number_five() -> u8 {
let x: u8 = 5;
let reference_to_x = ref x;
let second_value_of_x = deref x; // u8 is `Copy` so this clones
println(x);
x.to_string();
let some_list = [
5,
10 + 3 / 2,
func_app(my_args, (so_many_args))];
return 5;
}
"#
.into(),
None,
);
let mut warnings: Vec<CompileWarning> = Vec::new();
let mut errors: Vec<CompileError> = Vec::new();
prog.unwrap(&mut warnings, &mut errors);
}
#[test]
fn test_parenthesized() {
let prog = parse(
r#"
contract;
pub fn some_abi_func() -> unit {
let x = (5 + 6 / (1 + (2 / 1) + 4));
return;
}
"#
.into(),
None,
);
let mut warnings: Vec<CompileWarning> = Vec::new();
let mut errors: Vec<CompileError> = Vec::new();
prog.unwrap(&mut warnings, &mut errors);
}
#[test]
fn test_unary_ordering() {
use crate::parse_tree::declaration::FunctionDeclaration;
let prog = parse(
r#"
script;
fn main() -> bool {
let a = true;
let b = true;
!a && b;
}"#
.into(),
None,
);
let mut warnings: Vec<CompileWarning> = Vec::new();
let mut errors: Vec<CompileError> = Vec::new();
let prog = prog.unwrap(&mut warnings, &mut errors);
if let AstNode {
content:
AstNodeContent::Declaration(Declaration::FunctionDeclaration(FunctionDeclaration {
body,
..
})),
..
} = &prog.tree.root_nodes[0]
{
if let AstNode {
content: AstNodeContent::Expression(Expression::LazyOperator { op, .. }),
..
} = &body.contents[2]
{
assert_eq!(op, &LazyOp::And)
} else {
panic!("Was not lazy operator.")
}
} else {
panic!("Was not ast node")
};
}
fn dedup_unsorted<T: PartialEq + std::hash::Hash>(mut data: Vec<T>) -> Vec<T> {
use smallvec::SmallVec;
use std::collections::hash_map::{DefaultHasher, Entry};
use std::hash::Hasher;
let mut write_index = 0;
let mut indexes: HashMap<u64, SmallVec<[usize; 1]>> = HashMap::with_capacity(data.len());
for read_index in 0..data.len() {
let hash = {
let mut hasher = DefaultHasher::new();
data[read_index].hash(&mut hasher);
hasher.finish()
};
let index_vec = match indexes.entry(hash) {
Entry::Occupied(oe) => {
if oe
.get()
.iter()
.any(|index| data[*index] == data[read_index])
{
continue;
}
oe.into_mut()
}
Entry::Vacant(ve) => ve.insert(SmallVec::new()),
};
data.swap(write_index, read_index);
index_vec.push(write_index);
write_index += 1;
}
data.truncate(write_index);
data
}