Une alternative infiniment plus portable au langage de programmation C.

Pour ceux d'entre vous qui se souviennent de "gratuit", Oak est essentiellement une version plus robuste et de haut niveau de ce projet. L'objectif du chêne est d'être aussi élevé que possible dans le frontend, mais aussi petit et bas que possible dans le backend.
Je suis un diplômé du lycée et une étudiante de première année fraîchement frappées à la recherche de travail. Si vous aimez mes projets, envisagez de me soutenir en m'achète un café!
La clé de la portabilité folle d'Oak est son implémentation backend incroyablement compacte. Le code pour le backend d'Oak peut être exprimé en moins de 100 lignes de C. Une telle mise en œuvre est uniquement possible en raison du minuscule ensemble d'instructions de la représentation intermédiaire. L'IR d'Oak n'est composé que de 17 instructions différentes . C'est à égalité avec Brainfuck!
Le backend du chêne fonctionne très simplement. Chaque instruction fonctionne sur une bande de mémoire . Cette bande est essentiellement un tableau statique de flotteurs à double précision.
let x : num = 5.25 ; ... let p : & num = & x ; `beginning of heap`
| | |
v v v
[ 0 , 0 , 0 , 5.25 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , ... ]
^
|
`current location of the stack pointer`Lorsqu'une variable est définie dans une fonction, elle reçoit une position statique par rapport au pointeur de base actuel de la machine virtuelle. Ainsi, lorsqu'une fonction est appelée, l'espace pour les variables de la fonction est alloué sur la pile et le pointeur de base est incrémenté pour utiliser ce nouvel espace. Ensuite, le compilateur remplace simplement la variable par son adresse ajoutée au décalage du pointeur de base dans le reste du code!
De plus, la bande de mémoire fonctionne comme une pile et un tas . Après l'espace pour toutes les variables du programme, la mémoire utilisée pour la pile commence. La pile se développe et se rétrécit avec les données tout au long du programme: lorsque deux nombres sont additionnés, par exemple, ils sont sortis de la pile et remplacés par le résultat. De même, le tas grandit et se rétrécit tout au long du programme. Le tas, cependant, est utilisé pour les données allouées dynamiquement : informations avec une empreinte mémoire inconnue au moment de la compilation .
Maintenant que vous comprenez comment fonctionne fondamentalement le backend d'Oak, voici l'ensemble d'instructions complet!
| Instruction | Effet secondaire |
|---|---|
push(n: f64); | Poussez un numéro sur la pile. |
add(); | Papez deux numéros de la pile et poussez leur somme. |
subtract(); | Papez deux numéros hors de la pile. Soustrayez le premier de la seconde et poussez le résultat. |
multiply(); | Papez deux numéros de la pile et poussez leur produit. |
divide(); | Papez deux numéros hors de la pile. Divisez le second par le premier et poussez le résultat. |
sign(); | Éclatez un nombre de la pile. S'il est supérieur ou égal à zéro, push 1 , sinon push -1 . |
allocate(); | Éteignez un nombre de la pile et renvoyez un pointeur sur ce nombre de cellules libres sur le tas. |
free(); | Éteignez un nombre de la pile et allez à l'endroit où ce nombre pointe en mémoire. Éteignez un autre numéro de la pile et libèrez de nombreuses cellules à cet endroit en mémoire. |
store(size: i32); | Éteignez un nombre de la pile et allez à l'endroit où ce nombre pointe en mémoire. Ensuite, les numéros size pop hors de la pile. Stockez ces numéros dans l'ordre inverse à cet endroit en mémoire. |
load(size: i32); | Éteignez un nombre de la pile et allez à l'endroit où ce nombre pointe en mémoire. Ensuite, poussez size du nombre de cellules de mémoire consécutives sur la pile. |
call(fn: i32); | Appelez une fonction définie par l'utilisateur par son ID attribué au compilateur. |
call_foreign_fn(name: String); | Appelez une fonction étrangère par son nom dans Source. |
begin_while(); | Commencez une boucle de temps. Pour chaque itération, éclatez un nombre de la pile. Si le nombre n'est pas zéro, continuez la boucle. |
end_while(); | Marquez la fin d'une boucle de temps. |
load_base_ptr(); | Chargez le pointeur de base du cadre de pile établi, qui est toujours inférieur ou égal au pointeur de pile. Les variables sont stockées par rapport au pointeur de base pour chaque fonction. Ainsi, une fonction qui définit x: num et y: num , x peut être stocké à base_ptr + 1 , et y peut être stocké sur base_ptr + 2 . Cela permet aux fonctions de stocker des variables dans la mémoire dynamiquement et au besoin, plutôt que d'utiliser des emplacements de mémoire statique. |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | Défendez arg_size Nombre de cellules hors de la pile et rangez-les. Ensuite, appelez load_base_ptr pour reprendre le cadre de pile parent lorsque cette fonction se termine. Poussez local_scope_size Nombre de zéros sur la pile pour faire de la place pour les variables de la fonction. Enfin, repoussez les cellules d'argument stockées sur la pile car elles étaient à l'origine ordonnées. |
end_stack_frame(return_size: i32, local_scope_size: i32); | Éclatez le nombre de cellules return_size hors de la pile et rangez-les. Ensuite, pop local_scope_size de cellules hors de la pile pour éliminer la mémoire de la trame de pile. Éteignez une valeur hors de la pile et stockez-la dans le pointeur de base pour reprendre le cadre de pile parent. Enfin, repoussez les cellules de valeur de retour stockées sur la pile car elles ont été initialement ordonnées. |
En utilisant uniquement ces instructions, Oak est en mesure de mettre en œuvre des abstractions de niveau encore plus élevées que C peut offrir !!! Cela peut ne pas sembler beaucoup, mais c'est très puissant pour une langue aussi petite.
La syntaxe du chêne est fortement inspirée par le langage de programmation de la rouille.
Les fonctions sont déclarées avec le mot-clé fn et sont syntaxiquement identiques aux fonctions de rouille, à l'exception de la sémantique return . De plus, les types et constantes définis par l'utilisateur sont déclarés respectivement avec les mots clés type et const .
Semblable aux attributs externes de Rust, Oak introduit de nombreux drapeaux de temps de compilation. Certains d'entre eux sont démontrés ci-dessous avec d'autres caractéristiques de chêne.

Alors, comment fonctionne exactement le compilateur Oak?
Aplatir les structures dans leurs fonctions
putnumln(*bday.day) devient putnumln(*Date::day(&bday)) . C'est un processus assez simple.Calculez la taille du type de chaque opération
// `3` is the size of the structure on the stack
fn Date :: new ( month : 1 , day : 1 , year : 1 ) -> 3 {
month ; day ; year
}
// self is a pointer to an item of size `3`
fn Date :: day ( self : & 3 ) -> & 1 { self + 1 }
fn main ( ) -> 0 {
let bday : 3 = Date :: new ( 5 , 14 , 2002 ) ;
}Calculez statiquement l'empreinte de la mémoire du programme
Convertir les expressions et les déclarations en chêne en instructions IR équivalentes
Il existe de nombreuses circonstances différentes où un appel de méthode est valide. Les méthodes prennent toujours un pointeur vers la structure comme argument . Cependant, un objet qui appelle une méthode n'est pas nécessaire pour être un pointeur . Par exemple, le code suivant est valide: let bday: Date = Date::new(); bday.print(); . Le bday variable n'est pas un pointeur, mais la méthode .print() peut toujours être utilisée. Voici pourquoi.
Lorsque le compilateur voit un appel de méthode aplati, il doit trouver un moyen de transformer «l'expression d'instance» en pointeur. Pour les variables, c'est facile: ajoutez simplement une référence! Par exemple, des expressions qui sont déjà des pointeurs, c'est encore plus facile: ne faites rien! Pour tout autre type d'expression, cependant, c'est un peu plus verbeux. Le compilateur se faufile dans une variable cachée pour stocker l'expression, puis compile à nouveau l'appel de méthode en utilisant la variable comme expression d'instance. Assez cool, non?
Assembler les instructions IR pour une cible
Target . Si vous implémentez chacune des instructions de l'IR pour votre langue en utilisant le trait Target , Oak peut automatiquement compiler jusqu'à votre nouveau langage de programmation ou d'assemblage! Oui, c'est aussi simple que cela puisse paraître! Pour permettre aux utilisateurs de lire la documentation des bibliothèques et des fichiers sans accès à Internet, Oak fournit la sous-commande doc . Cela permet aux auteurs d'ajouter des attributs de documentation à leur code pour aider d'autres utilisateurs à comprendre leur code ou leur API sans avoir à passer au crible la source et à lire les commentaires.
Voici quelques exemples de code.
# [ std ]
# [ header ( "This file tests Oak's doc subcommand." ) ]
# [ doc ( "This constant is a constant." ) ]
const CONSTANT = 3 ;
// No doc attribute
const TEST = CONSTANT + 5 ;
# [ doc ( "This structure represents a given date in time.
A Date object has three members:
|Member|Value|
|-|-|
|`month: num` | The month component of the date |
|`day: num` | The day component of the date |
|`year: num` | The year component of the date |" ) ]
struct Date {
let month : num , day : num , year : num ;
# [ doc ( "The constructor used to create a date." ) ]
fn new ( month : num , day : num , year : num ) -> Date {
return [ month , day , year ] ;
}
# [ doc ( "Print the date object to STDOUT" ) ]
fn print ( self : & Date ) {
putnum ( self ->month ) ; putchar ( '/' ) ;
putnum ( self ->day ) ; putchar ( '/' ) ;
putnumln ( self ->year ) ;
}
}
# [ doc ( "This function takes a number `n` and returns `n * n`, or `n` squared." ) ]
fn square ( n : num ) -> num {
return n * n
}
fn main ( ) {
let d = Date :: new ( 5 , 14 , 2002 ) ;
d . print ( ) ;
} Et voici l'exemple d'utilisation de la sous-commande doc pour imprimer la documentation formatée au terminal.

Pour obtenir la construction de développement actuelle, cloner le référentiel et l'installer.
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . Pour obtenir la construction de version actuelle, installez-vous à partir de Crates.io.
# Also works for updating oakc
cargo install -f oakcEnsuite, les fichiers OAK peuvent être compilés avec le binaire Oakc.
oak c examples/hello_world.ok -c
main.exeC backend - Tout compilateur GCC qui prend en charge C99
Go Backend - Compilateur Golang 1.14
Tapiscript Backend - Compilateur TypeScript 3.9