Hallo Besucher, der Thread wurde 8,7k mal aufgerufen und enthält 94 Antworten

letzter Beitrag von daybyter am

Erfahrungen mit dem Versuch, eine Programmiersprache zu stricken - für alle, die es interessiert

  • Danke! :-D

    Kannte es zwar (nie genutzt, nur gelesen), aber ich werde schauen, ob ich zu diesem Buch komme, denn sowas ist immer spannend.

  • Und wieder komme ich in die konzeptuelle Krise, wie schon so oft, und frage mich, ob meine Ideen nicht völliger Unsinn für die 8-bit-Welt sind. Wahrscheinlich. Was mir jedesmal noch passiert, ist, dass ich die Sprache so einfach wie möglich halten will - aber dann draufkomme, dass die Umsetzung deshalb noch lange nicht einfach ist. In Gegenteil. Sie ist auf einem kleinen 8-bit-System praktisch so nicht machbar.


    Wie kann also eine Sprache für den C64 aussehen, die von aussen einfach zu verstehen und handhaben und im Inneren einfach gestrickt ist und dabei einigermassen performant bleibt?


    Vermutlich gibt es sie gar nicht?


    Hier habe ich so viele derartige Diskussionen gesehen, allesamt faszinierend. Vielleicht hätte ich besser zuhören bzw. zulesen sollen. Vielleicht brauche ich mich aber nur von der Idee verabschieden, dass der Quelltext auf dem C64 im Speicher editiert und in Zwischencode kompiliert werden sollte? Vielleicht wäre das der beste Ansatz, die "Hochsprache" auf dem PC zu belassen und bloß den Bytecode-Interpreter auf dem C64 zu implementieren? Oder zumindest, wenn doch auf dem C64, dann einen Compiliervorgang getrennt vom Lauf selbst vorzusehen, also den Zwischencode dann vor dem Lauf von einem File laden zu müssen?

    Noch schwimme ich ganz schön, und weiß noch nicht, in welche Richtung.


    Naja. Wenn ich wieder irgendwo Land sehe, melde ich mich erneut. :D

  • Mach Dir doch das Leben leicht, und schreib das erstmal auf dem pc. Dort musst Du nicht auf Resourcen achten. Dann siehst Du ja, wieviel Speicher und Rechenleistung Du brauchst. Und die Entwicklung geht schneller voran.

    Danke für die moralische Unterstützung. :thumbsup: Es ist ja auch ein naheliegender Gedanke. Aber es gibt auch zwei Vorteile, beide Versionen gleich reinzurechnen:


    1. Der Unterschied fasziniert mich, und es schadet gar nicht, sozusagen als Denksport eine krass abgespeckte Version zu konzipieren.

    2. Wenn ich alles aus denselben C-Sourcen compilieren will, kann ich so die entsprechenden Schalter-Macros gleich mit einbauen.


    Mittlerweile sehe ich übrigens schon wieder etwas mehr Licht, und zwar weil:

    Wenn ich auf eine normalerweise wichtige Eigenschaften verzichte, nämlich "referentielle Transparenz" oder "immutability" (d.h. man kann keine Variablen verändern), und auch kein Multithreading einbaue, kann ich für die 8-bit-Version enorm Komplexität und Speicher einsparen. Dann kann ich mir auch die teuren Datenstrukturen, mit denen ich auf dem PC um mich werfe, sparen, und reine Arrays verwenden.

    Wird schon wieder. :D

  • Wie kann also eine Sprache für den C64 aussehen, die von aussen einfach zu verstehen und handhaben und im Inneren einfach gestrickt ist und dabei einigermassen performant bleibt?

    Vielleicht so eine Art Pascal oder einfach C (cc65, vbcc, kickC).


    Da kommt manchmal gar nicht so peinlicher Code bei raus.

    Werden aber eben auf dem PC compiliert.


    Dass Du da eine Sprache mit interaktiver Programmierung, REPL-Style, auf dem C-64 selbst übersetzen willst, ist ein hehres Ziel, aber... warum? :)

    Deutlich bessere Performance-Ergebnisse als bei compiliertem BASIC sind da nicht zu erwarten, und da hat man 2 Schritte auf dieser kleinen Maschine (Entwickeln in BASIC, dann Compiler drüberlaufen lassen).

  • Vielleicht so eine Art Pascal oder einfach C (cc65, vbcc, kickC).

    Ja, das wären dann imperative Sprachen, und die sind natürlich genial schnell, besonders auf dem Kleinen. In der modernen Welt sind aber manche funktionale Sprachen auch ganz hübsch schnell und mit C vergleichbar, allerdings brauchts dazu natürlich enorme Optimierungsverfahren, daran braucht man bei <64k gar nicht erst denken.

    Da kommt manchmal gar nicht so peinlicher Code bei raus.

    Werden aber eben auf dem PC compiliert.

    Oh ja. Ich habe enormen Respekt vor dem, was moderne Compiler-Bastler da rausholen.

    Dass Du da eine Sprache mit interaktiver Programmierung, REPL-Style, auf dem C-64 selbst übersetzen willst, ist ein hehres Ziel, aber... warum? :)

    Mich hat Programmiersprachen-Design immer schon fasziniert. Vor allem deshalb. Aber auch weil ich mich stets frage, wie man das Programmieren an sich noch einfacher gestalten kann. Wobei das natürlich auch hochgradig subjektiv ist, und sehr von den Ansprüchen abhängt. Will man was grob skizzieren und laufen sehen, ist man damit zufrieden, oder ist man ein Micro-Manager und will sich in jedes Detail einmischen? Mir würds eher um das grobe Skizzieren "und los" gehen. Nicht zuviel über die Interna nachdenken. Hinschreiben, was man will, nicht wie man es will.

    Deutlich bessere Performance-Ergebnisse als bei compiliertem BASIC sind da nicht zu erwarten, und da hat man 2 Schritte auf dieser kleinen Maschine (Entwickeln in BASIC, dann Compiler drüberlaufen lassen).

    Stimmt natürlich. Insofern ist meine Leidenschaft, eine Sprache auch auf dem C64 zu implementieren vergleichbar mit der Nutzung des C64 selbst. Auf einem modernen PC wirds naturgemäß etwas schneller ablaufen. :D Aber ich habe eine kleine Hoffnung: Vielleicht schaffe ich es, dass die Programme selbst viel kleiner sein können als in anderen Sprachen. Wenn ich es schaffe, dass ich Programme in der Sprache schneller entwickeln kann, und sie weniger Platz im Speicher beanspruchen als in anderen Sprachen, ist das schon eine beachtliche Leistung. Und hätte bei dem beengten Platz sogar Sinn. Auch natürlich ein "unmögliches" Ziel. Aber ich lerne enorm viel aus den Überlegungen. Und aus dem Feedback. Danke übrigens für den Rascal-Link, das ist genial!

  • SYNTAX SUBTILITÄTEN


    Dieser Gedanke beschäftigt mich seit Langem: Wenn ich Symbole (also Namen, oder Operatoren) als "first order values" betrachte - also z.B. function im Programmtext wirklich für das Symbol "function" steht, dann brauche ich offenbar ein Zusatzsymbol, um eine Funktion des Namens function aufzurufen. Zum Beispiel könnte das so aussehen:

    Code
    1. :function(arg1,arg2,arg3)

    Der Doppelpunkt hieße dann sinngemäß (also "semantisch", was nichts anderes heißt als "sinngemäß"): Finde im Scope die Definition, die mit dem Symbol "function" bezeichnet ist, und wende sie auf die Liste der Argumente an.


    Nun habe ich die Vorgabe, den Parser möglichst klein zu halten. Ich will keine Infix-Notation, sondern ausschließlich Präfix. Und ich will die üblichen Funktionssymbole benutzen. Wie sähe dann ein stinknormaler arithmetischer Ausdruck aus? Z.B. würde ich a*b+c*d so ausdrücken:

    Code
    1. :+ :*(.a .b) :*(:c :d)

    Wobei der Punkt Definitionen auf dem Stack abruft, der Doppelpunkt Definitionen im Scope. Wie liest sich das?
    "Lies den Wert von d vom Stack, lege ihn auf den Stack, lies den Wert von c vom Stack, legen ihn auf den Stack, multipliziere, lies den Wert von b vom Scope, lege ihn auf den Stack, lies den Wert von a vom Scope, lege ihn auf den Stack multipliziere, addiere.

    Man könnte es auch so formulieren:

    Code
    1. :+ :* :* (.a .b :c :d)

    und das käme FORTH noch näher (halt umgekehrt)


    Aber sind die Doppelpunkte und Punkte schön? Viel schöner wäre doch:

    Code
    1. + *(a,b) *(c,d)

    Naja, am schönsten, so rein intuitiv, wäre natürlich a*b+c*d, aber das würde den Parser verkomplizieren. Also nicht.

    Will ich also lieber die Symbole automatisch zu ihren Definitionen umgewandelt? Und dass der Interpreter von selber weiß, wo er eine Definition findet (Stack oder Scope)? Und bräuchte ich dann einen eigenen Operator, um sie als sie selbst darzustellen? Wie z.B. 'function? Das wäre dann wieder konzeptuell nicht schön.


    Oder nehme ich alle Operatoren automatisch als evaluiert an, aber bloß die alphanumerisch zusammengesetzten Symbole nicht? Das wäre wieder inkonsequent.


    Oder lege ich das bei der Definition der Funktion, also bei der Verwendung fest? Oder lege ich das irgendwo vorher eigens pro Symbol fest?


    Das wäre wieder sehr kompliziert.


    Wäre diese "nackte" Schreibweise nicht viel schöner? Viellecht ja, aber dann hätte ich keine Unterscheidung mehr zwischen Stack und Scope (ja, sind beides Scopes, ich suche grade nach besseren Worten), und die könnte wichtig sein. Oder auch nicht. Ich muss daher erst überlegen, wie wichtig diese Unterscheidung noch werden kann, bevor ich entscheiden kann, ob ich Symbole so oder so verwende.


    Das sind so die Gedanken, die mich ewig und drei Tage aufhalten, weil ich eine Entscheidung treffen muss, und jede ihre Vor- und Nachteile hat.


    Vielleicht pfeif ich am besten auf die optische Schönheit und lasse mir lieber noch die konzeptuellen Türen offen. Eine optisch "hässliche" Sprache ist besser als eine, die nie fertig wird. :LOL

  • Der Doppelpunkt hieße dann sinngemäß (also "semantisch", was nichts anderes heißt als "sinngemäß"): Finde im Scope die Definition, die mit dem Symbol "function" bezeichnet ist, und wende sie auf die Liste der Argumente an.

    Möglicherweise habe ich das Problem nicht richtig verstanden, aber üblicherweise wird das doch bereits durch die öffnende Klammer unterschieden? Eine generelle Präfix-Notation wäre vielleicht sauberer, aber in diesem Fall auch abschreckender - einfach weil ungebräuchlich.

  • Dieser Gedanke beschäftigt mich seit Langem: Wenn ich Symbole (also Namen, oder Operatoren) als "first order values" betrachte - also z.B. function im Programmtext wirklich für das Symbol "function" steht, dann brauche ich offenbar ein Zusatzsymbol, um eine Funktion des Namens function aufzurufen. Zum Beispiel könnte das so aussehen:

    Keine Ahnung, ob ich das jetzt richtig verstanden habe, aber wozu sollte man das tun wollen? Das führt doch zu völlig unleserlichen Programmen. function ist ein reserviertes Schlüsselwort und als Funktionsname verboten.


    Naja, am schönsten, so rein intuitiv, wäre natürlich a*b+c*d, aber das würde den Parser verkomplizieren. Also nicht.

    Ich sehe schon, das wird eine sehr akademische Angelegenheit. Beliebt wird diese Sprache sicher nicht. ;)


    Und wenn das dann sowieso niemand wirklich benutzen wird, dann würde ich mir nicht die Mühe machen, das auf den 64er zu implementieren sondern als Proof of Concept auf dem PC.

  • Der Doppelpunkt hieße dann sinngemäß (also "semantisch", was nichts anderes heißt als "sinngemäß"): Finde im Scope die Definition, die mit dem Symbol "function" bezeichnet ist, und wende sie auf die Liste der Argumente an.

    Möglicherweise habe ich das Problem nicht richtig verstanden, aber üblicherweise wird das doch bereits durch die öffnende Klammer unterschieden? Eine generelle Präfix-Notation wäre vielleicht sauberer, aber in diesem Fall auch abschreckender - einfach weil ungebräuchlich.

    Kommt wohl auf die Sprache an. Ich glaube, in Python ist es fast so. Aber nicht ganz. Dort "meint" man mit dem Namen immer noch die Funktion, auch wenn man sie nicht aufruft. In diesem Fall (der Sprache die ich zu designen versuche) "meint" man mit dem Symbol das Symbol selbst. Aber du hast recht: Damit habe ich noch nicht unterschieden, ob ich sie aufrufen will. Bloß, dass ich sie meine. Wobei das nicht das Problem ist. Wenn keine Argumente da sind (leerer Stack), dann läuft die Funktion einfach nicht los, und bleibt, was sie ist.

  • Keine Ahnung, ob ich das jetzt richtig verstanden habe, aber wozu sollte man das tun wollen? Das führt doch zu völlig unleserlichen Programmen. function ist ein reserviertes Schlüsselwort und als Funktionsname verboten.

    Kommt auf die Sprache an. Ich hätt genausogut "Karl-Heinz" hinschreiben können. Oder "f". Irgendein Symbol halt. In Pascal ist das ein Schlüsselwort, stimmt. In meiner Sprache nicht.

    Ich sehe schon, das wird eine sehr akademische Angelegenheit. Beliebt wird diese Sprache sicher nicht. ;)


    Und wenn das dann sowieso niemand wirklich benutzen wird, dann würde ich mir nicht die Mühe machen, das auf den 64er zu implementieren sondern als Proof of Concept auf dem PC.

    Kann sein, dass sie nicht beliebt wird. Weiß ich nicht. Werde ich vielleicht irgendwann sehen. :D

    Aber mich fasziniert, ob ich sie so klein kriege, dass sie auf einem C64 läuft. Wenn sie dann keiner will - auch kein Problem.

  • Mach es doch wie Scheme:


    (+ 4 5)


    Der erste Ausdruck in der Klammer ist immer die Funktion.

    Das könnte ich machen, aber dann gibt es andere Features nicht, die ich sehr wohl gern hätte. Drum ist meine Sprache keine LISP-ähnliche. Ich möchte Funktionen durch hintereinanderschreiben kombinieren können. In LISP wäre das (f (g x)). Ich will f g als Ausdruck verstehen, der zuerst g auf irgendwas ausführt und dann f auf das Ergebnis. Dabei ist relevant, dass Funktionen beliebig viel zurückgeben können. Das macht sie FORTH ähnlich, nur dass dort die Reihenfolge umgekehrt wäre, also g f.


    LISP gibt es schon auf dem C64. Drum wäre es irgendwie fad, noch so eine ähnliche Sprache zu realisieren.

  • Kannst ja sowas wie ((combine f g) 2 3) schreiben.


    Wobei ich es gerade sympatisch bei Scheme finde, dass man viel weniger Funktionen als bei Common Lisp hat, und sich viele Dinge selbst erstmal definieren muss. Ähnlich wie bei C z.B.


    Du kannst ja immer sowas wie (define (meine_funktion x) (f (g x))) schreiben.


    Aber Du willst wohl sowas wie es z.B. in Lisp mit den c*r Konstrukten gibt, also (cadddr x) , oder so.

  • Wobei ich es gerade sympatisch bei Scheme finde, dass man viel weniger Funktionen als bei Common Lisp hat, und sich viele Dinge selbst erstmal definieren muss. Ähnlich wie bei C z.B.

    Wusste ich nicht. Scheint mir auch sympathisch.

    Du kannst ja immer sowas wie (define (meine_funktion x) (f (g x))) schreiben.

    Schon. Aber das erscheint mir etwas kompliziert. Bei mir ist das Standard-Semantik des Hintereinanderschreibens, und Funktion vor der Klammer, nicht drin. f(x), nicht (f x).

    Aber Du willst wohl sowas wie es z.B. in Lisp mit den c*r Konstrukten gibt, also (cadddr x) , oder so.

    Das ist viel genereller gemeint. Ein Beispiel:


    Code
    1. swap := (x,y)->(y,x)

    Was müsste bei swap swap rauskommen?


    Code
    1. (x,y)->(x,y)

    Der Interpreter müsste swap swap Funktion wegrationalisieren, wenn es irgendwo steht. Ich möchte das ermöglichen, dass die Komposition von Funktionen aus anderen Funktionen der Normalfall ist, inklusive mancher einfacher Optimierungen.