This commit is contained in:
Nilton Constantino 2026-02-02 15:43:13 +00:00
parent 6732111328
commit ad1650592d
No known key found for this signature in database
7 changed files with 51 additions and 55 deletions

View File

@ -86,6 +86,7 @@ pub struct ParamNode {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FnDeclNode { pub struct FnDeclNode {
pub span: Span, pub span: Span,
pub vis: String,
pub name: String, pub name: String,
pub params: Vec<ParamNode>, pub params: Vec<ParamNode>,
pub ret: Option<Box<Node>>, pub ret: Option<Box<Node>>,

View File

@ -40,12 +40,16 @@ impl SymbolCollector {
} }
fn collect_fn(&mut self, decl: &FnDeclNode) { fn collect_fn(&mut self, decl: &FnDeclNode) {
// Top-level fn are always file-private in PBS v0 let vis = match decl.vis.as_str() {
"pub" => Visibility::Pub,
"mod" => Visibility::Mod,
_ => Visibility::FilePrivate,
};
let symbol = Symbol { let symbol = Symbol {
name: decl.name.clone(), name: decl.name.clone(),
kind: SymbolKind::Function, kind: SymbolKind::Function,
namespace: Namespace::Value, namespace: Namespace::Value,
visibility: Visibility::FilePrivate, visibility: vis,
ty: None, // Will be resolved later ty: None, // Will be resolved later
is_host: false, is_host: false,
span: decl.span, span: decl.span,

View File

@ -131,7 +131,7 @@ impl Parser {
fn parse_top_level_decl(&mut self) -> Result<Node, DiagnosticBundle> { fn parse_top_level_decl(&mut self) -> Result<Node, DiagnosticBundle> {
match self.peek().kind { match self.peek().kind {
TokenKind::Fn => self.parse_fn_decl(), TokenKind::Fn => self.parse_fn_decl("file".to_string()),
TokenKind::Pub | TokenKind::Mod | TokenKind::Declare | TokenKind::Service => self.parse_decl(), TokenKind::Pub | TokenKind::Mod | TokenKind::Declare | TokenKind::Service => self.parse_decl(),
TokenKind::Invalid(ref msg) => { TokenKind::Invalid(ref msg) => {
let code = if msg.contains("Unterminated string") { let code = if msg.contains("Unterminated string") {
@ -160,7 +160,17 @@ impl Parser {
match self.peek().kind { match self.peek().kind {
TokenKind::Service => self.parse_service_decl(vis.unwrap_or_else(|| "pub".to_string())), TokenKind::Service => self.parse_service_decl(vis.unwrap_or_else(|| "pub".to_string())),
TokenKind::Declare => self.parse_type_decl(vis), TokenKind::Declare => self.parse_type_decl(vis),
_ => Err(self.error("Expected 'service' or 'declare'")), TokenKind::Fn => {
let vis_str = vis.unwrap_or_else(|| "file".to_string());
if vis_str == "pub" {
return Err(self.error_with_code(
"Functions cannot be public. They are always mod or file-private.",
Some("E_RESOLVE_VISIBILITY"),
));
}
self.parse_fn_decl(vis_str)
}
_ => Err(self.error("Expected 'service', 'declare', or 'fn'")),
} }
} }
@ -329,7 +339,7 @@ impl Parser {
})) }))
} }
fn parse_fn_decl(&mut self) -> Result<Node, DiagnosticBundle> { fn parse_fn_decl(&mut self, vis: String) -> Result<Node, DiagnosticBundle> {
let start_span = self.consume(TokenKind::Fn)?.span; let start_span = self.consume(TokenKind::Fn)?.span;
let name = self.expect_identifier()?; let name = self.expect_identifier()?;
let params = self.parse_param_list()?; let params = self.parse_param_list()?;
@ -351,6 +361,7 @@ impl Parser {
Ok(Node::FnDecl(FnDeclNode { Ok(Node::FnDecl(FnDeclNode {
span: Span::new(self.file_id, start_span.start, body_span.end), span: Span::new(self.file_id, start_span.start, body_span.end),
vis,
name, name,
params, params,
ret: _ret, ret: _ret,
@ -1141,6 +1152,28 @@ fn good() {}
assert!(result.is_err()); assert!(result.is_err());
} }
#[test]
fn test_parse_mod_fn() {
let source = "mod fn test() {}";
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().expect("mod fn should be allowed");
if let Node::FnDecl(fn_decl) = &result.decls[0] {
assert_eq!(fn_decl.vis, "mod");
} else {
panic!("Expected FnDecl");
}
}
#[test]
fn test_parse_pub_fn() {
let source = "pub fn test() {}";
let mut parser = Parser::new(source, 0);
let result = parser.parse_file();
assert!(result.is_err(), "pub fn should be disallowed");
let err = result.unwrap_err();
assert!(err.diagnostics[0].message.contains("Functions cannot be public"));
}
#[test] #[test]
fn test_ast_json_snapshot() { fn test_ast_json_snapshot() {
let source = r#" let source = r#"

View File

@ -203,10 +203,10 @@ Visibility is mandatory for services.
### 3.4 Functions ### 3.4 Functions
``` ```
FnDecl ::= 'fn' Identifier ParamList ReturnType? ElseFallback? Block FnDecl ::= Visibility? 'fn' Identifier ParamList ReturnType? ElseFallback? Block
``` ```
Toplevel `fn` are always fileprivate. Toplevel `fn` are `mod` or `file-private` (default). They cannot be `pub`.
--- ---

View File

@ -202,7 +202,7 @@ The **value namespace** contains executable and runtime-visible symbols.
Symbols in the value namespace are introduced by: Symbols in the value namespace are introduced by:
* `service` * `service`
* top-level `fn` - always file-private. * top-level `fn` `mod` or `file-private` (default).
* top-level `let` are not allowed. * top-level `let` are not allowed.
Rules: Rules:
@ -360,9 +360,9 @@ Top-level `fn` declarations define reusable executable logic.
Rules: Rules:
* A top-level `fn` is always **file-private**. * A top-level `fn` is always **mod** or **file-private**.
* A top-level `fn` cannot be declared as `mod` or `pub`. * A top-level `fn` cannot be declared as `pub`.
* A top-level `fn` is visible only within the file where it is declared. * `fn` defaults to **file-private** visibility.
Example (VALID): Example (VALID):

View File

@ -1,47 +1,3 @@
## PR-13 — Build Plan v0: deterministic compilation order
**Why:** We need a stable, reproducible pipeline: compile dependencies first, then the root project.
### Scope
* Implement `prometeu_compiler::build::plan`:
* **Input:** `ResolvedGraph`
* **Output:** `BuildPlan` with topologically sorted build steps
* Each `BuildStep` MUST include:
* `project_id` — canonical project identity (`prometeu.json.name`)
* `project_dir` — absolute or normalized path
* `target``main` or `test`
* `sources` — ordered list of `.pbs` source files (from `src/<target>/modules`)
* `deps` — dependency edge map: `alias -> ProjectId`
### Determinism Rules (MANDATORY)
* Topological sort must be stable:
* when multiple nodes have indegree 0, choose by lexicographic `project_id`
* `sources` list must be:
* discovered only under `src/<target>/modules`
* sorted lexicographically by normalized relative path
* `deps` must be stored/exported in deterministic order (e.g. `BTreeMap`)
### Deliverables
* `BuildPlan { steps: Vec<BuildStep> }`
### Tests
* topo ordering stable across runs
* sources ordering stable regardless of filesystem order
### Acceptance
* BuildPlan is deterministic and contains all information needed to compile without further graph traversal.
---
## PR-14 — Compiler Output Format v0: emit per-project object module (intermediate) ## PR-14 — Compiler Output Format v0: emit per-project object module (intermediate)
**Why:** Linking requires a well-defined intermediate representation per project. **Why:** Linking requires a well-defined intermediate representation per project.

View File

@ -641,6 +641,7 @@
"start": 739, "start": 739,
"end": 788 "end": 788
}, },
"vis": "file",
"name": "add", "name": "add",
"params": [ "params": [
{ {
@ -742,6 +743,7 @@
"start": 790, "start": 790,
"end": 1180 "end": 1180
}, },
"vis": "file",
"name": "frame", "name": "frame",
"params": [], "params": [],
"ret": { "ret": {