Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

61

22.06.2014, 18:02

Ich hab übrigens jetzt die Datentypen mit der union gelöst.
Strings funktionieren noch nicht, der Rest aber schon. Sieht dann jetzt so aus.

Theoretisch sollte ich jetzt recht einfach die Kontrollstrukturen reinkloppen können, aber ich weiß noch nicht wie ich am besten in Code Blöcke gehe oder nicht. Bei der If z.B. könnte man es so lösen dass direkt hinter dem If Befehl die Adresse kommt, sollte die Bedingung nicht erfüllt werden. Und dann ein jump durchgeführt wird (z.B. in den optionalen else Block).
Nur dazu müsste ich die Adresse hinter dem If Block wissen und außerdem die des else Blocks, falls diese übersprungen wird...

Ich hab mal angefangen in allen Kindklassen von Expr (früher Expression, war mir zu lang :P) die Adresse (in Byte) des aktuellen Befehlts zu zählen. Damit könnte man evt. dann dahin kommen. Aber wie ich das am besten mache weiß ich noch nicht.

Achja und das not (if(!bla)) kann ich auch noch nicht parsen. Ist nicht ganz so einfach, da es direkt vor dem Ausdruck steht und es auch noch != gibt.

Insgesamt kann man mit der Syntax aber schon kranke Sachen machen:

Quellcode

1
2
_x = 100/10*(9+9-5)%6-2+(5 < 6 && false)/true
print _x

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

62

22.06.2014, 19:40

Was die Bedingungen angeht, da liegst du schon richtig mit deiner Idee. Hinterher Codezeilen bzw Adressen mitzählen und dann mit Sprüngen arbeiten. Was den !-Operator angeht, du musst das nächste Zeichen betrachten. Ist das ein =, dann handelt es sich eben um ungleich, ansonsten um eine Verneinung. Ich denke etwas in der Art wirst du vermutlich auch an anderer Stelle brauchen. Dein Lexer wird ja auch = und == unterscheiden.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

63

01.07.2014, 09:59

So um das mal wieder zu updaten: if und while sind drin, die anderern Kontrollstrukturen kommen jetzt.
Der !-Operator funktioniert zwar, aber nicht wie man unbedingt erwarten würde... gefällt mir noch nicht. Er nimmt komischerweise alles was dann folgt, also z.B. if(!true && false) -> true.
Dann kann ich noch keine Codeblöcke parsen die über einen Befehl hinausgehen... das bekomme ich aber hin.

Außerdem habe ich spaßeshalber mal die Performance mit C++ verglichen. Bei 10 Mio. Iterationen durch eine while ist Snippet ca 332x langsamer als C++ :D
Optimierungen sind was für später. Jetzt kommen erstmal die restlichen Strukturen und dann Arrays.

Die Implementierung der jumps habe ich übrigens mit Hilfe des Dateizeigers gelöst, mitzählen war zu ungenau, bzw. ich hatte mich sicher irgendwo verzählt.
Hier mal die while:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void While::evaluate(std::ofstream &output){
    auto start = output.tellp(); // start of while

    // condition
    if(left){
        left->evaluate(output);
    }

    // while
    unsigned char cmd = Snippet::INSTRUCTIONS::COND;
    output.write(reinterpret_cast<char*>(&cmd), sizeof(unsigned char));

    // write jump address placeholder
    auto jmpaddr = output.tellp();
    unsigned int pos = 0;
    output.write(reinterpret_cast<char*>(&pos), sizeof(unsigned int));

    // code block
    if(right){
        right->evaluate(output);
    }

    // jump to start of while
    cmd = Snippet::INSTRUCTIONS::JMP;
    output.write(reinterpret_cast<char*>(&cmd), sizeof(unsigned char));
    output.write(reinterpret_cast<char*>(&start), sizeof(unsigned int));

    // write jump address (end of while block)
    pos = output.tellp();
    output.seekp(jmpaddr);
    output.write(reinterpret_cast<char*>(&pos), sizeof(unsigned int));
    output.seekp(pos);
}

Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

64

03.07.2014, 13:56

Bei mitzählen meinte ich nicht von Hand zählen. Das sollte dein Code natürlich für dich tun. Was ich auch schon gesehen habe ist zuerst mit Zeigern/Referenzen zu arbeiten und dann bei der Codegenerierung durch Zeilennummern zu ersetzen. Jeder Befehl kennt dann die Zeile in welcher er steht und so kannst du die Referenzen zu direkt Zeilennummern auflösen. Was den !-Operator angeht, liefert der wirklich immer true zurück? Auch bei if(! true ) ? Ansonsten vermute ich ist die Bindung einfach falsch und aus ( ! true && false ) wird für ihn ( ! ( true && false ) ) was dann tatsächlich true wäre. Da kann ich jetzt aber nur vemuten. Das mit der Bindung sollte sich ja durch debuggen deines Syntaxbaumen auch relativ schnell lösen lassen. Was die Geschwindigkeit angeht, so kann man ja immer nur bestimmte Funtionen testen. Je nachdem was du in deinem While-Loop so tust dürfte das Ergebnis ja wieder anders aussehen. Hinzu kommt dass du eine VM benutzt. Dadurch bläht sich das ganze natürlich auch noch ein wenig auf. Schlimm sein sollte das aber denke ich nicht und wie du schon sagst optimieren kann man hinterher noch.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

65

03.07.2014, 14:58

Ich meide konkrete Adressen, sondern nutze Positions-Unabhängigen Code (PIC). Bei einem Sprung gebe ich lediglich einen Offset in Instruktionen an, der recht simpel zu berechnen ist, da Instruktionen bei Onyx immer 4 Bytes groß sind. Beispiel in Pseudocode:

Quellcode

1
2
3
jif r1, 1      // Erhöhe den IC um 1, wenn true.
add r1, r2, r3 // Springt hier hin, wenn r1 false/nil liefert.
mul r1, r1, 2  // Springt hier hin, wenn r1 zu true evaluiert.
Dadurch spare ich mir lästiges Lesen irgendwelcher konkreten Adressen und dem Code ist egal, wo er hingeschmissen wird. Bei einer Code-Prüfung ist natürlich zu beachten, dass jeder Sprung um -1 ungültig ist, da er zur selben Sprung-Instruktion hüpft. Aber das ist sehr schnell getestet.

Ansonsten sieht dein !-Bug für mich danach aus, dass der Operator viel zu schwach bindet, der Ausdruck also zu !(true&&false) wird, wie Schorsch schrieb.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

66

03.07.2014, 23:47

Jup der ! Operator bindet zu schwach, sortier ich später aus.

@Evrey: Das mit dem offset finde ich interessant, aber wie löst du z.B. Funktionen auf? Dazu wäre ein offset eigentlich auch direkt die Adresse...
@Schorsch: was du mit Zeilen meinst verstehe ich nicht ganz. Das Problem ist ja recht simpel, ich kann noch nicht sagen wohin gesprungen werden soll weil ich die Länge des Code Blocks noch nicht kenne. So funktioniert es aber eigentlich. Also erstmal so lassen ;)

Dann habe ich jetzt einen "read" Befehl drin der eine Zeile als String von der Konsole liest. Ich dachte sowas mache ich (genau wie print) lieber als Funktion, aber eigentlich ist das so essentiell, dass es auch ein Befehl sein darf. Und ich hab noch keine Funktionen :P
Außerdem kann ich jetzt Code Blöcke lesen. Dazu habe ich eine Expr namens Block die einfach so lange in eine Liste schreibt, bis ein unerwarteter Ausdruck kommt, also meistens }.

Die nächsten Schritte sind erstmal doch nicht Arrays, sondern die ganzen kleinen Dinge die ich vergessen hatte. Z.B. die Operatoren +=, -=, ...
Ab morgen um 22:30 bin ich frei und kann bis Oktober durchprogrammieren, juhu!

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

67

04.07.2014, 10:21

Zitat

Das mit dem offset finde ich interessant, aber wie löst du z.B. Funktionen auf? Dazu wäre ein offset eigentlich auch direkt die Adresse...
Funktionen sind Objekte erster Klasse und zugleich normale Member. Wird also eine Funktion aufgerufen, wird einfach ein Funktions-Objekt gegriffen, das sich in einem Register befindet. Pseudocode:

Quellcode

1
2
3
4
move o0, i0   // Kopiere die self-Referenz ins Output-Register.
loadi16 o1, 3 // Füge als nächsten Parameter 3 hinzu.
loadi16 o2, 4 // Füge als nächsten Parameter 4 hinzu.
call r4       // Rufe das Funktions-Objekt im Register R4 mit den Parametern (self,3,4) auf.
Die VM kümmert sich dann darum, die richtige Adresse aus dem Funktions-Objekt zu greifen, also C++-/JIT-Funktion oder Bytecode-Funktion. Und ja, Methoden werden durch Funktionen realisiert, die als Erstes den self-Parameter erwarten.

Quellcode

1
2
3
4
5
6
7
8
import IO;
a = new Object();
met a.foo() {IO.println("Hi!");}
fun foo() {IO.println("Meh...");}

a.foo();                 // => prints "Hi!\n"
IO.println(type(a.foo)); // => prints "<Function@DEADBEEF>\n"
IO.println(type(foo));   // => prints "<Function@08150815>\n"

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »Evrey« (04.07.2014, 10:34)


Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

68

04.07.2014, 12:46

Das mit dem Zeilen zählen ist im Prinzip eine andere Version von Evreys Offset. Stell dir dein Programm wie ein BASIC Programm vor. Jede Zeile ist nummeriert wobei ich hier davon ausgehe dass aufsteigend Nummeriert wird und jede darauffolgende Zeilennummer um eins größer als die letzte ist. Also einfach durchnummeriert. Sollte denke ich klar sein. Dieses hoch zählen kannst du teilweise im Syntaxbaum schon erledigen, oder bei der Umwandlung in Intermediate Code. Das Prinzip ist eigentlich das selbe wie bei der Offset Variante. Ob ein Jumpbefehl weiß dass er 5 Zeilen weiter springen soll oder ob er weiß er soll zu Zeile 92 springen. Für die Vernetzung dazwischen kannst du wie gesagt im Jump Befehl erst eine Referenz auf das Ziel speichern und hinterher wenn alle Zeilennummern bekannt sind kannst du das eigentliche Ziel zu dem gewünschten Index auflösen.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

69

11.07.2014, 18:56

Jup danke, so werde ich es machen.

Für das mitzählen nutze ich ja gerade std::ofstream. Das werde ich dann langfristig einfach durch eine eigene kleine Schnittstelle ersetzen, dann kann man auch Code compilieren und direkt ausführen, ohne ihn vorher zwischen zu speichern.

DeKugelschieber

Community-Fossil

  • »DeKugelschieber« ist der Autor dieses Themas

Beiträge: 2 641

Wohnort: Rheda-Wiedenbrück

Beruf: Software-Entwickler

  • Private Nachricht senden

70

12.07.2014, 02:12

So ich habe gerade doch mal aus jux und dollerei versucht die Performance zumindest etwas zu verbessern.
Mein erster Gedanke war das new und delete doch evt. teuer sind. Und ich hatte recht. Folgendes Konstrukt läuft fast 40% (38,75% bei 10 Tests) schneller, wenn ich die Zeiger durch normale Variablen ersetze:

Quellcode

1
2
3
4
5
_x = 0

while(_x < 100000){
    _x = _x+1
}


Und das in der Debug Version!

Außerdem habe ich, wenn möglich, die if...else if... else Strukturen durch switches ersetzt. Das bringt zwar so gut wie nichts, aber es liest sich einfacher.
Und ich überlege die std::string* in der union der Variablen durch einen shared_ptr zu ersetzen... quasi der Garbage Collector für Arme.

Werbeanzeige