Skip to content

Commit 58fd255

Browse files
committed
feat: add build_order function, refactor and test it
1 parent ad841ae commit 58fd255

5 files changed

Lines changed: 300 additions & 16 deletions

File tree

src/driver/mod.rs

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use std::collections::{HashMap, VecDeque};
55
use std::path::{Path, PathBuf};
66
use std::sync::Arc;
77

8-
use crate::error::ErrorCollector;
9-
use crate::parse::{self, ParseFromStrWithErrors};
8+
use crate::error::{ErrorCollector, Span};
9+
use crate::parse::{self, ParseFromStrWithErrors, Visibility};
10+
use crate::str::Identifier;
1011
use crate::LibConfig;
1112

1213
/// Represents a single, isolated file in the SimplicityHL project.
@@ -22,13 +23,22 @@ pub struct Module {
2223
pub struct ProjectGraph {
2324
/// Arena Pattern: the data itself lives here.
2425
/// A flat vector guarantees that module data is stored contiguously in memory.
25-
pub modules: Vec<Module>,
26+
pub(self) modules: Vec<Module>,
27+
28+
/// The configuration environment.
29+
/// Used to resolve xternal library dependencies and invoke their associated functions.
30+
pub config: Arc<LibConfig>,
2631

2732
/// Fast lookup: File Path -> Module ID.
2833
/// A reverse index mapping absolute file paths to their internal IDs.
2934
/// This solves the duplication problem, ensuring each file is only parsed once.
3035
pub lookup: HashMap<PathBuf, usize>,
3136

37+
/// Fast lookup: Module ID -> File Path.
38+
/// A direct index mapping internal IDs back to their absolute file paths.
39+
/// This serves as the exact inverse of the `lookup` map.
40+
pub paths: Vec<PathBuf>,
41+
3242
/// The Adjacency List: Defines the Directed acyclic Graph (DAG) of imports.
3343
///
3444
/// The Key (`usize`) is the ID of a "Parent" module (the file doing the importing).
@@ -39,6 +49,18 @@ pub struct ProjectGraph {
3949
pub dependencies: HashMap<usize, Vec<usize>>,
4050
}
4151

52+
#[derive(Clone, Debug)]
53+
pub struct Resolution {
54+
pub visibility: Visibility,
55+
}
56+
57+
pub struct Program {
58+
//pub graph: ProjectGraph,
59+
pub items: Arc<[parse::Item]>,
60+
pub scope_items: Vec<HashMap<Identifier, Resolution>>,
61+
pub span: Span,
62+
}
63+
4264
#[derive(Debug)]
4365
pub enum C3Error {
4466
CycleDetected(Vec<usize>),
@@ -91,15 +113,16 @@ fn merge(mut seqs: Vec<Vec<usize>>) -> Option<Vec<usize>> {
91113
}
92114

93115
impl ProjectGraph {
94-
pub fn new(lib_cfg: &LibConfig, root_program: &parse::Program) -> Result<Self, String> {
116+
pub fn new(config: Arc<LibConfig>, root_program: &parse::Program) -> Result<Self, String> {
95117
let mut modules: Vec<Module> = vec![Module {
96118
parsed_program: root_program.clone(),
97119
}];
98120
let mut lookup: HashMap<PathBuf, usize> = HashMap::new();
121+
let mut paths: Vec<PathBuf> = vec![config.root_path.clone()];
99122
let mut dependencies: HashMap<usize, Vec<usize>> = HashMap::new();
100123

101124
let root_id = 0;
102-
lookup.insert(lib_cfg.root_path.clone(), root_id);
125+
lookup.insert(config.root_path.clone(), root_id);
103126
dependencies.insert(root_id, Vec::new());
104127

105128
// Implementation of the standard BFS algorithm with memoization and queue
@@ -112,7 +135,7 @@ impl ProjectGraph {
112135

113136
for elem in current_program.items() {
114137
if let parse::Item::Use(use_decl) = elem {
115-
if let Ok(path) = lib_cfg.get_full_path(use_decl) {
138+
if let Ok(path) = config.get_full_path(use_decl) {
116139
pending_imports.push(path);
117140
}
118141
}
@@ -140,6 +163,7 @@ impl ProjectGraph {
140163
parsed_program: program,
141164
});
142165
lookup.insert(path.clone(), last_ind);
166+
paths.push(path.clone());
143167
dependencies.entry(curr_id).or_default().push(last_ind);
144168

145169
queue.push_back(last_ind);
@@ -148,7 +172,9 @@ impl ProjectGraph {
148172

149173
Ok(Self {
150174
modules,
175+
config,
151176
lookup,
177+
paths,
152178
dependencies,
153179
})
154180
}
@@ -202,4 +228,115 @@ impl ProjectGraph {
202228

203229
Ok(result)
204230
}
231+
232+
fn process_use_item(
233+
scope_items: &mut [HashMap<Identifier, Resolution>],
234+
file_id: usize,
235+
ind: usize,
236+
elem: &Identifier,
237+
use_decl_visibility: Visibility,
238+
) -> Result<(), String> {
239+
if matches!(
240+
scope_items[ind][elem].visibility,
241+
parse::Visibility::Private
242+
) {
243+
return Err(format!(
244+
"Function {} is private and cannot be used.",
245+
elem.as_inner()
246+
));
247+
}
248+
249+
scope_items[file_id].insert(
250+
elem.clone(),
251+
Resolution {
252+
visibility: use_decl_visibility,
253+
},
254+
);
255+
256+
Ok(())
257+
}
258+
259+
fn register_def(
260+
items: &mut Vec<parse::Item>,
261+
scope: &mut HashMap<Identifier, Resolution>,
262+
item: &parse::Item,
263+
name: Identifier,
264+
vis: &parse::Visibility,
265+
) {
266+
items.push(item.clone());
267+
scope.insert(
268+
name,
269+
Resolution {
270+
visibility: vis.clone(),
271+
},
272+
);
273+
}
274+
275+
// TODO: @LesterEvSe, consider processing more than one error at a time
276+
fn build_program(&self, order: &Vec<usize>) -> Result<Program, String> {
277+
let mut items: Vec<parse::Item> = Vec::new();
278+
let mut scope_items: Vec<HashMap<Identifier, Resolution>> =
279+
vec![HashMap::new(); order.len()];
280+
281+
for &file_id in order {
282+
let program_items = self.modules[file_id].parsed_program.items();
283+
284+
for elem in program_items {
285+
match elem {
286+
parse::Item::Use(use_decl) => {
287+
let full_path = self.config.get_full_path(use_decl)?;
288+
let ind = self.lookup[&full_path];
289+
let visibility = use_decl.visibility();
290+
291+
let use_targets = match use_decl.items() {
292+
parse::UseItems::Single(elem) => std::slice::from_ref(elem),
293+
parse::UseItems::List(elems) => elems.as_slice(),
294+
};
295+
296+
for target in use_targets {
297+
ProjectGraph::process_use_item(
298+
&mut scope_items,
299+
file_id,
300+
ind,
301+
target,
302+
visibility.clone(),
303+
)?;
304+
}
305+
}
306+
parse::Item::TypeAlias(alias) => {
307+
Self::register_def(
308+
&mut items,
309+
&mut scope_items[file_id],
310+
elem,
311+
alias.name().clone().into(),
312+
alias.visibility(),
313+
);
314+
}
315+
parse::Item::Function(function) => {
316+
Self::register_def(
317+
&mut items,
318+
&mut scope_items[file_id],
319+
elem,
320+
function.name().clone().into(),
321+
function.visibility(),
322+
);
323+
}
324+
parse::Item::Module => {}
325+
}
326+
}
327+
}
328+
329+
Ok(Program {
330+
items: items.into(),
331+
scope_items,
332+
span: *self.modules[0].parsed_program.as_ref(),
333+
})
334+
}
335+
336+
pub fn resolve_complication_order(&self) -> Result<Program, String> {
337+
// TODO: @LesterEvSe, resolve errors more appropriately
338+
let mut order = self.c3_linearize().unwrap();
339+
order.reverse();
340+
self.build_program(&order)
341+
}
205342
}

src/driver/tests.rs

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn create_simf_file(dir: &Path, rel_path: &str, content: &str) -> PathBuf {
2020

2121
// Helper to mock the initial root program parsing
2222
fn parse_root(path: &Path) -> parse::Program {
23-
parse_and_get_program(path).expect("Root parsing failed")
23+
parse_and_get_program(path).unwrap()
2424
}
2525

2626
/// Initializes a graph environment for testing.
@@ -51,8 +51,8 @@ fn setup_graph(files: Vec<(&str, &str)>) -> (ProjectGraph, HashMap<String, usize
5151
let root_p = root_path.expect("main.simf must be defined in file list");
5252
let root_program = parse_root(&root_p);
5353

54-
let config = LibConfig::new(lib_map, &root_p);
55-
let graph = ProjectGraph::new(&config, &root_program).unwrap();
54+
let config = Arc::from(LibConfig::new(lib_map, &root_p));
55+
let graph = ProjectGraph::new(config, &root_program).unwrap();
5656

5757
// Create a lookup map for tests: "A.simf" -> FileID
5858
let mut file_ids = HashMap::new();
@@ -87,7 +87,7 @@ mod graph_construction_and_dependency_resolution {
8787

8888
assert!(
8989
graph.dependencies[&root_id].contains(&math_id),
90-
"Root (main.simf) should depend on Math (main.simf)"
90+
"Root (main.simf) should depend on Math (math.simf)"
9191
);
9292
}
9393

@@ -231,6 +231,124 @@ mod c3_linearization {
231231
}
232232
}
233233

234+
#[cfg(test)]
235+
mod visibility_and_access_control {
236+
use super::*;
237+
238+
#[test]
239+
fn test_local_definitions_visibility() {
240+
// Scenario:
241+
// main.simf defines a private function and a public function.
242+
// Expected: Both should appear in the scope with correct visibility.
243+
244+
let (graph, ids, _dir) = setup_graph(vec![(
245+
"main.simf",
246+
"fn private_fn() {} pub fn public_fn() {}",
247+
)]);
248+
249+
let root_id = ids["main"];
250+
let order = vec![root_id]; // Only one file
251+
252+
let program = graph
253+
.build_program(&order)
254+
.expect("Failed to build program");
255+
let scope = &program.scope_items[root_id];
256+
257+
// Check private function
258+
let private_res = scope
259+
.get(&Identifier::from("private_fn"))
260+
.expect("private_fn missing");
261+
assert_eq!(private_res.visibility, Visibility::Private);
262+
263+
// Check public function
264+
let public_res = scope
265+
.get(&Identifier::from("public_fn"))
266+
.expect("public_fn missing");
267+
assert_eq!(public_res.visibility, Visibility::Public);
268+
}
269+
270+
#[test]
271+
fn test_pub_use_propagation() {
272+
// Scenario: Re-exporting.
273+
// 1. A.simf defines `pub fn foo`.
274+
// 2. B.simf imports it and re-exports it via `pub use`.
275+
// 3. main.simf imports it from B.
276+
// Expected: B's scope must contain `foo` marked as Public.
277+
278+
let (graph, ids, _dir) = setup_graph(vec![
279+
("libs/lib/A.simf", "pub fn foo() {}"),
280+
("libs/lib/B.simf", "pub use lib::A::foo;"),
281+
("main.simf", "use lib::B::foo;"),
282+
]);
283+
284+
let id_a = ids["A"];
285+
let id_b = ids["B"];
286+
let id_root = ids["main"];
287+
288+
// Manual topological order: A -> B -> Root
289+
let order = vec![id_a, id_b, id_root];
290+
291+
let program = graph
292+
.build_program(&order)
293+
.expect("Failed to build program");
294+
295+
// Check B's scope
296+
let scope_b = &program.scope_items[id_b];
297+
let foo_in_b = scope_b
298+
.get(&Identifier::from("foo"))
299+
.expect("foo missing in B");
300+
301+
// This is the critical check: Did `pub use` make it Public in B?
302+
assert_eq!(
303+
foo_in_b.visibility,
304+
Visibility::Public,
305+
"B should re-export foo as Public"
306+
);
307+
308+
// Check Root's scope
309+
let scope_root = &program.scope_items[id_root];
310+
let foo_in_root = scope_root
311+
.get(&Identifier::from("foo"))
312+
.expect("foo missing in Root");
313+
314+
// Root imported it via `use` (not pub use), so it should be Private in Root
315+
assert_eq!(
316+
foo_in_root.visibility,
317+
Visibility::Private,
318+
"Root should have foo as Private"
319+
);
320+
}
321+
322+
#[test]
323+
fn test_private_import_encapsulation_error() {
324+
// Scenario: Access violation.
325+
// 1. A.simf defines `pub fn foo`.
326+
// 2. B.simf imports it via `use` (Private import).
327+
// 3. main.simf tries to import `foo` from B.
328+
// Expected: Error, because B did not re-export foo.
329+
330+
let (graph, ids, _dir) = setup_graph(vec![
331+
("libs/lib/A.simf", "pub fn foo() {}"),
332+
("libs/lib/B.simf", "use lib::A::foo;"), // <--- Private binding!
333+
("main.simf", "use lib::B::foo;"), // <--- Should fail
334+
]);
335+
336+
let id_a = ids["A"];
337+
let id_b = ids["B"];
338+
let id_root = ids["main"];
339+
340+
// Order: A -> B -> Root
341+
let order = vec![id_a, id_b, id_root];
342+
343+
let result = graph.build_program(&order);
344+
345+
assert!(
346+
result.is_err(),
347+
"Build should fail when importing a private binding"
348+
);
349+
}
350+
}
351+
234352
#[cfg(test)]
235353
mod error_diganostic_and_terminal_formatting {
236354
use super::*;
@@ -248,8 +366,8 @@ mod error_diganostic_and_terminal_formatting {
248366
lib_map.insert("std".to_string(), temp_dir.path().join("libs/std"));
249367

250368
let root_program = parse_root(&root_path);
251-
let config = LibConfig::new(lib_map, &root_path);
252-
let result = ProjectGraph::new(&config, &root_program);
369+
let config = Arc::from(LibConfig::new(lib_map, &root_path));
370+
let result = ProjectGraph::new(config, &root_program);
253371

254372
assert!(result.is_err(), "Should fail for missing file");
255373
let err_msg = result.err().unwrap();

0 commit comments

Comments
 (0)