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
322 changes: 305 additions & 17 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion 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 @@ -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/modules.simf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use example::of::my::file;
pub use another::file::*;
pub use some::list::{of, items};

pub fn get_five() -> u32 {
5
}

fn main() {
let five: u32 = dbg!(get_five());
assert!(jet::eq_32(five, 5));
}
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)
}
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
}
116 changes: 87 additions & 29 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use miniscript::iter::{Tree, TreeLike};
use simplicity::jet::Elements;

use crate::debug::{CallTracker, DebugSymbols, TrackedCallName};
use crate::driver::ProgramResolutions;
use crate::error::{Error, RichError, Span, WithSpan};
use crate::num::{NonZeroPow2Usize, Pow2Usize};
use crate::parse::MatchPattern;
Expand All @@ -19,7 +20,7 @@ use crate::types::{
};
use crate::value::{UIntValue, Value};
use crate::witness::{Parameters, WitnessTypes, WitnessValues};
use crate::{impl_eq_hash, parse};
use crate::{driver, impl_eq_hash, parse, SourceName};

/// A program consists of the main function.
///
Expand Down Expand Up @@ -522,6 +523,10 @@ impl TreeLike for ExprTree<'_> {
/// 4. Resolving calls to custom functions
#[derive(Clone, Debug, Eq, PartialEq, Default)]
struct Scope {
resolutions: ProgramResolutions,
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 @@ -532,6 +537,26 @@ struct Scope {
}

impl Scope {
pub fn new(resolutions: ProgramResolutions, 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 +567,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 +594,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 @@ -690,9 +725,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.to_path_buf()))?, // TODO: File or pub type
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 @@ -715,9 +780,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 All @@ -743,35 +809,37 @@ impl Program {
}

impl AbstractSyntaxTree for Item {
type From = parse::Item;
type From = driver::Item;

fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
assert!(ty.is_unit(), "Items cannot return anything");
assert!(scope.is_topmost(), "Items live in the topmost scope only");

match from {
parse::Item::TypeAlias(alias) => {
driver::Item::TypeAlias(alias) => {
scope
.insert_alias(alias.name().clone(), alias.ty().clone())
.with_span(alias)?;
Ok(Self::TypeAlias)
}
parse::Item::Function(function) => {
driver::Item::Function(function) => {
Function::analyze(function, ty, scope).map(Self::Function)
}
parse::Item::Module => Ok(Self::Module),
driver::Item::Module => Ok(Self::Module),
}
}
}

impl AbstractSyntaxTree for Function {
type From = parse::Function;
type From = driver::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 @@ -788,12 +856,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 @@ -1318,14 +1386,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 @@ -1336,11 +1399,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 @@ -1351,11 +1410,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 @@ -1431,6 +1486,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