dev/pbs #8
93
crates/prometeu-compiler/tests/pbs_golden_tests.rs
Normal file
93
crates/prometeu-compiler/tests/pbs_golden_tests.rs
Normal file
@ -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::<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,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;
|
||||
Loading…
x
Reference in New Issue
Block a user