eigene scriptsprache
-
Dazu vielleicht noch ein Nachtrag: Jupp, es macht auf jeden Fall Sinn einen Baum zu erzeugen und einen guten Teil der Optimierungen dort auszuführen, dazu gehören sicher constant Expressions, die freundlicherweise trivial sind, spannender sind da schon Common Subexpressions, aber natürlich nicht so einfach zu finden, da sie von den Variableninhalten abhängen und nicht jeder gleiche Teilbaum zusammengefasst werden kann. Will Man Bytecode erzeugen, so kann man dann auf der Seite noch heufig vorkommende Folgen in Komplexere Bytecodes zusammenfassen, dort finden sich auch Optimierungen die im Baum nicht oder viel schwieriger zu entdecken sind, da sie über Teilbaumgrenzen hinweg entstehen können.
Insgesammt kann man massig Arbeit und Zeit in den Optimizer stecken und ich kann nur empfehlen, durch Profiling zu prüfen was man wirklich braucht, insbesondere wenn man abschätzen kann das später ein bestimmter Typ von Problemlösungen damit gescriptet werden soll. Es mag sich dann lohnen speziell auf diesen Bedarf hin zu optimieren.
Ich bin übrigends schon neugierig, wofür die Sprache eingesetzt werden soll. Game-Logik (wie viele Eigenentwicklungen), Anwendungs-Erweiterung oder General Purpose?
-
ScriptMe schrieb:
Will Man Bytecode erzeugen, so kann man dann auf der Seite noch heufig vorkommende Folgen in Komplexere Bytecodes zusammenfassen, dort finden sich auch Optimierungen die im Baum nicht oder viel schwieriger zu entdecken sind, da sie über Teilbaumgrenzen hinweg entstehen können.
jup!
ohne frage kann man den assembler-code noch optimieren und kommt erst dann auf das optimale ergebnis. halt das verzwirbeln, um optimale registerbelegung zu bekommen und die ganze parallelität des prozessors auszunutzen.
je nach bytecode hat der selber auch optimierungsmöglichkeiten. je weiter er vom baum weg ist, desto mehr.
-
ScriptMe schrieb:
Das ist schwer zu sagen, zur Zeit scheinen die Code-basierten Sprachen schneller zu sein als die Baum-basierten.
Dann würde ich aber Äpfel mit Birnen vergleichen. Die Bytecodeversion kann in jeder Situation unterbrochen werden, die Baum-basierte nur wenn sie einen Stack hat. Dies ist sehr praktisch wenn zum Beispiel einen Debugger basteln will.
volkard schrieb:
beachte zuerst mal
struct Node{ void interpret(); Node* optimoze(); }
ein optimierer auf hoch-ebenbe! ist das nicht geil!? der wird alle zur compilzeit brechenbaren ausdrücke berechnen, der wird NodePlus(NodeInt(5),NodeInt(7)) leicht zu NodeInt(12) machen können.
Ich sehe du scheinst Erfahrung auf diesem Gebiet, also gleich noch eine Frage. Wenn ich die Hochsprachenoptimizirungen wirklich einbaue dann braucht ich ja mehr als ein optimize(). Das hier ist ja das Minimum:
struct Node{ Node*optimize(); bool is_constant(); Value reduce_to_constant(); bool has_side_effects(); bool has_sub_expression(const Node*); bool equals(const Node*); bool has_tail_recursion(); //... };
Je mehr Optimierungen man einführt, je mehr werden die eigentlichen Node Klassen, welche ja eigentlich nur die Aufgabe haben Code zu repräsentieren, aufgeblasen. Des weiteren sind die Methoden die an einer Aufgabe werkeln sehr schnell weit verteilt (oder man organisiert die cpp Dateien nicht nach Klassen). Hast du nicht eine Idee wie man hier die verschiedenen Optimierungsmethoden von einander und vor allem von den Nodes trennt damit die Übersicht erhalten bleibt? Alle meine bisherigen Ansätze diesbezüglich endeten mit Code der schon in den frühen Entwicklungsstadien unwartbarer wurde.
-
Ben04 schrieb:
Ich sehe du scheinst Erfahrung auf diesem Gebiet, also gleich noch eine Frage.
hab es noch nicht versucht. hab nur vorüberlegungen betrieben, auchmal sowas zu machen.
Alle meine bisherigen Ansätze diesbezüglich endeten mit Code der schon in den frühen Entwicklungsstadien unwartbarer wurde.
oh. du bist viel weiter als ich. ich hab mir nur vorgestellt, daß optimize im wesenlichen klappt. in sachen wartbarkeit könnte ich nur helfen, wenn ich schon programmierversuche gemacht hätte.
-
hi ich bins nochmal.
also hab mich da mal bissl reingesteigert und bin sogar schon garnicht so wenig weit gekommen. Im moment Arbeite ich noch am "Variablen" system. Wo gleich die nächste Frage aufkommt: ich hätte gerne, das der backslash "funktionier" so wie man das gewohnt ist.
Also sprich "\"das ist innem string\"" soll dann eben die \"\" zu "" machen wenn die äußeren aufgelöst werden. und auch alle anderen zeichen mit \ davor.
weil ich denke, dass es bessere methoden als zeilenlange switchanweisungen gibtdanke schonmal
-
schrengtoi schrieb:
Wo gleich die nächste Frage aufkommt: ich hätte gerne, das der backslash "funktionier" so wie man das gewohnt ist.
Also sprich "\"das ist innem string\"" soll dann eben die \"\" zu "" machen wenn die äußeren aufgelöst werden. und auch alle anderen zeichen mit \ davor.
weil ich denke, dass es bessere methoden als zeilenlange switchanweisungen gibtdanke schonmal
Dies sollte die Aufgabe des sog. Lexers sein, der aus einer Skript-Datei eine Folge von "Tokens" generiert. Ich habe das auch mal gemacht.
Kannst du dir ja mal anschauen, der Lexer ist in src/compiler/cllexer.cpp:
http://www.zak2project.de/files/tech/cl2-31-03-2005.zip. Außerdem werden C- und C++-Kommentare entfernt.
(Meine Skriptsprache selbst ist nur auf Einfachheit ausgelegt - keine Optimierung, aber Bytecode, ungefähr 5x so langsam wie Perl)-Gunnar
-
Ich hab auch mal eine kleine Skriptsprache implementiert, ist auch nicht optimiert sonder ganz geradeaus interpretiert. Geschwindigkeit keine Ahnung, aber vielleicht hilft's ja jemand
Implementierung ist allerdings in C#.
CScript
-
schrengtoi schrieb:
hi ich bins nochmal.
also hab mich da mal bissl reingesteigert und bin sogar schon garnicht so wenig weit gekommen. Im moment Arbeite ich noch am "Variablen" system. Wo gleich die nächste Frage aufkommt: ich hätte gerne, das der backslash "funktionier" so wie man das gewohnt ist.
Also sprich "\"das ist innem string\"" soll dann eben die \"\" zu "" machen wenn die äußeren aufgelöst werden. und auch alle anderen zeichen mit \ davor.
weil ich denke, dass es bessere methoden als zeilenlange switchanweisungen gibtdanke schonmal
An sich hat das aber eigentlich nicht sehr viel mit einem Variabelensystem zu tun. Ein Stringliteral lässt sich meiner Erfahrung nach am besten mit einer Inlinefunktion die den String als Rückgabewert hat implementieren.
Das Parsen ist trivial:
char match_escape_sequence(char c){ switch(c){ case '\'':case '\\':case '\"':return c; case 'n':return '\n'; default:error(); } } string read_string(istream&in) { ignore_spaces(in); if(in.peek() != '\"' && in.peek() != '\i') error(); char enclose = in.get(); string str; int c; while((c = in.get()) != enclose){ if(c == EOF) error(); else if(c == '\\'){ c = in.get(); if(c == EOF) error(); str += match_escape_sequence(c); }else str += c; } return str; }
Parserfunktionen schreiben gehört eigentlich sehr oft zu den Teilen eines Interpreter die am meisten Spaß machen zu schreiben.
Zum Thema Token habe ich die besten Erfahrungen mit folgendem Lexer gemacht:
bool is_at_string(istream&in); string read_string(istream&in); bool is_at_number(istream&in); double read_number(istream&in);
istream sollte man aber noch durch eine eigene Kreation ersetzen die ihre Position verhält damit man ordentlich Fehler melden kann und die auch richtig Seeken kann und nicht diese Arrayemulation die man mit istream::seekg betreibt, dadurch dass man einen Offset angibt. Solange dieser Offset da ist wird das nie performant und speichersparend.
Oft versuchen Leute eine große Lexerclasse zu schreiben und dieser dann eine Methode ReadToken zu verpassen. Dies hat ein großes Problem : man versucht viele grundverschiedene Tokentypen über einen gemeinsamen Returntype zu quetschen. Das wird nie schön und endet fast immer in einer Struktur wie:
struct Token{ string value; enum TokenType{ T_String, T_Number }type; };
Meiner Meinung nach erfüllt solch ein Lexer noch nicht mal seine Aufgabe. Ein Token "0xFF" ist meiner Meinung nach nicht verschieden von einem Token "255" dieser Lexer trägt dem aber keine Rechnung oder es wird sehr viel gecastet:
- "0xFF" und "255" => int
- int => string
- dursch ReadToken
- string => int
Dies ist aber meine Meinung und es gibt auch Leute die sagen ein Lexer sollte ausschließlich die einzeln Token von einander abgrenzen und das Lesen der Token außerhalb ihres Kontextes gehört nicht mehr zu seiner Aufgabe. Dies empfinde ich allerdings als ein unnötiger Mehraufwand.
read_string und read_number sollten auch meiner Meinung nach auch global sein. Sie in eine Klasse zu quetschen bringt keinen Gewinn schließlich sind es ja zustandlose Prozeduren. Ein eigener Namensraum wäre allerdings sicher nicht verkehrt.
oh. du bist viel weiter als ich. ich hab mir nur vorgestellt, daß optimize im wesenlichen klappt. in sachen wartbarkeit könnte ich nur helfen, wenn ich schon programmierversuche gemacht hätte.
Schade.
Dann muss ich wohl diesbezüglich noch weiter in meinem Labor forschen.
-
so. Ich grab nochmal den alten thread raus.
Bin wieder ein bischen weitergekommen .... jetzt steh' ich nurnoch vor einem riesen Problem.Wie realisiere ich am besten If-Abfragen? ich habs mir soweit überlegt:
ich laufe in einer schleife alle bedingungen, getrennt druch "&&" oder "||", durch.
Das mache ich in eine do-while schleife, damit er es auch einmal ausführt wenn keins von beiden existiert.
aber wie checke ich jetzt weiterhin, will er nur prüfen ob die variable existiert, bzw. ob sie true ist (mit if($la) z.B). Will ers nicht, kann ich nach einem ! suchen, aber wann sollte ich das am besten suchen?
und in welcher Reihenfolge soll ich das abarbeiten, auch zzgl. den anderen Operatoren "<=" ">=" "==" "!="?mfg schrengtoi
-
Suche das Trennzeichen mit der niedrigsten Priorität, spalte dort auf und suche wieder rekursiv das Trennzeichen mit niedrigster Priorität. Wenn es kein Trennzeichen im Teilbereich mehr gibt, werte den Ausdruck aus.
Du könntest auch Ook! als Skriptsprache verwenden.