PHS: Meine Wunsch-Weiterentwicklung von PHP (Programmiersprache)

Dieses Thema im Forum "Webentwicklung" wurde erstellt von Murdoc, 1. November 2014 .

Schlagworte:
  1. 1. November 2014
    Zuletzt von einem Moderator bearbeitet: 14. April 2017
    Um was gehts?

    Wie manche vielleicht wissen arbeite ich seit geraumer Zeit an einer eigenen Programmiersprache die - zumindest für mich - PHP in vielen Dingen ablösen bzw. erleichtern soll.

    Ich habe die Sprache PHS genannt, weil ich der Meinung bin, dass PHP stinkt (PHP Sucks). Aber nicht weil PHP zu wenig kann oder die Syntax doof wäre, nein, wenn das der Fall gewesen wäre hätte ich meine Sprache direkt für die JVM oder HHVM konzipiert. Der Grund warum PHP stinkt ist (für mich) das alter der Sprache.

    Sicherlich werden immer mehr tolle Features in PHP implementiert (z.b. anonyme Funktionen oder vor Kurzem Variadic-Argumente), die die Sprache moderner erscheinen lassen, aber viele Dinge, die man erwarten würde können nicht mehr nachgereicht werden, weil diese nicht abwärtskompatibel wären.

    Daher meine Idee: Warum nicht einfach einen Compiler entwerfen, der diese Features alle nachrüstet?

    Für die Syntax und Semantik der Sprache habe ich mich von vielen anderen Sprachen inspirieren lassen.
    Namhafte Beispiele wären:

    • Rust
    • JavaScript
    • Java
    • Dart
    • C++ und D
    • Python

    Folgende Dinge hatte ich auf meinem Zettel:

    • Module statt Namensräume
    • Private Funktionen, Variablen und Klassen (...) in Dateien (wie es mittels "static" auch in C/C++ möglich ist)
    • Block-Geltungsbereiche *
    • Vom Compiler konstruiertes capturing von Variablen in Funktionen -> function() use($bar) { $bar; }
    • Funktionale Aspekte (Funktionen als Objekt statt als String übergeben können, Objekt-Eigenschaften ausführbar machen)
    • Festgelegte Klassenhierarchie (Standard Elternklasse wird implizit vererbt)
    • Objekte statt Assoziative Arrays
    • Fokus auf die aktuelle Entwicklung von PHP statt Altlasten nachzubauen

    * Block-Geltungsbereiche lassen sich in etwa so umschreiben:
    Bild


    Wie soll das funktionieren?

    Darüber habe ich wirklich lange Zeit nachgedacht. PHP kann alles was ich will, aber zwingt mir teilweise eine komische handhabe auf die man im Grunde gar nicht wirklich braucht wenn man den Code ein wenig inspiziert. Dieses verhalten wurde zwar mittlerweile geändert, aber das bringt nicht viel, da, wie bereits angesprochen, der Code immer noch abwärtskompatibel bleiben muss.

    Mein Compiler geht da ein paar Schritte weiter. Nach dem verarbeiten des Quelltextes wird ein Syntaxbaum erstellt, welcher anschließend in mehreren (teils recht aufwendigen) Schritten untersucht wird. Damit ist es möglich noch vor dem Ausführen des Codes viele wichtige Informationen zu sammeln, mit dessen Hilfe im nächsten Schritt optimierter und vor allem sinnvoller Code generiert werden kann.

    Folgendes wird (u.A.) bereits vor dem Ausführen bestimmt:

    • Kann auf eine Variable oder Funktion/Klasse ... zugegriffen werden? (wenn es 100% ersichtlich ist)
    • Welche Symbole werden in einer Funktion referenziert? (Welche Variablen müssen `eingefangen` werden)
    • Was wird mit den Symbolen gemacht (wird eine Funktion aufgerufen oder nur abgefragt?)
    • Macht parent::xyz() überhaupt Sinn? (existiert eine Elternklasse* und hat diese Zugriff auf eine Methode namens "xyz"?)
    • Wird eine Variable noch gebraucht?

    * Wie bereits erwähnt erben alle Klassen nun von einer Standardelternklasse, aber dazu später mehr


    Die Syntax

    Reservierte Wörter:

    Code:
    fn let use enum class trait iface module require
    true false null this super self get set
    do if elif else for try goto break continue throw
    catch finally while assert switch case default
    return print const final static extern
    public private protected
    __sealed__ __inline__ __global__ __php__
    __test__ __end__
    yield new del as is in 
    int integer bool boolean float double string
    __dir__ __file__ __line__ __coln__
    __fn__ __class__ __trait__ __method__ __module__
    Das sind auf den ersten Blick ggf. zu viele, aber daran arbeite ich noch.


    Funktionen deklarieren

    Code:
    fn func() {
     // weiterer code
    }

    Variablen deklarieren

    Code:
    let var = 1234;

    Klassen/Traits/Interfaces

    Code:
    iface Foo {
     fn method();
    }
    
    trait Bar {
     fn trait_method() {
     // weiterer code
     }
    }
    
    class Baz {
     fn func() {
     // weiterer code
     }
    }
    
    class Qrz: Baz ~ Foo {
     use Bar;
    
     let prop = 1234;
     
     new () {
     // constructor
     }
    
     del () {
     // destructor
     }
    
     fn method() {
     // weiterer code
     }
    }

    Weitere Beispiele folgen!


    Ein Problem mit Variablen

    Variablen in PHP werden so gut wie immer implizit erstellt (d.h. müssen nicht deklariert werden). Zudem kennt PHP nur zwei (ausgenommen Klassen/Objekt Kontext) Geltungsbereiche für Variablen: Global oder innerhalb einer Funktion.

    Dieses Verhalten war mir zu einfach gestrickt und zu unflexibel. Aus diesem Grund habe ich mich von Java/C/C++ und vielen anderen Programmiersprachen inspirieren lassen und Block-Geltungsbereiche implementiert, die es möglich machen Variablen nur für einen bestimmten Bereich im Speicher zu halten (fernab von Funktionen). Zudem müssen in meiner Sprache Variablen nun explizit deklariert werden, damit der jeweilige Geltungsbereich einwandfrei ersichtlich ist.

    Beispiel:

    Code:
    {
     // beginn: Block-Geltungsbereich
     let foo = 1;
     print foo;
    } // ende: Block-Geltungsbereich, `foo` wird an dieser Stelle gelöscht
    

    Funktionen

    Ein weiteres komisches Verhalten von PHP bekommt man zu spüren wenn man mit Funktionen arbeitet.
    PHP kennt zwei Arten von Funktionen: Normale Funktionen und Lambdas (aka. Anonyme Funktionen aka. Closures).

    Lediglich Lambdas eignen sich ohne Workaround zu funktionalen Programmierung. Dabei muss man neidlos anerkennen, dass die Implementation von Closures (Lambdas innerhalb von Methoden) gut durchdacht wurde und sich ohne weiteres zu tun sinnvoll verwenden lässt.

    Normale Funktionen hingegen lassen sich im Grunde nur aufrufen. Anderer Zugriff ist nur mittels Strings (der Name der Funktion als String) möglich.
    Zudem ist es nicht möglich Funktionen innerhalb von anderen Funktionen zu deklarieren, bzw. es ist möglich, aber die Funktion wird dann global deklariert, was nur einmalig funktioniert und das Ganze ad absurdum führt.

    Daher habe ich das Konzept von Funktionen komplett überdacht und, wie ich finde, eine gute Lösung gefunden:

    • Deklariert man eine globale Funktion erhält man eine normale Funktion (wie in PHP auch)
    • Deklariert man eine Funktion in einem Block (also auch innerhalb einer anderen Funktion) erhält man ein Lambda
    • Referenziert man eine normale Funktion lesend (z.b. als Argument für eine andere Funktion) erhält man automatisch dessen absoluten Namen als String *
    • Bei der Deklaration von Funktionen werden automatisch Referenzen innerhalb der Funktion aus dem gleichen oder übergeordneten Geltungsbereich erfasst (wie z.b. in JavaScript) - Der Compiler weiß an dieser Stelle was mit "use" und was mit "global" referenziert werden muss/kann/soll
    • Alle Funktionsdeklarationen sind automatisch konstant, egal ob der Compiler ein Lambda generiert hat oder nicht.

    * Selbes Prinzip trifft auch auf Klassen, Traits und Interfaces zu, aber dazu später mehr

    Ein weiteres Problem sind Closures als Eigenschaften von Objekten.
    PHP unterschiedet hier strikt zwischen Eigenschaften und Methoden und erlaubt lediglich den Zugriff auf Methoden wenn man die $obj->prop() Syntax verwendet. Ob hier ggf. eine Eigenschaft namens "prop" existiert ist dem PHP-Interpreter schlicht egal.

    Dieses Problem lässt sich mit einem Workaround lösen:

    PHP:
    $fn  $obj -> prop ;
    $fn ();
    Das wäre sicherlich auch möglich gewesen, aber sehr sehr schwer zu implementieren in einer dynamischen Sprache (welchen Typ hat $obj? Ist "prop" eine Methode?).
    Daher habe ich mich dazu entschlossen eine Standard-Elternklasse einzubauen, wie man sie auch von Java und JavaScript (und neuem Python) kennt. Diese Standard Klasse implementiert eine __call Methode genau für diesen Zweck.

    PHP:
    function  __call ( $prop $args ) {
      
    $func  $this ->{ $prop };
      return 
    $func (... $args );
    }
    Dem einen oder anderen fällt vielleicht auf, dass so keine Referenzen mehr übergeben werden können. Das stimmt und steht bereits auf meiner Todo-Liste. Dazu sei gesagt, dass der PHP-Workaround ohne Probleme auch in meiner Sprache funktioniert falls das ein Problem darstellen sollte.


    Namensräume

    Namensräume existieren nur im internen Code des Compilers in den Symboltabellen.

    Folgende Tabellen werden hierbei genutzt:
    • Module
    • Klassen, Traits und Interfaces
    • Funktionen und Variablen

    Jep, Funktionen und Variablen teilen sich den gleichen Namensraum.

    Diese Entscheidung lag auf der Hand als ich mit der Konzeption von Funktionen fertig war. Wenn der Compiler je nach Kontext eine normale Funktion oder ein Lambda generiert, macht es nicht viel Sinn diese beiden Symboltypen voneinander zu trennen - wie es PHP tut.

    Das hat zudem einen riesen Vorteil innerhalb von Klassen, denn wie bei normalen Funktionen kann PHP auch Methoden nicht lesend ansprechen, sondern nur aufrufen, es sei denn man nutzt auch hier einen Workaround:

    PHP:
    map ( $obj -> func );  // klappt nicht
    map ([  $this 'func'  ]);  // klappt
    Daher dachte ich mir: Wenn Funktionen und Variablen in meinem Compiler im selben Namensraum definiert werden, warum nicht einfach folgendes von Haus aus implementieren:

    PHP:
    class  Foo  {
      public 
    $func ;

      public function 
    __construct () {
        
    // implizit
        
    $this -> func  = [  $this 'func'  ];
      }

      public function 
    func () {
        echo 
    "hello world\n" ;
      }
    }

    $obj  = new  Foo ;
    $obj -> func ();  // methodenaufruf
    $func  $obj -> func // zugriff auf eigenschaft "func"
    $func ();  // selbes ergebnis wie $obj->func();

    Module

    Als Ersatz zu PHP "namespace" habe ich mir ein richtiges Modulsystem ausgedacht. Aber anstatt hier, wie in PHP, stumpf nur Klassen, Funktionen, Traits, Interfaces und "echten" Konstanten ein "Präfix" zu verpassen dachte ich einen Schritt weiter.

    Was ist mit Variablen? Und warum kann ich nicht angeben auf welche Symbole von außen (wie bei Klassen) zugegriffen werden darf?
    Module werden sowieso rein vom Compiler verwaltet, also warum bohren wir das Ganze nicht ein wenig auf!

    Daraus entstand ein Modulsystem das dem von JavaScript und Rust ähnelt:

    • Module können alle möglichen Symbole verwalten (inklusive Variablen)
    • Es ist möglich nur bestimmte Symbol zu "exportieren" (wie in Klassen)
    • Mittels "use" kann man alle möglichen Symbole referenzieren (auch Variablen)
    • Mittels "use" lassen sich Aliase definieren (ähnlich wie "export")

    Im Grunde kann man sich ein Modul wie eine große statische Klasse vorstellen.
    Zugriff auf Symbole erfolgt absolut (man spricht hier von "voll qualifiziert) oder mittels "use" (wie man es von PHP bereits kennt).

    Code:
    module foo {
     let bar = 1; // privat
     public baz = 2; // öffentlich
    
     public use self::baz as qrz; // öffentliches alias ("self" bezieht sich auf das modul)
     public use anderes_modul; // öffentliches alias auf ein anderes modul
     use noch_ein_anderes_modul; // privates alias auf ein anderes modul
    }
    
    foo::bar; // zugriff auf privates symbol -> fehlermeldung
    foo::baz; // okay
    foo::qrz; // entspricht foo::baz


    Das war es soweit, weiteres wird nach und nach ergänzt!

    Mich würde brennend interessieren was ihr davon haltet!
    Eine ausführliche Dokumentation wird erstellt wenn mein Compiler benutzbar ist (das Gröbste ist aber bereits implementiert!).

    Den Code kann man sich soweit bei Github ansehen:
    droptable/phs-lang · GitHub (Lines of Code derzeit: ~25.000)

    Eine erste Alpha-Version sollte in den nächsten Wochen folgen!
     
  2. 23. November 2014
    Zuletzt von einem Moderator bearbeitet: 14. April 2017
    AW: PHS: Meine Wunsch-Weiterentwicklung von PHP (Programmiersprache)

    Erste Alphaversion:

    Download: phsc.zip | www.xup.to

    Im Archiv befindet sich in phsc/bin eine Konsole für Windows (cmd-XX.bat) und die jeweiligen Shell-Skripte:

    phsc.bat für Windows
    phsc.sh für *nix

    Achtung: Der Compiler läuft mit PHP 5.5+, die erzeugten Skripte benötigen PHP 5.6+

    Zuerst könnt ihr testen ob der Compiler überhaupt läuft:

    Code:
    phsc -?
    Im Idealfall sollte eine Liste mit möglichen Optionen für den Compiler ausgegeben werden.
    Sollte das nicht funktionieren, dann müsst ihr zusätzlich noch PHP installieren (ich nutze /usr/bin/env um die PHP-Binary aufzulösen).

    Oder falls ihr mit Windows unterwegs seid:

    Falls kein XAMPP installiert ist oder was ähnliches:
    PHP For Windows: Binaries and sources Releases

    (TS oder NTS spielt keine Rolle).

    Die .zip runterladen und entpacken wo sie euch nicht weiter stört, aber nicht vergessen wo, denn:

    Den Pfad zur php.exe in die PATH Umgebungsvariable ausfnehmen:
    1. Klick auf [Start]
    2. Suche nach "System" bis es unter "Systemsteuerung" auftaucht und klick den Eintrag an
    3. "Erweiterte Einstellungen" in der Sidebar wählen
    4. Den Button "Umgebungsvariablen" klicken
    5. Falls unter "Benutzervariablen" kein Eintrag namens "Path" existiert (oder PATH) -> diesen anlegen
    6. Den Eintrag "Path" (oder auch PATH) bearbeiten
    7. Ganz an das Ende der Texteingabe ein Semikolon setzen [noparse](;)[/noparse] und dann den absoluten Pfad zur PHP.exe einfügen (ohne "php.exe" am Ende!)
    8. Das Fenster mit "Okay" schließen
    9. fertig


    Die Konsole schließen und neu öffnen!

    Sollte das Ganze immer noch nicht funktionieren lasst es mich wissen.

    Ein installer ist in Planung.


    Hello World

    Legt eine Datei namens "test.phs" (der Einfachheit halber in phsc/bin) an mit Inhalt:

    Code:
    print "hello world";
    Dann folgendes ausführen:

    Code:
    phsc -r -o test.phar test.phs
    Idealerweise sollte nun "hello world" in der Konsole ausgegeben werden

    Info:

    Die Option "-r" ist dazu da die übersetze Datei gleich auszuführen.
    Der Start der Datei dauert ein wenig, aber das ist normal.

    Um die Datei nur zu übersetzen einfach die "-r" Option weglassen und die erzeugte Datei selbst ausführen (ganz normal mit PHP), dann geht das ein wenig flotter.

    Code:
    phsc -o test.phar test.phs
    Code:
    php test.phar
    Einmal übersetzt ist die Datei ganz normal wie andere PHP-Skripte ausführbar (muss nicht erneut übersetzt werden).

    Webserver

    Wer das Ganze lieber von seinem Apachen ausgeliefert haben will muss das Skipt ein wenig anders übersetzen:

    Code:
    phsc -o test.phar --pack phar-web test.phs
    Die Option "--pack phar-web" erzeugt eine Webfähige PHAR-Datei:
    PHP: Phar::webPhar - Manual

    Anschließend die erzeugte Datei "test.phar" in den htdocs-Ordner kopieren und ganz normal aufrufen.
    Falls es nicht auf Anhieb funktioniert muss ggf. noch "phar" als PHP-Datei registriert werden, das geht so:

    Code:
    <FilesMatch "\.phar$">
     SetHandler application/x-httpd-php
    </FilesMatch>
    Es stehen noch weitere Modi zur Verfügung, z.b. "--pack zip" oder "--pack none", Infos dazu gibts mit dem Kommando:
    Code:
    phsc -?
     
  3. Video Script

    Videos zum Themenbereich

    * gefundene Videos auf YouTube, anhand der Überschrift.