pr 11
This commit is contained in:
parent
ced7ff0607
commit
f37c15f3d6
@ -47,6 +47,7 @@ impl SymbolCollector {
|
|||||||
namespace: Namespace::Value,
|
namespace: Namespace::Value,
|
||||||
visibility: Visibility::FilePrivate,
|
visibility: Visibility::FilePrivate,
|
||||||
ty: None, // Will be resolved later
|
ty: None, // Will be resolved later
|
||||||
|
is_host: false,
|
||||||
span: decl.span,
|
span: decl.span,
|
||||||
};
|
};
|
||||||
self.insert_value_symbol(symbol);
|
self.insert_value_symbol(symbol);
|
||||||
@ -64,6 +65,7 @@ impl SymbolCollector {
|
|||||||
namespace: Namespace::Type, // Service is a type
|
namespace: Namespace::Type, // Service is a type
|
||||||
visibility: vis,
|
visibility: vis,
|
||||||
ty: None,
|
ty: None,
|
||||||
|
is_host: false,
|
||||||
span: decl.span,
|
span: decl.span,
|
||||||
};
|
};
|
||||||
self.insert_type_symbol(symbol);
|
self.insert_type_symbol(symbol);
|
||||||
@ -87,6 +89,7 @@ impl SymbolCollector {
|
|||||||
namespace: Namespace::Type,
|
namespace: Namespace::Type,
|
||||||
visibility: vis,
|
visibility: vis,
|
||||||
ty: None,
|
ty: None,
|
||||||
|
is_host: decl.is_host,
|
||||||
span: decl.span,
|
span: decl.span,
|
||||||
};
|
};
|
||||||
self.insert_type_symbol(symbol);
|
self.insert_type_symbol(symbol);
|
||||||
|
|||||||
@ -61,6 +61,26 @@ impl ContractRegistry {
|
|||||||
log.insert("writeTag".to_string(), 0x5002);
|
log.insert("writeTag".to_string(), 0x5002);
|
||||||
mappings.insert("Log".to_string(), log);
|
mappings.insert("Log".to_string(), log);
|
||||||
|
|
||||||
|
// System mappings
|
||||||
|
let mut system = HashMap::new();
|
||||||
|
system.insert("hasCart".to_string(), 0x0001);
|
||||||
|
system.insert("runCart".to_string(), 0x0002);
|
||||||
|
mappings.insert("System".to_string(), system);
|
||||||
|
|
||||||
|
// Asset mappings
|
||||||
|
let mut asset = HashMap::new();
|
||||||
|
asset.insert("load".to_string(), 0x6001);
|
||||||
|
asset.insert("status".to_string(), 0x6002);
|
||||||
|
asset.insert("commit".to_string(), 0x6003);
|
||||||
|
asset.insert("cancel".to_string(), 0x6004);
|
||||||
|
mappings.insert("Asset".to_string(), asset);
|
||||||
|
|
||||||
|
// Bank mappings
|
||||||
|
let mut bank = HashMap::new();
|
||||||
|
bank.insert("info".to_string(), 0x6101);
|
||||||
|
bank.insert("slotInfo".to_string(), 0x6102);
|
||||||
|
mappings.insert("Bank".to_string(), bank);
|
||||||
|
|
||||||
Self { mappings }
|
Self { mappings }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator,
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct Lowerer<'a> {
|
pub struct Lowerer<'a> {
|
||||||
_module_symbols: &'a ModuleSymbols,
|
module_symbols: &'a ModuleSymbols,
|
||||||
program: Program,
|
program: Program,
|
||||||
current_function: Option<Function>,
|
current_function: Option<Function>,
|
||||||
current_block: Option<Block>,
|
current_block: Option<Block>,
|
||||||
@ -21,7 +21,7 @@ pub struct Lowerer<'a> {
|
|||||||
impl<'a> Lowerer<'a> {
|
impl<'a> Lowerer<'a> {
|
||||||
pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
|
pub fn new(module_symbols: &'a ModuleSymbols) -> Self {
|
||||||
Self {
|
Self {
|
||||||
_module_symbols: module_symbols,
|
module_symbols,
|
||||||
program: Program {
|
program: Program {
|
||||||
const_pool: ir_core::ConstPool::new(),
|
const_pool: ir_core::ConstPool::new(),
|
||||||
modules: Vec::new(),
|
modules: Vec::new(),
|
||||||
@ -224,17 +224,26 @@ impl<'a> Lowerer<'a> {
|
|||||||
}
|
}
|
||||||
Node::MemberAccess(ma) => {
|
Node::MemberAccess(ma) => {
|
||||||
if let Node::Ident(obj_id) = &*ma.object {
|
if let Node::Ident(obj_id) = &*ma.object {
|
||||||
|
// Check if it's a host contract according to symbol table
|
||||||
|
let is_host_contract = self.module_symbols.type_symbols.get(&obj_id.name)
|
||||||
|
.map(|sym| sym.kind == SymbolKind::Contract && sym.is_host)
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
// Ensure it's not shadowed by a local variable
|
||||||
|
let is_shadowed = self.lookup_local(&obj_id.name).is_some();
|
||||||
|
|
||||||
|
if is_host_contract && !is_shadowed {
|
||||||
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
|
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
|
||||||
self.emit(Instr::Syscall(syscall_id));
|
self.emit(Instr::Syscall(syscall_id));
|
||||||
} else {
|
return;
|
||||||
// Regular member call (method)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular member call (method) or fallback
|
||||||
// In v0 we don't handle this yet, so emit dummy call
|
// In v0 we don't handle this yet, so emit dummy call
|
||||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
_ => {
|
||||||
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
self.emit(Instr::Call(FunctionId(0), n.args.len() as u32));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -171,9 +171,17 @@ impl<'a> Resolver<'a> {
|
|||||||
self.exit_scope();
|
self.exit_scope();
|
||||||
}
|
}
|
||||||
Node::MemberAccess(n) => {
|
Node::MemberAccess(n) => {
|
||||||
|
if let Node::Ident(id) = &*n.object {
|
||||||
|
if self.lookup_identifier(&id.name, Namespace::Value).is_none() {
|
||||||
|
// If not found in Value namespace, try Type namespace (for Contracts/Services)
|
||||||
|
if self.lookup_identifier(&id.name, Namespace::Type).is_none() {
|
||||||
|
// Still not found, use resolve_identifier to report error in Value namespace
|
||||||
|
self.resolve_identifier(&id.name, id.span, Namespace::Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
self.resolve_node(&n.object);
|
self.resolve_node(&n.object);
|
||||||
// For member access, the member name itself isn't resolved in the value namespace
|
}
|
||||||
// unless it's a property. In v0, we mostly care about host calls.
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@ -235,21 +243,41 @@ impl<'a> Resolver<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option<Symbol> {
|
fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option<Symbol> {
|
||||||
// Built-ins (minimal for v0)
|
if self.is_builtin(name, namespace) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(sym) = self.lookup_identifier(name, namespace) {
|
||||||
|
Some(sym)
|
||||||
|
} else {
|
||||||
if namespace == Namespace::Type {
|
if namespace == Namespace::Type {
|
||||||
match name {
|
self.diagnostics.push(Diagnostic {
|
||||||
"int" | "float" | "string" | "bool" | "void" | "optional" | "result" => return None,
|
level: DiagnosticLevel::Error,
|
||||||
_ => {}
|
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
||||||
|
message: format!("Unknown type: {}", name),
|
||||||
|
span: Some(span),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.error_undefined(name, span);
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if namespace == Namespace::Value {
|
fn is_builtin(&self, name: &str, namespace: Namespace) -> bool {
|
||||||
match name {
|
match namespace {
|
||||||
"none" | "some" | "ok" | "err" | "true" | "false" => return None,
|
Namespace::Type => match name {
|
||||||
_ => {}
|
"int" | "float" | "string" | "bool" | "void" | "optional" | "result" => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
Namespace::Value => match name {
|
||||||
|
"none" | "some" | "ok" | "err" | "true" | "false" => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lookup_identifier(&self, name: &str, namespace: Namespace) -> Option<Symbol> {
|
||||||
// 1. local bindings
|
// 1. local bindings
|
||||||
if namespace == Namespace::Value {
|
if namespace == Namespace::Value {
|
||||||
for scope in self.scopes.iter().rev() {
|
for scope in self.scopes.iter().rev() {
|
||||||
@ -280,16 +308,6 @@ impl<'a> Resolver<'a> {
|
|||||||
return Some(sym.clone());
|
return Some(sym.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if namespace == Namespace::Type {
|
|
||||||
self.diagnostics.push(Diagnostic {
|
|
||||||
level: DiagnosticLevel::Error,
|
|
||||||
code: Some("E_TYPE_UNKNOWN_TYPE".to_string()),
|
|
||||||
message: format!("Unknown type: {}", name),
|
|
||||||
span: Some(span),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.error_undefined(name, span);
|
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,6 +341,7 @@ impl<'a> Resolver<'a> {
|
|||||||
namespace: Namespace::Value,
|
namespace: Namespace::Value,
|
||||||
visibility: Visibility::FilePrivate,
|
visibility: Visibility::FilePrivate,
|
||||||
ty: None, // Will be set by TypeChecker
|
ty: None, // Will be set by TypeChecker
|
||||||
|
is_host: false,
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ pub enum SymbolKind {
|
|||||||
Local,
|
Local,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Namespace {
|
pub enum Namespace {
|
||||||
Type,
|
Type,
|
||||||
Value,
|
Value,
|
||||||
@ -32,6 +32,7 @@ pub struct Symbol {
|
|||||||
pub namespace: Namespace,
|
pub namespace: Namespace,
|
||||||
pub visibility: Visibility,
|
pub visibility: Visibility,
|
||||||
pub ty: Option<PbsType>,
|
pub ty: Option<PbsType>,
|
||||||
|
pub is_host: bool,
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use crate::frontends::pbs::ast::*;
|
|||||||
use crate::frontends::pbs::symbols::*;
|
use crate::frontends::pbs::symbols::*;
|
||||||
use crate::frontends::pbs::types::PbsType;
|
use crate::frontends::pbs::types::PbsType;
|
||||||
use crate::frontends::pbs::resolver::ModuleProvider;
|
use crate::frontends::pbs::resolver::ModuleProvider;
|
||||||
|
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct TypeChecker<'a> {
|
pub struct TypeChecker<'a> {
|
||||||
@ -13,6 +14,7 @@ pub struct TypeChecker<'a> {
|
|||||||
mut_bindings: Vec<HashMap<String, bool>>,
|
mut_bindings: Vec<HashMap<String, bool>>,
|
||||||
current_return_type: Option<PbsType>,
|
current_return_type: Option<PbsType>,
|
||||||
diagnostics: Vec<Diagnostic>,
|
diagnostics: Vec<Diagnostic>,
|
||||||
|
contract_registry: ContractRegistry,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TypeChecker<'a> {
|
impl<'a> TypeChecker<'a> {
|
||||||
@ -27,6 +29,7 @@ impl<'a> TypeChecker<'a> {
|
|||||||
mut_bindings: Vec::new(),
|
mut_bindings: Vec::new(),
|
||||||
current_return_type: None,
|
current_return_type: None,
|
||||||
diagnostics: Vec::new(),
|
diagnostics: Vec::new(),
|
||||||
|
contract_registry: ContractRegistry::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +143,24 @@ impl<'a> TypeChecker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType {
|
fn check_member_access(&mut self, n: &MemberAccessNode) -> PbsType {
|
||||||
|
if let Node::Ident(id) = &*n.object {
|
||||||
|
// Check if it's a known host contract
|
||||||
|
if let Some(sym) = self.module_symbols.type_symbols.get(&id.name) {
|
||||||
|
if sym.kind == SymbolKind::Contract && sym.is_host {
|
||||||
|
// Check if the method exists in registry
|
||||||
|
if self.contract_registry.resolve(&id.name, &n.member).is_none() {
|
||||||
|
self.diagnostics.push(Diagnostic {
|
||||||
|
level: DiagnosticLevel::Error,
|
||||||
|
code: Some("E_RESOLVE_UNDEFINED".to_string()),
|
||||||
|
message: format!("Method '{}' not found on host contract '{}'", n.member, id.name),
|
||||||
|
span: Some(n.span),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return PbsType::Void;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let _obj_ty = self.check_node(&n.object);
|
let _obj_ty = self.check_node(&n.object);
|
||||||
// For v0, we assume member access on a host contract is valid and return a dummy type
|
// For v0, we assume member access on a host contract is valid and return a dummy type
|
||||||
// or resolve it if we have contract info.
|
// or resolve it if we have contract info.
|
||||||
|
|||||||
@ -7,6 +7,8 @@ use prometeu_compiler::ir_core;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_host_contract_call_lowering() {
|
fn test_host_contract_call_lowering() {
|
||||||
let code = "
|
let code = "
|
||||||
|
declare contract Gfx host {}
|
||||||
|
declare contract Log host {}
|
||||||
fn main() {
|
fn main() {
|
||||||
Gfx.clear(0);
|
Gfx.clear(0);
|
||||||
Log.write(\"Hello\");
|
Log.write(\"Hello\");
|
||||||
@ -31,9 +33,61 @@ fn test_host_contract_call_lowering() {
|
|||||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001))));
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x5001))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contract_call_without_host_lowering() {
|
||||||
|
let code = "
|
||||||
|
declare contract Gfx {}
|
||||||
|
fn main() {
|
||||||
|
Gfx.clear(0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let mut parser = Parser::new(code, 0);
|
||||||
|
let ast = parser.parse_file().expect("Failed to parse");
|
||||||
|
|
||||||
|
let mut collector = SymbolCollector::new();
|
||||||
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||||
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||||
|
|
||||||
|
let lowerer = Lowerer::new(&module_symbols);
|
||||||
|
let program = lowerer.lower_file(&ast, "test");
|
||||||
|
|
||||||
|
let func = &program.modules[0].functions[0];
|
||||||
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||||
|
|
||||||
|
// Should NOT be a syscall if not declared as host
|
||||||
|
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shadowed_contract_call_lowering() {
|
||||||
|
let code = "
|
||||||
|
declare contract Gfx host {}
|
||||||
|
fn main() {
|
||||||
|
let Gfx = 10;
|
||||||
|
Gfx.clear(0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let mut parser = Parser::new(code, 0);
|
||||||
|
let ast = parser.parse_file().expect("Failed to parse");
|
||||||
|
|
||||||
|
let mut collector = SymbolCollector::new();
|
||||||
|
let (type_symbols, value_symbols) = collector.collect(&ast).expect("Failed to collect symbols");
|
||||||
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
||||||
|
|
||||||
|
let lowerer = Lowerer::new(&module_symbols);
|
||||||
|
let program = lowerer.lower_file(&ast, "test");
|
||||||
|
|
||||||
|
let func = &program.modules[0].functions[0];
|
||||||
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||||
|
|
||||||
|
// Should NOT be a syscall because Gfx is shadowed by a local
|
||||||
|
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_contract_call_lowering() {
|
fn test_invalid_contract_call_lowering() {
|
||||||
let code = "
|
let code = "
|
||||||
|
declare contract Gfx host {}
|
||||||
fn main() {
|
fn main() {
|
||||||
Gfx.invalidMethod(0);
|
Gfx.invalidMethod(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,6 +116,7 @@ fn test_visibility_error() {
|
|||||||
namespace: Namespace::Type,
|
namespace: Namespace::Type,
|
||||||
visibility: Visibility::FilePrivate,
|
visibility: Visibility::FilePrivate,
|
||||||
ty: None,
|
ty: None,
|
||||||
|
is_host: false,
|
||||||
span: Span::new(1, 0, 0),
|
span: Span::new(1, 0, 0),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
@ -160,6 +161,7 @@ fn test_import_resolution() {
|
|||||||
namespace: Namespace::Type,
|
namespace: Namespace::Type,
|
||||||
visibility: Visibility::Pub,
|
visibility: Visibility::Pub,
|
||||||
ty: None,
|
ty: None,
|
||||||
|
is_host: false,
|
||||||
span: Span::new(1, 0, 0),
|
span: Span::new(1, 0, 0),
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,9 @@ fn check_code(code: &str) -> Result<(), String> {
|
|||||||
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
|
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
|
||||||
errors.push(format!("{}: {}", code, diag.message));
|
errors.push(format!("{}: {}", code, diag.message));
|
||||||
}
|
}
|
||||||
Err(errors.join(", "))
|
let err_msg = errors.join(", ");
|
||||||
|
println!("Compilation failed: {}", err_msg);
|
||||||
|
Err(err_msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,6 +103,31 @@ fn test_unknown_type() {
|
|||||||
assert!(res.unwrap_err().contains("E_TYPE_UNKNOWN_TYPE"));
|
assert!(res.unwrap_err().contains("E_TYPE_UNKNOWN_TYPE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_host_method() {
|
||||||
|
let code = "
|
||||||
|
declare contract Gfx host {}
|
||||||
|
fn main() {
|
||||||
|
Gfx.invalidMethod();
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let res = check_code(code);
|
||||||
|
assert!(res.is_err());
|
||||||
|
assert!(res.unwrap_err().contains("E_RESOLVE_UNDEFINED"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_valid_host_method() {
|
||||||
|
let code = "
|
||||||
|
declare contract Gfx host {}
|
||||||
|
fn main() {
|
||||||
|
Gfx.clear(0);
|
||||||
|
}
|
||||||
|
";
|
||||||
|
let res = check_code(code);
|
||||||
|
assert!(res.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_void_return_ok() {
|
fn test_void_return_ok() {
|
||||||
let code = "fn main() { return; }";
|
let code = "fn main() { return; }";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user