Eine unendlich tragbare Alternative zur C -Programmiersprache.

Für diejenigen unter Ihnen, die sich an "frei" erinnern, ist Oak im Wesentlichen eine robustere und hochrangigere Version dieses Projekts. Das Ziel von Eiche ist es, in der Frontend so hoch wie möglich zu sein, aber im Backend so klein und niedrig wie möglich.
Ich bin ein frisch geprägter Highschool -Absolvent und Studienanfänger im College, der Arbeit sucht. Wenn Sie meine Projekte genießen, überlegen Sie mich, mich zu unterstützen, indem Sie mir einen Kaffee kaufen!
Der Schlüssel zur verrückten Portabilität von Oak ist die unglaublich kompakte Backend -Implementierung. Der Code für Oaks Backend kann in weniger als 100 Zeilen von C ausgedrückt werden. Eine solche kleine Implementierung ist nur aufgrund des winzigen Anweisungssatzes der Zwischendarstellung möglich. Oaks IR besteht nur aus 17 verschiedenen Anweisungen . Das ist mit Brainfuck gleich!
Das Backend of Oak funktioniert sehr einfach. Jede Anweisung arbeitet auf einem Speicherband . Dieses Band ist im Wesentlichen ein statisches Array von Doppelprezisionsschwimmer.
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`Wenn eine Variable in einer Funktion definiert ist, erhält sie eine statische Position relativ zum aktuellen Basiszeiger der virtuellen Maschine. Wenn also eine Funktion aufgerufen wird, wird der Platz für die Variablen der Funktion auf dem Stapel zugewiesen, und der Basizeiger wird inkrementiert, um diesen neuen Speicherplatz zu verwenden. Anschließend ersetzt der Compiler die Variable nur durch seine Adresse, die dem Basiszeiger -Offset im Rest des Codes hinzugefügt wurde!
Zusätzlich fungiert das Speicherband als Stapel und Haufen . Nachdem der Platz für alle Variablen des Programms zugewiesen ist, beginnt der für den Stapel verwendete Speicher. Der Stapel wächst und schrumpft mit Daten im gesamten Programm: Wenn beispielsweise zwei Zahlen summiert werden, werden sie vom Stapel ausgestoßen und durch das Ergebnis ersetzt. In ähnlicher Weise wächst und schrumpft der Haufen während des gesamten Programms. Der Heap wird jedoch für dynamisch zugewiesene Daten verwendet: Informationen mit einem zur Kompilierungszeit unbekannten Speicher Fußabdruck.
Nachdem Sie verstehen, wie Oaks Backend im Grunde genommen funktioniert, ist hier der vollständige Anweisungssatz!
| Anweisung | Nebenwirkung |
|---|---|
push(n: f64); | Drücken Sie eine Nummer auf den Stapel. |
add(); | Stecken Sie zwei Zahlen aus dem Stapel und schieben Sie ihre Summe. |
subtract(); | Pop zwei Zahlen aus dem Stapel. Subtrahieren Sie den ersten vom zweiten und drücken Sie das Ergebnis. |
multiply(); | Stecken Sie zwei Zahlen aus dem Stapel und schieben Sie ihr Produkt. |
divide(); | Pop zwei Zahlen aus dem Stapel. Teilen Sie die zweite durch den ersten und drücken Sie das Ergebnis. |
sign(); | Streichen Sie eine Nummer aus dem Stapel. Wenn es größer oder gleich Null ist, drücken Sie 1 , ansonsten -1 . |
allocate(); | Stecken Sie eine Nummer aus dem Stapel und geben Sie einen Zeiger auf diese Anzahl freier Zellen auf dem Haufen zurück. |
free(); | Stecken Sie eine Nummer aus dem Stapel und gehen Sie zu dieser Zahl im Speicher. Stecken Sie eine weitere Anzahl aus dem Stapel und befreien Sie, dass viele Zellen an diesem Ort im Gedächtnis. |
store(size: i32); | Stecken Sie eine Nummer aus dem Stapel und gehen Sie zu dieser Zahl im Speicher. Dann size Zahlen aus dem Stapel. Speichern Sie diese Zahlen in umgekehrter Reihenfolge an diesem Ort im Speicher. |
load(size: i32); | Stecken Sie eine Nummer aus dem Stapel und gehen Sie zu dieser Zahl im Speicher. Drücken Sie dann size der aufeinanderfolgenden Gedächtniszellen auf den Stapel. |
call(fn: i32); | Rufen Sie eine benutzerdefinierte Funktion durch den zugewiesenen Compiler -ID auf. |
call_foreign_fn(name: String); | Rufen Sie eine Fremdfunktion mit ihrem Namen in der Quelle auf. |
begin_while(); | Beginnen Sie eine Weile Schleife. Streichen Sie für jede Iteration eine Zahl von dem Stapel. Wenn die Zahl nicht Null ist, setzen Sie die Schleife fort. |
end_while(); | Markieren Sie das Ende einer Weile. |
load_base_ptr(); | Laden Sie den Basizeiger des etablierten Stapelrahmens, der immer kleiner oder gleich dem Stapelzeiger ist. Variablen werden relativ zum Basizeiger für jede Funktion gespeichert. Eine Funktion, die x: num und y: num , x definiert, kann bei base_ptr + 1 gespeichert werden, und y kann bei base_ptr + 2 gespeichert werden. Auf diese Weise können Funktionen dynamisch und nach Bedarf Variablen im Speicher speichern, anstatt statische Speicherorte zu verwenden. |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | Streichen Sie die arg_size der Zellen aus dem Stapel ab und speichern Sie sie weg. Rufen Sie dann load_base_ptr auf, um den übergeordneten Stapelrahmen wieder aufzunehmen, wenn diese Funktion endet. Drücken Sie die Anzahl der Nullen local_scope_size auf den Stapel, um Platz für die Variablen der Funktion zu schaffen. Schieben Sie schließlich die gespeicherten Argumentationszellen zurück auf den Stapel, wie sie ursprünglich geordnet wurden. |
end_stack_frame(return_size: i32, local_scope_size: i32); | return_size Anzahl von Zellen aus dem Stapel aus und speichern Sie sie weg. Dann pop local_scope_size Anzahl der Zellen aus dem Stapel, um den Speicher des Stapelrahmens zu verwerfen. Stecken Sie einen Wert aus dem Stapel und speichern Sie ihn im Basiszeiger, um den übergeordneten Stapelrahmen wieder aufzunehmen. Drücken Sie schließlich die gespeicherten Rückkehrwertzellen wieder auf den Stapel, wie sie ursprünglich bestellt wurden. |
Mit diesen Anweisungen ist Oak in der Lage, Abstraktionen mit noch höherer Ebene zu implementieren, als C anbieten kann !!! Das klingt vielleicht nicht nach viel, aber es ist sehr mächtig für eine so kleine Sprache.
Die Syntax von Eichen ist stark von der Rost -Programmiersprache inspiriert.
Funktionen werden mit dem fn -Schlüsselwort deklariert und sind mit Ausnahme der return syntaktisch identisch mit Rostfunktionen. Zusätzlich werden benutzerdefinierte Typen und Konstanten mit den Schlüsselwörtern vom type bzw. const deklariert.
Ähnlich wie bei den äußeren Attributen von Rust führt Oak viele Kompilierzeitflags ein. Einige davon werden unten zusammen mit anderen Eichenfunktionen demonstriert.

Wie genau funktioniert der Eichen Compiler?
Strukturen in ihre Funktionen flachen
putnumln(*bday.day) wird putnumln(*Date::day(&bday)) . Dies ist ein ziemlich einfacher Prozess.Berechnen Sie die Größe des Typs jeder Operation
// `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 ) ;
}Berechnen Sie den Speicherausdruck des Programms statisch
Umwandeln Sie Eichenausdrücke und Aussagen in äquivalente IR -Anweisungen
Es gibt viele verschiedene Umstände, unter denen ein Methodenaufruf gültig ist. Methoden nehmen immer einen Zeiger auf die Struktur als Argument . Ein Objekt, das eine Methode aufruft, muss jedoch kein Zeiger sein . Zum Beispiel ist der folgende Code gültig: let bday: Date = Date::new(); bday.print(); . Der Variable bday ist kein Zeiger, aber die Methode .print() kann weiterhin verwendet werden. Hier ist der Grund.
Wenn der Compiler einen abgeflachten Methodenaufruf sieht, muss er einen Weg finden, um den "Instanzausdruck" in einen Zeiger umzuwandeln. Für Variablen ist dies einfach: Fügen Sie einfach eine Referenz hinzu! Zum Beispiel Ausdruck, die bereits Zeiger sind, ist es noch einfacher: Tun Sie nichts! Für jede andere Art von Ausdruck ist es jedoch etwas ausführlicher. Der Compiler schleicht sich in eine versteckte Variable, um den Ausdruck zu speichern, und kompiliert dann den Methodenaufruf erneut mit der Variablen als Instanzausdruck. Ziemlich cool, oder?
Montieren Sie die IR -Anweisungen für ein Ziel
Target . Wenn Sie jede der Anweisungen des IR für Ihre Sprache mithilfe des Target implementieren, kann Oak automatisch bis hin zu Ihrer neuen Programmier- oder Montagesprache kompilieren! Ja, es ist so einfach wie es klingt! Damit Benutzer die Dokumentation von Bibliotheken und Dateien ohne Zugriff auf das Internet lesen können, stellt OAK den doc -Unterbefehl bereit. Auf diese Weise können Autoren ihren Code Dokumentationsattribute hinzufügen, um anderen Benutzern zu helfen, ihren Code oder ihre API zu verstehen, ohne die Quelle durchsuchen und Kommentare lesen müssen.
Hier ist ein Beispielcode.
# [ 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 ( ) ;
} Und hier ist eine Beispielverwendung des doc -Unterbefehls zum Drucken der formatierten Dokumentation in das Terminal.

Um den aktuellen Entwicklungsbau zu erhalten, klonen Sie das Repository und installieren Sie es.
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . Um den aktuellen Release -Build zu erhalten, installieren Sie von crate.io.
# Also works for updating oakc
cargo install -f oakcDann können Eichendateien mit der Oakc -Binärdatei zusammengestellt werden.
oak c examples/hello_world.ok -c
main.exeC Backend - Jeder GCC -Compiler, der C99 unterstützt
Gehen Sie Backend - Golang 1.14 Compiler
TypeScript Backend - TypeScript 3.9 Compiler