From b814a6c48dbed92c3350112b41fc0a4466a530e1 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Wed, 28 Jan 2026 19:21:30 +0000 Subject: [PATCH] pr 14 --- .../tests/pbs_golden_tests.rs | 93 ++++++ test-cartridges/color-square-pbs/src/main.pbs | 281 ------------------ 2 files changed, 93 insertions(+), 281 deletions(-) create mode 100644 crates/prometeu-compiler/tests/pbs_golden_tests.rs delete mode 100644 test-cartridges/color-square-pbs/src/main.pbs diff --git a/crates/prometeu-compiler/tests/pbs_golden_tests.rs b/crates/prometeu-compiler/tests/pbs_golden_tests.rs new file mode 100644 index 00000000..5e1630f2 --- /dev/null +++ b/crates/prometeu-compiler/tests/pbs_golden_tests.rs @@ -0,0 +1,93 @@ +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::>() + .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); +} diff --git a/test-cartridges/color-square-pbs/src/main.pbs b/test-cartridges/color-square-pbs/src/main.pbs deleted file mode 100644 index 45fcd62d..00000000 --- a/test-cartridges/color-square-pbs/src/main.pbs +++ /dev/null @@ -1,281 +0,0 @@ -# arquivo: g/base.pbs -# services => singletons que soh possuem metodos, usados para DI -// default: vis?vel s? no arquivo -// mod X: exporta para o m?dulo (diret?rio) -// pub X: exporta para quem importar o arquivo (API p?blica do arquivo) -// quem importa o arquivo nao pode usar o mod, arquivos no mesmo diretorio nao precisam de import -pub service base -{ - // fn define um funcao - fn do_something(a: long, b: int, c: float, d: char, e: string, f: bool): void - { - // do something - } -} - -# arquivo: a/service.pbs -import { base } from "@g/base.pbs"; - -// service sem pub (default) => private, soh sera acessivel dentro do arquivo atual -service bla -{ - fn do_something_else(): void - { - // do something else - } -} - -// mod indica que esse sera exportado para o modulo (cada diretorio eh um modulo) - no caso "@a" -mod service bla2 -{ - fn do_something_else_2(): void - { - // do something else 2 - } -} - - -pub service obladi -{ - fn do_something(a: long, b: int, c: float, d: char, e: string, f: bool): void - { - base.do_something(a,b,c,d,e,f); - bla.do_something_else(); - } -} - -# arquivo: b/service.pbs -import { base } from "@g/base.pbs"; - -pub service oblada -{ - fn do_something(a: long, b: int, c: float, d: char, e: string, f: bool): void - { - base.do_something(a,b,c,d,e,f); - } -} - -#arquivo: main.pbs (root) -# import carrega aquela API: @ sempre se referencia a raiz do projeto -import { obladi as sa } from "@a/service.pbs"; -import { oblada as sb } from "@b/service.pbs"; - - -// funcoes podem ser declaradas fora de services, mas serao SEMPRE private -fn some(a: int, b: int): int // recebe a e b e retorna a soma -{ - return a + b; -} - -fn frame(): void -{ - sa.do_something(1l,2,3.33,'4',"5",true); // chama o metodo do service de a - sb.do_something(1l,2,3.33,'4',"5",true); // chama o metodo do service de b - - // tipos - // void: nao retorna nada - // int : i32 - // long: i64 - // float: f32 - // double: f64 - // char: u32 nao sei se sera muito efetivo, me lembro de C (char = unsigned int, UTF-8) precisa de 32 aqui? - // string: handle imut?vel para constant pool (e futuramente heap) - // bool: true/false (1 bit) - - // nao eh possivel ter duas variaveis com o mesmo nome, isso eh soh um exemplo - // coercao implicita: - Sugest?o simples e consistente (recomendo para v0): - * Widen num?rico impl?cito permitido: - int -> long -> float -> double (se voc? quiser float->double tamb?m) - * Narrow/truncar NUNCA impl?cito (sempre cast expl?cito) - ent?o long = 1.5 exige as long - int = 1.5 exige as int - use as como cast - - // comentario de linha - /* comentario de bloco */ - let x: int = 1; // tipo explicito - let y = 1; // tipo implicito, com inferencia direta para tipos primitivos - - // z nao existe aqui! - { // scope - let z: int = 1; - } - // z nao existe aqui! - - let resultado = soma(1,2); // chama a fn soma e associa a uma variavel soma - sem problemas - - if (resultado > 10) - { - // sys.println(resultado); - } - else if (resultado > 100) - { - // sys.println(resultado); - } - else - { - // sys.println(resultado); - } - - for i from [0..10] // conta de 0 a 10 i default sempre int - { - } - - // porem tb eh possivel - for i: long from [0L..10L] - { - } - - for i from [0..10[ // conta de 0 a 9 - { - } - - for i from ]0..10] // conta de 1 a 10 - { - } - - for i from ]0..10[ // conta de 1 a 9 - { - } - - for i from [10..0] // conta de 10 a 0 - { - } -} - - -// definicao de uma struct, x e y sao privados por default -define Vector(x: float, y: float) -[ - (): (0, 0) - - (a: float): (a, a) - { - normalize(); - } -] -[[ // bloco estatico (opcional) - // o bloco estatico deve ser usado para definir constantes desse mesmo tipo e nao outro, por isso declaracao - // atraves de construtores - // assim podemos ter um tipo de enum com valores estaticos/constantes sem precisar de uma classe/instancia (vao para o constant pool) - ZERO: () - ONE: (1, 1) -]] -{ - // permitir x como sugar para this.x apenas dentro de m?todos de struct e apenas se n?o houver vari?vel local com mesmo nome. Caso exista, exige this.x. - // this s? ? permitido como tipo dentro do corpo de um define. - // this resolve para o tipo do define atual (Vector, Model, etc.) - // this tamb?m ? permitido como valor (this.x) dentro de m?todos. - // fora do define, this ? erro. - pub fn add(x: float, y: float): this - { - this.x += x; - this.y += y; - } - - // privado nao pode ser usado fora da struct - fn normalize(): void - { - let l = sqrt(x*x + y*y); - x /= l; - y /= l; - } - - // literals sao sempre stack allocated - // nesse caso aqui, como Vector nao eh alterado, ou seja, o valor de x e y nao muda - // o compilador passa a ref de v - // acesso aos campos - // private ? por tipo, n?o por inst?ncia - // Ent?o Vector pode acessar Vector.x em qualquer Vector. - pub fn dot(v: Vector): float - { - return x*v.x + y*v.y; - } -} - -define Model(c: Vector) -[ - (): (Vector()) -] -{ - // nesse caso, por exemplo, como v vai ser alterado, Vector deve ser passado como mutavel (seus valores sao copiados) - // e o Vector de origem fica inalterado - fn sum(v: mut Vector): Vector - { - return v.add(c.x, c.y); - } -} - -# arquivo: z/contract.pbs -// SDK ... o compilador injeta as defs de gfx aqui -contract gfx // nome do contrato eh gfx -{ - fn drawText(x: int, y: int, text: string, color: Color): void; -} - -contract interface // nome do contrato eh interface (eh mongol mas nao sou muito criativo) -{ - fn bla(x: int, y: int): void; -} - -pub service bla1: interface -{ - fn bla(x: int, y: int): void - { - // do something - } -} - -pub service bla2: interface -{ - fn bla(x: int, y: int): void - { - // do something else - } -} - ->> -Regra final recomendada (simples e limpa) -Existem dois namespaces globais -Tipos: define, interface, contract -Valores: fn, let, service - -Regras -Dentro de um mesmo escopo: -? dois s?mbolos de valor com mesmo nome ? erro -? um valor n?o pode ter o mesmo nome de um tipo vis?vel ? erro (opcional, mas recomendado) -Shadowing entre escopos: -* permitido apenas para vari?veis -* n?o permitido para fun??es/services (para evitar confus?o) - ->> -8) return v.add(c.x, c.y); com v: mut Vector -Se mut significa ?c?pia mut?vel?, ent?o add modifica v e retorna this (o mesmo v). Ok. -Mas a?: -* add precisa declarar retorno this e o compiler deve entender que this = Vector. -* this s? existe no contexto de define. - -declare Struct // struct sem valores eh um service :D -{ - pub fn sum(v): Struct - { - return this; - } - - // Isso eh soh sugar para o mesmo acima - pub fn sum(v): this - { - } - - // OU - - pub fn sum(v): me // mais uma keyword... - { - } -} - - -// para condicionais : - -let x = when a == b then 1 else 2;