half spec of language
This commit is contained in:
parent
5b9524f401
commit
62e68bf7ff
281
test-cartridges/color-square-pbs/src/main.pbs
Normal file
281
test-cartridges/color-square-pbs/src/main.pbs
Normal 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;
|
||||
648
test-cartridges/color-square-pbs/src/prometeu_scripting.md
Normal file
648
test-cartridges/color-square-pbs/src/prometeu_scripting.md
Normal 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 module’s 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 module’s **public index** contains **only** `pub` symbols from those files.
|
||||
* Subdirectories never contribute to the parent module’s 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 module’s 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.****
|
||||
Loading…
x
Reference in New Issue
Block a user