Skip to content

Commit 640a195

Browse files
committed
feat: update AST architecture and test module flow
- Refactor AST to include tests and modules - Add simple tests for module flow
1 parent 58fd255 commit 640a195

17 files changed

Lines changed: 1103 additions & 494 deletions

File tree

Cargo.lock

Lines changed: 181 additions & 345 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/multiple_libs/main.simf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use merkle::build_root::get_root;
2+
use math::simple_op::hash;
3+
4+
pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 {
5+
let root: u32 = get_root(tx1, tx2);
6+
hash(prev_hash, root);
7+
}
8+
9+
fn main() {
10+
let block_val_hash: u32 = get_block_value(5, 10, 20);
11+
assert!(jet::eq_32(block_val_hash, 27));
12+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn hash(x: u32, y: u32) -> u32 {
2+
jet::xor_32(x, y)
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use math::simple_op::hash;
2+
3+
pub fn get_root(tx1: u32, tx2: u32) -> u32 {
4+
hash(tx1, tx2)
5+
}

examples/single_lib/main.simf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pub use temp::two::two;
2+
use temp::funcs::{get_five, Smth};
3+
4+
fn seven() -> u32 {
5+
7
6+
}
7+
8+
fn main() {
9+
let (_, temp): (bool, u32) = jet::add_32(two(), get_five());
10+
assert!(jet::eq_32(temp, seven()));
11+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub type Smth = u32;
2+
3+
pub fn get_five() -> u32 {
4+
5
5+
}

examples/single_lib/temp/two.simf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pub use temp::funcs::Smth;
2+
3+
pub fn two() -> Smth {
4+
2
5+
}

src/ast.rs

Lines changed: 105 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use miniscript::iter::{Tree, TreeLike};
99
use simplicity::jet::Elements;
1010

1111
use crate::debug::{CallTracker, DebugSymbols, TrackedCallName};
12+
use crate::driver::ProgramResolutions;
1213
use crate::error::{Error, RichError, Span, WithSpan};
1314
use crate::num::{NonZeroPow2Usize, Pow2Usize};
1415
use crate::parse::MatchPattern;
@@ -19,7 +20,7 @@ use crate::types::{
1920
};
2021
use crate::value::{UIntValue, Value};
2122
use crate::witness::{Parameters, WitnessTypes, WitnessValues};
22-
use crate::{impl_eq_hash, parse};
23+
use crate::{driver, impl_eq_hash, parse, SourceName};
2324

2425
/// A program consists of the main function.
2526
///
@@ -520,8 +521,12 @@ impl TreeLike for ExprTree<'_> {
520521
/// 2. Resolving type aliases
521522
/// 3. Assigning types to each witness expression
522523
/// 4. Resolving calls to custom functions
523-
#[derive(Clone, Debug, Eq, PartialEq, Default)]
524+
#[derive(Clone, Debug, Eq, PartialEq)]
524525
struct Scope {
526+
resolutions: ProgramResolutions,
527+
paths: Arc<[SourceName]>,
528+
file_id: usize, // ID of the file from which the function is called.
529+
525530
variables: Vec<HashMap<Identifier, ResolvedType>>,
526531
aliases: HashMap<AliasName, ResolvedType>,
527532
parameters: HashMap<WitnessName, ResolvedType>,
@@ -531,7 +536,44 @@ struct Scope {
531536
call_tracker: CallTracker,
532537
}
533538

539+
impl Default for Scope {
540+
fn default() -> Self {
541+
Self {
542+
resolutions: Arc::from([]),
543+
paths: Arc::from([]),
544+
file_id: 0,
545+
variables: Vec::new(),
546+
aliases: HashMap::new(),
547+
parameters: HashMap::new(),
548+
witnesses: HashMap::new(),
549+
functions: HashMap::new(),
550+
is_main: false,
551+
call_tracker: CallTracker::default(),
552+
}
553+
}
554+
}
555+
534556
impl Scope {
557+
pub fn new(resolutions: ProgramResolutions, paths: Arc<[SourceName]>) -> Self {
558+
Self {
559+
resolutions,
560+
paths,
561+
file_id: 0,
562+
variables: Vec::new(),
563+
aliases: HashMap::new(),
564+
parameters: HashMap::new(),
565+
witnesses: HashMap::new(),
566+
functions: HashMap::new(),
567+
is_main: false,
568+
call_tracker: CallTracker::default(),
569+
}
570+
}
571+
572+
/// Access to current function file id.
573+
pub fn file_id(&self) -> usize {
574+
self.file_id
575+
}
576+
535577
/// Check if the current scope is topmost.
536578
pub fn is_topmost(&self) -> bool {
537579
self.variables.is_empty()
@@ -542,6 +584,11 @@ impl Scope {
542584
self.variables.push(HashMap::new());
543585
}
544586

587+
pub fn push_function_scope(&mut self, file_id: usize) {
588+
self.push_scope();
589+
self.file_id = file_id;
590+
}
591+
545592
/// Push the scope of the main function onto the stack.
546593
///
547594
/// ## Panics
@@ -564,6 +611,11 @@ impl Scope {
564611
self.variables.pop().expect("Stack is empty");
565612
}
566613

614+
pub fn pop_function_scope(&mut self, previous_file_id: usize) {
615+
self.pop_scope();
616+
self.file_id = previous_file_id;
617+
}
618+
567619
/// Pop the scope of the main function from the stack.
568620
///
569621
/// ## Panics
@@ -693,9 +745,39 @@ impl Scope {
693745
}
694746
}
695747

696-
/// Get the definition of a custom function.
697-
pub fn get_function(&self, name: &FunctionName) -> Option<&CustomFunction> {
698-
self.functions.get(name)
748+
/// Get the definition of a custom function with visibility and existence checks.
749+
///
750+
/// # Errors
751+
///
752+
/// - `Error::FileNotFound`: The specified `file_id` does not exist in the resolutions.
753+
/// - `Error::FunctionUndefined`: The function is not found in the file's scope OR not defined globally.
754+
/// - `Error::FunctionIsPrivate`: The function exists but is private (and thus not accessible).
755+
pub fn get_function(&self, name: &FunctionName) -> Result<&CustomFunction, Error> {
756+
// The order of the errors is important!
757+
let function = self
758+
.functions
759+
.get(name)
760+
.ok_or_else(|| Error::FunctionUndefined(name.clone()))?;
761+
762+
let source_name = self.paths[self.file_id].clone();
763+
764+
let file_scope = match source_name {
765+
SourceName::Real(path) => self
766+
.resolutions
767+
.get(self.file_id)
768+
.ok_or(Error::FileNotFound(path))?,
769+
SourceName::Virtual(_) => {
770+
return Ok(function);
771+
}
772+
};
773+
774+
let identifier: Identifier = name.clone().into();
775+
776+
if file_scope.contains_key(&identifier) {
777+
Ok(function)
778+
} else {
779+
Err(Error::FunctionIsPrivate(name.clone()))
780+
}
699781
}
700782

701783
/// Track a call expression with its span.
@@ -718,9 +800,10 @@ trait AbstractSyntaxTree: Sized {
718800
}
719801

720802
impl Program {
721-
pub fn analyze(from: &parse::Program) -> Result<Self, RichError> {
803+
// TODO: Add visibility check inside program
804+
pub fn analyze(from: &driver::Program) -> Result<Self, RichError> {
722805
let unit = ResolvedType::unit();
723-
let mut scope = Scope::default();
806+
let mut scope = Scope::new(Arc::from(from.resolutions()), Arc::from(from.paths()));
724807
let items = from
725808
.items()
726809
.iter()
@@ -746,36 +829,37 @@ impl Program {
746829
}
747830

748831
impl AbstractSyntaxTree for Item {
749-
type From = parse::Item;
832+
type From = driver::Item;
750833

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

755838
match from {
756-
parse::Item::TypeAlias(alias) => {
839+
driver::Item::TypeAlias(alias) => {
757840
scope
758841
.insert_alias(alias.name().clone(), alias.ty().clone())
759842
.with_span(alias)?;
760843
Ok(Self::TypeAlias)
761844
}
762-
parse::Item::Function(function) => {
845+
driver::Item::Function(function) => {
763846
Function::analyze(function, ty, scope).map(Self::Function)
764847
}
765-
parse::Item::Use(_) => todo!(),
766-
parse::Item::Module => Ok(Self::Module),
848+
driver::Item::Module => Ok(Self::Module),
767849
}
768850
}
769851
}
770852

771853
impl AbstractSyntaxTree for Function {
772-
type From = parse::Function;
854+
type From = driver::Function;
773855

774856
fn analyze(from: &Self::From, ty: &ResolvedType, scope: &mut Scope) -> Result<Self, RichError> {
775857
assert!(ty.is_unit(), "Function definitions cannot return anything");
776858
assert!(scope.is_topmost(), "Items live in the topmost scope only");
859+
let previous_file_id = scope.file_id();
777860

778861
if from.name().as_inner() != "main" {
862+
let file_id = from.file_id();
779863
let params = from
780864
.params()
781865
.iter()
@@ -792,12 +876,12 @@ impl AbstractSyntaxTree for Function {
792876
.map(|aliased| scope.resolve(aliased).with_span(from))
793877
.transpose()?
794878
.unwrap_or_else(ResolvedType::unit);
795-
scope.push_scope();
879+
scope.push_function_scope(file_id);
796880
for param in params.iter() {
797881
scope.insert_variable(param.identifier().clone(), param.ty().clone());
798882
}
799883
let body = Expression::analyze(from.body(), &ret, scope).map(Arc::new)?;
800-
scope.pop_scope();
884+
scope.pop_function_scope(previous_file_id);
801885
debug_assert!(scope.is_topmost());
802886
let function = CustomFunction { params, body };
803887
scope
@@ -1322,14 +1406,9 @@ impl AbstractSyntaxTree for CallName {
13221406
.get_function(name)
13231407
.cloned()
13241408
.map(Self::Custom)
1325-
.ok_or(Error::FunctionUndefined(name.clone()))
13261409
.with_span(from),
13271410
parse::CallName::ArrayFold(name, size) => {
1328-
let function = scope
1329-
.get_function(name)
1330-
.cloned()
1331-
.ok_or(Error::FunctionUndefined(name.clone()))
1332-
.with_span(from)?;
1411+
let function = scope.get_function(name).cloned().with_span(from)?;
13331412
// A function that is used in a array fold has the signature:
13341413
// fn f(element: E, accumulator: A) -> A
13351414
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
@@ -1340,11 +1419,7 @@ impl AbstractSyntaxTree for CallName {
13401419
}
13411420
}
13421421
parse::CallName::Fold(name, bound) => {
1343-
let function = scope
1344-
.get_function(name)
1345-
.cloned()
1346-
.ok_or(Error::FunctionUndefined(name.clone()))
1347-
.with_span(from)?;
1422+
let function = scope.get_function(name).cloned().with_span(from)?;
13481423
// A function that is used in a list fold has the signature:
13491424
// fn f(element: E, accumulator: A) -> A
13501425
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
@@ -1355,11 +1430,7 @@ impl AbstractSyntaxTree for CallName {
13551430
}
13561431
}
13571432
parse::CallName::ForWhile(name) => {
1358-
let function = scope
1359-
.get_function(name)
1360-
.cloned()
1361-
.ok_or(Error::FunctionUndefined(name.clone()))
1362-
.with_span(from)?;
1433+
let function = scope.get_function(name).cloned().with_span(from)?;
13631434
// A function that is used in a for-while loop has the signature:
13641435
// fn f(accumulator: A, readonly_context: C, counter: u{N}) -> Either<B, A>
13651436
// where
@@ -1435,6 +1506,9 @@ fn analyze_named_module(
14351506
from: &parse::ModuleProgram,
14361507
) -> Result<HashMap<WitnessName, Value>, RichError> {
14371508
let unit = ResolvedType::unit();
1509+
1510+
// IMPORTANT! If modules allow imports, then we need to consider
1511+
// passing the resolution conetxt by calling `Scope::new(resolutions)`
14381512
let mut scope = Scope::default();
14391513
let items = from
14401514
.items()

0 commit comments

Comments
 (0)