Skip to content

Commit 45456e9

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 995a900 commit 45456e9

File tree

17 files changed

+1140
-499
lines changed

17 files changed

+1140
-499
lines changed

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: 106 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,19 @@ use miniscript::iter::{Tree, TreeLike};
99
use simplicity::jet::Elements;
1010

1111
use crate::debug::{CallTracker, DebugSymbols, TrackedCallName};
12+
use crate::driver::FileResolutions;
1213
use crate::error::{Error, RichError, Span, WithSpan};
1314
use crate::num::{NonZeroPow2Usize, Pow2Usize};
1415
use crate::parse::MatchPattern;
1516
use crate::pattern::Pattern;
17+
use crate::resolution::SourceName;
1618
use crate::str::{AliasName, FunctionName, Identifier, ModuleName, WitnessName};
1719
use crate::types::{
1820
AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible, UIntType,
1921
};
2022
use crate::value::{UIntValue, Value};
2123
use crate::witness::{Parameters, WitnessTypes, WitnessValues};
22-
use crate::{impl_eq_hash, parse};
24+
use crate::{driver, impl_eq_hash, parse};
2325

2426
/// A program consists of the main function.
2527
///
@@ -520,8 +522,12 @@ impl TreeLike for ExprTree<'_> {
520522
/// 2. Resolving type aliases
521523
/// 3. Assigning types to each witness expression
522524
/// 4. Resolving calls to custom functions
523-
#[derive(Clone, Debug, Eq, PartialEq, Default)]
525+
#[derive(Clone, Debug, Eq, PartialEq)]
524526
struct Scope {
527+
resolutions: Arc<[FileResolutions]>,
528+
paths: Arc<[SourceName]>,
529+
file_id: usize, // ID of the file from which the function is called.
530+
525531
variables: Vec<HashMap<Identifier, ResolvedType>>,
526532
aliases: HashMap<AliasName, ResolvedType>,
527533
parameters: HashMap<WitnessName, ResolvedType>,
@@ -531,7 +537,44 @@ struct Scope {
531537
call_tracker: CallTracker,
532538
}
533539

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

588+
pub fn push_function_scope(&mut self, file_id: usize) {
589+
self.push_scope();
590+
self.file_id = file_id;
591+
}
592+
545593
/// Push the scope of the main function onto the stack.
546594
///
547595
/// ## Panics
@@ -564,6 +612,11 @@ impl Scope {
564612
self.variables.pop().expect("Stack is empty");
565613
}
566614

615+
pub fn pop_function_scope(&mut self, previous_file_id: usize) {
616+
self.pop_scope();
617+
self.file_id = previous_file_id;
618+
}
619+
567620
/// Pop the scope of the main function from the stack.
568621
///
569622
/// ## Panics
@@ -693,9 +746,39 @@ impl Scope {
693746
}
694747
}
695748

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

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

720803
impl Program {
721-
pub fn analyze(from: &parse::Program) -> Result<Self, RichError> {
804+
// TODO: Add visibility check inside program
805+
pub fn analyze(from: &driver::Program) -> Result<Self, RichError> {
722806
let unit = ResolvedType::unit();
723-
let mut scope = Scope::default();
807+
let mut scope = Scope::new(Arc::from(from.resolutions()), Arc::from(from.paths()));
724808
let items = from
725809
.items()
726810
.iter()
@@ -746,36 +830,37 @@ impl Program {
746830
}
747831

748832
impl AbstractSyntaxTree for Item {
749-
type From = parse::Item;
833+
type From = driver::Item;
750834

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

755839
match from {
756-
parse::Item::TypeAlias(alias) => {
840+
driver::Item::TypeAlias(alias) => {
757841
scope
758842
.insert_alias(alias.name().clone(), alias.ty().clone())
759843
.with_span(alias)?;
760844
Ok(Self::TypeAlias)
761845
}
762-
parse::Item::Function(function) => {
846+
driver::Item::Function(function) => {
763847
Function::analyze(function, ty, scope).map(Self::Function)
764848
}
765-
parse::Item::Use(_) => todo!(),
766-
parse::Item::Module => Ok(Self::Module),
849+
driver::Item::Module => Ok(Self::Module),
767850
}
768851
}
769852
}
770853

771854
impl AbstractSyntaxTree for Function {
772-
type From = parse::Function;
855+
type From = driver::Function;
773856

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

778862
if from.name().as_inner() != "main" {
863+
let file_id = from.file_id();
779864
let params = from
780865
.params()
781866
.iter()
@@ -792,12 +877,12 @@ impl AbstractSyntaxTree for Function {
792877
.map(|aliased| scope.resolve(aliased).with_span(from))
793878
.transpose()?
794879
.unwrap_or_else(ResolvedType::unit);
795-
scope.push_scope();
880+
scope.push_function_scope(file_id);
796881
for param in params.iter() {
797882
scope.insert_variable(param.identifier().clone(), param.ty().clone());
798883
}
799884
let body = Expression::analyze(from.body(), &ret, scope).map(Arc::new)?;
800-
scope.pop_scope();
885+
scope.pop_function_scope(previous_file_id);
801886
debug_assert!(scope.is_topmost());
802887
let function = CustomFunction { params, body };
803888
scope
@@ -1322,14 +1407,9 @@ impl AbstractSyntaxTree for CallName {
13221407
.get_function(name)
13231408
.cloned()
13241409
.map(Self::Custom)
1325-
.ok_or(Error::FunctionUndefined(name.clone()))
13261410
.with_span(from),
13271411
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)?;
1412+
let function = scope.get_function(name).cloned().with_span(from)?;
13331413
// A function that is used in a array fold has the signature:
13341414
// fn f(element: E, accumulator: A) -> A
13351415
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
@@ -1340,11 +1420,7 @@ impl AbstractSyntaxTree for CallName {
13401420
}
13411421
}
13421422
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)?;
1423+
let function = scope.get_function(name).cloned().with_span(from)?;
13481424
// A function that is used in a list fold has the signature:
13491425
// fn f(element: E, accumulator: A) -> A
13501426
if function.params().len() != 2 || function.params()[1].ty() != function.body().ty()
@@ -1355,11 +1431,7 @@ impl AbstractSyntaxTree for CallName {
13551431
}
13561432
}
13571433
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)?;
1434+
let function = scope.get_function(name).cloned().with_span(from)?;
13631435
// A function that is used in a for-while loop has the signature:
13641436
// fn f(accumulator: A, readonly_context: C, counter: u{N}) -> Either<B, A>
13651437
// where
@@ -1435,6 +1507,9 @@ fn analyze_named_module(
14351507
from: &parse::ModuleProgram,
14361508
) -> Result<HashMap<WitnessName, Value>, RichError> {
14371509
let unit = ResolvedType::unit();
1510+
1511+
// IMPORTANT! If modules allow imports, then we need to consider
1512+
// passing the resolution conetxt by calling `Scope::new(resolutions)`
14381513
let mut scope = Scope::default();
14391514
let items = from
14401515
.items()

0 commit comments

Comments
 (0)