dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
27 changed files with 1494 additions and 1503 deletions
Showing only changes of commit 4174d01a43 - Show all commits

View File

@ -228,3 +228,47 @@ impl<'a> BytecodeEmitter<'a> {
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::module::{Module, Function};
use crate::ir::instr::{Instruction, InstrKind};
use crate::ir::types::Type;
use crate::ir_core::ids::FunctionId;
use crate::ir_core::const_pool::ConstantValue;
use crate::common::files::FileManager;
use prometeu_bytecode::pbc::{parse_pbc, ConstantPoolEntry};
#[test]
fn test_emit_module_with_const_pool() {
let mut module = Module::new("test".to_string());
let id_int = module.const_pool.insert(ConstantValue::Int(12345));
let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string()));
let function = Function {
id: FunctionId(0),
name: "main".to_string(),
params: vec![],
return_type: Type::Void,
body: vec![
Instruction::new(InstrKind::PushConst(id_int), None),
Instruction::new(InstrKind::PushConst(id_str), None),
Instruction::new(InstrKind::Ret, None),
],
};
module.functions.push(function);
let file_manager = FileManager::new();
let result = emit_module(&module, &file_manager).expect("Failed to emit module");
let pbc = parse_pbc(&result.rom).expect("Failed to parse emitted PBC");
assert_eq!(pbc.cp.len(), 3);
assert_eq!(pbc.cp[0], ConstantPoolEntry::Null);
assert_eq!(pbc.cp[1], ConstantPoolEntry::Int64(12345));
assert_eq!(pbc.cp[2], ConstantPoolEntry::String("hello".to_string()));
}
}

View File

@ -109,3 +109,66 @@ impl From<Diagnostic> for DiagnosticBundle {
bundle
}
}
#[cfg(test)]
mod tests {
use crate::frontends::pbs::PbsFrontend;
use crate::frontends::Frontend;
use crate::common::files::FileManager;
use std::fs;
use tempfile::tempdir;
fn get_diagnostics(code: &str) -> String {
let mut file_manager = FileManager::new();
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("main.pbs");
fs::write(&file_path, code).unwrap();
let frontend = PbsFrontend;
match frontend.compile_to_ir(&file_path, &mut file_manager) {
Ok(_) => "[]".to_string(),
Err(bundle) => bundle.to_json(&file_manager),
}
}
#[test]
fn test_golden_parse_error() {
let code = "fn main() { let x = ; }";
let json = get_diagnostics(code);
assert!(json.contains("E_PARSE_UNEXPECTED_TOKEN"));
assert!(json.contains("Expected expression"));
}
#[test]
fn test_golden_lex_error() {
let code = "fn main() { let x = \"hello ; }";
let json = get_diagnostics(code);
assert!(json.contains("E_LEX_UNTERMINATED_STRING"));
}
#[test]
fn test_golden_resolve_error() {
let code = "fn main() { let x = undefined_var; }";
let json = get_diagnostics(code);
assert!(json.contains("E_RESOLVE_UNDEFINED"));
}
#[test]
fn test_golden_type_error() {
let code = "fn main() { let x: int = \"hello\"; }";
let json = get_diagnostics(code);
assert!(json.contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_golden_namespace_collision() {
let code = "
declare struct Foo {}
fn main() {
let Foo = 1;
}
";
let json = get_diagnostics(code);
assert!(json.contains("E_RESOLVE_NAMESPACE_COLLISION"));
}
}

View File

@ -106,6 +106,9 @@ mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
use prometeu_bytecode::pbc::parse_pbc;
use prometeu_bytecode::disasm::disasm;
use prometeu_bytecode::opcode::OpCode;
#[test]
fn test_invalid_frontend() {
@ -125,4 +128,151 @@ mod tests {
assert!(result.unwrap_err().to_string().contains("Invalid frontend: invalid"));
}
#[test]
fn test_compile_hip_program() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
).unwrap();
let code = "
fn frame(): void {
let x = alloc int;
mutate x as v {
let y = v + 1;
}
}
";
fs::write(project_dir.join("main.pbs"), code).unwrap();
let unit = compile(project_dir).expect("Failed to compile");
let pbc = parse_pbc(&unit.rom).expect("Failed to parse PBC");
let instrs = disasm(&pbc.rom).expect("Failed to disassemble");
let opcodes: Vec<_> = instrs.iter().map(|i| i.opcode).collect();
assert!(opcodes.contains(&OpCode::Alloc));
assert!(opcodes.contains(&OpCode::LoadRef));
assert!(opcodes.contains(&OpCode::StoreRef));
assert!(opcodes.contains(&OpCode::Add));
assert!(opcodes.contains(&OpCode::Ret));
}
#[test]
fn test_golden_bytecode_snapshot() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
).unwrap();
let code = r#"
declare contract Gfx host {}
fn helper(val: int): int {
return val * 2;
}
fn main() {
Gfx.clear(0);
let x = 10;
if (x > 5) {
let y = helper(x);
}
let buf = alloc int;
mutate buf as b {
let current = b + 1;
}
}
"#;
fs::write(project_dir.join("main.pbs"), code).unwrap();
let unit = compile(project_dir).expect("Failed to compile");
let pbc = parse_pbc(&unit.rom).expect("Failed to parse PBC");
let instrs = disasm(&pbc.rom).expect("Failed to disassemble");
let mut disasm_text = String::new();
for instr in instrs {
let operands_str = instr.operands.iter()
.map(|o| format!("{:?}", o))
.collect::<Vec<_>>()
.join(" ");
let line = if operands_str.is_empty() {
format!("{:04X} {:?}\n", instr.pc, instr.opcode)
} else {
format!("{:04X} {:?} {}\n", instr.pc, instr.opcode, operands_str.trim())
};
disasm_text.push_str(&line);
}
let expected_disasm = r#"0000 GetLocal U32(0)
0006 PushConst U32(1)
000C Mul
000E Ret
0010 PushConst U32(2)
0016 Syscall U32(4097)
001C PushConst U32(3)
0022 SetLocal U32(0)
0028 GetLocal U32(0)
002E PushConst U32(4)
0034 Gt
0036 JmpIfFalse U32(94)
003C Jmp U32(66)
0042 GetLocal U32(0)
0048 Call U32(0) U32(1)
0052 SetLocal U32(1)
0058 Jmp U32(100)
005E Jmp U32(100)
0064 Alloc
0066 SetLocal U32(1)
006C GetLocal U32(1)
0072 LoadRef U32(0)
0078 SetLocal U32(2)
007E GetLocal U32(2)
0084 PushConst U32(5)
008A Add
008C SetLocal U32(3)
0092 GetLocal U32(2)
0098 StoreRef U32(0)
009E Ret
"#;
assert_eq!(disasm_text, expected_disasm);
}
#[test]
fn test_project_root_and_entry_resolution() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
// Create prometeu.json
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "src/main.pbs"
}"#,
).unwrap();
// Create src directory and main.pbs
fs::create_dir(project_dir.join("src")).unwrap();
fs::write(project_dir.join("src/main.pbs"), "").unwrap();
// Call compile
let result = compile(project_dir);
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
}
}

View File

@ -265,3 +265,163 @@ fn is_identifier_start(c: char) -> bool {
fn is_identifier_part(c: char) -> bool {
c.is_alphanumeric() || c == '_'
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontends::pbs::token::TokenKind;
#[test]
fn test_lex_basic_tokens() {
let source = "( ) { } [ ] , . : ; -> = == + - * / % ! != < > <= >= && ||";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::OpenParen, TokenKind::CloseParen,
TokenKind::OpenBrace, TokenKind::CloseBrace,
TokenKind::OpenBracket, TokenKind::CloseBracket,
TokenKind::Comma, TokenKind::Dot, TokenKind::Colon, TokenKind::Semicolon,
TokenKind::Arrow, TokenKind::Assign, TokenKind::Eq,
TokenKind::Plus, TokenKind::Minus, TokenKind::Star, TokenKind::Slash, TokenKind::Percent,
TokenKind::Not, TokenKind::Neq,
TokenKind::Lt, TokenKind::Gt, TokenKind::Lte, TokenKind::Gte,
TokenKind::And, TokenKind::Or,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_keywords() {
let source = "import pub mod service fn let mut declare struct contract host error optional result some none ok err if else when for in return handle borrow mutate peek take alloc weak as";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Import, TokenKind::Pub, TokenKind::Mod, TokenKind::Service,
TokenKind::Fn, TokenKind::Let, TokenKind::Mut, TokenKind::Declare,
TokenKind::Struct, TokenKind::Contract, TokenKind::Host, TokenKind::Error,
TokenKind::Optional, TokenKind::Result, TokenKind::Some, TokenKind::None,
TokenKind::Ok, TokenKind::Err, TokenKind::If, TokenKind::Else,
TokenKind::When, TokenKind::For, TokenKind::In, TokenKind::Return,
TokenKind::Handle, TokenKind::Borrow, TokenKind::Mutate, TokenKind::Peek,
TokenKind::Take, TokenKind::Alloc, TokenKind::Weak, TokenKind::As,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_identifiers() {
let source = "foo bar _baz qux123";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Identifier("foo".to_string()),
TokenKind::Identifier("bar".to_string()),
TokenKind::Identifier("_baz".to_string()),
TokenKind::Identifier("qux123".to_string()),
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_literals() {
let source = "123 3.14 255b \"hello world\"";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::IntLit(123),
TokenKind::FloatLit(3.14),
TokenKind::BoundedLit(255),
TokenKind::StringLit("hello world".to_string()),
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_comments() {
let source = "let x = 10; // this is a comment\nlet y = 20;";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Let,
TokenKind::Identifier("x".to_string()),
TokenKind::Assign,
TokenKind::IntLit(10),
TokenKind::Semicolon,
TokenKind::Let,
TokenKind::Identifier("y".to_string()),
TokenKind::Assign,
TokenKind::IntLit(20),
TokenKind::Semicolon,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_spans() {
let source = "let x = 10;";
let mut lexer = Lexer::new(source, 0);
let t1 = lexer.next_token(); // let
assert_eq!(t1.span.start, 0);
assert_eq!(t1.span.end, 3);
let t2 = lexer.next_token(); // x
assert_eq!(t2.span.start, 4);
assert_eq!(t2.span.end, 5);
let t3 = lexer.next_token(); // =
assert_eq!(t3.span.start, 6);
assert_eq!(t3.span.end, 7);
let t4 = lexer.next_token(); // 10
assert_eq!(t4.span.start, 8);
assert_eq!(t4.span.end, 10);
let t5 = lexer.next_token(); // ;
assert_eq!(t5.span.start, 10);
assert_eq!(t5.span.end, 11);
}
#[test]
fn test_lex_invalid_tokens() {
let source = "@ #";
let mut lexer = Lexer::new(source, 0);
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
}
#[test]
fn test_lex_unterminated_string() {
let source = "\"hello";
let mut lexer = Lexer::new(source, 0);
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
}
}

View File

@ -372,3 +372,209 @@ impl<'a> Lowerer<'a> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontends::pbs::parser::Parser;
use crate::frontends::pbs::collector::SymbolCollector;
use crate::frontends::pbs::symbols::ModuleSymbols;
use crate::ir_core;
#[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 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");
// 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, ir_core::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, ir_core::Instr::Add)));
}
#[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 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 max_func = &program.modules[0].functions[0];
// Should have multiple blocks for if-else
assert!(max_func.blocks.len() >= 3);
}
#[test]
fn test_hip_lowering() {
let code = "
fn test_hip() {
let g = alloc int;
mutate g as x {
let y = x + 1;
}
}
";
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();
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc)));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::ReadGate)));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::WriteGate)));
}
#[test]
fn test_host_contract_call_lowering() {
let code = "
declare contract Gfx host {}
declare contract Log host {}
fn main() {
Gfx.clear(0);
Log.write(\"Hello\");
}
";
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();
// Gfx.clear -> 0x1001
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x1001))));
// Log.write -> 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]
fn test_invalid_contract_call_lowering() {
let code = "
declare contract Gfx host {}
fn main() {
Gfx.invalidMethod(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 invalid
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
// Should be a regular call (which might fail later or be a dummy)
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _))));
}
}

View File

@ -827,3 +827,157 @@ impl Node {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_parse_empty_file() {
let mut parser = Parser::new("", 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.imports.len(), 0);
assert_eq!(result.decls.len(), 0);
}
#[test]
fn test_parse_imports() {
let source = r#"
import std.io from "std";
import math from "./math.pbs";
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.imports.len(), 2);
if let Node::Import(ref imp) = result.imports[0] {
assert_eq!(imp.from, "std");
if let Node::ImportSpec(ref spec) = *imp.spec {
assert_eq!(spec.path, vec!["std", "io"]);
} else { panic!("Expected ImportSpec"); }
} else { panic!("Expected Import"); }
}
#[test]
fn test_parse_fn_decl() {
let source = r#"
fn add(a: int, b: int): int {
return a + b;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::FnDecl(ref f) = result.decls[0] {
assert_eq!(f.name, "add");
assert_eq!(f.params.len(), 2);
assert_eq!(f.params[0].name, "a");
assert_eq!(f.params[1].name, "b");
} else { panic!("Expected FnDecl"); }
}
#[test]
fn test_parse_type_decl() {
let source = r#"
pub declare struct Point {
x: int,
y: int
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::TypeDecl(ref t) = result.decls[0] {
assert_eq!(t.name, "Point");
assert_eq!(t.type_kind, "struct");
assert_eq!(t.vis, Some("pub".to_string()));
} else { panic!("Expected TypeDecl"); }
}
#[test]
fn test_parse_service_decl() {
let source = r#"
pub service Audio {
fn play(sound: Sound);
fn stop(): bool;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::ServiceDecl(ref s) = result.decls[0] {
assert_eq!(s.name, "Audio");
assert_eq!(s.members.len(), 2);
} else { panic!("Expected ServiceDecl"); }
}
#[test]
fn test_parse_expressions() {
let source = r#"
fn main() {
let x = 10 + 20 * 30;
let y = (x - 5) / 2;
foo(x, y);
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
}
#[test]
fn test_parse_if_when() {
let source = r#"
fn main(x: int) {
if x > 0 {
print("positive");
} else {
print("non-positive");
}
let msg = when {
x == 0 -> { return "zero"; },
x == 1 -> { return "one"; }
};
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
}
#[test]
fn test_parse_error_recovery() {
let source = r#"
fn bad() {
let x = ; // Missing init
let y = 10;
}
fn good() {}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file();
assert!(result.is_err());
}
#[test]
fn test_ast_json_snapshot() {
let source = r#"
fn main() {
return 42;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
let json = serde_json::to_string_pretty(&Node::File(result)).unwrap();
assert!(json.contains("\"kind\": \"File\""));
assert!(json.contains("\"kind\": \"FnDecl\""));
assert!(json.contains("\"name\": \"main\""));
}
}

View File

@ -382,3 +382,208 @@ impl<'a> Resolver<'a> {
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::frontends::pbs::*;
use crate::common::files::FileManager;
use crate::common::spans::Span;
use std::path::PathBuf;
fn setup_test(source: &str) -> (ast::FileNode, usize) {
let mut fm = FileManager::new();
let file_id = fm.add(PathBuf::from("test.pbs"), source.to_string());
let mut parser = parser::Parser::new(source, file_id);
(parser.parse_file().expect("Parsing failed"), file_id)
}
#[test]
fn test_duplicate_symbols() {
let source = "
declare struct Foo {}
declare struct Foo {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let result = collector.collect(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string())));
}
#[test]
fn test_namespace_collision() {
let source = "
declare struct Foo {}
fn Foo() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let result = collector.collect(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_NAMESPACE_COLLISION".to_string())));
}
#[test]
fn test_undefined_identifier() {
let source = "
fn main() {
let x = y;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
}
#[test]
fn test_local_variable_resolution() {
let source = "
fn main() {
let x = 10;
let y = x;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_ok());
}
#[test]
fn test_visibility_error() {
let source = "
import PrivateType from \"./other.pbs\"
fn main() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct MockProvider {
other: ModuleSymbols,
}
impl ModuleProvider for MockProvider {
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
if path == "./other.pbs" { Some(&self.other) } else { None }
}
}
let mut other_ts = SymbolTable::new();
other_ts.insert(Symbol {
name: "PrivateType".to_string(),
kind: SymbolKind::Struct,
namespace: Namespace::Type,
visibility: Visibility::FilePrivate,
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
}).unwrap();
let mock_provider = MockProvider {
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
};
let mut resolver = Resolver::new(&ms, &mock_provider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_VISIBILITY".to_string())));
}
#[test]
fn test_import_resolution() {
let source = "
import PubType from \"./other.pbs\"
fn main() {
let x: PubType = 10;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct MockProvider {
other: ModuleSymbols,
}
impl ModuleProvider for MockProvider {
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
if path == "./other.pbs" { Some(&self.other) } else { None }
}
}
let mut other_ts = SymbolTable::new();
other_ts.insert(Symbol {
name: "PubType".to_string(),
kind: SymbolKind::Struct,
namespace: Namespace::Type,
visibility: Visibility::Pub,
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
}).unwrap();
let mock_provider = MockProvider {
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
};
let mut resolver = Resolver::new(&ms, &mock_provider);
let result = resolver.resolve(&ast);
assert!(result.is_ok());
}
#[test]
fn test_invalid_import_module_not_found() {
let source = "
import NonExistent from \"./missing.pbs\"
fn main() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_INVALID_IMPORT".to_string())));
}
}

View File

@ -600,3 +600,176 @@ impl<'a> TypeChecker<'a> {
});
}
}
#[cfg(test)]
mod tests {
use crate::frontends::pbs::PbsFrontend;
use crate::frontends::Frontend;
use crate::common::files::FileManager;
use std::fs;
fn check_code(code: &str) -> Result<(), String> {
let mut file_manager = FileManager::new();
let temp_dir = tempfile::tempdir().unwrap();
let file_path = temp_dir.path().join("test.pbs");
fs::write(&file_path, code).unwrap();
let frontend = PbsFrontend;
match frontend.compile_to_ir(&file_path, &mut file_manager) {
Ok(_) => Ok(()),
Err(bundle) => {
let mut errors = Vec::new();
for diag in bundle.diagnostics {
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
errors.push(format!("{}: {}", code, diag.message));
}
let err_msg = errors.join(", ");
println!("Compilation failed: {}", err_msg);
Err(err_msg)
}
}
}
#[test]
fn test_type_mismatch_let() {
let code = "fn main() { let x: int = \"hello\"; }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_type_mismatch_return() {
let code = "fn main(): int { return \"hello\"; }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_type_mismatch_call() {
let code = "
fn foo(a: int) {}
fn main() {
foo(\"hello\");
}
";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_missing_return_path() {
let code = "fn foo(): int { if (true) { return 1; } }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_RETURN_PATH"));
}
#[test]
fn test_implicit_none_optional() {
let code = "fn foo(): optional<int> { if (true) { return some(1); } }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok()); // Implicit none allowed for optional
}
#[test]
fn test_valid_optional_assignment() {
let code = "fn main() { let x: optional<int> = none; let y: optional<int> = some(10); }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok());
}
#[test]
fn test_valid_result_usage() {
let code = "
fn foo(): result<int, string> {
if (true) {
return ok(10);
} else {
return err(\"error\");
}
}
";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok());
}
#[test]
fn test_unknown_type() {
let code = "fn main() { let x: UnknownType = 10; }";
let res = check_code(code);
assert!(res.is_err());
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]
fn test_void_return_ok() {
let code = "fn main() { return; }";
let res = check_code(code);
assert!(res.is_ok());
}
#[test]
fn test_binary_op_mismatch() {
let code = "fn main() { let x = 1 + \"hello\"; }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_struct_type_usage() {
let code = "
declare struct Point { x: int, y: int }
fn foo(p: Point) {}
fn main() {
// Struct literals not in v0, but we can have variables of struct type
}
";
let res = check_code(code);
assert!(res.is_ok());
}
#[test]
fn test_service_type_usage() {
let code = "
pub service MyService {
fn hello(name: string): void
}
fn foo(s: MyService) {}
";
let res = check_code(code);
assert!(res.is_ok());
}
}

View File

@ -6,3 +6,129 @@ pub mod validate;
pub use instr::{Instruction, InstrKind, Label};
pub use module::{Module, Function, Global, Param};
pub use types::Type;
#[cfg(test)]
mod tests {
use super::*;
use crate::ir_core::ids::{ConstId, FunctionId};
use crate::ir_core::const_pool::{ConstPool, ConstantValue};
use serde_json;
#[test]
fn test_vm_ir_serialization() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::String("Hello VM".to_string()));
let module = Module {
name: "test_module".to_string(),
const_pool,
functions: vec![Function {
id: FunctionId(1),
name: "main".to_string(),
params: vec![],
return_type: Type::Null,
body: vec![
Instruction::new(InstrKind::PushConst(ConstId(0)), None),
Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None),
Instruction::new(InstrKind::Ret, None),
],
}],
globals: vec![],
};
let json = serde_json::to_string_pretty(&module).unwrap();
let expected = r#"{
"name": "test_module",
"const_pool": {
"constants": [
{
"String": "Hello VM"
}
]
},
"functions": [
{
"id": 1,
"name": "main",
"params": [],
"return_type": "Null",
"body": [
{
"kind": {
"PushConst": 0
},
"span": null
},
{
"kind": {
"Call": {
"func_id": 2,
"arg_count": 1
}
},
"span": null
},
{
"kind": "Ret",
"span": null
}
]
}
],
"globals": []
}"#;
assert_eq!(json, expected);
}
#[test]
fn test_lowering_smoke() {
use crate::ir_core;
use crate::lowering::lower_program;
let mut const_pool = ir_core::ConstPool::new();
const_pool.insert(ir_core::ConstantValue::Int(42));
let program = ir_core::Program {
const_pool,
modules: vec![ir_core::Module {
name: "test_core".to_string(),
functions: vec![ir_core::Function {
id: FunctionId(10),
name: "start".to_string(),
params: vec![],
return_type: ir_core::Type::Void,
blocks: vec![ir_core::Block {
id: 0,
instrs: vec![
ir_core::Instr::PushConst(ConstId(0)),
],
terminator: ir_core::Terminator::Return,
}],
}],
}],
};
let vm_module = lower_program(&program).expect("Lowering failed");
assert_eq!(vm_module.name, "test_core");
assert_eq!(vm_module.functions.len(), 1);
let func = &vm_module.functions[0];
assert_eq!(func.name, "start");
assert_eq!(func.id, FunctionId(10));
assert_eq!(func.body.len(), 3);
match &func.body[0].kind {
InstrKind::Label(Label(l)) => assert!(l.contains("block_0")),
_ => panic!("Expected label"),
}
match &func.body[1].kind {
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
_ => panic!("Expected PushConst"),
}
match &func.body[2].kind {
InstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}
}

View File

@ -50,3 +50,49 @@ impl ConstPool {
self.insert(ConstantValue::String(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir_core::ids::ConstId;
#[test]
fn test_const_pool_deduplication() {
let mut pool = ConstPool::new();
let id1 = pool.insert(ConstantValue::Int(42));
let id2 = pool.insert(ConstantValue::String("hello".to_string()));
let id3 = pool.insert(ConstantValue::Int(42));
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(pool.constants.len(), 2);
}
#[test]
fn test_const_pool_deterministic_assignment() {
let mut pool = ConstPool::new();
let id0 = pool.insert(ConstantValue::Int(10));
let id1 = pool.insert(ConstantValue::Int(20));
let id2 = pool.insert(ConstantValue::Int(30));
assert_eq!(id0, ConstId(0));
assert_eq!(id1, ConstId(1));
assert_eq!(id2, ConstId(2));
}
#[test]
fn test_const_pool_serialization() {
let mut pool = ConstPool::new();
pool.insert(ConstantValue::Int(42));
pool.insert(ConstantValue::String("test".to_string()));
pool.insert(ConstantValue::Float(3.14));
let json = serde_json::to_string_pretty(&pool).unwrap();
assert!(json.contains("\"Int\": 42"));
assert!(json.contains("\"String\": \"test\""));
assert!(json.contains("\"Float\": 3.14"));
}
}

View File

@ -17,3 +17,86 @@ pub use function::*;
pub use block::*;
pub use instr::*;
pub use terminator::*;
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_ir_core_manual_construction() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::String("hello".to_string()));
let program = Program {
const_pool,
modules: vec![Module {
name: "main".to_string(),
functions: vec![Function {
id: FunctionId(10),
name: "entry".to_string(),
params: vec![],
return_type: Type::Void,
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::Call(FunctionId(11), 0),
],
terminator: Terminator::Return,
}],
}],
}],
};
let json = serde_json::to_string_pretty(&program).unwrap();
let expected = r#"{
"const_pool": {
"constants": [
{
"String": "hello"
}
]
},
"modules": [
{
"name": "main",
"functions": [
{
"id": 10,
"name": "entry",
"params": [],
"return_type": "Void",
"blocks": [
{
"id": 0,
"instrs": [
{
"PushConst": 0
},
{
"Call": [
11,
0
]
}
],
"terminator": "Return"
}
]
}
]
}
]
}"#;
assert_eq!(json, expected);
}
#[test]
fn test_ir_core_ids() {
assert_eq!(serde_json::to_string(&FunctionId(1)).unwrap(), "1");
assert_eq!(serde_json::to_string(&ConstId(2)).unwrap(), "2");
assert_eq!(serde_json::to_string(&TypeId(3)).unwrap(), "3");
}
}

View File

@ -121,3 +121,87 @@ fn lower_type(ty: &ir_core::Type) -> ir::Type {
ir_core::Type::Function { .. } => ir::Type::Function,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir_core;
use crate::ir_core::*;
use crate::ir::*;
#[test]
fn test_full_lowering() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::Int(100)); // ConstId(0)
let program = Program {
const_pool,
modules: vec![ir_core::Module {
name: "test_mod".to_string(),
functions: vec![ir_core::Function {
id: FunctionId(1),
name: "main".to_string(),
params: vec![],
return_type: ir_core::Type::Void,
blocks: vec![
Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::Call(FunctionId(2), 1),
],
terminator: Terminator::Jump(1),
},
Block {
id: 1,
instrs: vec![
Instr::Syscall(42),
],
terminator: Terminator::Return,
},
],
}],
}],
};
let vm_module = lower_program(&program).expect("Lowering failed");
assert_eq!(vm_module.name, "test_mod");
let func = &vm_module.functions[0];
assert_eq!(func.name, "main");
assert_eq!(func.body.len(), 7);
match &func.body[0].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_0"),
_ => panic!("Expected label block_0"),
}
match &func.body[1].kind {
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
_ => panic!("Expected PushConst 0"),
}
match &func.body[2].kind {
InstrKind::Call { func_id, arg_count } => {
assert_eq!(func_id.0, 2);
assert_eq!(*arg_count, 1);
}
_ => panic!("Expected Call"),
}
match &func.body[3].kind {
InstrKind::Jmp(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected Jmp block_1"),
}
match &func.body[4].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected label block_1"),
}
match &func.body[5].kind {
InstrKind::Syscall(id) => assert_eq!(*id, 42),
_ => panic!("Expected Syscall 42"),
}
match &func.body[6].kind {
InstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}
}

View File

@ -1,44 +0,0 @@
use prometeu_compiler::ir::module::{Module, Function};
use prometeu_compiler::ir::instr::{Instruction, InstrKind};
use prometeu_compiler::ir::types::Type;
use prometeu_compiler::ir_core::ids::FunctionId;
use prometeu_compiler::ir_core::const_pool::ConstantValue;
use prometeu_compiler::backend::emit_module;
use prometeu_compiler::common::files::FileManager;
use prometeu_bytecode::pbc::parse_pbc;
use prometeu_bytecode::pbc::ConstantPoolEntry;
#[test]
fn test_emit_module_with_const_pool() {
let mut module = Module::new("test".to_string());
// Insert constants into IR module
let id_int = module.const_pool.insert(ConstantValue::Int(12345));
let id_str = module.const_pool.insert(ConstantValue::String("hello".to_string()));
let function = Function {
id: FunctionId(0),
name: "main".to_string(),
params: vec![],
return_type: Type::Void,
body: vec![
Instruction::new(InstrKind::PushConst(id_int), None),
Instruction::new(InstrKind::PushConst(id_str), None),
Instruction::new(InstrKind::Ret, None),
],
};
module.functions.push(function);
let file_manager = FileManager::new();
let result = emit_module(&module, &file_manager).expect("Failed to emit module");
let pbc = parse_pbc(&result.rom).expect("Failed to parse emitted PBC");
// Check constant pool in PBC
// PBC CP has Null at index 0, so our constants should be at 1 and 2
assert_eq!(pbc.cp.len(), 3);
assert_eq!(pbc.cp[0], ConstantPoolEntry::Null);
assert_eq!(pbc.cp[1], ConstantPoolEntry::Int64(12345));
assert_eq!(pbc.cp[2], ConstantPoolEntry::String("hello".to_string()));
}

View File

@ -1,29 +0,0 @@
use std::fs;
use tempfile::tempdir;
use prometeu_compiler::compiler;
#[test]
fn test_project_root_and_entry_resolution() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
// Create prometeu.json
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "src/main.pbs"
}"#,
).unwrap();
// Create src directory and main.pbs
fs::create_dir(project_dir.join("src")).unwrap();
fs::write(project_dir.join("src/main.pbs"), "").unwrap();
// Call compile
let result = compiler::compile(project_dir);
// It should now succeed or at least fail at a later stage,
// but the point of this test is config resolution.
assert!(result.is_ok(), "Failed to compile: {:?}", result.err());
}

View File

@ -1,43 +0,0 @@
use prometeu_compiler::ir_core::const_pool::{ConstPool, ConstantValue};
use prometeu_compiler::ir_core::ids::ConstId;
#[test]
fn test_const_pool_deduplication() {
let mut pool = ConstPool::new();
let id1 = pool.insert(ConstantValue::Int(42));
let id2 = pool.insert(ConstantValue::String("hello".to_string()));
let id3 = pool.insert(ConstantValue::Int(42));
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(pool.constants.len(), 2);
}
#[test]
fn test_const_pool_deterministic_assignment() {
let mut pool = ConstPool::new();
let id0 = pool.insert(ConstantValue::Int(10));
let id1 = pool.insert(ConstantValue::Int(20));
let id2 = pool.insert(ConstantValue::Int(30));
assert_eq!(id0, ConstId(0));
assert_eq!(id1, ConstId(1));
assert_eq!(id2, ConstId(2));
}
#[test]
fn test_const_pool_serialization() {
let mut pool = ConstPool::new();
pool.insert(ConstantValue::Int(42));
pool.insert(ConstantValue::String("test".to_string()));
pool.insert(ConstantValue::Float(3.14));
let json = serde_json::to_string_pretty(&pool).unwrap();
// Check for deterministic shape in JSON
assert!(json.contains("\"Int\": 42"));
assert!(json.contains("\"String\": \"test\""));
assert!(json.contains("\"Float\": 3.14"));
}

View File

@ -1,80 +0,0 @@
use prometeu_compiler::ir_core::*;
use serde_json;
#[test]
fn test_ir_core_manual_construction() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::String("hello".to_string()));
let program = Program {
const_pool,
modules: vec![Module {
name: "main".to_string(),
functions: vec![Function {
id: FunctionId(10),
name: "entry".to_string(),
params: vec![],
return_type: Type::Void,
blocks: vec![Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::Call(FunctionId(11), 0),
],
terminator: Terminator::Return,
}],
}],
}],
};
let json = serde_json::to_string_pretty(&program).unwrap();
// Snapshot check for deterministic shape
let expected = r#"{
"const_pool": {
"constants": [
{
"String": "hello"
}
]
},
"modules": [
{
"name": "main",
"functions": [
{
"id": 10,
"name": "entry",
"params": [],
"return_type": "Void",
"blocks": [
{
"id": 0,
"instrs": [
{
"PushConst": 0
},
{
"Call": [
11,
0
]
}
],
"terminator": "Return"
}
]
}
]
}
]
}"#;
assert_eq!(json, expected);
}
#[test]
fn test_ir_core_ids() {
assert_eq!(serde_json::to_string(&FunctionId(1)).unwrap(), "1");
assert_eq!(serde_json::to_string(&ConstId(2)).unwrap(), "2");
assert_eq!(serde_json::to_string(&TypeId(3)).unwrap(), "3");
}

View File

@ -1,156 +0,0 @@
use prometeu_compiler::frontends::pbs::lexer::Lexer;
use prometeu_compiler::frontends::pbs::token::TokenKind;
#[test]
fn test_lex_basic_tokens() {
let source = "( ) { } [ ] , . : ; -> = == + - * / % ! != < > <= >= && ||";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::OpenParen, TokenKind::CloseParen,
TokenKind::OpenBrace, TokenKind::CloseBrace,
TokenKind::OpenBracket, TokenKind::CloseBracket,
TokenKind::Comma, TokenKind::Dot, TokenKind::Colon, TokenKind::Semicolon,
TokenKind::Arrow, TokenKind::Assign, TokenKind::Eq,
TokenKind::Plus, TokenKind::Minus, TokenKind::Star, TokenKind::Slash, TokenKind::Percent,
TokenKind::Not, TokenKind::Neq,
TokenKind::Lt, TokenKind::Gt, TokenKind::Lte, TokenKind::Gte,
TokenKind::And, TokenKind::Or,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_keywords() {
let source = "import pub mod service fn let mut declare struct contract host error optional result some none ok err if else when for in return handle borrow mutate peek take alloc weak as";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Import, TokenKind::Pub, TokenKind::Mod, TokenKind::Service,
TokenKind::Fn, TokenKind::Let, TokenKind::Mut, TokenKind::Declare,
TokenKind::Struct, TokenKind::Contract, TokenKind::Host, TokenKind::Error,
TokenKind::Optional, TokenKind::Result, TokenKind::Some, TokenKind::None,
TokenKind::Ok, TokenKind::Err, TokenKind::If, TokenKind::Else,
TokenKind::When, TokenKind::For, TokenKind::In, TokenKind::Return,
TokenKind::Handle, TokenKind::Borrow, TokenKind::Mutate, TokenKind::Peek,
TokenKind::Take, TokenKind::Alloc, TokenKind::Weak, TokenKind::As,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_identifiers() {
let source = "foo bar _baz qux123";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Identifier("foo".to_string()),
TokenKind::Identifier("bar".to_string()),
TokenKind::Identifier("_baz".to_string()),
TokenKind::Identifier("qux123".to_string()),
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_literals() {
let source = "123 3.14 255b \"hello world\"";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::IntLit(123),
TokenKind::FloatLit(3.14),
TokenKind::BoundedLit(255),
TokenKind::StringLit("hello world".to_string()),
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_comments() {
let source = "let x = 10; // this is a comment\nlet y = 20;";
let mut lexer = Lexer::new(source, 0);
let expected = vec![
TokenKind::Let,
TokenKind::Identifier("x".to_string()),
TokenKind::Assign,
TokenKind::IntLit(10),
TokenKind::Semicolon,
TokenKind::Let,
TokenKind::Identifier("y".to_string()),
TokenKind::Assign,
TokenKind::IntLit(20),
TokenKind::Semicolon,
TokenKind::Eof,
];
for kind in expected {
let token = lexer.next_token();
assert_eq!(token.kind, kind);
}
}
#[test]
fn test_lex_spans() {
let source = "let x = 10;";
let mut lexer = Lexer::new(source, 0);
let t1 = lexer.next_token(); // let
assert_eq!(t1.span.start, 0);
assert_eq!(t1.span.end, 3);
let t2 = lexer.next_token(); // x
assert_eq!(t2.span.start, 4);
assert_eq!(t2.span.end, 5);
let t3 = lexer.next_token(); // =
assert_eq!(t3.span.start, 6);
assert_eq!(t3.span.end, 7);
let t4 = lexer.next_token(); // 10
assert_eq!(t4.span.start, 8);
assert_eq!(t4.span.end, 10);
let t5 = lexer.next_token(); // ;
assert_eq!(t5.span.start, 10);
assert_eq!(t5.span.end, 11);
}
#[test]
fn test_lex_invalid_tokens() {
let source = "@ #";
let mut lexer = Lexer::new(source, 0);
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
assert_eq!(lexer.next_token().kind, TokenKind::Eof);
}
#[test]
fn test_lex_unterminated_string() {
let source = "\"hello";
let mut lexer = Lexer::new(source, 0);
assert!(matches!(lexer.next_token().kind, TokenKind::Invalid(_)));
}

View File

@ -1,89 +0,0 @@
use prometeu_compiler::ir_core;
use prometeu_compiler::ir_core::*;
use prometeu_compiler::lowering::lower_program;
use prometeu_compiler::ir::*;
#[test]
fn test_full_lowering() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::Int(100)); // ConstId(0)
let program = Program {
const_pool,
modules: vec![ir_core::Module {
name: "test_mod".to_string(),
functions: vec![ir_core::Function {
id: FunctionId(1),
name: "main".to_string(),
params: vec![],
return_type: ir_core::Type::Void,
blocks: vec![
Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::Call(FunctionId(2), 1),
],
terminator: Terminator::Jump(1),
},
Block {
id: 1,
instrs: vec![
Instr::Syscall(42),
],
terminator: Terminator::Return,
},
],
}],
}],
};
let vm_module = lower_program(&program).expect("Lowering failed");
assert_eq!(vm_module.name, "test_mod");
let func = &vm_module.functions[0];
assert_eq!(func.name, "main");
// Instructions expected:
// 0: Label block_0
// 1: PushConst 0
// 2: Call { func_id: 2, arg_count: 1 }
// 3: Jmp block_1
// 4: Label block_1
// 5: Syscall 42
// 6: Ret
assert_eq!(func.body.len(), 7);
match &func.body[0].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_0"),
_ => panic!("Expected label block_0"),
}
match &func.body[1].kind {
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
_ => panic!("Expected PushConst 0"),
}
match &func.body[2].kind {
InstrKind::Call { func_id, arg_count } => {
assert_eq!(func_id.0, 2);
assert_eq!(*arg_count, 1);
}
_ => panic!("Expected Call"),
}
match &func.body[3].kind {
InstrKind::Jmp(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected Jmp block_1"),
}
match &func.body[4].kind {
InstrKind::Label(Label(l)) => assert_eq!(l, "block_1"),
_ => panic!("Expected label block_1"),
}
match &func.body[5].kind {
InstrKind::Syscall(id) => assert_eq!(*id, 42),
_ => panic!("Expected Syscall 42"),
}
match &func.body[6].kind {
InstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}

View File

@ -1,155 +0,0 @@
use prometeu_compiler::frontends::pbs::parser::Parser;
use prometeu_compiler::frontends::pbs::ast::*;
use serde_json;
#[test]
fn test_parse_empty_file() {
let mut parser = Parser::new("", 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.imports.len(), 0);
assert_eq!(result.decls.len(), 0);
}
#[test]
fn test_parse_imports() {
let source = r#"
import std.io from "std";
import math from "./math.pbs";
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.imports.len(), 2);
if let Node::Import(ref imp) = result.imports[0] {
assert_eq!(imp.from, "std");
if let Node::ImportSpec(ref spec) = *imp.spec {
assert_eq!(spec.path, vec!["std", "io"]);
} else { panic!("Expected ImportSpec"); }
} else { panic!("Expected Import"); }
}
#[test]
fn test_parse_fn_decl() {
let source = r#"
fn add(a: int, b: int): int {
return a + b;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::FnDecl(ref f) = result.decls[0] {
assert_eq!(f.name, "add");
assert_eq!(f.params.len(), 2);
assert_eq!(f.params[0].name, "a");
assert_eq!(f.params[1].name, "b");
} else { panic!("Expected FnDecl"); }
}
#[test]
fn test_parse_type_decl() {
let source = r#"
pub declare struct Point {
x: int,
y: int
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::TypeDecl(ref t) = result.decls[0] {
assert_eq!(t.name, "Point");
assert_eq!(t.type_kind, "struct");
assert_eq!(t.vis, Some("pub".to_string()));
} else { panic!("Expected TypeDecl"); }
}
#[test]
fn test_parse_service_decl() {
let source = r#"
pub service Audio {
fn play(sound: Sound);
fn stop(): bool;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
if let Node::ServiceDecl(ref s) = result.decls[0] {
assert_eq!(s.name, "Audio");
assert_eq!(s.members.len(), 2);
} else { panic!("Expected ServiceDecl"); }
}
#[test]
fn test_parse_expressions() {
let source = r#"
fn main() {
let x = 10 + 20 * 30;
let y = (x - 5) / 2;
foo(x, y);
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
}
#[test]
fn test_parse_if_when() {
let source = r#"
fn main(x: int) {
if x > 0 {
print("positive");
} else {
print("non-positive");
}
let msg = when {
x == 0 -> { return "zero"; },
x == 1 -> { return "one"; }
};
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
assert_eq!(result.decls.len(), 1);
}
#[test]
fn test_parse_error_recovery() {
let source = r#"
fn bad() {
let x = ; // Missing init
let y = 10;
}
fn good() {}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file();
// It should fail but we should see both good and bad decls if we didn't return Err early
// Currently parse_file returns Err if there are any errors.
assert!(result.is_err());
}
#[test]
fn test_ast_json_snapshot() {
let source = r#"
fn main() {
return 42;
}
"#;
let mut parser = Parser::new(source, 0);
let result = parser.parse_file().unwrap();
let json = serde_json::to_string_pretty(&Node::File(result)).unwrap();
// We don't assert the exact string here because spans will vary,
// but we check that it serializes correctly and has the "kind" field.
assert!(json.contains("\"kind\": \"File\""));
assert!(json.contains("\"kind\": \"FnDecl\""));
assert!(json.contains("\"name\": \"main\""));
}

View File

@ -1,112 +0,0 @@
use prometeu_compiler::frontends::pbs::parser::Parser;
use prometeu_compiler::frontends::pbs::collector::SymbolCollector;
use prometeu_compiler::frontends::pbs::symbols::ModuleSymbols;
use prometeu_compiler::frontends::pbs::lowering::Lowerer;
use prometeu_compiler::ir_core;
#[test]
fn test_host_contract_call_lowering() {
let code = "
declare contract Gfx host {}
declare contract Log host {}
fn main() {
Gfx.clear(0);
Log.write(\"Hello\");
}
";
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();
// Gfx.clear -> 0x1001
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(0x1001))));
// Log.write -> 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]
fn test_invalid_contract_call_lowering() {
let code = "
declare contract Gfx host {}
fn main() {
Gfx.invalidMethod(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 invalid
assert!(!instrs.iter().any(|i| matches!(i, ir_core::Instr::Syscall(_))));
// Should be a regular call (which might fail later or be a dummy)
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Call(_, _))));
}

View File

@ -1,64 +0,0 @@
use prometeu_compiler::frontends::pbs::PbsFrontend;
use prometeu_compiler::frontends::Frontend;
use prometeu_compiler::common::files::FileManager;
use std::fs;
use tempfile::tempdir;
fn get_diagnostics(code: &str) -> String {
let mut file_manager = FileManager::new();
let temp_dir = tempdir().unwrap();
let file_path = temp_dir.path().join("main.pbs");
fs::write(&file_path, code).unwrap();
let frontend = PbsFrontend;
match frontend.compile_to_ir(&file_path, &mut file_manager) {
Ok(_) => "[]".to_string(),
Err(bundle) => bundle.to_json(&file_manager),
}
}
#[test]
fn test_golden_parse_error() {
let code = "fn main() { let x = ; }";
let json = get_diagnostics(code);
println!("{}", json);
assert!(json.contains("E_PARSE_UNEXPECTED_TOKEN"));
assert!(json.contains("Expected expression"));
}
#[test]
fn test_golden_lex_error() {
let code = "fn main() { let x = \"hello ; }";
let json = get_diagnostics(code);
println!("{}", json);
assert!(json.contains("E_LEX_UNTERMINATED_STRING"));
}
#[test]
fn test_golden_resolve_error() {
let code = "fn main() { let x = undefined_var; }";
let json = get_diagnostics(code);
println!("{}", json);
assert!(json.contains("E_RESOLVE_UNDEFINED"));
}
#[test]
fn test_golden_type_error() {
let code = "fn main() { let x: int = \"hello\"; }";
let json = get_diagnostics(code);
println!("{}", json);
assert!(json.contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_golden_namespace_collision() {
let code = "
declare struct Foo {}
fn main() {
let Foo = 1;
}
";
let json = get_diagnostics(code);
println!("{}", json);
assert!(json.contains("E_RESOLVE_NAMESPACE_COLLISION"));
}

View File

@ -1,50 +0,0 @@
use prometeu_compiler::compiler;
use prometeu_bytecode::pbc::parse_pbc;
use prometeu_bytecode::disasm::disasm;
use prometeu_bytecode::opcode::OpCode;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_compile_hip_program() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
// Create prometeu.json
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
).unwrap();
// Create main.pbs with HIP effects
let code = "
fn frame(): void {
let x = alloc int;
mutate x as v {
let y = v + 1;
}
}
";
fs::write(project_dir.join("main.pbs"), code).unwrap();
// Compile
let unit = compiler::compile(project_dir).expect("Failed to compile");
// Parse PBC
let pbc = parse_pbc(&unit.rom).expect("Failed to parse PBC");
// Disassemble
let instrs = disasm(&pbc.rom).expect("Failed to disassemble");
// Verify opcodes exist in bytecode
let opcodes: Vec<_> = instrs.iter().map(|i| i.opcode).collect();
assert!(opcodes.contains(&OpCode::Alloc));
assert!(opcodes.contains(&OpCode::LoadRef)); // From ReadGate
assert!(opcodes.contains(&OpCode::StoreRef)); // From WriteGate
assert!(opcodes.contains(&OpCode::Add));
assert!(opcodes.contains(&OpCode::Ret));
}

View File

@ -1,93 +0,0 @@
use prometeu_compiler::compiler;
use prometeu_bytecode::pbc::parse_pbc;
use prometeu_bytecode::disasm::disasm;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_golden_bytecode_snapshot() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
).unwrap();
let code = r#"
declare contract Gfx host {}
fn helper(val: int): int {
return val * 2;
}
fn main() {
Gfx.clear(0);
let x = 10;
if (x > 5) {
let y = helper(x);
}
let buf = alloc int;
mutate buf as b {
let current = b + 1;
}
}
"#;
fs::write(project_dir.join("main.pbs"), code).unwrap();
let unit = compiler::compile(project_dir).expect("Failed to compile");
let pbc = parse_pbc(&unit.rom).expect("Failed to parse PBC");
let instrs = disasm(&pbc.rom).expect("Failed to disassemble");
let mut disasm_text = String::new();
for instr in instrs {
let operands_str = instr.operands.iter()
.map(|o| format!("{:?}", o))
.collect::<Vec<_>>()
.join(" ");
let line = if operands_str.is_empty() {
format!("{:04X} {:?}\n", instr.pc, instr.opcode)
} else {
format!("{:04X} {:?} {}\n", instr.pc, instr.opcode, operands_str.trim())
};
disasm_text.push_str(&line);
}
let expected_disasm = r#"0000 GetLocal U32(0)
0006 PushConst U32(1)
000C Mul
000E Ret
0010 PushConst U32(2)
0016 Syscall U32(4097)
001C PushConst U32(3)
0022 SetLocal U32(0)
0028 GetLocal U32(0)
002E PushConst U32(4)
0034 Gt
0036 JmpIfFalse U32(94)
003C Jmp U32(66)
0042 GetLocal U32(0)
0048 Call U32(0) U32(1)
0052 SetLocal U32(1)
0058 Jmp U32(100)
005E Jmp U32(100)
0064 Alloc
0066 SetLocal U32(1)
006C GetLocal U32(1)
0072 LoadRef U32(0)
0078 SetLocal U32(2)
007E GetLocal U32(2)
0084 PushConst U32(5)
008A Add
008C SetLocal U32(3)
0092 GetLocal U32(2)
0098 StoreRef U32(0)
009E Ret
"#;
assert_eq!(disasm_text, expected_disasm);
}

View File

@ -1,95 +0,0 @@
use prometeu_compiler::frontends::pbs::parser::Parser;
use prometeu_compiler::frontends::pbs::collector::SymbolCollector;
use prometeu_compiler::frontends::pbs::symbols::ModuleSymbols;
use prometeu_compiler::frontends::pbs::lowering::Lowerer;
use prometeu_compiler::ir_core;
#[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 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");
// 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, ir_core::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, ir_core::Instr::Add)));
}
#[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 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 max_func = &program.modules[0].functions[0];
// Should have multiple blocks for if-else
assert!(max_func.blocks.len() >= 3);
}
#[test]
fn test_hip_lowering() {
let code = "
fn test_hip() {
let g = alloc int;
mutate g as x {
let y = x + 1;
}
}
";
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();
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::Alloc)));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::ReadGate)));
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::WriteGate)));
}

View File

@ -1,200 +0,0 @@
use prometeu_compiler::frontends::pbs::*;
use prometeu_compiler::common::files::FileManager;
use prometeu_compiler::common::spans::Span;
use std::path::PathBuf;
fn setup_test(source: &str) -> (ast::FileNode, usize) {
let mut fm = FileManager::new();
let file_id = fm.add(PathBuf::from("test.pbs"), source.to_string());
let mut parser = parser::Parser::new(source, file_id);
(parser.parse_file().expect("Parsing failed"), file_id)
}
#[test]
fn test_duplicate_symbols() {
let source = "
declare struct Foo {}
declare struct Foo {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let result = collector.collect(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string())));
}
#[test]
fn test_namespace_collision() {
let source = "
declare struct Foo {}
fn Foo() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let result = collector.collect(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_NAMESPACE_COLLISION".to_string())));
}
#[test]
fn test_undefined_identifier() {
let source = "
fn main() {
let x = y;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string())));
}
#[test]
fn test_local_variable_resolution() {
let source = "
fn main() {
let x = 10;
let y = x;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_ok());
}
#[test]
fn test_visibility_error() {
let source = "
import PrivateType from \"./other.pbs\"
fn main() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct MockProvider {
other: ModuleSymbols,
}
impl ModuleProvider for MockProvider {
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
if path == "./other.pbs" { Some(&self.other) } else { None }
}
}
let mut other_ts = SymbolTable::new();
other_ts.insert(Symbol {
name: "PrivateType".to_string(),
kind: SymbolKind::Struct,
namespace: Namespace::Type,
visibility: Visibility::FilePrivate,
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
}).unwrap();
let mock_provider = MockProvider {
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
};
let mut resolver = Resolver::new(&ms, &mock_provider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_VISIBILITY".to_string())));
}
#[test]
fn test_import_resolution() {
let source = "
import PubType from \"./other.pbs\"
fn main() {
let x: PubType = 10;
}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct MockProvider {
other: ModuleSymbols,
}
impl ModuleProvider for MockProvider {
fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> {
if path == "./other.pbs" { Some(&self.other) } else { None }
}
}
let mut other_ts = SymbolTable::new();
other_ts.insert(Symbol {
name: "PubType".to_string(),
kind: SymbolKind::Struct,
namespace: Namespace::Type,
visibility: Visibility::Pub,
ty: None,
is_host: false,
span: Span::new(1, 0, 0),
}).unwrap();
let mock_provider = MockProvider {
other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() },
};
let mut resolver = Resolver::new(&ms, &mock_provider);
let result = resolver.resolve(&ast);
assert!(result.is_ok());
}
#[test]
fn test_invalid_import_module_not_found() {
let source = "
import NonExistent from \"./missing.pbs\"
fn main() {}
";
let (ast, _) = setup_test(source);
let mut collector = SymbolCollector::new();
let (ts, vs) = collector.collect(&ast).expect("Collection failed");
let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs };
struct EmptyProvider;
impl ModuleProvider for EmptyProvider {
fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None }
}
let mut resolver = Resolver::new(&ms, &EmptyProvider);
let result = resolver.resolve(&ast);
assert!(result.is_err());
let bundle = result.unwrap_err();
assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_INVALID_IMPORT".to_string())));
}

View File

@ -1,169 +0,0 @@
use prometeu_compiler::frontends::pbs::PbsFrontend;
use prometeu_compiler::frontends::Frontend;
use prometeu_compiler::common::files::FileManager;
use std::fs;
fn check_code(code: &str) -> Result<(), String> {
let mut file_manager = FileManager::new();
let temp_dir = tempfile::tempdir().unwrap();
let file_path = temp_dir.path().join("test.pbs");
fs::write(&file_path, code).unwrap();
let frontend = PbsFrontend;
match frontend.compile_to_ir(&file_path, &mut file_manager) {
Ok(_) => Ok(()),
Err(bundle) => {
let mut errors = Vec::new();
for diag in bundle.diagnostics {
let code = diag.code.unwrap_or_else(|| "NO_CODE".to_string());
errors.push(format!("{}: {}", code, diag.message));
}
let err_msg = errors.join(", ");
println!("Compilation failed: {}", err_msg);
Err(err_msg)
}
}
}
#[test]
fn test_type_mismatch_let() {
let code = "fn main() { let x: int = \"hello\"; }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_type_mismatch_return() {
let code = "fn main(): int { return \"hello\"; }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_type_mismatch_call() {
let code = "
fn foo(a: int) {}
fn main() {
foo(\"hello\");
}
";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_missing_return_path() {
let code = "fn foo(): int { if (true) { return 1; } }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_RETURN_PATH"));
}
#[test]
fn test_implicit_none_optional() {
let code = "fn foo(): optional<int> { if (true) { return some(1); } }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok()); // Implicit none allowed for optional
}
#[test]
fn test_valid_optional_assignment() {
let code = "fn main() { let x: optional<int> = none; let y: optional<int> = some(10); }";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok());
}
#[test]
fn test_valid_result_usage() {
let code = "
fn foo(): result<int, string> {
if (true) {
return ok(10);
} else {
return err(\"error\");
}
}
";
let res = check_code(code);
if let Err(e) = &res { println!("Error: {}", e); }
assert!(res.is_ok());
}
#[test]
fn test_unknown_type() {
let code = "fn main() { let x: UnknownType = 10; }";
let res = check_code(code);
assert!(res.is_err());
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]
fn test_void_return_ok() {
let code = "fn main() { return; }";
let res = check_code(code);
assert!(res.is_ok());
}
#[test]
fn test_binary_op_mismatch() {
let code = "fn main() { let x = 1 + \"hello\"; }";
let res = check_code(code);
assert!(res.is_err());
assert!(res.unwrap_err().contains("E_TYPE_MISMATCH"));
}
#[test]
fn test_struct_type_usage() {
let code = "
declare struct Point { x: int, y: int }
fn foo(p: Point) {}
fn main() {
// Struct literals not in v0, but we can have variables of struct type
}
";
let res = check_code(code);
assert!(res.is_ok());
}
#[test]
fn test_service_type_usage() {
let code = "
pub service MyService {
fn hello(name: string): void
}
fn foo(s: MyService) {}
";
let res = check_code(code);
assert!(res.is_ok());
}

View File

@ -1,124 +0,0 @@
use prometeu_compiler::ir::*;
use prometeu_compiler::ir_core::ids::{ConstId, FunctionId};
use prometeu_compiler::ir_core::const_pool::{ConstPool, ConstantValue};
use serde_json;
#[test]
fn test_vm_ir_serialization() {
let mut const_pool = ConstPool::new();
const_pool.insert(ConstantValue::String("Hello VM".to_string()));
let module = Module {
name: "test_module".to_string(),
const_pool,
functions: vec![Function {
id: FunctionId(1),
name: "main".to_string(),
params: vec![],
return_type: Type::Null,
body: vec![
Instruction::new(InstrKind::PushConst(ConstId(0)), None),
Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None),
Instruction::new(InstrKind::Ret, None),
],
}],
globals: vec![],
};
let json = serde_json::to_string_pretty(&module).unwrap();
// Snapshot check
let expected = r#"{
"name": "test_module",
"const_pool": {
"constants": [
{
"String": "Hello VM"
}
]
},
"functions": [
{
"id": 1,
"name": "main",
"params": [],
"return_type": "Null",
"body": [
{
"kind": {
"PushConst": 0
},
"span": null
},
{
"kind": {
"Call": {
"func_id": 2,
"arg_count": 1
}
},
"span": null
},
{
"kind": "Ret",
"span": null
}
]
}
],
"globals": []
}"#;
assert_eq!(json, expected);
}
#[test]
fn test_lowering_smoke() {
use prometeu_compiler::ir_core;
use prometeu_compiler::lowering::lower_program;
let mut const_pool = ir_core::ConstPool::new();
const_pool.insert(ir_core::ConstantValue::Int(42));
let program = ir_core::Program {
const_pool,
modules: vec![ir_core::Module {
name: "test_core".to_string(),
functions: vec![ir_core::Function {
id: FunctionId(10),
name: "start".to_string(),
params: vec![],
return_type: ir_core::Type::Void,
blocks: vec![ir_core::Block {
id: 0,
instrs: vec![
ir_core::Instr::PushConst(ConstId(0)),
],
terminator: ir_core::Terminator::Return,
}],
}],
}],
};
let vm_module = lower_program(&program).expect("Lowering failed");
assert_eq!(vm_module.name, "test_core");
assert_eq!(vm_module.functions.len(), 1);
let func = &vm_module.functions[0];
assert_eq!(func.name, "start");
assert_eq!(func.id, FunctionId(10));
// Check if instructions were lowered (label + pushconst + ret)
assert_eq!(func.body.len(), 3);
match &func.body[0].kind {
InstrKind::Label(Label(l)) => assert!(l.contains("block_0")),
_ => panic!("Expected label"),
}
match &func.body[1].kind {
InstrKind::PushConst(id) => assert_eq!(id.0, 0),
_ => panic!("Expected PushConst"),
}
match &func.body[2].kind {
InstrKind::Ret => (),
_ => panic!("Expected Ret"),
}
}