Skip to content

Commit 6c34920

Browse files
committed
feat: implemented loading one lib from another and tested its functionality
1 parent 4a07c04 commit 6c34920

12 files changed

Lines changed: 534 additions & 336 deletions

File tree

examples/multiple_libs/main.simf

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
use merkle::build_root::get_root;
2-
use math::simple_op::hash;
1+
use merkle::build_root::{get_root, hash as and_hash};
2+
use base_math::simple_op::hash as or_hash;
33

44
pub fn get_block_value_hash(prev_hash: u32, tx1: u32, tx2: u32) -> u32 {
55
let root: u32 = get_root(tx1, tx2);
6-
hash(prev_hash, root);
6+
or_hash(prev_hash, root)
77
}
88

99
fn main() {
10-
let block_val_hash: u32 = get_block_value(5, 10, 20);
10+
let block_val_hash: u32 = get_block_value_hash(5, 10, 20);
1111
assert!(jet::eq_32(block_val_hash, 27));
12+
13+
let first_value: u32 = 15;
14+
let second_value: u32 = 22;
15+
assert!(jet::eq_32(and_hash(first_value, second_value), 6));
1216
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
use math::simple_op::hash;
1+
use math::simple_op::hash as temp_hash;
22

33
pub fn get_root(tx1: u32, tx2: u32) -> u32 {
4-
hash(tx1, tx2)
4+
temp_hash(tx1, tx2)
5+
}
6+
7+
pub fn hash(tx1: u32, tx2: u32) -> u32 {
8+
jet::and_32(tx1, tx2)
59
}

examples/temp/libs/lib/math.simf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn add(a: u32, b: u32) {}

examples/temp/main.simf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
use lib::math::add;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub fn get_some_value() -> u64 {
2+
1
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
use r#jet::r#fn::r#let;
2+
3+
pub fn main() {}

src/driver.rs

Lines changed: 135 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::error::{Error, ErrorCollector, RichError, Span};
77
use crate::parse::{self, AliasedIdentifier, ParseFromStrWithErrors, Visibility};
88
use crate::str::{AliasName, FunctionName, Identifier};
99
use crate::types::AliasedType;
10-
use crate::{get_full_path, impl_eq_hash, LibTable, SourceFile, SourceName};
10+
use crate::{impl_eq_hash, DependencyMap, SourceFile, SourceName};
1111

1212
/// Represents a single, isolated file in the SimplicityHL project.
1313
/// In this architecture, a file and a module are the exact same thing.
@@ -28,8 +28,8 @@ pub struct ProjectGraph {
2828
pub(self) modules: Vec<Module>,
2929

3030
/// The configuration environment.
31-
/// Used to resolve xternal library dependencies and invoke their associated functions.
32-
pub libraries: Arc<LibTable>,
31+
/// Used to resolve external library dependencies and invoke their associated functions.
32+
pub dependency_map: Arc<DependencyMap>,
3333

3434
/// Fast lookup: `SourceName` -> Module ID.
3535
/// A reverse index mapping absolute file paths to their internal IDs.
@@ -399,7 +399,7 @@ impl ProjectGraph {
399399

400400
pub fn new(
401401
root_source: SourceFile,
402-
libraries: Arc<LibTable>,
402+
dependency_map: Arc<DependencyMap>,
403403
root_program: &parse::Program,
404404
handler: &mut ErrorCollector,
405405
) -> Option<Self> {
@@ -434,11 +434,10 @@ impl ProjectGraph {
434434
// PHASE 1: Resolve Imports
435435
for elem in current_program.items() {
436436
if let parse::Item::Use(use_decl) = elem {
437-
match get_full_path(&libraries, use_decl) {
437+
match resolve_single_import(importer_source.clone(), use_decl, &dependency_map)
438+
{
438439
Ok(path) => valid_imports.push((path, *use_decl.span())),
439-
Err(err) => {
440-
resolution_errors.push(err.with_source(importer_source.clone()))
441-
}
440+
Err(err) => resolution_errors.push(err),
442441
}
443442
}
444443
}
@@ -482,7 +481,7 @@ impl ProjectGraph {
482481
} else {
483482
Some(Self {
484483
modules,
485-
libraries,
484+
dependency_map,
486485
lookup,
487486
paths: paths.into(),
488487
dependencies,
@@ -691,13 +690,27 @@ impl ProjectGraph {
691690
let mut errors: Vec<RichError> = Vec::new();
692691
match elem {
693692
parse::Item::Use(use_decl) => {
693+
let full_path = match resolve_single_import(
694+
importer_source.clone(),
695+
use_decl,
696+
&self.dependency_map,
697+
) {
698+
Ok(path) => path,
699+
Err(err) => {
700+
handler.push(err);
701+
continue;
702+
}
703+
};
704+
705+
/*
694706
let full_path = match get_full_path(&self.libraries, use_decl) {
695707
Ok(path) => path,
696708
Err(err) => {
697709
handler.push(err.with_source(importer_source.clone()));
698710
continue;
699711
}
700712
};
713+
*/
701714
let source_full_path = SourceName::Real(Arc::from(full_path));
702715
let ind = self.lookup[&source_full_path];
703716

@@ -776,6 +789,33 @@ impl ProjectGraph {
776789
}
777790
}
778791

792+
/// Resolves a single `use` declaration into a physical file path.
793+
fn resolve_single_import(
794+
importer_source: SourceFile,
795+
use_decl: &parse::UseDecl,
796+
dependency_map: &DependencyMap,
797+
) -> Result<PathBuf, RichError> {
798+
// TODO: @LesterEvSe or someone else, reconsider this architectural approach.
799+
// Consider removing this `match` statement, or dropping `SourceName` from `paths` and `lookup`.
800+
let curr_path = match importer_source.name() {
801+
SourceName::Real(path) => path,
802+
SourceName::Virtual(name) => {
803+
// Notice we use `return Err(...)` here instead of `continue`
804+
return Err(RichError::new(
805+
Error::Resolution(format!(
806+
"Virtual source '{name}' cannot be used to resolve library imports"
807+
)),
808+
*use_decl.span(),
809+
));
810+
}
811+
};
812+
813+
match dependency_map.resolve_path(&curr_path, use_decl) {
814+
Ok(path) => Ok(path),
815+
Err(err) => Err(err.with_source(importer_source.clone())),
816+
}
817+
}
818+
779819
/// C3 Merge Algorithm
780820
///
781821
/// Merges a list of sequences (parent linearizations) into a single sequence.
@@ -989,9 +1029,30 @@ pub(crate) mod tests {
9891029
(program.expect("Root parsing failed internally"), source)
9901030
}
9911031

992-
/// Sets up a graph with "lib" mapped to "libs/lib".
993-
/// Files format: vec![("main.simf", "content"), ("libs/lib/A.simf", "content")]
994-
fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap<String, usize>, TempDir) {
1032+
/// Bootstraps a mock file system and attempts to construct a `ProjectGraph`.
1033+
///
1034+
/// This is the low-level, non-panicking test helper. It is designed specifically for
1035+
/// "negative tests" where you expect the graph construction to fail (e.g., due to syntax
1036+
/// errors in an imported dependency).
1037+
///
1038+
/// The mock environment automatically maps the alias `"lib"` to the `"libs/lib"` directory.
1039+
///
1040+
/// # Arguments
1041+
/// * `files` - A vector of tuples containing `(file_path, file_content)`.
1042+
/// **Note:** One of the files *must* be named exactly `"main.simf"`.
1043+
///
1044+
/// # Returns
1045+
/// A tuple containing:
1046+
/// 1. `Option<ProjectGraph>` - `Some` if construction succeeded, `None` if compilation failed.
1047+
/// 2. `ErrorCollector` - Contains all diagnostics emitted during parsing and resolution.
1048+
/// 3. `TempDir` - The temporary directory. It must be kept alive until the test completes.
1049+
///
1050+
/// # Panics
1051+
/// Panics if the `files` vector does not contain a `"main.simf"` entry, or if writing
1052+
/// the mock files to the OS filesystem fails.
1053+
fn setup_graph_raw(
1054+
files: Vec<(&str, &str)>,
1055+
) -> (Option<ProjectGraph>, ErrorCollector, TempDir) {
9951056
let temp_dir = TempDir::new().unwrap();
9961057

9971058
// 1. Create Files
@@ -1005,20 +1066,55 @@ pub(crate) mod tests {
10051066
let root_p = root_path.expect("Tests must define 'main.simf'");
10061067

10071068
// 2. Setup Libraries (Hardcoded "lib" -> "libs/lib" for simplicity in tests)
1008-
let mut lib_map = HashMap::new();
1009-
lib_map.insert("lib".to_string(), temp_dir.path().join("libs/lib"));
1069+
let mut dependency_map = DependencyMap::new();
1070+
dependency_map.test_insert_without_canonicalize(
1071+
temp_dir.path(), // The root of mock project
1072+
"lib".to_string(),
1073+
&temp_dir.path().join("libs/lib"),
1074+
);
10101075

10111076
// 3. Parse & Build
10121077
let (root_program, source) = parse_root(&root_p);
1013-
10141078
let mut handler = ErrorCollector::new();
10151079

1016-
let graph = ProjectGraph::new(source, Arc::from(lib_map), &root_program, &mut handler)
1017-
.expect(
1018-
"setup_graph expects a valid graph construction. Use manual setup for error tests.",
1019-
);
1080+
let result = ProjectGraph::new(
1081+
source,
1082+
Arc::from(dependency_map),
1083+
&root_program,
1084+
&mut handler,
1085+
);
1086+
1087+
// Return the raw result and the handler so the test can inspect the errors
1088+
(result, handler, temp_dir)
1089+
}
10201090

1021-
// 4. Create Lookup (File Name -> ID) for easier asserting
1091+
/// Bootstraps a mock file system and constructs a valid `ProjectGraph`.
1092+
///
1093+
/// This is the standard test helper for "happy path" scenarios. It wraps [`setup_graph_raw`]
1094+
/// and mathematically guarantees that the graph construction succeeds. It also generates a
1095+
/// convenient filename-to-ID lookup map to make asserting on specific files easier.
1096+
///
1097+
/// # Arguments
1098+
/// * `files` - A vector of tuples containing `(file_path, file_content)`.
1099+
/// **Note:** One of the files *must* be named exactly `"main.simf"`.
1100+
///
1101+
/// # Returns
1102+
/// A tuple containing:
1103+
/// 1. `ProjectGraph` - The fully constructed, valid dependency graph.
1104+
/// 2. `HashMap<String, usize>` - A mapping of simple filenames (e.g., `"math.simf"`) to their node IDs.
1105+
/// 3. `TempDir` - The temporary directory. It must be kept alive until the test completes.
1106+
///
1107+
/// # Panics
1108+
/// Panics if the compiler encounters any errors during parsing or resolution,
1109+
/// or if `"main.simf"` is missing. For testing compiler errors, use [`setup_graph_raw`] instead.
1110+
fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap<String, usize>, TempDir) {
1111+
let (graph_result, _handler, temp_dir) = setup_graph_raw(files);
1112+
1113+
let graph = graph_result.expect(
1114+
"setup_graph expects a valid graph construction. Use manual setup for error tests.",
1115+
);
1116+
1117+
// Create Lookup (File Name -> ID) for easier asserting
10221118
let mut file_ids = HashMap::new();
10231119
for (source_name, id) in &graph.lookup {
10241120
let simple_name = match source_name {
@@ -1319,37 +1415,7 @@ pub(crate) mod tests {
13191415
assert!(graph.dependencies[&ids["main"]].is_empty());
13201416
}
13211417

1322-
#[test]
1323-
fn test_missing_file_error() {
1324-
// MANUAL SETUP REQUIRED
1325-
// We cannot use `setup_graph` here because we expect `ProjectGraph::new` to fail/return None.
1326-
1327-
let temp_dir = TempDir::new().unwrap();
1328-
let root_path = create_simf_file(temp_dir.path(), "main.simf", "use lib::ghost::Phantom;");
1329-
// We purposefully DO NOT create ghost.simf
1330-
1331-
let mut lib_map = HashMap::new();
1332-
lib_map.insert("lib".to_string(), temp_dir.path().join("libs/lib"));
1333-
1334-
let (root_program, root_source) = parse_root(&root_path);
1335-
let mut handler = ErrorCollector::new();
1336-
1337-
let result =
1338-
ProjectGraph::new(root_source, Arc::from(lib_map), &root_program, &mut handler);
1339-
1340-
assert!(result.is_none(), "Graph construction should fail");
1341-
assert!(handler.has_errors());
1342-
1343-
let error_msg = ErrorCollector::to_string(&handler);
1344-
assert!(
1345-
error_msg.contains("File not found") || error_msg.contains("ghost.simf"),
1346-
"Error message should mention 'ghost.simf' or 'File not found'. Got: {}",
1347-
error_msg
1348-
);
1349-
}
1350-
13511418
// Tests for aliases
1352-
// TODO: @LesterEvSe, @Sdoba16 add more tests for alias
13531419
#[test]
13541420
fn test_renaming_with_use() {
13551421
// Scenario: Renaming imports.
@@ -1574,27 +1640,29 @@ Try reordering your `use` statements to avoid cross-wiring."#
15741640
}
15751641

15761642
// --- Dependent File Error Display Tests ---
1577-
15781643
#[test]
1579-
#[ignore = "TODO(Error_Formatting): The compiler currently strips the .simf extension from file paths during graph construction. This test expects the extension to be preserved."]
1580-
fn test_display_error_in_imported_dependency() {
1581-
let temp_dir = TempDir::new().unwrap();
1582-
let root_path = create_simf_file(temp_dir.path(), "main.simf", "use lib::math::add;");
1583-
1584-
create_simf_file(
1585-
temp_dir.path(),
1586-
"libs/lib/math.simf",
1587-
"pub fn add(a: u32 b: u32) {}",
1588-
);
1644+
fn test_missing_file_error() {
1645+
let (result, handler, _dir) =
1646+
setup_graph_raw(vec![("main.simf", "use lib::ghost::Phantom;")]);
15891647

1590-
let mut lib_map = HashMap::new();
1591-
lib_map.insert("lib".to_string(), temp_dir.path().join("libs/lib"));
1648+
assert!(result.is_none(), "Graph construction should fail");
1649+
assert!(handler.has_errors());
15921650

1593-
let (root_program, root_source) = parse_root(&root_path);
1594-
let mut handler = ErrorCollector::new();
1651+
let error_msg = handler.to_string();
1652+
assert!(
1653+
error_msg.contains("File not found") || error_msg.contains("ghost.simf"),
1654+
"Error message should mention 'ghost.simf' or 'File not found'. Got: {}",
1655+
error_msg
1656+
);
1657+
}
15951658

1596-
let result =
1597-
ProjectGraph::new(root_source, Arc::from(lib_map), &root_program, &mut handler);
1659+
#[test]
1660+
#[ignore = "TODO(Error_Formatting): The compiler currently strips the .simf extension from file paths during graph construction. This test expects the extension to be preserved."]
1661+
fn test_display_error_in_imported_dependency() {
1662+
let (result, handler, _dir) = setup_graph_raw(vec![
1663+
("main.simf", "use lib::math::add;"),
1664+
("libs/lib/math.simf", "pub fn add(a: u32 b: u32) {}"), // NOTE: The comma is missing on purpose.
1665+
]);
15981666

15991667
assert!(
16001668
result.is_none(),

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ pub enum Error {
427427
ListBoundPow2(usize),
428428
BitStringPow2(usize),
429429
CannotParse(String),
430+
Resolution(String),
430431
Grammar(String),
431432
Syntax {
432433
expected: Vec<String>,
@@ -497,6 +498,10 @@ impl fmt::Display for Error {
497498
f,
498499
"Cannot parse: {description}"
499500
),
501+
Error::Resolution(description) => write!(
502+
f,
503+
"Resolution error: {description}"
504+
),
500505
Error::Grammar(description) => write!(
501506
f,
502507
"Grammar error: {description}"

0 commit comments

Comments
 (0)