dev/pbs #8
@ -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 {
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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>>,
|
||||
|
||||
@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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};
|
||||
|
||||
40
crates/prometeu-compiler/src/semantics/export_surface.rs
Normal file
40
crates/prometeu-compiler/src/semantics/export_surface.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/prometeu-compiler/src/semantics/mod.rs
Normal file
1
crates/prometeu-compiler/src/semantics/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod export_surface;
|
||||
@ -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();
|
||||
|
||||
@ -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![]);
|
||||
|
||||
@ -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": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user