www.linux-swt.de

Edwards JavaScript vom wohnungslosen Hartz-IV Empfänger seit 2012 Homepage

"number"
"number"
"string"
"object"
"object"
"boolean"
7 tests completed in : 146ms
EXCEPTION THROWN AT TEST 7
ReferenceError: Symbol ist noch nicht definiert oder von mir noch nicht gefunden worden.
    at Identifier (/l/syntax.js:4904:9)
    at ToValue (/l/syntax.js:5366:25)
    at CallExpression (/l/syntax.js:4807:25)
    at ToValue (/l/syntax.js:5366:25)
    at NewExpression (/l/syntax.js:5152:16)
    at ToValue (/l/syntax.js:5366:25)
    at VariableDeclaration (/l/syntax.js:4944:83)
    at ToValue (/l/syntax.js:5366:25)
    at Program (/l/syntax.js:5309:25)
    at ToValue (/l/syntax.js:5366:25)

Number of Tests: 7
Number of executed Assertions: 0
Number of passed Assertions: 0
Number of failed Assertions: 0
Number of unexpected Exceptions in Test: 1
EXCEPTION: Exception thrown at function () {
    var r = syn.toValue("var v = new Symbol(); type...: Symbol ist noch nicht definiert oder von mir noch nicht gefunden worden.

Lieber Gizmo. Mama hat dich nicht vergessen. Racker und Pünktchen deinen Käfig und die Decke. Papa dankt dir für all deine Liebe, die du ihm gegeben hast. Für all deine Aufmerksamkeit. Und die vielen Stunden. Wir lieben dich auch. Mama ganz doll. Du hast es geschafft. Ruhe in Frieden.

6.050 Information and Entropy

Fuer August habe ich bereits einen OCW Kurs gefunden: 6.050 Information and Entropy.

Bits of Informations, States und Bitrepresentations, und mehr, habe gestern abend die Intro was geguckt, das fing mit Bits an.. und mit schwarzen Loechern. Die Berechnungsmethoden dort werden fuer Berechnungen eingesetzt, die Veraenderungen beschreiben. Aber das ist noch keine richtige Auskunft. Seht selbst. ocw.mit.edu und guckt nach 6.050 Information und Entropy. Ich leeche jetzt erstmal das Kursmaterial, was dazugehoert. OCW hat tolles schriftliches Material, dem man nur nacheifern kann.

Ende der Juli Seite

Ok, auf einen neuen Monat..

Also auch, wenn ich nichts gemacht habe, ist der diff vom letzten zu diesem Monat immens und umfasst mehrere tausend Aenderungen. Ein Zwischendiff vor einigen Tagen und einigen davor zeigte das bereits. Ein aktueller Diff aus zwei minimierten Files ist zu unleserlich, vom Juni habe ich leider kein Backup mehr. Ich habe mal ein wc (wordcount) drauf ausgefuehrt...

   457   2213  69084 1
   556   2721  93006 2
  1013   4934 162090 total

Die Ausgabe von wc 1 (juni) und 2 (juli). Der Vergleich bezieht sich auf die minimierten Dateien. Wie man sieht, 23922 Bytes Zuwachs. Sowie 99 minimierte Zeilen. Ungepackt hat sich die Datei auf ueber 6000 Zeilen erweitert. Ein kleines Programm bislang, das weiss ich. Aber auch, wo ich nichts getan habe, hat sich was bewegt.

highlight

Gerade habe ich die highlight Funktion aus dem highlighter Paket extrahiert und die Funktion umgeschrieben, sowie die globale Leak-Map oder das Referenzen haltende all Objekt entfernt, das brauche ich nicht. Ich denke erstmal hier weiter zu machen, ich habe jetzt bestimmt 20 Minuten gebraucht.

Highlighter im Editor ausmachen!

Es gibt doch ein paar Tricks, um mit einem alten Rechner und einem modernen Editor zu arbeiten. Alle Features abschalten.

Ich programmiere die meiste Zeit mit mcedit, dem blauen Editor vom Midnight Commander. Vor 13 Jahren hatte ich emamcs-Befehle gelernt. Als ich letztes Jahr wieder programmieren lernen wollte, blieb ich direkt beim einfachsten Texteditor, weil der zum schreiben reicht. Ich denke, formatieren kann man nach dem Arbeiten.

Aber auch der ist mit Highlighter bei den (gerade mal) 6000 Zeilen von syntax.js bereits ueberfordert und braucht 15-20 Sekunden zum Starten. Schaltet man den Highlighter aus, startet er in 1-10ms.

Das habe ich gerade mit Sublime Text auch gemacht. Ein Super Editor, mit mehreren Cursorn, in den ich mich wegen den Schwierigkeiten mit dem Tempo immer noch nicht eingearbeitet habe, weil das einfache Tippen laenger dauert, als eine ganz niedrige Anschlagrate mit hohen Delay. Geradezu unertraeglich, auf einzelne Zeichen ewig warten zu muessen.

Ich hab in Sublime alles abgeschaltet, oder auch nicht und lese dabei die Konfig. Eclipse hatte ich auch mal, oder kdevelop, oder den QtDesigner hatte ich auch genutzt, ich glaube mit dem kriege ich noch GUI hin, die laeuft.

Dem MCEDIT mit Strg-S den Highlighter abzuschalten war eine Superidee. Ich kann wieder normal tippen.

Uebrigens, was mir Sorgen macht. Meine Festplatte braucht 4 Sekunden (!!!) zum hochspinnen. — -- Ich weiss nicht, wie lange die noch mitmacht.

Um die alte Hardware auszugleichen gilt nur eins: OPTIONEN ABSCHALTEN, WO ES GEHT.

isEscaped(string, charpos) Algorithmus Idee

Wenn ein Character gefluchtet wird, kann es sein, dass direkt davor eine Reihe von Backslashes stehen. Bei einer geraden Anzahl von Backslashes davor ist der Buchstabe nicht gefluchtet. Dann steht eine Reihe von gefluchteten Backslashes (Anzahl / 2). Ist eine ungerade Anzahl Backslashes davor, ist das Zeichen gefluchtet und alles davor sind gefluchtete Backslashes ((Anzahl-1) / 2). Man kann zum Beispiel mit Anzahl % 2 rausfinden, ob 1 Backslash uebrigbleibt, der das fluchtet.

Das besondere ist, dass er so wie er jetzt designt ist, nur slashes vor einem Zeichen erkennt, was kein Fluchtzeichen wie \n oder \r ist. Das Slash wird bereits entfernt.

Ein Algorithmus:

  1. Zaehle alle direkt aufeinanderfolgenden Backslashes vor dem Zeichen.
  2. Rechne der Rest ist gleich die Anzahl der Backslashes modulo 2
  3. Ist der Rest 1 return true (ist gefluchtet), ansonstet false (ist nicht gefluchtet.

Hinzu kommt zuerst die Bauernregel vom bereits interpretierten Zeichen:

Die bereits uebersetzte Sequenz kann naemlich nicht mehr weiter gefluchtet werden.

Die kompliziertere Variante aus syntax.js StringLiteral geht so, damit wird herausgefunden, ob die Quotes richtig gefluchtet ist, oder vor der letzten Quote Slashes stehen.

Auch hier sollte erstmal geguckt werden, ob der aktuelle Charakter nicht in einer Tabelle von EscapeSequenzen true zurueckwirft.

Fuer einen Stream von Zeichen geht er wie folgt.

  1. Wiederhole ab n = Charakterposition - 1 mit do
    • Ist string[n] kein Backslash, return false (nicht escaped)
    • Anderseits (string[n] ist im else-alternate-Block auf jeden Fall dann ein Backslash)
      • Ist string[n-1] ein Backslash aber string[n-2] keiner, return false (nicht escaped)
    • Solange string[n-=2] ein Backslash ist.
  2. return true, weil das zeichen gefluchtet sein muss, die nicht-Konditionen konnten nicht returnen.
    var InEscapedCharacters = { "\n": true, "\r":true, "\\": true, "\b":true, "\t":true, "\a":true };
    function isEscaped(string, charpos) {
	if (charpos === undefined) charpos = string.length - 1; // letztes Zeichen, _vor_ dem alle Slashes stehen
	if (InEscapedCharacters[string[charpos]]) return true;
	var n = charpos-1; // Gucke ein Zeichen davor
	do {
	    if (string[n] !== "\\") return false; // es ist nicht gefluchtet
	    else if (string[n-1] === "\\" && string[n-2] !== "\\") return false; // gefluchtet gewesen, aber dieses Zeichen hebt es auf
	} while (string[n-=2] === "\\"); // das dritte ist auch gefluchtet (wiederhole ab dem 3. von vorne)
	return true; 
    }

Ich denke mal, ein Count-Slashes ist intelligenter. Dennoch gibt es hier noch was zu gucken. Eine Escape-Character Tabelle sollte aufgesucht werden, denn Zeichen wie \n werden als nicht gefluchtet gesehen, weil sie direkt uebersetzt werden.

Ich muss mir das nochmal durchlesen, um das neu zu formulieren zu lernen.

(A)MD

Ach, und in defines statt in Iffies habe ich die einzelnen Module jetzt gewrappt, anstatt requireJS oder node machen das drei einfache Funktionen, die ein Object in require.cache[id] speichern, was aussieht wie ein Modul. Den Rest kann ich hinterher addieren, wenn man es jetzt in Modulloader laedt, laeuft es auch. Es gibt aber Abhaengigkeiten von unten nach oben. Das kleinste Modul ist tables, es enthaelt fast alle Hashtabellen. Dann folgt tokenize mit dem Splitter. Danach perser mit createSyntaxTree (sollte umbenannt werden). Danach kommen Klasse Platzhalter, der angefangene Interpreter, und dann der Highlighter mit DOM Gui, den Tabellen, Tokenizer, Parser, Interpreter. Hierbei sollte ich ueberlegen, highlight und highlightElements zu zweien zu machen und damit die DOM Gui nochmal von einem reinen HTML Textprozessor unterscheiden, dass man ihn so einsetzen kann, ohne den GUI Teil zu laden..Nebenbei gilt es Abhaengigkeiten zu tables zu reduzieren, vielleicht sollte ich WhiteSpaces oder LineTerminator oder Punctuators dort reinkopieren. Damit es ohne auskommt. Muss man ausprobieren. Anstatt meine alte Define und Module.js Geschichte aufzuwaermen, habe ich die Funktion jetzt um die bestehenden Iffies geschrieben. Man muss require noch einen Request einbauen, wenn das Modul nicht im Cache ist. Define arbeitet mit require um dependencies zu holen. require, exports, module gebe ich normal als Parameter. In NodeJS kann man exports auch nicht mit einem exports = function () loeschen, sondern muss module.exports nehmen. Liegt am Callbyvalue fuer exports. Ich denke, die Funktionen nun einfach in den Header der Homepageseite zu nehmen, um sie frueh zu definieren, so kann ich auch alle anderen Dateien einfach im AMD Format einbinden. Hinzufuegen muss man require noch den Request und den gibt es bereits und makePromise auch. Ausprobiert habe ich sie noch nicht. Ich glaube require("ajax").request oder .makePromise muesste jetzt sogar gehen.

Im naechsten Beispiel wird auffaellig dass der gefluchtete Slash noch nichtnun auch im RegularExpressionLiteral angekommen ist, den ich im StringLiteral verbaute. Ich gedenke, die Funktion zu generalisieren und den empfangenen String zurueckzulaufen um zu sagen, ob er valide escaped ist, oder ein Bruch faellig.

    var nameExp = /(?:([^\/]+)$)/g;  
    function basename(full) {
	return full.match(nameExp);
    }
    
    var dirExp = /^((?:\/[^\/]+)*\/)/g;  
    function basedir(full) {
	return full.match(dirExp);  
    }

    var extExp = /(\.[^\/]+)$/;  
    function baseext(full) {
	return full.match(extExp);
    }

    console.dir(basedir("/sdfsf/dfgd/dfgdfg/sdfsdf.html"));
    console.dir(basedir("/"));
    console.dir(basedir("/das/ist/ein/pfad"));
    console.dir(basedir("/das/ist/auch/ein/pfad.js"));

    console.dir(basename("/das/ist/ein/pfad"));
    console.dir(basename("/das/ist/auch/ein/pfad.js"));

    console.dir(baseext("/das/ist/auch/ein/pfad.js"));
    console.dir(baseext("/das/ist/ein/file.js"));
    console.dir(baseext("/das/sind/zwei.ein/file.html"));

Ich habe schon einmal damit angefangen, basename, basedir und baseext zu definieren. EcmaScript benoetigt aber einen URL Parser. Und fuer define und require ist noch der Ausdruck, der lexikalisch definierte require("string") sucht, zu addieren, das sind fuenf, von denen die drei base Funktionen durch das URL-Parser Objekt und entsprechende Felder ersetzt werden koennen.

[ '/sdfsf/dfgd/dfgdfg/' ]
[ '/' ]
[ '/das/ist/ein/' ]
[ '/das/ist/auch/ein/' ]

[ 'pfad' ]
[ 'pfad.js' ]

[ '.js', '.js', index: 22, input: '/das/ist/auch/ein/pfad.js' ]
[ '.js', '.js', index: 17, input: '/das/ist/ein/file.js' ]
[ '.html',
  '.html',
  index: 23,
  input: '/das/sind/zwei.ein/file.html' ]

Statische Analyse von require

Hier ein Ausdruck und eine Funktion, um nach require(string) im Code zu gucken. In der AMD wird das dazu benutzt, um diese Module zu prefetchen. Sie auf die Deps Liste zu setzen, wenn sie nicht bei define(id, [deps], fact) oder require([deps], fact) unter den deps stehen.

    var staticRequireExp = /(?:require\("([\.\/\w\_\-]*)"\))/g;
    
    function findStaticRequires (fn_tostring) {
	var deps = [],
	match;
	do { 
	    match = staticRequireExp.exec(fn_tostring);
	    if (match && match[1]) deps.push(match[1]);
	} while (match);
	return deps;
    }

    var test = function () {
	var j = require("joghurt");
	var k = require("kaese");
	var d = require("drugs");
	var e = require("exams");
	var m = require("mottenkugeln");
    };
    
    console.dir(findStaticRequires(""+test));

Der Ausdruck findet die auch. Er ist von letztem Jahr, von meinem module.js.

    
    > n findrq.js
    [ 'joghurt', 'kaese', 'drugs', 'exams', 'mottenkugeln' ]

Bytecode Heap

Am meisten denke ich aber darueber nach, wie ich den ByteCode, den ich erzeugen will formatiere und abspiele.

Tokenizer

Update: Da es im Draft genau 3x das Wort InputElementRegExp gibt, und in der Grammatik von Teramako das Symbol nur bei MemberExpressions (Parser Stage) auftritt und die einzige direkte Regel: "InputElementDiv ist das Default Symbol", und "sollte ein LT eine Expression unterbrechen, kein Semikolon da stehen und der Slash das erste Zeichen sein, wird eine Division angenommen" kann ich mir gerade nicht vorstellen, wie ich das machen will. Ich bei lastTokenType und NotFollowedByRegExp[lastTokenType] geblieben. stack, pushState, und prev(), sowie lastch und ch3 habe ich entfernt und ch2 in lookahead umbenannt, WhiteSpaces sind jetzt einzelne Token statt zusammengefasst.

Dem Highlighter werde ich all[id] entfernen und live-reinterpretation statt Megabytes Speicherbedarf fuer alle Texte und Elemente vorziehen. Die Funktionen werden ja auch schneller.

Ich habe mich jetzt den Juli über regelmässig mit dem Programm sowie hauptsächlich dem Standard Draft beschäftigt. Nicht so viele Stunden, aber regelmässig. Ich habe wieder einen gewissen Überblick erhalten. Könnte aber kritisieren, mehr machen zu müßen. So habe ich vor zwei Wochen schon erkannt, wie ich das folgende Problemchen angehen muss, mich bislang aber nicht rangesetzt.

Um mich von DivPunctuator() in gemischter Form zu trennen, sowie vom Abspeichern des letzten Tokentyps, wofuer ich jedes Token eine AssignmentExpression extra zahle, denke ich, es mit einer Zustandsvariable zu regeln. Die wird nur in bestimmten Faellen gesetzt oder verglichen und entspricht der eigentlichen Grammatik sehr. Zur Vereinfachung des Vergleichs (goalSymbol === "InputElementDiv" ist teurer als goalSymbol === 1) habe ich die Zahlen in leserlicher Form vermerkt, werde sie aber als Zahl einsetzen um das Aufloesen des Identifiers ebenfalls einzusparen. Ich muss noch die Stellen im Draft finden, wo das Goal Symbol nicht 1 ist. (Weil es das Default Symbol ist und daher immer, ausser wenn genannt, richtig ist.) Dort muss ich Zuweisen und vor dem naechsten Lauf wieder auf 1 zurueckstellen.

    var InputElementDiv = 1;
    var InputElementRegExp = 2;
    var InputElementTemplateTail = 3;
    var goalSymbol = InputElementDiv;
    

Wenn das ZielSymbol geaendert wird, aendert sich die Belegung des / DivPunctuators. So brauch ich nicht den Typen des letzten Tokens zu speichern um zu vergleichen, welche Terminals erlaubt sind.

Das andere ist, ich habe prev() sowie pushState() zu loeschen. Als ich im Januar eine Hilfe wie diese einbaute, ging es glaube ich, sogar um den RegExp und zum anderen darum, fuer den Highlighter nicht abzustuerzen. Da hatte ich weder die Grammatik richtig gelesen, ich fing damit gerade erst an, noch Übung im Schreiben solcher Funktionen. Ausserdem habe ich ch, ch2, last und forelast definiert, brauche aber nur ch und lookahead (ch2). In Punctuator kann man ch3 und ch4 hinzunehmen, um erstmal bis zum >>>= Rechenzeichen zu pruefen. Aber der Tokenizer sollte nur einen character und lookahead haben, mehr braucht man nicht.

Die Funktion prev() braucht man nicht. Und ein Stack ist bei der EcmaScript Grammatik im Tokenizer auf keinen Fall notwendig. Auf keinen Fall.

Wenn eine Division erlaubt ist, und die Zeile mit einem LineTerminator gebrochen wird und das erste Zeichen ein Slash ist, wird es auch als Division interpretiert, auch wenn es einem Regulaeren Ausdruck gehoert. So in 7 Lexical Conventions

Statische semantische Regeln

Im Folgekapitel 5.3 erfaehrt man, dass kontextfreie Grammatiken alleine nicht ausreichen, um ein valides oder invalides Skript zu identifizieren. In einigen Fällen braucht man zusätzliche Regeln, die durch entweder Algorithmische Konventionen oder Anforderungen ausgedrückt werden. Diese Regeln, die immer mit einer Produktion der Grammatik assoziiert sind, nennt man statische Semantik der Produktion.

Sie haben Namen und werden typischerweise mit einem Algorithmus ausgedrueckt. Wenn eine Produktion mehrere alternative Definitionen hat, werden ebenfalls fuer jede Alternative andere statisch semantische Regeln beschrieben.

Die Standardregel Contains liefert true oder false zurueck und wird wie folgt definiert:

  1. Für jedes terminal oder non-terminal Symbol, sym, dieser Produktion
    1. Falls sym das gleiche Symbol wie symbol ist, return true
    2. Falls sym ein non-terminal ist
      1. Lasse contained das resultat von Contains(sym, symbol) sein
      2. Falls contained true ist, returne true
  2. return false

Anmerkung von mir: Wer einen einfachen Algorithmus fuer CFGs sucht, hat ihn hier gefunden. "Wenn Symbol das Terminal matcht, true zurueckgeben. Trifft man auf ein Non-Terminal, was aus mehreren Terminals und Non-Terminals besteht, rekursiv vorgehen. Den habe ich auch schon immer mal lesen wollen.

 
	    function Contains(symbol, sym) {
		if (Array.isArray(symbol)) {
		    for (var i=0, j=symbol.length; i < j; i++) 
			if (Contains(symbol[i], sym)) return true;
		} else if (sym === symbol) return true;
		return false;
	    }
	

Eine spezielle Form der sematischen Regeln sind der Early Error Rules, die frühe Fehlerbedingungen zu spezifischen Produktionen definieren. Eine konforme Implementation muss vor der Evaluierung des Skripts alle Early Errors validieren. Sollte eine der Regeln verletzt werden ist das Skript invalid.

Anmerkung von mir. Ein Beispiel fuer diese Early Errors ist in der Funktion StringLiteral der SyntaxError. Der Spezifikationstext haelt am Ende jedes Kapitels, was von der Grammatik handelt entsprechende Regeln fest. Sie sind dort und static semantics sowie mit early errors angegeben. Genau diese Regeln muss man haeufiger lesen, um sie sich merken zu koennen, auch wenn man sie aus der Praxis eigentlich schon kennt.

floor(x)

In Kapitel 5.2 Algorithmic Conventions sind nach dem Hinweis, wie Schritte und Unterschritte geschrieben (1. Step, a. Substep und i. SubSubStep wdh.) und interpretiert (if/else) werden, ein paar Tips für Standard Funktionen zu lesen. Standardfunktionen, die man auswendig koennen sollte, da sie alltägliche mathematische Operationen beschreiben.

NOTE floor(x) = x - (x modulo 1)

Bei floor wird also der Rest von einer Teilung durch 1 abgezogen. Darauf dachte ich, bei ceil wird dann noch eins addiert. Und abs gibt -x bei negativem x und x bei positivem x zurueck. Die sign(x) Funktion wird im EcmaScript Standard nicht für den Fall 0 eingesetzt. (

    function floor(x) {
	return x - (x % 1);
    }
    
    function ceil(x) {
	return x - (x % 1) + 1;
    }
    
    function abs(x) {
	return x < 0 ? -x : x;
    }
    
    function sign(x) {
	return x < 0 ? -1 : 1;
    }

    function min () {
	var min = Infinity;
	var n;
	for (var i=0, j=arguments.length; i < j; i++) 
	    if ((n=arguments[i]) < min) min = n;
	return min;
    }

    function max () {
	var max = -Infinity;
	var n;
	for (var i=0, j=arguments.length; i < j; i++) 
	    if ((n=arguments[i]) > max) max = n;
	return max;
    }


    floor(12.5) === 12;
    ceil(12.5) === 13;
    abs(-3) === 3;
    abs(3) === 3;
    sign(5) === 1;
    sign(-5) === -1;
    min(3,1,2) === 1;
    max(1,3,2) === 3;
    

Funktionen, die man auswendig koennen sollte.

Out of order

Das Programm in dem Highlighter. Ich bin beim Umschreiben theoretisch ja schon weiter als praktisch, weil ich die Definitionen ja lesen muß und abends erschöpft und schläfrig.

Ich hab das Programm aus Versehen ueberspielt. Die "Engine" ist erstmal ausser Betrieb.

Update: Noch ist der AST inkompatibel zur Mozilla Parser API, aber nur mit geringeren Abweichungen. Heute habe ich meine PSB drum gebeten, ihn drucken zu duerfen. Jetzt studiere ich gluecklich die Unterschiede zu dem syntax.js AST und kann die Interfaces ablesen, um sie zu korrigieren.

Gestern abend habe ich dann noch das StringLiteral nach einem halben Jahr endlich mal wieder editiert. Ich war nach 2-5 Minuten fertig. Nein, nicht ganz. Etwas später habe ich um unlimited Backslashes sowie das korrekte Auffangen der gethrowten SyntaxErrors bei ploetzlichem Zeilenende erweitert.

// ALT: Januar 2013 (mit illegal durchgehen lassen fuer den da gerade begonnenen Highlighter)

            function StringLiteral() {
                var quotecharacter;
                var string = "";
                var multilinestring = false;
                var illegal;
                var pos;
                if (ch == '"' || ch == "'") {
                    pos = i;
                    quotecharacter = ch;
                    string += ch;
                    next();
                    while (ch !== undefined) {
                        if (ch === "\n") nextLine();
                        if (LineTerminators[ch2]) {
                            if (!multilinestring) {
                                if (ch === "\\") { // \
                                    multilinestring = true;
                                    //    ch = ""; // kurz fassen und die \\ mit \n ersetzen :-)
                                } else {
                                    multilinestring = false;
                                    illegal = true;
                                    while (ch !== quotecharacter && i > pos) {
                                        prev();
                                        string = source.substring(pos, i);
                                    }
                                    break;
                                }
                            } else if (ch == "\\") {
                                //    ch = "";
                            }
                        }
                        if (ch === quotecharacter && lastch !== "\\") break;
                        string += ch;
                        next();
                    }
                    string += ch;
                    // if (illegal) throw new SyntaxError("Unexpected token ILLEGAL");
                    return pushtoken("StringLiteral", string, string.substr(1, string.length - 2));
                }
                return false;
            }
            

Gestern abends. Ein halbes Jahr später hat das StringLiteral praktisch eine ausgereifte Form erreicht.

// NEU (29. Juli)
	    function validBackslashes(string, n) {
		
	    }

            function StringLiteral() {
                var quotecharacter;
                var string = "";
                var multiline = false;
                var n;
                if (Quotes[ch]) {
            	    quotecharacter = ch;
            	    string += ch;
            	    big:
		    while (next()) {
			string += ch;
			if (ch === quotecharacter) {
			    n = string.length -2;
			    do {	
				if (string[n] !== "\\") break big;
				else if (string[n-1] === "\\" && string[n-2] !== "\\") break big; 
			    } while (string[n-=2] == "\\");
			}
			if (LineTerminators[ch2] {
			    n = string.length-1;
			    while (string[n] === "\\") {
				if  (string[n-1] === "\\") { 
				    multiline = false; 
				    n-=2;
				} else {
				    multiline = true; 
				    nextLine();
				    break; 
				}
			    } 
			}
			if (LineTerminators[ch]) {
			    if  (!multiline) throw new SyntaxError("Unexpected token ILLEGAL");
			    else multiline = false;
			}
		    }
                    return pushtoken("StringLiteral", string, string.substr(1, string.length - 2));
                }
                return false;
            }

Jetzt probiere ich das Ding nochmal aus. Danach erfinde ich kurz das try-catch um die highlight Text Funktion, um die Exception anzeigen zu koennen (*), denke aber, auch hier noch was generischer werden zu koennen, um Fehler benutzerdefiniert zu behandeln (anzuzeigen).

(*) Das Auffangen des SyntaxErrors ebnet den Weg, hinterher auch jeden anderen early Error anzuzeigen. Womit der Highlighter zur Hilfe wird, die Typos in Tutorials zu finden, anstatt mit abzustuerzen.


var s = "123\\34\\224\\\242\"\\";
var s2 = "\\\"\\\"\\\\\"\\\\\\\"\\\"";
var s3 = "av\\\\\\\\\\\\\\\\\\\\\"";
var s4 = "av\\\\\\\\\\\\\\\\\\\\\"\\\\\"''";

/* 
    Jetzt benoetigt der Tokenizer einen Error Callback, der anstelle 
    eines einfachen throws gerufen werden kann, damit die Fehlermeldungen
    in das Dokument weitergegeben werden koennen.
*/ 

let ach = "Dann hab ich vergessen,\
den hier auszuprobieren. Hatte ES5 jetzt double slashes oder einen wie C?\
Ein Space steht jedenfalls nicht hinter dem\
und es sollte kein Syntax Error sein.\
Ich habe Programming Style and your Brain gesehen. Auch wenn es das nicht..";

let vergessen = "Ich will jetzt nur wissen, ob das geht\
denn den Error Callback habe ich noch nicht, dann staend hier\
Syntax Error Und der TokenStream waer zuende oder NUR die Message\
kommt per Callback.";

Man habe ich gestern abend hier lange Absaetze geschrieben. Ich probiere jetzt nochmal kurz die Fehlerfaelle aus. Ein Fuzzy Test wuerde das gleiche ergeben, denke ich.

var error1 = "sdfsdfsdfsdf
sdfsdfsdfsfd";

und

var error2 = "\";

wiederholen

var good3 = "fdfd\\\\\
gdfgdfg\\\
fsdfsdfsfsdfsdf\
sfsdfsd\\\
sdfsdf";

des trivialen backslashloops

var bad4 = "sfsfsdfsdf\
sdfsdfsdfsdfsdfsdf\\
sdfsdfsdf\
sdfsff";

auch ein space muss floppen.

var bad5 = "space hinter dem slash gag\ 
der sollte ein syntax error sein.";

Als ich beim ersten Edit (2-5 Minuten) von StringLiteral noch in "\\" rannte und die Backslashes rekursiv zaehlen musste, musste ich mir die Loops dazu neu ausdenken. Was eher 10-15 Minuten extra machte, mit ausprobieren und nochmal editieren, weil bad4 zeigte, das jede Zeile auf false zu setzen ist. Vielleicht kann man das noch optimieren.

asm.js

Ein striktes JavaScript Subset, was für Compiler gedacht ist, und dazu genutzt wird, C++ Spiele mit Emscripten und LLVM nach JavaScript und WebGL zu konvertieren.

Bei Brendan Eichs Keynote zur Fluent fiel mir das | (or) schon auf, er erklaerte kurz das Memory Model fuer den Heap, in der Spec zu ASM.js kann man das was er sagte nochmal lesen. Er hat es also kurz aus Erfahrung erklaert.

linux-qc59:~ # n
> var x = 1, y;
undefined
> (x+y)|0
0
> (x+y)
NaN
> (x+y)|0
0
> (x+y)
NaN
> (x+y)|0
0
> y = 12;
12
> (x+y)|0
13

Interessant war jetzt, dass man mit HEAP32[p>>2]|0 nicht nur im Int32Array einen 32-bit Integer an offset p ansprechen kann (aus dem draft, der shift konvertiert das offset p in ein 32-bit element offset), sondern mit |0 (sprich: bitwise coercion) dafuer gesorgt wird, dass ein moeglicherweise falsches Ergebniss (undefined, NaN) wieder in eine Integer Zahl umgewandelt wird.

Ich habe in dem Beispiel Listing nur kurz ausprobiert, ob |0 in einen Integer umwandelt. Und das ist richtig.

Grund fuer meine ASM.js Begutachtung ist die Tatsache, dass es sich um ein hochperformantes JS Subset fuer Compiler handelt, und auch wenn be davor warnt, es von Hand zu benutzen und es fuer Compiler ist, man was raus lernen kann. Und nicht nur, dass man sich mit |0 vor NaN "schuetzen" kann. Ich denke aber nicht daran, jetzt asm.js auszuprobieren, sondern die Konzepte verstehen zu lernen. Nach dem Besuch der MIT Algorithmen Klassen und 6.172 Performance Engineering und den anderen Kursen, liegt mir was daran, auch optimierter zu programmieren. Was in der Entwicklungsphase nicht immer beruecksichtigt werden kann, weil man es nicht kann, aber nachtraeglich kann alles ausgearbeitet und berichtigt werden, dass man optimalen Code erreicht.

HTML5

Man sollte auch das hier bevorzugt lesen, wenn man von dem anderen Thema kurzfristig keine neuen Informationen aufnehmen kann. Interessant sind da natuerlich die adjazenten Dokumente.

http://www.whatwg.org/specs

pushState crawlbar!

Als ich auf einen JavaScript lately Podcast stiess, handelte eine Folge davon, dass HTML5 Ajax Apps, die die History API nutzen, bei Google und Bing jetzt crawlbar sind. Technische Details kenne ich nicht. Den Podcast habe ich auch nicht gesehen. Was mich daran gehindert hatte, die Dokumente alle mit new XMLHttpRequest; und history.pushState({data:".."}, null, "/erscheinender_name.html");, replaceState und "onpopstate", wo man den ersten Parameter {data:".."} in event.state findet, da kann man also was mitspeichern, was man so an Registerflags braucht, war eben der Punkt, dass all die Schnipsel nicht mehr indiziert werden.

<main> Element

HTML5 ist auch ein lebendiger Standard. Und wenn wer sich ausgiebig damit beschäftigt, sich auskennen lernt, und durch ausprobieren und erfahren weiss, was ist, wenn, der kann auch dazu beitragen, daß dieser Standard weiter entwickelt und verbessert wird.

Darauf gebracht hat mich das kurze Lesen eines Artikels, welcher das neue main Element beschreibt, http://html5doctor.com/the-main-element.

es-discuss

Man sollte immer einen Link zu es-discuss auf seiner Seite haben.

Nicht ablenken lassen

Die 478 Seiten ES6 sind schoen anstrengend zu lernen. Ich habe alle mal überflogen. Ich bin immernoch bei den ersten 100.


Dennoch gibt es was zu sehen. Auf es-discuss habe ich von Function.setTypeOf(V,U). KeineLeise Ahnung was er mit user defined types meint, dass man selbst types definieren kann. Von dem Thema gibt es mehrere Mails, sowie einen Ausschnitt aus 11.4.3 (typeof operator, was mit dem Hinweis, man sollte keine eigenen Typen definieren, sondern, wenn es geht, object waehlen, endet). Bei Function.setTypeOf(N,V) geht es wohl bereits um ES7. http://www.slideshare.net/BrendanEich/value-objects

Wenn syntax.js schon fertig waere, koennte ich sowas bereits ausprobieren. Manchmal träume ich schon davon.

[[Call]]

Ich habe zwar schon wieder ein gutes Stueck geschafft, aber eigentlich wollte ich diese Funktion schreiben.

8.3.15.1 [[Call]] ( thisArgument, argumentsList)

The [[Call]] internal method for an ordinary Function object F is called with parameters thisArgument and argumentsList, a List of ECMAScript language values. The following steps are taken:

    Let callerContext be the running execution context.
    If, callerContext is not already suspended, then Suspend callerContext.
    Let calleeContext be a new ECMAScript Code execution context.
    Let calleeRealm be the value of F’s [[Realm]] internal data property.
    Set calleeContext’s Realm to calleeRealm.
    Let thisMode be the value of F’s [[ThisMode]] internal data property.
    If thisMode is lexical, then
        Let localEnv be the result of calling NewDeclarativeEnvironment passing the value of the [[Scope]] internal data property of F as the argument.
    Else,
        If thisMode is strict, set thisValue to thisArgument.
        Else
            if thisArgument is null or undefined, then
                Set thisValue to calleeRealm.[[globalThis]].
            Else if Type(thisArgument) is not Object, set the thisValue to ToObject(thisArgument).
            Else set the thisValue to thisArgument.
        Let localEnv be the result of calling NewFunctionEnvironment passing F and thisValue as the arguments.
    Set the LexicalEnvironment of calleeContext to localEnv.
    Set the VariableEnvironment of calleeContext to localEnv.
    Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
    Let status be the result of performing Function Declaration Instantiation using the function F, argumentsList , and localEnv as described in 10.5.3.
    If status is an abrupt completion, then
        Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
        Return status.
    Let result be the result of EvaluateBody of the production that is the value of F's [[Code]] internal data property passing F as the argument.
    Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
    Return result.

NOTE 1 Most ordinary functions use a Function Environment Record as their LexicalEnvironment. Ordinary functions that are arrow functions use a Declarative Environment Record as their LexicalEnvironment.

NOTE 2 When calleeContext is removed from the execution context stack it must not be destroyed because it may have been suspended and retained by a generator object for later resumption.

Ein anderer Satz weist darauf hin, das FunctionEnvironment Ordinary Objects sind bis auf einige Spezialisierungen und besondere Eigenschaften. Ja, die Hierarchien der und die Beziehungen zwischen den Objekten muss man mehrmals einueben, dann klappt das auch.

Gestern

Ok, ich breche dann mal den kompletten Code auf, und schreibe alles neu, bis es wieder laeuft. Und weil ich gleich einschlafe, und dann wieder zu meinem Schatz rueberkrieche und weiterschnarche, mache ich das morgen weiter.

8 tests completed in : 194ms
EXCEPTION THROWN AT TEST 4
TypeError: Cannot call method 'Call' of undefined
    at CallExpression (/l/syntax.js:4378:19)
    at ToValue (/l/syntax.js:4977:32)
    at Program (/l/syntax.js:4890:29)
    at ToValue (/l/syntax.js:4977:32)
    at ToValue2 (/l/syntax.js:4988:24)
    at null. (/l/ftest.js:26:17)
    at /l/tester.js:167:15
    at Array.forEach (native)
    at run_tests [as run] (/l/tester.js:163:16)
    at Object. (/l/ftest.js:51:6)
EXCEPTION THROWN AT TEST 5
ReferenceError: g ist noch nicht definiert oder von mir noch nicht gefunden worden.
    at Identifier (/l/syntax.js:4489:9)
    at ToValue (/l/syntax.js:4977:32)
    at CallExpression (/l/syntax.js:4374:25)
    at BinaryExpression (/l/syntax.js:4676:52)
    at ToValue (/l/syntax.js:4977:32)
    at ReturnStatement (/l/syntax.js:4412:49)
    at ToValue (/l/syntax.js:4977:32)
    at Program (/l/syntax.js:4890:29)
    at ToValue (/l/syntax.js:4977:32)
    at ToValue2 (/l/syntax.js:4988:24)
EXCEPTION THROWN AT TEST 8
ReferenceError: first ist noch nicht definiert oder von mir noch nicht gefunden worden.
    at Identifier (/l/syntax.js:4489:9)
    at BinaryExpression (/l/syntax.js:4675:50)
    at ToValue (/l/syntax.js:4977:32)
    at ReturnStatement (/l/syntax.js:4412:49)
    at ToValue (/l/syntax.js:4977:32)
    at Object.Call (/l/syntax.js:4357:70)
    at CallExpression (/l/syntax.js:4378:19)
    at ToValue (/l/syntax.js:4977:32)
    at Program (/l/syntax.js:4890:29)
    at ToValue (/l/syntax.js:4977:32)

Number of Tests: 8
Number of executed Assertions: 5
Number of passed Assertions: 5
Number of failed Assertions: 0
Number of unexpected Exceptions in Test: 3
PASS: assert: actual=Edward, expected=Edward: message=Einfaches Identifier argument.
PASS: deepEquals: actual=[3,4,5,], expected=[3,4,5,]: message=f. f(...rest) (ohne ;) returns rest
PASS: deepEquals: actual=[3,4,5,], expected=[3,4,5,]: message=f. f(...rest) (mit ;) returns rest
EXCEPTION: Exception thrown at function (test) {
    var src = "var object = { name: 'Edward Ger...: Cannot call method 'Call' of undefined
EXCEPTION: Exception thrown at function (test) {
    var r = syn.toValue("function f() { functio...: g ist noch nicht definiert oder von mir noch nicht gefunden worden.
PASS: deepEquals: actual=[1,2,3,], expected=[1,2,3,]: message=default parameters returned
PASS: equals: actual=0, expected=0: message=f(a) returns 0 and is == string 0
EXCEPTION: Exception thrown at function (test) {
    var r = syn.toValue("function f( {first, la...: first ist noch nicht definiert oder von mir noch nicht gefunden worden.

Default Parameter funktionieren bereits. Sowie das ReturnStatement, oder die FunctionEnvironment. Ein Completion Objekt wird dabei verwendet. Das ist doch ein Anfang. Ich wunderte mich, als ich den Ordinary Object Test sah. Denn dabei handelt es sich wie bei der FunctionEnvironment bereits um eine eigene Struktur und nicht um ein blosses Objektliteral, was angesteuert wird. Jetzt muss ich mich um das BindingPattern kuemmern, ich muss nochmal notieren, wie ich den ASt gestaltet hatte.

    console.log(JSON.stringify(syntaxjs.createAst("let {first, last}={first:'Edward',last:'Gerhold'};"), null, 4));

Um den Satz wieder zum Laufen zu kriegen, muss ich die AssignmentExpression erstmal ebenfalls auf die EnvironmentRecords umschreiben.

[[Call]]

    if (p.type === "Identifier") {
	v.CreateMutableBinding(p.value);
	v.InitialiseBinding(p.value, argList[i]);
    }

Ich wusste, dass die [[Call]]-aehnliche Methode Schrott wurde, als ich (ein halbes Jahr spaeter) schnell zu den Parametern noch Spread und Rest addierte und zu den Argumenten noch Spread. Bei den Parametern fehlt noch das Bindingpattern. Jedenfalls hatte ich da schon den Stackframe gewechselt, war sogar so hohl, mir this abzuspeichern, wenn es auf der Zeile vorkommt, brauche ich ja nicht das neue der Funktion, was ich mir mit der Stackframefunktion gleich mit eingerichtet hatte. Gerade habe ich das wieder schoen getrennt. Erstmal mache ich aus den Argumenten einen Array, da koennen dann auch unzaehlige Spreads drin sein, weil es nicht falsch eingebaut ist. Jetzt kann ich die an [[Call]](any, argList) uebergeben, und dort wird es auf die Parameter und den Restparameter verteilt und kann selbst als Arguments Objekt genutzt werden.

    for (i=0, j=code.length; i < j; i++) 
	if (isAbruptCompletion(cmpl = ToValue(code[i]))) break;
    return cmpl;

Jetzt ist die folgende [[Call]]-Routine auch nicht mehr so hohl. Und der Clou. Es werden die Variablen nicht mehr in variables, identfiers und constants abgelegt, sondern in den Environment Records. Dort speichere Ich auch ObjectEnvironments und FunctionEnvironments (von der rede ich gerade ueberhaupt) ab. Genaugenommen ist jede Variable jetzt ein Record. (Womit die Highspeedmaschine von vorher erledigt ist, aber das hol ich schon wieder rein.) Die [[Call]] Routine ist jetzt wieder normal und besitzt keinen hohlen Counter fuer ein reingehacktes Spread. (Ich bin da ca. zwei Blocks zu tief gewesen um das richtig aufzubauen, koennte man sagen.) Jetzt sitzts. Praesentation demnaechst.

Noch ein Eindruck

Mit CreateMutableBinding und InitialiseBinding ist das BindingPattern auf der Parameterzeile ganz anders. Ich hatte es letzten noch gar nicht geschrieben. Aber das ist ganz anders als mein Hack vom Destructuring, wo ToValue sich das Pattern mit einer eigenen Funktion fuer die Node uebersetzt, was auch bleiben wird (nur ebenfalls umformuliert), denn das ist jetzt das Destructuring eines Funktionsparameters.

So langsam wie JavaScript um die 2000

Interessant und geradezu komplex ist das Traversieren der Bindings ueber die Outer Property. Ich hoffe, ich optimiere dass, dass outer = dem Prototype des Binding Objekts wird. this.bindings = Object.create(this.outer.bindings) - damit ich nicht den langsamen Weg ueber if (!this.HasBinding(N) && this.outer) return this.outer.HasBinding(N) gehen muss, wie zur Zeit.

Dann fiel mir auf, dass die FunctionEnvironment auch Properties hat. Doch ich habe der FunctionEnvironment noch kein eigenes Bindings Objekt besorgt. Aber man hat vordefiniert F.name, F.arguments, F dot ... Die gehoeren dorthin. Mit dem Globalen Objekt muss ich ebenfalls nochmals ueben, denn nicht jede var * ist auch eine window.* und so gilt das auch fuer das globale Objekt.

Update: Function Environment stammt von Object Environment (was vom Top-Level Environment Record abstammt) ab.

Unicode

Auf Seite 14/15 des Working Drafts steht der UTF-16 Algorithmus. Der gleiche, den ich von M.Bynens abgeguckt habe, nur mit Integern, statt mit Hexadecimalzahlen. Es ist die normale Umwandlung des Code Points in zwei Code Units. In dem folgenden Auszug geht es um 6 Source Text

SourceCharacter :: any Unicode character

Ein SourceCharacter ist ein Zeichen. Ein beliebige Unicode-Zeichen. Um diesen Unicode Character jetzt wird der folgende Algorithmus dazu benutzt, eine Funktion zu beschreiben, die einen CodePoint kleiner als 0x10000 zurueckgibt und einen groesser 0xFFFF in zwei Code Units zerlegt. 1024 ist Hexadezimal 0x0400, mit der man dividieren, oder den Rest nehmen muss. Dann wird 0xD800 bei Code Unit 1 und 0xDC00 bei Code Unit 2 addiert. Ich habe weiter unten korrigiert, dass man beim zweiten DC00 addiert.

ECMAScript String values (8.4) are computational sequences of 16-bit integer values called “code units”.
ECMAScript language constructs that generate string values from SourceCharacter sequences use UTF-16
encoding to generate the code unit values.

Static Semantics: UTF-16 Encoding
The UTF-16 Encoding of a numeric code point value, cp, is determined as follows:

1. Assert: 0 ≤ cp ≤ 0x10FFFF
2. If cp ≤ 65535, then return cp.
3. Let cu1 be floor((cp – 65536) / 1024) + 55296. NOTE 55296 is 0xD800.
4. Let cu2 be ((cp – 65536) modulo 1024) + 56320. NOTE 56320 is 0xDC00.
5. Return the code unit sequence consisting of cu1 followed by cu2.

Ich habe die Spezifikation wieder auf Seite 1 begonnen, und werde das wiederholen. Mit meinem Gedächtnis ist das unmöglich, dass ich es mir einfach direkt in einem durch einpräge. Aber ich versuche es.

Anstrengend ist nur, es am Bildschirm zu lesen, anstatt gedruckt unterwegs in der Bahn, oder bequem mit Papier in der Hand. Gerade das Umblättern ist am Computer umständlich.

Auf Seite 33 des Working Drafts steht noch was zu den Code Units und Surrogate Pairs.. Im Abschmit 8.1.4 The String Type. Einer Sequenz von Uint16ern (16bit unsigned integers).

Some operations interpret String contents as UTF-16 encoded Unicode code points. In that case the
interpretation is:
A code unit in the range 0 to 0xD7FF or in the range 0xE000 to 0xFFFF is interpreted as a code point
with the same value.
A sequence of two code units, where the first code unit c1 is in the range 0xD800 to 0xDBFF and the
second code unit c2 is in the range 0xDC00 to 0xDFFF, is a surrogate pair and is interpreted as a code
point with the value (c1 - 0xD800) × 0x400 + (c2 – 0xDC00) + 0x10000.
A code unit that is in the range 0xD800 to 0xDFFF, but is not part of a surrogate pair, is interpreted as
a code point with the same value.

Ausblick - grammar.js

var NonTerminals = {
    "SourceCharacter": [get_character]
};
var Terminals = {
};

var grammar = {};
grammar.get_character = get_character;
grammar.reset = reset;
grammar.parse = parse;
grammar.NonTerminals = NonTerminals;
grammar.Terminals = Terminals;

function get_character () {
    if (grammar.pos < grammar.text.length - 1) {
    var p = ++grammar.pos;
    grammar.lookahead = grammar.text[p+1];
    return grammar.ch = grammar.text[p];
    }
}

function reset(text) {
    grammar.text = text || grammar.text;
    grammar.pos = -1;
}

function parse(NonTerminal) {
    var seq = NonTerminals[NonTerminal];
    var s, r;
    var nt = [];
    for (var i=0, j=seq.length; i < j; i++) {
	s = seq[i];
	if (typeof s === "function") r = s();
	else r = parse(s);
	if (r) nt.push(r);
    }
    if (nt.length>1) return nt;
    if (nt.length===1) return nt[0];
}
grammar.reset(require("fs").readFileSync("grammar.js","utf8"));
while (grammar.parse("SourceCharacter")) console.log(grammar.ch);
/*
v
a
r
 
N
o
n
T
e
r
m
i
n
a
l
s
*/

Besonders gut geeignet ist das Tool "buildsrc", um die Tabellen aus Syntax.js aus dem File rauszuholen, separat zu speichern und in beide Projekte zu importieren. Dann brauch ich die nicht hin- und herkopieren.

function parse(NonTerminal) {
    var t = NonTerminals[NonTerminal];
    var ty = typeof t;
    if (ty === "function") return t();
    if (ty === "object") return t[this.token];
}

(Ausschnitt aus der Fortsetzung des oberen Abschnitts, mittlerweile schon so voller Versuche, dass ich es nicht mehr poste.)

tester.js

Ich habe dieses kurze JavaScript geschrieben, um damit syntax.js -einen JavaScript-Syntaxhighlighter mit JavaScript-Parser- zu testen. Weil das Debuggen der ToValue() Funktion, einem ES6-Interpreter, im Browser nicht unbeding das Wahre ist. Man müsste viele Examples schreiben, die alle anklicken zum Ausprobieren und dann bei jedem Fehler erstmal in der Konsole und im Sourcecode graben. Mit tester.js bekommt man all das automatisch ausgefuehrt, ohne dass ich ToValue ausprobieren muss.

Anforderungen

  • Einfache Anwendung. Testen soll einfach sein und Spass machen.
  • Ein Test ist eine Funktion. Mehrere Tests werden in einem Objekt zusammengefasst. Das Objekt hat eine run-Funktion um alle aufzufangen.
  • In der Testfunktion sind ueber den this-Identifier, und uber den ersten Parameter, das gleiche Testobjekt mit den Assertions, Tests und Resultaten verfuegbar.
  • Die Tests werden im Array .tests festgehalten.
  • Die Ergebnisse der Assertions werden in .results festgehalten.
  • Die Assertions werden mit .assert, .equals etc. angesprochen.
  • Die Resultate bestehen aus Records mit pass, type, actual, expected, message und print (einem vorformatierten Ergebnis-String)
  • Zur Ausgabe gibt print (console.log), html (element.innerHTML) sowie draw (canvas)
  • Unerwartete Exceptions werden aufgefangen und zu Beginn mitsamt stack ausgegeben, um debuggt werden zu können.
  • Das Programm kann mit node.js gestartet werden, oder auch im Browser eingebunden werden.
  • Man kann damit lokale Tests durchführen, oder sie auf seiner Homepage demonstrieren.
  • Das Testen der syntaxjs-GUI-Elemente (und allgemein DOM GUI) muss ich noch nachruesten.

Man kann tester.js als script oder mit require(file) einbinden. Das Objekt heisst schlicht und einfach Test. Man instanziiert es mit new Test.

{
    //
    // Daten
    tests: [],
    results: [],
    //
    // Ausfuehrung
    add: function (f),
    run: function (),
    //
    // Ausgabe
    print: function (),
    //
    // Zusicherungen
    assert: function (act, exp, message),
    deepEquals: function (act, exp, message),
}

Ein new Test; hat die folgenden Attribute und Methoden. Es sind die Eigenschaften .tests und .results, in dem einmal die Funktionen und zum anderen die Ergebnisse liegen. Und alle Operationen, die tester.js zu bieten hat. Zum einen .add(fn) und .run(), die zum hinzufuegen von Testfunktionen und zum abarbeiten des .tests-Arrays gedacht sind. Dann gibt es print() fuer console.log der vorformatierten Ausgabe, sowie html({ el: "#element" }) und draw({ el: "#canvas" }), die sich das Element el holen und entweder HTML hinzufuegt (html) oder auf dem Canvas malt (draw). Dann gibt es noch Hilfsfunktionen wie var text = test.load(file); und var s = stringify(obj); die zum Laden von Text und Ausgeben von Objekten und Arrays verfügbar sind. Und natürlich die Assertions (Zusicherungen) wie .assert und .equals, damit man den Testling pruefen und dem .results Array records mit { pass, type, act, exp, mess, print } hinzufuegen kann.

In .tests speichert man mit .add oder manuell Funktionen mit dem Parameter (test) oder ohne. Nutzt man ihn nicht, referenziert man mit this (und that). In .results werden die Resultate der Assertions in einer Struktur { pass, type, act, exp, mess, print } gespeichert. Bei unerwartet aufgefangenen Exceptions wird diese im Feld .ex mit dem type "Exception" gefuehrt. Die anderen Types haben den Namen des jeweiligen Tests, der gerufen wurde. Sowie ihre Parameter actual (.act), expected (.exp) und message (.mess), .pass enthaelt das Ergebnis als Boolean Wert. Dieser Record ermoeglicht eine Auswertung des Tests.

Beispiele

Mit NodeJS kann man ganz einfach Programme mit tester.js testen.

Hier schreibe ich ein Testprogramm fuer syntax.js

var Test = require("./tester.js").Test;
var sjs = require("./syntax.js").syntaxjs;

var test = new Test();

// Die Assertions erreicht man mit this
test.add(function () {
    this.assert(typeof syntaxjs, "object", "syntaxjs sollte ein globales Objekt sein");
});

// Oder ueber den ersten Parameter.
test.add(function (test) {
    test.assert(typeof syntaxjs, "object", "syntaxjs sollte ein globales Objekt sein");
});
// Ausfuehren von test.tests (den Funktionen)
test.run();
// Ausdrucken von test.results (Objekte mit dem Testdaten)
test.print();

Dieses Programm zeigt nur, wie man den test als Parameter der Funktion, oder als this-Referenz verwenden kann. Es soll die Nutzung des Tests einfach machen.

Ausgabe von node test.js. Durch .run() wurden die zwei Funktionen ausgefuehrt. Und .print() druckt aus, was im Results Array gespeichert ist. Und das ist der vorformatierte String

2 Tests completed in: 1 ms
PASS: assert: act=object, exp=object: syntaxjs sollte ein globales Objekt sein
PASS: assert: act=object, exp=object: syntaxjs sollte ein globales Objekt sein

Unerwartete Exceptions

Unerwartete, nicht im Test eingeplante Exceptions werden abgefangen und zu Beginn ausgegeben und als extra .results mit .ex Exception und .test Funktion gespeichert.

18 tests completed in : 251ms
EXCEPTION THROWN AT TEST 8
EvalError: Sorry, no evaluation of ClassDeclaration today.
    at ToValue (/l/syntax.js:441:143)
    at Program (/l/syntax.js:437:108)
    at ToValue (/l/syntax.js:441:85)
    at ToValue2 (/l/syntax.js:442:203)
    at null.<anonymous> (/l/syntest.js:37:17)
    at /l/tester.js:176:15
    at Array.forEach (native)
    at run_tests [as run] (/l/tester.js:173:16)
    at Object.<anonymous> (/l/syntest.js:86:6)
    at Module._compile (module.js:456:26)
[...]

(Ende der begonnenen Dokumentation)

Resume (Fortsetzung)

Das ist der Anfang der (vollstaendigen) Dokumentation zu tester.js einem Tool, was fuer syntax.js programmiert wird und was alleine genutzt werden kann. Ich werde ihm eine separate Seite widmen. Herausgekommen ist beim Nachdenken ueber meine bisherige Version:

Brainstorm (ungeordnetes Kauderwelsch)

InputElementRegExp, InputElementDiv, InputElementTemplateTail

Kurzmeldung (wieder nichts neues)

In der Spezifikation ist der Punkt, dass ein / zum einen fuer den Regulaeren Ausdruck und einmal fuer die Division gilt, nicht nur festgehalten, sondern auch mit den Oberbegriffen aus der Ueberschrift verbunden. Gleichzeitig ist das die Regel fuer die Token. Kann man leicht ueberlesen, wenn man nicht aufpasst. (Mehr darueber, wenn ich den Text besser kann. Wie immer, denn ich wiederhole mich.)

Ich hab jetzt den englischen Wortlaut vergessen, aber genau darum ging es bei der Grammatik, bei der Bezeichnung dieser InputElemente. Bei dem ein kann es ein Regexp sein, bei dem anderen nicht. Da liegt die Regel fuer den / DivPunctuator (bzw. wird da genauestens beschrieben..)

Update: Also in Kapitel 7, Lexical Conventions auf Seite 15 des aktuellen Working Draft kann man nachlesen, dass die Zielsymbole Kontextabhaengig sein koennen und man darum mehrere ZielSymbole fuer das InputElement definieren musste.

Eine deutlichere und auffaelligere Mehrfachdefinition sind die -NoIn Produktionen, die einzig und allein dazu da sind, den "in" Operator nicht zuzulassen. Kapitel 7 erlaeutert aber ein-eindeutig, wozu die verschiedenen InputElementDefinitionen gut sind. Und im weiteren Verlauf muss man dann aufpassen, wann welche drankommt. Weiter unten auf der Seite hatte ich selbst geguckt, wann der RegExp drankommt und wann nicht, ohne Kapitel 7 zu referenzieren.

6.042/18.062 Mathe für Informatiker

Tja, Eddie! Wirst du wohl büffeln müssen. Ich habe mich vor gut drei Monaten den Monat intensiv mit Mathematik beschäftigt und vieles aufgefrischt und sogar was dazu gelernt. Ich habe mir aber gerade das Examen durchgelesen. Ich falle da ohne Probleme durch. Nicht mit 0 Punkten, aber ich würde es nicht bestehen. Dennoch lese ich, wie einfach es ist, und mir auf der Zunge liegt, beziehungsweise, ich es nicht nachvollziehen kann, weil ich die Unterrichtsstunde und das Material noch nicht gemacht habe. Ein guter Anhaltspunkt weiterzumachen. Dass ich OCW und MIT nicht an einem Wochenende oder in einem Monat packe ist logisch. Und Lernen muss man sein Leben lang. Mit und ohne Examen. Darum auf ein Neues. Bis ich die Fragen (aus dem Kopf) beantworten kann.

Assembler für Anfänger

Ein bißchen im Internet geguckt, konnte ich schnell entdecken, wie man Systemrufe unter Linux ruft. Nach %eax kommt die Nummer des Systemcalls, dann folgen die Argumente. Mit int $0x80 kann man den dann rufen. Der einfachste ist mit $1 in %eax und $0 in %ebx und beendet das Programm. Der Aufruf fuer ein print oder write ist Systemruf $4 ($ steht fuer Wert/Value, 4 hiess Adresse 0x04), dem man die Daten und die Laenge des Buffers uebergibt.

.section .data
str: .ascii "Das ist jetzt der erste String, den ich mit Assembly ausgeben werde.\n"
strlen: .long 69

.section .text
.global _start
_start:
    jmp print
exit:
    movl $1, %eax
    movl $0, %ebx
    int $0x80
print:
    movl $4, %eax
    movl $1, %ebx
    movl $str, %ecx
    movl strlen, %edx
    int $0x80
    jmp exit

Die jmps habe ich mir ausgedacht, um zu gucken, ob ich das auch kann. Der Code haette auch einfach untereinander stehen koennen. Wie man die String-Laenge berechnet muss ich noch herausfinden, ich habe sie jetzt einfach mit dem Editor abgelesen.

Man muss die Datei mit as kompilieren und mit ld linken, Um das zu vereinfachen, habe ich mir kurz das Skript mkasm geschrieben.

echo "as"
as $1.asm -o $1.o -v
echo "ld"
ld $1.o -o $1 -v
echo "done"

Mit ./print habe ich dann folgende Ausgabe erhalten, nachdem ich erstmal nichts erhielt und nochmal das $ vor str und strlen ausprobierte und es dann von strlen wieder entfernen musste.

Das ist jetzt der erste String, den ich mit Assembly ausgeben werde.

syntax.js

Code Realms und Intrinsics (in der Spezifikation)

Auch wenn ich ein paar Tage ECMA gelesen habe und nicht so viel programmiert, wie ich wollte, bin ich trotzdem weiter gekommen. Inzwischen weiss ich, wie ich Objekte wie Array oder Object und die anderen implementiere. Ein Verweis auf die internen Namen wie %Object% oder %ObjectPrototype% bringt mich dazu, im Realm nach den Intrinsics (internen Operationen) zu gucken.

Die Algorithmen werden anders in Code umgewandelt, als geparste Funktionen, die eine [[Code]] Property mit dem FunctionBody enthalten. Man benutzt dazu das interne API, was man bereits programmiert hat. Und anstelle der Bindings, die dann wiederum AST Nodes (oder Bytecode) enthalten verweist man mit dem internen Namen auf die interne Operation, die ausgeführt wird.

Genauso wird auch mit den @@Funktionen (well-known Symbols) verfahren. Alles in allem brauche ich wohl noch ein paar Tage, bis ich das angekündigte Stück ECMA endlich eingebaut habe, mit seinen 6000 Zeilen Code ist syntax.js zwar überschaubar und noch einfach. Im Detail sind es aber tausende (!) Änderungen. Darum rutscht auch mir mal kurz ein Uff! über die Lippen.

C und Assembler - Vorbereitung

#include <iostream>
using std::cout;
using std::endl;
// von ibm.com/developerworks/linux/library/l-ia/
// auf der Suche nach Assembler Referenzen
int main()
{
    int x = 10, y;
    asm ("movl %1, %%eax;"
	 "movl %%eax, %0;" // "asm template" 
	 
	:"=&r"(y)	// : output operand 
	:"r"(x)		// : input operand
	:"%eax");	// : clobbered register
    cout << y << endl;
}
/*

10

*/

C++ Funktionzeiger

Wenn man (*funktionsname) statt funktionsname schreibt, erzeugt man einen einfachen Funktionszeiger. Mit dem &-Adressoperator kann man dem Strukturfeld oder der Zeigervariable dann die Adresse einer Funktion mit identischer Signatur (Parametertypen und -zahl, Rueckgabewert) uebergeben. Die kann man dann wie eine normale Funktion aufrufen.

#include <iostream>
using std::cout;
using std::endl;

struct NutzlosesObject {
    const char *name;
    void (*sprich)(const char *); // Funktionszeiger 1
    const char *(*sage)();	  // Funktionszeiger 2
};
const char *sage () {			// Funktion 2
    return "Auch das hier geschieht!";
}
void sprich(const char* was) {		// Funktion 1
    cout << was << endl;
}
int main () {
    NutzlosesObject obj;
    obj.name = "Edward";
    obj.sprich = &sprich;	// Zeige mit dem &-Adressoperator auf Funktion 1
    obj.sage = &sage;		// Zeige mit dem &-Adressoperator auf Funktion 2
    obj.sprich(obj.name);
    cout << obj.sage() << endl;
}

Nur ein dynamisches this wie im JavaScript gibt es nicht, so konnte ich obj.name zum Beispiel nicht mit this->name ausgeben. Der Compiler sieht die Funktion als non-member function an. Ob man das noch einen Grad steigern kann?

Edward
Auch das hier geschieht!

Die node.js Bibliothek libuv macht Gebrauch davon, um Callbacks Strukturen zuzuordnen. Der Linux Kernel macht ebenfalls intensiven Gebrauch davon. Und natuerlich x-andere.

type_info

typeid() gibt eine type_info zurueck, womit man den aktuellen Typen ermitteln kann. Seltsamerweise kommt bei .name() anstelle eines lesbaren const char *s nur was Muell mit dem ersten Buchstaben raus.

#include <typeinfo>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;

struct Useless {
    static const char c = 10;
} b;

int main() {
    int i = 10;
    char c = 61;
    string s("Eddie");
    int *j = new int;
    *j = 12;
    cout << "i= " << typeid(i).name() << endl;
    cout << "c= " << typeid(c).name() << endl;
    cout << "s= " << typeid(s).name() << endl;
    cout << "b= " << typeid(b).name() << endl;

    cout << "j= " << typeid(j).name() << endl;
    cout << "*j= " << typeid(*j).name() << endl;
    cout << "&j= " << typeid(&j).name() << endl;

    cout << "&i= " << typeid(&i).name() << endl;
    cout << "&c= " << typeid(&c).name() << endl;
    cout << "&s= " << typeid(&s).name() << endl;
    cout << "&b= " << typeid(&b).name() << endl;
}

/*
i= i
c= c
s= Ss
b= 7Useless
j= Pi
*j= i
&j= PPi
&i= Pi
&c= Pc
&s= PSs
&b= P7Useless
*/

Ich habe .name() kurz in typeinfo auf cplusplus.com nachgeschlagen. Anders als bei mir kommt, ich hatte .name statt .name() geschrieben, dass ich erstmal gucken musste, kommt dort int und int * und immer der richtige Typ raus. Warum das jetzt nicht der Fall ist, werde ich in den naechsten Wochen lernen.

syntax.js

Ich habe den Code in den jsbeautifier.org kopiert, einruecken lassen, und wieder zurueck kopiert. Bis syntax.js das selbst kann, ist es noch ein ganzes Stueck Arbeit, auch wenn das Programm bereits einige Aufgaben loesen kann. (*)

(*) Hinterher fiel mir ein, daß die Ergebnisse des Tokenizers bereits für einen Beautifier reichen. Ein Code-Optimizer benoetigt den kompletten AST. Aber ein Einruecker nicht. Gedenke, einen solchen hinzuzufuegen!

[iframe src=syntax0.txt]

So sieht das Programm bislang aus. Es ist das gleiche, was ich minifiziert in die Seite einbinde, um damit alle PRE Tags und CODE Tags zu highlighten. (Wenn ich mir den durchlese, als ob den jemand lesen koenne, fallen mir gleich alle Sachen neu auf, die unter "wird in x-Edits sowieso nicht mehr da stehen, hier wird laufend refaktoriert" laufen. Das ist gut fuers Gehirn, aber ich sollte lernen, das auch so so zu sehen.

#!/usr/local/bin/node
// usage: node [options] jstrim.js file1.js [file2.js file3.js ...]
(function () {
    var argv = process.argv;
    var argc = argv.length;
    var filenames = argv.slice(argv.indexOf(__filename)+1, argc);
    if (filenames.length < 1) {
        console.log("usage: node [options] jstrim.js file1.js [file2.js file3.js ...]");
        process.exit();
    } else {
        console.log("node jstrim.js: convert tabs to 4 spaces and trim trailing spaces");
    }
    var fs = require("fs");
    var trailingExp = /([\s]+)$/gm;
    var tabExp = /(\t)/gm;
    var trimTrailingSpaces = function ( code ) {
        return code.toString().replace(trailingExp, "");
    };
    var replaceTabsWithSpaces = function ( code ) {
        return code.toString().replace(tabExp, "    ");
    };
    var forEachHandler = function (filename) {
        var writeHandler, readHandler, existsHandler;
        writeHandler = function (filename) {
            return function (err) {
                if (err) {
                    console.log("error: writing file"+filename);
                } else {
                    console.log("wrote: "+filename);
                }
            };
        };
        readHandler = function (err, data) {
            if (err) {
                console.log("error: cant read "+filename);
            } else if (data) {
                fs.writeFile(filename+".backup", data, "UTF-8", writeHandler(filename+".backup"));
                data = replaceTabsWithSpaces(data);
                data = trimTrailingSpaces(data);
                fs.writeFile(filename, data, "UTF-8", writeHandler(filename));
            }
        };
        existsHandler = function (exists) {
            if (exists) { fs.readFile(filename, readHandler); }
            else { console.log("error: "+filename+" does not exist"); }
        };
        fs.exists(filename, existsHandler);
    };
    filenames.forEach(forEachHandler);
}());

/usr/local/bin/jstrim: Mit dem Code kann man alle WhiteSpaces am Ende entfernen und alle voranstehenden in Tabs verwandeln. Es legt vorher eine .backup Datei vom Original an. Geschrieben hatte ich es, weil JSLint wegen den Spaces meckerte.

C++ für Anfänger - Datei lesen

Abgeguckt auf cplusplus.com. Ich sollte nicht nur cin.getline kennen, sondern auch mit den Standard Filestreams arbeiten koennen. Wobei ich direkt drauf hinweise: Die libuv von node.js ist bereits hier gut integriert, wenn man sie direkt hier gut integriert. Sie bietet ansynchrone I/O an und ist im node.js Projekt verarbeitet. Ich hatte sie hier bald einem dreiviertel Jahr schon einmal tölpelhaft probiert.

#include <iostream>
#include <fstream>
#include <string>
using std::string;
using std::ifstream;
using std::cout;
using std::endl;
using std::getline;

void usage() {
    cout << "usage: read filename.txt" << endl;
}

int main (int argc, char **argv) {
    string s;
    ifstream f((const char *)argv[1]);
    if (f.is_open()) {
	while (f.good()) {
	    getline(f,s);
	    cout << s << endl;
	}
	f.close();
    } else usage();
    return 0;
}

Das funktioniert wunderbar. Schnell merken, denn ich muss die Skripte ja einlesen koennen. Class Tokenizer ist diesmal darauf vorbereitet, von Class Parser verwendet zu werden. Es wird aber noch ein Weilchen dauern, bis sie überhaupt arbeiten werden.

linux-qc59:/l # c++ read.cpp -o read
linux-qc59:/l # ./read I.js
// I.js - Input template
function A() {
${first}
}
function B() {
${second}
}
${third}
console.log(A()+" "+B()+" "+C());

Weniger Glück: String

Der erste Versuch, einen eigenen String zu schreiben, klappt nach 30 Minuten noch nicht. Genug für den ersten Versuch. Jetzt ist erstmal lesen und weitere Strukturen probieren angesagt. Besonders freue ich mich heute oder morgen auf FreeLists zur schnellen Speicherallozierung und den Stackallocator, ebenfalls zur schnellen Allozierung. Gesehen habe ich die in Kurs 6.172 Performance Engineering. Ich dachte, wo ich schonmal angefangen habe, kann ich auch die machen.

class String {
public:
    char *s;
    long l;
    String(const char *);
    ~String();
    long len(const char *);
    String cpy(const char *);
    String operator=(const char *);
    char *str();
};
String::String(const char *s) {
    this->cpy(s);
}
String::~String() {
    delete s;
}
long String::len(const char *t = 0) {
    if (t==0) return l;
    long i = 0;
    do {
	if (t[i]=='\0') break;
	i+=1;
    } while (1);
    return i;
}
String String::cpy(const char *s) {
    long k = this->len(s);
    this->s = new char[k+1];
    for (long i=0;is[i] = s[i];
    this->s[k]='\0';
    l=k;
    return this->s;
}
String String::operator=(const char *s) {
    return this->cpy(s);
}
char *String::str() {
    char *s = new char[l+1];
    for (long i=0;i <= l;i++) 
	s[i] = this->s[i];
    return s;
}

#include <iostream>
using std::cout;
using std::endl;

int main() {
    String s("Edward sein erster String");
    String *t = new String("Ich habe noch nie einen eigenen in C++ probiert.");
    cout << s.str() << endl;
    cout << t->str() << endl;
} 
   
/*
linux-qc59:/l # g++ string.cpp
linux-qc59:/l # ./a.out 
Segmentation fault
linux-qc59:/l # mcedit string.cpp
*/

Gestern habe ich mich ausserdem auch mit C++ beschäftigt. Ich habe heute sogar schon ein wenig cplusplus.com gelesen. Aber zum Erweitern meiner Fähigkeiten brauche ich einige Tage oder viele Stunden Praxis. Inzwischen werde ich an syntax.js weiterschreiben, für daß ich die Woche die Spezifikation gelesen habe.

C++ Stack

Edward
Stacksize ist: 1
stack waechst auf 3
stack waechst auf 4
Stacksize ist: 5
popped: vielleicht ein anfang
popped: und liste
popped: mit template< class T>
popped: ein stack
popped: mit Liste
Stacksize ist: 0
top: Ganz oben auf dem Stack
top: liegt wieder was neues
pop: liegt wieder was neues - Stacksize ist jetzt nur noch 1
pop: Ganz oben auf dem Stack - Stacksize ist jetzt nur noch 0

Ich denke seit einiger Zeit daran, wieder mit C/C++ anzufangen. Dazu die notwendigen Datenstrukturen zu implementieren. Heute habe ich mal wieder einen Versuch gewagt. Ich habe beim Schreiben sogar von Beginn an aufgepasst, damit ich mich nicht mit JavaScript vertu. Herausgekommen ist ein Stack mit einer verketteten Liste. Ich habe so einen ebenfalls in JavaScript geschrieben. Habe ihn aber nicht als Vorlage verwendet. Dieser hier wird kompiliert. In JavaScript, da kann man einen Array stack = []; nehmen, um mit pop(), push() und function top() { return stack[stack.length-1] } zu arbeiten. Mit C++ ist die Definition geringfügig aufwaendiger.

#define NULL 0
// Eine Node auf dem Stack
template< class T> class StackItem {
public:
    StackItem< T> *prev;
    StackItem< T> *next;
    T *item;
    StackItem(T*, StackItem*, StackItem*);
    ~StackItem();
};
template< class T> StackItem< T>::StackItem(T* item = NULL, StackItem* prev = NULL, StackItem *next = NULL) {
    this->item = item;
    this->next = next;
    this->prev = prev;
    if (this->prev != NULL) this->prev->next = this;
}
template< class T> StackItem< T>::~StackItem() {
}
// Der Stack mit der verketteten Liste
template< class T> class Stack {
public:
    StackItem< T> *list;
    long size;
    Stack();
    ~Stack();
    long push(T *);
    T* pop();
    T* top();
};
template< class T> Stack< T>::Stack() {
    this->list = NULL;
    this->size = 0;
}
template< class T> Stack< T>::~Stack() {
}
template< class T> long Stack< T>::push(T *item) {
    this->list = new StackItem< T>(item, list);
    this->size += 1;
    return this->size;
}
template< class T> T* Stack< T>::pop() {
    T *item;
    StackItem< T> *si = this->list;
    if (this->list == NULL) return NULL;
    this->list = si->prev;
    this->size -= 1;
    if (this->list != NULL) this->list->next = NULL;
    item = si->item;
    delete si;
    return item;
}
template< class T> T* Stack< T>::top() {
    if (this->list == NULL) return NULL;
    return this->list->item;
}
// Ein Testprogramm

#include <string>
#include <iostream>
using std::cout;
using std::endl;
using std::string;

int main () {
    Stack< string> stack;
    string *s;
    stack.push(new string("Edward"));
    cout << stack.pop()->c_str() << endl;
    stack.push(new string("mit Liste"));
    cout << "Stacksize ist: " << stack.size << endl;
    stack.push(new string("ein stack"));
    cout << "stack waechst auf " << stack.push(new string("mit template< class T>")) << endl;
    cout << "stack waechst auf " << stack.push(new string("und liste")) << endl;
    stack.push(new string("vielleicht ein anfang"));
    cout << "Stacksize ist: " << stack.size << endl;
    while ((s=stack.pop()) != NULL) cout << "popped: " << s->c_str() << endl;
    if ((s=stack.pop()) == NULL) cout << "Stacksize ist: " << stack.size << endl;
    stack.push(new string("Ganz oben auf dem Stack"));
    cout << "top: " << stack.top()->c_str() << endl;
    stack.push(new string("liegt wieder was neues"));
    cout << "top: " << stack.top()->c_str() << endl;
    while (s=stack.pop()) cout << "pop: " << s->c_str() << " - Stacksize ist jetzt nur noch " << stack.size << endl; 
}

Ich habe jetzt ein Attribut data-syntaxjs-language=C++ zum pre Tag addiert, was noch keine Bedeutung hat. Als der Highlighter noch 99 Zeilen hatte, und mit Regulaeren Ausdruecken scannte, hatte ich ein paar benutzerdefinierte Tokens. Wenn ich jetzt verstaerkt wieder C++ einsetzen will, sollte ich mir Gedanken um die Erweiterung von Syntax.js machen. Die ich mir sowieso mache. Denn langsam kann ich mir das Generieren von rekursiven Grammatiken (in nur(!) linearer Zeit!) vorstellen. Wobei ein Tool wie buildsrc (uebernaechster Artikel) uebrigens helfen koennte. Wichtig ist aber, dass ich alle Parserfunktionen nicht mehr direkt, sondern von parser[name] rufe. Dem Objekt parser = {} weise ich die Funktionen bereits zu. Das Objekt engine[...] nutzt sich fuer ToValue(). Um Hooks fuer die Analyse zu injizieren, muss ich ebenfalls auf das Objekt umsteigen. Selbiges gilt fuer den Tokenizer. -- Zur Kuerzung der Laufzeit kann ich selbige Hashingmethode nutzen, wie bei den Token. Es ist schneller, so die entsprechende Methode zu rufen, als alle mit || OR zu verknuepfen und mit O(n) durchzuprobieren, wie das Original vom Januar was da noch laeuft noch macht. Auch wenn ich die Idee, Funktionen mit OR zu verknuepfen cool fand, ist sie nicht Performanceoptimiert. Nutze ich eine Tabelle fuer das aktuelle Symbol kann ich auf O(1) fuer die Funktionsallokation runter. Das ist super, weil PrimaryExpressions gleich 19 Funktionen testen, bis Expression() drankommt. Wenn ich erst die Funktion anhand des Tokens alloziere, ist es nur noch O(1).

Bonus verkettete Liste

Dann habe ich gerade list.cpp, was am Ende vom Juni ganz unten auf der Seite stand, unter dem Verweis zum Mai, geöffnet und editiert, als ein File list.cpp anlegen wollte. Ich stosse bereits an meine aktuellen Grenzen. Ich kann Templates nicht ueberladen, mein einziger Versuch List< string>::print() speziell fuer den String zu schreiben scheiterte und weil ich keine Zeit verschwenden will, habe ich das direkt wieder gestoppt. Der andere Fall ist, der T* Pointer, wie im Stack benutzt ist dumm. T und Punkt-Notation zum Aufrufen sind richtig, denn dann kann ich den * beim Deklarieren und Instanziieren mit uebergeben. Auf dem Wege wie jetzt verlangt die Liste bei ints bereits int *. Und das ist nicht Sinn der Sache. -- Ein andere Idee ist, auch fuer Objekte keinen Pointer zu nehmen, weil man das ganze Objekt direkt an die Adresse verlegen kann, die im Item reserviert ist, wenn es richtig gespeichert werden soll. -- Hier kann ich mich den Abend oder am Wochende wohl mal ein wenig unterhalten und einlesen.

#define NULL 0
#include <iostream>
#include &l;string>
using std::string;
using std::cout;
using std::endl;

// Eine Node der Liste (ob methodenlose struct mit void *next und void* prev besser ist?)
template< class T> class ListItem {
public:
    ListItem< T> *prev;
    ListItem< T> *next;
    T *item;
    ListItem(T*,ListItem< T>*,ListItem< T>*);
};
template< class T> ListItem< T>::ListItem(T *item, ListItem< T> *prev=NULL, ListItem< T> *next=NULL) {
    this->item = item;
    this->prev = prev;
    this->next = next;
    if (prev != NULL) this->prev->next = this;
    if (next != NULL) this->next->prev = this;
}

// Die Liste
template< class T> class List {
public:
    ListItem< T> *list;
    long size;
    List();
    long insert(T*);
    T* nth(long);
    T* remove(T*);
    void print();
};
template< class T> List< T>::List () {
    this->list = new ListItem< T>(NULL);
    this->list->prev = this->list;
    this->list->next = this->list;
    this->size = 0;
}
template< class T> long List< T>::insert(T* item) {
    ListItem< T> *l = new ListItem< T>(item, this->list->prev, this->list);
    this->size += 1;
    return this->size;
}   
template< class T> T* List< T>::nth(long n) {
    long a = 0;
    if (n > this->size || n < 1) return NULL;
    ListItem< T> *l = this->list;
    l=l->next;
    do {
	++a;
	if (a == n) return l->item;
    } while ((l=l->next) != this->list);
    return NULL;
}
template< class T> T* List< T>::remove(T* item) {
    ListItem< T> *l = this->list;
    l=l->next;
    do {
	if (l->item == item) {
	    l->prev->next = l->next;
	    l->next->prev = l->prev;
	    l->next = NULL;
	    l->prev = NULL;
	    this->size -= 1; 
	    return l->item;
	}
    } while ((l=l->next) != this->list);
    return NULL;
}
// Die Funktion ist grenzwertig eingebaut, weil fuer strings (schwerer Defekt!)
template< class T> void List< T>::print () {
    if (this->size == 0) return;
    ListItem< T> *l = this->list;
    l = l->next;
    do {
        cout << l->item->c_str() << endl;
    } while ((l=l->next) != this->list);
} 

int main () {
    string *s;
    List< string> *L = new List< string>();
    L->insert(new string("Erstes Item"));
    L->insert(s=new string("Zweites Item"));
    L->print();
    L->insert(new string("Drittes Item"));
    L->print();    
    L->remove(s);
    cout << "nth(2): " << L->nth(2)->c_str() << endl;
    L->print();
}

Das ist die Ausgabe der Liste. Wie im Absatz vorher beschrieben ist die Liste selbst aber bereits problematisch. Und dann kam ich nicht drauf, static_cast, dynamic_cast, reference_cast gab es nicht und wie der unsafe_cast heisst, weiss ich nicht mehr, ich kam nicht drauf. Ist auch nicht weiter wichtig. Mit dem Stueck verabschiede ich mich erstmal bis abends. Jetzt ist Zeit, Pfandflaschen zu sammeln. Wir haben noch einen Haushalt, der versorgt werden muss.

Inzwischen bin ich wieder zuhause, habe den passenden HashTable begonnen, und der Liste hier mal nth und remove hinzugefuegt. Die muss jetzt alle paar Tage verfeinert werden, damit ich die, den Hash und den Stack, sowie den (kommt gleich) selbstgeschriebenen String mit ein paar anderen Finessen einsetzen kann.

Erstes Item
Zweites Item
Erstes Item
Zweites Item
Drittes Item
nth(2): Drittes Item
Erstes Item
Drittes Item

Wie man sieht ist noch nichts geprueft ausser insert und print, und das nichtmal richtig. Wie man aber sehen kann, es ist eine doppelt-verkettete-Liste MIT Sentinel. Die sollte nicht so einfach Abstuerzen, weil man eine zirkuläre anstatt NULL-Referenz hat, gegen die man testen kann. Am schlimmsten scheint aber T* und -> statt T und . zu sein. So werden immer Pointer verlangt werden. Das ist so ein Designfehler und unpraktisch.

---

Noch speichern die oberen Beispiele bloss Pointer. T* und -> ist eine schlechtere Idee als T und.

Doch es geht noch besser. Der Referenzoperator & sorgt bei Funktionsaufrufen fuer Call-By-Reference, statt Call-By-Value. Wie ich im naechsten Beispiel bemerke ist T anstatt T* schonmal sehr gut. Aber hier muss noch die Referenz her, damit ich die Daten und nicht nur die Adressen oder Kopien speichere und man die Strukturen als richtige Datenstrukturen nutzen kann, weil sie dann auch die Daten selbst speichern und die dann dort sind, wo sie hinsollen.

Der HashTable der noch nicht fertig ist

Ein bisschen mehr schlechtes C++ von mir. Der angefangene HashTable von gestern abend kurz zum Laufen gebracht, aber ganz fertig ist das noch nicht. Wie ich an den return Wert bei nicht gefundenen Keys lese. Ausserdem habe ich die Hashfunktionen vergessen und kannte wie alle nur noch hval % N. Kein Grund aufzugeben. Hierdrin landen spaeter allerhand wichtige Daten. Die STL in C++ bietet all diese Strukturen uebrigens auch an.

#include <string>
#include <iostream>
using std::string;
using std::cout;
using std::endl;

template < class T> class HashTableCell {
public:
    string key;
    T value;
    HashTableCell();
    HashTableCell(string, T);
};
template < class T> HashTableCell< T>::HashTableCell(string key, T value) {
    this->key = key;
    this->value = value;
}
template < class T> HashTableCell< T>::HashTableCell () {
}

template < class T> class HashTable {
public:
    long N;
    HashTableCell< T> DELETED;
    HashTableCell< T> NIL;
    HashTableCell< T> *table;
    long size;
    HashTable(long);
    ~HashTable();
    long resize(long);
    void insert(string, T);
    T find(string);
    T remove(string);
    long h(string, long);
};
template < class T> HashTable< T>::HashTable(long N = 61) {
    this->N = N;
    this->size = 0;
    this->table = new HashTableCell< T>[N];
    this->NIL = HashTableCell< T>("", (T)0);
    this->DELETED = HashTableCell< T>("", (T)NULL);
    for (long i=0;itable[i] = this->NIL;
}
template < class T> HashTable< T>::~HashTable() {
}
template < class T> long HashTable< T>::resize(long M) {
    HashTableCell< T> *table2 = new HashTableCell< T>[M];
    HashTableCell< T> *table = this->table;
    long N = this->N;
    this->N = M;
    this->table = table2;
    for (long i=0;itable[i] = this->NIL;
    for (long i = 0; i < N; i++) {
	this->insert(table[i].key, table[i].value);
    }
    delete[] table; 
    return this->N;
}
template < class T> void HashTable< T>::insert(string key, T value) {
    HashTableCell< T> cell(key, value);
    if (this->size >= (this->N-1)) {
	this->resize(2*this->N);
    } 
    long slot = this->h(key);
    this->table[slot] = cell;
}
template < class T> T HashTable< T>::find(string key) {
    long slot;
    long probe = 0;
    HashTableCell< T> cell;
    do {
	slot = this->h(key, probe);
	cell = this->table[slot];
	if (&cell == &this->NIL) return NULL;
	++probe;
    } while (&cell == &this->DELETED);
    return cell.value;
}
template < class T> T HashTable< T>::remove(string key) {
    HashTableCell< T> cell;
    long slot = this->h(key);
    cell = this->table[slot];
    this->table[slot] = this->DELETED;
    return cell.value;
}
template < class T> long HashTable< T>::h(string key, long probe = 0) {
    long hashval = 0;
    long i, j;
    for (i=0, j=key.size(); i < j; i++) {
	hashval += key[i] + probe;
    }
    return hashval % this->N;
}
int main () {
    HashTable *keywords = new HashTable();
    keywords->insert(string("var"), true);
    keywords->insert(string("const"), true);
    keywords->insert(string("cons"), true);
    keywords->remove(string("cons"));
    keywords->insert(string("let"), true);
    cout << "find var: " << keywords->find("var") << endl;            
    cout << "find dull: " << keywords->find("dull") << endl;            
    cout << "find var: " << keywords->find("var") << endl;            
    cout << "find cons: " << keywords->find("cons") << endl;            
    cout << "find const: " << keywords->find("const") << endl;            
    cout << "find var: " << keywords->find("var") << endl;            
    cout << "find let: " << keywords->find("let") << endl;            
    keywords->remove(string("let"));
    cout << "find let: " << keywords->find("let") << endl;            
    cout << "find ven: " << keywords->find("ven") << endl;            
    keywords->insert(string("ven"), true);
    keywords->remove(string("var"));
    cout << "find ven: " << keywords->find("ven") << endl;            
    cout << "find var: " << keywords->find("var") << endl;            
}

Der Table hat probing und DELETED dummys. Aber auch einen NULL dummy, wo ich mir sicher bin, dringend eine Referenz aufzusuchen, denn das O(n) initialisieren mit dem NULL dummy ist mir fremd.

find var: 1
find dull: 0
find var: 1
find cons: 0
find const: 1
find var: 1
find let: 1
find let: 0
find ven: 1
find ven: 0
find var: 0

Das NIL Objekt ist eine Aushilfe. Ich muss noch eine Referenz aufsuchen.

6.035 Computer Language Engineering

Es ist schade, dass es nur acht Videos von den ganzen Sitzungen gibt. In der Klasse lernen sie, einen Compiler fuer die imperative Decaf-Sprache zu schreiben. In Java. Das Interessante, worin ich noch nicht so gut bin, ist die Umwandlung des Syntaxbaumes in Assembler-Instruktionen.

Hierbei lerne ich bereits für die C-Version der ECMAScript Maschine, die ich gerade einuebe. Aus dem Kurs habe ich bereits allerhand Informationen gewonnen, wie man den AST optimieren kann. Das Wichtigste ist hierbei, das Kursmaterial und damit alle Folien und Texte (.pdf Format) zu lesen.

Drucken kann ich zur Zeit nicht, was es mir einfacher machen würde, weil die Maschine zum einen lahm ist, und zum anderen besetzt, wenn ich am Bildschirm lese. Mit der Spec und den Infos neben dem Rechner koennte ich schneller und entspannter arbeiten. Aber es ist nur Zeit (und etwas mehr Geduld), die ich mehr aufwende.

6.035 Computer Language Engineering

buildsrc.js

Ein simples Building Tool.

Ich denke, es ist besser, wenn ich das Tool syntax.js noch weiter in Dateien zerlegen will, ein Tool zu haben, was mir die Dateien wieder zusammenstellt. Es gibt eine ganze Reihe von Softwareengineeringtools, die sowas machen, und die Idee ist bestimmt nicht neu. Diese und die Ausfuehrung ist aber ganz bestimmt von mir. Neben dem tester.js und dem tostring.js nun das dritte, was ich für syntax.js ins Leben gerufen habe.

/* buildsrc.js ist ein simplifiziertes Building Tool, um mehrere Files in eins zu integrieren. */
var fs = require("fs");
var arg2 = process.argv[2];
console.log("Reading configuration from "+arg2);
if (!arg2 || !fs.existsSync(arg2)) throw "node buildsrc config.json - first argument is the config! config not found!";
var config = JSON.parse(fs.readFileSync(arg2));
console.log("Reading input file "+config.input);
if (!fs.existsSync(config.input)) throw config.input + " does not exist!";
var input = fs.readFileSync(config.input,"utf8");
console.log("Reading all keys and the appropriate modules");
var modules = {};
var macros = Object.keys(config.modules).map(function (k, i) {
    var macro = "${"+k+"}";
    var m = config.modules[k];
    if (!fs.existsSync(m)) throw "Stopped reading modules at ${"+k+"}: "+m+" Please correct or remove the entry.";
    modules[macro] = fs.readFileSync(m, "utf8");
    return "\\"+macro;
});
console.log("Instantiating RegularExpression with ${macros}");
var expression = new RegExp(macros.toString().replace(/,/g,"|"), "g");
console.log("Replacing the variables in the Template String from config.input (my main job).");
var output = input.replace(expression, function (macro) {
    console.log("inserting "+macro);
    return modules[macro];
});
console.log("(Over)writing output to "+config.output);
fs.writeFileSync(config.output, output);
console.log("Finished");

Es funktioniert ganz einfach. Man definiert eine Konfiguration, ein JSON Objekt, mit { input: "datei", modules: { "key1": "datei2", .... }, output: "dateix" } und schreibt in seine input-datei ${key1} bis ${keyn} in doppelten Curlies nach Art TemplateSubstitution Notation gesetzt. Man muss unique Identifier nehmen, um nicht mit dem TemplateString in ES6 zu kollidieren. Aber das ist jedem klar. Dafür habe ich das Programm gerade geaendert. Zuerst stand das Macro in {{}} wie in eine Substitute Funktion. Gelernt habe ich substitue oder string.replace(/{{([^{}]*)}}/g, function (wcurlies, nocurlies, indexof, input) { return data[nocurlies]; }) mal von {{Crockford}} und gesehen habe ich substitute in Action letztes Jahr in Videos über YUI.

{
    "input": "I.js",
    "modules": {
	"first": "T1.js",
	"second": "T2.js",
	"third": "T3.js"
    },
    "output": "O.js"
}

Das war die Konfiguration, hier kommt das Template

function A() {
${first}
}
function B() {
${second}
}
${third}
console.log(A()+" "+B()+" "+C());

Das steht stellvertretend fuer ein Programm. Jetzt folgt ${first}.

    // T1.js template 1
    return "Ein eingesetztes Stueck Code, was alles sein koennte...";

Das ist ${second} oder config.modules["second"] = "T2.js";.

    // T2.js template 2
    return "und ein anderes Stueck Code.";

Das ist ${third} oder config.modules["third"] = "T3.js";

// T3.js drittes Template
function C() {
    return "Ganze Module koennen das sein.";
}

Dann startet man das Programm mit node buildsrc.js config.I. Ich habe es .I genannt, weil ich input .I genannt habe. Natuerlich ist das jetzt keine Konvention, sondern der erste Name, den ich benutzt hatte, als ich das Programm begann. Was unpassenderes fiel mir nicht mehr ein. Andere benennen ihre Programmiersprache, die dafuer verwendet wurde gar nach der Orga, wo sie es veröffentlicht haben. Wobei der Name davor noch schwachsinniger war. Klingt doch gut, oder so als ob ich mit config.I fuer I.js durchkommen. Natuerlich kann man es auch I.config.json oder anders nennen.
break
Jedenfalls schreibt das Programm, nachdem es die Eingabedateien gelesen und alles in dem grossen InputString mit den Variablen ersetzt hat, die Ausgabe.

Reading configuration from config.I
Reading input file I.js
Reading all keys and the appropriate modules
Instantiating RegularExpression with ${macros}
Replacing the variables in the Template String from config.input (my main job).
inserting ${first}
inserting ${second}
inserting ${third}
(Over)writing output to O.js
Finished

Und heraus kommt folgendes Programm.

function A() {
    // T1.js template 1
    return "Ein eingesetztes Stueck Code, was alles sein koennte...";
}
function B() {
    // T2.js template 2
    return "und ein anderes Stueck Code.";
}
// T3.js drittes Template
function C() {
    return "Ganze Module koennen das sein."
}
console.log(A()+" "+B()+" "+C());

Neue Ideen?

Constructor, CallExpression, Literal

Gestern bemerkte ich bereits einen signifikanten Performanceverlust, als ich einige ECMAScript Interna in Betrieb nahm. Ich dachte, es liegt an den Konstruktorfunktionen und wollte mir vorschlagen, zumindest einfache Funktionen zu nehmen, die Literale zurueckwerfen. Ein Irrtum.

Gerade hatte ich den Test gemacht und das Literal gewann vor dem Konstruktor, mit Function zuletzt. Ein weiterer Irrtum, wie sich bei der Wiederholung herausstellt, die ich machen musste, weil ich new callexpress() statt callexpress() probierte. Jetzt liegen Constructor und Function vorne und Literal hinten. Warum das Literal jetzt nach entfernen des new vor callexpress() auf einmal 6ms mehr braucht ist mir schleierhaft, aber das Ergebnis scheint in mehreren Versuchen bei unterschiedlicher Auslastung des Computers zu halten.

var i, x;
function Constructor() {
    this.a = 1;
    this.b = 2;
    this.c = 3;
}
function callexpress() {
    return {
	a: 1,
	b: 2,
	c: 3
    };
}

console.time("Constructor");
for (i=0;i<10000;i++) {
    x = new Constructor();
}
console.timeEnd("Constructor");

console.time("Function");
for (i=0;i<10000;i++) {
    x = callexpress();
}
console.timeEnd("Function");

console.time("Literal");
for (i=0;i<10000;i++) {
    x = { a: 1, b: 2, c: 3 };
}
console.timeEnd("Literal");

/*
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 8ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 1ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
Constructor: 2ms
Function: 2ms
Literal: 7ms
*/

Warum ich mir keine Meinung bilde, liegt am vorherigen Ergebniss, was verfaelscht ist, da ich new callexpress() statt callexpress() rief. Aber das Literal kommt da auf andere Werte.

/*
Achtung: hier wird "new callexpress()" gerufen. Darum ist das Ergebnis fuer
Function falsch.

[..]
Constructor: 1ms
Function: 9ms
Literal: 1ms
Constructor: 2ms
Function: 9ms
Literal: 1ms
Constructor: 2ms
Function: 9ms
Literal: 2ms
Constructor: 1ms
Function: 9ms
Literal: 2ms
Constructor: 2ms
Function: 9ms
Literal: 1ms
[...]

Seltsamerweise hat das Literal die Nase hier weit vorne.
*/

Hier wollte ich dem Literal bereits den Sieg zurechnen. Wie man sieht, muss man immer alles x-mal pruefen, bis man es glauben kann...

Ich halte mich jetzt erstmal zurueck, mir eine Meinung zu bilden. Und gehe das noch ein paar mal durch.

/*
Hier ist var ref = { a:1,b:2,c:3 };
und in Reference wird nur x = ref zugewiesen.
Constructor: 2ms
Function: 2ms
Literal: 7ms
Reference: 0ms
*/

Das Literal scheint sich nach seinem Sieg im ersten Versuch nun zurueck zu fallen und dauerhaft das letzte Ergebnis zu erzielen.

In Zukunft jsperf.com?

Eine gute Idee ist bestimmt, in Zukunft verstaerkt jsperf.com abzubrowsen. Bekannt aus anderen Artikeln und durch Profis, kann man hier (nur) JavaScript Performance-Tests finden. Hier kann ich bestimmt einige Ergebnisse einsehen, die ich immer wissen wollte. Waehrend Algorithmen von mathematischen-textuellen Rechenwegen und asymptotischen Vergleichen handelt, kann man in jsperf die Millisekunden direkt ablesen. Ich denke, das wird helfen.

Ordinary Objects and Function Objects

Wahrscheinlich wird das Beispiel hier schon stehen, bevor es läft. Hier probiere ich einen ordinären Object Environment Record, einen Function Environment Record, eine .thisValue aus. Das sind jetzt keine JavaScript Objekte oder Funktionen mehr, sondern interne Datenstrukturen. Was die Zugriffszeit gering erhoeht, aber das ist momentan nicht zur Sprache bringbar, es muss erstmal laufen. Ich denke, eval(Browser) wird funktionieren -tut es nicht, da default parameter verwendet werden, ich vergass-, toValue ergibt das gleiche, was der Test ergibt. Bis ich hier einen [x] fixed Status hinklebe. Dann laufen aber bereits die neuen Strukturen.

    var object = {
	name: "Edward Gerhold"
	a: function (first = "Edward") {
	    return this.b(first);
	},
	b: function (first, last = "Edward") {
	    return first+" "+last ==== this.name;
	}
    };
    object.a();
    

Umgebungen -> Soll gleich eine ObjectEnvironment mit 3 Bindings sein, von denen gleich zwei Stueck wiederum FunctionEnvironments sind. Das Objekt wiederum speichere ich in einer deklarativen VariableEnvironment in der GlobalEnvironment ab. In der GlobalEnvironment und den FunctionEnvironments kann ich VariableStatements und FunctionDeclarations hoisten. Was ich bislang auch gekonnt haette, abern noch nicht gemacht. Der AST ist abzuscannen.

(Bereits hier wird es wichtig, dass der line-Counter a.s.a.p. richtig mitzaehlt, weil es mehrere Stellen gibt, wo ich besser wissen sollte, wo ich gerade bin, zum Beispiel, wann die Variable zu initialisieren ist. Dann kann ich den AST bereits kuerzen. Ich ueberlege, ob ich beim "hoisten" bereits eliminieren oder auscanceln soll.)

Probiert mit ToValue klappt es eh nicht. Ich habe zwar eine thisValue auf der ParameterZeile der Funktion, aber dem Identifier ThisExpression nicht (mehr) das richtige Objekt zugewiesen. Das macht aber nichts. Das Objekt ist gleich eine Datenstruktur, die Variablen mit ihren Records haelt. Und aehnlich so die Funktion. Und da gibt es dann eine thisValue Property und eine Funktion, die das setzen und lesen kann, die wiederum nur auf das Objekt (ObjectEnvironment) zeigt.

    FAIL: Exception thrown at Function ( {
	var src= "var object = { name: 'Edward Gerhold...: Cannot read property 'b' of undefined
    

Natuerlich habe ich syntest.js direkt einen schweren gemischten Fall hinzugefuegt. Das geht jetzt blitzschnell, Fortschritte zu pruefen, anstatt, dass ich im Browser neu starten muesste. Ich habe bislang keine Realtimetools installiert. Durch den Kommandozeilentest eruebrigt sich der Browser insgesamt erstmal.

syntax.js-Tests

Wir sehen hier nichts, keine automatischen Tests, keine Continuous Integration, keine Test Coverage Analyse. Nur Bugs, aber ohne Bugtracker.

Meine Platte braucht, weil sie alt und kaputt ist, mehrere Sekunden, bis sie hochspint (richtig langsam geht die los). Der Editor braucht bei 933 MHz und nur (!) 5400 Zeilen Code fast 10 Sekunden (!) um aufzugehen. Der Browser braucht ewig, um umgeschaltet zu werden. Der Computer ist einfach uralt und lahm. Und genauso schwierig ist es, Fehlerlesen zu veranstalten. Da sind ein paar solide Tests, die man nach mehreren Changes rufen kann um zu gucken, ob alles andere ebenfalls noch laeuft, besser. Mir sollte das, wie jedem anderen, eine ganze Menge Zeit sparen. Besonders, wo der Rechner hier zu alt ist.

Syntax.js ist unser Testling für heute. Test262 wird das Ding vielleicht nie absolvieren koennen. Vielleicht aber auch irgendwann doch. Irgendwomit muessen wir anfangen. Herausgekommen ist auf einmal folgendes.

var syn = require("./syntax.js").syntaxjs;
var Test = require("./tester.js").Test;
var test = new Test();
test.add(function () {
    var r = syn.toValue("1+2");
    this.assert(r,3, "1+2 should be 3");
});
test.add(function () {
    var r = syn.toValue("let b = 1+2; b;");
    this.assert(r,3, "b should be 3");
});
test.add(function () {
    var r = syn.toValue("const c = 1+2; c;");
    this.assert(r,3, "c should be 3");
});
test.add(function () {
    var r = syn.toValue("let {first,last} = {first:'Edward',last:'Gerhold'}; first+' '+last;");
    this.assert(r,"Edward Gerhold", "{first, last} sollten Edward und Gerhold sein.");
});
test.add(function () {
    var r = syn.toValue("function f(...rest) { return rest } f(3,4,5)");
    this.deepEquals(r, [3,4,5], "f. f(...rest) (ohne ;) returns rest");
});
test.add(function () {
    var r = syn.toValue("function f(...rest) { return rest; } f(3,4,5);");
    this.deepEquals(r, [3,4,5], "f. f(...rest) (mit ;) returns rest");
});
test.add(function () {
    var r = syn.toValue("var x='x', y='y'; let r={x,y}; r");
    this.deepEquals(r, {x:"x",y:"y"}, "{x,y} desugars into {x:x, y:y}");
});
test.add(function () {
    var r = syn.toValue("var x=1; let y=2; var r = {x,y}; r;");
    this.deepEquals(r, {x:1,y:2}, "{x,y} desugars into {x:x, y:y} - anders geschrieben.");
});
test.add(function () {
    var r = syn.toValue("class Person { constructor(x,y) { } }");
    this.assert(r, undefined, "Nur eine Klasse deklarieren sollte undefined ergeben.");
    
});
test.add(function () {
    var r = syn.toValue("var x = 10;");
    this.assert(r, undefined, "Nur eine Variable var x = 10 deklarieren sollte undefined ergeben.");
    
});

test.run();
test.print();

Eine Klasse, die mehrere Testfaelle halten kann. In dem Fall habe ich das so geregelt, dass ich alle Funktionen ueber this rufen kann. Zuletzt hatte ich meinen handgeschriebenen CSS-Selektor getestet. Dadurch kann ich in den Test-Callbacks einfach Datensaetze erzeugen, die hinterher ausgegeben werden koennen. Wie man sieht, kann man einen laufenden Tester mit vier Funktionen und zwei Arrays programmieren. Schon hat man eine Moeglichkeit, seine Tests uebersichtlich zu ordnen.

exports.Test = SimpleTest;
function SimpleTest () {
    this.tests = [];
    this.results = [];
}
SimpleTest.prototype = Object.create(null);
SimpleTest.prototype.add = add_test;
SimpleTest.prototype.assert = assert;
SimpleTest.prototype.deepEquals = deepEquals;
SimpleTest.prototype.print = print_results;
SimpleTest.prototype.process = process_results;
SimpleTest.prototype.run = run_tests;
SimpleTest.prototype.stringify = stringify;
var Pass = {
    "true": "PASS",
    "false": "FAIL"
};
function stringify (obj) {
    var s, k;
    if (Array.isArray(obj)) {
	s="[";
	for (k in obj) if (Object.hasOwnProperty.call(obj, k)) s+=obj[k]+",";
	s+="]";
    } else if (typeof obj === "object" && obj != null) {
        s = "{";
	for (k in obj) if (Object.hasOwnProperty.call(obj, k)) s+=k+":"+obj[k]+",";
	s+="}";
    } else {
	s = Object.prototype.toString.call(obj);
    }
    return s;
}
function assert(act, exp, mess) {
    var pass = act === exp;
    var s = Pass[pass]+": act="+act+", exp="+exp+":"+mess;
    var rec = {
	pass: pass,
	type: "assert",
	actual: act,
	expected: exp,
	message: mess,
	print: s
    };
    this.results.push(rec);
    return rec;
}
function deepEquals(act, exp, mess) {
    var pass, s, keys;
    if (typeof act != typeof exp) pass = false;
    else {
	if (Array.isArray(exp)) {
	    pass = exp.length === act.length;
	    if (pass)
	    pass = act.every(function (acti, i) {
		return acti === exp[i];		
	    });
	} else if (typeof exp === "object") {
	    pass = (keys=Object.keys(act)).every(function (k) {
		return act[k] === exp[k];
	    }); 
	    if (pass)
	    pass = Object.keys(exp).length === keys.length;
	} else {
	    pass = act === exp;
	}
    }
    s = Pass[pass]+": act="+stringify(act)+", exp="+stringify(exp)+":"+mess;
    var rec = {
	pass: pass,
	type: "deepEquals",
	actual: act,
	expected: exp,
	message: mess,
	print: s
    };
    this.results.push(rec);
    return rec;
}
function add_test(f) {
    this.tests.push(f);
}
function run_tests() {
    var that = this;
    this.tests.forEach(function (test, i) {
	var r;
	try {
	    r = test.call(that)
	} catch(ex) {
	    that.results.push({
		pass: false,
		type: "Exception",
		test: test,
		print: Pass[false]+": Exception thrown at "+test.toString().substr(0,65)+"...: "+ex.message
	    });
	    console.log("EXCEPTION THROWN AT TEST "+i);
	}
    });
}
function process_results(f) {
    this.results.forEach(f);
}
function print_results() {
    this.results.forEach(function (rec) {
	console.log(rec.print);
    });
}

Ich ueberlege, ob ich das Test Objekt anstelle als this (hat alle Funktionen als this Properties, ebenso wie tests und results) als Parameter function test(utils) { utils.assert(); } uebergeben soll. Aber eigentlich gibt es an dem this nichts zu ruetteln. Ich glaube nicht, dass ich damit Probleme kriege.

Die Ergebnisse des oben angezeigten Tests mit node syntest.js.

EXCEPTION THROWN AT TEST 8
PASS: act=3, exp=3:1+2 should be 3
PASS: act=3, exp=3:b should be 3
PASS: act=3, exp=3:c should be 3
PASS: act=Edward Gerhold, exp=Edward Gerhold:{first, last} sollten Edward und Gerhold sein.
PASS: act=[3,4,5,], exp=[3,4,5,]:f. f(...rest) (ohne ;) returns rest
PASS: act=[3,4,5,], exp=[3,4,5,]:f. f(...rest) (mit ;) returns rest
PASS: act={x:x,y:y,}, exp={x:x,y:y,}:{x,y} desugars into {x:x, y:y}
PASS: act={x:1,y:2,}, exp={x:1,y:2,}:{x,y} desugars into {x:x, y:y} - anders geschrieben.
FAIL: Exception thrown at function () {
    var r = syn.toValue("class Person { constructor...: Sorry, no evaluation of ClassDeclaration today.
PASS: act=undefined, exp=undefined:Nur eine Variable var x = 10 deklarieren sollte undefined ergeben.

Kann sich sehen lassen. Dachte ich mir. So schmiert man einen TestFall (der aus mehreren Tests besteht) hin. Ich merke, ich kann mich nicht mehr ganz dran erinnern, wie das alles auf Deutsch heisst. Aber das ist ja nicht schlimm. Mit wenigen Zeilen Code kann man sich was zum Testen schreiben.

Neues ECMAScript 6 Draft: 15. Juli

Auf http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts kann man eine aktuelle Version des neuen ES6 Drafts ansehen. Ich denke, es wird dann auch bald unter http://people.mozilla.org/~jorendorff/es6-draft.html online sein. Und https://teramako.github.com/ECMAScript/ecma6th-syntax.html wird es auch updaten, denke ich.

Die Changes in den Kapiteln, die ich gerade studiere sind minimal, hier mal ein Zeichen, da mal ein Zeichen, aber das wars.

mehr ToValue()

Wenn ich gleich nicht mehr an to Value schreibe, weil ich dem ECMA-Interpreter naeher kommen will, indem ich die Spec lese, dann fixe ich erstmal die Bugs im Tokenizer und im Parser, damit toValue korrekte Ergebnisse hat. Waehrend ich die identifiers, variables und globals gegen Evironments austausche und eine Completion, statt jsval passe, von der ToValue spaeter nur den Wert zurueckgibt. Oder ich definiere in einem Dokument, dass die Variablen, so wie ich sie formuliert habe, dem Teil der Spec da entsprechen. ;-)

    let bin = 0b111;
    console.log(bin === 7);
    console.log(bin === 0b111);
    console.log("Die selbstprogrammierte console Funktion gibt undefined zurueck, und der Rueckgabewert wird nach der Funktion aufgegriffen, weil es keine FunctionDeclaration sondern 'natives' JavaScript ist. Das aendert sich im Laufe der Wochen, wenn ich mal bei den Builtins bin");
    

Ich habe bereits ein kleines simpletest.js begonnen, womit ich ToValue einige Tests schreiben kann. Das geht schneller, als im Browser einzelne Beispiele auszuprobieren.

    return "Sollte ein Fehler sein, denn im Programm darf man nicht returnen.";
    

Es wird aber noch Stunden und Tage (ich mache immer nur vormittags ne Stunde oder zwei, dann den Tag ueber nichts, und abends nochmal) dauern, bis. Und weil ich nichts neues habe, notiere ich Absaetze wie diesen hier. Jetzt werde ich erstmal weiter gruppieren. Das Anwenden auf den Code funktioniert aber wunderbar.

Break ist im Hauptprogramm wie in Funktionsbodys verboten, und gilt nur in Iterationen.

    break duerfte_auch_nicht_im_script_body_stattfinden;
    

Kein continue

    continue;
    

Ob ein Fehler rauskommt?

    throw EvalError("Na, ob du dich dran erinnerst? Throwst du, if throws?");
    

Dabei faellt mir auf, dass ich meinen StackFrame nochmal auf das Ding auftragen darf und den Stacktrace aus meinem Stackframe Objekt erzeugen muss.

(viel zu lernen)

0b111 mit Left Shift

Weil ich sah, daß +(number.substr(2)).toString(10) ohne das 0b oder 0B am Anfang aber auch bei Basis 10 normale Hundertelf rauskommen, wenn man 111 umwandelt (is ja klar, ... und ES5 keine BinaerLiterale kann). Ich kann 255 mit toString(2) in Binaerzahlen verwandeln, aber nicht umgekehrt. Ich denke, das kann man mit dem left-shift Operator machen, der den Wert der Stelle um die Anzahl der Stellen nach links shiftet. Von rechts nach links wachsend, kriegt man dann die richtige Zahl raus.

	var number = "0b111";
	var computed = 0;
	for (var a=2, b=number.length-1; a <= b; a++) computed += (+number[a] << (b-a)); 
	computed;
	// 7
	// Run: Wenn man auf eval(Browser) statt auf ToValue(AST) klickt, kann man das lesen, dass es stimmt. Ich muss for noch machen. Dann gehts bei beiden.
    

Kann man auch mit ToValue(). Das kann wegen let und 0b eval(Browser) aber nicht.

    let b = 0b1111;
    b;
    

Das sollte die richtige Zahl ergeben und hoffentlich ist das auch im AST unten bei den Zahlen zu sehen, bei denen ich mich wundere, dass irgendeine node nicht gefunden werden kann, weil das Beispiel vorher bereits lief und 219 am Ende rauskam.. Gut ist.

Ich habs noch nicht probiert, aber ich denke, es geht. also 111 nach links ergibt 7. Wenn ich es von Hand addiere. x+= 1 << 2; x+= 1 << 1; x+= 1 << 0; Das ergibt sieben bei mir. Das sollte der Tokenizer jetzt auch bereits richtig machen. Der, wo er alles hat, kurz umrechnet, was rauskommt.

(funktioniert)

UnicodeEscape

Immer noch nicht fertig

In Kapitel 7.8 von der ECMA Spezifikation steht die Character Value (CV) of UnicodeCodeEscapeSequence :: u HexDigit HexDigit HexDigit HexDigit is the code unit value that is (4096 x 1. Digit) + (256 x 2. Digit) + (16 x 3. Digit) + 4. Digit. Kurzgefasst.

Mir fiel auf, dass man CVs und MVs zum Beispiel als enum schreiben kann, aber in JavaScript auch als Hash, mit passendem Schluessel und Wert. In kompilierten Sprachen hat man es mit enums natuerlich etwas besser, als mit ein paar HashTable inserts, aber hier fahren wir gut damit. Finde ich zumindest.

Inzwischen habe ich gelernt, wie Surrogate Pairs funktionieren, und dank M.Bynens kann ich inzwischen aus dem Kopf von dem codeunit 0x10000 abzuziehen, dann mit 0x0400 zu dividieren oder den Rest zu nehmen und dann 0xD800 zum ersten und 0xDC00 zum zweiten addieren. Surrogate Pairs koennen sein, wenn zwei \u nacheinander folgen. Sollten die in der Range bis 0xD800 bis 0xDFFF sein, aber kein Teil des Surrogate Pairs, wirds als code point mit demselben Wert interpretiert.

(noch in Arbeit)

Update: Weiter oben auf der Seite ist ein Auszug von Seite 14 der Ecma Spec.

syntax.js

Aus meinen Variablen breaks, continues, throws, returns mache ich einen CompletionRecord. Aber anstelle CompletionRecord.type zu pruefen, bleibe ich bei completion.breaks = true, etc. Ich denke, das darf ich implementationsspezifisch machen. ReturnIfAbrupt ist ja auch gut als C Macro, aber nicht als JavaScript Funktion, weil man danach deren return-Wert erst ein 2. mal gegenprüfen muß. Darum bleibe ich bei einem inline.

[...]

Laange TO DO Liste und viel Spezifikations Text, der einfach zu verstehen ist. Eine sinnvolle Aufgabe.

Die ECMAScript 6th Edition Draft-Spezifikation vom 14. Mai 2013

Jobcenter

Die coolste Nachricht der Woche war, ich war am Donnerstag im Jobcenter zum Gespräch, daß ich eine Weiterbildung machen kann. Ich habe es auf 6 Monate Linux-Administrator LPI-zertifiziert abgesehen. Sie haben ein OK gegeben, und ich bin das erste mal seit Jahren glücklich aus dem Laden raus. (Ich habe Jahre drum gekämpft das zu dürfen. Wie einigen anderen, wurde auch mir sonst nicht besonders zugehört, obwohl ich die ganzen Jahre einsatzfähig war.) Das heisst nicht nur büffeln (ich kenn die Curricula vom LPI seit 13 Jahren, und die sind garantiert mitgewachsen), sondern auch Chancen. Freue mich schon drauf. Hoffe von dem Institut, welches das anbietet, einen Platz zu kriegen.

syntax.js

Zur Zeit lese ich den ECMA Standard, um die ToValue() Funktion, die ich gerade wieder aufgegriffen habe, zu erweitern. Genaugenommen heisst es, sie umschreiben zu dürfen. Was ja nicht schwer ist. Hinzu kommt aber etwas mehr Aufwand für den Interpreter, den Code auszufuehren, weil er etwas tiefer in verschiedene Strukturen eindringen muss und mehr Funktionen zu rufen hat.

Hier ist mal ein Ausschnitt zum Completion Record, der den Zustand einer Funktion oder Schleife festhalten kann. Wenn man einen TypeError schmeissen soll, heisst das, einen CompletionRecord("throw", new TypeError(""), EMPTY) zu verwenden. Nach Beendigung der Arbeit wird der CompletionRecord dann ausgewertet und sorgt dafuer, dass abgebrochen wird, oder gesprungen, oder fortgefahren. Ein Befehlszeile zerfaellt in mehrere Schritte, die alle damit arbeiten koennen.

	var EMPTY = ""; // Ein leerer String fuer das .target Label der Completion
		    
	// Completion Records haben Informationen, wie die Ausfuehrung verlaufen ist
	// type = "normal", "return", "throw", "break", "continue"
	// target ist fuer ein labeled statement das entsprechende Label.
	// Sowas gibt es in jedem Interpreter, den man so geschrieben hat, in der oder der Form
	
	function CompletionRecord (type, value, target) {
	    this.type = type;
	    this.value = target;
	    this.target = value;
	}
    
	// Die Funktion kommt jeden Algorithmus dran:
	// Beziehungsweise, das ist keine Funktion, sondern ein gesprochener Algorithmus,
	// der in der Spec "wie eine Funktion" dran kommt. In C leicht durch MACROS (#define)
	// zu realisieren, in JavaScript aber besser in-zu-linen.
    
	function ReturnIfAbrupt(arg) {
	    if (arg.type !== "normal") return arg;
	    else return arg.value; 
	}
	
	
	function NormalCompletion (value) {
	    return new CompletionRecord("normal", value, EMPTY);
	}
	
    

Und das ist noch gar nichts. Es wird aber auch nicht viel schwerer. Nur umfangreicher. Aber auch das ist auf ca. 400 Seiten grosszuegig verteilten Text beschraenkt.

parser api updated

Abstract Syntax Tree: Inspiriert hatte mich dieses Dokument: SpiderMonkey/Parser_API. Esprima, Escodegen und Acorn verwenden dieses Dokument ebenfalls zur Definition ihrer Syntaxbaeume. Seit dem letzten Besuch vor 6 Monaten hat sich in diesem Dokument was getan, ich konnte einige Neueintraege wie For..Of oder ArrowFunctions identifizieren. Ebenso wie einen anderen Baum (eigene Felder) fuer Default oder Rest. Hier habe ich dann ebenfalls zu editieren.

code annotation

Ein haessliches Tool. Aber ich gedenke, die Annotationen, wenn man den Source hovered zu erweitern und dazu muss ich mehrzeilige Beschreibungen in einen String fuer das AnnotationsObjekt zusammenfassen. Die Grundidee tostring.js soll dann escapen koennen _und_ html Optionen haben.

/* Tool, was einzeilige escapte Strings aus mehrzeiligen Texten erzeugt. */
var fs = require("fs");// in neuen Node Versionen entfaellt das require interner Module
var f = process.argv[2];
if (!fs.existsSync(f)) throw "node tostring.js tostring.txt, parameter missing."
var text = fs.readFileSync(f, "utf8");
var c, str = "";
var escape = {
    "\n":"\\n",
    "\r":"\\r",
    '"':'\\"',
    "'":"\\'",
    "\t":"\\t"
};
for (var i = 0, j=text.length; i < j; i++) {
    c = text[i];
    c = escape[c] || c;
    str += c;
}
str = "\"" + str + "\"";
process.stdout.write(str);

/* Eingabe:
Das ist eine normale Dokumentation für meine Homepage.
Hier schreibe ich eine Zeile.

    Dann gibt es hier eine Zeile.
    Und hier eine Zeile.
    
Nachher will ich hieraus einen String machen.
Alle Zeichen muessen escaped werden und ersetzt.

    function f() {
	console.log("Hallo Leute");
    }
    
Hier gibt es noch einige interessante Dinge zu sehen.
*/
node tostring.js tostring.txt
/* Ausgabe:
"Das ist eine normale Dokumentation für meine Homepage.\nHier schreibe ich eine Zeile.\n\n    Dann gibt es hier eine Zeile.\n    Und hier eine Zeile.\n    \nNachher will ich hieraus einen String machen.\nAlle Zeichen muessen escaped werden und ersetzt.\n\n    function f() {\n\tconsole.log(\"Hallo Leute\");\n    }\n    \nHier gibt es noch einige interessante Dinge zu sehen.\n"
*/

Ich denke fuer \t vier mal &nbsp; zu nehmen und fuer " " einmal &nbsp;, wenn html verlangt wird. (Sollte dem "Tool" die zwei Optionen goennen. Normales Escapen und ToHTML escapen.

syntax.js

...geht gleich weiter...

    // Funktionierende ToValue(AST) Beispiele gibt es bei If oder Destructuring u.a.
    var s = "";
    for (var k of [1,2,3]) {
	s+=k+",";
    }
    s;
    

Ich versuch auch mal, den ConsolePuffer selbst zu lenken, indem ich die Builtin Funktion bereits ersetze. (Nur innerhalb von ToValue())

Ich hab aber noch nen Bug beim BlockScope (erst zum Teil addiert, sollte aber eigentlich immer frische Blocks erzeugen, wenn Blocks eingesetzt werden. Und beim For, While, DoWhile, SwitchCase, (With [for convenience]).

	// Wenns fertig ist, erscheint es dort, wo die Ergebnisse erscheinen.
	console.log("Test");
	for (var k of ["a","b","c"]) console.log(k);
    

Und nochmal, solange for of nicht geht, geht das mit dem consolebuffer in dem Beispiel nicht.

    function log(s) {
	console.log(s);
    }
    log("Edward");
    for (let k of [1,2,3]) {
	console.log(k);
    }
    

Naja, Bugs im Blockscope sowie im Parser. Aber umschreiben muss ich ersteres jetzt sowieso und letzteres aendere ich dabei nebenbei ab. Brauch ich bei der Zeit, die ich am Rechner verbringe halt ein wenig laenger, aber macht das was? Nein.

IfStatement (Prototyp)

Der Maschine mal beibringen. Kontrollstrukturen sind das A und O in einem Programm. Ohne diese gaebe es keine Entscheidungen, wie durch If oder Else und auch keine Iterationen (Durchzähl-Schleifen) wie bei While oder For. Um den Interpreter dazu zu kriegen, dass ihr eure Programme drauf starten könnt, muß ich ihm natürlich den gesamten Befehlssatz der Sprache beibringen. Zur Zeit bin ich dabei, sofern ich mal wieder am Computer bin.

	if (true) {
	    "Richtig";
	} else {
	    "Falsch";
	}
    

Mal gucken, ob bei beiden IfStatements mit ToValue Richtig rauskommt.

	if (false) {
	    "Falsch";
	} else {
	    "Richtig";
	}
    
    

Jetzt muss die noch Syntax ohne Klammern kennen.

	// Vorher: ToValue() ergibt undefined, weil da am Ende ;; steht.
	// 	Beim Erzeugen des ASTs bereits sollte ich das EmptyStatement skippen (wuerde man beim Compiler-Optimieren 100% machen).
	
	// Update: Behalte den letzten Wert bei. Ist nahe der Anleitung, wo ebenfalls der vorherige Wert rauskommt.
	// Aber nicht gleich. Wird im Laufe der Fortentwicklung eh geaendert.
	
	function f() {
	    if (true) return "Richtig";
	    else return "False";
	}
	function g() {
	    if (false) return "Falsch";
	    else return "Richtig";
	}
	f()+" "+g();;
	
    

^ Wegen dem EmptyStatement am Ende kommt undefined raus. Es ist eine leere Funktion. Die sollte ich lieber ueberspringen.

	function f() {
	    if (true) return "Richtig";
	    else return "False";
	}
	function g() {
	    if (false) return "Falsch";
	    else return "Richtig";
	}
	f()+" "+g();
    

Und weil es so schoen ist, nochmal mit Curlies.

	function f() {
	    if (true) { return "Richtig"; }
	    else { return "False"; }
	}
	function g() {
	    if (false) {return "Falsch";}
	    else {return "Richtig";}
	}
	f()+" "+g();
	
    

Und ohne (ToValue(AST) druecken nicht vergessen)

	let s = "";
        if (true) s+= "Richtig";
	else s+= "False";
	s+=" ";
	if (false) s+= "Falsch";
	else s+= "Richtig";
	s;
    

Ich glaube es nicht, let is nach dem If immernoch "". Es war so. Ich hatte das += vergessen.

	let s = "Test";
        if (true) {
    	    let s = "Rest";
	}
    	s;    	
    

vEin Anfang?

Januar: Die function flip_nodes() ordnet BinaryExpressions nach der Operator Precedence. Flip Nodes ist eine Rotationsfunktion. Im Januar wusste ich aber noch nicht, wie so eine Baum-Rotation funktioniert und bin da so drauf gekommen.

	// Ergibt 142.
	var a = 10, b = 11, c = 12;
	a+b*c;
    

Die ToValue() virtuelle Maschine, die schon heute ein wenig ES6 Code ausführen kann, hatte in ihrem ursprünglichen Nachmittagshack vom Januar einen zu grossen Aufwand betrieben, um Variablen zu deklarieren. Es gab gleich "identifiers, values, types, astnodes" fuer je eine Variable. Als ich mir den Code jetzt nach einem halben Jahr und einem guten Semester ueber Algorithmen durchlas, wusste ich, dass zu kürzen ist.

	// Kann man eine Property mit undefined initialisieren? Ja. (Klasse!)
	
	>var o = {};
	>o.x = undefined
	>for (var k in o) console.log(k);
	x
    

Initialisieren mit undefined ist also for free. Das ist super.

So hatte ich dann innerhalb von 20 Minuten alle relevanten Teile des Codes ausgetauscht und alles funktioniert nur noch mit einem identifiers Object, was ich aus Verständlichkeit noch in Scope umbenennen sollte. Denn mit wachsendem Callstack waechst auch das Objekt zu einer Kette heran, die funktioniert wie eine Scope Chain.

    // ToValue(AST) ergibt Incredible Machine (very simple)
    function f() {
	return "Incredible Machine (very simple)";
    }
    f();
    

Zur Zeit kann der Parser noch nicht alles parsen und der Interpreter noch nicht alles interpretieren. Dem Tokenizer scheint nur noch der Unicode fuer ID_Start und ID_Continue zu fehlen. Was einen, ich habe nachgeforscht, sehr komplizierten RegExp ergibt. Ich denke an O(ranges), Ein faellt mir aber ein moeglicherweise sehr grosses Objektliteral mit O(1), oder ein Vergleich der Ranges mit Binary Search fuer O(lg ranges). Wird sich demnaechst herausstellen.

	// ToValue(AST) ergibt 142
	var d = 10, e = 11, f = 12;
	function g(a,b,c) {
	    return a+b*c;
	}
	g(d,e,f);
    

f(d,e,f) ergab NaN, was ein Programmfehler ist.. Aber f(n) ergibt meinen Namen.

Dann fiel mir auf, dass ich f mal nach g umbenennen sollte.. funktioniert wohl.

    // ToValue(AST) ergibt Edward Gerhold
    function f(n) {
	return "Edward " + n;
    }
    f("Gerhold");
    

Testen wir mal, ob ich eine Variable ausserhalb der Funktion innerhalb der Funktion habe.

	// ToValue(AST) ergibt 242
	var d = 10, e = 11, f = 12, h = 100;
	function g(a,b,c) {
	    return a+b*c + h;
	}
	g(d,e,f);
    

Hier nochmal mein ES6 Beispiel vom BindingLiteral.

    // ToValue(AST) ergibt {first:"Edward",last:"Gerhold"}
    var first = "Edward", last = "Gerhold";
    var z = {first,last}; // ES6 Shorthand für {first:first,last:last}
    JSON.stringify(z);
    

JSON.stringify ist kein Trick. Anstelle einer AST-FunctionDeclaration wird hier "natives" JavaScript ausgefuehrt. Das sind zwei Zweige in der CallExpression, die ich gehen kann, um Funktionen zu rufen. Da ich Object und Array und Co erstmal neu implementieren muesste, habe ich mir erlaubt, "natives" JavaScript rufbar zu machen.

Swapping (erweiterte AssignmentExpression)

    // ToValue() ergibt nun 20 (1. Tag [x] (*))
    
    var a = 10, b = 20;
    [b,a] = [a,b];
    a;
    

Hier ist die Linke Seite noch ArrayLiteral genannt. (*)

(*) Doch irgendwann, dann, dann kommt er, der Refaktor-Mann. Der Refaktor-Mann.

Default Parameter

(wie in mozjs17 und firefox schon ewig laeuft)

    // ToValue() gibt jetzt korrekt die Default Werte aus. (1. Tag [x])
    function f(a=10, b=20, c=30) {
	return [a,b,c];
    }
    f();
    
	if (p.type === "DefaultParameter") 
	    identifiers[p.id.value] = ToValue(p.init);
	else identifiers[p.value] = undefined;
    

Den AST dazu gab es schon vor sechs Monaten.

Nun funktioniert auch ToValue()

    // Was passiert, wenn ich einen Parameter addiere? (1. Tag [x])
    function f(a=10, b=20, c=30) {
	return [a,b,c];
    }
    f(1);
    

Destructuring

Ich glaube ich habe das Destructuring auf der Funktionszeile vergessen.

    // Extrahiere first und last aus dem Objekt und weise sie ed und ger zu (2. Tag [x])
    let {first: ed, last: ger} = { first: "Edward", last: "Gerhold" };
    ed+" "+ger;
    

Auch hier gibt ToValue(AST) die richtigen Values aus.

    // Extrahiere first und last als first und last aus dem Objekt (2. Tag [x])
    let {first, last} = { first: "Edward", last: "Gerhold" };
    first+" "+last;
    

Ob das auch mit Arrays geht?

	// ToValue(AST) ergibt den richtigen String
	
	let [ed, ger] = ["Edward","Gerhold"];
	ed+" "+ger;
    

Rest und Spread

Den AST Eintrag gibt es ja schon seit Januar.

Die RestArgumente habe ich in wenigen Minuten zum Laufen gebracht gehabt.

    // RestArguments. ToValue sollte [1,2,3] ausgeben. Aber das kann noch Tage dauern. (2. Tag[x])
    function f(...rest) {
	return rest;
    }
    f(1,2,3);
    

Spread war eigentlich schnell in das exisitierende Stueck CallExpression integriert.

Es fehlt aber noch das Destructuring auf der Zeile.

    // SpreadOperator. ToValue sollte [1,2,3] ausgeben. Aber das kann noch Tage dauern. (2. Tag [x])
    function f(...rest) {
	return rest;
    }
    f(...f(1,2,3));
    
    // Spread funktioniert auch, aber erstmal "provisorisch" (2. Tag [x]) . Warum provisorisch? Naechstes Beispiel.
    function f(...rest) {
	return rest;
    }
    f(1,...f(2,3,4), 5, 6);
    
    // Will noch nicht (ahne und weiss aber was ist der CallExpression schuld ist)
    function f(...rest) {
	return rest;
    }
    f("Das",...f("ist","toll","sagt"), ...f("Edward", "zu", "sich"), "!!!");
    
    /*
	Hier darf ich rest, defaults und spread neu programmieren.
	
	Im ersten Versuch hatte ich erstmal die Parameter auf die Definierten
	verteilt. Den Rest auf Arguments.
	
	Dann hatte ich rest hinzugefuegt.
	Dann noch die DefaultParameter.
	Dann Spread.
	
	Bei Spread habe ich fuer diese Option einen Counter eingerichtet.
	
	Weil das aber daemlich ist, und ich das selbst weiss, und mir 
	beim schreiben schon sagte, das darfst du neu formulieren,
	
	werde ich das auch machen.
    
    */
    
    

WhileStatement + Kontrollstrukturen To Do.

Hier geht es darum, den ToValue Button zu erweitern. Ab der Ueberschrift "IfStatement" und "Swapping", "Destructuring", "Rest", "Spread" funktionieren ein paar Beispiele.

Noch ohne Break Statement. (Aber nicht lange). Dann fehlt noch das LabelledStatement zum gezielten Brechen und Continuen.

	let i = 0;
	let a = [];
	while (i<5) {
	    a.push(i);
	    i+=1;
	}
	a;
    

Beide funktionieren aber noch nicht. Wie ich sehe ist am AST auch was nicht korrekt.

	let i = 0;
	let a = [];
	while (i<5) {
	    a.push(i);
	    i+=1;
	    if (i === 4) break;
	}
	a;
    

Mein Schatz hat gleich Geburtstag. Heute oder morgen wird das nichts mehr.

	var s = "";
	var obj = { das:true, will:true, ich:true, sehen:true };
	for (var k in obj) s+= " "+k;
	s;
    

Die Kontrollstrukturen muss ich alle zum laufen kriegen. Mit label:, break, continue.

    var s = "";
    var i;
    for (i=0; i<20; i++) {
	s+= i;
	s+= ",";
    }
    s;
    

Hier habe ich wieder was zu tun für den ToValue(AST) Button

To do

- ES6 features

Wenn ich das richtig sehe, sollte ich jetzt am Parser weiterarbeiten. Denn der kann ein paar wichtige Sachen noch nicht richtig. Zum Beispiel ClassDeclarations, die eine kurze Syntax fuer das bekannte Prototype Pattern bieten.

    // Hier kommt noch gar nichts raus. Der AST ist nicht fertig.
    
    class Person {
	constructor(first, last) {
	    this.firstName = first;
	    this.lastName = last;
	},
	getName() { 
	    return this.firstName+" "+this.lastName;
	}
    }
    let eddie = new Person("Edward", "Gerhold");
    eddie.getName();
    

Gut, so sollte ich einen Einstiegspunkt zum weitermachen haben. Sowie folgenden, damit ich was zu tun habe.

    let f = x => x*x;
    let g = (a,b) => a+b;
    let h = (a,b) => { a+=10; b+=10; return [a,b]; };
    

Achso, Array Comprehensions. Den Parser hatte ich noch nicht fertig. Mit dem ES6 Draft hatte ich erst angefangen, wo ich die ES5 Grammatik praktisch schon 1x durch hatte. Und im Februar hatte ich an dem Programm nicht mehr weiter geschrieben. Es gibt eine Comprehensionfunktion, die auch eine AST-Node erzeugt, wenn sie nach dem ersten Element auf for trifft. Aber sie erwartet glaube ich noch ein Komma.

linux-qc59:/mozjs17/js/src/shell # js17
js> var x = [1,2,3,4,5];
js> [ v for (v of x) if (v>1) ]
[2, 3, 4, 5]
js>   

Also Spidermonkey aus FF 17 kann das, wie zum Beispiel auch das Destructuring. Hier habe ich eine Vorlage.

    var x = [1,2,3,4,5];
    [v for (v of x) if (v > 1)]
    

Generator functions und Iteratoren werden hier noch nicht ausgefuehrt.

Ach! Und die Kontrollstrukturen muessen ebenfalls noch programmiert werden.

    // ToValue sollte demnaechst eine random Number ausgeben.
    function rand () {
	while (1) yield Math.random();
    }
    var r = rand();
    r.next();
    r.next();
    

Modules werden bislang noch nichtmal geparsed. (Doch, wenn ich es aufklappe, steht export aber ausserhalb vom Modul.)

Intern habe ich gestern zum einen das Promise fertig geschrieben, zum anderen ist es für function Module. Das ist eine AMD kompatible Module Funktion, mit der ich das Module dann intern emulieren kann. Ich hatte letztes Jahr mal Module und require geschrieben.

	module mod {
	    let z = 10;
	    function id (x) {
		return x;
	    }
	    export id;
	}
	import id from mod;
	id("Edward");
    

Wenn es fertig ist, sollte Edward rauskommen. Wird aber einige Tage dauern, bis ich das geschrieben habe. Ich sitze ja nicht lange oder gar ununterbrochen vor der ollen Flimmerkiste.

Ach, da gibt es noch mehr, was laufen muss, wenn man auf ToValue(AST) drueckt.

Eine lange TO DO Liste. Und das ist noch nicht alles, was ES6 zu bieten hätte. Noch lange nicht.

Jetzt fahr ich erstmal mit Racker zum Tierarzt. Und weil es dort immer sehr voll ist, und die, die nicht sofort bezahlen zuletzt dran kommen, es ist eine sehr gute Ärztin, mit Klasse Team, aber auch sehr gut besucht, denke ich, vor 23 Uhr nicht zurueck zu sein und darum heute auch nichts mehr zu schaffen.

MIT 6.035 Computer Language Engineering

Auf Deutsch: Compilerbau. Schon vor drei Monaten entdeckt, mache ich jetzt nebenbei das Kursmaterial, was meine eigenstaendige Arbeit dann hier und da abrundet, daß ich die Grenzen heutigen Wissens nicht überschreite.

MIT 6.451 Principles of Digital Communication I+II

Wie der Name schon sagt, kriegt man hier die Grundprinzipien der digitalen Kommunikation vermittelt. Das wollte ich schon immer mal gemacht haben. Das letzte was ich lernte, waren VoIP Protokolle, und das war ca. 2006.

Es gibt einen weiteren Kurs zur Wireless Communication und natürlich noch mehr zu den Themen. Es ist schön auf über 2000 Resourcen einer renommierten technischen Uni zugreifen zu können.

MIT 6.041 Probabilistic System Analysis and Applied Probability

Wahrscheinlichkeit, die zweite. Nach einer Einfuehrung in Probability in 6.042 Mathe für Informatiker, die ich im April oder Mai genossen habe, aber noch nicht praktisch anwende, gedenke ich, jetzt erstmal mit dem Kurs weiterzumachen und 6.042 zu wiederholen.

(Kurzer Auszug)

syntax.js

Im Januar hatte ich aus einem 99 Zeilen Syntaxhighlighter ein 4800 Zeilen Monstrum gebastelt. Mit Tokenizer, Parser, Engine, Editor. Nach hinten abnehmend. Im Januar hatte ich viel dran programmiert. Im Februar hatte ich was anderes gemacht. Ich glaube, ich war sogar kaum zuhause. Im März hatte ich mit OpenCourseWare angefangen. Im April (Mathe), Mai und Juni habe ich die Computerzeit mit Lesungen und ein paar Proben verbracht. Ich habe eine Menge über Komplexität gelernt. Im Januar hatte ich das Program mit Array.indexOf und Regulären Ausdrücken erstmal sehr lahm gemacht. Jetzt läuft jede Komponente für sich linear. Aber... Wenn alle drei laufen sollen, sollte ich sie zu einer zusammenfassen. Aber das ist nicht alles, was ich identifiziert habe, oder noch werde.

var binary1 = 0b1111;
let binary2 = 0B0000;
var hex = 0xFA34FF33;
var hex2 = 0XFF;
const dec1 = 10e6;
var dec2 = 10e+6;
var dec3 = 10e-7;
let dec4 = .77;
var dec5 = 6.77;
const dec6 = 5333.2542e+12;
var oct1 = 0o0555;
var oct2 = 0O333;

Hier gibt es eine Grammatik: die Grammatik. Beziehungsweise das Draft vom Maerz. Mit Grammatik und dem Rest.

Dann gibt es noch eins: die Grammatik. Die finde ich ganz hilfreich, um den Parser weiterzuschreiben.

var r = /a/igu;
var a = "a", b = 12;
a / b / c / d / e / f /g/h/i/j/k;	// Hier wurden fehlerhaft Regulaere Ausdrucke gefunden. Das ist aber noch nicht vollstaendig geregelt. Siehe naechster Fall.

b=a/a/.test(b); // Besonders gemeine minifizierte Zeile, wo ich im Tokenizer bereits die Expression links kennen muesste. Und rechts vom Ausdruck die CallExpression .test, die mir garantiert, das /a/ gecallt werden soll.

Dem Tokenizer muss ich also bereits was Intelligenz addieren. Einfach nur zerlegen reicht nicht ganz aus. (Dann pruefte ich erstmal meine Intelligenz, und ob sowas in anderen Engines geht.)

linux-qc59:~ # n
> var a = "a", b = "b";
undefined
> b=a/a/.test(b);
... .
Invalid REPL keyword
undefined
> b=a/a/.test(b);
... 
... 
... 
... .
Invalid REPL keyword
undefined
> 
(^C again to quit)
> 
linux-qc59:~ # n
> var a = "a", b = "b";
undefined
> b=a /a/.test(b);
... .
Invalid REPL keyword
undefined

V8 wehrt sich schonmal.

linux-qc59:/mozjs17/js/src/shell # js17
js> let a="a", b="b"; 
js> b=a/a/.test(b);
typein:2:0 SyntaxError: syntax error:
typein:2:0 b=a/a/.test(b);
typein:2:0 ......^
js> b=a /a/.test(b);
typein:3:0 SyntaxError: syntax error:
typein:3:0 b=a /a/.test(b);
typein:3:0 .......^
js> b=a;/a/.test(b);
true
js> 

Ok, damit ist der Fall schonmal geklärt, wieviel Intelligenz der Tokenizer braucht. Oder eher, wie das im Dokument definiert ist. Dazu ist die Spezifikation lesen und es wird schon klar. Ich muss noch die subsequenten Parser fuer den Ausdruck und das TemplateLiteral schreiben. Die Funktionen kann man hinterher mit so Strings fuettern und kriegt ein Ergebnis wie vom Tokenizer.

  • Syntax Testseite
  • Syntax Testseite 2
  • Hier sind ein paar Aufgaben für mich, die es zu vervollständigen gilt.

    let template = `Das ist ein ${template}`;
    
    const arrow_fun = x => x*x;
    
    function* rand () { while (1) yield Math.random(); }
    
    var Edw\u0061rd = "Edward";
    

    Wenn man sich die Token durchliest, wird der Unicode uebersetzt. Aber ich muss zugeben, mit einem Hack. String.fromCharCode geht nicht. Und String.fromCodePoint gibt es nicht. Aber eval. Ich glaube nicht, das ein Kommentar dabei Sinn macht. Da ist nichts zu hacken. Ich gebe den Code in "\'"+"\\u0061"+"\'" ein und erhalte einen String "a" zurueck. Wenn mir was besseres einfaellt, mache ich das. Das Problem mit den \\ hatte ich vor sechs Monaten schonmal und durch einfaches benutzen umgewandelt. StringLiteral, Comments sind aber ebenfalls zu ueberholen. Am Schluss wird alles ueberholt sein.

    ES6 Symbols

    Bislang gab es keine Moeglichkeiten, in JavaScript Properties vor anderen zu schuetzen, ausser sie unerreichbar in der Closure zu verstecken. Damit ist es schwierig, dran zu kommen, ausser man schafft interface Funktionen, die wiederrum von anderen genutzt werden koennen, ausser man nutzt eine Technik wie den Sealer/Unsealer den Crockford immer vorgestellt hatte.

    In ES6 gibt es bald Symbols. Angelehnt an Sprachen wie LISP, wo es das gibt, gibt ein Aufruf des Symbol Contructors ein frisches uniques Symbol. Dazu kommt eine neue Syntax, um computed Properties zu erzeugen. Symbolen werden in [] eingeschlossen.

        
    	let privateProperty = Symbol();
    	
    	var object = {
    	    [privateProperty]: function () { return "Man kann mich nur mit dem Symbol rufen."; }
    	};
    	
    	let s = object[privateProperty]();
        
        

    Das Symbol kann man auch an Freunde und andere API Anwender weitergeben, oder es direkt fuer sich behalten. Damit kann man auf einem Objekt auch Hilfsmethoden definieren, die man sonst neben das Objekt in einer Closure speichern musste, um nicht den globalen oder lokalen Namensraum mit "Muell" zu verstopfen. Hier muss man nur das Token (das Symbol) wieder passen. Punkt.

    Der typeof UnaryOperator bekommt dazu einen neuen Typen "symbol". Entgegen der Annahme, dass typeof null === "null" wird, bleibt typeof null === "object". Macht aber auch keine Probleme. Es gibt genug bekannte "Workarounds" oder "Prinzipien" wie das richtig funktioniert. Aber, "symbol" ist neu. Und steht so in Kapitel 11.4.3 in der May Spezifikation von ES6

    	let sym = Symbol();
    	typeof sym === "symbol";
    	// true
        

    Das Symbol wird einigen Code aufraeumen und ist eine sehr gute Idee.

    Open Course Ware

    Ich bin froh, Open Course Ware entdeckt zu haben. Und dafuer, dass es erst drei Monate her ist, bin ich ueberzeugt, auch schon was dazu gelernt zu haben. Ihr muesst den Fachkraeftemangel ja eh mit Langzeitarbeitslosen abdecken, weil es nicht anders geht, darum komm ich euch entgegen.

    Mittlerweile kann ich mit dem Kursmaterial auch ohne Lesungen arbeiten. Egal, welche Faecher. Ich verstehe, wie es vermittelt wird, und vermittel es mir selbst so. So machen die Kurse "ohne Video" richtig Spass.

    mozjs17

    Eine Woche nach dem Download dann mal kompiliert, habe ich die jsshell gestartet, die mit kompiliert wurde. Hier gibt es ein paar Dinge, die es bei Mozilla schon laenger gibt, die bald zu ES6 gehoeren.

    Default Parameters

    js> function f(a=10,b=20,c=30) { return [a,b,c]; }
    js> f();
    [10,20,30]
    

    Destructuring (extrahieren von Werten)

    js> function g({a,b}) { return [a,b]; }
    js> g({a:10, b:20});
    [10,20]
    

    Und natuerlich die ...rest Arguments.

    js> function x(...rest) { return rest; }
    js> x(1,2,3);
    [1,2,3]
    

    ...spread geht da aber nicht.

    js> x(...x(1,2,3))
    SyntaxError
    

    Ebenso SyntaxError bei Arrow Functions. let a = x => x*x; ergab einen Syntax Error. Auch wenn man (x) => x*x; oder (x) => { return x*x; } probiert. Der Fehler liegt beim => Arrow selbst.

    Danach habe ich js> this eingeben. Die Defaultshell ist also mit sehr interessanten Hilfsfunktionen ausgestattet. Und danach version(). Das Ergebnis war 185. Was natuerlich auf JS1.8.5 von 2011 hinweist. Das haette ich nicht erwartet. Weil diese mozjs17 offiziell von Maerz 2013 ist. Nun gut, ich weiss mal wieder von nichts. Ich bin ja auch alleine bei der Sache. Obwohl ich hier im Gruenderzentrum Deutschlands bin. Meine Frau interessiert das nicht besonders. Bin ich ja von zuhause gewohnt gewesen.

    Interessante News

    Ein neuer Monat, neue 5GB. Brendan Eichs Keynote beweist, wozu JavaScript inzwischen in der Lage ist. Douglas Crockford erklaert woanders, wie sein TDOP-Parser funktioniert. Promises sind im DOM4 angekommen.

    Von uns gegangen

    Am 26.6.2013 ist unser Freund und Kaninchen Gizmo verstorben. Auch im Juli will ich mich an ihn erinnern. Papa Edward, Mama Micro, Pünktchen und Racker haben dich für immer lieb.

    Juni

    Habe ich als juni-2013.html archiviert. Die Seite ist den Binary Search Trees, dem Splay Tree, AVL Tree gewidmet. Zeigt aber auch eine wichtige Benchmark, die zeigt, wie günstig Objektliterale, und wie gefäährlich Arrays, aber auch Regular Expressions, für die Performance von zum Beispiel Parsern sind. Ausserdem gibt es einen gerichteten Graphen mit konstantem Zugriff auf die Edges, Fibonacci und Recursion Trees, die für die Rekursionsanalyse unverzichtbar sind.