half spec of language

This commit is contained in:
bQUARKz 2026-01-25 19:53:07 +00:00
parent 5b9524f401
commit 62e68bf7ff
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 929 additions and 0 deletions

View File

@ -0,0 +1,281 @@
# 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;

View File

@ -0,0 +1,648 @@
# Prometeu Base Script (PBS)
**Status:** v0 (frontend-freeze)
**Goal:** stack-only language, no GC, explicit semantics, predictable runtime
---
## 0. Philosophy (anchor section)
PBS is designed as:
* **Value-first** (no implicit references, no aliasing of mutable state)
* **Stack-only** (no heap, no GC)
* **Explicit mutability** (bindings, not types)
* **Didactic** (rules visible in syntax)
* **Runtime-cheap** (frontend-heavy, backend-simple)
Everything in the language flows from these constraints.
---
## 1. Project, Files & Modules
### 1.1 Files
* Extension: `.pbs`
* Project has a **root directory**
* `@path` is resolved relative to project root
### 1.2 Module Model
* **One directory = one module**.
* A module boundary is **real**: parent/child/sibling directories do **not** get automatic visibility.
* Code in `@project:modA` does **not** automatically see declarations in `@project:modA/sub`.
* Code in `@project:modA/sub` does **not** automatically see declarations in `@project:modA`.
* Sibling modules (e.g., `@project:modA` and `@project:modB`) are completely isolated unless you `import`.
* **Cross-module access always requires `import`**, and only `pub` symbols may cross module boundaries.
### 1.3 Automatic Module Index
* No mandatory barrel file
* Compiler builds an index from:
* all `pub` symbols
* in `.pbs` files **directly inside the directory**
* Subdirectories are excluded
### 1.4 Visibility Modifiers
PBS uses explicit visibility modifiers to control symbol exposure. Visibility is the **only** mechanism that decides whether another file/module can see a symbol.
* **default (no modifier)** — **file-private**
* Visible **only** within the declaring `.pbs` file.
* Never visible from any other file, even inside the same module.
* **`mod`** — **module-visible**
* Visible to **all files** in the same module (same directory).
* Not visible outside the module.
* No `import` is required for other files in the same module to refer to it.
* **`pub`** — **public API**
* Exported as part of the modules public surface.
* May be imported by other modules.
* Within the same module, `pub` behaves like `mod` (visible across files without import).
Important:
* A symbol is either **file-private**, **module-visible**, or **public**. There is no other visibility level.
* Visibility is checked independently in the **type** and **value** namespaces.
Visibility applies uniformly to:
* `declare` type declarations
* `service`
* value-level symbols
---
## 2. Namespaces & Visibility
### 2.1 Global Namespaces
Existem **dois namespaces globais**:
**Type namespace**
Tipos são introduzidos **exclusivamente** por declarações `declare`.
O que vem após `declare` define a categoria do tipo.
Formas válidas:
```pbs
declare struct Name { ... }
declare error Name { ... }
declare contract Name { ... }
```
**Value namespace**
Introduzido por declarações executáveis:
* `fn`
* `service`
* `let`
---
## 3. Top-level Declarations
A `.pbs` file may contain:
* `import`
* **type declarations** via `declare`:
* `declare struct`
* `declare error`
* `declare contract`
* `service`
* `fn` (always file-private)
Order is free.
---
## 4. Resolver Rules
This section defines how the compiler resolves modules, imports, and symbol names into concrete declarations.
### 4.1 Units of compilation
* The compilation unit is a **project** (root + all referenced modules/files).
* The root of the project is associated with the src/main/modules
* A **module** is exactly one directory.
* A **file unit** is one `.pbs` file.
Examples, lets say we have a project called: `project`:
```
project
|- prometeu.json
|- prometeu-cache
| |-cache.lock
|- src
| |- main
| | |- modules
| | | |- module-1
| | | | |- file-1.pbs
| | | | |- file-2.pbs
| | | | | |- fearture A
| | | | |- file-3.pbs
| | | | |- module-2
| | | | | |- file-1.pbs
| | | | | |- file-2.pbs
| | | | | | |- fearture B
| | | | | |- file-3.pbs
| |- resources
| | |- resource-1.txt
| |- test
| | |- modules
| | | |- module-1
| | | | |- file-1.pbs
| | | | |- file-2.pbs
| | | | |- file-3.pbs
| | |- resources
```
For a project `project/src/main/` is the root, and it will be presented in the import as (when looking for feature A and B):
```
import { featureA } from "@project:module-1"
import { featureB } from "@project:module-1/module-2"
```
The project name is a configuration inside `prometeu.json`. A project always defines a unit compilation. Other compilation
units could be linked to the project as a dependency. When building a project, all dependencies will be copied into a
directory called `prometeu-cache` (read-only) inside the project root.
A `project.json` could be look like.
```json
{
"name": "project",
"dependencies":{
"project-A": {
"version": "1.0.0",
"source": "path:../project-A"
},
"project-B": {
"version": "1.5.3",
"source": "git:https://github.com/project-B.git"
}
}
}
```
When compiling, all deps and current project will be flattened, and Project names should be unique otherwise it will
cause conflicts and compilation errors on the current project.
### 4.2 Namespaces
Resolution is performed in **two independent namespaces**:
* **Type namespace**: declarations introduced by `declare` (`struct`, `error`, `contract`).
* **Value namespace**: declarations introduced by `service`, `fn`, and `let`.
A name may exist in both namespaces, but this is **discouraged**; toolchains may report this as an error or warning.
### 4.3 Visibility gates
For any candidate symbol `S`, visibility is checked explicitly:
* **file-private (default)** — visible only within the same file
* **`mod`** — visible to any file in the same module
* **`pub`** — visible to other modules via `import`
No implicit visibility exists beyond these rules.
### 4.4 Module index construction
For each module directory `M`:
* The compiler scans all `.pbs` files **directly inside** `M`.
* The modules **public index** contains **only** `pub` symbols from those files.
* Subdirectories never contribute to the parent modules index.
Duplicate rules:
* Any `structs, services, contracts, errors` duplicate `pub` names in the same namespace cause a compile error:
* `duplicate public symbol in module`.
### 4.5 Import resolution
Imports are the **only** mechanism for cross-module access.
```pbs
import { X, Y as Z } from "@project:module";
```
Rules:
* Only `pub` symbols may be imported.
* Import-by-module resolves against the module public index.
* Import-by-file is not allowed at all.
### 4.6 Local resolution order
Within a file, name lookup follows this order **within each namespace**:
1. Local bindings (parameters, local `let`, innermost scope first) but with warnings
- when `import { X as Y } from "@project:module"` and `let Y: int = 0;` `let Y` wins but could generate a warning
2. File-level declarations (file-private, `mod`, and `pub` in the same file)
3. Imported bindings
4. When `import { X } from "@project:module"` (or when using `as`) and we already have a `mod X ...` for this module, compilation error.
Shadowing rules:
* Local `let` shadowing is allowed.
* Shadowing of `fn` and `service` names is not allowed.
### 4.7 Cross-file and cross-module access
**Within the same module:**
* `mod` and `pub` symbols are visible across files without `import`.
* file-private symbols are never visible.
**Across modules:**
* only `pub` symbols are visible
* access always requires an explicit `import`.
### 4.8 Cycles
* Import cycles are allowed only if name resolution can be completed.
* PBS has no top-level execution, so cycles are resolved purely at the symbol level.
* Any cycle that prevents construction of a complete symbol table is a compile error.
### 4.9 Contracts and services
* `declare contract C` introduces `C` in the **type namespace**.
* `service S: C` resolves `C` as a type and validates that `S` implements all declared signatures.
* they can be implemented by the Prometeu runtime / host environment.
---
## 4. Services
A `service` represents an **explicit API boundary** between PBS code and either:
* other PBS modules, or
* it is definitely a singleton by design
* can implement a contract, or not
- when implementing a contract, signatures must match exactly
* every `fn` inside in a `service` will follow the same visibility as the service it belongs (`pub` or `mod`).
- once being able to see the service, it can see all its methods.
A service is never an implementation detail.
### 4.1 Declaration and visibility
A service must always be declared with an explicit visibility modifier:
* `pub service` — part of the modules public API
* `mod service` — internal API, visible only inside the module
There is **no such thing** as a private service.
### 4.2 Service methods
* All methods declared inside a service are **public within that service**.
* There are no private or helper methods inside a service.
* A service method may call:
* top-level `fn` in the same file
* other services it can legally see
### 4.3 Implementation rule
If logic is not conceptually part of the API, it **must not** live inside a service.
Such logic must be implemented as file-private `fn`.
---
## 6. Types
This section describes all value types available in PBS and how they are declared.
### 6.1 Primitive types
PBS provides a small, fixed set of primitive types:
* `void`
* `int` (32-bit signed)
* `long` (64-bit signed)
* `float` (32-bit IEEE-754)
* `double` (64-bit IEEE-754)
* `bool`
* `char` (Unicode code point, 32-bit)
* `string`
Important notes:
* `string` values are **immutable**.
* Strings exist only as literals stored in a constant pool.
* There is no dynamic string allocation at runtime.
### 6.2 Struct types
User-defined value types are declared using `declare struct`.
A struct:
* is a **pure value type**
* has no identity
* follow the rules of **mutability and borrowing" described below
* has no inheritance or subtyping
* it is only copied when mutated, otherwise it is **viewed as immutable** and passed as ref by default.
All behavior related to the type must be defined inside the struct body.
### 6.3 `this`
Inside a struct body, the special type `this` refers to the struct itself.
Rules:
* `this` may only appear inside a `declare struct` body.
* Outside a struct, `this` is illegal.
---
## 7. Structs & Constructors
```pbs
declare struct Vector(x: float, y: float)
[
(): (0,0) as default { }
(a: float): (a,a) as square { }
(): (1,1) as normalize { }
]
[[
ZERO: default()
ONE: square(1.0)
]]
{
pub fn len(self: this): float { ... }
pub fn scale(self: mut this): void { ... }
pub fn normalize(self: mut this): void { ... }
}
```
Key rules:
* fields private by default
* constructor aliases live in type namespace but can never be imported, in the exemplo only `Vector` can be imported.
* no static methods
* static block values are compile-time constants
There will never be a conflict between an alias and a method of the same name.
A constructor will be called as `let v = Vector.normalize()` and methods will be called as `v.normalize()`.
In fact, they call different things, one is static, and the other is an instance method (there are no static methods on structs).
The static block `[[ ]]` should be static (and generate static on constant pool) and can use only constructors and static values, as helpers.
---
## 8. Mutability & Borrow Model (Core Rule)
This is a foundational rule of PBS.
Mutability is **never** a property of a type.
Mutability belongs **only** to bindings (variables and parameters).
### 8.1 Immutable by default
All bindings are immutable unless explicitly marked `mut`.
```pbs
let v = Vector.ZERO; // immutable binding
let mut w = Vector.ZERO; // mutable binding (local copy)
```
Some examples of mutable bindings:
```pbs
let v = Vector.ZERO;
v.scale(); // ERROR: cannot mutate a read-only binding
let mut w = v;
w.scale(); // OK: when mut it copies the value, and could be changed
fn f(v: Vector): Vector
{
let mut w = v;
return w; // when it happens compiler will generate a copy otherwise w will be lost in the stack
}
fn g(v: Vector): void
{
let vro = f(Vector.ZERO); // OK! and it will be a readonly copy
let vrw = mut f(Vector.ZERO); // OK! and it will be a mutable copy of the copy
}
```
### 8.2 Consequences of binding-based mutability
* Mutating a value never affects any other binding.
* Static values can never be mutated.
* There is no aliasing of mutable state.
### 8.3 Parameters and copying
Function parameters follow these rules:
* `T` parameters are passed as **read-only borrows**.
* `mut T` parameters create a **local mutable copy**.
The caller is never affected by mutations performed inside the function.
### 8.4 Method receivers
Methods on structs declare mutability explicitly on the receiver:
```pbs
fn len(self: this): float // read-only
fn scale(self: mut this): void // mutating
```
A mutating method:
* requires a **mutable lvalue** at the call site
* cannot be called on temporaries or static values
---
## 9. Expressions & Control Flow
* `if / else`
* `for` with ranges
* `when` expression
* `return`
`when`:
```pbs
let x = when a > b then 1 else 2;
```
---
## 10. Return Fallback (`else`)
```pbs
fn v(): Vector else Vector.ZERO
{
if cond return Vector.ONE;
}
```
Used to guarantee total functions without boilerplate.
---
## 11. Numeric Rules
* implicit widen: `int → long → float → double`
* no implicit narrowing
* cast syntax: `expr as Type`
---
## 12. `bounded`
`bounded` is a dedicated scalar type for **indices and sizes**.
### 12.1 Purpose
The goal of `bounded` is to make indexing and counting:
* explicit
* safe
* visually distinct from general arithmetic
### 12.2 Representation
* Internally represented as an unsigned 16-bit integer (`u16`).
* Valid range: `0 .. 65535`.
### 12.3 Usage rules
`bounded` is used for:
* array indices
* array lengths
* default `for` loop counters
Operations are intentionally limited:
* allowed: comparison, checked `+` and `-`
* disallowed: multiplication, division, bitwise operations
---
## 13. Fixed Arrays
```pbs
Array<T>[Nb]
```
* `Nb` is compile-time `bounded` literal
* immutable bindings = view
* mutable bindings = owned storage
Supports:
* indexing (`bounded` only)
* slicing `[a..b[` (half-open)
Return escape model prevents dangling views.
---
## 14. `optional<T>`
`optional<T>` represents the explicit presence or absence of a value.
### 14.1 Design intent
* Absence is a **normal, explicit state**.
* No traps or implicit unwraps exist.
* No heap allocation is involved.
### 14.2 Extraction rule
The **only** supported way to extract a value from an optional is with `else`:
```pbs
let x: int = opt else 0;
```
This makes fallback behavior explicit at the call site.
---
## 15. `result<T,E>`
* typed errors
* no exceptions
* no implicit extraction
```pbs
let v = f()?;
```
### Error handling with `handle`
`handle` is the *only* construct that allows value extraction from a `result` while mapping errors.
```pbs
let x: int = handle r
{
ErrorA.not_found => ErrorB.io,
ErrorA.denied => ErrorB.permission,
_ => ErrorB.unknown,
};
```
Semantics:
* If `r` is `ok(v)`, the expression evaluates to `v`
* If `r` is `err(e)`, the first matching arm is selected and an early `return err(mapped)` is executed
Rules:
* Arms must be exhaustive (explicit or via `_`)
* Right-hand side must be a label of the destination error type
* No traps are permitted
* **The only possible way to extract a value is by handling the error**
---
## 16. What is *deliberately missing* (v0)
* heap allocation
* GC
* references
* inheritance
* traits / interfaces
* async
* closures
* generics (beyond containers)
---
## 17. Roadmap (v1+ ideas)
This is a non-binding list of future directions. None of these are part of v0.
* generics for `struct`
* slice views with runtime bounds
* iterator sugar
* `defer`
* pattern matching on `result`
* ABI boundary spec (VM ↔ PBS)
* contract versioning
---
**This document is now the canonical organized v0 spec.****