dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
14 changed files with 135 additions and 67 deletions
Showing only changes of commit 6cf1e71b99 - Show all commits

View File

@ -237,14 +237,19 @@ impl Linker {
if let Some(local_func_idx) = meta.func_idx {
let global_func_idx = module_function_offsets.last().unwrap() + local_func_idx;
final_exports.insert(format!("{}:{}", key.module_path, key.symbol_name), global_func_idx);
// Also provide short name for root module exports to facilitate entrypoint resolution
if !final_exports.contains_key(&key.symbol_name) {
final_exports.insert(key.symbol_name.clone(), global_func_idx);
}
}
}
}
// v0: Fallback export for entrypoint `src/main/modules:frame` (root module)
if !final_exports.iter().any(|(name, _)| name.ends_with(":frame")) {
// v0: Fallback export for entrypoint `frame` (root module)
if !final_exports.iter().any(|(name, _)| name.ends_with(":frame") || name == "frame") {
if let Some(&root_offset) = module_function_offsets.last() {
if let Some((idx, _)) = combined_function_names.iter().find(|(i, name)| *i >= root_offset && name == "frame") {
final_exports.insert("frame".to_string(), *idx);
final_exports.insert("src/main/modules:frame".to_string(), *idx);
}
}
@ -274,9 +279,9 @@ mod tests {
use std::collections::BTreeMap;
use super::*;
use crate::building::output::{ExportKey, ExportMetadata, ImportKey, ImportMetadata};
use crate::semantics::export_surface::ExportSurfaceKind;
use crate::building::plan::BuildTarget;
use crate::deps::resolver::ProjectId;
use crate::frontends::pbs::symbols::SymbolKind;
use prometeu_bytecode::opcode::OpCode;
use prometeu_bytecode::FunctionMeta;
@ -294,7 +299,7 @@ mod tests {
lib_exports.insert(ExportKey {
module_path: "math".into(),
symbol_name: "add".into(),
kind: SymbolKind::Function,
kind: ExportSurfaceKind::Service,
}, ExportMetadata { func_idx: Some(0), is_host: false, ty: None });
let lib_module = CompiledModule {

View File

@ -12,6 +12,7 @@ use crate::frontends::pbs::resolver::{ModuleProvider, Resolver};
use crate::frontends::pbs::symbols::{ModuleSymbols, Namespace, Symbol, SymbolKind, Visibility};
use crate::frontends::pbs::typecheck::TypeChecker;
use crate::frontends::pbs::types::PbsType;
use crate::semantics::export_surface::ExportSurfaceKind;
use crate::lowering::core_to_vm;
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo, FunctionMeta};
use serde::{Deserialize, Serialize};
@ -22,7 +23,7 @@ use std::path::Path;
pub struct ExportKey {
pub module_path: String,
pub symbol_name: String,
pub kind: SymbolKind,
pub kind: ExportSurfaceKind,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -165,12 +166,17 @@ pub fn compile_project(
let sym = Symbol {
name: key.symbol_name.clone(),
kind: key.kind.clone(),
namespace: match key.kind {
SymbolKind::Function | SymbolKind::Service => Namespace::Value,
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => Namespace::Type,
_ => Namespace::Value,
kind: match key.kind {
ExportSurfaceKind::Service => SymbolKind::Service,
ExportSurfaceKind::DeclareType => {
match &meta.ty {
Some(PbsType::Contract(_)) => SymbolKind::Contract,
Some(PbsType::ErrorType(_)) => SymbolKind::ErrorType,
_ => SymbolKind::Struct,
}
}
},
namespace: key.kind.namespace(),
visibility: Visibility::Pub,
ty: meta.ty.clone(),
is_host: meta.is_host,
@ -245,31 +251,35 @@ pub fn compile_project(
for (module_path, ms) in &module_symbols_map {
for sym in ms.type_symbols.symbols.values() {
if sym.visibility == Visibility::Pub {
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: sym.kind.clone(),
}, ExportMetadata {
func_idx: None,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) {
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: surface_kind,
}, ExportMetadata {
func_idx: None,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
}
}
}
for sym in ms.value_symbols.symbols.values() {
if sym.visibility == Visibility::Pub {
// Find func_idx if it's a function or service
let func_idx = vm_module.functions.iter().position(|f| f.name == sym.name).map(|i| i as u32);
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: sym.kind.clone(),
}, ExportMetadata {
func_idx,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
if let Some(surface_kind) = ExportSurfaceKind::from_symbol_kind(sym.kind) {
// Find func_idx if it's a function or service
let func_idx = vm_module.functions.iter().position(|f| f.name == sym.name).map(|i| i as u32);
exports.insert(ExportKey {
module_path: module_path.clone(),
symbol_name: sym.name.clone(),
kind: surface_kind,
}, ExportMetadata {
func_idx,
is_host: sym.is_host,
ty: sym.ty.clone(),
});
}
}
}
}
@ -359,7 +369,7 @@ mod tests {
let vec2_key = ExportKey {
module_path: "src/main/modules".to_string(),
symbol_name: "Vec2".to_string(),
kind: SymbolKind::Struct,
kind: ExportSurfaceKind::DeclareType,
};
assert!(compiled.exports.contains_key(&vec2_key));

View File

@ -62,7 +62,7 @@ pub struct ImportSpecNode {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ServiceDeclNode {
pub span: Span,
pub vis: String, // "pub" | "mod"
pub vis: Option<String>, // "pub" | "mod"
pub name: String,
pub extends: Option<String>,
pub members: Vec<Node>, // ServiceFnSig
@ -86,7 +86,7 @@ pub struct ParamNode {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FnDeclNode {
pub span: Span,
pub vis: String,
pub vis: Option<String>,
pub name: String,
pub params: Vec<ParamNode>,
pub ret: Option<Box<Node>>,

View File

@ -1,6 +1,7 @@
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
use crate::frontends::pbs::ast::*;
use crate::frontends::pbs::symbols::*;
use crate::semantics::export_surface::ExportSurfaceKind;
pub struct SymbolCollector {
type_symbols: SymbolTable,
@ -40,11 +41,14 @@ impl SymbolCollector {
}
fn collect_fn(&mut self, decl: &FnDeclNode) {
let vis = match decl.vis.as_str() {
"pub" => Visibility::Pub,
"mod" => Visibility::Mod,
let vis = match decl.vis.as_deref() {
Some("pub") => Visibility::Pub,
Some("mod") => Visibility::Mod,
_ => Visibility::FilePrivate,
};
self.check_export_eligibility(SymbolKind::Function, vis, decl.span);
let symbol = Symbol {
name: decl.name.clone(),
kind: SymbolKind::Function,
@ -59,11 +63,13 @@ impl SymbolCollector {
}
fn collect_service(&mut self, decl: &ServiceDeclNode) {
let vis = match decl.vis.as_str() {
"pub" => Visibility::Pub,
"mod" => Visibility::Mod,
_ => Visibility::FilePrivate, // Should not happen with valid parser
let vis = match decl.vis.as_deref() {
Some("pub") => Visibility::Pub,
_ => Visibility::Mod, // Defaults to Mod
};
self.check_export_eligibility(SymbolKind::Service, vis, decl.span);
let symbol = Symbol {
name: decl.name.clone(),
kind: SymbolKind::Service,
@ -89,6 +95,9 @@ impl SymbolCollector {
"error" => SymbolKind::ErrorType,
_ => SymbolKind::Struct, // Default
};
self.check_export_eligibility(kind.clone(), vis, decl.span);
let symbol = Symbol {
name: decl.name.clone(),
kind,
@ -148,4 +157,15 @@ impl SymbolCollector {
span: Some(symbol.span),
});
}
fn check_export_eligibility(&mut self, kind: SymbolKind, vis: Visibility, span: crate::common::spans::Span) {
if let Err(msg) = ExportSurfaceKind::validate_visibility(kind, vis) {
self.diagnostics.push(Diagnostic {
level: DiagnosticLevel::Error,
code: Some("E_SEMANTIC_EXPORT_RESTRICTION".to_string()),
message: msg,
span: Some(span),
});
}
}
}

View File

@ -154,7 +154,7 @@ impl Parser {
fn parse_top_level_decl(&mut self) -> Result<Node, DiagnosticBundle> {
match self.peek().kind {
TokenKind::Fn => self.parse_fn_decl("file".to_string()),
TokenKind::Fn => self.parse_fn_decl(None),
TokenKind::Pub | TokenKind::Mod | TokenKind::Declare | TokenKind::Service => self.parse_decl(),
TokenKind::Invalid(ref msg) => {
let code = if msg.contains("Unterminated string") {
@ -181,23 +181,14 @@ impl Parser {
};
match self.peek().kind {
TokenKind::Service => self.parse_service_decl(vis.unwrap_or_else(|| "pub".to_string())),
TokenKind::Service => self.parse_service_decl(vis),
TokenKind::Declare => self.parse_type_decl(vis),
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)
}
TokenKind::Fn => self.parse_fn_decl(vis),
_ => Err(self.error("Expected 'service', 'declare', or 'fn'")),
}
}
fn parse_service_decl(&mut self, vis: String) -> Result<Node, DiagnosticBundle> {
fn parse_service_decl(&mut self, vis: Option<String>) -> Result<Node, DiagnosticBundle> {
let start_span = self.consume(TokenKind::Service)?.span;
let name = self.expect_identifier()?;
let mut extends = None;
@ -362,7 +353,7 @@ impl Parser {
}))
}
fn parse_fn_decl(&mut self, vis: String) -> Result<Node, DiagnosticBundle> {
fn parse_fn_decl(&mut self, vis: Option<String>) -> Result<Node, DiagnosticBundle> {
let start_span = self.consume(TokenKind::Fn)?.span;
let name = self.expect_identifier()?;
let params = self.parse_param_list()?;
@ -1181,7 +1172,7 @@ fn good() {}
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");
assert_eq!(fn_decl.vis, Some("mod".to_string()));
} else {
panic!("Expected FnDecl");
}
@ -1191,10 +1182,12 @@ fn good() {}
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"));
let result = parser.parse_file().expect("pub fn should be allowed in parser");
if let Node::FnDecl(fn_decl) = &result.decls[0] {
assert_eq!(fn_decl.vis, Some("pub".to_string()));
} else {
panic!("Expected FnDecl");
}
}
#[test]

View File

@ -3,14 +3,14 @@ use crate::frontends::pbs::types::PbsType;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
FilePrivate,
Mod,
Pub,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
pub enum SymbolKind {
Function,
Service,

View File

@ -48,6 +48,7 @@ pub mod manifest;
pub mod deps;
pub mod sources;
pub mod building;
pub mod semantics;
use anyhow::Result;
use clap::{Parser, Subcommand};

View File

@ -0,0 +1,40 @@
use crate::frontends::pbs::symbols::{SymbolKind, Visibility};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ExportSurfaceKind {
Service,
DeclareType, // struct, storage struct, type alias
}
impl ExportSurfaceKind {
pub fn from_symbol_kind(kind: SymbolKind) -> Option<Self> {
match kind {
SymbolKind::Service => Some(ExportSurfaceKind::Service),
SymbolKind::Struct | SymbolKind::Contract | SymbolKind::ErrorType => {
Some(ExportSurfaceKind::DeclareType)
}
SymbolKind::Function | SymbolKind::Local => None,
}
}
pub fn validate_visibility(kind: SymbolKind, vis: Visibility) -> Result<(), String> {
if vis == Visibility::Pub {
if Self::from_symbol_kind(kind).is_none() {
let kind_str = match kind {
SymbolKind::Function => "Functions",
_ => "This declaration",
};
return Err(format!("{} are not exportable in this version.", kind_str));
}
}
Ok(())
}
pub fn namespace(&self) -> crate::frontends::pbs::symbols::Namespace {
match self {
ExportSurfaceKind::Service => crate::frontends::pbs::symbols::Namespace::Type,
ExportSurfaceKind::DeclareType => crate::frontends::pbs::symbols::Namespace::Type,
}
}
}

View File

@ -0,0 +1 @@
pub mod export_surface;

View File

@ -67,7 +67,7 @@ fn test_integration_test01_link() {
let mut vm = VirtualMachine::default();
// Use initialize to load the ROM; entrypoint must be numeric or empty (defaults to 0)
vm.initialize(unit.rom, "").expect("Failed to initialize VM");
vm.initialize(unit.rom, "frame").expect("Failed to initialize VM");
let mut native = SimpleNative;
let mut hw = SimpleHardware::new();

View File

@ -6,7 +6,6 @@ use prometeu_core::virtual_machine::{LogicalFrameEndingReason, VirtualMachine};
use prometeu_core::Hardware;
use std::fs;
use std::path::Path;
use prometeu_bytecode::BytecodeLoader;
struct MockNative;
impl NativeInterface for MockNative {
@ -40,7 +39,6 @@ fn test_canonical_cartridge_heartbeat() {
let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?");
// Determine entrypoint from the compiled module exports
let module = BytecodeLoader::load(&pbc_bytes).expect("Failed to parse PBC");
let entry_symbol = "src/main/modules:frame";
let mut vm = VirtualMachine::new(vec![], vec![]);

View File

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