dev/pbs #8
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(_)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(_, _))));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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\""));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
@ -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(_)));
|
||||
}
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
@ -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\""));
|
||||
}
|
||||
@ -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(_, _))));
|
||||
}
|
||||
@ -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"));
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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)));
|
||||
}
|
||||
@ -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())));
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user