Seite 1 von 2 12 LetzteLetzte
Ergebnis 1 bis 20 von 25

Thema: Wie entsteht eine neue Programmiersprache?

  1. #1

    Wie entsteht eine neue Programmiersprache?

    Schönen guten Abend.

    Eine kleine Frage aus reiner Neugierde.
    Wie entwickelt man eine neue Programmiersprache? Mich interessiert es einfach wie soetwas entsteht und was dafür nötig ist? Erfahrung und Kenntnis ist natürlich selbstverständlich.

    Vielen Dank für alle Antworten.
    Cornix.

  2. #2
    Außer Zeit und Programmiererfahrung nicht viel. Einen Compiler für einen Programmiersprache mit sehr eingeschränkter Funktionalität kann man schnell schreiben. Du solltest dir aber überlegen für welchen Prozessor du den Compiler schreibst, alternativ kannst du natürlich auch z.B. JAVA-Bytecode produzieren.
    Das Buch von Wirth ist dazu übrigens ganz gut: http://www-old.oberon.ethz.ch/WirthPubl/CBEAll.pdf

  3. #3
    Idealerweise entwickelt man eine Programmiersprache erstmal seeehr lange auf dem "Papier" und nicht am Computer (in der IDE). Planung ist der erste Schritt: Was wird das für eine Sprache (prozedural, OOP, funktional,...), was für Features soll die Sprache haben, Syntax und Semantik (wenn einem langweilig ist auch formell) definieren - das ist die halbe Miete. Die Umsetzung ist dann, ich will nicht sagen einfach, aber der leichtere Teil. Eine Programmiersprache, die einfach "gemacht" wurde, ohne eingehende Planung, und sich dann entwickelt hat, haben wir schon - nennt sich C. Unintuitive Syntax für später hinzugefügte Features sind die Folge.

  4. #4
    Vielen Dank für die Antworten.
    Allerdings geht es mir hier nicht darum eine eigene Sprache zu entwickeln. Meine Fähigkeiten in diesem Bereich sind, wenn ich nicht damit schon übertreibe, mehr als dürftig.
    Es interessiert mich lediglich die technische Komponente dahinter. Ich kann mir nur schwer vorstellen wie sich so eine Programmiersprache entwickelt.

    So weit ich es verstanden habe arbeitet ein Computer lediglich mit Einsen und Nullen. Die Programmiersprache mit der wir schreiben muss daher wohl für den Computer leserlich gemacht werden indem sie in dieses Binärformat übertragen wird. Doch wie dies gemacht wird kann ich mir garnicht vorstellen.
    Und wenn solch ein Compiler in einer anderen Programmiersprache geschrieben werden kann, wie wurde dann der erste Compiler geschrieben? Es muss ja schier unmöglichsein für einen Menschen direkt mit Einsen und Nullen ein Programm zu schreiben. Das ist für mich einfach undenkbar.

  5. #5
    Zitat Zitat von Cornix Beitrag anzeigen
    [...] Und wenn solch ein Compiler in einer anderen Programmiersprache geschrieben werden kann, wie wurde dann der erste Compiler geschrieben? Es muss ja schier unmöglichsein für einen Menschen direkt mit Einsen und Nullen ein Programm zu schreiben. Das ist für mich einfach undenkbar.
    Und doch ist genau so gegangen: zuerst schreibt irgendein Nerd mit zu viel Zeit und Hintergrundwissen eine sehr einfache, grundlegende Programmiersprache mit Einsen und Nullen (beispielsweise Assembler), die nicht viel mehr kann, als für Menschen halbwegs verständliche Begriffe in Einsen und Nullen zu übersetzen. Dann kommt irgendwer anders daher, und nutzt diese Programmiersprache, um eine höhere zu programmieren, die stärker abstrahiert ist, und dementsprechend für Menschen einfacher zu verstehen. Man wiederhole diesen Vorgang ein paar mal, und schon hat man die Möglichkeit, mit einer einzigen Zeile Quelltext in einer Hochsprache eine wahre Kaskade von Einsen und Nullen in seinem Rechner heraufzubeschwören. =)

  6. #6
    Geht es darum wie ein Computer aus Einsen und Nullen komplexere Befehle ausführt, oder um den Aufbau eines Compilers?
    Das wird glücklicherweise mittlerweile gut getrennt.
    Der CPU-Hersteller gibt einen bestimmten Befehlssatz vor, oft mit nur rudimentären Operationen (Speicherzugriffe, Einfache Rechenoperationen, Shiften und ein paar Sprunganweisungen).
    Für einen Compiler ist das natürlich das begehrte Zielformat.

    Der Schritt vom Quellcode zum fertigen Programm ist relativ komplex und wird daher in verschiedene Schritte unterteilt.
    1. Lexing
    Der Quellcode wird mithilfe von regulären Ausdrücken in eine Reihe von synaktischen Tokens zerlegt. Statt eines Zeichenstroms kriegt der nachfolgende Parser die einzelnen syntaktischen Bausteine Stück für Stück. (Statt, "if x > 1 then y" liest der parser: if-token, identifier, greater, constant, then, identifier)
    In dem Schritt werden auch Whitespaces und Kommentare "überlesen".
    2. Parser
    Der Parser arbeitet auf diesem Tokenstream des Lexers und muss entscheiden ob der Input ein syntaktisch korrektes Programm darstellt oder nicht. Hier wird mit kontextfreien Grammatiken (bzw. mit etwas eingeschränkten kfG) gearbeitet. Der Parser baut als Output einen Syntax-Baum auf (genau genommen einen sogenanntne "Abstrakten Syntax Baum" AST, da der konkret zum Parsen verwendete Syntaxbaum für die spätere Arbeit etwas unhandlich ist)

    In der Regel spricht man direkt den Parser an, der dann jeweils den Lexer aufruft um das nächste Token zu kriegen. Da Parsing ein relativ komplexes Problem ist baut man mittlerweile eher selten Parser von Hand. Stattdessen gibt es Programme, die für eine gegebene Grammatik einen entsprechenden Parser oder Lexer (oder beides) generieren.

    Ich benutz jetzt mal eine einfache Anweisung wie "y = x + 2" als Beispiel.
    Im AST hätten wir also hier einen Zuweisungsknoten, dessen linkes Kind ein Identifier Knoten wäre (die variable y), der rechte Knoten wäre ein Add-Knoten, mit den jeweiligen Kindern: Identifier-Knoten (variable x) und Konstante-Knoten (die 2).
    Auf diesem AST kann man nun Typchecking (stimmen die typen der kinder des add-knoten für eine addition, ist der ergebene typ des Add-Knotens kompatibel mit dem typ von y) und diverse Optimierungen vornehmen.

    Am Ende ist es relativ "simpel" für einen bestimmten Knoten im AST den entsprechenden Assemblercode zu generieren. Für obiges Beispiel wäre das zB:
    1. Lade die Adresse des linken Kinderknoten (des Ziels) in ein Register A
    2. Führe den Code zur Berechnung des Rechten Kinderknotens aus (Ergebnis steht in einem fest definierten Register B)
    3. Kopiere den Wert aus B an die Adresse, die in A steht

    Ich hab auf der Platte noch den Compiler (Source Code) für eine einfache Programmiersprache liegen, den wir letztes Semester als Projekt schreiben mussten. Der Compiler ist in Java geschrieben und erzeugt als Output Code für einen Assembler, der das ganze in Java-bytecode übersetzt. Falls du Interesse hast, kannst du ihn gerne haben =).

  7. #7
    Sehr detailierte Antworten, vielen Dank.
    Ich denke ich konnte meinen Kopf damit ein klein wenig erleichtern.

    Und vielen Dank für das Angebot, allerdings denke ich ist dies nicht notwendig MagicMagor.

  8. #8
    Ich wäre allerdings durchaus interessiert

  9. #9
    Da wirft sich mir allerdings doch noch eine Frage auf.
    Läuft ein Interpreter mit dem selben Prinzip? Es müsste doch noch ein klein wenig anders sein schätze ich.
    Wird eine Zeile nach der anderen Compiliert? Oder der Befehl welcher gerade ausgeführt wird? Oder die ganze Methode die gerade ausgeführt wird?
    Denn falls es nur Zeilen / Funktionsweise compiliert werden würde dann müssten if/then/else Strukturen etwas komplizierter ausfallen oder täusche ich mich?

  10. #10
    Die Compiler arbeiten quasi immer nach dem selben Schema.
    If-then-else sind nichts weiteres als sog. Sprungbefehle. Sie springen von einem Speicherbereich zu nächsten.
    Du kennst vielleicht den goto-Befehl aus einigen Sprachen. If-then-else ist im Grunde nichts anderes, als ein goto-Befehl.

    Ein Compiler ist ja kein Zeileninterpreter, wie bei Scriptsprachen, sondern er übersetzt das komplette Programm. Wenn das Programm gestartet wird, wird das Programm in den Arbeitsspeicher geladen und dort ausgeführt. Somit gibt es für den Prozessor auch zwischen Variable und Funktion keinen Unterschied. Beides sind nur Bereiche im Speicher, die der Prozessor abarbeitet. Vielleicht hast du auch schonmal was von (Funktions-)Zeigern gehört. Also ein Zeiger, der auf eine Variable oder Funktion zeigen.

    Zeileninterpreter interpretieren nur die Zeile, in der sich sich grad befinden. Bei If-Then-Else würde er dann mehrere Zeilen, zum Else-teil überspringen, wenn die Bedingung nicht erfüllt ist.

  11. #11
    Vielen Dank, ganz genau scheine ich das aber noch nicht verstanden zu haben glaube ich.
    Was bedeutet dies konkret bei einer Programmiersprache wie beispielsweise Ruby (welche in dem RPG-Maker XP verwendet wird), der Code wird nunmal nicht compiliert wie in beispielsweise C++, wenn man das Programm startet was passiert dann damit? Wird der Code Zeile für Zeile eingelesen?

  12. #12
    Ich weiß nicht ob man tatsächlich einen Interpreter schreiben kann der Zeile für Zeile einliest und direkt ausführt.
    Ich wühle mich gerade für meine BA-Arbeit durch den Code des Orc-Interpreters und muss einen entsprechenden Interpreter in Prolog schreiben. Orc liest das Programm erst einmal komplett ein und baut den kompletten AST auf. Dadurch können zB. Syntaxfehler sofort erkannt werden. Auch semantische Überprüfungen wie Typechecking und ob ungebundene Variablen vorkommen erledigt Orc direkt vor der Ausführung (das könnte man aber natürlich auch erst im laufenden Betrieb machen).
    Der Interpreter durchläuft dann letzlich den AST, fängt beim Wurzelknoten an und führt je nach Knotentyp bestimmte Aktionen aus. Klingt für mich nach der besten Methode einen Interpreter zu schreiben.

    @dfyx
    Du hast gleich Post

  13. #13
    Zitat Zitat von MagicMagor Beitrag anzeigen
    Ich weiß nicht ob man tatsächlich einen Interpreter schreiben kann der Zeile für Zeile einliest und direkt ausführt.
    BASIC oder die Linux Shell besitzen einen Zeileninterpreter, die Befehl für Befehl einlesen und ausführen.
    Am Besten demonstriert dies der C64, da dort die Benutzeroberfläche ein BASIC Interpreter war. Gleichzeitig war die Benutzeroberfläche auch eine Entwicklungsumgebung.

  14. #14
    Aber wenn der Interpreter vor dem Programm Start ersteinmal das gesamte Programm einliest und überprüft, wo genau liegt dann der Unterschied zu den Compilern, außer, dass die Compiler diese Arbeit nur einmal zu erledigen haben?

  15. #15
    Geschwindigkeit.Ein Zeileninterpreter muss die Zeile parsen und dann in Maschinensprache umwandeln und erst dann kann der Befehl ausgeführt werden. Ein Compiler hingegen hat nun alle Zeit der Welt, um den Programmcode zu analysieren. Die meisten Compiler können dann noch den Quellcode optimieren, sodass er noch ein tick schneller arbeitet. Wenn z.B.
    Code:
    a = 1;
    a = a + 4;
    im Quelltext steht, dann macht der Compiler daraus
    Code:
    a=5;
    (um jetzt mal ein einfaches Beispiel zu nennen)
    Somit muss das Programm nicht zwei Befehle ausführen, sondern nur einen. Eine Addition besteht ja auch noch aus mehreren Prozessinstruktionen.

    Ein Interpreter läuft auch im Hintergrund, um den Quelltext zu interpretieren. Bei einem compilierten Programm läuft nichts im Hintergrund, was dann noch Ressourcen einspart, weil ein Interpreter ja auch seine Rechenzeit und sein Speicher benötigt.

  16. #16
    Vielen Dank für die Antwort, allerdings hatte ich mich dabei eher auf den Beitrag von MagicMagor bezogen.
    Wo der Sinn eines Zeileninterpreters liegt ist mir ja einleuchtend, allerdings, wenn der Interpreter sowieso den gesamten Code vor dem Start durchgeht so wie MagicMagor es beschrieb, oder ich es zumindest verstanden habe, dann liegt doch der Unterschied, wenn überhaupt, lediglich auf einer graduellen Ebene oder irre ich mich da?

  17. #17
    Zitat Zitat
    dann liegt doch der Unterschied, wenn überhaupt, lediglich auf einer graduellen Ebene oder irre ich mich da?
    Jein. Der Geschwindigkeitsvorteil von (gut) compiliertem Code zu Interpreter ist beachtlich. Du musst bedenken, der Interpreter durchläuft beim Ausführen den Syntaxbaum, hat also einiges an Verwaltungsaufwand und selbst bei der Ausführung eines einzelnen Knoten kommen zusätzliche Befehle hinzu.
    Wo der Compiler also vielleicht 3-5 Maschinenanweisungen für einen Knoten produziert, laufen beim Interpreter ungleich mehr Anweisungen für denselben Knoten ab. Hinzu kommt, dass für bestimmte Anwendungen eventuell spezielle Maschinenanweisungen bereit stehen, die das ganze noch effizienter und schneller ausführen, der Compiler kann gezielt solche Anweisungen statt "normaler" Anweisungen nutzen, beim Interpreter ist das nicht unbedingt möglich.
    Der Compiler vollführt beim Compilieren eine Unmenge an Analyse- und Optimierungsaufwand der sich in einer langsamen Ausführung äussern. Wenn du mal ein größeres Projekt selbst compiliert hast (zB Open Office) weißt du was das heißt. Dieser Aufwand muss aber nur 1 mal gemacht werden, nämlich beim Entwickler der seinen Code compiliert - der ausführbare Code läuft deswegen aber sehr flott.
    Wenn der Interpreter diese ganzen Analysen auch machen würde, sobald er ein Programm einliest, könnte er vielleicht die Ausführung des Codes beschleunigen, würde aber mitunter minutenlang rechnen, bevor er mit der Auführung beginnt - was in der Praxis natürlich undenkbar ist.
    Der Interpreter hat dabei noch das Problem, das das Interpreterprogramm anwesend sein muss wenn man das Programm ausführen muss, ebenso wie der zu interpretierende Code - beim compilierten Programm braucht man nur das Programm, das man ausführen will.

    Ein weiterer Vorteil des Compilers ist das compilieren in andere Zielformate. Du kannst aus demselben Code, sofern der Compiler es unterstützt, ausführbare Programme für ganz unterschiedliche Plattformen produzieren, von anderen Betriebssystemen bis hin zu gänzlich anderer Hardware. Beim Interpreter müsstest du den kompletten Interpreter für die Zielplattform portieren - ein ziemlicher Aufwand.

    Java versucht aber genau so etwas, eine Art Verbindung von beidem. Java-Code wird in ein Zwischenformat compiliert, den sogenannten Bytecode. Dieses "Java-Programm" wird dann beim Benutzer durch die Java Virtual Machine (JVM) zur Laufzeit interpretiert. Der Vorteil des Entwickler ist, das sein Code in ein portables Format umgewandelt wird, er also die Besonderheiten der Zielplattform nicht beachten muss - compile once, run everywhere.
    Nachteil ist, dass der Benutzer dafür sorgen muss, das er eine passende JVM installiert hat, die natürlich auch jede Menge Ressourcen schluckt und Java-Code ist trotz aller Optimierungen immer noch deutlich langsamer als nativ compilierter C(++) Code. Gerade aktuelle Top-Spiele brauchen aber Geschwindigkeit, Java fällt dort alleine deswegen schon durch.

    Der Geschwindigkeitsvorteil mag sich nur "graduell" unterscheiden, ist aber in der Praxis bei einigen Anwendungen gravierend. Ein Spaziergang unterscheidet sich ja auch nur "graduell" von einem olympischen 100-Meter Sprint - eigentlich wird bei beiden ja dasselbe gemacht, oder?

  18. #18
    Jedoch bleibt es im Kern die gleiche Arbeit welche der Interpreter (welcher das gesamte Programm einliest und nicht Zeilenweise arbeitet) leistet und ein Compiler.

    Ich bin am überlegen, ob man nicht mit einer Programmiersprache beides verwenden kann.
    Denn soweit ich das sehe ist der Sinn des Interpreters nicht bei dem Benutzer gelegen sondern bei dem Entwickler. Für den Nutzer stellt eine Interpretersprache lediglich einen Nachteil dar, dem Entwickler hilft es aber schneller zu arbeiten. Gerade bei Programmen bei welchen sehr oft kleinere Abänderungen am Code vorgenommen werden müssen würde andauerndes Compilieren des Programmes zu viel Zeit in Anspruch nehmen welche von der Entwicklungszeit des Scripters abgezogen werden würde. Mit einem Interpreter kann man den Code schneller testen. Soweit hoffe ich, liege ich mit meiner These richtig.
    Also wäre es doch vorteilhaft, wenn man mit einer Programmiersprache arbeiten kann und sie sowohl direkt, über einen Interpreter, testen, als auch im finalen Zustand direkt compilieren könnte. Als ob man die Möglichkeit hätte Hochsprachen wie C++ auch ohne Compilierung direkt durchlaufen zu lassen, mit erheblichen Performanceverlusten versteht sich, allerdings wären diese Performanceverluste bei den meisten kleinen Tests nicht sonderlich schwerwiegend.

  19. #19
    Zitat Zitat von Cornix Beitrag anzeigen
    Ich bin am überlegen, ob man nicht mit einer Programmiersprache beides verwenden kann.
    Ich komme da wieder mit BASIC.
    QBASIC und Visual Basic (6.0) hatten beides. Einen Zeileninterpreter und einen Compiler.
    In der Entwicklungsumgebung wird halt der Zeileninterpreter benutzt. Zusätzlich hat man die Möglichkeit, mit einem Compiler eine Anwendung zu compilileren.

    Allerdings benötigt ein Programmierer nicht unbedingt einen Zeileninterpreter. Bei größeren Projekten wird eh alles in Modulen aufgeteilt. Ein Programmerierer arbeitet dann nur an seinem derzeiten Modul. Er braucht somit gar nicht die anderen Module seiner Kollegen, um sein Modul zu testen. Auch wäre es ziemlich unsinnig, immer das komplette Programm zu starten, nur um eine Sache zu testen. Stell dir vor, du arbeitest an einer Office Software bastelst an der Schnittstelle für den Drucker. Da wäre es doch idiotisch, immer wieder die Office Software zu starten und dann im Menü durchzuklicken, bis du endlich die Druckereinstellung erreichst. Da schreibt man kurz ein kleines Testprogramm, mit dem man dann sein Modul testet. Auch gibt es sog. Unit Tests, die automatisiert ein Programm testen können.
    Erst zum Schluss, wenn alles fertig ist, werden die Module zusammengesetzt und evtl. noch ein wenig angepasst. Dann geht es in die sog. Alpha Phase.
    Ein Compiler übersetzt ja auch nicht bei jedem Compilieren das Programm komplett neu. Er merkt schon, wenn bestimmte Programmteile sich nicht verändert haben. Dann werden die Teile auch nicht neu compiliert. Also dauert es nur beim ersten Mal ein wenig länger. Wenn man dann nur eine Zeile ändert, übersetzt er dann auch nur diese Zeile neu.

    Ein Zeileninterpreter hat auch das Problem mit der Geschwindigkeit. Oftmals will man schon während der Entwicklung feststellen, ob ein Algorithmus schnell genug ist, wenn man einen komplexen Algorithmus basteln muss. Da kann es schon vorkommen, dass der Zeileninterpreter viel zu langsam ist.

  20. #20
    Zitat Zitat von Cornix Beitrag anzeigen
    Ich bin am überlegen, ob man nicht mit einer Programmiersprache beides verwenden kann.
    Kann man. Es gibt beispielsweise mehrere (brauchbare) Interpreter für C und C++. Umgekehrt hab ich auch schon einen (weniger brauchbaren) Compiler für PHP gesehen.

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •