diff --git a/test-cartridges/color-square-pbs/src/main.pbs b/test-cartridges/color-square-pbs/src/main.pbs new file mode 100644 index 00000000..45fcd62d --- /dev/null +++ b/test-cartridges/color-square-pbs/src/main.pbs @@ -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; diff --git a/test-cartridges/color-square-pbs/src/prometeu_scripting.md b/test-cartridges/color-square-pbs/src/prometeu_scripting.md new file mode 100644 index 00000000..6a0f0d0a --- /dev/null +++ b/test-cartridges/color-square-pbs/src/prometeu_scripting.md @@ -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[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` + +`optional` 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` + +* 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.****