ProtoScript V2 est un langage :
- à syntaxe familière (C / JavaScript)
- à sémantique stricte
- object-oriented, prototype-based, sans classes
- typé statiquement par inférence ou déclaration
- conçu pour des performances structurelles
- compilable vers un IR simple, du C, ou du natif
ProtoScript V2 est conçu comme un langage de construction, destiné à l’écriture de logiciels durables, lisibles et performants.
Il ne cherche ni à masquer la complexité réelle des programmes, ni à offrir un confort syntaxique au prix d’ambiguïtés sémantiques. Chaque règle du langage vise à rendre explicites :
- les types
- les coûts
- les allocations
- les dépendances
L’objectif est de permettre à l’humain de raisonner correctement sur le code, et à la machine de l’analyser, l’optimiser et le compiler efficacement.
ProtoScript V2 vise explicitement :
- une lisibilité stricte du code source
- une prévisibilité totale du comportement à l’exécution
- des performances structurelles comparables à du C bien écrit
- une compilation hors ligne (AOT) possible sans heuristique spéculative
- une séparation nette entre texte, données binaires et structures
Le langage doit permettre l’écriture d’algorithmes intensifs (traitement d’images, de données, de texte) sans pénalité sémantique ou structurelle.
ProtoScript V2 ne vise pas :
- la compatibilité avec JavaScript ou ECMAScript
- l’exécution dans un navigateur
- le typage dynamique
- la métaprogrammation réflexive
- les conversions implicites
Ces choix sont assumés afin de préserver la cohérence globale du langage et la confiance de l’utilisateur dans ce qu’il écrit.
ProtoScript V2 est guidé par un principe central :
Ce qui est lisible pour l’humain doit être directement exploitable par la machine.
Toute fonctionnalité qui améliorerait artificiellement le confort d’écriture au détriment de la lisibilité, de l’analyse statique ou des performances est volontairement exclue.
Règles normatives :
- le runtime C de ProtoScript2 est l’implémentation normative d’exécution ;
- le CLI runtime (
c/ps run) doit être autonome et exécuter localement via le runtime C ; - la commande
c/ps runDOIT exécuter le même pipeline frontend quecheck(parse + analyse sémantique + diagnostics) avant toute exécution ; - si des erreurs statiques sont présentes,
c/ps runDOIT s’arrêter immédiatement, retournerEXIT_FAILURE, et NE DOIT PAS invoquer le runtime ni poursuivre la génération ; - le CLI runtime ne doit déléguer ni directement ni indirectement vers un compilateur/référence externe ;
- les mécanismes de délégation de type
exec,system,popenou l’invocation d’un runtime Node.js sont interdits pour l’exécution dec/ps run.
Cadre de conformité :
- Node.js peut être utilisé pour la génération d’IR, la génération C et le crosscheck/oracle de conformité ;
- la parité Node ↔ C doit être validée par les tests, jamais obtenue par redirection d’exécution.
Règles normatives :
- La VM C est l’implémentation normative de ProtoScript2.
- La cible WASM est la VM C compilée (mêmes sources C).
- La cible emit-C doit reproduire exactement la sémantique de la VM C.
- Aucun backend ne définit la sémantique du langage.
- Toute divergence backend vs VM C est un bug.
- Un correctif backend ne doit jamais modifier :
- le lookup de
clone, - la résolution de
super, - la hiérarchie prototype,
- le modèle d’erreur.
- le lookup de
- Toute allocation spécifique backend doit être :
- purement technique,
- confinée au codegen/backend concerné,
- documentée explicitement comme non-sémantique.
Règles normatives pour toute nouvelle structure builtin :
-
Définir une surface canonique en ProtoScript :
- prototype,
- méthodes publiques,
- signature de
clone(), - comportement
super, - règles de typage du retour de
clone(), - erreurs normatives.
- La surface publique d’un builtin doit être décrivable en ProtoScript (prototype + méthodes), sans champ magique requis côté utilisateur.
- Toute représentation interne (handle natif, struct C, layout backend) est opaque et non normative.
-
Vérifier structurellement :
- lookup
clone, - dispatch,
- override,
- invariants de type et d’héritage.
- lookup
-
Classifier explicitement le builtin :
Native handlenon clonable (R1013 RUNTIME_CLONE_NOT_SUPPORTEDobligatoire) :- instanciation contrôlée par API/module ;
- aucun clone implicite, aucune duplication best-effort.
Data typeclonable :clone()etsupersuivent les règles normales du langage.
-
Tests obligatoires :
clone(),super.clone(),- override + signature,
- mapping des erreurs runtime,
- parité VM C native / VM C WASM / emit-C.
-
Interdictions :
- backend-first design,
- champs publics implicites,
- sémantique cachée côté C/runtime.
-
Cohérence sous-typage/runtime :
- si le typage statique accepte les sous-types d’un builtin, le runtime doit accepter ces sous-types ;
- sinon le builtin doit être explicitement
sealed.
-
Sealed prototype rule:
- Un prototype
sealedNE DOIT PAS être hérité.
La clonabilité est indépendante de
sealed. Elle dépend uniquement de la sémantique propre du prototype. - Un prototype
-
Builtin prototypes (closed list): Builtins sealed et non clonables:
TextFileBinaryFileDirWalkerRegExpPathInfoPathEntryRegExpMatchProcessEventProcessResult
Builtin clonable et non sealed:
CivilDateTime
ProtoScript V2 définit un ensemble restreint de types scalaires fondamentaux, dont la sémantique est fixe, explicite et exploitable statiquement.
Types scalaires intégrés :
boolbyte— entier non signé 8 bits (donnée binaire brute)glyph— scalaire Unicode, représentant un glypheint— entier signé 64 bits, type entier par défaut du langagefloat— flottant double précision IEEE‑754string— séquence de glyphes, stockée en UTF‑8
Les types scalaires fondamentaux sont strictement :
boolbyteglyphintfloatstring
Propriétés normatives :
- ils sont manipulés par valeur
- ils ne participent pas au mécanisme d’héritage
- ils ne possèdent aucun champ utilisateur
- ils sont immuables au niveau du langage
- toute mutation indexée de
stringest interdite
Les types composés (list<T>, map<K,V>) et les prototype ne sont pas des types scalaires fondamentaux.
Les littéraux entiers peuvent être écrits sous les formes suivantes :
- décimale :
12,0,5 - hexadécimale :
0xFF,0xab - octale :
0644 - binaire :
0b01100110
Règles :
- les littéraux entiers ne portent pas de signe
- toute valeur négative résulte de l’application de l’opérateur unaire
- - les littéraux entiers non suffixés sont de type
int - l’affectation à
byteexige que la valeur soit dans l’intervalle valide
Les littéraux flottants supportent les notations suivantes :
- notation décimale :
.2,4.,-.14,-2.5 - notation scientifique :
1e3,-2.5e-4,3.14E2
Règles :
- tout littéral contenant un point décimal ou un exposant est de type
float - les opérations sur
floatsuivent la sémantique IEEE‑754 double précision - les valeurs
NaN,+Infinity,-Infinityet-0peuvent être produites - aucune exception implicite n’est levée lors des opérations arithmétiques sur
float - les comparaisons avec
NaNsuivent IEEE‑754 :NaN != NaNesttrueet toute comparaison ordonnée avecNaNestfalse +0et-0sont distincts au niveau IEEE‑754, mais restent égaux via==
ProtoScript V2 ne définit aucune valeur null universelle.
Règles :
- les types scalaires fondamentaux (
int,float,bool,byte,glyph,string) ont toujours une valeur valide stringn’est jamaisnull(la chaîne vide""représente l’absence de contenu)list<T>etmap<K,V>ne sont jamaisnull(une collection vide est valide)- toute situation d’absence, d’échec ou d’erreur est gérée explicitement par des exceptions
Un glyph est une valeur scalaire Unicode logique, représentant un caractère Unicode abstrait.
Règles normatives :
- un
glyphreprésente un point de code Unicode valide (Unicode scalar value) - sa représentation runtime est fixe et indépendante du nombre d’octets UTF‑8
- un
glyphest représenté par un entier non signé de 32 bits (uint32) - aucun
glyphne correspond à un surrogate UTF‑16 - un
glyphn’est pas propriétaire de mémoire
Conceptuellement, un glyph correspond à :
uint32_t glyph;La correspondance entre les bytes UTF‑8 d’une string et les glyph est assurée par le runtime lors de l’itération ou de l’indexation.
Les méthodes suivantes sont définies pour le type glyph :
g.isLetter();
g.isDigit();
g.isWhitespace();
g.isUpper();
g.isLower();
g.toUpper();
g.toLower();
g.toString();
g.toInt();
g.toUtf8Bytes();Règles :
- les méthodes Unicode utilisent les tables Unicode standards
toUpper()/toLower()retournent unglyphtoInt()retourne le code point Unicode sous forme d’inttoUtf8Bytes()retourne unelist<byte>en UTF‑8 strict (exactement les octets du glyph)- les opérations arithmétiques/unaires (
+,-,*,/,%,<<,>>,&,|,^,~,++,--) surglyphsont interdites et doivent lever une exception runtime - les méthodes sont pures (sans effet de bord)
- aucune allocation implicite n’est effectuée
- les méthodes sont résolues statiquement
Un group est une déclaration statique d’un ensemble fermé de constantes homogènes.
Syntaxe normative (EBNF) :
GroupDecl = ScalarType "group" Identifier "{" GroupMemberList "}" ;Contraintes normatives :
Tdoit être un type scalaire fondamental- chaque membre doit être assignable à
T - les membres sont implicitement constants
- aucun membre ne peut être réassigné
- aucun doublon d’
Identifiern’est autorisé dans un mêmegroup - un
groupne définit pas un nouveau type - un
groupne peut pas être instancié - un
groupne peut pas être utilisé comme type - le nom du
groupdésigne un descripteur nominal accessible comme valeur (Color) - le descripteur n’est pas un type et ne définit pas d’opérations
- les membres sont substitués comme constantes dans l’IR
Contraintes sur les membres :
- chaque membre est de la forme
Identifier = Expression - chaque
Expressiondoit être une expression constante, sans effet de bord - aucun
MethodDecl, aucun modificateur ni annotation n’est autorisé dans ungroup
Erreurs statiques associées :
E3120—GROUP_NON_SCALAR_TYPE:groupexige un type scalaire fondamentalE3121—GROUP_TYPE_MISMATCH: un membre n’est pas assignable àTE3122—GROUP_MUTATION: tentative d’affectation à un membre degroup
Cette section définit les structures de données fondamentales de ProtoScript V2, leurs règles de typage, leur sémantique d’accès et leurs garanties de complexité.
Les structures décrites ici sont fermées, statiquement typées et conçues pour offrir des performances structurelles prévisibles.
ProtoScript V2 autorise l’appel de méthodes sur les valeurs scalaires et structurées, sans adopter un modèle « tout est objet ».
Les méthodes font partie de l’API standard du langage et sont résolues statiquement en fonction du type.
bool
b.toString();byte
b.toInt();
b.toFloat();
b.toString();Règle normative :
byte.toInt()retourne unintde même valeur numérique (0..255), sans conversion implicite.
int
i.toByte();
i.toFloat();
i.toString();
i.toBytes();
i.abs();
i.sign();int.toBytes()
int.toBytes() retourne une list<byte> de 8 octets, copie brute de la représentation mémoire de l’entier.
Règles normatives :
- l’ordre des octets est celui de la mémoire sur la machine exécutant le runtime ;
- aucune normalisation d’endianness n’est effectuée ;
- le contenu de la liste est une copie (pas de vue).
float
f.isNaN();
f.isInfinite();
f.isFinite();
f.toInt();
f.toString();
f.toBytes();
f.abs();float.toBytes()
float.toBytes() retourne une list<byte> de 8 octets, copie brute de la représentation mémoire IEEE‑754 de l’entier.
Règles normatives :
- l’ordre des octets est celui de la mémoire sur la machine exécutant le runtime ;
- aucune normalisation d’endianness n’est effectuée ;
- le contenu de la liste est une copie (pas de vue).
Un littéral numérique est non typé.
- Son type est déterminé par le contexte d’utilisation.
- En l’absence de contexte, le type par défaut est
int.
La forme (T)expr est une conversion explicite vers le type numérique T.
Une conversion explicite est autorisée uniquement si la valeur de expr est exactement représentable dans T.
Toute conversion entraînant :
- un dépassement de plage,
- une perte d’information,
- une troncature,
- un enroulement (wrap),
- une saturation implicite
est une erreur statique.
byte: entier ∈[0, 255]int: entier signé de l’implémentation ciblefloat: nombre réel IEEE‑754
| Source → Cible | byte | int | float |
|---|---|---|---|
| byte | OK | OK | OK |
| int | OK si ∈ [0,255] |
OK | OK |
| float | OK si entier exact ∈ [0,255] |
OK si entier exact | OK |
(byte)256; // erreur statique
(byte)-1; // erreur statique
(int)3.14; // erreur statique
(byte)3.0; // OK
(byte)3.5; // erreur statiqueAucune conversion implicite n’est autorisée entre types numériques.
string
s.length(); // nombre de glyphes
s.isEmpty();
s.toString(); // identité
s.toInt();
s.toFloat();
s.toUpper();
s.toLower();
s.concat(parts); // concaténation explicite
s.toUtf8Bytes(); // list<byte> (UTF-8 strict)
s.length(); // longueur en glyphes
s.isEmpty(); // bool
s.subString(start, length); // sous-chaîne (glyphes)
s.indexOf(needle); // position en glyphes (ou -1)
s.contains(needle); // bool
s.lastIndexOf(needle); // dernière position en glyphes (ou -1)
s.startsWith(prefix);
s.endsWith(suffix);
s.split(sep); // list<string>
s.trim();
s.trimStart();
s.trimEnd();
s.replace(old, new); // remplace la première occurrence
s.replaceAll(old, new); // remplace toutes les occurrences (non-chevauchantes)
s.glyphAt(index); // équivalent à s[index]
s.repeat(count);
s.padStart(targetLength, pad);
s.padEnd(targetLength, pad);Règles normatives d’accès indexé sur string :
string[index]en lecture retourne unglyph- l’index est exprimé en glyphes (et non en bytes UTF-8)
- si l’index est hors bornes, une exception runtime est levée (
RUNTIME_INDEX_OOB) - toute écriture
string[index] = valueest une erreur statique (IMMUTABLE_INDEX_WRITE) stringest immuable et ne constitue pas une collection mutablestring.toUtf8Bytes()retourne unelist<byte>en UTF-8 strictlist<byte>.toUtf8String()retourne unestringen UTF-8 strictstring.length()retourne le nombre de glyphes (Unicode scalar values)string.isEmpty()retournetruesi la longueur en glyphes est0string.subString(start, length)retourne une nouvellestringextraite en glyphesstring.subStringn’expose ni vue ni référence partagée- les indices et positions retournés par les méthodes de recherche sont exprimés en glyphes
string.indexOf(needle)retourne l’index (en glyphes) de la première occurrence, ou-1si absentstring.contains(needle)est équivalent àstring.indexOf(needle) >= 0; sineedle == "", retournetruestring.lastIndexOf(needle)retourne l’index (en glyphes) de la dernière occurrence, ou-1si absent ; sineedle == "", retournestring.length()string.startsWith(prefix)etstring.endsWith(suffix)retournentboolstring.split(sep)retourne unelist<string>et ne fait aucun traitement regex- si
sepest une chaîne vide,splitretourne une liste de chaînes d’un glyphe chacune string.trim*retire uniquement les espaces ASCII (' ','\t','\n','\r') en début/fin selon la variantestring.replace(old, new)remplace la première occurrence exacte (pas de regex) et retourne une nouvellestringstring.replaceAll(old, new)remplace toutes les occurrences exactes non-chevauchantes (pas de regex)string.replaceAll("", new)doit lever une exception runtimeRUNTIME_INVALID_ARGUMENTstring.glyphAt(index)retourne leglyphàindex(même règle d’erreur questring[index])string.repeat(count)retournecountrépétitions ;count < 0doit leverRUNTIME_INVALID_ARGUMENTstring.padStart(targetLength, pad)etstring.padEnd(targetLength, pad)paddent en glyphes ; si padding requis etpad == "", leverRUNTIME_INVALID_ARGUMENT
list
list.length();
list.isEmpty();
list.push(x);
list.pop();
list.join(","); // si T est string
list.concat(); // si T est string (concaténation sans séparateur)
list.sort(); // si T est comparable
list.contains(x);
list.toUtf8String(); // si T est byte (UTF-8 strict)map<K,V>
map.length();
map.isEmpty();
map.keys(); // list<K>
map.values(); // list<V>
map.containsKey(k);bool
| méthode | arité |
|---|---|
toString() |
0 |
byte
| méthode | arité |
|---|---|
toInt() |
0 |
toFloat() |
0 |
toString() |
0 |
int
| méthode | arité |
|---|---|
toByte() |
0 |
toFloat() |
0 |
toString() |
0 |
toBytes() |
0 |
abs() |
0 |
sign() |
0 |
float
| méthode | arité |
|---|---|
toInt() |
0 |
toString() |
0 |
toBytes() |
0 |
abs() |
0 |
isNaN() |
0 |
isInfinite() |
0 |
isFinite() |
0 |
string
| méthode | arité |
|---|---|
length() |
0 |
isEmpty() |
0 |
toString() |
0 |
toInt() |
0 |
toFloat() |
0 |
toUpper() |
0 |
toLower() |
0 |
toUtf8Bytes() |
0 |
trim() |
0 |
trimStart() |
0 |
trimEnd() |
0 |
concat(x) |
1 |
indexOf(x) |
1 |
contains(x) |
1 |
lastIndexOf(x) |
1 |
startsWith(x) |
1 |
endsWith(x) |
1 |
split(x) |
1 |
glyphAt(index) |
1 |
repeat(count) |
1 |
subString(start, length) |
2 |
replace(old, new) |
2 |
replaceAll(old, new) |
2 |
padStart(targetLength, pad) |
2 |
padEnd(targetLength, pad) |
2 |
list
| méthode | arité |
|---|---|
length() |
0 |
isEmpty() |
0 |
pop() |
0 |
sort() |
0 |
concat() |
0 |
toUtf8String() |
0 |
push(x) |
1 |
contains(x) |
1 |
join(sep) |
1 |
map<K,V>
| méthode | arité |
|---|---|
length() |
0 |
isEmpty() |
0 |
keys() |
0 |
values() |
0 |
containsKey(k) |
1 |
Modules standards (Io, Math, JSON)
- l’arité des fonctions est strictement celle de
modules/registry.json - aucune arité variable n’est implicite : tout cas d’arité variable doit être explicitement listé (ex.
TextFile.read(size)etTextFile.tell()sont des méthodes distinctes)
Note (non-normative) :
- L’implémentation de référence charge ce registry depuis
modules/registry.jsonou via la variable d’environnementPS_MODULE_REGISTRY.
Règles générales :
- les méthodes disponibles dépendent strictement du type
- certaines méthodes sont conditionnelles (ex.
sort()) - aucune méthode ne peut être ajoutée dynamiquement
- l’appel de méthode n’implique pas que « tout est objet »
- l’arité des méthodes/fonctions est stricte : tout appel avec trop ou pas assez d’arguments est une erreur statique (
E1003ARITY_MISMATCH) - les signatures indiquées dans cette section font foi pour l’arité (ex.
string.concat(x)→ 1 argument,list.concat()→ 0,map.keys()→ 0) - l’exactitude fonctionnelle, la sécurité et les performances sont évaluées conjointement sur l’ensemble des chemins d’exécution
list<int> a;
list<int> b = [1, 2, 3];
list<byte> bytes = [0xFF, 0xab, 0x14, 17, 0b00101111, 04];
int x = b[0];- le type
Test obligatoire - stockage contigu en mémoire
- type invariant après déclaration
- accès indexé via l’opérateur
[]avec un index de typeint - initialisation possible par littéral de liste avec
[] - l’ordre des éléments est celui du littéral
- le littéral vide
[]doit être typé par le contexte ; hors contexte typé explicite, il est invalide
Les garanties suivantes sont normatives et indépendantes de l’implémentation.
| Opération | Complexité | Notes |
|---|---|---|
list.isEmpty() |
O(1) | test de longueur |
list.length() |
O(1) | longueur stockée |
accès list[i] |
O(1) | accès direct mémoire |
affectation list[i] = x |
O(1) | écriture stricte, sans resize implicite |
itération for (T v of list) |
O(n) | parcours séquentiel |
list.push(x) |
O(1) amorti | réallocation possible |
list.pop() |
O(1) | suppression en fin |
list.contains(x) |
O(n) | comparaison séquentielle |
list.sort() |
O(n log n) | si T satisfait les conditions définies ci-dessous |
list.slice(offset, len) |
O(1) | création de vue |
Règles complémentaires :
- aucune opération
listne cache une allocation non documentée - la réallocation ne peut se produire que lors de
push - toute opération O(n) est proportionnelle au nombre d’éléments
- l’accès indexé est toujours en temps constant
list[i] = xest une écriture stricte : l’index doit existerlist[i] = xne doit jamais redimensionner la listelist.pop(): erreur statique si la vacuité est prouvée, sinon exception runtime si la liste est vide
list<T>.sort() est défini comme un tri en place des éléments de la liste.
Il ne modifie que l’ordre des éléments, sans changer leur identité ni leur valeur.
Sa complexité doit être O(n log n).
Types autorisés : list<T>.sort() est autorisé uniquement si T est :
int,float,byte,string, ou- un prototype qui déclare une méthode de comparaison conforme.
Ordre de tri pour les types scalaires fondamentaux :
int,byte: ordre numérique croissant.float: ordre numérique croissant ;NaNest classé supérieur à toute valeur numérique ; deuxNaNsont équivalents.string: ordre lexicographique UTF‑8 binaire, indépendant de la locale.
Tri des prototypes utilisateur :
list<T>.sort()est autorisé uniquement si le prototype deTdéclare une méthodecompareTo(T other) : int.- La méthode doit retourner une valeur < 0 si
self < other, 0 siselfest égal àother, > 0 siself > other. - En absence de cette méthode, l’appel de
list<T>.sort()est une erreur statique.
Clarification normative de résolution :
compareToest une méthode d’instance.- La comparaison de deux éléments
aetbest effectuée par l’appela.compareTo(b). - La résolution est strictement statique selon le type
Tde la liste. - Aucun dispatch dynamique supplémentaire ni recherche RTTI n’est effectué.
- Si
compareToest héritée d’un prototype parent deT, elle est considérée valide selon les règles générales de résolution des méthodes.
Interdictions explicites :
- Aucune variante
sort(cmpFunction)n’est définie. - Aucun comparateur externe (lambda/callback) n’est autorisé.
- Aucune généricité implicite ni fallback dynamique/RTTI n’est autorisé.
Propriétés supplémentaires :
- Le tri doit être déterministe.
- Le tri doit être stable.
- La complexité doit être O(n log n).
- Aucun comportement ne doit dépendre de la locale.
list<T>.reverse() inverse l’ordre des éléments de la liste en place.
La complexité doit être O(n).
Cette opération est autorisée pour tout list<T>, sans contrainte sur T.
Propriétés normatives :
- L’opération est déterministe.
- Aucun nouvel élément n’est créé : l’inversion est en place.
- Aucune relation d’ordre nouvelle n’est introduite : il s’agit d’une inversion structurelle.
- Aucun comportement ne doit dépendre de la locale.
- Aucun dispatch dynamique ni comparateur n’est impliqué.
map<string, int> values = {"a": 12, "b": 8, "c": 0x45};
int v = values["a"];- les types
KetVsont obligatoires - initialisation possible par littéral de map avec
{ key : value } - les clés doivent être de type
K - les valeurs doivent être de type
V - le littéral vide
{}doit être typé par le contexte ; hors contexte typé explicite, il est invalide - l’ordre d’itération est l’ordre d’insertion
- les clés dupliquées dans un littéral sont interdites
- accès par clé via l’opérateur
[] - en lecture,
map[k]doit lever une exception runtime sikest absente - en écriture,
map[k] = vdoit insérer une nouvelle entrée sikest absente - en écriture,
map[k] = vdoit mettre à jour la valeur associée sikest présente map.remove(k)doit supprimer la clé si elle existe et retournertrue, sinonfalsemap.remove(k)ne doit jamais lever pour clé absente- supprimer puis ré‑insérer une clé doit la placer en fin de l’ordre d’itération
- la lecture
map[k]est stricte : la clé doit exister au moment de l’accès - la lecture d’une clé absente doit lever une exception runtime (
RUNTIME_MISSING_KEY) - l’écriture
map[k] = vest constructive : elle doit produire un état valide après l’opération - pour
map, cela impose : insertion si la clé est absente, mise à jour si elle est présente
Les garanties suivantes sont normatives et indépendantes de l’implémentation.
| Opération | Complexité | Notes |
|---|---|---|
map.isEmpty() |
O(1) | test de taille |
map.length() |
O(1) | taille stockée |
accès map[k] |
O(1) amorti | table de hachage |
affectation map[k] = v |
O(1) amorti | insertion ou mise à jour |
map.containsKey(k) |
O(1) amorti | lookup |
map.remove(k) |
O(1) amorti | suppression |
itération for (V v of map) |
O(n) | ordre d’insertion |
itération for (K k in map) |
O(n) | ordre d’insertion |
map.keys() |
O(n) | création d’une liste |
map.values() |
O(n) | création d’une liste |
Règles complémentaires :
- les collisions de hachage sont gérées par l’implémentation
- aucune opération ne dépend de la taille des valeurs
- le coût du calcul de hash dépend uniquement du type
K - aucune mutation structurelle implicite
Les structures list<T> et map<K,V> peuvent contenir des valeurs de type prototype, c’est-à-dire des instances clonées à partir d’un prototype.
Cette capacité fait partie intégrante du modèle de données de ProtoScript V2 et ne constitue pas un cas particulier.
T(ouVpourmap<K,V>) peut être un type prototype- les collections sont strictement monomorphes
- aucune collection hétérogène n’est autorisée
- le type statique de la collection gouverne les accès et les méthodes disponibles
Exemples :
prototype Point { int x; int y; }
prototype ColoredPoint : Point { int color; }
list<Point> points;
points.push(Point.clone());
points.push(ColoredPoint.clone()); // autorisé (substitution)
map<string, Point> table;
table["a"] = ColoredPoint.clone(); // autoriséLes règles de substitution structurelle définies en section 4 s’appliquent intégralement aux collections.
- une instance d’un prototype enfant peut être stockée dans une collection typée par le prototype parent
- la validité de la substitution est vérifiée à la compilation
- aucun test de type runtime n’est requis
L’accès aux éléments est limité par le type statique de la collection :
Point p = points[0];
p.x = 1; // autorisé
p.color = 3; // erreur de compilation- les collections stockent des références structurées vers les instances
- aucun boxing dynamique n’est introduit
- aucun surcoût runtime lié au type réel des éléments
- les garanties de complexité (
O(1),O(n)) restent inchangées
ProtoScript V2 ne permet pas, dans les collections :
- de tests dynamiques de type (
instanceof, RTTI) - de downcast explicite ou implicite
- de mutation structurelle dépendante du type réel
Tout comportement doit être exprimé via le type statique et la délégation structurelle.
ProtoScript V2 repose sur un invariant global unique, valable pour les objets, les collections et les vues :
Le type statique gouverne toujours les accès, indépendamment du type réel stocké.
Conséquences normatives :
- les prototypes, les collections (
list,map) et les vues (slice,view) partagent exactement les mêmes règles de typage - la substitution parent / enfant est autorisée partout, mais uniquement de manière statique et structurelle
- aucun mécanisme dynamique (RTTI,
instanceof, downcast) n’est jamais introduit - les garanties de performance et de layout mémoire sont préservées à tous les niveaux
Ainsi, les constructions suivantes sont toutes équivalentes du point de vue des règles de typage :
Point p;
list<Point> lp;
view<Point> vp;Dans tous les cas :
- seuls les champs et méthodes de
Pointsont accessibles - le comportement ne dépend jamais du type réel (
PointouColoredPoint) - le compilateur peut raisonner de manière uniforme et déterministe
Cet invariant garantit la cohérence globale du langage et constitue l’un des piliers de sa compilabilité et de ses performances.
ProtoScript V2 définit des types de vue non possédants permettant de référencer des sous-parties de données sans allocation ni copie.
slice<T>est une vue mutableview<T>est une vue en lecture seule
Les types slice<T> et view<T> suivent exactement les mêmes règles de typage et de substitution que list<T> et map<K,V>.
Ils peuvent donc référencer des instances de prototypes, y compris dans un contexte de substitution parent / enfant, sans introduire de mécanisme dynamique supplémentaire.
Règles fondamentales :
Tpeut être un type prototype- les vues sont monomorphes et statiquement typées
- aucune information de type runtime n’est conservée ou exposée
- le type statique de la vue gouverne les accès possibles
Exemple :
list<Point> points;
points.push(ColoredPoint.clone());
view<Point> v = points.view();
Point p = v[0]; // autorisé
p.color = 1; // erreur de compilation(ptr, length)- aucun ownership
- aucune allocation
slice<T> s = list.slice(offset, length);
view<T> v = list.view(offset, length);- bornes vérifiées
- création en O(1)
slice<T> sub = s.slice(offset, length);
view<T> sub = v.view(offset, length);- aucune allocation
- même buffer sous-jacent
view<glyph> chars = s.view();
view<glyph> part = s.view(start, length);- indexation en glyphes
- aucune copie UTF-8
slice.length();
slice.isEmpty();
slice.slice(offset, length);
view.view(offset, length);Règles :
- aucune méthode ne peut provoquer d’allocation implicite
- aucune méthode ne peut modifier la taille sous-jacente
slice<T>[i] = xest autorisé, écrit dans le stockage sous-jacent, sans mutation structurelleview<T>interdit toute écriture
ProtoScript V2 adopte un modèle objet prototype-based, sans classes, avec des contraintes fortes de typage statique et de structure afin de garantir lisibilité, analyse statique et performances.
Conceptuellement, ProtoScript V2 met en œuvre une délégation statique, et non un héritage dynamique.
Les objets sont des structures fermées, définies à partir de prototypes explicites, avec un layout mémoire déterministe.
- il n’existe aucune notion de classe
- un objet est créé par clonage d’un prototype
- les prototypes sont définis explicitement
- la structure d’un objet est figée après sa définition
- aucun champ ne peut être ajouté ou supprimé dynamiquement
Ce modèle permet :
- une sémantique simple et lisible
- une résolution statique des accès
- des performances comparables à des structures C
Un prototype définit :
- un ensemble de champs typés
- un ensemble de méthodes
- éventuellement un prototype parent
Exemple conceptuel :
prototype Point {
int x;
int y;
function move(int dx, int dy) : void {
self.x = self.x + dx;
self.y = self.y + dy;
}
}Règles :
- les types des champs sont obligatoires
- les méthodes ont des signatures statiquement typées
selfdésigne l’instance courante- la résolution des champs et méthodes est statique
constn’est pas autorisé sur les déclarations de champs
Le modificateur sealed appliqué à un prototype interdit l’héritage depuis ce prototype.
Règles :
- un
sealed prototypepeut déclarer un parent - un
sealed prototypereste instanciable sealedn’interdit pasclone()sealedn’affecte pas les règles d’instanciation
Erreur statique associée :
E3140—SEALED_INHERITANCE: tentative d’héritage depuis unsealed prototype
Les objets sont créés par clonage d’un prototype.
Point p = Point.clone();
p.x = 10;
p.y = 20;Règles :
- Tous les prototypes héritent implicitement d’un prototype racine
Object. clone()est une méthode normale résolue par lookup d’héritage.Object.clone()est la définition par défaut.
Règles normatives :
- Les instances issues de
prototypesont manipulées par référence. - L’opérateur
=copie uniquement la référence vers l’objet. - Aucune copie implicite (profonde ou superficielle) d’instance n’est effectuée.
- Deux variables assignées à partir de la même valeur objet désignent la même instance.
Exemple normatif :
prototype P { int n; }
function main() : void {
P a = P.clone();
P b = a;
b.n = 7;
Debug.assert(a.n == 7); // vrai
}ProtoScript2 ne possède aucun mécanisme implicite de copie d’instance.
clone() n’est pas une primitive spéciale.
Règles normatives :
P.clone()est un appel de méthode normal sur un receveur prototype.p.clone()est un appel de méthode normal sur un receveur instance.- La résolution suit la chaîne d’héritage (lookup normal).
- Si un ancêtre redéfinit
clone()et qu’aucun descendant ne redéfinit, cette redéfinition s’applique. Object.clone()alloue un objetoet fixe :o.__proto = Psi receveur prototypePo.__proto = p.__protosi receveur instancep
- Aucune logique implicite supplémentaire n’est autorisée.
- Toute forme interne de lowering (ex.
__clone_static) est un détail d’implémentation :- elle ne doit jamais contourner le lookup d’héritage de
clone(), - elle ne peut pas modifier l’owner effectivement résolu pour
clone().
- elle ne doit jamais contourner le lookup d’héritage de
Clarifications normatives supplémentaires :
clone()instancie le prototype dynamique du receveur.- L’objet retourné est distinct du receveur.
- Les champs sont initialisés selon les règles du prototype.
clone()ne copie pas l’état dynamique d’une instance existante.
Exemple normatif :
prototype P { int n = 1; }
function main() : void {
P p = P.clone();
p.n = 9;
P q = p.clone();
Debug.assert(q.n == 1); // réinitialisé, pas copié
}clone() instancie toujours le prototype dynamique du receveur.
Règle formelle (normative) :
Après résolution complète d’un appel E.clone(),
si la méthode résolue est nommée exactement "clone",
alors TypeOf(E.clone()) = TypeOf(E).
Cette règle :
- s’applique à tous les niveaux d’héritage,
- ne constitue pas un mécanisme général de covariance implicite,
- ne s’applique à aucune autre méthode.
Exemple normatif :
prototype A {
function clone() : A {
return super.clone();
}
}
prototype B : A { }
function main() : void {
B b = B.clone(); // valide
}Exemple multi-niveaux :
prototype A {
function clone() : A { return super.clone(); }
}
prototype B : A { }
prototype C : B { }
function main() : void {
C c = C.clone(); // valide
}Un module natif ProtoScript en C peut restreindre l’accès à certains prototypes non exportés.
Cette restriction relève du mécanisme de module et n’altère pas la règle générale du langage concernant l’instanciation des prototypes.
Un champ de prototype peut déclarer un initialiseur explicite :
Type name = Expr;Règles :
- un champ peut être déclaré
constsous la formeconst Type name = Expr; constsur champ est autorisé uniquement pour les types scalaires fondamentaux- un champ
constdoit obligatoirement avoir un initialiseur explicite - l’expression d’initialisation est vérifiée statiquement et doit être assignable au type du champ
- l’expression d’initialisation est évaluée au clonage (
clone()), dans l’ordre parent → enfant puis ordre de déclaration - sans initialiseur explicite, la valeur par défaut de 15.3 s’applique
- un initialiseur de champ ne peut pas référencer
self - après instanciation, un champ
constne peut pas être réassigné
Erreurs statiques associées :
E3150—INVALID_FIELD_INITIALIZERE3151—CONST_FIELD_MISSING_INITIALIZERE3130—CONST_REASSIGNMENT
ProtoScript V2 définit un modèle de visibilité à deux niveaux :
public(par defaut)internal
Règles normatives :
- La visibilité
internalest une frontière lexicale attachée au prototype de définition. - Un membre
internalest accessible uniquement depuis :- les méthodes du prototype de définition ;
- les méthodes de ses prototypes enfants.
- La regle d'accès est determinée à la compilation par l'appartenance lexicale de l'expression d'accès à une méthode d'un prototype
Q. L'accès a un membreinternaldéfini dansPest autorisé siQ == PouQest un enfant deP. - Un acces
internalest interdit depuis toute fonction globale, même située dans le même fichier/module que le prototype. - Un acces
internalest interdit depuis un autre prototype non-enfant, même situé dans le même fichier/module. - La possession d'une instance ne confère aucun droit d'accès supplémentaire.
- Tout accès hors de cette frontière est interdit.
- La vérification de visibilité est strictement statique.
- La visibilité n'affecte pas le layout mémoire, l'ordre des champs,
clone(), ni la substitution parent/enfant. internalne modifie pas la résolution statique des méthodes.- Il est interdit de redéfinir une methode
publiceninternaldans un prototype enfant. - Le mot-cle
internalest autorisé uniquement sur les membres de prototype (champs/méthodes) et interdit ailleurs (fonctions globales,group, blocs).
Erreurs statiques associées :
E3200—INVALID_VISIBILITY_LOCATIONE3201—VISIBILITY_VIOLATION
Diagnostic minimal valide :
error[E3201]: member 'state' is internal to prototype 'Core'
ProtoScript V2 supporte un héritage par délégation statique, fortement restreint afin de préserver la lisibilité et les performances.
Un prototype peut déclarer explicitement un prototype parent unique.
prototype ColoredPoint : Point {
int color;
}Règles :
- un prototype peut avoir au plus un parent
- la relation parent → enfant est définie à la compilation
- la chaîne de délégation est figée et linéaire
- aucune modification dynamique de la délégation n’est autorisée
La résolution suit un ordre strict et déterministe :
- champs et méthodes définis dans le prototype courant
- champs et méthodes du prototype parent
- remontée récursive dans la chaîne de délégation
Il n’existe aucune ambiguïté possible, car la délégation est linéaire.
Un prototype enfant peut redéfinir une méthode du parent.
prototype ColoredPoint : Point {
function move(int dx, int dy) : void {
// comportement spécialisé
self.x = self.x + dx;
self.y = self.y + dy;
}
}Règles :
- la signature doit être strictement identique à celle du parent
- aucune surcharge n’est autorisée
- la résolution est statique (pas de dispatch virtuel tardif)
ProtoScript V2 définit super pour l’appel explicite au parent :
super.methodName(args...);Règles normatives :
supern’est valide que dans le corps d’une méthode.superest lié au prototype déclarant la méthode courante.- La résolution de
super.mcommence au parent direct du prototype déclarant. - Le receveur effectif reste le même
selfque la méthode courante.
Diagnostics associés :
E3210—INVALID_SUPER_USAGEE3211—SUPER_NO_PARENTE3212—SUPER_METHOD_NOT_FOUND
Le layout mémoire d’un objet respecte l’ordre suivant :
- champs du prototype parent
- champs du prototype enfant
Conséquences :
- offsets connus à la compilation
- compatibilité ABI avec le parent
- passage sûr d’un objet enfant là où un parent est attendu (substitution structurelle)
Exemple :
prototype Point {
int x;
int y;
}
prototype ColoredPoint : Point {
int color;
}Représentation conceptuelle en mémoire (layout déterministe) :
Point (vu statiquement comme Point)
+0 int x
+8 int y
sizeof(Point) = 16
ColoredPoint (layout réel de l’objet)
+0 int x (hérité de Point)
+8 int y (hérité de Point)
+16 int color (ajouté par ColoredPoint)
sizeof(ColoredPoint) = 24
Règles :
- le préfixe mémoire d’un enfant est bit-à-bit compatible avec le parent
- un
Point*peut pointer sur unColoredPointsans adaptation - l’accès aux champs du parent est identique quel que soit le prototype réel
Code ProtoScript V2 :
prototype Point {
int x;
int y;
function move(int dx, int dy) : void {
self.x = self.x + dx;
self.y = self.y + dy;
}
function sum() : int {
return self.x + self.y;
}
}
prototype ColoredPoint : Point {
int color;
// override (signature identique)
function move(int dx, int dy) : void {
Point.move(self, dx, dy);
self.color = self.color + 1;
}
}
function demo() : int {
Point p = ColoredPoint.clone();
p.x = 10;
p.y = 20;
p.move(1, 2);
return p.sum();
}Traduction C conceptuelle (cible de premier rang) :
// types
typedef struct {
int64_t x;
int64_t y;
} Point;
typedef struct {
// préfixe compatible Point
int64_t x;
int64_t y;
int64_t color;
} ColoredPoint;
// méthodes (appels statiques, self explicite)
static inline void Point_move(Point* self, int64_t dx, int64_t dy) {
self->x = self->x + dx;
self->y = self->y + dy;
}
static inline int64_t Point_sum(const Point* self) {
return self->x + self->y;
}
static inline void ColoredPoint_move(ColoredPoint* self, int64_t dx, int64_t dy) {
// appel parent explicite
Point_move((Point*)self, dx, dy);
self->color = self->color + 1;
}
// clone : allocation/initialisation (dépend runtime)
static inline ColoredPoint ColoredPoint_clone(void) {
ColoredPoint v;
v.x = 0;
v.y = 0;
v.color = 0;
return v;
}
int64_t demo(void) {
// substitution : stockage local en tant que ColoredPoint, vue statique Point*
ColoredPoint cp = ColoredPoint_clone();
Point* p = (Point*)&cp;
p->x = 10;
p->y = 20;
// appel résolu statiquement selon le type connu du récepteur au point d’appel.
// Ici, si le type statique est Point, l’appel est Point_move.
// Pour appeler l’override, il faut un récepteur typé ColoredPoint.
ColoredPoint_move(&cp, 1, 2);
return Point_sum(p);
}Notes normatives :
- aucun dispatch dynamique n’est requis pour exprimer l’héritage
- l’appel du parent est explicite et compilable en appel C direct
- la substitution repose sur la compatibilité de layout (préfixe mémoire)
- l’accès aux membres est gouverné par le type statique
ProtoScript V2 définit des règles strictes de substitution structurelle, permettant d’utiliser un objet enfant là où un objet parent est attendu, sans introduire de polymorphisme dynamique.
Un objet de prototype Child peut être utilisé dans tout contexte où un objet de prototype Parent est attendu si et seulement si :
Childdélègue (directement ou indirectement) àParent- tous les champs et méthodes accessibles via
Parentexistent dansChild - les signatures des méthodes héritées ou redéfinies sont strictement identiques
Ce principe est une forme contrôlée du principe de substitution de Liskov, appliqué de manière structurelle et statique.
Dans l’OO classique à classes (Java, C++, C#), le principe de substitution de Liskov est :
- conceptuel, mais rarement garanti structurellement
- dépendant du polymorphisme dynamique
- souvent violé par des effets de bord, des états internes ou des contrats implicites
ProtoScript V2 adopte une approche différente :
- la substitution est vérifiée à la compilation
- elle repose sur la délégation structurelle, pas sur des hiérarchies nominales complexes
- elle n’introduit aucun dispatch dynamique, aucune vtable
En conséquence :
- un objet enfant est substituable au parent par construction, non par convention
- la validité de la substitution ne dépend pas du comportement runtime
- le compilateur peut raisonner sur les objets comme sur des structures C compatibles
ProtoScript V2 privilégie ainsi une lecture mécanique et vérifiable de Liskov, là où l’OO classique en propose une lecture essentiellement contractuelle et dynamique.
Les mécanismes suivants, courants dans l’OO à classes, sont explicitement exclus de ProtoScript V2, car ils introduisent ambiguïté, coûts cachés ou dépendances runtime.
Vtables et dispatch virtuel
- appel de méthode résolu au runtime
- indirection mémoire systématique
- comportement dépendant du type dynamique réel
→ Rejeté : ProtoScript V2 utilise uniquement des appels statiquement résolus, sans table virtuelle.
Downcast et cast dynamique (dynamic_cast, (Child)x)
- hypothèses runtime sur le type réel
- erreurs détectées tardivement
- nécessité de RTTI
→ Rejeté : aucun cast dynamique, aucun test runtime de type. Si le code compile, la substitution est valide.
instanceof** et tests de type**
- logique conditionnelle dépendante du type dynamique
- code fragile face à l’évolution des hiérarchies
→ Rejeté : ProtoScript V2 impose que le comportement soit déterminé par le type statique et la structure.
Ces mécanismes sont remplacés par :
- une délégation structurelle explicite
- un typage statique strict
- des layouts mémoire compatibles
- une substitution vérifiable à la compilation
| Aspect | ProtoScript V2 | Java / C++ (OO classique) |
|---|---|---|
| Modèle objet | Prototypes, délégation statique | Classes, héritage nominal |
| Héritage multiple | Non | C++ : oui / Java : interfaces |
| Dispatch de méthodes | Statique | Dynamique (vtable) |
| Vtables | Aucune | Centrale au runtime |
| Substitution | Structurelle, vérifiée à la compilation | Nominale, partiellement contractuelle |
| Override | Signature strictement identique | Polymorphe, parfois covariant |
| Downcast | Interdit | Autorisé (souvent risqué) |
instanceof / RTTI |
Absent | Présent |
| Ajout dynamique de champs | Impossible | Impossible (mais contournements RTTI) |
| Coût d’un appel méthode | Équivalent appel C | Indirection + vtable |
| Compilation C | Directe, naturelle | Indirecte / non idiomatique |
Ce tableau met en évidence que ProtoScript V2 privilégie la prévisibilité, la vérifiabilité et la performance, là où l’OO classique privilégie la flexibilité dynamique, au prix d’une complexité runtime accrue.
| Aspect | ProtoScript V2 | JavaScript |
|---|---|---|
| Modèle objet | Prototypes statiques et fermés | Prototypes dynamiques et ouverts |
| Ajout de champs | Interdit après définition | Autorisé à tout moment |
| Typage | Statique strict | Dynamique |
| Résolution des méthodes | À la compilation | Au runtime |
| Dispatch | Statique | Dynamique |
this / self |
self explicite, statique |
this dynamique et contextuel |
prototype / __proto__ |
Déclaration explicite, figée | Chaîne mutable et observable |
instanceof |
Absent | Présent |
| Cast / tests de type | Absents | Fréquents et nécessaires |
| Performances | Prévisibles, proches du C | Dépendantes du moteur (JIT) |
| Compilation C | Naturelle | Non applicable |
Ce tableau montre que, bien que les deux langages soient prototype-based, ProtoScript V2 adopte une approche déterministe et compilable, tandis que JavaScript privilégie la flexibilité dynamique, au prix d’ambiguïtés sémantiques et de performances dépendantes du runtime.
Le type statique d’une variable détermine :
- les champs accessibles
- les méthodes appelables
- les garanties de layout mémoire
Exemple :
Point p = ColoredPoint.clone();
p.x = 1; // autorisé
p.y = 2; // autorisé
p.move(1, 1); // autorisé
p.color = 3; // erreur de compilationRègles :
- l’accès est limité au type statique (
Point) - aucun downcast implicite n’est autorisé
- aucun test dynamique de type n’est fourni par le langage
ProtoScript V2 ne définit :
- ni
instanceof - ni
dynamic_cast - ni RTTI exposée au langage
Tout changement de vue typée doit être explicite et structurellement justifié au moment de la compilation.
Ces règles garantissent :
- absence totale de dispatch dynamique
- appels directs compilables en C
- aucune indirection supplémentaire
- performances identiques à des structures C équivalentes
Considérons les prototypes suivants :
prototype Point {
int x;
int y;
}
prototype ColoredPoint : Point {
int color;
}La représentation mémoire d’une instance de ColoredPoint est strictement la suivante :
+-----------------+
| Point.x (int) | offset 0
+-----------------+
| Point.y (int) | offset 8
+-----------------+
| color (int) | offset 16
+-----------------+
Règles garanties :
- les champs du parent précèdent toujours ceux de l’enfant
- les offsets sont connus à la compilation
- un
ColoredPoint*est ABI-compatible avec unPoint*
Cela rend la substitution sûre, triviale et sans coût.
prototype Point {
int x;
int y;
function move(int dx, int dy) : void {
self.x = self.x + dx;
self.y = self.y + dy;
}
}
prototype ColoredPoint : Point {
int color;
}
function translate(Point p) : void {
p.move(1, 1);
}
ColoredPoint cp = ColoredPoint.clone();
cp.x = 10;
cp.y = 20;
cp.color = 3;
translate(cp);typedef struct {
int64_t x;
int64_t y;
} Point;
typedef struct {
Point base; // layout compatible
int64_t color;
} ColoredPoint;
static inline void Point_move(Point* self, int64_t dx, int64_t dy) {
self->x += dx;
self->y += dy;
}
void translate(Point* p) {
Point_move(p, 1, 1);
}
void example(void) {
ColoredPoint cp;
cp.base.x = 10;
cp.base.y = 20;
cp.color = 3;
translate((Point*)&cp); // cast sûr et implicite côté ProtoScript
}Points clés :
- aucun vtable
- aucun test runtime
- aucun coût caché
- substitution réalisée par compatibilité mémoire
Ce modèle verrouille définitivement la correspondance entre le modèle objet ProtoScript V2 et une compilation C directe et performante.
Les méthodes :
- sont résolues statiquement
- ne sont pas des closures capturant implicitement l’environnement
- accèdent à l’instance via
self
Exemple :
p.move(1, 2);Règles :
- l’appel de méthode est équivalent à un appel de fonction avec
selfexplicite - aucun dispatch dynamique tardif
selfne peut pas être retourné ; les méthodes mutantes doivent retournervoid- aucun style fluent/chaîné basé sur mutation n’est supporté
Le modèle objet garantit :
- layout mémoire déterministe
- offsets de champs connus à la compilation
- absence de polymorphisme dynamique non contrôlé
- compatibilité directe avec une compilation vers C
Ce modèle permet d’exprimer une orientation objet sans introduire les coûts et ambiguïtés des systèmes à classes dynamiques.
Cette section définit le modèle de fonctions de ProtoScript V2, la syntaxe des signatures, les règles de typage statique et les mécanismes d’inférence.
Le modèle vise à concilier lisibilité humaine, simplicité sémantique et exploitabilité machine.
Une fonction est déclarée par le mot-clé function, suivi de son nom, de sa liste de paramètres et de son type de retour.
function add(int a, int b) : int {
return a + b;
}Règles :
- les paramètres sont typés explicitement
- le type de retour est obligatoire
- l’ordre des paramètres fait partie de la signature
- aucune surcharge de fonction n’est autorisée
ProtoScript V2 utilise une syntaxe de type postfixée pour les signatures de fonctions.
function f(int x, float y) : float { ... }Règles :
- le séparateur
:introduit le type de retour - la syntaxe
->n’est pas utilisée - cette forme évite toute ambiguïté syntaxique
ProtoScript V2 supporte une inférence de type locale, limitée et prévisible.
var x = 42; // x : int
var y = 3.14; // y : float
var s = "hello"; // s : stringRègles :
- l’inférence s’applique uniquement à l’initialisation
- une variable déclarée avec
varreçoit un type définitif - le type ne peut pas changer ultérieurement
- aucune inférence globale ou polymorphe
Portée :
constest autorisé uniquement pour les variables de types scalaires fondamentaux
Sémantique :
constinterdit toute réaffectationconstne modifie pas le typeconstne crée pas d’immuabilité profonde
Erreur statique associée :
E3130—CONST_REASSIGNMENT
- les paramètres sont passés par valeur
- pour les structures (
list,map, objets), la valeur est une référence structurée - les fonctions retournent une seule valeur
function length(list<int> v) : int {
return v.length();
}Une fonction sans valeur de retour explicite retourne void.
function log(string s) : void {
Io.printLine(s);
}- aucune conversion implicite entre types
- toute incompatibilité de type est une erreur de compilation
- les types sont connus et figés à la compilation
- les signatures de fonctions sont entièrement résolues statiquement
ProtoScript V2 ne définit pas de fonctions génériques.
Il n’existe aucun mécanisme de paramétrage par type (<T>), d’inférence polymorphe ou de monomorphisation implicite.
ProtoScript V2 ne définit pas de fonctions comme valeurs.
Les fonctions ne peuvent pas être :
- stockées dans des variables
- passées comme paramètres
- retournées par d’autres fonctions
Les comportements paramétrables sont exprimés exclusivement via :
- les prototypes
- les méthodes
- l’override statique
Ce choix garantit :
-
une résolution entièrement statique des appels
-
l’absence totale de closures, lambdas ou callbacks dynamiques
-
une compilation directe et transparente vers le C
-
des performances prévisibles et optimisables
-
aucune conversion implicite entre types
-
toute incompatibilité de type est une erreur de compilation
-
les types sont connus et figés à la compilation
-
les signatures de fonctions sont entièrement résolues statiquement
ProtoScript V2 ne définit pas de paramètres optionnels ni de valeurs par défaut.
Règles normatives :
- toute fonction doit être appelée avec l’ensemble de ses paramètres explicitement fournis
- l’arité d’une fonction fait partie intégrante de sa signature
- aucun mécanisme de surcharge implicite ou d’arguments omis n’est autorisé
Ce choix garantit :
- une lisibilité immédiate des appels de fonction
- une analyse statique simple et complète
- une traduction directe vers des appels C sans réécriture implicite
ProtoScript V2 supporte des fonctions variadiques strictement typées, homogènes et sans ambiguïté.
La forme variadique est conçue comme un sucre syntaxique contrôlé pour la capture d’un nombre variable de valeurs d’un type unique, sans introduire de mécanisme dynamique.
Un paramètre variadique est déclaré comme une liste typée, suivie de ....
function sum(list<int> values...) : int {
int acc = 0;
for (int v of values)
acc = acc + v;
return acc;
}Le type T de list<T> peut être :
- un type scalaire fondamental (
int,float,string, etc.) - un type prototype
- une fonction peut définir au plus un paramètre variadique
- le paramètre variadique doit être le dernier de la signature
- le paramètre variadique est obligatoirement typé sous la forme
list<T> - toutes les valeurs passées dans la partie variadique doivent être de type
T - aucune hétérogénéité n’est autorisée
Clarification normative :
list<T> ...est une syntaxe de déclaration pour un paramètre variadique homogène- le type canonique interne du paramètre variadique est
view<T>(lecture seule) - la transformation
list<T> ...→view<T>est implicite et normativement définie view_of(...)est un intrinsic de compilation et n’est pas exposé comme API utilisateur- la vue créée n’entraîne aucune allocation
- la vue créée a une durée de vie strictement limitée à l’appel
- la vue créée ne peut pas être stockée, retournée ni prolongée
- toute opération nécessitant une collection possédante (
push,pop, redimensionnement, etc.) sur ce paramètre est une erreur de compilation
Exemples :
sum(1, 2, 3); // valide
sum(1); // valide
sum(); // valide (liste variadique vide)
sum(1, "x"); // erreur de compilationÀ l’appel, les arguments variadiques sont capturés comme une vue non possédante (view<T>), sans allocation implicite.
- la capture est O(1)
- aucune copie des éléments n’est effectuée
- le coût est équivalent à un passage explicite de
view<T>
Conceptuellement, un appel :
sum(1, 2, 3);est équivalent à :
view<int> tmp = view_of(1, 2, 3);
sum(tmp);Le paramètre variadique est considéré comme un paramètre unique du point de vue de la signature.
Ainsi :
- l’arité minimale de la fonction reste fixe
- la règle de la section 5.7 reste pleinement valable
Les paramètres non variadiques restent obligatoires ; la partie variadique peut être vide.
Les méthodes définies sur des prototypes peuvent être variadiques, selon exactement les mêmes règles que les fonctions variadiques définies en section 5.8.
Il n’existe aucune règle spéciale pour les méthodes : une méthode variadique est une fonction variadique dont le premier paramètre implicite est self.
Une méthode variadique est déclarée en utilisant un paramètre list<T> ... en dernière position.
prototype Logger {
function log(list<string> messages...) : void {
for (string s of messages)
Io.printLine(s);
}
}Appels valides :
Logger l = Logger.clone();
l.log("start");
l.log("x=", "42", "done");- une méthode peut définir au plus un paramètre variadique
- le paramètre variadique doit être le dernier paramètre explicite (après
self) - le type
Tpeut être un type scalaire fondamental ou un type prototype - toutes les valeurs passées doivent être du même type
T - aucune hétérogénéité n’est autorisée
Pour toute signature se terminant par :
list<T> ident...les règles suivantes s’appliquent :
- L’arité minimale est égale au nombre de paramètres non-variadiques explicites.
selfest exclu du calcul d’arité utilisateur sur appel d’instance (obj.m(...)), et reste explicite sur appel statique (Proto.m(self, ...)).- Si aucun argument n’est fourni pour la partie variadique :
- le paramètre reçoit une
view<T>vide ; - aucune erreur
E1003ARITY_MISMATCHne doit être levée.
- le paramètre reçoit une
- Si le nombre d’arguments est inférieur au nombre de paramètres non-variadiques,
E1003ARITY_MISMATCHdoit être produit. Cette règle ne concerne pas les signatures purement variadiques. - Ces règles sont identiques pour :
- les fonctions globales ;
- les méthodes de prototype.
Exemple valide (aucun paramètre fixe) :
function f(list<int> xs...) : void { }
f(); // valideExemple en erreur :
function f(int a, list<int> xs...) : void { }
f(); // ERREUR : paramètre fixe "a" manquant -> E1003 ARITY_MISMATCHLe type statique du récepteur gouverne :
- les méthodes accessibles
- la signature exacte, y compris la partie variadique
prototype Point {
function moveAll(list<Point> points...) : void { ... }
}L’appel :
p.moveAll(p1, p2, p3);est équivalent à :
view<Point> tmp = view_of(p1, p2, p3);
Point.moveAll(p, tmp);ProtoScript V2 autorise l’override de méthodes dans un prototype enfant avec les règles suivantes.
Cela vaut pour toutes les méthodes, variadiques ou non.
Règles normatives :
- le nom de la méthode doit être identique
- le nombre de paramètres doit être identique
- le type de chaque paramètre doit être identique et dans le même ordre
- le type de retour doit être identique ou un sous-type du retour parent
- aucune surcharge par variation de paramètres n’est autorisée
Règle formelle (normative) :
ReturnType_override ≤ ReturnType_parent
Diagnostic en cas de violation :
E3221 INVALID_OVERRIDE_RETURN_TYPE
Ainsi, ProtoScript V2 ne supporte pas la surcharge de méthodes au sens de Java ou C++.
Une méthode variadique peut être override dans un prototype enfant avec paramètres identiques, et un retour identique ou covariant.
Règles normatives spécifiques :
- le paramètre variadique (
list<T> ...) doit être présent - le type
Tdoit être identique - le paramètre variadique doit rester en dernière position
- aucune modification de l’arité minimale n’est autorisée
Exemple valide :
prototype Logger {
function log(list<string> messages...) : void {
for (string s of messages)
Io.printLine("[LOG] ".concat(s));
}
}
prototype DebugLogger : Logger {
function log(list<string> messages...) : void {
Io.printLine("[DEBUG]");
Logger.log(self, messages);
}
}Exemples interdits :
prototype BadLogger1 : Logger {
function log(string s) : void { } // erreur : signature différente
}
prototype BadLogger2 : Logger {
function log(list<string> messages..., int level) : void { } // erreur
}Dans tous les cas :
- la résolution de la méthode reste strictement statique
- aucun dispatch dynamique n’est introduit
- les règles de substitution et de performance restent inchangées
- la capture variadique se fait sous forme de
view<T> - aucune allocation implicite
- aucun dispatch dynamique supplémentaire
- coût identique à un appel de méthode non variadique
Les méthodes variadiques ne modifient en rien les garanties de performance ni les invariants du modèle objet.
Le modèle de fonction garantit :
- appels directs sans dispatch dynamique
- possibilité d’inlining agressif
- génération de code C simple et lisible
Les fonctions constituent l’unité fondamentale d’optimisation du compilateur.
Cette section définit l’ensemble des opérateurs du langage, leurs catégories et leurs règles de typage. Les opérateurs sont strictement typés, résolus statiquement et sans conversion implicite.
- aucun opérateur n’introduit de conversion implicite
- le type des opérandes détermine le type du résultat
- toute incompatibilité de type est une erreur de compilation
- les opérateurs sont des primitifs du langage (non surchargeables)
Opérateurs supportés : +, -, *, /
Opérateurs arithmétiques additionnels :
| Opérateur | Description |
|---|---|
% |
reste entier |
++ |
incrémentation |
-- |
décrémentation |
- |
négation unaire |
Types autorisés :
intavecint→intfloatavecfloat→float
Règles :
- aucune promotion automatique (
int + floatest interdit) - la division entière
/entreintest une division entière %est réservé aux types entiers
Opérateurs supportés : ==, !=, <, <=, >, >=
Résultat : bool
Règles :
- comparaisons autorisées uniquement entre types identiques
- pour
string,==/!=comparent le contenu exact ;</<=/>/>=comparent lexicographiquement la séquence UTF‑8 (pas de locale, pas de normalisation) - pour les types structurés (objets/prototypes,
list,map,slice,view), la comparaison porte sur l’identité de valeur (pas de deep compare implicite)
Opérateurs supportés : &&, ||, !
Types autorisés :
- opérandes de type
bool
Règles :
- évaluation court-circuitée (
&&,||) - aucun autre type n’est accepté
Opérateur principal : =
Règles :
- le type de l’expression assignée doit être strictement identique au type de la variable
- l’affectation est une instruction, sans valeur de retour
- aucune affectation chaînée n’est autorisée
Opérateurs composés (+=, -=, *=, /=) :
- définis uniquement lorsque l’opérateur arithmétique correspondant est valide
- équivalents à une écriture explicite sans effet de bord
L’interdiction de l’affectation chaînée garantit l’absence d’effets de bord implicites, simplifie l’analyse statique et renforce la lisibilité.
- accès aux éléments :
[] - accès aux membres :
. - appel de fonction ou méthode :
()
Règles :
- l’accès
[]est défini pourlist,mapetstring - pour
string, l’accès[]est strictement en lecture - toute affectation via
string[index] = valueest interdite (erreur statique) - l’opérateur
.est résolu statiquement selon le type connu
ProtoScript V2 définit des opérateurs binaires explicites pour les types entiers int et byte.
Opérateurs supportés : &, |, ^, ~, <<, >>
Types autorisés :
intavecint→intbyteavecbyte→byte
Règles :
- aucun opérateur binaire n’est défini pour
float - aucun mélange de types (
int & byteest interdit) - les décalages sont effectués sur la largeur native du type
ProtoScript V2 ne définit pas :
- d’opérateur de concaténation de chaînes
- d’opérateur ternaire implicite sur types non booléens
- d’opérateurs de surcharge utilisateur
- d’opérateurs dépendants du runtime ou du type réel
Ces absences sont des choix de conception, visant à garantir lisibilité, prévisibilité et performances.
Cette section définit les structures de contrôle de flux de ProtoScript V2. Elles sont volontairement classiques, explicites et sans ambiguïté, afin de garantir lisibilité, prévisibilité et performances.
ProtoScript V2 ne cherche pas à innover syntaxiquement dans ce domaine, mais à fournir un ensemble cohérent et suffisant.
- un bloc est délimité par
{et} - un bloc est requis lorsqu’il contient plusieurs instructions
- lorsqu’une structure de contrôle ne contient qu’une seule instruction, les accolades sont optionnelles
if (x > 0)
y = x;if (x > 0) {
y = x;
} else if (x < 0) {
y = -x;
} else {
y = 0;
}Règles :
- l’expression conditionnelle doit être de type
bool - aucune conversion implicite vers
bool - la chaîne
else ifest une simple composition syntaxique
ProtoScript V2 supporte l’opérateur ternaire classique.
int y = (x > 0) ? x : -x;Règles :
- la condition est de type
bool - les deux branches doivent avoir un type compatible
- l’expression résultante est typée statiquement
- l’opérateur conditionnel est une expression pure : il ne peut introduire ni affectation implicite, ni effet de bord non explicitement visible
for (int i = 0; i < n; i++) {
sum = sum + i;
}- toutes les expressions sont optionnelles (
for (;;)autorisé)
for (int v of list) {
sum = sum + v;
}Règles :
ofitère sur les valeurs- l’ordre d’itération est l’ordre naturel de la structure
- applicable à
string,list<T>,map<K,V>(valeurs)
for (string k in map) {
print(k);
}Règles :
initère sur les clés- applicable uniquement aux structures associatives (
map)
while (cond) {
work();
}
do {
work();
} while (cond);Règles :
- la condition est évaluée explicitement
- la condition doit être de type
bool
for (int i = 0; i < n; i++) {
if (i == 5)
continue;
if (i == 8)
break;
}breaksort de la boucle courantecontinuepasse à l’itération suivante
switch (x) {
case 0:
y = 0;
break;
case 1:
y = 10;
break;
default:
y = -1;
break;
}Règles :
- l’expression du
switchest évaluée une seule fois - les
casesont comparés par égalité stricte - le fallthrough implicite est interdit
- chaque clause
caseetdefaultdoit se terminer explicitement parbreak,return,throwou toute autre instruction qui quitte leswitch - l’absence de terminaison explicite est une erreur statique
defaultest optionnel
ProtoScript V2 rejette le fallthrough implicite afin que chaque branche de contrôle ait un effet local, explicite et immédiatement lisible.
Les structures de contrôle de flux garantissent :
- absence d’effets implicites
- absence de conversions automatiques
- traduction directe vers des structures de contrôle C
- aucune allocation implicite liée au contrôle de flux
Cette section définit le modèle d’itération de ProtoScript V2. L’itération est conçue comme un mécanisme simple, explicite et déterministe, sans abstraction implicite ni allocation cachée.
Le modèle d’itération est volontairement séparé des structures de contrôle (section 6) afin de clarifier les responsabilités sémantiques.
ProtoScript V2 définit l’itération comme une capacité intrinsèque de certains types, et non comme un protocole dynamique.
Sont itérables nativement :
string(englyph)list<T>(enT)map<K,V>
L’ordre d’itération est strictement déterministe et dépend uniquement de la structure sous-jacente.
La forme for … of permet d’itérer sur les valeurs d’une structure itérable.
for (int v of list) {
sum = sum + v;
}Règles :
- la variable d’itération est typée explicitement ou par inférence locale
ofitère sur les éléments- l’ordre d’itération est l’ordre naturel de la structure
- aucune allocation implicite n’est effectuée
Types supportés :
for (glyph g of string)for (T v of list<T>)for (V v of map<K,V>)
La forme for … in permet d’itérer sur les clés d’une structure associative.
for (string key in map) {
print(key);
}Règles :
initère sur les clés- applicable uniquement à
map<K,V> - l’ordre d’itération est l’ordre d’insertion
for (var v of list) {
// v a le type T
}Règles :
- l’inférence est locale à la variable d’itération
- le type inféré est fixe et non polymorphe
- aucune conversion implicite n’est autorisée
Les garanties suivantes sont normatives :
- l’itération est O(n) sur le nombre d’éléments
- aucune allocation implicite liée à l’itération
- aucune création d’objet itérateur visible
- traduction directe vers une boucle
forouwhileen C
ProtoScript V2 ne définit :
- ni interface
Iterable - ni méthode
iterator() - ni objet itérateur exposé
Les types itérables sont connus statiquement.
Ce choix permet :
- une analyse statique complète
- des performances prévisibles
- une compilation directe vers du code bas niveau
Le modèle d’itération de ProtoScript V2 privilégie :
- la simplicité
- la déterminisme
- la performance
- l’absence d’abstraction inutile
Il est conçu pour être compris immédiatement par l’humain et exploité efficacement par la machine.
Cette section définit les règles d’exécution de ProtoScript V2, les invariants runtime et les objectifs de performance considérés comme non négociables.
ProtoScript V2 est conçu pour produire un code prévisible, analysable et performant, sans dépendre de mécanismes dynamiques opaques.
ProtoScript V2 adopte un modèle d’exécution strict et déterministe.
Règles fondamentales :
- l’ordre d’évaluation des expressions est strictement défini
- aucune évaluation paresseuse implicite
- aucune conversion implicite de type
- toute allocation mémoire est explicite ou documentée
Le comportement d’un programme est indépendant de l’environnement d’exécution (pas de dépendance à un moteur ou à un JIT).
ProtoScript V2 vise des performances de première catégorie, comparables à du C bien écrit.
Les objectifs suivants sont considérés comme non négociables :
- aucune allocation implicite dans les boucles critiques
- aucune conversion de type implicite
- accès direct en temps constant aux données structurées
- représentation mémoire stable et déterministe
- performances prévisibles, indépendantes du contenu des données
- capacité à traiter efficacement des charges lourdes (image, signal, texte, données)
Le langage doit permettre l’écriture d’algorithmes intensifs sans pénalité structurelle.
Les invariants suivants sont garantis par le compilateur et le runtime.
int: entier signé 64 bitsfloat: flottant double précision IEEE-754byte: entier non signé 8 bitsglyph: entier non signé 32 bits (Unicode scalar value)
list
- stockage contigu en mémoire
- représentation
(ptr, length, capacity) - éléments monomorphes de type
T - aucun boxing
map<K,V>
- table de hachage
- accès O(1) amorti
- ordre d’insertion conservé
slice / view
- vues non possédantes
- représentation
(ptr, length) - aucune allocation
- aucune extension de durée de vie
string
- stockage UTF-8
- index glyphes construit paresseusement
- itération séquentielle sans allocation
- layout mémoire figé par prototype
- offsets de champs connus à la compilation
- aucune mutation structurelle dynamique
ProtoScript V2 ne définit pas de garbage collector obligatoire.
Règles :
- les allocations sont explicites
- la durée de vie des objets est déterminée statiquement ou par le scope
- aucune allocation cachée liée au langage
Les stratégies d’allocation (heap, arena, stack étendue) sont des choix d’implémentation compatibles avec la spec.
Une implémentation peut proposer plusieurs modes :
-
mode debug :
- vérifications de bornes
- assertions actives
- messages d’erreur détaillés
-
mode release :
- vérifications minimales
- optimisations agressives
- aucune surcharge inutile
Le passage d’un mode à l’autre ne modifie pas la sémantique observable du programme.
Les erreurs runtime :
- sont levées sous forme d’exceptions dérivant de
Exception - incluent implicitement : fichier, ligne, colonne
- ne peuvent pas être silencieuses
Aucune erreur runtime n’est convertie implicitement en valeur.
La sémantique d’exécution de ProtoScript V2 privilégie :
- la prévisibilité
- la performance
- la lisibilité
- l’absence de magie runtime
Elle est conçue pour être directement traduite vers du code bas niveau sans perte d’intention.
Cette section définit la représentation intermédiaire (IR) de ProtoScript V2 et les stratégies de compilation associées.
L’IR est conçu comme un pivot central, simple, typé et déterministe, permettant :
- une analyse statique complète,
- des optimisations prévisibles,
- une compilation vers plusieurs backends,
- en particulier une génération de code C de premier rang.
L’IR de ProtoScript V2 constitue une forme normalisée du programme source.
Ses objectifs principaux sont :
- éliminer toute ambiguïté syntaxique ou sémantique,
- rendre explicites les types, les allocations et les accès mémoire,
- servir de base unique à l’optimisation et à la génération de code.
Chaque programme ProtoScript V2 valide peut être traduit sans perte d’information vers l’IR.
L’IR respecte les invariants suivants :
- typage entièrement statique
- aucune conversion implicite
- chaque variable possède un type concret
- chaque accès mémoire est explicite
- chaque champ et élément possède un offset connu
- aucun dispatch dynamique tardif
L’IR est volontairement bas niveau, proche d’une forme SSA, sans être lié à une implémentation particulière.
Les constructions du langage sont traduites de manière directe :
- fonctions → unités IR avec signature typée
- prototypes → structures IR avec layout figé
- appels de méthodes → appels de fonctions avec
selfexplicite - boucles → structures de contrôle explicites
slice/view→ vues mémoire sans allocation- membres de
group→ constantes typées (substitution, aucune entité IR)
Aucune transformation spéculative n’est effectuée à ce stade.
Les optimisations s’effectuent sur l’IR, jamais directement sur le code source.
Optimisations attendues :
- inlining de fonctions
- élimination du code mort
- propagation de constantes
- suppression des vérifications inutiles en mode release
Toutes les optimisations doivent préserver strictement la sémantique observable du programme.
La compilation vers C est une cible de premier rang de ProtoScript V2.
Objectifs :
- produire du C lisible et standard
- s’appuyer sur les compilateurs existants (clang, gcc)
- bénéficier de leurs optimisations (vectorisation, register allocation)
- faciliter l’interopérabilité avec des bibliothèques natives
| ProtoScript | C |
|---|---|
int |
int64_t |
float |
double |
byte |
uint8_t |
glyph |
uint32_t |
list<T> |
struct { T* ptr; size_t len; size_t cap; } |
slice<T> / view<T> |
struct { T* ptr; size_t len; } |
L’IR permet également :
- une compilation directe vers du code natif
- une interprétation simple pour le debug
- des backends expérimentaux (WASM, etc.)
Ces backends partagent le même IR et les mêmes garanties sémantiques.
L’IR de ProtoScript V2 est :
- simple
- typé
- déterministe
- conçu pour la performance
Il constitue le socle technique reliant la lisibilité du langage source à l’efficacité du code généré.
Cette section définit le modèle d’erreurs et d’exceptions de ProtoScript V2. L’objectif est de fournir des messages clairs, contextualisés, et une gestion explicite des erreurs, sans ambiguïté sémantique ni coût caché.
ProtoScript V2 considère la qualité des erreurs comme un élément fondamental de l’ergonomie du langage.
- toute erreur est explicite
- aucune erreur n’est silencieuse
- aucune erreur n’est convertie implicitement en valeur
- les erreurs sont soit détectées à la compilation, soit levées à l’exécution
Les erreurs runtime sont représentées par des exceptions.
Les diagnostics statiques et runtime font partie du contrat de stabilité du langage.
Format normatif obligatoire :
<file>:<line>:<column> <ErrorCode> <ErrorName>: <message>
Règles normatives :
- une implémentation conforme MUST produire exactement ce format sur la ligne principale
fileMUST être le chemin logique source du programmelineetcolumnMUST pointer sur le premier caractère du lexème fautifErrorCodeMUST être un code canonique stable (ex.E2001,R1004)ErrorNameMUST être la catégorie canonique stable associée au code- pour les erreurs liées aux identifiants, le lexème fautif MUST être cité (
'name') - le message MUST expliciter :
- ce qui est fautif
- ce qui est attendu (
expected ...)
- les lignes de suggestion sont optionnelles et MUST apparaître uniquement après la ligne principale
- une suggestion MUST NOT modifier le code d’erreur
Format de suggestion (si présente) :
Did you mean 'X'?
Did you mean 'X' or 'Y'?
Une implémentation MUST NOT émettre d’autres formats de continuation.
Exemples conformes :
script.pts:15:16 E2001 UNRESOLVED_NAME: unknown identifier 'Colr' (expected value)
Did you mean 'Color'?
script.pts:10:5 E2001 UNRESOLVED_NAME: unknown group member 'Purple' in group 'Color' (expected member)
Les codes de diagnostic sont hiérarchiques et stables.
Plages réservées (normatives) :
| Domaine | Plage | Statut |
|---|---|---|
| Parse / lexing / arité | E1xxx |
réservé |
| Résolution de noms | E2xxx |
réservé |
| Typage | E3xxx |
réservé |
| Sémantique statique | E4xxx |
réservé |
| Modules / imports | E5xxx |
réservé |
| Vérifications statiques runtime futures | E6xxx |
réservé |
| Runtime | R1xxx |
réservé |
Règles :
- un code MUST être unique
- un code canonique publié MUST NOT être repurposé
- l’association
ErrorCode -> ErrorNameMUST rester stable entre versions mineures - les codes font partie du contrat de compatibilité de ProtoScript2
Les frontends doivent construire les diagnostics via un objet structuré avant formatage.
Champs normatifs :
filelinecolumncodenamemessageexpected_kind(optionnel)actual_kind(optionnel)suggestions[](optionnel)
Règles :
- le rendu texte (10.2) MUST être produit par un formateur centralisé
- la logique sémantique MUST transmettre des données structurées (code, nom, position, contexte), pas uniquement une chaîne finale
suggestions[]MUST être déterministe pour un même input
Pour les erreurs de résolution de noms (E2xxx), une implémentation peut ajouter une suggestion déterministe.
Règles :
- la distance utilisée MUST être une distance d’édition (Levenshtein)
- le seuil maximal MUST être
2 - une suggestion est autorisée uniquement si un meilleur candidat minimal est trouvé
- si plusieurs candidats partagent la distance minimale :
- un format à deux suggestions est autorisé (
XouY) - en cas d’ambiguïté plus large, aucune suggestion ne doit être émise
- un format à deux suggestions est autorisé (
- l’ordre des suggestions MUST être déterministe
- les suggestions MUST NOT modifier le code ou le nom du diagnostic principal
Toutes les exceptions dérivent du prototype racine Exception.
Caractéristiques :
Exceptionest un prototype standard du langage- elle contient implicitement :
- le fichier
- la ligne
- la colonne
- elle peut contenir explicitement :
- un message
- une cause
ExceptionetRuntimeExceptionsont des prototypes non appelables : l’instanciation se fait exclusivement viaclone()
Ces champs sont des propriétés accessibles de l’objet d’exception.
Prototype standard additionnel :
RuntimeExceptionest un prototype standard qui dérive deException- toutes les erreurs runtime doivent lever une instance de
RuntimeException - seules les instances de
RuntimeExceptionexposent obligatoirement :code(code canonique, ex.R1004)category(catégorie canonique, ex.RUNTIME_DIVIDE_BY_ZERO)
Aucune valeur autre qu’une instance dérivée du prototype Exception ne peut être levée.
Une exception peut être levée explicitement :
Exception e = Exception.clone();
e.message = "invalid state";
throw e;Règles :
- seul un objet dérivant de
Exceptionpeut être levé throwattend une valeur d’exception déjà construite, jamais un effet ni un constructeur implicite- les champs de localisation sont automatiquement renseignés par le runtime
- le message et la cause sont à l’initiative du développeur
ProtoScript V2 supporte la gestion structurée des exceptions.
try {
work();
} catch (Exception e) {
log(e.toString());
} finally {
cleanup();
}Règles :
catchintercepte une exception par type (prototype), avec substitution parent/enfantcatch (Exception e)est un catch‑all- la variable
eest une instance d’un prototype dérivant deException eexpose au minimum :file,line,column,message,cause- si l’exception dérive de
RuntimeException,e.codeete.categorysont définis finallyest toujours exécuté- l’exception est propagée si elle n’est pas interceptée
- une exception non interceptée produit un diagnostic
R1011/UNHANDLED_EXCEPTIONincluant :- le type dynamique de l’exception
- la localisation source
- l’attente d’un
catchcorrespondant
Clarification normative (RTTI et exceptions) :
- l’interdiction de RTTI concerne les valeurs utilisateur : objets/prototypes, collections et vues
- aucune forme de
instanceof, de downcast dynamique ou d’introspection de type n’est autorisée pour les valeurs utilisateur - le mécanisme d’exception repose sur une métadonnée de type interne au runtime, utilisée exclusivement pour la propagation et l’interception des exceptions
- cette métadonnée n’est pas introspectable par le langage et n’est accessible que dans le mécanisme
catch catch (T e)intercepte une exception si son type dynamique estTou dérive deT- ce mécanisme n’introduit aucun RTTI général, ne permet aucun test de type dynamique hors exceptions et ne modifie pas le coût du chemin nominal
Le modèle d’exception garantit :
- le mécanisme de propagation des exceptions (
unwind, dispatch) n’introduit aucun coût tant qu’aucune exception n’est levée - un coût explicite et localisé existe lors de la levée et de la propagation d’une exception
- les vérifications runtime exigées par la spécification font partie de l’exécution normale
- un backend peut éliminer une vérification runtime uniquement s’il prouve statiquement qu’elle est inutile
Les exceptions sont considérées comme un mécanisme de contrôle de flux exceptionnel, et non comme un substitut aux retours de fonction.
Le modèle d’erreurs et d’exceptions de ProtoScript V2 privilégie :
- la clarté des diagnostics
- la cohérence sémantique
- la sécurité
- la prévisibilité des performances
Il est conçu pour faciliter le débogage et renforcer la confiance du développeur dans le langage.
Cette section explicite ce que ProtoScript V2 ne cherche volontairement pas à être, ainsi que les principes directeurs qui guident ces refus.
Ces non-objectifs sont des choix de conception assumés, destinés à préserver la cohérence, la lisibilité et les performances du langage.
ProtoScript V2 ne vise pas :
- la compatibilité avec JavaScript, ECMAScript ou leurs écosystèmes
- l’exécution dans un navigateur ou un moteur web
- le typage dynamique ou graduel
- la métaprogrammation réflexive ou l’introspection runtime
- la modification dynamique des structures de données ou des objets
- la surcharge d’opérateurs implicite
- les conversions implicites entre types
- la dissimulation des allocations mémoire
- les abstractions à coût imprévisible
Ces limitations sont intentionnelles.
ProtoScript V2 refuse les abstractions qui :
- masquent les coûts algorithmiques
- introduisent des effets de bord implicites
- rendent le comportement dépendant du runtime
- empêchent une analyse statique complète
En particulier, ProtoScript V2 n’introduit :
- ni garbage collector obligatoire
- ni mécanisme de dispatch dynamique tardif
- ni protocoles implicites (itérateurs, conversions, hooks)
Tout code ProtoScript V2 doit permettre à un lecteur humain de comprendre :
- les types en jeu
- les allocations effectuées
- les coûts dominants
- les chemins d’erreur possibles
Si une fonctionnalité améliore artificiellement la concision au détriment de cette lisibilité, elle est rejetée.
ProtoScript V2 privilégie un comportement :
- déterministe
- reproductible
- indépendant de l’environnement d’exécution
Deux exécutions du même programme, avec les mêmes entrées, doivent produire le même comportement observable.
ProtoScript V2 est conçu pour que le développeur puisse avoir confiance dans ce qu’il écrit.
Cela implique :
- pas de "magie" runtime
- pas de comportement implicite
- pas de règles contextuelles cachées
Le langage doit se comporter comme il est écrit.
Les non-objectifs et principes de ProtoScript V2 servent un but unique :
permettre l’écriture de logiciels lisibles, fiables et performants, sans dette conceptuelle.
Ils constituent le garde-fou qui empêche le langage de dériver vers des compromis incohérents.
Cette section situe ProtoScript V2 dans le paysage des langages de programmation et conclut la spécification par une synthèse de ses choix fondamentaux.
ProtoScript V2 n’est pas un langage généraliste permissif. C’est un langage de construction, conçu pour durer, être compris et être maîtrisé.
ProtoScript V2 se positionne à l’intersection de plusieurs traditions :
- la sobriété du C, pour la maîtrise des coûts et des représentations
- un modèle prototype-based, pour l’expressivité objet sans classes
- la lisibilité syntaxique de JavaScript, débarrassée de ses ambiguïtés
- la rigueur des langages compilés, sans dépendance à un runtime spéculatif
Il ne cherche pas à concurrencer les langages dynamiques sur la rapidité d’écriture, ni les langages ultra-abstraits sur la métaprogrammation.
Il vise un autre objectif : permettre d’écrire du code clair, fiable et performant, sans dette conceptuelle.
ProtoScript V2 s’adresse principalement :
- aux développeurs souhaitant comprendre réellement ce que fait leur code
- aux ingénieurs sensibles aux coûts mémoire et algorithmiques
- aux projets nécessitant des performances prévisibles
- aux systèmes durables, maintenables sur le long terme
Il n’est pas conçu pour :
- le prototypage rapide jetable
- l’expérimentation syntaxique
- les environnements fortement dynamiques ou réflexifs
ProtoScript V2 assume explicitement :
- l’absence de classes
- l’absence de typage dynamique
- l’absence de conversions implicites
- l’absence de garbage collector obligatoire
- l’absence de surcharge syntaxique trompeuse
Ces choix ne sont pas des limitations accidentelles, mais des décisions structurantes.
ProtoScript V2 pose un socle volontairement strict.
Toute évolution future du langage devra :
- préserver les invariants existants
- respecter les garanties de complexité
- maintenir la lisibilité humaine
- ne jamais introduire de magie runtime
Les extensions possibles (nouveaux modules, backends, optimisations) devront s’inscrire dans ce cadre.
ProtoScript V2 est fondé sur une conviction simple :
un langage de programmation doit être lisible pour l’humain et honnête pour la machine.
En rendant explicites les types, les coûts, les allocations et les erreurs, ProtoScript V2 vise à restaurer une relation de confiance entre le développeur et son outil.
Il ne promet pas la facilité immédiate, mais la maîtrise à long terme.
Les sections 13 à 18 et les annexes A à C sont normatives.
Interprétation des termes normatifs :
- doit / MUST : exigence obligatoire
- ne doit pas / MUST NOT : interdiction absolue
- devrait / SHOULD : recommandation forte, dérogation motivée
- peut / MAY : option autorisée
En cas de conflit d’interprétation :
- les sections normatives prévalent sur les sections descriptives
- les annexes A, B, C prévalent sur les exemples informatifs
Cette section définit la grammaire normative de ProtoScript V2.
- une implémentation conforme doit accepter tout programme valide selon l’annexe B
- une implémentation conforme doit rejeter tout programme invalide selon l’annexe B
- aucun parseur ne doit dépendre d’une interprétation implicite non décrite ici
- Annexe A : grammaire lexicale complète
- Annexe B : grammaire syntaxique EBNF complète
- Annexe C : précédence, associativité, ordre d’évaluation
Les règles suivantes sont obligatoires :
- le
elseest rattaché auifnon fermé le plus proche - une déclaration variadique est reconnue uniquement sous la forme
list<T> ident... - un paramètre variadique représente une séquence d’arguments (possiblement vide)
for (... of ...)etfor (... in ...)sont distincts defor (init; cond; step):dans une signature de fonction introduit exclusivement le type de retour- les littéraux
maputilisent{ key : value }et ne sont jamais des blocs
- la précédence et l’associativité sont définies exclusivement par l’annexe C
- l’ordre d’évaluation des sous-expressions est strictement de gauche à droite
&&et||doivent court-circuiter- les effets de bord doivent être observables dans l’ordre d’évaluation défini
- l’affectation est une instruction sans valeur ;
a = b = cest grammaticalement invalide
Un programme est grammaticalement valide si et seulement si :
- son flux de tokens est valide selon l’annexe A
- il dérive de la règle
Programde l’annexe B - il respecte les règles de désambiguïsation de la section 13.2
Cette section définit les comportements obligatoires sur cas limites.
- aucun comportement ne peut être laissé dépendant de l’implémentation
- tout cas non statiquement rejeté doit avoir un comportement runtime défini
- une violation détectable à la compilation doit produire une erreur statique
- une violation non détectable statiquement doit lever une exception runtime
- le mode release ne doit pas modifier le résultat observable d’un programme valide
Principe général (normatif) :
- toute lecture par accès indexé doit être stricte (la donnée ciblée doit exister)
- toute écriture par accès indexé doit suivre la règle du type ciblé sans effet implicite non spécifié
- pour
map<K,V>, l’écriture est constructive (insertion si absente, mise à jour si présente)
| Cas | Statut | Comportement normatif | Diagnostic minimal |
|---|---|---|---|
overflow int (add/sub/mul, négation de la valeur minimale) |
exception runtime | l’opération doit lever une exception avant publication d’un résultat invalide | catégorie RUNTIME_INT_OVERFLOW, file:line:column |
overflow byte (hors [0,255]) |
erreur statique si constant, sinon exception runtime | assignation/conversion doit échouer | catégorie STATIC_BYTE_RANGE ou RUNTIME_BYTE_RANGE, position |
division entière a / b avec b == 0 |
exception runtime | l’évaluation doit lever une exception | catégorie RUNTIME_DIVIDE_BY_ZERO, position |
reste entier a % b avec b == 0 |
exception runtime | l’évaluation doit lever une exception | catégorie RUNTIME_DIVIDE_BY_ZERO, position |
| division flottante par zéro | autorisé | comportement IEEE-754 (+Inf, -Inf, NaN) |
pas d’exception |
décalage <</>> avec décalage négatif ou >= largeur(type) |
exception runtime | l’évaluation doit lever une exception | catégorie RUNTIME_SHIFT_RANGE, position |
index hors bornes list, string, slice, view |
exception runtime | accès lecture/écriture doit lever une exception | catégorie RUNTIME_INDEX_OOB, position |
écriture string[i] = v |
erreur statique | toute mutation indexée de string doit être rejetée |
catégorie IMMUTABLE_INDEX_WRITE, position |
écriture view[i] = v |
erreur statique | toute mutation indexée de view<T> doit être rejetée |
catégorie IMMUTABLE_INDEX_WRITE, position |
list.pop() sur liste vide |
erreur statique si prouvée vide, sinon exception runtime | l’appel doit être rejeté statiquement quand prouvable ; sinon lever une exception runtime | catégorie STATIC_EMPTY_POP ou RUNTIME_EMPTY_POP, position |
conversion UTF-8 invalide (list<byte>.toUtf8String()) |
exception runtime | la conversion doit échouer si l’octetage n’est pas UTF-8 valide | catégorie RUNTIME_INVALID_UTF8, position |
sous-chaîne hors bornes (string.subString) |
exception runtime | l’extraction doit échouer si start ou length est invalide |
catégorie RUNTIME_INDEX_OOB, position |
argument invalide (string.replaceAll("", ...), string.repeat(count<0), string.padStart/padEnd(..., "") si padding requis) |
exception runtime | l’appel doit échouer sur argument interdit | catégorie RUNTIME_INVALID_ARGUMENT, position |
accès map[k] avec clé absente |
exception runtime | l’accès doit lever une exception | catégorie RUNTIME_MISSING_KEY, position |
écriture map[k] = v avec clé absente |
autorisé | l’opération doit insérer une entrée (k, v) |
pas d’exception |
mutation structurelle pendant itération (list/map) |
exception runtime | l’itération doit détecter la mutation et lever une exception au plus tard au prochain pas | catégorie RUNTIME_CONCURRENT_MUTATION, position |
opération arithmétique/uniaire sur glyph |
exception runtime | l’opération doit lever une exception | catégorie RUNTIME_TYPE_ERROR, position |
Règles obligatoires :
- le mode debug peut ajouter des vérifications supplémentaires
- le mode release peut supprimer certaines vérifications redondantes prouvées
- debug et release doivent préserver la même sémantique observable
- seule la qualité des diagnostics peut varier (message plus ou moins détaillé)
Cette section définit l’analyse statique normative.
Règles :
- la portée est lexicale et hiérarchique par blocs
- la résolution d’un identifiant local suit l’ordre : bloc courant, blocs englobants, paramètres, champs via
self, symboles globaux - un nom doit être déclaré avant usage dans son bloc
- les types et prototypes doivent être résolus sans ambiguïté à la compilation
Règles :
- un identifiant local peut masquer un identifiant d’un bloc englobant
- un paramètre ne peut pas être redéclaré dans le même bloc
- deux déclarations homonymes dans le même bloc sont interdites
- un champ de prototype peut être masqué localement, mais l’accès au champ doit alors être explicite via
self.<field>
Règle normative complémentaire :
- À l’intérieur d’un même bloc, un identificateur ne peut faire l’objet que d’une seule déclaration.
- Une violation DOIT produire un diagnostic statique
E3131 REDECLARATION. - Cette règle évite les ambiguïtés d’initialisation (ex: auto-référence lors d’une redéclaration) et garantit une sémantique identique entre backends JS et C.
Exemple INVALID :
import Io;
function main(): void {
string t = Io.tempPath();
TextFile t = Io.openText(t, "w"); // E3131 REDECLARATION
}Exemple VALID (shadowing dans un bloc enfant) :
import Io;
function main(): void {
string t = Io.tempPath();
{
TextFile t = Io.openText(t, "w");
}
}Principe général :
Dans ProtoScript2, toute variable, champ ou valeur allouée est implicitement initialisée au moment de sa création.
Il n’existe pas d’état non initialisé observable dans le langage.
Par conséquent :
- toute lecture d’une variable est toujours définie ;
- aucune vérification d’assignation préalable n’est requise ;
- l’utilisation d’une variable avant affectation explicite n’est pas une erreur statique.
Ce choix garantit une sémantique simple, déterministe et performante, et facilite l’implémentation des backends, notamment l’émission de code C.
Valeurs par défaut par type :
bool→falsebyte→0int→0float→0.0glyph→U+0000(glyph nul)string→""(chaîne vide)
Ces valeurs s’appliquent uniformément :
- aux variables locales,
- aux champs de prototypes,
- aux objets clonés,
- aux valeurs retournées par allocation implicite.
Priorité d’initialisation des champs de prototype :
- si un champ possède un initialiseur explicite (section 4.3.2), cet initialiseur prévaut
- sinon, la valeur par défaut de cette section s’applique
Règles :
- aucun typage implicite n’est autorisé hors règles explicitement définies
- les opérations binaires exigent des opérandes de types compatibles selon la section 5.11
- les collections sont invariantes sur leurs paramètres (
list<T>,map<K,V>,slice<T>,view<T>) - la substitution parent/enfant est autorisée pour les valeurs de prototypes selon les règles de la section 4
- toute conversion doit être explicite via une opération/méthode définie par la spec
- toute écriture indexée sur un type immuable (
string,view<T>) est une erreur statique - les littéraux vides
[]et{}exigent un typage contextuel explicite
Règles :
- un
caseoudefaultsans terminaison explicite (break,return,throwou instruction équivalente quittant leswitch) est invalide - un paramètre variadique déclaré
list<T> ident...peut être lié à une séquence vide - une affectation ne peut pas être utilisée comme valeur d’expression
- l’affectation chaînée est invalide
Un paramètre variadique est une forme d’argumentation répétée, capturée en view<T> et pouvant être vide.
Une implémentation conforme doit classifier ses diagnostics statiques avec des codes stables.
Familles minimales :
E1xxx: erreurs lexicales/syntaxiques et arité invalideE2xxx: erreurs de nommage/résolutionE3xxx: erreurs de typage/compatibilitéE4xxx: erreurs sémantiques statiquesE5xxx: erreurs modules/importsE6xxx: réservée pour vérifications statiques runtime futures
Codes canoniques minimaux :
E1001:PARSE_UNEXPECTED_TOKENE1002:PARSE_UNCLOSED_BLOCKE1003:ARITY_MISMATCHE2001:UNRESOLVED_NAMEE2002:IMPORT_PATH_NOT_FOUNDE2003:IMPORT_PATH_BAD_EXTENSIONE2004:IMPORT_PATH_NO_ROOT_PROTOE3001:TYPE_MISMATCH_ASSIGNMENTE3003:SWITCH_CASE_NO_TERMINATIONE3004:IMMUTABLE_INDEX_WRITEE3005:STATIC_EMPTY_POPE3007:INVALID_RETURNE3120:GROUP_NON_SCALAR_TYPEE3121:GROUP_TYPE_MISMATCHE3122:GROUP_MUTATIONE3130:CONST_REASSIGNMENTE3140:SEALED_INHERITANCEE3150:INVALID_FIELD_INITIALIZERE3151:CONST_FIELD_MISSING_INITIALIZERE3200:INVALID_VISIBILITY_LOCATIONE3201:VISIBILITY_VIOLATION
Exigences minimales de diagnostic :
- code canonique (ex.
E3007) - nom canonique (
ErrorName) - position
file:line:column - message descriptif
- format strict conforme à 10.2
Les modifications introduites par la présente spécification doivent respecter les contraintes suivantes :
- Aucun comportement existant valide ne doit devenir invalide sauf si explicitement lié à l’introduction de
group,constousealed. - Aucune règle implicite supplémentaire ne doit être ajoutée au système de types.
- Aucun mécanisme d’inférence automatique ne doit être introduit.
prototypeconserve intégralement sa sémantique actuelle.sealedne modifie pas les règles d’instanciation.constn’introduit pas d’immuabilité profonde.groupn’introduit aucun nouveau type nominal.- L’IR existant pour les expressions constantes ne doit pas être modifié sauf pour la substitution explicite des accès
G.Member.
Le modèle mémoire décrit ici est abstrait et observable.
- la spec ne fixe pas d’ABI
- la spec fixe les garanties comportementales visibles par le programmeur
Représentations conceptuelles minimales :
list<T>:(ptr, len, cap)slice<T>/view<T>:(ptr, len)map<K,V>: table associative avec ordre d’insertion stable- objet de prototype : bloc mémoire à layout figé, offsets connus statiquement
Règles :
list<T>etmap<K,V>sont possédantsslice<T>etview<T>sont non possédants- toute vue doit référencer un stockage vivant pendant toute sa durée d’utilisation
- une vue ne doit pas survivre au stockage source qu’elle référence
Règles :
- toute réallocation de
list<T>invalide les vues/pointeurs dérivés sur son buffer - toute mutation structurelle de
map<K,V>peut invalider les vues/états d’itération dérivés - l’usage d’une vue invalidée est interdit : erreur statique si prouvable, sinon exception runtime
Règles :
- l’aliasing est autorisé tant qu’il ne viole pas les règles de validité mémoire
- l’implémentation doit préserver l’ordre observable des lectures/écritures défini par le langage
- aucune optimisation backend ne peut supposer l’absence d’aliasing non prouvée
Règles :
- debug peut instrumenter des contrôles supplémentaires (bornes, validité de vues, mutation durant itération)
- release peut retirer des contrôles démontrés redondants
- aucune différence de résultat observable n’est autorisée
Cette section définit le contrat normatif entre source, frontend IR et backend.
L’IR doit définir au minimum les entités :
ModuleFunctionBlockInstructionType
Règles :
- chaque valeur IR doit avoir un type statique unique
- aucune conversion implicite n’est autorisée
- toute conversion explicite doit être représentée par une instruction IR dédiée
Une unité IR valide doit respecter :
- CFG valide (blocs terminés par une terminaison explicite)
- définition avant usage sur tous les chemins
- appels résolus statiquement (cible connue à la compilation)
- accès mémoire typés (lecture/écriture cohérentes avec le type adressé)
- absence d’instruction sémantiquement non définie par la spec
Le frontend qui produit l’IR doit :
- rejeter tout programme source invalide selon sections 13 à 16
- matérialiser explicitement les contrôles runtime exigés par la spec
- préserver la correspondance source -> IR pour diagnostics (
file:line:column) - ne générer aucun comportement hors spec (pas d’UB caché)
Un backend conforme peut supposer :
- IR bien typé et conforme aux invariants 17.3
- sémantique explicite de chaque instruction
- absence de dépendance à un RTTI utilisateur
Un backend conforme doit :
- préserver strictement la sémantique observable
- ne pas supprimer un contrôle runtime normatif non prouvé redondant
Règles :
- chaque construction source doit avoir une traduction IR définie
- chaque erreur runtime normative doit correspondre à un point IR identifiable
- la chaîne source -> IR -> backend doit conserver les garanties des sections 14, 15 et 16
Un group ne doit générer :
- aucune entité
Type - aucune structure mémoire instanciable
Le nom du group doit toutefois être résolu comme descripteur nominal (valeur runtime) dont la seule vocation est la description et le diagnostic (ex. Debug.dump(Color)). Ce descripteur est partagé et rattaché à l’IR (propriété du module), sans copie par clone ni par frame.
Pour chaque membre :
T group G { A = Expr }
le frontend doit :
- vérifier que
Exprest une expression constante valide - évaluer
Exprà la compilation si requis par les règles existantes - enregistrer
G.Acomme constante typée dans la table des symboles
Toute occurrence de :
G.A
doit être remplacée dans l’IR par la valeur constante correspondante.
Il ne doit exister aucune instruction IR de type “GroupAccess”.
Toute occurrence de :
G
doit être abaissée en IR comme constante de type group (descripteur nominal), sans introduction d’un type nominal ni d’un objet instanciable.
Les membres d’un group ne peuvent pas dépendre d’effets de bord.
Toute expression non constante ou dépendant d’un état runtime doit déclencher une erreur statique.
La conformité d’une implémentation est définie par cette section et ses annexes normatives.
La suite de conformité doit contenir au minimum :
valid/: programmes valides avec sortie/résultat attenduinvalid/parse: programmes invalides lexicalement/syntaxiquementinvalid/type: programmes invalides statiquementinvalid/runtime: programmes valides qui déclenchent une exception normativeedge/: cas limites obligatoiresinvalid/type/switch-no-termination:case/defaultsans terminaison explicite
Chaque test doit définir :
- le fichier source
- le statut attendu (
accept,reject-static,reject-runtime) - la sortie attendue ou l’exception attendue
- les exigences minimales de diagnostic :
- catégorie d’erreur
- position
file:line:column - code d’erreur canonique
Règles :
- une implémentation conforme doit réussir 100 % des tests normatifs
- un échec sur un test normatif rend l’implémentation non conforme
- des tests supplémentaires non normatifs peuvent exister, mais ne remplacent pas les tests normatifs
Règles :
- les codes canoniques normatifs (familles
E*etR*) doivent être stables entre versions mineures de la spec - le texte du message peut évoluer, pas la signification du code
- un code existant ne doit jamais être réassigné à une autre signification
- les suggestions (
Did you mean ... ?) n’altèrent pas le code ni la catégorie du diagnostic principal
Règles normatives :
- le point d’entrée d’un programme exécutable est la fonction
main - seules les signatures suivantes sont autorisées :
function main() : voidfunction main() : intfunction main(list<string> args) : voidfunction main(list<string> args) : int
- toute autre signature de
mainest une erreur statique - si
mainretourne unint, cette valeur devient le code de sortie du processus - si
mainretournevoid, le code de sortie est déterminé par l’état d’exécution :0si l’exécution se termine sans erreur2pour une erreur utilisateur (syntaxique, statique, ou runtime)1pour une erreur interne (assertion, panne interne, OOM)
- si
mainacceptelist<string> args, il reçoit l’argumentation brute du système (argv), sans filtrage :- la liste contient le binaire (
argv[0]) et tous les arguments suivants - aucun argument n’est consommé par l’outil avant transmission au langage
- la liste contient le binaire (
Cette spécification définit ProtoScript Language Specification v2.0.
Règles normatives :
- la version publiée de cette spécification constitue la référence normative unique pour ProtoScript V2
- la suite de conformité normative (section 18) fait partie intégrante du contrat de conformité
- aucun changement sémantique n’est autorisé sans incrément de version majeure de la spécification
- les modifications éditoriales non sémantiques peuvent être publiées sans changement de version majeure
Déclaration de statut :
ProtoScript V2 est un langage spécifié au sens de cette spécification.
Cette section est normative.
Un module étend l’environnement de compilation (symboles disponibles) et ne modifie jamais la sémantique du langage.
- un module doit exporter uniquement des symboles statiquement typés (fonctions, constantes)
- un module ne doit pas définir de nouveaux mots-clés, opérateurs, règles de typage ou comportements runtime hors spec
- la résolution des modules et symboles est exclusivement statique (compilation)
- un programme doit être rejeté si un import ne peut pas être résolu de manière unique
- tout symbole importé doit avoir une signature complète connue à la compilation
- le linking des modules natifs est statique
- aucun chargement dynamique, aucune RTTI, aucune réflexion, aucune extension syntaxique
ImportStmt = "import" ImportTarget ";" ;
ImportTarget =
ImportByName
| ImportByPath ;
ImportByName =
ModulePath [ "as" Identifier ]
| ModulePath "." "{" ImportItem { "," ImportItem } "}" ;
ImportByPath =
StringLiteral [ "as" Identifier ]
| StringLiteral "." "{" ImportItem { "," ImportItem } "}" ;
ModulePath = Identifier { "." Identifier } ;
ImportItem = Identifier [ "as" Identifier ] ;Règles de désambiguïsation :
- si le token suivant
importest unStringLiteral, alors c’est unImportByPath; - sinon, c’est un
ImportByName.
Un ImportByPath référence un fichier .pts explicite.
Exemples :
import "./datastruct/Stack.pts";
import "/abs/path/collections/Stack.pts".{push, pop};Règles :
- le chemin doit se terminer par
.pts - chemin relatif : résolu par rapport au fichier courant (celui qui effectue l’import)
- chemin absolu : utilisé tel quel
- aucun parcours de
search_pathsn’est effectué pour un import par chemin - le fichier doit contenir exactement un prototype racine public
Exemples :
import std.io as io;
import math.core.{abs, clamp as clip};import A.B as X;introduit un espace de noms localX; l’accès se fait viaX.symbolimport A.B.{s1, s2 as y};introduit uniquement les symboles listés dans la portée locale- les imports wildcard (
*) sont interdits - deux imports produisant le même nom local sans alias explicite sont une erreur statique
- les symboles importés suivent les règles normales de portée et shadowing de la section 15
- le compilateur résout
(module, symbole)contre un registre de modules fourni au build - toute ambiguïté, absence de symbole ou incompatibilité de type est une erreur statique
- aucun fallback runtime n’est autorisé
- les appels importés restent des appels statiques (
call_staticou équivalent IR)
- le code C généré par
emit-csuppose la disponibilité du runtime C et des modules natifs requis - un import de module (
Io,Math,JSON, ou tout module du registre) doit échouer à l’édition de liens ou au chargement si le module n’est pas fourni - aucun fallback implicite n’est autorisé : le build doit fournir explicitement le registre et les modules natifs
Les imports par chemin (import "...") doivent produire des diagnostics statiques dédiés :
E2002:IMPORT_PATH_NOT_FOUNDE2003:IMPORT_PATH_BAD_EXTENSIONE2004:IMPORT_PATH_NO_ROOT_PROTO
typedef enum {
PS_T_INT, PS_T_FLOAT, PS_T_BOOL, PS_T_BYTE, PS_T_GLYPH, PS_T_STRING, PS_T_VOID
} ps_type_tag;
typedef struct {
const char* name;
ps_type_tag ret_type;
size_t param_count;
const ps_type_tag* param_types;
const char* c_symbol; // symbole C lié statiquement
} ps_native_fn_sig;
typedef struct {
const char* module_name; // ex: "std.io"
size_t fn_count;
const ps_native_fn_sig* fns;
} ps_native_module_desc;
// API du compilateur/backend (phase compilation)
int ps_register_native_module(const ps_native_module_desc* module);Règles :
ps_register_native_moduleest appelé avant compilation- le backend doit émettre des appels vers
c_symbolconnus au linking statique - aucune découverte dynamique de symboles n’est autorisée
Le module Io fait partie de l’environnement standard et doit être résolu statiquement via le registre de modules.
Symboles requis (minimum) :
Io.openText(path, mode): ouvre un fichier texte et retourne unTextFile.Io.openBinary(path, mode): ouvre un fichier binaire et retourne unBinaryFile.Io.print(value): écritvaluesur la sortie standard, sans ajout implicite de fin de ligne.Io.printLine(value): équivalent àIo.print(value)suivi de l’écriture deIo.EOL.Io.EOL: constante de fin de ligne ("\n").Io.stdin,Io.stdout,Io.stderr: flux standards (typesTextFile).
Prototypes scellés :
TextFileetBinaryFilesont des prototypes scellés fournis par le moduleIo.- Méthodes (noms identiques, types distincts) :
read(size),write(value),tell(),seek(pos),size(),name(),close().
Aucune conversion implicite texte ↔ binaire n’est autorisée. Le comportement complet du module Io est normatif et défini dans docs/module_io_specification.md.
Le module Math fait partie de l’environnement standard et doit être résolu statiquement via le registre de modules.
Symboles requis (minimum) :
- constantes :
PI,E,LN2,LN10,LOG2E,LOG10E,SQRT1_2,SQRT2 - fonctions :
abs,min,max,floor,ceil,round,trunc,sign,fround,sqrt,cbrt,pow,sin,cos,tan,asin,acos,atan,atan2,sinh,cosh,tanh,asinh,acosh,atanh,exp,expm1,log,log1p,log2,log10,hypot,clz32,imul,random
Règles :
- tous les paramètres et retours sont de type
float - la promotion implicite
int → floatest autorisée à l’appel - les fonctions suivent la sémantique IEEE‑754 (NaN, ±Infinity, −0)
- aucune exception n’est levée pour des valeurs hors domaine : le résultat est
NaNou±Infinity Math.random()retourne unfloatuniforme dans 0.0 ≤ x < 1.0, ne prend aucun argument, avance un PRNG interne, sans allocation ni dépendance système
Le comportement complet du module Math est normatif et défini dans docs/module_math_specification.md.
Encadré de cohérence :
- les modules étendent l’environnement de noms, pas le langage
- ProtoScript V2 conserve un modèle déterministe : tout est résolu, typé et vérifié à la compilation
Le module JSON fait partie de l’environnement standard et doit être résolu statiquement via le registre de modules.
JSONValue est un prototype standard scellé représentant un type somme JSON fermé.
Il ne peut pas être étendu par l’utilisateur.
Sous‑types normatifs :
JsonNullJsonBoolJsonNumberJsonStringJsonArrayJsonObject
Méthodes d’accès (sur JSONValue) :
isNull(),isBool(),isNumber(),isString(),isArray(),isObject()→boolasBool()→boolasNumber()→floatasString()→stringasArray()→list<JSONValue>asObject()→map<string, JSONValue>
asX() déclenche une erreur runtime si le type ne correspond pas.
Le module fournit des constructeurs explicites pour produire des valeurs JSON :
JSON.null()→JSONValueJSON.bool(bool b)→JSONValueJSON.number(float x)→JSONValue(promotion impliciteint → floatautorisée)JSON.string(string s)→JSONValueJSON.array(list<JSONValue> items)→JSONValueJSON.object(map<string, JSONValue> members)→JSONValue
Règle snapshot normative :
JSON.arrayetJSON.objectcapturent un snapshot de leurs entrées.JSONValue.asArray()etJSONValue.asObject()ne doivent pas invalider la stabilité observée duJSONValue.
JSON.encode(value) -> stringJSON.decode(string text) -> JSONValueJSON.isValid(string text) -> bool
Règles :
JSON.encodeaccepte :- un
JSONValue, ou - toute valeur récursivement sérialisable :
bool,int,float,string,list<T>,map<string,T>(avecTsérialisable).
- un
NaN,+Infinity,-Infinitysont interdits à l’encode : erreur runtime.-0est préservé lorsqu’il est sérialisé.JSON.decodeparse du JSON strict UTF‑8 et retourne un arbreJSONValue; en cas d’échec, erreur runtime.JSON.isValidretournetrue/falsesans exception si le texte n’est pas un JSON valide (erreur runtime uniquement si argument non‑string).
Le comportement complet du module JSON est normatif et défini dans docs/module_json_specification.md.
- le texte source doit être en UTF-8 valide
- les fins de ligne
LFetCRLFsont acceptées - un caractère invalide UTF-8 est une erreur lexicale
- espaces, tabulations et fins de ligne sont des séparateurs
- commentaire ligne :
// ...jusqu’à la fin de ligne - commentaire bloc :
/* ... */non imbriqué
Catégories :
- identifiants
- mots-clés réservés
- littéraux
- opérateurs
- ponctuation
prototype, sealed, function, var, const, internal, group, int, float, bool, byte, glyph, string, list, map, slice, view, void, if, else, for, of, in, while, do, switch, case, default, break, continue, return, try, catch, finally, throw, true, false, self, super
- entier décimal, hexadécimal (
0x), binaire (0b), octal (0préfixé) - flottant décimal/scientifique
- chaîne UTF-8 entre guillemets doubles
- échappements reconnus dans un littéral de chaîne :
\"→"\\→\\n→ U+000A\t→ U+0009\r→ U+000D\b→ U+0008\f→ U+000C\uXXXX→ point de code Unicode (4 hexadécimaux), encodé en UTF-8
- toute séquence d’échappement invalide est une erreur lexicale
- les littéraux entiers sont non signés lexicalement ; le signe
-est un opérateur unaire
- regex normative :
[A-Za-z_][A-Za-z0-9_]* - un mot-clé réservé ne peut pas être utilisé comme identifiant
Program = { TopDecl } ;
TopDecl = PrototypeDecl | FunctionDecl | GroupDecl | VarDecl ";" ;
PrototypeDecl = [ "sealed" ] "prototype" Identifier [ ":" TypeName ] "{" { ProtoMember } "}" ;
ProtoMember = FieldDecl | MethodDecl ;
Visibility = "internal" ;
FieldDecl = [ Visibility ] [ "const" ] Type Identifier [ "=" Expr ] ";" ;
MethodDecl = [ Visibility ] FunctionDecl ;
GroupDecl = ScalarType "group" Identifier "{" GroupMemberList "}" ;
FunctionDecl = "function" Identifier "(" [ ParamList ] ")" ":" Type Block ;
ParamList = Param { "," Param } ;
Param = Type Identifier | "list" "<" Type ">" Identifier "..." ;
VarDecl = "var" Identifier "=" Expr
| Type Identifier [ "=" Expr ]
| ConstDecl ;
ConstDecl = "const" ScalarType Identifier [ "=" Expr ] ;
Type = PrimitiveType
| TypeName
| "list" "<" Type ">"
| "map" "<" Type "," Type ">"
| "slice" "<" Type ">"
| "view" "<" Type ">" ;
ScalarType = "bool" | "byte" | "glyph" | "int" | "float" | "string" ;
PrimitiveType = ScalarType | "void" ;
TypeName = Identifier ;
Block = "{" { Stmt } "}" ;
Stmt = Block
| IfStmt
| ForStmt
| WhileStmt
| DoWhileStmt
| SwitchStmt
| TryStmt
| ReturnStmt
| BreakStmt
| ContinueStmt
| ThrowStmt
| VarDecl ";"
| AssignStmt
| ExprStmt ;
IfStmt = "if" "(" Expr ")" Stmt [ "else" Stmt ] ;
ForStmt = "for" "(" ( ForClassic | ForOf | ForIn ) ")" Stmt ;
ForClassic = [ ForInit ] ";" [ Expr ] ";" [ ForStep ] ;
ForInit = VarDecl | AssignNoValue | Expr ;
ForStep = AssignNoValue | Expr ;
ForOf = ( Type Identifier | "var" Identifier ) "of" Expr ;
ForIn = ( Type Identifier | "var" Identifier ) "in" Expr ;
WhileStmt = "while" "(" Expr ")" Stmt ;
DoWhileStmt = "do" Stmt "while" "(" Expr ")" ";" ;
SwitchStmt = "switch" "(" Expr ")" "{"
{ "case" Expr ":" { Stmt } }
[ "default" ":" { Stmt } ]
"}" ;
TryStmt = "try" Block { CatchClause } [ FinallyClause ] ;
CatchClause = "catch" "(" Type Identifier ")" Block ;
FinallyClause = "finally" Block ;
ReturnStmt = "return" [ Expr ] ";" ;
BreakStmt = "break" ";" ;
ContinueStmt = "continue" ";" ;
ThrowStmt = "throw" Expr ";" ;
AssignStmt = AssignNoValue ";" ;
ExprStmt = Expr ";" ;
Expr = ConditionalExpr ;
AssignNoValue = PostfixExpr AssignOp ConditionalExpr ;
AssignOp = "=" | "+=" | "-=" | "*=" | "/=" ;
ConditionalExpr = OrExpr [ "?" ConditionalExpr ":" ConditionalExpr ] ;
OrExpr = AndExpr { "||" AndExpr } ;
AndExpr = EqExpr { "&&" EqExpr } ;
EqExpr = RelExpr { ( "==" | "!=" ) RelExpr } ;
RelExpr = ShiftExpr { ( "<" | "<=" | ">" | ">=" ) ShiftExpr } ;
ShiftExpr = AddExpr { ( "<<" | ">>" ) AddExpr } ;
AddExpr = MulExpr { ( "+" | "-" | "|" | "^" ) MulExpr } ;
MulExpr = UnaryExpr { ( "*" | "/" | "%" | "&" ) UnaryExpr } ;
UnaryExpr = [ "!" | "~" | "-" | "++" | "--" ] PostfixExpr ;
PostfixExpr = PrimaryExpr { PostfixPart } ;
PostfixPart = "(" [ ArgList ] ")"
| "[" Expr "]"
| "." Identifier
| "++"
| "--" ;
ArgList = Expr { "," Expr } ;
PrimaryExpr = Literal
| Identifier
| "self"
| "super"
| "(" Expr ")"
| ListLiteral
| MapLiteral ;
ListLiteral = "[" [ Expr { "," Expr } ] "]" ;
MapLiteral = "{" [ MapPair { "," MapPair } ] "}" ;
MapPair = Expr ":" Expr ;
Literal = IntLiteral | FloatLiteral | StringLiteral | BoolLiteral ;
BoolLiteral = "true" | "false" ;
GroupMemberList = GroupMember { "," GroupMember } [ "," ] ;
GroupMember = Identifier "=" Expression ;
Expression = Expr ;| Niveau | Opérateurs | Associativité |
|---|---|---|
| 1 | postfix (), [], ., x++, x-- |
gauche |
| 2 | préfixe !, ~, -, ++x, --x |
droite |
| 3 | *, /, %, & |
gauche |
| 4 | +, -, ` |
, ^` |
| 5 | <<, >> |
gauche |
| 6 | <, <=, >, >= |
gauche |
| 7 | ==, != |
gauche |
| 8 | && |
gauche |
| 9 | ` | |
| 10 | ?: |
droite |
Règles obligatoires :
- les opérandes sont évalués de gauche à droite
- les arguments d’appel sont évalués de gauche à droite
- le receveur d’un accès membre/index est évalué avant le membre/index
- pour
cond ? a : b, la condition est évaluée avant la branche sélectionnée ; seule la branche sélectionnée est évaluée - pour
a op= b,aest évalué une seule fois &&et||court-circuitent
- les effets de bord doivent être visibles dans l’ordre défini en C.2
- une implémentation ne doit pas réordonner des effets de bord observables
Cette annexe est informative et ne modifie pas les exigences normatives.
Exemples recommandés :
- exemples de parsing ambigu levé par les règles de la section 13.2
- exemples de diagnostics
E*etR* - exemples de cas limites de la section 14
- exemples de correspondance source -> IR de la section 17
Cette spécification décrit le comportement normatif du langage.
L’annexe E ne liste que ce qui reste à faire (écarts à la spécification) et les éléments partiellement ou non implémentés selon les backends.
L’état détaillé est maintenu séparément dans le fichier docs/STATE_OF_CONFORMITY.md.