Skip to content

Commit ebd02ff

Browse files
committed
feat: add build_order function, refactor and test it
1 parent e3c42b0 commit ebd02ff

5 files changed

Lines changed: 297 additions & 14 deletions

File tree

src/driver/mod.rs

Lines changed: 143 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ 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};
1010
use crate::resolution::LibConfig;
11+
use crate::str::Identifier;
1112

1213
/// Represents a single, isolated file in the SimplicityHL project.
1314
/// In this architecture, a file and a module are the exact same thing.
@@ -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
}
@@ -212,4 +238,115 @@ impl ProjectGraph {
212238

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

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

@@ -230,6 +230,124 @@ mod c3_linearization {
230230
}
231231
}
232232

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

249367
let root_program = parse_root(&root_path);
250-
let config = LibConfig::new(lib_map, &root_path);
251-
let result = ProjectGraph::new(&config, &root_program);
368+
let config = Arc::from(LibConfig::new(lib_map, &root_path));
369+
let result = ProjectGraph::new(config, &root_program);
252370

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

src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ impl TemplateProgram {
5959
let parse_program = parse::Program::parse_from_str_with_errors(&file, &mut error_handler);
6060

6161
if let Some(program) = parse_program {
62-
let _ = if let Some(lib_cfg) = lib_cfg {
63-
Some(ProjectGraph::new(lib_cfg, &program)?)
62+
// TODO: Consider a proper resolution strategy later.
63+
let _: Option<driver::Program> = if let Some(cfg) = lib_cfg {
64+
let config_arc = Arc::new(cfg.clone());
65+
let graph = ProjectGraph::new(config_arc, &program)?;
66+
67+
// TODO: Perhaps add an `error_handler` here, too.
68+
Some(graph.resolve_complication_order()?)
6469
} else {
6570
None
6671
};

0 commit comments

Comments
 (0)