- Der High C Compiler für FM TOWNS von 1989 bot über die Unterstützung der DOS-Umgebung hinaus mehrere benutzerorientierte Sprachfunktionen, die für C-Compiler jener Zeit ungewöhnlich waren.
- In Kombination mit Phar Laps DOS Extender wurde er im Entwicklungsfluss genutzt, der in einer 16-Bit-MS-DOS-Umgebung den 32-Bit-80386 ausnutzte, und wurde zum 1st-Party-C-Compiler von FM TOWNS.
- Unterstriche in Zahlenliteralen, Label-Argumente,
case-Bereiche, verschachtelte Funktionen und Generatoren sind Funktionen, die erst viel später in C/C++-Standards aufgenommen wurden oder bis heute nicht standardisiert sind. - Verschachtelte Funktionen boten in Form eines nicht-entkommenden Closures mit gemeinsam übergebenem Funktionszeiger und Kontextzeiger einen „full function value“ und waren damit ausdrucksstärker als gewöhnliche C-Funktionszeiger.
- Generatoren wurden als syntaktischer Zucker über verschachtelten Funktionen implementiert und funktionierten als einfache Struktur, bei der der Rumpf der
for-Schleife des Aufrufers in eine verschachtelte Funktion umgewandelt und alsyield-Argument übergeben wurde.
Die Rolle von FM TOWNS und High C
- Ein Handbuch für einen C-Compiler aus den 1980er-Jahren, das aus einem Stapel von FM-TOWNS-Büchern auftauchte, enthielt überraschend viele Spracherweiterungen.
- Um C und verwandte Sprachen in realen Umgebungen einzusetzen, waren lange Zeit Vendor-Erweiterungen nötig.
- In heutigen, von GCC, Clang und MSVC geprägten Umgebungen konzentrieren sich Erweiterungen meist auf plattformspezifische Verarbeitung oder Low-Level-Detailkontrolle.
- In den 1980er-Jahren konkurrierten mehr und kleinere Firmen um Verbreitung, daher waren auch die Erweiterungen vielfältiger.
- Phar Lap entwickelte einen der frühen DOS Extender, die in einer 16-Bit-MS-DOS-Umgebung die Nutzung des 32-Bit-80386-Prozessors ermöglichten.
- MetaWare portierte den High C Compiler im Auftrag von Phar Lap auf das DOS-Extender-SDK von Phar Lap.
- Fujitsu integrierte Phar Laps DOS Extender in das OS der auf dem 803386 basierenden FM TOWNS-Plattform, und High C wurde zum 1st-Party-C-Compiler dieser Plattform.
- FM TOWNS erschien 1989, unmittelbar vor der Ratifizierung von C89, dem ersten ANSI-C-Standard.
Kleine Komfortfunktionen, die ihrer Zeit voraus waren
-
Unterstrich-Trennzeichen in Zahlenliteralen
- Um lange Zahlenliterale besser lesbar zu machen, konnten innerhalb von Zahlen Unterstrich-Trennzeichen verwendet werden.
- C++ führte in C++14 Apostroph-Trennzeichen wie
1'000'000ein. - C führte eine ähnliche Funktion erst in C23 ein.
-
Label-Argumente
- Bei Funktionen mit vielen Parametern oder mit Typen wie
bool, deren Bedeutung an der Aufrufstelle schwer zu erkennen ist, konnten Argumentnamen angegeben werden. - Die Label-Argumente von High C funktionieren ähnlich wie ein populäres Feature von Python.
- Argument-Labels sind optional.
- Wenn Labels verwendet werden, können Argumente mit der Syntax
argumentName => valuein beliebiger Reihenfolge angegeben werden. - Argumente mit und ohne Label können gemischt werden, aber für alle Parameter der Funktion muss ein entsprechendes Argument vorhanden sein.
- In Standard-C und C++ gibt es diese Funktion bis heute nicht.
- Bei Funktionen mit vielen Parametern oder mit Typen wie
-
case-Bereiche- Wie Pascals
case low..highkonnte ein Wertebereich auf einmal abgeglichen werden. - Standard-C und C++ haben diese Funktion nicht übernommen.
- Wie Pascals
Verschachtelte Funktionen und full function value
- High C konnte wie Pascal verschachtelte Funktionen innerhalb von Funktionen deklarieren.
- Die Implementierung kam einer vollständigeren Form näher als Standard-Pascal oder die GCC-Erweiterung für verschachtelte Funktionen.
- High C konnte nicht nur verschachtelte Funktionsdeklarationen, sondern auch den Typ full function value deklarieren.
- Anders als ein traditioneller C-Funktionszeiger enthält er zusammen mit dem Funktionszeiger auch einen Kontextzeiger.
- Dadurch kann der von der verschachtelten Funktion eingefangene Kontext wiedergefunden werden.
- Es handelt sich um ein nicht-entkommendes Closure, dessen Lebensdauer nicht über die Rückkehr der äußeren Funktion hinausreicht.
- Die GCC-Erweiterung für verschachtelte Funktionen schrieb ausführbaren Code auf den Aufruf-Stack, um verschachtelte Funktionen als normale Funktionszeiger referenzieren zu können, und thunkte so den Kontextzeiger.
- Dieses Verfahren führte zu erheblichen Sicherheitsrisiken, weshalb viele Plattformen die Funktion vollständig deaktivierten.
- Lokale Funktionsreferenzen in High C lassen sich wie Werte erster Klasse verwenden, ihre Lebensdauer wird aber nicht über die Rückkehr der äußeren Funktion hinaus verlängert.
- Verschachtelte Funktionen konnten auch per
gotozur Elternfunktion springen.- Wie bei Smalltalk-Blöcken war ein nichtlokaler Ausstieg aus der verschachtelten Funktion möglich.
- Damit ließen sich Funktionen bauen, die sich wie Kontrollfluss verhalten.
- Objective-C erhielt 2009 mit Blocks Closures, die auch als escaping closures verwendet werden können, und C++ führte 2011 Lambdas ein.
- Beide Funktionen boten jedoch keinen nichtlokalen Ausstieg.
- Im Standard-C gibt es bis heute keine offizielle Funktion für verschachtelte Funktionen.
Generator-Koroutinen
- MetaWare betonte die Funktion Generatoren so stark, dass ihr ein ganzes Kapitel gewidmet wurde.
- High C unterstützte bereits 1989 Python-artige Generator-Koroutinen in plain C.
- Generatorfunktionen werden mit der Syntax
void foo(Arg arguments) -> (Yield yields)deklariert.- Innerhalb der Funktion kann die magische Funktion
yield(values...)mehrfach aufgerufen werden, um eine Sequenz von Werten zu erzeugen. - Der Aufrufer durchläuft die erzeugten Werte nacheinander mit einer neuen
for-Schleifensyntax der Formfor variable... <- foo(arguments...) do { ... }.
- Innerhalb der Funktion kann die magische Funktion
- Diese Implementierung konnte komplex mit verschachtelten Funktionen kombiniert werden.
- Verschachtelte Funktionen innerhalb eines Generators konnten das
yield-Verhalten des äußeren Generators capturen. - Eine verschachtelte Funktion konnte sich selbst rekursiv aufrufen, um Bäume oder rekursive Datenstrukturen zu traversieren und bei jedem Schritt
yieldauszuführen.
- Verschachtelte Funktionen innerhalb eines Generators konnten das
- Diese Form scheint sich in Python oder vielen Mainstream-Sprachen mit Generator-Koroutinen nur schwer umsetzen zu lassen.
Implementierung der Generatoren und Unterschiede zu Standardsprachen
- Die Generatoren von High C funktionieren ohne High-Level-Runtime als syntaktischer Zucker über verschachtelten Funktionen.
- Eine Generator-Deklaration der Form
void foo(Arg arguments) -> (Yield yields)ist äquivalent zu einer normalen Funktionsdeklarationvoid foo(void yield(Yield yields)!, Arg arguments).yieldist ein impliziter Parameter vom Typ „full function value“.- Ein Aufruf von
yield(values)im Generatorrumpf ist ein gewöhnlicher Funktionsaufruf dieses impliziten Funktionsparameters.
- Der Rumpf der
for-Schleife auf Seiten des Aufrufers wird in eine verschachtelte Funktion umgewandelt.- Diese verschachtelte Funktion wird als
yield-Argument an den Generator übergeben. - Die Struktur ist einfach, aber effektiv.
- Diese verschachtelte Funktion wird als
- Da verschachtelte Funktionen nichtlokale Ausstiege unterstützen, funktionieren auch
break,continueundgoto, die aus dem Rumpf derfor-Schleife herausführen, alsgotoan die passende Position außerhalb der Schleife. - Es ist unwahrscheinlich, dass Standard-C solche Funktionen integrieren wird.
- C++20 bietet eine sehr flexible und komplexe Koroutinen-Funktion auf Basis von Coroutine-Transformationen zur Compile-Zeit.
- Damit lassen sich vermutlich Generatoren implementieren.
- Das Ergebnis dürfte sich jedoch nicht so intuitiv mit lokalen Funktionen kombinieren lassen.
1 Kommentare
Hacker-News-Kommentare
forzusammengefasst. Es war schon damals eine der längst vergessenen Funktionen, und ich behandelte auch, wie es aussehen könnte, wenn es in den damaligen C++-Standard aufgenommen würde.Mit etwas Glück besitze ich ein Exemplar der englischen Ausgabe der High C/C++ Language Reference.
http://jdebp.uk./FGA/metaware-iterator-driven-for.html
http://jdebp.uk./Proposals/metaware-iterator-driven-for.html
breakoderreturnkompiliert wurden. Musste dieyield-Funktion in eine Form umgewandelt werden, die einen Statuscode zurückgibt, der dann an der Aufrufstelle geprüft wird?case-Bereiche, benannte Argumente, verschachtelte Funktionen, statische verschachtelte Funktionen und etwas Generator-Ähnliches.Möglich sind zum Beispiel Formen wie
int a = 1_234_567;,case 5 .. case 6:undtest(b:3, a:4);.Statische verschachtelte Funktionen können nicht auf Frame-Variablen der äußeren Funktion zugreifen, sodass Fehler wie
Error: static function test.foo.plus cannot access variable i in frame of function test.fooentstehen.Die generatorähnliche Funktion findet sich unter https://dlang.org/spec/statement.html#foreach_over_struct_an....
Wenn man zum Beispiel einen In-Memory-Cache-Dienst baut, ist es besser, wenn die Cache-Einträge selbst nicht vom Garbage Collector verfolgt werden. Der Garbage Collector kennt die tatsächlichen Zugriffsmuster oft nicht und stört deshalb eher. Für die meisten anderen Komponenten dieses Dienstes passt ein Garbage Collector aber besser.
Warum haben benannte Argumente die Form
test(a:4, b:3)und nichttest(.a=4, b.=3);?Außerdem frage ich mich, wie man in C First-Class-Typen handhaben könnte.
lcc-winOperator-Overloading, Standardargumente für Funktionen und Function Overloading hinzugefügt. In der Dokumentation siehe „generic functions“ [1].Auch der Plan-9-C-Compiler führte mehrere Spracherweiterungen ein; einige davon, etwa anonyme Structs/Unions, gingen später in den C-Standard ein. Heute akzeptiert GCC das Flag
-fplan9-extensions[2] und kann damit ziemlich nützliche Funktionen aktivieren, etwa die automatische Umwandlung von Struct-Pointern in anonyme Felder bei Funktionsaufrufen und Zuweisungen.[1] https://lcc-win32.services.net/C-Tutorial.pdf
[2] https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
Schade, dass sie sich nicht weltweit verbreitet und die Sprachstandards beeinflusst haben. Es ist erstaunlich, dass es solche Funktionen schon so früh gab.
Auf Hacker News wurde das früher schon einmal behandelt: https://news.ycombinator.com/item?id=38938402
Gibt es irgendwo eine PDF-Kopie?
for-Schleifen mit Generatoren undyield[0]. Die Icon-Sprache aus ähnlicher Zeit hatte ebenfalls ähnliche Generator-Funktionen [1] und verwendetesuspendfüryield. Soweit ich weiß, hatte auch Ada (1983) so etwas.Diese Sprachfunktionen waren also nicht völlig unbekannt.
[0] https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-2...
[1] https://dl.acm.org/doi/pdf/10.1145/800055.802034
Es beschreibt Unterstriche in Zahlen,
case-Bereiche, benannte Parameter, verschachtelte Funktionen und sogar vollwertige Funktionsvariablen.https://bitsavers.org/pdf/metaware/…
Siehe Appendix A, ungefähr 50 Seiten vor dem Dateiende.
Damals, als ich Code lernte und schrieb, bin ich über leicht dubiose Seiten auf sie gestoßen.
Man entdeckt dabei auch die reiche Geschichte der Systemprogrammiersprachen. Außerdem sieht man, wie ähnlich sich das Design von C und Go darin ist, dass es ignoriert hat, was in anderen Ökosystemen geschah und welche Erfahrungen es bereits gab.
Der Link zum Compiler-Handbuch ist unter https://winworldpc.com/product/metaware-high-c-cpp/33x zu finden
Das PDF des C-Handbuchs trägt einen Copyright-Hinweis von 2007
https://f.duriansoftware.com/@joe/113195961485703110
¥nstatt mit\nenden: Diese Codebeispiele scheinen in Shift-JIS geschrieben zu sein. In Shift-JIS steht an der Stelle des ASCII-\ein¥[0] https://en.wikipedia.org/wiki/JIS_X_0201
Für diesen Zweck ist EUC-JP besser, weil es dieses Problem nicht hat. In Pascal tritt dieses Problem mit Shift-JIS nicht auf, wenn man
(* *)-Kommentare verwendet und keine{ }-KommentareWahrscheinlicher ist stattdessen, dass JIS X 0201 (https://en.m.wikipedia.org.org/wiki/JIS_X_0201), die Grundlage von Shift-JIS, verwendet wurde
C:¥stattC:\Call (Param_A => 1, Param_B => "Foo");, Unterstriche in Zahlen beliebiger Basis (X : Integer := 1_000;), verschachtelte Subprogramme und bereichsbasierte PrüfungenMan vergisst offenbar oft, wie unglaublich primitiv C damals im Vergleich zu vielen anderen Sprachen war
Ich kenne mich mit japanischer Schreibweise oder Kerning-Regeln nicht gut genug aus, aber es sieht so aus, als hätte man eine Proportionalschrift mit sowohl Kanji als auch lateinischen Buchstaben genommen und sie gewaltsam in ein monospaced Raster gepresst
Immerhin ist es schön, dass die Codebeispiele nicht wie in vielen meiner Bücher in 8-pt-Schrift gesetzt sind
try_fold()denken (https://scribe.rip/@veedrac/rust-is-slow-and-i-am-the-cure-3...)Aber genau deshalb scheinen solche Erweiterungen relativ unbekannt geblieben zu sein und mussten Jahrzehnte später in modernem C/C++ wiederentdeckt und neu erfunden werden