Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
446 changes: 285 additions & 161 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ arbitrary = { version = "1", optional = true, features = ["derive"] }
clap = "4.5.37"
chumsky = "0.11.2"

[dev-dependencies]
tempfile = "3"

[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["js"] }

Expand Down Expand Up @@ -68,7 +71,7 @@ copy_iterator = "warn"
default_trait_access = "warn"
doc_link_with_quotes = "warn"
doc_markdown = "warn"
empty_enum = "warn"
empty_enums = "warn"
enum_glob_use = "allow"
expl_impl_clone_on_copy = "warn"
explicit_deref_methods = "warn"
Expand Down Expand Up @@ -152,7 +155,7 @@ struct_field_names = "warn"
too_many_lines = "allow"
transmute_ptr_to_ptr = "warn"
trivially_copy_pass_by_ref = "warn"
unchecked_duration_subtraction = "warn"
unchecked_time_subtraction = "warn"
unicode_not_nfc = "warn"
unnecessary_box_returns = "warn"
unnecessary_join = "warn"
Expand Down
12 changes: 12 additions & 0 deletions examples/multiple_libs/main.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use merkle::build_root::get_root;
use math::simple_op::hash;

pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 {
let root: u32 = get_root(tx1, tx2);
hash(prev_hash, root);
}

fn main() {
let block_val_hash: u32 = get_block_value(5, 10, 20);
assert!(jet::eq_32(block_val_hash, 27));
}
3 changes: 3 additions & 0 deletions examples/multiple_libs/math/simple_op.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn hash(x: u32, y: u32) -> u32 {
jet::xor_32(x, y)
}
5 changes: 5 additions & 0 deletions examples/multiple_libs/merkle/build_root.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use math::simple_op::hash;

pub fn get_root(tx1: u32, tx2: u32) -> u32 {
hash(tx1, tx2)
}
5 changes: 5 additions & 0 deletions examples/simple_multilib/crypto/hashes.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub fn sha256(data: u32) -> u256 {
let ctx: Ctx8 = jet::sha_256_ctx_8_init();
let ctx: Ctx8 = jet::sha_256_ctx_8_add_4(ctx, data);
jet::sha_256_ctx_8_finalize(ctx)
}
7 changes: 7 additions & 0 deletions examples/simple_multilib/main.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use math::arithmetic::add;
use crypto::hashes::sha256;

fn main() {
let sum: u32 = add(2, 3);
let hash: u256 = sha256(sum);
}
4 changes: 4 additions & 0 deletions examples/simple_multilib/math/arithmetic.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub fn add(a: u32, b: u32) -> u32 {
let (_, res): (bool, u32) = jet::add_32(a, b);
res
}
11 changes: 11 additions & 0 deletions examples/single_lib/main.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub use temp::two::two;
use temp::funcs::{get_five, Smth};

fn seven() -> u32 {
7
}

fn main() {
let (_, temp): (bool, u32) = jet::add_32(two(), get_five());
assert!(jet::eq_32(temp, seven()));
}
5 changes: 5 additions & 0 deletions examples/single_lib/temp/funcs.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub type Smth = u32;

pub fn get_five() -> u32 {
5
}
5 changes: 5 additions & 0 deletions examples/single_lib/temp/two.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub use temp::funcs::Smth;

pub fn two() -> Smth {
2
}
130 changes: 105 additions & 25 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ use miniscript::iter::{Tree, TreeLike};
use simplicity::jet::Elements;

use crate::debug::{CallTracker, DebugSymbols, TrackedCallName};
use crate::driver::FileResolutions;
use crate::error::{Error, RichError, Span, WithSpan};
use crate::num::{NonZeroPow2Usize, Pow2Usize};
use crate::parse::MatchPattern;
use crate::pattern::Pattern;
use crate::resolution::SourceName;
use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName};
use crate::types::{
AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, UIntType,
};
use crate::value::{UIntValue, Value};
use crate::witness::{Parameters, WitnessTypes, WitnessValues};
use crate::{impl_eq_hash, parse};
use crate::{driver, impl_eq_hash, parse};

/// A program consists of the main function.
///
Expand Down Expand Up @@ -520,8 +522,12 @@ impl TreeLike for ExprTree<'_> {
/// 2. Resolving type aliases
/// 3. Assigning types to each witness expression
/// 4. Resolving calls to custom functions
#[derive(Clone, Debug, Eq, PartialEq, Default)]
#[derive(Clone, Debug, Eq, PartialEq)]
struct Scope {
resolutions: Arc<[FileResolutions]>,
paths: Arc<[SourceName]>,
file_id: usize, // ID of the file from which the function is called.

variables: Vec<HashMap<Identifier, ResolvedType>>,
aliases: HashMap<AliasName, ResolvedType>,
parameters: HashMap<WitnessName, ResolvedType>,
Expand All @@ -531,7 +537,44 @@ struct Scope {
call_tracker: CallTracker,
}

impl Default for Scope {
fn default() -> Self {
Self {
resolutions: Arc::from([]),
paths: Arc::from([]),
file_id: 0,
variables: Vec::new(),
aliases: HashMap::new(),
parameters: HashMap::new(),
witnesses: HashMap::new(),
functions: HashMap::new(),
is_main: false,
call_tracker: CallTracker::default(),
}
}
}

impl Scope {
pub fn new(resolutions: Arc<[FileResolutions]>, paths: Arc<[SourceName]>) -> Self {
Self {
resolutions,
paths,
file_id: 0,
variables: Vec::new(),
aliases: HashMap::new(),
parameters: HashMap::new(),
witnesses: HashMap::new(),
functions: HashMap::new(),
is_main: false,
call_tracker: CallTracker::default(),
}
}

/// Access to current function file id.
pub fn file_id(&self) -> usize {
self.file_id
}

/// Check if the current scope is topmost.
pub fn is_topmost(&self) -> bool {
self.variables.is_empty()
Expand All @@ -542,6 +585,11 @@ impl Scope {
self.variables.push(HashMap::new());
}

pub fn push_function_scope(&mut self, file_id: usize) {
self.push_scope();
self.file_id = file_id;
}

/// Push the scope of the main function onto the stack.
///
/// ## Panics
Expand All @@ -564,6 +612,11 @@ impl Scope {
self.variables.pop().expect("Stack is empty");
}

pub fn pop_function_scope(&mut self, previous_file_id: usize) {
self.pop_scope();
self.file_id = previous_file_id;
}

/// Pop the scope of the main function from the stack.
///
/// ## Panics
Expand Down Expand Up @@ -693,9 +746,39 @@ impl Scope {
}
}

/// Get the definition of a custom function.
pub fn get_function(&self, name: &FunctionName) -> Option<&CustomFunction> {
self.functions.get(name)
/// Get the definition of a custom function with visibility and existence checks.
///
/// # Errors
///
/// - `Error::FileNotFound`: The specified `file_id` does not exist in the resolutions.
/// - `Error::FunctionUndefined`: The function is not found in the file's scope OR not defined globally.
/// - `Error::FunctionIsPrivate`: The function exists but is private (and thus not accessible).
pub fn get_function(&self, name: &FunctionName) -> Result<&CustomFunction, Error> {
// The order of the errors is important!
let function = self
.functions
.get(name)
.ok_or_else(|| Error::FunctionUndefined(name.clone()))?;

let source_name = self.paths[self.file_id].clone();

let file_scope = match source_name {
SourceName::Real(path) => self
.resolutions
.get(self.file_id)
.ok_or(Error::FileNotFound(path))?,
SourceName::Virtual(_) => {
return Ok(function);
}
};

let identifier: Identifier = name.clone().into();

if file_scope.contains_key(&identifier) {
Ok(function)
} else {
Err(Error::FunctionIsPrivate(name.clone()))
}
}

/// Track a call expression with its span.
Expand All @@ -718,9 +801,10 @@ trait AbstractSyntaxTree: Sized {
}

impl Program {
pub fn analyze(from: &parse::Program) -> Result<Self, RichError> {
// TODO: Add visibility check inside program
pub fn analyze(from: &driver::Program) -> Result<Self, RichError> {
let unit = ResolvedType::unit();
let mut scope = Scope::default();
let mut scope = Scope::new(Arc::from(from.resolutions()), Arc::from(from.paths()));
let items = from
.items()
.iter()
Expand Down Expand Up @@ -762,6 +846,10 @@ impl AbstractSyntaxTree for Item {
parse::Item::Function(function) => {
Function::analyze(function, ty, scope).map(Self::Function)
}
parse::Item::Use(use_decl) => Err(RichError::new(
Error::UnknownLibrary(use_decl.path_buf().to_string_lossy().to_string()),
*use_decl.span(),
)),
parse::Item::Module => Ok(Self::Module),
}
}
Expand All @@ -773,8 +861,10 @@ impl AbstractSyntaxTree for Function {
fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
assert!(ty.is_unit(), "Function definitions cannot return anything");
assert!(scope.is_topmost(), "Items live in the topmost scope only");
let previous_file_id = scope.file_id();

if from.name().as_inner() != "main" {
let file_id = from.file_id();
let params = from
.params()
.iter()
Expand All @@ -791,12 +881,12 @@ impl AbstractSyntaxTree for Function {
.map(|aliased| scope.resolve(aliased).with_span(from))
.transpose()?
.unwrap_or_else(ResolvedType::unit);
scope.push_scope();
scope.push_function_scope(file_id);
for param in params.iter() {
scope.insert_variable(param.identifier().clone(), param.ty().clone());
}
let body = Expression::analyze(from.body(), &ret, scope).map(Arc::new)?;
scope.pop_scope();
scope.pop_function_scope(previous_file_id);
debug_assert!(scope.is_topmost());
let function = CustomFunction { params, body };
scope
Expand Down Expand Up @@ -1321,14 +1411,9 @@ impl AbstractSyntaxTree for CallName {
.get_function(name)
.cloned()
.map(Self::Custom)
.ok_or(Error::FunctionUndefined(name.clone()))
.with_span(from),
parse::CallName::ArrayFold(name, size) => {
let function = scope
.get_function(name)
.cloned()
.ok_or(Error::FunctionUndefined(name.clone()))
.with_span(from)?;
let function = scope.get_function(name).cloned().with_span(from)?;
// A function that is used in a array fold has the signature:
// fn f(element: E, accumulator: A) -> A
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
Expand All @@ -1339,11 +1424,7 @@ impl AbstractSyntaxTree for CallName {
}
}
parse::CallName::Fold(name, bound) => {
let function = scope
.get_function(name)
.cloned()
.ok_or(Error::FunctionUndefined(name.clone()))
.with_span(from)?;
let function = scope.get_function(name).cloned().with_span(from)?;
// A function that is used in a list fold has the signature:
// fn f(element: E, accumulator: A) -> A
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
Expand All @@ -1354,11 +1435,7 @@ impl AbstractSyntaxTree for CallName {
}
}
parse::CallName::ForWhile(name) => {
let function = scope
.get_function(name)
.cloned()
.ok_or(Error::FunctionUndefined(name.clone()))
.with_span(from)?;
let function = scope.get_function(name).cloned().with_span(from)?;
// A function that is used in a for-while loop has the signature:
// fn f(accumulator: A, readonly_context: C, counter: u{N}) -> Either<B, A>
// where
Expand Down Expand Up @@ -1434,6 +1511,9 @@ fn analyze_named_module(
from: &parse::ModuleProgram,
) -> Result<HashMap<WitnessName, Value>, RichError> {
let unit = ResolvedType::unit();

// IMPORTANT! If modules allow imports, then we need to consider
// passing the resolution conetxt by calling `Scope::new(resolutions)`
let mut scope = Scope::default();
let items = from
.items()
Expand Down
Loading
Loading