2831 lines
115 KiB
Rust
2831 lines
115 KiB
Rust
use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, Severity};
|
|
use crate::common::spans::Span;
|
|
use crate::frontends::pbs::ast::*;
|
|
use crate::frontends::pbs::contracts::ContractRegistry;
|
|
use crate::frontends::pbs::symbols::*;
|
|
use crate::frontends::pbs::resolver::ModuleProvider;
|
|
use crate::frontends::pbs::types::PbsType;
|
|
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId, SigId};
|
|
use crate::ir_core::{Block, ConstPool, Function, Instr, InstrKind, Module, Param, Program, Terminator, Type};
|
|
use crate::ir_core::signature::{Signature, global_signature_interner};
|
|
use prometeu_analysis::{NameInterner, NodeId};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Clone)]
|
|
struct LocalInfo {
|
|
slot: u32,
|
|
ty: Type,
|
|
}
|
|
|
|
pub struct Lowerer<'a> {
|
|
arena: &'a AstArena,
|
|
module_symbols: &'a ModuleSymbols,
|
|
imported_symbols: &'a ModuleSymbols,
|
|
module_provider: &'a dyn ModuleProvider,
|
|
interner: &'a NameInterner,
|
|
program: Program,
|
|
current_function: Option<Function>,
|
|
current_block: Option<Block>,
|
|
next_block_id: u32,
|
|
next_func_id: u32,
|
|
next_type_id: u32,
|
|
local_vars: Vec<HashMap<String, LocalInfo>>,
|
|
function_ids: HashMap<String, FunctionId>,
|
|
type_ids: HashMap<String, TypeId>,
|
|
struct_slots: HashMap<String, u32>,
|
|
struct_constructors: HashMap<String, HashMap<String, NodeId>>,
|
|
type_constants: HashMap<String, HashMap<String, NodeId>>,
|
|
current_type_context: Option<String>,
|
|
user_struct_field_offsets: HashMap<String, HashMap<String, u32>>,
|
|
user_struct_field_types: HashMap<String, HashMap<String, Type>>,
|
|
method_self_slot: Option<u32>,
|
|
contract_registry: ContractRegistry,
|
|
diagnostics: Vec<Diagnostic>,
|
|
max_slots_used: u32,
|
|
current_span: Option<Span>,
|
|
import_bindings: HashMap<String, (String, String)>,
|
|
}
|
|
|
|
impl<'a> Lowerer<'a> {
|
|
fn sig_from_pbs_fn(&self, pbs: &PbsType) -> Option<SigId> {
|
|
if let PbsType::Function { params, return_type } = pbs {
|
|
let mut core_params = Vec::with_capacity(params.len());
|
|
for p in params {
|
|
core_params.push(self.convert_pbs_type(p));
|
|
}
|
|
let core_ret = self.convert_pbs_type(return_type);
|
|
let sig = Signature { params: core_params, return_type: core_ret };
|
|
// Global, deterministic interner across the compiler process
|
|
let mut guard = global_signature_interner().lock().unwrap();
|
|
Some(guard.intern(sig))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
// #[inline]
|
|
// fn hash_tag_u16(s: &str) -> u16 {
|
|
// // FNV-1a 16-bit (simple, deterministic, allows small collisions)
|
|
// let mut hash: u16 = 0x811C; // offset basis (truncated)
|
|
// let prime: u16 = 0x0101; // 257
|
|
// for &b in s.as_bytes() {
|
|
// hash ^= b as u16;
|
|
// hash = hash.wrapping_mul(prime);
|
|
// }
|
|
// hash
|
|
// }
|
|
pub fn new(
|
|
arena: &'a AstArena,
|
|
module_symbols: &'a ModuleSymbols,
|
|
imported_symbols: &'a ModuleSymbols,
|
|
module_provider: &'a dyn ModuleProvider,
|
|
interner: &'a NameInterner,
|
|
) -> Self {
|
|
let mut field_offsets = HashMap::new();
|
|
field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation
|
|
|
|
let mut struct_slots = HashMap::new();
|
|
struct_slots.insert("Color".to_string(), 1);
|
|
struct_slots.insert("ButtonState".to_string(), 4);
|
|
// New service-based input returns `Button` (same layout as legacy ButtonState: 4 slots)
|
|
struct_slots.insert("Button".to_string(), 4);
|
|
struct_slots.insert("Pad".to_string(), 48);
|
|
struct_slots.insert("Touch".to_string(), 6);
|
|
|
|
Self {
|
|
arena,
|
|
module_symbols,
|
|
imported_symbols,
|
|
module_provider,
|
|
interner,
|
|
program: Program {
|
|
const_pool: ConstPool::new(),
|
|
modules: Vec::new(),
|
|
field_offsets,
|
|
field_types: HashMap::new(),
|
|
},
|
|
current_function: None,
|
|
current_block: None,
|
|
next_block_id: 0,
|
|
next_func_id: 1,
|
|
next_type_id: 1,
|
|
local_vars: Vec::new(),
|
|
function_ids: HashMap::new(),
|
|
type_ids: HashMap::new(),
|
|
struct_slots,
|
|
struct_constructors: HashMap::new(),
|
|
type_constants: HashMap::new(),
|
|
current_type_context: None,
|
|
user_struct_field_offsets: HashMap::new(),
|
|
user_struct_field_types: HashMap::new(),
|
|
method_self_slot: None,
|
|
contract_registry: ContractRegistry::new(),
|
|
diagnostics: Vec::new(),
|
|
max_slots_used: 0,
|
|
current_span: None,
|
|
import_bindings: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
fn error(&mut self, code: &str, message: String, span: Span) {
|
|
self.diagnostics.push(Diagnostic {
|
|
severity: Severity::Error,
|
|
code: code.to_string(),
|
|
message,
|
|
span,
|
|
related: Vec::new(),
|
|
});
|
|
}
|
|
|
|
pub fn lower_file(mut self, root: NodeId, module_name: &str) -> Result<Program, DiagnosticBundle> {
|
|
// Ensure per-module function id space starts clean and small
|
|
self.next_func_id = 1;
|
|
self.function_ids.clear();
|
|
let file = match self.arena.kind(root) {
|
|
NodeKind::File(file) => file,
|
|
_ => {
|
|
return Err(DiagnosticBundle::error(
|
|
"E_LOWER_INVALID_ROOT",
|
|
"Expected File node as root".to_string(),
|
|
self.arena.span(root),
|
|
))
|
|
}
|
|
};
|
|
|
|
// Construir mapa de imports: para cada import do arquivo, se vier do formato
|
|
// "@alias:module", associe cada símbolo importado em `spec.path` ao par (alias,module).
|
|
self.import_bindings.clear();
|
|
for &imp in &file.imports {
|
|
if let NodeKind::Import(imp_node) = self.arena.kind(imp) {
|
|
let from = imp_node.from.as_str();
|
|
if let Some(rest) = from.strip_prefix('@') {
|
|
// Espera-se formato @alias:module_path
|
|
let mut parts = rest.splitn(2, ':');
|
|
if let (Some(alias), Some(module_path)) = (parts.next(), parts.next()) {
|
|
if let NodeKind::ImportSpec(spec) = self.arena.kind(imp_node.spec) {
|
|
for name in &spec.path {
|
|
let sym = self.interner.resolve(*name).to_string();
|
|
self.import_bindings.insert(sym, (alias.to_string(), module_path.to_string()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Pre-scan for function declarations to assign IDs
|
|
for decl in &file.decls {
|
|
match self.arena.kind(*decl) {
|
|
NodeKind::FnDecl(n) => {
|
|
let id = FunctionId(self.next_func_id);
|
|
self.next_func_id += 1;
|
|
self.function_ids
|
|
.insert(self.interner.resolve(n.name).to_string(), id);
|
|
}
|
|
NodeKind::ServiceDecl(n) => {
|
|
let service_name = self.interner.resolve(n.name).to_string();
|
|
for m in &n.members {
|
|
if let NodeKind::ServiceFnDecl(decl) = self.arena.kind(*m) {
|
|
let full_name = format!("{}.{}", service_name, self.interner.resolve(decl.name));
|
|
let id = FunctionId(self.next_func_id);
|
|
self.next_func_id += 1;
|
|
self.function_ids.insert(full_name, id);
|
|
}
|
|
}
|
|
}
|
|
NodeKind::TypeDecl(n) => {
|
|
let id = TypeId(self.next_type_id);
|
|
self.next_type_id += 1;
|
|
self.type_ids
|
|
.insert(self.interner.resolve(n.name).to_string(), id);
|
|
|
|
// Pré-scan de métodos dentro do tipo (apenas FnDecl com corpo)
|
|
let type_name = self.interner.resolve(n.name).to_string();
|
|
if let Some(body_id) = n.body {
|
|
if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) {
|
|
for m in &tb.methods {
|
|
if let NodeKind::FnDecl(md) = self.arena.kind(*m) {
|
|
let full_name = format!("{}.{}", type_name, self.interner.resolve(md.name));
|
|
let id = FunctionId(self.next_func_id);
|
|
self.next_func_id += 1;
|
|
self.function_ids.insert(full_name, id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Second pre-scan: calculate struct slots (recursive)
|
|
let mut struct_nodes = HashMap::new();
|
|
for decl in &file.decls {
|
|
if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) {
|
|
if n.type_kind == "struct" {
|
|
struct_nodes.insert(self.interner.resolve(n.name).to_string(), *decl);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut changed = true;
|
|
while changed {
|
|
changed = false;
|
|
for (name, node_id) in &struct_nodes {
|
|
if !self.struct_slots.contains_key(name) {
|
|
let mut slots = 0;
|
|
let mut all_known = true;
|
|
let node = match self.arena.kind(*node_id) {
|
|
NodeKind::TypeDecl(node) => node,
|
|
_ => continue,
|
|
};
|
|
for param in &node.params {
|
|
let member_ty = self.lower_type_node(param.ty);
|
|
match &member_ty {
|
|
Type::Struct(sname) => {
|
|
if let Some(s_slots) = self.get_builtin_struct_slots(sname) {
|
|
slots += s_slots;
|
|
} else if let Some(s_slots) = self.struct_slots.get(sname) {
|
|
slots += s_slots;
|
|
} else {
|
|
all_known = false;
|
|
break;
|
|
}
|
|
}
|
|
_ => slots += self.get_type_slots(&member_ty),
|
|
}
|
|
}
|
|
if all_known {
|
|
self.struct_slots.insert(name.clone(), slots);
|
|
changed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for decl in &file.decls {
|
|
if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) {
|
|
let type_name = self.interner.resolve(n.name).to_string();
|
|
let mut constants = HashMap::new();
|
|
for c in &n.constants {
|
|
if let NodeKind::ConstantDecl(constant) = self.arena.kind(*c) {
|
|
constants.insert(
|
|
self.interner.resolve(constant.name).to_string(),
|
|
constant.value,
|
|
);
|
|
}
|
|
}
|
|
self.type_constants.insert(type_name.clone(), constants);
|
|
|
|
let mut ctors = HashMap::new();
|
|
|
|
// Default constructor: TypeName(...)
|
|
if n.type_kind == "struct" {
|
|
if let Some(default_ctor) = n.constructors.first() {
|
|
if let NodeKind::ConstructorDecl(_) = self.arena.kind(*default_ctor) {
|
|
ctors.insert(type_name.clone(), *default_ctor);
|
|
}
|
|
}
|
|
}
|
|
|
|
for ctor in &n.constructors {
|
|
if let NodeKind::ConstructorDecl(ctor_node) = self.arena.kind(*ctor) {
|
|
ctors.insert(self.interner.resolve(ctor_node.name).to_string(), *ctor);
|
|
}
|
|
}
|
|
self.struct_constructors.insert(type_name, ctors);
|
|
}
|
|
}
|
|
|
|
// Calcular offsets e tipos de campos para structs de usuário
|
|
for decl in &file.decls {
|
|
if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) {
|
|
if n.type_kind == "struct" {
|
|
let type_name = self.interner.resolve(n.name).to_string();
|
|
let mut offsets = HashMap::new();
|
|
let mut types = HashMap::new();
|
|
let mut acc: u32 = 0;
|
|
|
|
// Campos do cabeçalho
|
|
for p in &n.params {
|
|
let ty = self.lower_type_node(p.ty);
|
|
let slots = self.get_type_slots(&ty);
|
|
offsets.insert(self.interner.resolve(p.name).to_string(), acc);
|
|
types.insert(self.interner.resolve(p.name).to_string(), ty);
|
|
acc += slots;
|
|
}
|
|
// Campos adicionais (members) no corpo
|
|
if let Some(body_id) = n.body {
|
|
if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) {
|
|
for m in &tb.members {
|
|
let ty = self.lower_type_node(m.ty);
|
|
let slots = self.get_type_slots(&ty);
|
|
offsets.insert(self.interner.resolve(m.name).to_string(), acc);
|
|
types.insert(self.interner.resolve(m.name).to_string(), ty);
|
|
acc += slots;
|
|
}
|
|
}
|
|
}
|
|
|
|
self.user_struct_field_offsets.insert(type_name.clone(), offsets);
|
|
self.user_struct_field_types.insert(type_name, types);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut module = Module {
|
|
name: module_name.to_string(),
|
|
functions: Vec::new(),
|
|
};
|
|
|
|
for decl in &file.decls {
|
|
match self.arena.kind(*decl) {
|
|
NodeKind::FnDecl(_) => {
|
|
let func = self.lower_function(*decl).map_err(|_| DiagnosticBundle {
|
|
diagnostics: self.diagnostics.clone(),
|
|
})?;
|
|
module.functions.push(func);
|
|
}
|
|
NodeKind::ServiceDecl(n) => {
|
|
let service_name = self.interner.resolve(n.name).to_string();
|
|
for m in &n.members {
|
|
if let NodeKind::ServiceFnDecl(_) = self.arena.kind(*m) {
|
|
let func = self.lower_service_function(&service_name, *m).map_err(|_| DiagnosticBundle {
|
|
diagnostics: self.diagnostics.clone(),
|
|
})?;
|
|
module.functions.push(func);
|
|
}
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
// Baixar métodos de structs
|
|
for decl in &file.decls {
|
|
if let NodeKind::TypeDecl(n) = self.arena.kind(*decl) {
|
|
let type_name = self.interner.resolve(n.name).to_string();
|
|
if let Some(body_id) = n.body {
|
|
if let NodeKind::TypeBody(tb) = self.arena.kind(body_id) {
|
|
for m in &tb.methods {
|
|
if let NodeKind::FnDecl(_) = self.arena.kind(*m) {
|
|
let func = self.lower_method_function(&type_name, *m).map_err(|_| DiagnosticBundle {
|
|
diagnostics: self.diagnostics.clone(),
|
|
})?;
|
|
module.functions.push(func);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.program.modules.push(module);
|
|
Ok(self.program)
|
|
}
|
|
|
|
fn lower_function(&mut self, node: NodeId) -> Result<Function, ()> {
|
|
let n = match self.arena.kind(node) {
|
|
NodeKind::FnDecl(n) => n,
|
|
_ => return Err(()),
|
|
};
|
|
let func_name = self.interner.resolve(n.name).to_string();
|
|
let func_id = *self.function_ids.get(&func_name).unwrap();
|
|
self.next_block_id = 0;
|
|
self.local_vars = vec![HashMap::new()];
|
|
self.max_slots_used = 0;
|
|
|
|
let mut params = Vec::new();
|
|
let mut local_types = HashMap::new();
|
|
let mut param_slots = 0u32;
|
|
for param in &n.params {
|
|
let ty = self.lower_type_node(param.ty);
|
|
let slots = self.get_type_slots(&ty);
|
|
params.push(Param {
|
|
name: self.interner.resolve(param.name).to_string(),
|
|
ty: ty.clone(),
|
|
});
|
|
self.local_vars[0].insert(
|
|
self.interner.resolve(param.name).to_string(),
|
|
LocalInfo {
|
|
slot: param_slots,
|
|
ty: ty.clone(),
|
|
},
|
|
);
|
|
for i in 0..slots {
|
|
local_types.insert(param_slots + i, ty.clone());
|
|
}
|
|
param_slots += slots;
|
|
}
|
|
self.max_slots_used = param_slots;
|
|
|
|
let ret_ty = if let Some(ret) = n.ret {
|
|
self.lower_type_node(ret)
|
|
} else {
|
|
Type::Void
|
|
};
|
|
let return_slots = self.get_type_slots(&ret_ty);
|
|
|
|
// Build Signature and intern to SigId
|
|
let func_sig = Signature {
|
|
params: params.iter().map(|p| p.ty.clone()).collect(),
|
|
return_type: ret_ty.clone(),
|
|
};
|
|
let sig_id = {
|
|
let mut interner = global_signature_interner().lock().unwrap();
|
|
interner.intern(func_sig)
|
|
};
|
|
|
|
let func = Function {
|
|
id: func_id,
|
|
name: func_name,
|
|
sig: sig_id,
|
|
params,
|
|
return_type: ret_ty,
|
|
blocks: Vec::new(),
|
|
local_types,
|
|
param_slots: param_slots as u16,
|
|
local_slots: 0,
|
|
return_slots: return_slots as u16,
|
|
};
|
|
|
|
self.current_function = Some(func);
|
|
self.start_block();
|
|
self.lower_node(n.body)?;
|
|
|
|
// Ensure every function ends with a return if not already terminated
|
|
if let Some(mut block) = self.current_block.take() {
|
|
if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) {
|
|
block.terminator = Terminator::Return;
|
|
}
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
|
|
let mut final_func = self.current_function.take().unwrap();
|
|
final_func.local_slots = (self.max_slots_used - param_slots) as u16;
|
|
Ok(final_func)
|
|
}
|
|
|
|
fn lower_service_function(&mut self, service_name: &str, node: NodeId) -> Result<Function, ()> {
|
|
let n = match self.arena.kind(node) {
|
|
NodeKind::ServiceFnDecl(n) => n,
|
|
_ => return Err(()),
|
|
};
|
|
let method_name = self.interner.resolve(n.name).to_string();
|
|
let full_name = format!("{}.{}", service_name, method_name);
|
|
let func_id = match self.function_ids.get(&full_name) {
|
|
Some(id) => *id,
|
|
None => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("Missing function id for service method '{}'", full_name),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
self.next_block_id = 0;
|
|
self.local_vars = vec![HashMap::new()];
|
|
self.max_slots_used = 0;
|
|
|
|
let mut params = Vec::new();
|
|
let mut local_types = HashMap::new();
|
|
let mut param_slots = 0u32;
|
|
for p in &n.params {
|
|
let ty = self.lower_type_node(p.ty);
|
|
let slots = self.get_type_slots(&ty);
|
|
params.push(Param { name: self.interner.resolve(p.name).to_string(), ty: ty.clone() });
|
|
self.local_vars[0].insert(
|
|
self.interner.resolve(p.name).to_string(),
|
|
LocalInfo { slot: param_slots, ty: ty.clone() },
|
|
);
|
|
for i in 0..slots { local_types.insert(param_slots + i, ty.clone()); }
|
|
param_slots += slots;
|
|
}
|
|
self.max_slots_used = param_slots;
|
|
|
|
let ret_ty = self.lower_type_node(n.ret);
|
|
let return_slots = self.get_type_slots(&ret_ty);
|
|
// Build Signature and intern to SigId (espelha lower_function)
|
|
let func_sig = Signature {
|
|
params: params.iter().map(|p| p.ty.clone()).collect(),
|
|
return_type: ret_ty.clone(),
|
|
};
|
|
let sig_id = {
|
|
let mut interner = global_signature_interner().lock().unwrap();
|
|
interner.intern(func_sig)
|
|
};
|
|
// Inicializa a função atual (espelha lower_function)
|
|
let func = Function {
|
|
id: func_id,
|
|
// Nome público da função no módulo: use apenas o nome do método.
|
|
// O module_path fará a desambiguação durante export/link.
|
|
name: method_name,
|
|
sig: sig_id,
|
|
params,
|
|
return_type: ret_ty,
|
|
blocks: Vec::new(),
|
|
local_types,
|
|
param_slots: param_slots as u16,
|
|
local_slots: 0,
|
|
return_slots: return_slots as u16,
|
|
};
|
|
|
|
// Registrar como função corrente para que start_block/lower_node
|
|
// acumulem instruções corretamente.
|
|
self.current_function = Some(func);
|
|
self.start_block();
|
|
self.lower_node(n.body)?;
|
|
|
|
// Garantir terminador e empurrar bloco final
|
|
if let Some(mut block) = self.current_block.take() {
|
|
if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) {
|
|
block.terminator = Terminator::Return;
|
|
}
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
|
|
// Finalizar função: calcular local_slots e devolver
|
|
let mut final_func = self.current_function.take().unwrap();
|
|
final_func.local_slots = (self.max_slots_used - param_slots) as u16;
|
|
Ok(final_func)
|
|
}
|
|
|
|
fn lower_node(&mut self, node: NodeId) -> Result<(), ()> {
|
|
let old_span = self.current_span.clone();
|
|
self.current_span = Some(self.arena.span(node));
|
|
|
|
let res = match self.arena.kind(node) {
|
|
NodeKind::Block(n) => self.lower_block(node, n),
|
|
NodeKind::LetStmt(n) => self.lower_let_stmt(node, n),
|
|
NodeKind::ExprStmt(n) => self.lower_node(n.expr),
|
|
NodeKind::ReturnStmt(n) => self.lower_return_stmt(node, n),
|
|
NodeKind::IntLit(n) => {
|
|
let id = self.program.const_pool.add_int(n.value);
|
|
self.emit(InstrKind::PushConst(id));
|
|
Ok(())
|
|
}
|
|
NodeKind::FloatLit(n) => {
|
|
let id = self.program.const_pool.add_float(n.value);
|
|
self.emit(InstrKind::PushConst(id));
|
|
Ok(())
|
|
}
|
|
NodeKind::StringLit(n) => {
|
|
let id = self.program.const_pool.add_string(n.value.clone());
|
|
self.emit(InstrKind::PushConst(id));
|
|
Ok(())
|
|
}
|
|
NodeKind::BoundedLit(n) => {
|
|
self.emit(InstrKind::PushBounded(n.value));
|
|
Ok(())
|
|
}
|
|
NodeKind::Ident(n) => self.lower_ident(node, n),
|
|
NodeKind::MemberAccess(n) => self.lower_member_access(node, n),
|
|
NodeKind::Call(n) => self.lower_call(node, n),
|
|
NodeKind::Binary(n) => self.lower_binary(node, n),
|
|
NodeKind::Unary(n) => self.lower_unary(node, n),
|
|
NodeKind::IfExpr(n) => self.lower_if_expr(node, n),
|
|
NodeKind::WhenExpr(n) => self.lower_when_expr(node, n),
|
|
NodeKind::Alloc(n) => self.lower_alloc(node, n),
|
|
NodeKind::Mutate(n) => self.lower_mutate(node, n),
|
|
NodeKind::Borrow(n) => self.lower_borrow(node, n),
|
|
NodeKind::Peek(n) => self.lower_peek(node, n),
|
|
_ => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("Lowering for node kind {:?} not supported", self.arena.kind(node)),
|
|
self.arena.span(node),
|
|
);
|
|
Err(())
|
|
}
|
|
};
|
|
|
|
self.current_span = old_span;
|
|
res
|
|
}
|
|
|
|
fn lower_alloc(&mut self, _node: NodeId, n: &AllocNodeArena) -> Result<(), ()> {
|
|
let (ty_id, slots) = self.get_type_id_and_slots(n.ty)?;
|
|
self.emit(InstrKind::Alloc { ty: ty_id, slots });
|
|
Ok(())
|
|
}
|
|
|
|
fn get_type_id_and_slots(&mut self, node: NodeId) -> Result<(TypeId, u32), ()> {
|
|
match self.arena.kind(node) {
|
|
NodeKind::TypeName(n) => {
|
|
let name = self.interner.resolve(n.name);
|
|
let slots = self.struct_slots.get(name).cloned().unwrap_or(1);
|
|
let id = self.get_or_create_type_id(name);
|
|
Ok((id, slots))
|
|
}
|
|
NodeKind::TypeApp(ta) if self.interner.resolve(ta.base) == "array" => {
|
|
let size = if ta.args.len() > 1 {
|
|
if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) {
|
|
il.value as u32
|
|
} else {
|
|
1
|
|
}
|
|
} else {
|
|
1
|
|
};
|
|
let elem_ty = self.lower_type_node(ta.args[0]);
|
|
let name = format!("array<{}>[{}]", elem_ty, size);
|
|
let id = self.get_or_create_type_id(&name);
|
|
Ok((id, size))
|
|
}
|
|
_ => {
|
|
self.error(
|
|
"E_RESOLVE_UNDEFINED",
|
|
format!("Unknown type in allocation: {:?}", self.arena.kind(node)),
|
|
self.arena.span(node),
|
|
);
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_or_create_type_id(&mut self, name: &str) -> TypeId {
|
|
if let Some(id) = self.type_ids.get(name) {
|
|
*id
|
|
} else {
|
|
let id = TypeId(self.next_type_id);
|
|
self.next_type_id += 1;
|
|
self.type_ids.insert(name.to_string(), id);
|
|
id
|
|
}
|
|
}
|
|
|
|
fn lower_peek(&mut self, _node: NodeId, n: &PeekNodeArena) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
|
|
self.emit(InstrKind::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(InstrKind::BeginPeek { gate: ValueId(gate_slot) });
|
|
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int);
|
|
self.emit(InstrKind::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(InstrKind::EndPeek);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_borrow(&mut self, _node: NodeId, n: &BorrowNodeArena) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
|
|
self.emit(InstrKind::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(InstrKind::BeginBorrow { gate: ValueId(gate_slot) });
|
|
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int);
|
|
self.emit(InstrKind::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(InstrKind::EndBorrow);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_mutate(&mut self, _node: NodeId, n: &MutateNodeArena) -> Result<(), ()> {
|
|
// 1. Evaluate target (gate)
|
|
self.lower_node(n.target)?;
|
|
|
|
// 2. Preserve gate identity
|
|
let gate_slot = self.add_local_to_scope(format!("$gate_{}", self.get_next_local_slot()), Type::Int);
|
|
self.emit(InstrKind::SetLocal(gate_slot));
|
|
|
|
// 3. Begin Operation
|
|
self.emit(InstrKind::BeginMutate { gate: ValueId(gate_slot) });
|
|
self.emit(InstrKind::GateLoadField { gate: ValueId(gate_slot), field: FieldId(0) });
|
|
|
|
// 4. Bind view to local
|
|
self.local_vars.push(HashMap::new());
|
|
let view_slot = self.add_local_to_scope(self.interner.resolve(n.binding).to_string(), Type::Int);
|
|
self.emit(InstrKind::SetLocal(view_slot));
|
|
|
|
// 5. Body
|
|
self.lower_node(n.body)?;
|
|
|
|
// 6. End Operation
|
|
self.emit(InstrKind::EndMutate);
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_block(&mut self, _node: NodeId, n: &BlockNodeArena) -> Result<(), ()> {
|
|
self.local_vars.push(HashMap::new());
|
|
for stmt in &n.stmts {
|
|
// Lower the statement normally
|
|
self.lower_node(*stmt)?;
|
|
|
|
// Guardrail: if the statement is a standalone Call that returns values,
|
|
// discard them to keep the stack balanced. This is essential for host
|
|
// contract calls that return multi-slot structs (e.g., Button=4 slots).
|
|
if let NodeKind::Call(call) = self.arena.kind(*stmt) {
|
|
// Try to compute return slots conservatively
|
|
let mut return_slots: u32 = 0;
|
|
|
|
match self.arena.kind(call.callee) {
|
|
NodeKind::MemberAccess(ma) => {
|
|
// Static contract call: Contract.method(...)
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
let contract_name = self.interner.resolve(obj_id.name);
|
|
let method_name = self.interner.resolve(ma.member);
|
|
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
|
|
let ty = self.convert_pbs_type(&method.return_type);
|
|
return_slots = self.get_type_slots(&ty);
|
|
}
|
|
}
|
|
}
|
|
NodeKind::Ident(id_node) => {
|
|
// Calling a local function or imported symbol — attempt best effort via symbol table
|
|
let callee_name = self.interner.resolve(id_node.name).to_string();
|
|
if let Some(func_id) = self.function_ids.get(&callee_name) {
|
|
let _ = func_id; // unresolved return info in v0 → assume 0 (no discard)
|
|
} else {
|
|
// ImportCall or unknown — assume 0 (safe)
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if return_slots > 0 {
|
|
for _ in 0..return_slots {
|
|
self.emit(InstrKind::Pop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if let Some(tail) = n.tail {
|
|
self.lower_node(tail)?;
|
|
}
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_let_stmt(&mut self, _node: NodeId, n: &LetStmtNodeArena) -> Result<(), ()> {
|
|
self.lower_node(n.init)?;
|
|
|
|
let ty = if let Some(ty_node) = n.ty {
|
|
self.lower_type_node(ty_node)
|
|
} else {
|
|
// Very basic inference for host calls
|
|
if let NodeKind::Call(call) = self.arena.kind(n.init) {
|
|
if let NodeKind::MemberAccess(ma) = self.arena.kind(call.callee) {
|
|
if let NodeKind::Ident(obj) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj.name);
|
|
let member_name = self.interner.resolve(ma.member);
|
|
// 1) Prefer exact contract registry mapping (Pad/Touch services, etc.)
|
|
if let Some(method) = self.contract_registry.get_method(obj_name, member_name) {
|
|
self.convert_pbs_type(&method.return_type)
|
|
} else {
|
|
// 2) Legacy snapshot helpers
|
|
match (obj_name, member_name) {
|
|
("Input", "pad") => Type::Struct("Pad".to_string()),
|
|
("Input", "touch") => Type::Struct("Touch".to_string()),
|
|
_ => Type::Int,
|
|
}
|
|
}
|
|
} else {
|
|
Type::Int
|
|
}
|
|
} else {
|
|
Type::Int
|
|
}
|
|
} else if let NodeKind::MemberAccess(ma) = self.arena.kind(n.init) {
|
|
// Inferência para constantes de struct: Type.CONST
|
|
if let NodeKind::Ident(obj) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj.name);
|
|
if let Some(consts) = self.type_constants.get(obj_name) {
|
|
let member_name = self.interner.resolve(ma.member);
|
|
if consts.contains_key(member_name) {
|
|
Type::Struct(obj_name.to_string())
|
|
} else {
|
|
Type::Int
|
|
}
|
|
} else {
|
|
Type::Int
|
|
}
|
|
} else {
|
|
Type::Int
|
|
}
|
|
} else {
|
|
Type::Int
|
|
}
|
|
};
|
|
|
|
let slots = self.get_type_slots(&ty);
|
|
let slot = self.add_local_to_scope(self.interner.resolve(n.name).to_string(), ty);
|
|
|
|
for i in (0..slots).rev() {
|
|
self.emit(InstrKind::SetLocal(slot + i));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_return_stmt(&mut self, _node: NodeId, n: &ReturnStmtNodeArena) -> Result<(), ()> {
|
|
if let Some(expr) = n.expr {
|
|
self.lower_node(expr)?;
|
|
}
|
|
self.terminate(Terminator::Return);
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_ident(&mut self, node: NodeId, n: &IdentNodeArena) -> Result<(), ()> {
|
|
let name_str = self.interner.resolve(n.name);
|
|
if let Some(info) = self.find_local(name_str) {
|
|
let slots = self.get_type_slots(&info.ty);
|
|
for i in 0..slots {
|
|
self.emit(InstrKind::GetLocal(info.slot + i));
|
|
}
|
|
Ok(())
|
|
} else {
|
|
// Se estamos no corpo de um método, permitir acessar campos de `self` implicitamente
|
|
if let (Some(struct_name), Some(self_slot)) = (self.current_type_context.as_ref(), self.method_self_slot) {
|
|
let maybe_off = self
|
|
.user_struct_field_offsets
|
|
.get(struct_name)
|
|
.and_then(|m| m.get(name_str).cloned());
|
|
if let Some(off_val) = maybe_off {
|
|
let ty = self
|
|
.user_struct_field_types
|
|
.get(struct_name)
|
|
.and_then(|m| m.get(name_str))
|
|
.cloned()
|
|
.unwrap_or(Type::Int);
|
|
let slots = self.get_type_slots(&ty);
|
|
for i in 0..slots {
|
|
self.emit(InstrKind::GetLocal(self_slot + off_val + i));
|
|
}
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// Check for special identifiers
|
|
match name_str {
|
|
"true" => {
|
|
let id = self.program.const_pool.add_int(1);
|
|
self.emit(InstrKind::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
"false" => {
|
|
let id = self.program.const_pool.add_int(0);
|
|
self.emit(InstrKind::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
"none" => {
|
|
// For now, treat none as 0. This should be refined when optional is fully implemented.
|
|
let id = self.program.const_pool.add_int(0);
|
|
self.emit(InstrKind::PushConst(id));
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
// Check if it's a function (for first-class functions if supported)
|
|
if let Some(_id) = self.function_ids.get(name_str) {
|
|
// Push function reference? Not in v0.
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("First-class function reference '{}' not supported", name_str),
|
|
self.arena.span(node),
|
|
);
|
|
Err(())
|
|
} else {
|
|
self.error(
|
|
"E_RESOLVE_UNDEFINED",
|
|
format!("Undefined identifier '{}'", name_str),
|
|
self.arena.span(node),
|
|
);
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_member_access(&mut self, node: NodeId, n: &MemberAccessNodeArena) -> Result<(), ()> {
|
|
if let NodeKind::Ident(id) = self.arena.kind(n.object) {
|
|
let type_name = self.interner.resolve(id.name);
|
|
let member_name = self.interner.resolve(n.member);
|
|
if let Some(constants) = self.type_constants.get(type_name).cloned() {
|
|
if let Some(const_val) = constants.get(member_name) {
|
|
let old_ctx = self.current_type_context.replace(type_name.to_string());
|
|
let res = self.lower_node(*const_val);
|
|
self.current_type_context = old_ctx;
|
|
return res;
|
|
}
|
|
}
|
|
|
|
if type_name == "Color" {
|
|
let val = match member_name {
|
|
"BLACK" => 0x0000,
|
|
"WHITE" => 0xFFFF,
|
|
"RED" => 0xF800,
|
|
"GREEN" => 0x07E0,
|
|
"BLUE" => 0x001F,
|
|
"MAGENTA" => 0xF81F,
|
|
"TRANSPARENT" => 0x0000,
|
|
"COLOR_KEY" => 0x0000,
|
|
_ => {
|
|
// Check if it's a method call like Color.rgb, handled in lower_call
|
|
return Ok(());
|
|
}
|
|
};
|
|
self.emit(InstrKind::PushBounded(val));
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
if let Some((slot, ty)) = self.resolve_member_access(node) {
|
|
let slots = self.get_type_slots(&ty);
|
|
for i in 0..slots {
|
|
self.emit(InstrKind::GetLocal(slot + i));
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
// Fallback: Handle member access where the object is a Call to a host contract method
|
|
// Example: Pad.a().down — object is a Call(MemberAccess(Pad, a))
|
|
if let NodeKind::Call(call_node) = self.arena.kind(n.object) {
|
|
if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) {
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) {
|
|
let contract_name = self.interner.resolve(obj_id.name);
|
|
let method_name = self.interner.resolve(inner_ma.member);
|
|
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
|
|
// Determine return type and slots
|
|
let ret_ty = self.convert_pbs_type(&method.return_type);
|
|
let slots = self.get_type_slots(&ret_ty);
|
|
if let Type::Struct(struct_name) = &ret_ty {
|
|
// Lower the call to push all slots
|
|
self.lower_call(n.object, &call_node)?;
|
|
// Store into a temp local to avoid leaving extra slots on the operand stack
|
|
let tmp_slot = self.add_local_to_scope(
|
|
format!("$tmp_{}", self.get_next_local_slot()),
|
|
ret_ty.clone(),
|
|
);
|
|
for i in (0..slots).rev() {
|
|
self.emit(InstrKind::SetLocal(tmp_slot + i));
|
|
}
|
|
// Load only the requested field
|
|
let field_name = self.interner.resolve(n.member);
|
|
let field_off = self.get_field_offset(struct_name, field_name);
|
|
self.emit(InstrKind::GetLocal(tmp_slot + field_off));
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn resolve_member_access(&self, node: NodeId) -> Option<(u32, Type)> {
|
|
match self.arena.kind(node) {
|
|
NodeKind::MemberAccess(n) => match self.arena.kind(n.object) {
|
|
NodeKind::Ident(id) => {
|
|
let name_str = self.interner.resolve(id.name);
|
|
let member_str = self.interner.resolve(n.member);
|
|
let info = self.find_local(name_str)?;
|
|
if let Type::Struct(sname) = &info.ty {
|
|
let offset = self.get_field_offset(sname, member_str);
|
|
let ty = self.get_field_type(sname, member_str);
|
|
Some((info.slot + offset, ty))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
NodeKind::MemberAccess(_) => {
|
|
let member_str = self.interner.resolve(n.member);
|
|
let (base_slot, ty) = self.resolve_member_access(n.object)?;
|
|
if let Type::Struct(sname) = &ty {
|
|
let offset = self.get_field_offset(sname, member_str);
|
|
let final_ty = self.get_field_type(sname, member_str);
|
|
Some((base_slot + offset, final_ty))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 {
|
|
if let Some(map) = self.user_struct_field_offsets.get(struct_name) {
|
|
if let Some(off) = map.get(field_name) {
|
|
return *off;
|
|
}
|
|
}
|
|
match struct_name {
|
|
// New `Button` mirrors legacy `ButtonState` offsets
|
|
"Button" => match field_name {
|
|
"pressed" => 0,
|
|
"released" => 1,
|
|
"down" => 2,
|
|
"hold_frames" => 3,
|
|
_ => 0,
|
|
},
|
|
"ButtonState" => match field_name {
|
|
"pressed" => 0,
|
|
"released" => 1,
|
|
"down" => 2,
|
|
"hold_frames" => 3,
|
|
_ => 0,
|
|
},
|
|
"Pad" => match field_name {
|
|
"up" => 0,
|
|
"down" => 4,
|
|
"left" => 8,
|
|
"right" => 12,
|
|
"a" => 16,
|
|
"b" => 20,
|
|
"x" => 24,
|
|
"y" => 28,
|
|
"l" => 32,
|
|
"r" => 36,
|
|
"start" => 40,
|
|
"select" => 44,
|
|
_ => 0,
|
|
},
|
|
"Touch" => match field_name {
|
|
"f" => 0,
|
|
"x" => 4,
|
|
"y" => 5,
|
|
_ => 0,
|
|
},
|
|
_ => 0,
|
|
}
|
|
}
|
|
|
|
fn get_field_type(&self, struct_name: &str, field_name: &str) -> Type {
|
|
if let Some(map) = self.user_struct_field_types.get(struct_name) {
|
|
if let Some(ty) = map.get(field_name) {
|
|
return ty.clone();
|
|
}
|
|
}
|
|
match struct_name {
|
|
// Pad's per-button service returns a `Button` in the new API
|
|
"Pad" => Type::Struct("Button".to_string()),
|
|
// Field types for `Button` mirror legacy `ButtonState`
|
|
"Button" => match field_name {
|
|
"hold_frames" => Type::Bounded,
|
|
_ => Type::Bool,
|
|
},
|
|
"ButtonState" => match field_name {
|
|
"hold_frames" => Type::Bounded,
|
|
_ => Type::Bool,
|
|
},
|
|
"Touch" => match field_name {
|
|
// Touch.f() now returns a `Button`
|
|
"f" => Type::Struct("Button".to_string()),
|
|
_ => Type::Int,
|
|
},
|
|
_ => Type::Int,
|
|
}
|
|
}
|
|
|
|
fn lower_call(&mut self, node: NodeId, n: &CallNodeArena) -> Result<(), ()> {
|
|
match self.arena.kind(n.callee) {
|
|
NodeKind::Ident(id_node) => {
|
|
let callee_name = self.interner.resolve(id_node.name).to_string();
|
|
// 1. Check for constructor call: TypeName(...)
|
|
let ctor = self
|
|
.struct_constructors
|
|
.get(&callee_name)
|
|
.and_then(|ctors| ctors.get(&callee_name))
|
|
.copied();
|
|
|
|
if let Some(ctor) = ctor {
|
|
return self.lower_constructor_call(ctor, &n.args);
|
|
}
|
|
|
|
if let Some(ctx) = &self.current_type_context {
|
|
let ctor = self
|
|
.struct_constructors
|
|
.get(ctx)
|
|
.and_then(|ctors| ctors.get(&callee_name))
|
|
.copied();
|
|
|
|
if let Some(ctor) = ctor {
|
|
return self.lower_constructor_call(ctor, &n.args);
|
|
}
|
|
}
|
|
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
if let Some(func_id) = self.function_ids.get(&callee_name) {
|
|
self.emit(InstrKind::Call(*func_id, n.args.len() as u32));
|
|
Ok(())
|
|
} else if let Some(sym) = self.imported_symbols.value_symbols.get(id_node.name) {
|
|
if let Some(origin) = &sym.origin {
|
|
if origin.starts_with('@') {
|
|
// Format: @dep_alias:module_path
|
|
let parts: Vec<&str> = origin[1..].splitn(2, ':').collect();
|
|
if parts.len() == 2 {
|
|
let dep_alias = parts[0].to_string();
|
|
let module_path = parts[1].to_string();
|
|
// Compute signature id from symbol type
|
|
let base_name = self.interner.resolve(sym.name).to_string();
|
|
if let Some(ty) = &sym.ty {
|
|
if let Some(sig) = self.sig_from_pbs_fn(ty) {
|
|
self.emit(InstrKind::ImportCall {
|
|
dep_alias,
|
|
module_path,
|
|
owner: None,
|
|
base_name,
|
|
sig,
|
|
arg_count: n.args.len() as u32,
|
|
});
|
|
return Ok(());
|
|
}
|
|
}
|
|
self.error(
|
|
"E_LOWER_SIGNATURE",
|
|
format!("Missing or non-function type for imported symbol '{}' to compute signature id", base_name),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!(
|
|
"Calling symbol '{}' with origin {:?} is not supported yet in v0",
|
|
callee_name,
|
|
sym.origin
|
|
),
|
|
self.arena.span(n.callee),
|
|
);
|
|
Err(())
|
|
} else {
|
|
// Try default constructor for struct calls like TypeName(...)
|
|
if let Some(ctors) = self.struct_constructors.get(&callee_name) {
|
|
if let Some(ctor) = ctors.get(&callee_name) {
|
|
return self.lower_constructor_call(*ctor, &n.args);
|
|
}
|
|
}
|
|
|
|
let type_sym = self
|
|
.module_symbols
|
|
.type_symbols
|
|
.get(id_node.name)
|
|
.or_else(|| self.imported_symbols.type_symbols.get(id_node.name));
|
|
if let Some(sym) = type_sym {
|
|
if sym.kind == SymbolKind::Struct {
|
|
// TODO: handle implicit struct constructor (lower args as field values)
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
// Check for special built-in functions
|
|
match callee_name.as_str() {
|
|
"some" | "ok" | "err" => {
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
return Ok(());
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
self.error(
|
|
"E_RESOLVE_UNDEFINED",
|
|
format!("Undefined function '{}'", callee_name),
|
|
self.arena.span(n.callee),
|
|
);
|
|
Err(())
|
|
}
|
|
}
|
|
NodeKind::MemberAccess(ma) => {
|
|
// Special-case: Member access over a call expression that returns a struct from a host contract,
|
|
// e.g., Pad.a().down — we must:
|
|
// 1) lower the call (pushing all struct slots),
|
|
// 2) store it into a temporary local (all slots),
|
|
// 3) load only the requested field slot back to the stack.
|
|
if let NodeKind::Call(call_node) = self.arena.kind(ma.object) {
|
|
// Try to resolve if callee is a host contract method to infer return struct and slots
|
|
if let NodeKind::MemberAccess(inner_ma) = self.arena.kind(call_node.callee) {
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(inner_ma.object) {
|
|
let contract_name = self.interner.resolve(obj_id.name);
|
|
let method_name = self.interner.resolve(inner_ma.member);
|
|
if let Some(method) = self.contract_registry.get_method(contract_name, method_name) {
|
|
// Determine slots from the declared return type BEFORE lowering the call
|
|
let ret_ty = self.convert_pbs_type(&method.return_type);
|
|
let slots = self.get_type_slots(&ret_ty);
|
|
let struct_name = match &ret_ty { Type::Struct(s) => s.clone(), _ => String::new() };
|
|
|
|
// Lower the call first (this will push all return slots from HostCall)
|
|
self.lower_call(n.callee, &call_node)?;
|
|
|
|
// Allocate a temp local to capture the struct
|
|
let tmp_slot = self.add_local_to_scope(
|
|
format!("$tmp_{}", self.get_next_local_slot()),
|
|
ret_ty.clone(),
|
|
);
|
|
// Store all slots (top of stack has the last slot). We must store in reverse order.
|
|
for i in (0..slots).rev() {
|
|
self.emit(InstrKind::SetLocal(tmp_slot + i));
|
|
}
|
|
|
|
// Compute field offset/type and load only that field
|
|
let field_name = self.interner.resolve(ma.member);
|
|
let field_off = self.get_field_offset(&struct_name, field_name);
|
|
let _field_ty = self.get_field_type(&struct_name, field_name);
|
|
self.emit(InstrKind::GetLocal(tmp_slot + field_off));
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check if it's a constructor alias: TypeName.Alias(...)
|
|
let ctor = if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj_id.name);
|
|
let member_name = self.interner.resolve(ma.member);
|
|
self.struct_constructors
|
|
.get(obj_name)
|
|
.and_then(|ctors| ctors.get(member_name))
|
|
.copied()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(ctor) = ctor {
|
|
return self.lower_constructor_call(ctor, &n.args);
|
|
}
|
|
|
|
// Check for Pad.any()
|
|
let member_name = self.interner.resolve(ma.member);
|
|
if member_name == "any" {
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj_id.name);
|
|
if let Some(info) = self.find_local(obj_name) {
|
|
if let Type::Struct(sname) = &info.ty {
|
|
if sname == "Pad" {
|
|
self.lower_pad_any(info.slot);
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Host contract static calls: Contract.method(...)
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj_id.name);
|
|
let is_local = self.find_local(obj_name).is_some();
|
|
|
|
if !is_local {
|
|
// Check type symbol (current or imported) for a host contract
|
|
let sym_opt = self
|
|
.module_symbols
|
|
.type_symbols
|
|
.get(obj_id.name)
|
|
.or_else(|| self.imported_symbols.type_symbols.get(obj_id.name));
|
|
if let Some(sym) = sym_opt {
|
|
// Suporte a chamada estática de service: Service.method(...)
|
|
if sym.kind == SymbolKind::Service {
|
|
let full_name = format!("{}.{}", obj_name, member_name);
|
|
for arg in &n.args { self.lower_node(*arg)?; }
|
|
if let Some(func_id) = self.function_ids.get(&full_name).cloned() {
|
|
self.emit(InstrKind::Call(func_id, n.args.len() as u32));
|
|
} else {
|
|
// Usar o binding real do import para este Service (ex.: Log -> (sdk, log))
|
|
let obj_name_str = obj_name.to_string();
|
|
if let Some((dep_alias, module_path)) = self.import_bindings.get(&obj_name_str).cloned() {
|
|
// Determine the canonical module origin used when we synthesized dependency symbols
|
|
// Only supported style: "@alias:module"
|
|
let canonical_origin = format!("@{}:{}", dep_alias, module_path);
|
|
|
|
// Find candidates among imported value symbols matching:
|
|
// - name in the new canonical form: "Service.member#sigN" (prefix match on qualified base)
|
|
// - origin equals the bound synthetic module path
|
|
let mut candidates: Vec<&Symbol> = Vec::new();
|
|
for list in self.imported_symbols.value_symbols.symbols.values() {
|
|
for s in list {
|
|
let sname = self.interner.resolve(s.name);
|
|
// Accept both canonical qualified form and legacy simple form for compatibility
|
|
let qualified_base = format!("{}.{}", obj_name, member_name);
|
|
let matches_qualified = sname.starts_with(&format!("{}#sig", &qualified_base));
|
|
let matches_legacy = sname.starts_with(&format!("{}#sig", member_name));
|
|
if matches_qualified || matches_legacy {
|
|
if let Some(orig) = &s.origin {
|
|
if *orig == canonical_origin {
|
|
candidates.push(s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If multiple candidates, try to disambiguate by arity (exact arg count)
|
|
let mut filtered: Vec<&Symbol> = candidates;
|
|
if filtered.len() > 1 {
|
|
let argc = n.args.len();
|
|
filtered = filtered.into_iter().filter(|s| {
|
|
if let Some(PbsType::Function { params, .. }) = &s.ty { params.len() == argc } else { false }
|
|
}).collect();
|
|
}
|
|
|
|
let sig_opt = if filtered.len() == 1 {
|
|
filtered[0]
|
|
.ty
|
|
.as_ref()
|
|
.and_then(|t| self.sig_from_pbs_fn(t))
|
|
} else if filtered.is_empty() {
|
|
self.error(
|
|
"E_OVERLOAD_NOT_FOUND",
|
|
format!(
|
|
"No matching overload for imported service method '{}.{}' with {} argument(s)",
|
|
obj_name, member_name, n.args.len()
|
|
),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
} else {
|
|
// Ambiguous within the bound module context; emit deterministic error
|
|
self.error(
|
|
"E_OVERLOAD_AMBIGUOUS",
|
|
format!(
|
|
"Ambiguous imported service method '{}.{}' ({} candidates in module '{}')",
|
|
obj_name, member_name, filtered.len(), module_path
|
|
),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
};
|
|
|
|
if let Some(sig) = sig_opt {
|
|
let base_name = member_name.to_string();
|
|
self.emit(InstrKind::ImportCall {
|
|
dep_alias,
|
|
module_path,
|
|
owner: Some(obj_name.to_string()),
|
|
base_name,
|
|
sig,
|
|
arg_count: n.args.len() as u32,
|
|
});
|
|
} else {
|
|
// Fallback: attempt to compute from service method type if exposed in type_constants or error deterministically
|
|
self.error(
|
|
"E_LOWER_SIGNATURE",
|
|
format!(
|
|
"Unable to determine signature for imported service method '{}.{}' (missing type info)",
|
|
obj_name, member_name
|
|
),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
}
|
|
} else {
|
|
// Sem binding de import conhecido: erro claro de serviço não importado
|
|
self.error(
|
|
"E_RESOLVE_UNDEFINED",
|
|
format!("Undefined service member '{}.{}' (service not imported)", obj_name, member_name),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
if sym.kind == SymbolKind::Contract && sym.is_host {
|
|
// 1) Caminho padrão via ContractRegistry
|
|
if self.contract_registry.get_method(obj_name, member_name).is_some() {
|
|
// Extrai valores necessários sem manter o empréstimo do registry vivo
|
|
let (id, return_ty) = {
|
|
let method = self.contract_registry.get_method(obj_name, member_name).unwrap();
|
|
(method.id, method.return_type.clone())
|
|
};
|
|
|
|
// Lower arguments primeiro
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
|
|
// Compute return slots a partir do tipo retornado
|
|
let return_slots = match &return_ty {
|
|
PbsType::Void | PbsType::None => 0,
|
|
PbsType::Struct(name) => {
|
|
// Prefer builtin struct slots, then fallback to struct_slots map, default 1
|
|
if let Some(bi) = self.get_builtin_struct_slots(name) { bi } else { *self.struct_slots.get(name).unwrap_or(&1) }
|
|
}
|
|
other => {
|
|
let ty = self.convert_pbs_type(other);
|
|
self.get_type_slots(&ty)
|
|
}
|
|
};
|
|
|
|
self.emit(InstrKind::HostCall(id, return_slots));
|
|
return Ok(());
|
|
}
|
|
|
|
// (Açúcar de Log movido para antes do branch host)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for .raw()
|
|
if member_name == "raw" {
|
|
self.lower_node(ma.object)?;
|
|
return Ok(());
|
|
}
|
|
|
|
// Check for Color.rgb
|
|
if member_name == "rgb" {
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
if self.interner.resolve(obj_id.name) == "Color" {
|
|
if n.args.len() == 3 {
|
|
// Try to get literal values for r, g, b
|
|
let mut literals = Vec::new();
|
|
for arg in &n.args {
|
|
if let NodeKind::IntLit(lit) = self.arena.kind(*arg) {
|
|
literals.push(Some(lit.value));
|
|
} else if let NodeKind::BoundedLit(lit) = self.arena.kind(*arg) {
|
|
literals.push(Some(lit.value as i64));
|
|
} else {
|
|
literals.push(None);
|
|
}
|
|
}
|
|
|
|
if let (Some(r), Some(g), Some(b)) =
|
|
(literals[0], literals[1], literals[2])
|
|
{
|
|
let r5 = (r & 0xFF) >> 3;
|
|
let g6 = (g & 0xFF) >> 2;
|
|
let b5 = (b & 0xFF) >> 3;
|
|
let rgb565 = (r5 << 11) | (g6 << 5) | b5;
|
|
self.emit(InstrKind::PushBounded(rgb565 as u32));
|
|
return Ok(());
|
|
} else {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
"Color.rgb only supports literal arguments in this version"
|
|
.to_string(),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tentativa de chamada de método de instância: obj.method(...)
|
|
// 1) Descobrir tipo do objeto e slot
|
|
let mut obj_info: Option<(u32, Type)> = None;
|
|
match self.arena.kind(ma.object) {
|
|
NodeKind::Ident(id) => {
|
|
let obj_name = self.interner.resolve(id.name);
|
|
if let Some(info) = self.find_local(obj_name) {
|
|
obj_info = Some((info.slot, info.ty.clone()));
|
|
}
|
|
}
|
|
NodeKind::MemberAccess(_) => {
|
|
if let Some(info) = self.resolve_member_access(ma.object) {
|
|
obj_info = Some(info);
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
if let Some((base_slot, ty)) = obj_info.clone() {
|
|
if let Type::Struct(ref sname) = ty {
|
|
let member_name = self.interner.resolve(ma.member);
|
|
let full_name = format!("{}.{}", sname, member_name);
|
|
let func_id_opt = self.function_ids.get(&full_name).cloned();
|
|
if let Some(func_id) = func_id_opt {
|
|
// Empilha self (todas as slots da instância)
|
|
let self_slots = self.struct_slots.get(sname).cloned().unwrap_or(1);
|
|
for i in 0..self_slots {
|
|
self.emit(InstrKind::GetLocal(base_slot + i));
|
|
}
|
|
// Empilha argumentos
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
let arg_slots = n.args.len() as u32;
|
|
self.emit(InstrKind::Call(func_id, self_slots + arg_slots));
|
|
return Ok(());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback original
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
|
|
if let NodeKind::Ident(obj_id) = self.arena.kind(ma.object) {
|
|
let obj_name = self.interner.resolve(obj_id.name);
|
|
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);
|
|
|
|
let is_shadowed = self.find_local(obj_name).is_some();
|
|
|
|
if is_host_contract && !is_shadowed {
|
|
if let Some(method) = self.contract_registry.get_method(obj_name, member_name) {
|
|
let ir_ty = self.convert_pbs_type(&method.return_type);
|
|
let return_slots = self.get_type_slots(&ir_ty);
|
|
self.emit(InstrKind::HostCall(method.id, return_slots));
|
|
return Ok(());
|
|
} else {
|
|
self.error(
|
|
"E_RESOLVE_UNDEFINED",
|
|
format!("Undefined contract member '{}.{}'", obj_name, member_name),
|
|
self.arena.span(n.callee),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
"Method calls not supported in v0".to_string(),
|
|
self.arena.span(n.callee),
|
|
);
|
|
Err(())
|
|
}
|
|
_ => {
|
|
for arg in &n.args {
|
|
self.lower_node(*arg)?;
|
|
}
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
"Indirect calls not supported in v0".to_string(),
|
|
self.arena.span(n.callee),
|
|
);
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_method_function(&mut self, type_name: &str, node: NodeId) -> Result<Function, ()> {
|
|
let n = match self.arena.kind(node) {
|
|
NodeKind::FnDecl(n) => n,
|
|
_ => return Err(()),
|
|
};
|
|
|
|
let full_name = format!("{}.{}", type_name, self.interner.resolve(n.name));
|
|
let func_id = match self.function_ids.get(&full_name) {
|
|
Some(id) => *id,
|
|
None => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("Missing function id for method '{}'", full_name),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
self.next_block_id = 0;
|
|
self.local_vars = vec![HashMap::new()];
|
|
self.max_slots_used = 0;
|
|
|
|
let mut params = Vec::new();
|
|
let mut local_types = HashMap::new();
|
|
let mut param_slots = 0u32;
|
|
// Guardar contexto anterior
|
|
let prev_ctx = self.current_type_context.clone();
|
|
let prev_self_slot = self.method_self_slot.take();
|
|
self.current_type_context = Some(type_name.to_string());
|
|
|
|
for param in &n.params {
|
|
let ty = self.lower_type_node(param.ty);
|
|
let slots = self.get_type_slots(&ty);
|
|
let param_name = self.interner.resolve(param.name).to_string();
|
|
params.push(Param {
|
|
name: param_name.clone(),
|
|
ty: ty.clone(),
|
|
});
|
|
// Slot inicial deste parâmetro
|
|
let this_param_start = param_slots;
|
|
self.local_vars[0].insert(
|
|
self.interner.resolve(param.name).to_string(),
|
|
LocalInfo {
|
|
slot: this_param_start,
|
|
ty: ty.clone(),
|
|
},
|
|
);
|
|
for i in 0..slots {
|
|
local_types.insert(this_param_start + i, ty.clone());
|
|
}
|
|
if self.interner.resolve(param.name) == "self" {
|
|
self.method_self_slot = Some(this_param_start);
|
|
}
|
|
param_slots += slots;
|
|
}
|
|
self.max_slots_used = param_slots;
|
|
|
|
let ret_ty = if let Some(ret) = n.ret { self.lower_type_node(ret) } else { Type::Void };
|
|
let return_slots = self.get_type_slots(&ret_ty);
|
|
|
|
// Build Signature and intern to SigId
|
|
let func_sig = Signature {
|
|
params: params.iter().map(|p| p.ty.clone()).collect(),
|
|
return_type: ret_ty.clone(),
|
|
};
|
|
let sig_id = {
|
|
let mut interner = global_signature_interner().lock().unwrap();
|
|
interner.intern(func_sig)
|
|
};
|
|
|
|
let func = Function {
|
|
id: func_id,
|
|
name: full_name,
|
|
sig: sig_id,
|
|
params,
|
|
return_type: ret_ty,
|
|
blocks: Vec::new(),
|
|
local_types,
|
|
param_slots: param_slots as u16,
|
|
local_slots: 0,
|
|
return_slots: return_slots as u16,
|
|
};
|
|
|
|
self.current_function = Some(func);
|
|
self.start_block();
|
|
self.lower_node(n.body)?;
|
|
|
|
if let Some(mut block) = self.current_block.take() {
|
|
if !matches!(block.terminator, Terminator::Return | Terminator::Jump(_) | Terminator::JumpIfFalse { .. }) {
|
|
block.terminator = Terminator::Return;
|
|
}
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
|
|
let mut final_func = self.current_function.take().unwrap();
|
|
final_func.local_slots = (self.max_slots_used - param_slots) as u16;
|
|
|
|
// Restaurar contexto
|
|
self.current_type_context = prev_ctx;
|
|
self.method_self_slot = prev_self_slot;
|
|
|
|
Ok(final_func)
|
|
}
|
|
|
|
fn lower_constructor_call(&mut self, ctor: NodeId, args: &[NodeId]) -> Result<(), ()> {
|
|
let ctor_id = ctor;
|
|
let ctor = match self.arena.kind(ctor) {
|
|
NodeKind::ConstructorDecl(ctor) => ctor,
|
|
_ => return Err(()),
|
|
};
|
|
|
|
if args.len() != ctor.params.len() {
|
|
self.error(
|
|
"E_TYPE_MISMATCH",
|
|
format!(
|
|
"Expected {} arguments, found {}",
|
|
ctor.params.len(),
|
|
args.len()
|
|
),
|
|
self.arena.span(ctor_id),
|
|
);
|
|
return Err(());
|
|
}
|
|
|
|
self.local_vars.push(HashMap::new());
|
|
let mut param_slots = Vec::new();
|
|
|
|
for param in &ctor.params {
|
|
let ty = self.lower_type_node(param.ty);
|
|
let slot = self.add_local_to_scope(self.interner.resolve(param.name).to_string(), ty.clone());
|
|
param_slots.push((slot, ty));
|
|
}
|
|
|
|
for (index, arg) in args.iter().enumerate() {
|
|
if let Some((slot, ty)) = param_slots.get(index) {
|
|
self.lower_node(*arg)?;
|
|
let slots = self.get_type_slots(ty);
|
|
for i in (0..slots).rev() {
|
|
self.emit(InstrKind::SetLocal(slot + i));
|
|
}
|
|
}
|
|
}
|
|
|
|
for init in &ctor.initializers {
|
|
self.lower_node(*init)?;
|
|
}
|
|
|
|
self.local_vars.pop();
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_pad_any(&mut self, base_slot: u32) {
|
|
for i in 0..12 {
|
|
let btn_base = base_slot + (i * 4);
|
|
self.emit(InstrKind::GetLocal(btn_base)); // pressed
|
|
self.emit(InstrKind::GetLocal(btn_base + 1)); // released
|
|
self.emit(InstrKind::Or);
|
|
self.emit(InstrKind::GetLocal(btn_base + 2)); // down
|
|
self.emit(InstrKind::Or);
|
|
if i > 0 {
|
|
self.emit(InstrKind::Or);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn lower_binary(&mut self, node: NodeId, n: &BinaryNodeArena) -> Result<(), ()> {
|
|
self.lower_node(n.left)?;
|
|
self.lower_node(n.right)?;
|
|
match n.op.as_str() {
|
|
"+" => self.emit(InstrKind::Add),
|
|
"-" => self.emit(InstrKind::Sub),
|
|
"*" => self.emit(InstrKind::Mul),
|
|
"/" => self.emit(InstrKind::Div),
|
|
"==" => self.emit(InstrKind::Eq),
|
|
"!=" => self.emit(InstrKind::Neq),
|
|
"<" => self.emit(InstrKind::Lt),
|
|
"<=" => self.emit(InstrKind::Lte),
|
|
">" => self.emit(InstrKind::Gt),
|
|
">=" => self.emit(InstrKind::Gte),
|
|
"&&" => self.emit(InstrKind::And),
|
|
"||" => self.emit(InstrKind::Or),
|
|
_ => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("Binary operator '{}' not supported", n.op),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_unary(&mut self, node: NodeId, n: &UnaryNodeArena) -> Result<(), ()> {
|
|
self.lower_node(n.expr)?;
|
|
match n.op.as_str() {
|
|
"-" => self.emit(InstrKind::Neg),
|
|
"!" => self.emit(InstrKind::Not),
|
|
_ => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
format!("Unary operator '{}' not supported", n.op),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_if_expr(&mut self, _node: NodeId, n: &IfExprNodeArena) -> Result<(), ()> {
|
|
let then_id = self.reserve_block_id();
|
|
let else_id = self.reserve_block_id();
|
|
let merge_id = self.reserve_block_id();
|
|
|
|
self.lower_node(n.cond)?;
|
|
self.terminate(Terminator::JumpIfFalse {
|
|
target: else_id,
|
|
else_target: then_id,
|
|
});
|
|
|
|
// Then block
|
|
self.start_block_with_id(then_id);
|
|
self.lower_node(n.then_block)?;
|
|
self.terminate(Terminator::Jump(merge_id));
|
|
|
|
// Else block
|
|
self.start_block_with_id(else_id);
|
|
if let Some(else_block) = n.else_block {
|
|
self.lower_node(else_block)?;
|
|
}
|
|
self.terminate(Terminator::Jump(merge_id));
|
|
|
|
// Merge block
|
|
self.start_block_with_id(merge_id);
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_when_expr(&mut self, node: NodeId, n: &WhenExprNodeArena) -> Result<(), ()> {
|
|
if n.arms.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
let merge_id = self.reserve_block_id();
|
|
|
|
for (idx, arm_id) in n.arms.iter().enumerate() {
|
|
let arm = match self.arena.kind(*arm_id) {
|
|
NodeKind::WhenArm(arm) => arm,
|
|
_ => {
|
|
self.error(
|
|
"E_LOWER_UNSUPPORTED",
|
|
"Expected when arm".to_string(),
|
|
self.arena.span(node),
|
|
);
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
let body_id = self.reserve_block_id();
|
|
let next_cond_id = if idx + 1 < n.arms.len() {
|
|
self.reserve_block_id()
|
|
} else {
|
|
merge_id
|
|
};
|
|
|
|
self.lower_node(arm.cond)?;
|
|
self.terminate(Terminator::JumpIfFalse {
|
|
target: next_cond_id,
|
|
else_target: body_id,
|
|
});
|
|
|
|
self.start_block_with_id(body_id);
|
|
self.lower_node(arm.body)?;
|
|
self.terminate(Terminator::Jump(merge_id));
|
|
|
|
if idx + 1 < n.arms.len() {
|
|
self.start_block_with_id(next_cond_id);
|
|
}
|
|
}
|
|
|
|
self.start_block_with_id(merge_id);
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_type_node(&mut self, node: NodeId) -> Type {
|
|
match self.arena.kind(node) {
|
|
NodeKind::TypeName(n) => match self.interner.resolve(n.name) {
|
|
"int" => Type::Int,
|
|
"bounded" => Type::Bounded,
|
|
"float" => Type::Float,
|
|
"bool" => Type::Bool,
|
|
"string" => Type::String,
|
|
"void" => Type::Void,
|
|
"this" => {
|
|
if let Some(ctx) = &self.current_type_context {
|
|
Type::Struct(ctx.clone())
|
|
} else {
|
|
Type::Void
|
|
}
|
|
}
|
|
_ => {
|
|
if let Some(sym) = self
|
|
.module_symbols
|
|
.type_symbols
|
|
.get(n.name)
|
|
.or_else(|| self.imported_symbols.type_symbols.get(n.name))
|
|
{
|
|
let name = self.interner.resolve(n.name).to_string();
|
|
match sym.kind {
|
|
SymbolKind::Struct => Type::Struct(name),
|
|
SymbolKind::Service => Type::Service(name),
|
|
SymbolKind::Contract => Type::Contract(name),
|
|
SymbolKind::ErrorType => Type::ErrorType(name),
|
|
_ => Type::Struct(name),
|
|
}
|
|
} else {
|
|
Type::Struct(self.interner.resolve(n.name).to_string())
|
|
}
|
|
}
|
|
},
|
|
NodeKind::TypeApp(ta) => {
|
|
let base_name = self.interner.resolve(ta.base);
|
|
if base_name == "array" {
|
|
let elem_ty = self.lower_type_node(ta.args[0]);
|
|
let size = if ta.args.len() > 1 {
|
|
if let NodeKind::IntLit(il) = self.arena.kind(ta.args[1]) {
|
|
il.value as u32
|
|
} else {
|
|
0
|
|
}
|
|
} else {
|
|
0
|
|
};
|
|
Type::Array(Box::new(elem_ty), size)
|
|
} else if base_name == "optional" {
|
|
Type::Optional(Box::new(self.lower_type_node(ta.args[0])))
|
|
} else if base_name == "result" {
|
|
Type::Result(
|
|
Box::new(self.lower_type_node(ta.args[0])),
|
|
Box::new(self.lower_type_node(ta.args[1]))
|
|
)
|
|
} else {
|
|
Type::Struct(format!("{}<{}>", base_name, ta.args.len()))
|
|
}
|
|
}
|
|
_ => Type::Void,
|
|
}
|
|
}
|
|
|
|
fn start_block(&mut self) {
|
|
let id = self.reserve_block_id();
|
|
self.start_block_with_id(id);
|
|
}
|
|
|
|
fn start_block_with_id(&mut self, id: u32) {
|
|
if let Some(block) = self.current_block.take() {
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
self.current_block = Some(Block {
|
|
id,
|
|
instrs: Vec::new(),
|
|
terminator: Terminator::Return, // Default, will be overwritten
|
|
});
|
|
}
|
|
|
|
fn reserve_block_id(&mut self) -> u32 {
|
|
let id = self.next_block_id;
|
|
self.next_block_id += 1;
|
|
id
|
|
}
|
|
|
|
fn emit(&mut self, kind: InstrKind) {
|
|
if let Some(block) = &mut self.current_block {
|
|
block.instrs.push(Instr::new(kind, self.current_span.clone()));
|
|
}
|
|
}
|
|
|
|
fn terminate(&mut self, terminator: Terminator) {
|
|
if let Some(mut block) = self.current_block.take() {
|
|
block.terminator = terminator;
|
|
if let Some(func) = &mut self.current_function {
|
|
func.blocks.push(block);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_next_local_slot(&self) -> u32 {
|
|
self.local_vars.iter().flat_map(|s| s.values()).map(|info| self.get_type_slots(&info.ty)).sum()
|
|
}
|
|
|
|
fn add_local_to_scope(&mut self, name: String, ty: Type) -> u32 {
|
|
let slot = self.get_next_local_slot();
|
|
let slots = self.get_type_slots(&ty);
|
|
|
|
if slot + slots > self.max_slots_used {
|
|
self.max_slots_used = slot + slots;
|
|
}
|
|
|
|
self.local_vars.last_mut().unwrap().insert(name, LocalInfo { slot, ty: ty.clone() });
|
|
|
|
if let Some(func) = &mut self.current_function {
|
|
for i in 0..slots {
|
|
func.local_types.insert(slot + i, ty.clone());
|
|
}
|
|
}
|
|
slot
|
|
}
|
|
|
|
fn find_local(&self, name: &str) -> Option<LocalInfo> {
|
|
for scope in self.local_vars.iter().rev() {
|
|
if let Some(info) = scope.get(name) {
|
|
return Some(info.clone());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn get_builtin_struct_slots(&self, name: &str) -> Option<u32> {
|
|
match name {
|
|
"Pad" => Some(48),
|
|
"ButtonState" => Some(4),
|
|
"Color" => Some(1),
|
|
"Touch" => Some(6),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn get_type_slots(&self, ty: &Type) -> u32 {
|
|
match ty {
|
|
Type::Void => 0,
|
|
Type::Struct(name) => {
|
|
if let Some(slots) = self.get_builtin_struct_slots(name) {
|
|
slots
|
|
} else {
|
|
self.struct_slots.get(name).cloned().unwrap_or(1)
|
|
}
|
|
}
|
|
Type::Array(_, size) => *size,
|
|
_ => 1,
|
|
}
|
|
}
|
|
|
|
fn convert_pbs_type(&self, ty: &PbsType) -> Type {
|
|
match ty {
|
|
PbsType::Int => Type::Int,
|
|
PbsType::Float => Type::Float,
|
|
PbsType::Bool => Type::Bool,
|
|
PbsType::String => Type::String,
|
|
PbsType::Void => Type::Void,
|
|
PbsType::None => Type::Void,
|
|
PbsType::Bounded => Type::Bounded,
|
|
PbsType::Optional(inner) => Type::Optional(Box::new(self.convert_pbs_type(inner))),
|
|
PbsType::Result(ok, err) => Type::Result(
|
|
Box::new(self.convert_pbs_type(ok)),
|
|
Box::new(self.convert_pbs_type(err)),
|
|
),
|
|
PbsType::Struct(name) => Type::Struct(name.clone()),
|
|
PbsType::Service(name) => Type::Service(name.clone()),
|
|
PbsType::Contract(name) => Type::Contract(name.clone()),
|
|
PbsType::ErrorType(name) => Type::ErrorType(name.clone()),
|
|
PbsType::Function { params, return_type } => Type::Function {
|
|
params: params.iter().map(|p| self.convert_pbs_type(p)).collect(),
|
|
return_type: Box::new(self.convert_pbs_type(return_type)),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::common::spans::FileId;
|
|
use crate::frontends::pbs::collector::SymbolCollector;
|
|
use crate::frontends::pbs::parser::Parser;
|
|
use crate::frontends::pbs::symbols::ModuleSymbols;
|
|
use prometeu_analysis::NameInterner;
|
|
|
|
struct NullProvider;
|
|
impl crate::frontends::pbs::resolver::ModuleProvider for NullProvider {
|
|
fn get_module_symbols(&self, _from_path: &str) -> Option<&ModuleSymbols> { None }
|
|
}
|
|
|
|
fn lower_program(code: &str) -> Program {
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
lowerer.lower_file(parsed.root, "test").expect("Lowering failed")
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic_lowering() {
|
|
let code = "
|
|
fn add(a: int, b: int): int {
|
|
return a + b;
|
|
}
|
|
fn main() {
|
|
let x = add(10, 20);
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
// Verify program structure
|
|
assert_eq!(program.modules.len(), 1);
|
|
let module = &program.modules[0];
|
|
assert_eq!(module.functions.len(), 2);
|
|
|
|
let add_func = module.functions.iter().find(|f| f.name == "add").unwrap();
|
|
assert_eq!(add_func.params.len(), 2);
|
|
assert_eq!(add_func.return_type, Type::Int);
|
|
|
|
// Verify blocks
|
|
assert!(add_func.blocks.len() >= 1);
|
|
let first_block = &add_func.blocks[0];
|
|
// Check for Add instruction
|
|
assert!(first_block.instrs.iter().any(|i| matches!(i.kind, InstrKind::Add)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_binary_ops_lowering() {
|
|
let code = "
|
|
fn main() {
|
|
let a = 1 + 2;
|
|
let b = 3 - 1;
|
|
let c = 2 * 3;
|
|
let d = 8 / 2;
|
|
let e = 1 == 1;
|
|
let f = 1 < 2;
|
|
let g = 2 > 1;
|
|
let h = true && false;
|
|
let i = true || false;
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Add)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Sub)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Mul)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Div)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Eq)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Lt)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Gt)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::And)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Or)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_unary_ops_lowering() {
|
|
let code = "
|
|
fn main() {
|
|
let a = -1;
|
|
let b = !true;
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Neg)));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Not)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_control_flow_lowering() {
|
|
let code = "
|
|
fn max(a: int, b: int): int {
|
|
if (a > b) {
|
|
return a;
|
|
} else {
|
|
return b;
|
|
}
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let max_func = &program.modules[0].functions[0];
|
|
// Should have multiple blocks for if-else
|
|
assert!(max_func.blocks.len() >= 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_if_expr_lowering() {
|
|
let code = "
|
|
fn main(a: int, b: int) {
|
|
if (a > b) {
|
|
let x = a;
|
|
} else {
|
|
let y = b;
|
|
}
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect();
|
|
|
|
assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. })));
|
|
assert!(terminators.iter().any(|t| matches!(t, Terminator::Jump(_))));
|
|
assert!(main_func.blocks.len() >= 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_when_expr_lowering() {
|
|
let code = "
|
|
fn main(x: int) {
|
|
when {
|
|
x == 0 -> { return; },
|
|
x == 1 -> { return; }
|
|
};
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let terminators: Vec<_> = main_func.blocks.iter().map(|b| &b.terminator).collect();
|
|
|
|
assert!(terminators.iter().any(|t| matches!(t, Terminator::JumpIfFalse { .. })));
|
|
assert!(main_func.blocks.len() >= 5);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_type_node() {
|
|
let code = "
|
|
service MyService {
|
|
fn ping(): void;
|
|
}
|
|
declare contract MyContract host {}
|
|
declare error MyError {}
|
|
declare struct Point(x: int)
|
|
|
|
fn main(
|
|
s: MyService,
|
|
c: MyContract,
|
|
e: MyError,
|
|
p: Point,
|
|
o: optional<int>,
|
|
r: result<int, string>,
|
|
a: int[3]
|
|
) {}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let params: Vec<_> = main_func.params.iter().map(|p| p.ty.clone()).collect();
|
|
|
|
assert_eq!(params[0], Type::Service("MyService".to_string()));
|
|
assert_eq!(params[1], Type::Contract("MyContract".to_string()));
|
|
assert_eq!(params[2], Type::ErrorType("MyError".to_string()));
|
|
assert_eq!(params[3], Type::Struct("Point".to_string()));
|
|
assert_eq!(
|
|
params[4],
|
|
Type::Optional(Box::new(Type::Int))
|
|
);
|
|
assert_eq!(
|
|
params[5],
|
|
Type::Result(
|
|
Box::new(Type::Int),
|
|
Box::new(Type::String)
|
|
)
|
|
);
|
|
assert_eq!(
|
|
params[6],
|
|
Type::Array(Box::new(Type::Int), 3)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_call_lowering() {
|
|
let code = "
|
|
fn add(a: int, b: int): int {
|
|
return a + b;
|
|
}
|
|
fn main() {
|
|
let x = add(1, 2);
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::Call(_, 2))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_host_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
fn main() {
|
|
Gfx.clear(Color.WHITE);
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::HostCall(_, _))));
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::PushBounded(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_member_access_lowering() {
|
|
let code = "
|
|
declare contract Input host {}
|
|
fn main() {
|
|
let p: Pad = Input.pad();
|
|
let b: ButtonState = p.a;
|
|
let d: bool = p.a.down;
|
|
let c: Color = Color.WHITE;
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::GetLocal(16))));
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::GetLocal(18))));
|
|
assert!(instrs
|
|
.iter()
|
|
.any(|i| matches!(i.kind, InstrKind::PushBounded(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_constructor_call_lowering() {
|
|
let code = "
|
|
declare struct Vec2(x: int, y: int)
|
|
[
|
|
(x: int, y: int): (x, y) as default { }
|
|
(s: int): (s, s) as square { }
|
|
]
|
|
fn main() {
|
|
let a = Vec2(1, 2);
|
|
let b = Vec2.square(3);
|
|
}
|
|
";
|
|
let program = lower_program(code);
|
|
|
|
let module = &program.modules[0];
|
|
let main_func = module.functions.iter().find(|f| f.name == "main").unwrap();
|
|
let instrs: Vec<_> = main_func
|
|
.blocks
|
|
.iter()
|
|
.flat_map(|b| b.instrs.iter())
|
|
.collect();
|
|
|
|
let push_consts = instrs
|
|
.iter()
|
|
.filter(|i| matches!(i.kind, InstrKind::PushConst(_)))
|
|
.count();
|
|
|
|
assert_eq!(push_consts, 3);
|
|
assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::Call(_, _))));
|
|
assert!(!instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(_, _))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_lowering() {
|
|
let code = "
|
|
fn test_hip() {
|
|
let g = alloc int;
|
|
mutate g as x {
|
|
let y = x + 1;
|
|
}
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::Alloc { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_lowering_golden() {
|
|
let code = "
|
|
fn test_hip() {
|
|
let g = alloc int;
|
|
mutate g as x {
|
|
let y = x + 1;
|
|
}
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.unwrap();
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let json = serde_json::to_string_pretty(&program).unwrap();
|
|
|
|
// Assertions for PR-20 HIP Semantics:
|
|
// 1. Gate is preserved in a local (SetLocal(1) after GetLocal(0))
|
|
// 2. BeginMutate uses that local (BeginMutate { gate: 1 })
|
|
// 3. EndMutate exists
|
|
// 4. No ReadGate/WriteGate (they were removed from Instr)
|
|
|
|
assert!(json.contains("\"SetLocal\": 1"), "Gate should be stored in a local");
|
|
assert!(json.contains("\"BeginMutate\""), "Should have BeginMutate");
|
|
assert!(json.contains("\"gate\": 1"), "BeginMutate should use the gate local");
|
|
assert!(json.contains("\"EndMutate\""), "Should have EndMutate");
|
|
assert!(!json.contains("ReadGate"), "ReadGate should be gone");
|
|
assert!(!json.contains("WriteGate"), "WriteGate should be gone");
|
|
}
|
|
|
|
#[test]
|
|
fn test_hip_semantics_distinction() {
|
|
let code = "
|
|
fn test_hip(g: int) {
|
|
peek g as p {
|
|
let x = p;
|
|
}
|
|
borrow g as b {
|
|
let y = b;
|
|
}
|
|
mutate g as m {
|
|
let z = m;
|
|
}
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
// Assert distinct Core IR instruction sequences
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginPeek { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndPeek)));
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginBorrow { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndBorrow)));
|
|
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::BeginMutate { .. })));
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::EndMutate)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_host_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
declare contract LogHost host {}
|
|
fn main() {
|
|
Gfx.clear(0);
|
|
LogHost.write(2, \"Hello\");
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
// Gfx.clear -> 0x1010
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x1010, 0))));
|
|
// LogHost.write -> 0x5001 (registry updated to LogHost)
|
|
assert!(instrs.iter().any(|i| matches!(i.kind, InstrKind::HostCall(0x5001, _))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_contract_call_without_host_lowering() {
|
|
let code = "
|
|
declare contract Gfx {}
|
|
fn main() {
|
|
Gfx.clear(0);
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let result = lowerer.lower_file(parsed.root, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_shadowed_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
fn main() {
|
|
let Gfx = 10;
|
|
Gfx.clear(0);
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let result = lowerer.lower_file(parsed.root, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_LOWER_UNSUPPORTED"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_contract_call_lowering() {
|
|
let code = "
|
|
declare contract Gfx host {}
|
|
fn main() {
|
|
Gfx.invalidMethod(0);
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let result = lowerer.lower_file(parsed.root, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_struct_slots() {
|
|
let code = "
|
|
declare struct Vec3(x: int, y: int, z: int)
|
|
fn main() {
|
|
let v = alloc Vec3;
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let InstrKind::Alloc { ty, slots } = &i.kind {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 3, "Vec3 should have 3 slots");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_array_slots() {
|
|
let code = "
|
|
fn main() {
|
|
let a = alloc array<int>[10b];
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let InstrKind::Alloc { ty, slots } = &i.kind {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 10, "array<int>[10b] should have 10 slots");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_alloc_primitive_slots() {
|
|
let code = "
|
|
fn main() {
|
|
let x = alloc int;
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let program = lowerer.lower_file(parsed.root, "test").expect("Lowering failed");
|
|
|
|
let func = &program.modules[0].functions[0];
|
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
|
|
|
let alloc = instrs.iter().find_map(|i| {
|
|
if let InstrKind::Alloc { ty, slots } = &i.kind {
|
|
Some((ty, slots))
|
|
} else {
|
|
None
|
|
}
|
|
}).expect("Should have Alloc instruction");
|
|
|
|
assert_eq!(*alloc.1, 1, "Primitive int should have 1 slot");
|
|
assert!(alloc.0.0 > 0, "Should have a valid TypeId");
|
|
}
|
|
|
|
#[test]
|
|
fn test_missing_function_error() {
|
|
let code = "
|
|
fn main() {
|
|
missing_func();
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let result = lowerer.lower_file(parsed.root, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
|
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined function 'missing_func'")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_unresolved_ident_error() {
|
|
let code = "
|
|
fn main() {
|
|
let x = undefined_var;
|
|
}
|
|
";
|
|
let mut interner = NameInterner::new();
|
|
let mut parser = Parser::new(code, FileId(0), &mut interner);
|
|
let parsed = parser.parse_file().expect("Failed to parse");
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) = collector
|
|
.collect(&parsed.arena, parsed.root)
|
|
.expect("Failed to collect symbols");
|
|
let module_symbols = ModuleSymbols { type_symbols, value_symbols };
|
|
|
|
let imported = ModuleSymbols::new();
|
|
let provider = NullProvider;
|
|
let lowerer = Lowerer::new(&parsed.arena, &module_symbols, &imported, &provider, &interner);
|
|
let result = lowerer.lower_file(parsed.root, "test");
|
|
|
|
assert!(result.is_err());
|
|
let bundle = result.err().unwrap();
|
|
assert!(bundle.diagnostics.iter().any(|d| d.code == "E_RESOLVE_UNDEFINED"));
|
|
assert!(bundle.diagnostics.iter().any(|d| d.message.contains("Undefined identifier 'undefined_var'")));
|
|
}
|
|
}
|