Fork me on GitHub
www.linux-swt.de

Edwards Hobby JavaScript aus dem Obdachlosenwohnheim seit 2012 Homepage
(ohne 3D-Grafikkarte, kein Multicore, nur ein PIII/933 mit 512MB)
mit neuem gebrauchten Core 2 Duo Notebook von hp mit 2x2.1 + 2GB

Oh. Bei den Template Darstellungen koennen mal <> fehlen und vom HTML Parser geschluckt worden sein.

Ausserdem ist die Seite wieder mal alles andere als sachlich, hehe. Von Zeit zu Zeit habe ich meine Notizen schnell hingeschmiert. Hier ist alles vorhanden Gefühlsargumente, kleine Fehler, schlechter Code. Aber wen stört´s? Im unteren Drittel sind noch ein paar hundert Tests von syntax.js zu lesen, das Interessanteste. Wie auch immer. Viel Spass beim surfen und weiter surfen. :-)

He´s back again: Rev23 EcmaScript 6 (April 5)

Endlich eine neue Ausgabe. Mir war zwischendurch schon langweilig, obwohl noch genug alte Bugs offen sind. Welche Gründe das haben mag, meistens Änderungen, die das arbeiten an bestimmten Stellen unnötig gemacht haben, aber das kann sich nicht über die Builtins hinziehen. Wie auch immer. Wenn der Download fertig ist, geht´s los.

http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#april_5_2014_draft_rev_23

Einbuchen, einbuchen, einbuchen.

Java8

Ich werde dann mal meine 5GB von ALDI dazu nutzen, mir die offizielle Java SE 8 einzurichten, dazu gleich mit NetBeans 8, wo sich die doch anbietet. Das tolle an Netbeans ist, dass sie auch JavaScript unterstützt und gar HTML5.

Korrektur: Die mitgebundelte netbeans Version unterstützt nicht alles, was ein regulärer netbeans Download unterstützt. So ist zum Beispiel kein HTML5 dabei sondern Java, JavaFX, maven. Den Rest kriegt man dann bei netbeans.

WebStorm 8 Trial

Nachdem ich den letzen Monat von der WebStorm 7 Trial gerade mal 4-5 Tage verbraucht habe, haue ich diesen Monat mit der neuen Webstorm 8 Trial die gesamten 30 Tage rein. Mit Webstorm ist die Arbeit an Syntax.js besonders einfach, weil das Arbeiten mit mehreren Dateien kein Problem ist.

Auf dem alten PIII/933 war es das und ich musste 1MB in ein File programmieren, bis ich jetzt mit mehrern Files arbeite. Inzwischen macht sich das Repository ganz gut für den Anfang. Bis zur Professionalitaet ist es noch ein ganzes Stueck, aber das kommt mit der Arbeit. Denn wer zwei Monate kein syntax.js macht, der ist auch da, wo er vor zwei Monaten war stehen geblieben. :) Und das ist in Sachen Software sehr lang, da die Tastaturen heute sehr schnell klicken und das meiste automatisch gemacht wird. Da kann man viel schreiben, was ich von den Tagen weiss, wo ich den Nachmittag mit durchgemacht habe.

Dragonbook

"Compilers: Principles, Techniques & Tools" von Aho, Lam, Sethi und Ullman ist ein Buch über Compiler. Ich habe Professor Aiken´s Compiler Kurs von YouTube. Er behandelt dort einige der Optimiertechniken ausführlich. Das Buch steht auf dem Cover der Bücher von Terrence Parr, der mit Language Implementation Patterns und der ANTLR Referenz sehr gut erklärt, worum es hier gerade geht. Allerdings so praktisch wie es geht. Er zeigt den Code aus dem seine Tools bestehen. Das Dragonbook bietet eine eindeutige Sprache aus Grammatik und Mathematik und ausführlichen Erläuterungen und ist etwa 1000 Seiten stark.

makeAdapter

Leid, die vielen JavaScript Shells unter Dach und Fach zu bringen?!

Wenn man wenigstens eine einheitliche Definition fuer alle mehrere-Systeme Funktionen haette.

Mit makeAdapter erschliesse ich mir diesen Traum und diese Luecke mit einer einfachen Funktion, die ein Objekt mit den Funktionen, Tests für das System und die Funktion zum Ausführen, wenn ein System gefunden wurde. Laesst sich ausbauen um default tests, die man nicht mehr programmieren muss, wie hasConsole() oder hasJava() oder isNode(), isBrowser() oder isWorker(), hasLoad().

Wer eine Funktion schreiben will, die auf mehreren Systemen läuft hat mit makeAdapter ein einheitliches Entwurfsmuster. Darauf werde ich alle dementsprechenden Funktionen demnaechst umgeruestet haben, damit auch sie einheitlich wirken.

Wem das Auswahlverfahren mit linearer Anzahl Tests zu kostspielig ist, je nach Anwendungszweck, kann den Adapter um eine fixe Property erweitern, die den Typ angibt, oder die detection by creation durchführen, oder möglicherweise anders anders abwandeln.

    /*
	var concreteAdapter = makeAdapter({
	    test: {			
		node: function
		browser: function
		worker: function
		sm: function
		any: function
	    },
	    work: {
		node: function 
		browser: function
		worker: function
		sm: function
		any: function
	    },
	    default: function () {
	    }
	});
	
	// var result = concreteAdapter(arg0,arg1,argn);
	
	// returns a function calling to call work[k] if test[k] 
	
	// starts only a work[k] if a test[k] is existing and returning true
	// is O(n) on testing, can be replaced by a fix property
    */
    
    function makeAdapter(methods, optionalThis) {
	if (arguments.length == 0 || typeof methods !== "object" || methods === null) {
	    throw new TypeError("makeAdapter(methods, optionalThis) expects { test: {}, work: {} } where work[ĸ]() will be called iff test[k]() succeeds. Both need to be functions. Optional is a methods.default function if no test succeeds.");
	}
	var keys = Object.keys(methods.test);
	return function adapterFunction () {
	    for (var i = 0, j = keys.length; i < j; i++) {
		var k = keys[i];
		var test = methods.test[k];
		if (typeof test != "function") {
		    throw new TypeError("adapter: adaptee.test['"+k+"'] is not a function");
		}
		if (test()) { 
		    var work = methods.work[k];
		    if (typeof work != "function") {
			throw new TypeError("adapter: adaptee.work['"+k+"'] is not a function");
		    }
		    return work.apply(optionalThis || this, arguments);
		}
	    }
	    if (typeof methods["default"] === "function") return methods["default"].apply(optionalThis || this, arguments);
	};
    }

Beispiel


    var load = makeAdapter({
    
	test: {
	    browser: function () { return typeof window === "object"; },
	    node: function () { return typeof process === "object"; }
	},
	work: {
	    browser: function (url, callback) {
		var xhr = new XMLHttpRequest();
		xhr.open("GET", url, false);
		xhr.send(null);
		if (xhr.status >= 200 && xhr.status < 400) {
		    return callback(null, xhr.responseText);
		} else {
		    return callback(xhr.responseText, null);
		}
	    },
	    node: function (url, callback) {
		var ex, data;
		try {
		    data = fs.readFileSync(url, "utf8");
		} catch (e) {
		    ex = e;
		} 
		if (ex) {
		    return callback(ex, null);
		} else {
		    return callback(null, data);
		}
	    }
	},
	default: funciton () {
	    throw new TypeError("Sorry, this function is not supported in your system until today.");
	}
    })

Ob die geht, weiss ich nicht. Das ist jetzt PseudoCode gewesen, aber valides JavaScript.

Refactoring with Esprima

Meine [[DefineOwnProperty]] CallExpressions, welche CreateBuiltinFunction rufen und manuell PropertyDeskriptoren in den Argumenten tragen und durch eine Zeile Code ersetzt werden, nachdem die Funktion selbst an eine spezielle Variable jeweils zugewiesen wurde, um die mehrfach kompilierung von den Builtins zu ersparen und jedes Realm nur die Objekte (inkl. FunktionsObjekten) neu zu assemblen, die [[Call]] Fkt. aber nur einmal.

Dazu muss man aber jede Funktion aus dem Bauch der Parameter von CreateBuiltinFunction holen, und das [[DefineOwnProperty]] durch die VariableDeclaration und die CallExpression für die LazyDefineOwnProperty Variante (lazy heisst faul, nicht zeitversetzt, später, in dem Falle, weil ich mir das wiederholte eingeben von CreateBuiltinFunction in einem PropertyDeskriptor spare mit exakt gleichvielen argumenten. Je nach PropertyDeskriptor wird eine andere Define Funktion gerufen, die diese intern beinhaltet. Hier kann man nichts über-geben, es geht um´s re-writen des Codes nur automatisch wie eine IDE.)

/*
    refactorDOP.js
    
    DOP means [[DefineOwnProperty]]
    
    the aim of this tool is to replace
    a few hundred entries of DefineOwnProperty
    with a value of CreateBuiltinFunction or
    some other value (unimplemented)
    to replace the call with a call to another
    function, which saves some work and LOC,
    but would be too much to be changed manually.
        
    Here i can learn refactoring code with
    
    Mozilla Parser_API and syntax.js
    
    - first by hand.
    
    - Hope to find patterns.
    
    -- yes, read the code, analyze what to do.
    i just figured out a little and have no good
    nodeLocator()  or querySelector() for the AST yet. --
    
*/


var esprima = require("esprima");
var syntaxjs = require("syntaxjs").syntaxjs;
var withEsprima;

var fs = require("fs");

var state = {};

function getAst(file) {
    var code = fs.readFileSync(file, "utf8");
    if (withEsprima) {
    console.log("parsing "+file+" with esprima");
    return esprima.parse(code);
    }
    //console.log("hanging "+file+" with syntax.js (temp)");
    console.log("parsing "+file+" with syntax.js");
    return syntaxjs.parse(code);
}

function error(message) {
    return new Error(message);
}

function do_search(node) {
    state.sources = [];
    state.sourceStack = [];
    state.sourceStack.push(state.sources);    
    return search(node);
}

function search(node, parent) {

    // DO LISTS

    if (Array.isArray(node)) {
	node.forEach(function (child) {
	    search(child, parent);
	});
	return;
    }
    
    // Search for DefineOwnProperty(OBJ, NAME, DESC)

    if (node.type === "CallExpression") {
	if (node.callee.name === "DefineOwnProperty") {
	    console.log("found call to [[DefineOwnProperty]] ");	    
	    state.sources.push([node, parent]);
	}
    }

    if (typeof node === "object")
    Object.keys(node).forEach(function (k) {
	
	// visits more branches then necessary,
	// all DOP code is a top level callexpression
	// in a function body.
	// something to learn from
	
	switch (k) {
	    case "body":
	    case "expression":
	    case "sequence":
	    case "left":
	    case "right":
	    case "argument":
	    case "callee":
	    case "object":
	    case "value":
	    case "key":
		search(node[k], node);
	    break;
	    default:
	    break;
	}
    });

}

function do_replace () {
    state.current = state.sourceStack.shift();
    replace();
}

function replace() {

    state.decls = [];
    state.defines = [];
    state.replaced = [];

    state.current.forEach(function (array) {
	
	var node = array[0];
	var parent = array[1];
    
	// Die komplette CallExpression.
	// Auseinandernehmen, ersetzen.
	
	var params = node.arguments;

	var id = getValue(params[0]);
	var name = getValue(params[1]);	
	var desc = params[2];	
	// desc is ObjectExpression	
	var props = desc.properties; 
	    
        var enumer = false, 
	conf = false, 
	writ = false;
	
	var value, funcExpr;
	var ok;
	for (var i = 0, j = props.length; i < j; i++) {
	    var prop = props[i];
	    if (prop.key.name == "enumerable") enumer = getValue(prop.value);
	    if (prop.key.name == "configurable") conf = getValue(prop.value);
	    if (prop.key.name == "writable")     writ = getValue(prop.value);
	    if (prop.key.name == "value") {

		value = getValue(prop);
		if (value.type == "CallExpression") {
		
		    // suche:
		    // CallExpression
		    // callee: Identifier "CreateBuiltinFunction"
		    // arguments: [realm, funcExpr, flength, name]
		    if (value.callee.name == "CreateBuiltinFunction") {
			ok = true;			
						
			var args = value.arguments;			
			realm = getValue(args[0]);
			funcExpr = args[1];
			flength = getValue(args[2])|0;
			fname = getValue(args[3]);			
						
		    }	
		    
		} 
		
	    }
	}
	
	if (!ok) return;
	
	var callfn_VarName = id+"_"+name;	
	console.log("callfn_VarName = "+callfn_VarName);
	
	var varDecl = {
	    type: "VariableDeclaration",
	    kind: "var",
	    declarations: [
		{
		    type: "VariableDeclarator",
		    id: callfn_VarName,
		    init: funcExpr
		}
	    ],
	    loc: {
		start: {},
		end: {}
	    }
	};
	var callExpr = {
	    type: "CallExpression",
	    callee: {
		type: "Identifier",
		name: "LazyDefineBuiltinFunction"
	    },
	    arguments: [
		{
		    type: "Identifier",
		    name: realm
		},
		{
		    type: "Identifier",
		    name: id
		},
		{
		    type: "Literal",
		    value: name
		},
		{
		    type: "NumericLiteral",
		    value: flength
		},
		{
		    type: "Identifier",
		    value: callfn_VarName
		}
	    ],
	    loc: {
		start: {},
		end: {}
	    }
	};

	console.dir(varDecl);
	console.dir(varDecl.declarations[0].init);
	
	console.log("created varDecl and callExpr from original callexpr");
		
	var code1 = syntaxjs.toJsLang({type:"Program", body:[varDecl]});
	var code2 = syntaxjs.toJsLang({type:"Program", body:[callExpr]});

	state.decls.push(code1);
	state.defines.push(code2);
	
	state.replaced.push(array);
    
    });

}

function getValue(node) {
    if (!node) return node;
    switch (node.type) {    
    case "Identifier":
	return node.name;
    case "StringLiteral":
	return node.value.substr(1,node.value.length-2);
    case "NumericLiteral":
	return node.value;
    case "BooleanLiteral":
	return node.value;
    case "Literal":
	return node.value;
    default:
	return node.value;
    }
}

function about() {
    console.log("refactorDOP.js");
    console.log("is a specialised tool for doing a certain task");
    console.log("this tool replaces some DefineOwnProperty calls with better code.");


    console.log("");
}
function usage() {
    console.log("refactorDOP.js [input[, input[, ...]]] -o output.js [-e|-s]");
    console.log("-e parses with esprima, -s with syntax.js");
}

(function main (args) {
    about();
    if (!args.length) {
	usage();
    }
    
    state.inputs = [];
    
    for (var i = 0, j = args.length; i < j; i++) {
	var arg = args[i];
	if (arg === "-s") {
	    withEsprima = false;
	    continue;
	}
	if (arg === "-e") {
	    withEsprima = true;
	    continue;
	}	
	if (arg === "-o") {
	    var ofile = state.ofile = args[++i];
	    if (typeof ofile != "string" || !ofile.length) {
		throw error("-o [filename] requires filename argument");
	    }
	} else {
	    state.inputs.push(arg);
	}
    }

    if (!state.inputs.length) {
	throw error("nothing to process");
    }
    
    state.inputs.forEach(function (input) {

	console.error("processing file: "+input);	
	console.log("// OMITTING INPUT ");
		
        var ast = getAst(input);
        
        do_search(ast); // search is specialised. refactorDOP is hardcoded.
        do_replace();	// same here

	console.log("// DEFINE CALLS");

        state.decls.forEach(function (code) {
    	    console.log(code);
        });
        
	console.log("// CREATE INTRINSICS");

        state.defines.forEach(function (code) {
    	    console.log(code);
        });
        
        var rnum = state.replaced.length;
        console.error("// DONE after "+rnum+" entries");
    });

}(process.argv.slice(2)));

Das ist die Input Datei

DefineOwnProperty(obj, "name", {
    value: CreateBuiltinFunction(realm, function (thisArg, argList) {
	var one = argList[0]
	return NormalCompletion("Your name is: "+one);
    }, 1),
    enumerable: false,
    writable: false,
    configurable: false
});
DefineOwnProperty(obj1, "name", {
    value: CreateBuiltinFunction(realm, function (thisArg, argList) {
	var one = argList[0]
	return NormalCompletion("Your name is: "+one);
    }, 1),
    enumerable: false,
    writable: false,
    configurable: false
});
DefineOwnProperty(obj2, "name", {
    value: CreateBuiltinFunction(realm, function (thisArg, argList) {
	var one = argList[0]
	return NormalCompletion("Your name is: "+one);
    }, 1),
    enumerable: false,
    writable: false,
    configurable: false
});
DefineOwnProperty(obj3, "name", {
    value: CreateBuiltinFunction(realm, function (thisArg, argList) {
	var one = argList[0]
	return NormalCompletion("Your name is: "+one);
    }, 1),
    enumerable: false,
    writable: false,
    configurable: false
});

Das ist output mit syntax.js


refactorDOP.js
is a specialised tool for doing a certain task
this tool replaces some DefineOwnProperty calls with better code.

// OMITTING INPUT 
parsing refactor_testfile.js with syntax.js
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
callfn_VarName = obj_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj1_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj2_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj3_name
created varDecl and callExpr from original callexpr
// DEFINE CALLS
var obj_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion("Your name is: " + one);
};

var obj1_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion("Your name is: " + one);
};

var obj2_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion("Your name is: " + one);
};

var obj3_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion("Your name is: " + one);
};

// CREATE INTRINSICS
LazyDefineBuiltinFunction(realm, obj, name, 1, obj_name);

LazyDefineBuiltinFunction(realm, obj1, name, 1, obj1_name);

LazyDefineBuiltinFunction(realm, obj2, name, 1, obj2_name);

LazyDefineBuiltinFunction(realm, obj3, name, 1, obj3_name);

Das ist output mit dem eprima parser


refactorDOP.js
is a specialised tool for doing a certain task
this tool replaces some DefineOwnProperty calls with better code.

// OMITTING INPUT 
parsing refactor_testfile.js with esprima
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
found call to [[DefineOwnProperty]] 
callfn_VarName = obj_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj1_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj2_name
created varDecl and callExpr from original callexpr
callfn_VarName = obj3_name
created varDecl and callExpr from original callexpr
// DEFINE CALLS
var obj_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion(Your name is:  + one);
};

var obj1_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion(Your name is:  + one);
};

var obj2_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion(Your name is:  + one);
};

var obj3_name = function (thisArg, argList) {
    var one = argList[0];
    return NormalCompletion(Your name is:  + one);
};

// CREATE INTRINSICS
LazyDefineBuiltinFunction(realm, obj, name, 1, obj_name);

LazyDefineBuiltinFunction(realm, obj1, name, 1, obj1_name);

LazyDefineBuiltinFunction(realm, obj2, name, 1, obj2_name);

LazyDefineBuiltinFunction(realm, obj3, name, 1, obj3_name);

Noch ist es aber schrottiger Code, wie man oben lesen kann und noch gar nicht reif genug in der Art, wie ich den AST behandle.

Man sollte gleichzeitig aufzaehlen, man hat ein einfaches Geruest geschrieben, von dem sich die naechsten Schritte ableiten lassen. Erstmal habe ich von Hand aus dem Kopf gesucht. Dann fiel mir ein:

document.querySelector.

ast.querySelector("CallExpression[callee.name=DefineOwnProperty]")

Das ist nichts neues. Sowas haben andere schon lange erledigt, für jede Art von Baum.

testmaker.js tests.json

Ein leichterer Weg viel test.add(function () { eval, assert }) einzusparen.


tests.json:
{
	"test1":	
	{
	    "init": 
		"let x = 10;",
	    "tests": 
	    [
		["x",10],
		["x+x;", 20]
	    ]
	},
	
	"test2": 
	{
	    "init": 
		"const x = 20;",
	    "tests": 
	    [
		["x = 30; x;", 20],
		["x-=10", 10],
		["x", 20]
	    ]
	}
}

/* 
    linux-qc59:/s/tools # node testmaker.js tests.json 
*/

testmaker.js 0.0.1 for tester.js for syntax.js by Eddie

2 tests completed in : 2ms
Number of Tests: 2
Executed assertions: 2
Passed assertions: 2
Failed assertions: 0
Unexpected exceptions: 0
PASS: assert: actual=10, expected=10: message=x
PASS: assert: actual=20, expected=20: message=x+x;
3 tests completed in : 2ms
Number of Tests: 3
Executed assertions: 3
Passed assertions: 3
Failed assertions: 0
Unexpected exceptions: 0
PASS: assert: actual=20, expected=20: message=x = 30; x;
PASS: assert: actual=10, expected=10: message=x-=10
PASS: assert: actual=20, expected=20: message=x
# testmaker.js finished work for now

syntax.js Tests

in lib/test/highlighter/*html werde ich die Testreihe wiederholen und fortsetzen um eine Grundlage zu schaffen, glaubhaft zu wirken, wenn ich sage, der Rest soll mit Test262 abgecheckt werden.

syntax.js Tests

(mit der november 2013 version)

Variables, Lets, Consts, Hoisting, Initialisation

    var el = "#console189";
    console.html(el, "el="+el);
    console.html(el, "hoisting?");
    console.html(el, "(x="+x+")");
    console.html(el, "(y="+y+")");
    console.html(el, "(z="+z+")");
    console.html(el, "now the vars are labeled and defined!");
    var x;
    console.html(el, "x="+x);
    var y = 20;
    console.html(el, "y="+y);
    x = 10;
    console.html(el, "x="+x);    
    var z = x+y;
    console.html(el, "z="+z);        

Bemerkenswert ist, dass mit dem alten syntax.js kein "undefined" anstelle der gehoisteten variable steht (zumindest gerade nicht). Die aktuelle Version schreibt mir das im gleichen Browser hin, obwohl sich da eigentlich nichts geaendert hatte, weil ich es da schon hingekriegt hatte, sie nach Norm zu deklarieren und zu initialisieren.

    let x;
    let y = 20;
    x = 10;
    let z = x+y;
    const x;
    const y = 20;
    x = 10;
    const z = x+y;

Das aktuelle syntax.js laesst noch auf den Highlighter Bugfix warten.

function f() {
    var el = "#console288";
    console.html(el, "el="+el);
    console.html(el, "hoisting?");
    console.html(el, "(x="+x+")");
    console.html(el, "(y="+y+")");
    console.html(el, "(z="+z+")");
    console.html(el, "now the vars are labeled and defined!");
    var x;
    console.html(el, "x="+x);
    var y = 20;
    console.html(el, "y="+y);
    x = 10;
    console.html(el, "x="+x);    
    var z = x+y;
    console.html(el, "z="+z);        
    return z;
}

If Statements, Conditional Expressions

    let el = "#console334";
    var pass = false;
    if (true) {
	console.html(el, "TEST BESTANDEN");
	pass = true;
    } else {
	console.html(el, "TEST NICHT BESTANDEN");
    }
    pass;
    let el = "#console333";
    var pass = false;

    if (false) {
	console.html(el, "TEST NICHT BESTANDEN");
    } else {
	console.html(el, "TEST BESTANDEN");
	pass = true;
    }

    pass;

Parser Generatoren

Mittlerweile habe ich geguckt, was es denn noch fuer Parsergeneratoren gibt.

Hiermit koennte man auch die Engine implementieren, sofern man verstanden hat, wie man den Generator mit einer Grammatik mit Code-Klammern programmiert. Das erfordert erstmal etwas Uebung, und das Wissen, wo der Code hinkommt, und wie man auf eigene Datenstrukturen und Operationen zugreifen kann.

Das sind alles Parsergeneratoren, die aus Grammatiken Parser erzeugen fuer JS. Das sind nicht alle.

Allerdings habe ich noch nichts mit ausprobiert ausser mal generate gerufen und mir den parserSource ausgegeben, was mich überzeugt hat. Es war der von Jison und erinnerte mit allen yy-Konstrukten an Bison.

syntaxjs: TemplateLiteral

Erst zerlegte ich das TemplateLiteral `abcd${e}fg${h}i` vom TemplateHead "`abcd${" über die TemplateMiddle "}fg${" und das TemplateTail "}i`". Dafür baute ich auch das InputElementGoal so geschickt ein, wie man das schalten könnte, unter anderem, wohlgemerkt.

Dann hatte ich meine Token Nodes [TemplateHead, Identifer, TemplateMiddle, Identifier, TemplateTail] was aber noch keine Expressions ausmacht. Dann kontrollierte ich wieder meinen Code. Der die rawStrings und cookedStrings nach der ECMA-262 herstellt.

Dann fiel mir ein, die ` und ${ und ` nicht mitzuparsen, alle strings zu teilen und sie in einer Reihe zu speichern, da der Expression String zwischen den Templateteilen erst von parseGoal("Expression", templateSpan) zerlegt wird und ausgerechnet.

Darum habe ich den TemplateLexer nochmal umgeschrieben und in einen perfekten chaotischen Zustand gebracht, um überdacht und schön gemacht zu werden.

  /*
    function TemplateLiteral() {
        var template="";
        var parseRest = false;

        if (ch == "`" && (inputElementGoal !== inputElementTemplateTail)) {

    		  	inputElementGoal = inputElementTemplateTail;
                next();
                while ((ch + lookahead) != "${") {  // doubles constant factor of ch each character (tl = 2n)
                    template += ch;
                    next();
                    if (ch == "`") {
                        template += "`";
                        inputElementGoal = inputElementRegExp;
                        pushtoken("NoSubstitutionTemplate", template);
                        next();
                        return token;
                    }
                }
                template += ch; // $
                next();
                template += ch; // {

                pushtoken("TemplateHead", template);
                next();

            /* // das hier ist der zweite teil bereits einmal probiert anfang
            
                while (token.type != "TemplateTail") {
                    template = "";
                    if (ch == "`") {
                        pushtoken("TemplateTail")
                        next();
                        break;
                    }
                    if (ch == "}") {
                        while (ch + lookahead != "${") {
                            template += ch;
                            next();
                        }
                        template+=ch; // $
                        next();
                        template += ch; // {
                        pushtoken("TemplateMiddle", template);
                        next();

                        var cooked = "";
                        while (ch != "}") {
                            cooked += ch;
                            next();
                        }
                        pushtoken("Unparsed", cooked);
                    }

                } // das hier ist der zweite teil bereits einmal probiert ende

                inputElementGoal = inputElementRegExp;
            */
            
	    /*
                return token;
            } else if ((inputElementGoal == inputElementTemplateTail) && ch == "}") {

                while ((ch+lookahead) != "${") {

                    if (ch == "`") {

                        template += ch;
                        inputElementGoal = inputElementRegExp;
                        pushtoken("TemplateTail", template);
                        next();
                        return token;

                    }
                    template += ch;
                    next();
                }
                template += ch; // $
                next();
                template += ch; // {
                pushtoken("TemplateMiddle", template);
                next();
                return token;
    	    }


        return false;
    }
*/

    function TemplateLiteral () {
        //
        // new order
        // `edward ${ ""+ist+y } toll ${ oder } nicht?`
        // [ "edward ", " \""+ist+y ", " toll ", " oder ", " nicht" ]
        // where the first is always the template head, then the span, then the template
        // the source is deferred for parsing in the runtime.
        // this is raw and cooked strings.

        var template = "";
        var cooked = "";
        var spans = [];
        if (ch === "`") {

            template = "";
            // template += ch;
            next();

            while (ch != undefined) {

                // sammle template raw from }. to .${ and from `
                while (ch + lookahead != "${") {
                    // maybe it ends here
                    if (ch === "`") {
                        //template += "`";
                        pushtoken("TemplateLiteral", spans);
                        next();
                        return token;
                    }
                    template += ch;
                    next();
                }
                //template += ch; // $ dont collect dont strip
                next();
                //template += ch; // { dont collect dont strip
                spans.push(template);

                // sammle template cooked from ${. to  .}
                cooked = "";
                while (ch != "}") {
                    /* add blocks inside template?
                    if (ch == "{") {
                        parens.push("{");
                    }
                    */
                    cooked += ch;
                    next();
                    /*
                    if (ch == "}" ) {
                        if (parens.pop()) {
                            cooked += ch;
                            next();
                        }
                    }
                    */
                }
                spans.push(cooked);
                next(); // von } nach }.1

            }
        }
        return false;
    }

syntaxjs: Class Declaration

Ich glaube ich habe die ClassDeclaration repariert. Der Fehler war in MethodDefinition, bzw. in der Funktion PropName in den Static Semantics.

es6> class C { method() { return 10; } }
{}
es6> (new C).method();
10

syntaxjs: Tokenizer

Hab dem alten Tokenizer von syntax.js heute neues Leben eingehaucht

Gestern habe ich lexTemplateLiteral (siehe Java-Beispiel) eingebaut.

Aus

    function lexSomething () {
	if (lookahead === something) {
	    //...
	    return pushtoken("Something", something);
	}
    }
    // ...    
    
    while (ch != undefined) {
	lexSomething() || lex...();
	next();
    }

Wird so ein echter recursive descent lexer

    function lexSomething () {
	if (lookahead === something) {
	    //...
	    pushtoken("Something", something);
    	    next();
	    return token;
	}
	
    }
    // ...
    
    while (ch != undefined) {
	lexSomething() || lex...();
    }

Der Lexer ist noch aelter als mein daraufhin erworbenes Wissen gewesen...

Vielleicht sollte ich den Test, der in lexSomething() steht (if lookahead ===) noch in den Loop holen und einen switch-Table raus machen. Dann muss die Funktion nicht erst gerufen werden (spart dann jedesmal einige Zyklen durch dann fehlendes InstantiateFunctionDeclaration und Co.)

Buggy Toy Grammar

Das ist keine JavaScript Grammatik, sondern ein Haufen Mistakes. Durcheinandergebrachte Expressions. Und ein paar Wortfetzen aus JavaScript. Was aber nicht weiter schlimm ist.

Ich habe dafür ANTLRWORKS benutzt und eine Menge Spass gehabt.

Noch ist keine Interaktion drin. Aber der Terrence Parr beschreibt auch, wie man returns [Type type] programmiert. Ein sauberes Lehrprogramm.

    
grammar eddy;

@header {         
import java.util.HashMap;
}

@members {         
HashMap< String, Object> memory = new HashMap< String, Object>();
}

ID
  : ('a'..'z')('a'..'z'|'0'..'9')* 
  ;

NUM     
   : '0'..'9'+
   ;

BOOL
    : ('true'|'false')
    ;

NULL
    : 'null'
    ;

LIT
    : NUM|BOOL|NULL
    ;

WS : ('\t'|' ') 
   ;
NEWLINE : '\r'?'\n'
        ;
LBRACE : '{';
RBRACE : '}';
COLON : ':';
LBRACK: '[';
RBRACK: ']';
LPAREN: '(';
RPAREN: ')';
ASSIGN: '=';
COMMA: ',';          
IF: 'if';
WHILE: 'while';
ELSE: 'else';
FOR: 'for';
SEMICOLON: ';';

/* parser ab hier */

propertyNameList
    :  ID COLON assignmentExpression
    ;
objectLiteral
    : LBRACE propertyNameList* RBRACE
    ;


elementList
    : expression (COMMA expression|COMMA)*
    ;

arrayLiteral
    : RBRACK elementList* LBRACK
    ;

primaryExpression
    : ID
    | LIT
    | objectLiteral
    | arrayLiteral
    ;

statement
    : blockStatement    
    | ifStatement
    | whileStatement      
    | forStatement
    | expressionStatement
    | NEWLINE
    ;


blockStatement 
    : LBRACE statement* RBRACE
    ;

forStatement 
            : 'for' '(' expression ';' expression ';' expression ')' statement
            ;

ifStatement
    : IF LPAREN expression RPAREN statement (ELSE statement)?
    ;
whileStatement
    : WHILE LPAREN expression RPAREN statement
    ;

expressionStatement    
    : sequenceExpression     
    | RPAREN expression LPAREN
    ;

sequenceExpression 
    : expression (COMMA expression)*
    ;

expression 
    : assignmentExpression
    | primaryExpression
    ;

assignmentExpression 
    : leftHandSide ASSIGN assignmentExpression 
      {
            memory.put($leftHandSide.text, (Object)Integer.parseInt($assignmentExpression.text));
            System.out.println("stored variable "+$leftHandSide.text);
       }
    ;

leftHandSide
    : primaryExpression
    ;

script
    : statement+   
    ; 

Diese Grammatik kann nicht benutzt werden. Die hatte ich heute nachmittag im Wartezimmer vom Tierarzt eingegeben. Ich musste gut eine Stunde warten, dabei habe ich mit der und der ANTLR Reference rumgespielt.

Hardcore: Ich musste Statement und Co und statement und co umwandeln und musste aus den Parser Regeln saemtliche 'woerter' Lexerkonstanten substituieren, sonst wollte die Grammatik nicht kompilieren.

Nebenbei habe ich den Unterschied zwischen der ANTLR v3 Referenz (über Martin 'Refactoring' Fowler oder Guido 'Python' von Rossum wohl offiziell im Internet) und ANTLR v4 bemerkt.

antlrworks

Für die NetBeans IDE 7.4 gibt es ein tolles Tool, eine GUI für ANTLR. Genannt "ANTLRWorks 2.1".

Man bekommt rechts ein Editorframe, links die Liste mit Parser und Lexer Rules, unten drunter kann man ein Syntax Diagram gucken. Es gibt einen Lexer Debugger.

Genaugenommen kann ich damit noch GAR NICHTS. Aber das ist ein Anfang.

PLALR(k) Notizen

(Immernoch nicht optimal geloest, wenn auch nah dran)

Die parametrisierten Grammatiken bekommen durch ANTLR ein neues Antlitz für mich. Als grobe Parameter-Definition laesst sich das für mich mit einem Stack loesen. Als uebergebens Argument hatte ich bei NoIn bereits schlechtere Erfahrung gemacht. Allerdings macht es bei einer Grammatikdefinition scheinbar weniger Schwierigkeiten.

/*
    old understanding of parameterized productions
*/
    function parseSomeThingWithParamSet() {
	parameters["Yield"].push(true); // parameter wegen nesting auf den stack
	parseSomeThing();
	parameters["Yield"].pop();
    }
    function parseSomeThing() {
	var list = [];
	list.push( parseRuleWithoutYield() );
	var p = parameters["Yield"];
	if (p[p.length-1] === true) {
	    // falls parameter gesetzt führe regel aus
	    list.push( parseRuleWithYield() );
	    // es gibt noch alternativen wie "falls gesetzt fuehre nicht aus" etc.
	}
	list.push( parseMoreRule() );
	return list;
    }

Mit ANTLR ist das übergeben von parametern ganz anders zu erledigen, man kann sie direkt an den Generator übergeben um damit zu arbeiten. Übrigens ist die Ecma Grammatik so definiert, dass man sie einem eigenen Generator geben könnte, warum ich dabei bin, mir ebenfalls sowas auszudenken.


/*
    this will become the basis for a better understanding
*/

parseSomeThing[boolean Yield] returns [Completion R]
    : { /* code vorher */ } parseRule[Yield] { /* code nachher */ }
    ;

// argh, __jetzt habe ich verpasst, wie ich konditional auf den
// Parameter die Regel starte__
// Jetzt kann ich doch nicht aufloesen

Da muss ich wohl nochmal nachschlagen, bevor ich das loesen kann. Solange lass ich das mal so stehen, man kann sehen wo das hinführt.

Jetzt muss man nur noch die Parameter der JavaScript Syntax entsprechend übergeben können. mit [?Yield] und [~Yield] und [+Yield] und [Yield].

Jedenfalls changed das mein Mind.

/*
    i already had this.
*/

function parseStatementList(Return) {
    if (Return === true) {
	// bzw. parse...(Return);    
    }
    // parse...();
}

Allerdings hatten wir das in syntax.js bereits so gehandhabt. Ich hatte NoIn urspruenglich so implementiert. Habe es aber für umstaendlicher angesehen, als noIn auf den Stack zu pushen. Umstaendlich wurd´s, als ich Aenderungen vornehmen wollte, und die ubergebenen Argumente an die Funktionen dabei im Weg hatte. Darum hatte ich den Stack statt dem FormalParameter genommen.

    function parseX() {
	pushParameter(Y, true);	// <--- das ist meine aktuelle
	parseWithParam(); /* can contain if (getParam(Y)==true)  */
	popParameter(Y);		// <--- Loesung
    }

Mehr ANTLR4 vom Anfänger hier

antlr4_parse_tree.png

(mit dem Kommando "grun --tree --gui Calculator calc" erstellt)

/*
      Sowas 
*/

Starting Calculator (generated with http://ANTLR.org v4)
enter some simple arithmetic expression and press strg-d to return
java Calc -v prints out the tokenNames and ruleNames after strg-d
1+2
eddie=3
4+eddie
1+2 = 3
statement result: 3
Stored 3 in eddie
statement result: 3
4+eddie = 7
statement result: 7

/*
    natürlich nur unter Zuhilfenahme der Referenz
*/


grammar Calculator;

@header {
import java.util.HashMap;
}

@members {
    HashMap< String, Integer> table = new HashMap<>();
}

ID : ('a'..'z')('a'..'z'|'0'..'9')*
;

NUM : ('0'..'9')+ 
;

WS : (' '|'\t'|'\r'|'\n')+;

calc 
    : (WS? stat WS?)+
    ;
    
atom returns [int value] 
    : NUM { $value = Integer.parseInt($NUM.text); }
    | ID { 
	    Integer v = (Integer)table.get($ID.text); 
	    if (v != null) $value = v;
	    else { 
		System.out.println("undefined variable: "+$ID.text);
		$value = 0;
	    }
	}
    | '(' expr ')' { $value = $expr.value; }
    ;

expr returns [int value]
     : atom { $value = $atom.value; }
     | a=atom { $value = $a.value; } ('+' b=atom { $value+=$b.value; })*
     | a=atom { $value = $a.value; } ('-' b=atom { $value-=$b.value; })*
     | a=atom { $value = $a.value; } ('*' b=atom { $value*=$b.value; })*
     | a=atom { $value = $a.value; } ('/' b=atom { $value/=$b.value; })*
    ;

stat returns [int value] 
    @after {
	System.out.println("statement result: "+$value);
    }
    : ID '=' NUM '\n' { 
	table.put($ID.text, $value = Integer.parseInt($NUM.text)); 
	System.out.println("Stored "+$NUM.text+ " in "+$ID.text);
      }
    | expr '\n' { 
	$value= $expr.value;
	System.out.println($expr.text + " = " + $value);
     }
    ;


/*
    Aber dafür bereits mit ACTION
*/

import org.antlr.v4.runtime.*;
import org.antlr.v4.*;
import java.io.IOException;

class Calc {
    public static void main(String[] args) throws IOException {
    	System.out.println("Starting Calculator (generated with http://ANTLR.org v4)");
    	System.out.println("enter some simple arithmetic expression and press strg-d to return");
    	System.out.println("java Calc -v prints out the tokenNames and ruleNames after strg-d");
	ANTLRInputStream input = new ANTLRInputStream(System.in);
	CalculatorLexer lex = new CalculatorLexer(input);
	CommonTokenStream tok = new CommonTokenStream(lex);
	CalculatorParser parser = new CalculatorParser(tok);
	if (args.length > 0 && args[0].equals("-v")) {
		String grammarFileName = parser.getGrammarFileName();
		String[] tokenNames = parser.getTokenNames();
		String[] ruleNames = parser.getRuleNames();
		System.out.println("parser.getGrammarFileName(): "+grammarFileName);
    		System.out.println("parser.getTokenNames():");
		for (String s : tokenNames) {
		    System.out.println("tokenNames: "+s);
		}
		System.out.println("parser.ruleNames():");
		for (String s : ruleNames) {
		    System.out.println("ruleNames: "+s);
		}
	}
	parser.calc();
    }
}

Das ist aber noch nicht alles, was ANTLR kann. Es gibt viele Kommandos und Felder. Der Terrence Parr arbeitet da wohl schon zwanzig Jahre dran. Und schreibt auch dementsprechend gute Literatur.

Lexer

Den kann man generalisieren, wenn man den ein zweites mal verwenden will. Ich hab ihn mir für HTML rangenommen, jetzt kann ich die relevanten Teile zusammenfassen. Die Funktion public Token nextToken() muss @Overridden werden. Die Funktion nextCharacter() kann overridden werden, wenn der Standard Eingabestrom nicht reicht.

Anders als Streams kann man einen Eingabestring aber leichter zurueckgehen und auch kopieren. Hier muesste eine weitere Schicht für mich persönlich implementiert werden, dass ich den Stream wie einen String nutzen kann oder einen String anstelle des Streams. Das werde ich gleich machen.

Von vorgestern

/* Diese Grammatik mit antlr kompiliert */

grammar first;

/*
    Hier sieht man noch die totale Unsicherheit in der Syntax.
    Aber dafür gibt es ja die Referenz.
*/ 

ID : ('$'|'_'|'A'..'Z'|'a'..'z')('A'..'Z'|'a'..'z'|'0'..'9'|'$'|'_')* ;

number : ('0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9')+ ;
    
lit : ID  { System.out.println("ID: "+$ID.text); }
    | number { System.out.println("number: "+$number.text); }
    ;
    
primexpr: lit
	 | callexpr;

expr :
     | primexpr
     | '(' expr ')'
     | expr '+' primexpr { System.out.println("adding '" + $primexpr.text + "' to '"+ $expr.text +"'"  ); } 
     | expr '-' primexpr { System.out.println("subtracting '" + $primexpr.text + "' from '"+ $expr.text +"'"  ); } 
     | expr '*' primexpr { System.out.println("multiplying '" + $primexpr.text + "' with '"+ $expr.text +"'"  ); }
     | expr '/' primexpr { System.out.println("dividing '" + $primexpr.text + "' by '"+ $expr.text +"'"  ); }
     | expr ',' primexpr  { System.out.println("'" + $primexpr.text + "' follows '"+ $expr.text +"'"  ); }
     | 
     ;

callexpr : ID'('')'';' { System.out.println("invoking function symbol "+$ID.text+ " for a call."); } 
	;
	
statement : expr | block;

newline: '\n';

block : '{' statementList '}' { System.out.println("Block Statement"); }
;

statementList : statement newline { System.out.println("statement terminated with newline: "+$statement.text); } 
	      | statement ';' { System.out.println("statement terminated with semicolon: "+$statement.text); } 
	      | statementList statement '' { System.out.println("statementList '"+$statementList.text+ "' with following statement: '"+$statement.text+"'"); } 
	      | newline  { System.out.println("blank line"); } 
	      |
	      ;
/* Mit ein paar Zeilen Code gerufen */

ANTLRInputStream in = new ANTLRInputStream(System.in);
firstLexer lexer = new firstLexer(in);
CommonTokenStream stream = new CommonTokenStream(lex);
firstParser parser = new firstParser(stream);
parser.statementList();

/* Und der Eingabetext gibt */ 

1+2+3+4
1*2*4/3
eddie()
eddie() + eddie();
a() + b + c;
a,b,c,d;
a;b;c;
a
23425
adad
{ block; }



/* folgende Ausgaben */

number: 1
number: 2
adding '2' to '1+2'
number: 3
adding '3' to '1+2+3'
number: 4
adding '4' to '1+2+3+4'
statement terminated with newline: 1+2+3+4
number: 1
number: 2
multiplying '2' with '1*2'
number: 4
multiplying '4' with '1*2*4'
number: 3
eddie()
eddie()
dividing '3
eddie()
eddie()' by '1*2*4/3
eddie()
eddie()'
invoking function symbol eddie for a call.
adding 'eddie();' to '1*2*4/3
eddie()
eddie()+eddie();'
ID: b
adding 'b' to '1*2*4/3
eddie()
eddie()+eddie();
a()+b'
ID: c
adding 'c' to '1*2*4/3
eddie()
eddie()+eddie();
a()+b+c'
ID: b
'b' follows '1*2*4/3
eddie()
eddie()+eddie();
a()+b+c;
a,b'
ID: c
'c' follows '1*2*4/3
eddie()
eddie()+eddie();
a()+b+c;
a,b,c'
ID: d
'd' follows '1*2*4/3
eddie()
eddie()+eddie();
a()+b+c;
a,b,c,d'
statementList '1+2+3+4
1*2*4/3
eddie()
eddie()+eddie();
a()+b+c;
a,b,c,d;
a;b;c;
a
23425
adad
{block;}

' with following statement: '1*2*4/3
eddie()
eddie()+eddie();
a()+b+c;
a,b,c,d;
a;b;c;
a
23425
adad
{block;}

'

/* Und die folgenden Fehler */

line 2:7 extraneous input '\n' expecting {'/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '+', ',', '-', ''}
line 4:7 token recognition error at: ' '
line 4:9 token recognition error at: ' '
line 4:18 extraneous input '\n' expecting {'/', '*', '+', ',', '-', ''}
line 5:3 token recognition error at: ' '
line 5:5 token recognition error at: ' '
line 5:7 token recognition error at: ' '
line 5:9 token recognition error at: ' '
line 5:11 extraneous input ';' expecting {'/', '*', '+', ',', '-', ''}
line 6:7 extraneous input ';' expecting {'/', '*', '+', ',', '-', ''}
line 11:1 token recognition error at: ' '
line 11:8 token recognition error at: ' '

Lookahead Liste

Terrence Parr zeigt nicht nur den zirkulaeren Lookahead Puffer, sondern auch den mit Liste. Mit einer ArrayList hat man konstante get() Resultate, dass man sie genausogut nutzen kann. Sie hat den Vorteil, dass man k (von LL(k)) beliebig erweitern und verkleinern kann. Und eine ArrayList wird mit 10 Array-Plaetzen, wenn man es nicht anders angibt, vorinitialisiert. Man kann sie z.B. k anpassen. Nachteil ist, dass man die geboxten Integer und Character verwenden muss, implementiert man sich nicht selbst eine, mit einem Template, welches nicht <? extends Object> definiert.

    public init () {
        MyArrayList lookahead = new MyArrayList<>();
	for (int i = 0; i < k; i++) lookahead.add(stream.read);
    }
    
    public int lookAt(int i) {
	return lookahead.get(i);
    }
    
    public int nextCharacter() {
    	lookahead.removeFirst();
	lookahead.addLast(stream.read());
	return lookahead.get(0);
    }
    

Folien

Rechnerorganisation finde ich gut, da habe ich keine Videos von. Aber wie ich lese, schon gut mitgemacht am MIT und sonst.

Ich muss noch Algorithmen abspeichern. Den Kurs habe ich wohl vorher schon gemacht. Aber der deutsche ist auch echt anspruchsvoll und hat noch andere Themen, dass ich den noch nicht durch habe. Ausserdem wollte ich das Rechnen mit Big Oh und Theta auch nochmal auf deutsch lernen. Ich schätze, die Folien werden noch ein paar Zusätze bieten, wie die anderen auch. Und wenn da auch noch Klausuren bei sind, na dann aber Hallo und nicht wie ran.

Cool

Ich hab die Folien fürs SWT Semester gefunden. Das sind einige hundert Seiten und viel mehr als in den Lesungen. Das wird mir helfen, die Inhalte besser zu lernen.

Dann gibt es Übungen, Klausuren und Musterlösungen. Ich bin gleich drüber hergefallen, um mir die reinzuprügeln und aufzubügeln. Das werde ich jetzt tagelang machen.

Ich freu mich schon aufs neue JavaScript-Draft, was jetzt kommen sollte. Dann geh ich erstmal wieder darauf ab und schreib, was ich schon lange vor habe.

Ich weiss auch jetzt schon, dass ich ab nächsten Monat meine 5GB mit Mathe verbraten werde.

Ein Schein wär mir lieber. Aber unser Arbeitsamt weigert sich seit eh und je mir eine Lehre zu ermöglichen. HartzVierEmpfaenger eddie = (HartzVierEmpfaenger)suppressMax(getRandom(getPeople())).harrasWith(stupidMom());;

Roadmap

Problem: Das Arbeitsamt unterstützt mich nicht, so trete ich auf der Stelle. Meine Gesuche eine Schulung zu bekommen, zum Beispiel sechs Monate LPIC1+2 stossen auf taube Ohren. Man bescheinigt mir überdurchschnittliche intellektuelle Leistungen beim berufspsychologischen und der ärztliche meint, ich wär nicht belastbar. Wahrscheinlich habe ich mich nur realistisch über Arbeits- und Wohnungslosigkeit geäussert, dass die das wieder falsch verstanden haben. Ich werde jedes halbe Jahr aufs nächste vertröstet.

Bei der nächsten Absage halte ich die Klagefrist ein. Vielleicht sollte ich dem Arbeitsministerium schreiben, weil es für mich nicht nachvollziehbar ist, dass ich nicht einzusetzen oder auszubilden bin. Ich bin nicht meine Mutter.

Ausser Haus

Ich war die ganze Woche beim Tierarzt und hinterher nicht so dabei, zu programmieren. Darum gibt es mal nichts neues.

Alter Lexer

Hier ist nochmal das Listing von weiter unten. Als Snapshot, ohne es jetzt vollendet zu haben. Hier und da sind noch Dinge zu regeln. Es gibt überflüssige Substitutionen an char c = chr; Die Unicode und EscapeSequenzen sind nicht drin. Die Abbruchkonditionen in den Loops sind noch nicht komplett. InputElementDiv sollte in Abwechslung zu InputElementRegExp schalten.

Idee: Eine gute Überlegung ist, einen GenericLexer abzuleiten, der dient mir gerade als Grundlage für einen HTMLTokenizer, da passen lookahead und inputStream rein. Tip: public abstract Token nextToken();

package de.linux_swt.jsparse;
import java.io.*;
import static de.linux_swt.jsparse.JavaScriptSets.*;

public class JavaScriptLexer {

    protected boolean dotSeen, signSeen, expSeen;
    public char chr;
    
    public final Token END_TOKEN = new Token(S_EMPTY, TokenType.EOF);
    final int EOF = -1;

    public JavaScriptLexerState mem;

    public void setMemento(JavaScriptLexerState mem) { this.mem = mem; }
    public JavaScriptLexerState getMemento() { return this.mem; }

    public JavaScriptLexer(InputStream in) throws IOException {
        setMemento(new JavaScriptLexerState(in, 4));
        nextCharacter(3);
        mem.inputElementGoal = INPUTELEMENT_REGEXP;
    }

    public void debug(String msg) {
        System.out.format("%s\n", msg);
    }

    public char lookAtChar(int i) {
        return (char)mem.lookahead[(mem.p + i) % mem.k];
    }

    public int lookAt(int i) {
        return mem.lookahead[(mem.p + i) % mem.k];
    }

    public void match(int ch) throws IOException {
        int la = mem.lookahead[mem.p];
        if (la == ch) nextCharacter();
        else throw new IOException("Unexpected character: '"+(char)la+"'. Expected '"+(char)ch+"'!");
    }

    public void consumeCharacter () throws IOException {
        mem.lookahead[mem.p] = mem.buffer.read();
        mem.p = (mem.p + 1) % mem.k;
    }

    public void nextCharacter(int n) throws IOException {
        for (int i = 0; i < n; i++) nextCharacter();
    }

    public int nextCharacter() throws IOException {
        consumeCharacter();
        chr = lookAtChar(0);
        if (chr == '\n') {
            mem.lineNumber = mem.lineNumber + 1;
            mem.columnNumber = 0;
        } else {
            mem.columnNumber = mem.columnNumber + 1;
        }
        return lookAt(0);
    }

    public Token addToken (Token t) {
        if (t != null) mem.tokens.add(mem.token = t);
        return t;
    }

    public Token nextToken () throws IOException {
        while (lookAt(0) != -1) {
            if (isWhiteSpace()) {
                lexWhiteSpace();
                continue;
            }
            if (isCommentStart()) {
                addToken(lexComment());
                continue;
            }
            if (isLineTerminator()) return addToken(lexLineTerminator());
            if (isIdentifierStart()) return addToken(lexIdentifier());
            if (isTemplateStart()) return addToken(lexTemplateString());
            if (isStringStart()) return addToken(lexString());
            if (isRegularExpressionStart()) return addToken(lexRegularExpression());
            if (isNumberStart()) return addToken(lexNumber());
            if (isOperatorStart()) return addToken(lexOperator());

            System.out.format("Unknown character %s at line %s, column %s\n", chr, mem.lineNumber, mem.columnNumber);
            nextCharacter();
        }
        return END_TOKEN;
    }

    protected boolean isUnicodeEscapeSequence () {
        return (chr == C_BACKSLASH && lookAtChar(1) == 'u');
    }
    protected boolean isEscapeSequence () {
        return (chr == C_BACKSLASH && escapeCharacters.contains(lookAtChar(1)));
    }

    protected boolean isRegularExpressionStart () {
        if (mem.inputElementGoal == INPUTELEMENT_REGEXP) {
            return (chr == C_SLASH) && !regExpNoneStarters.contains(lookAtChar(1));
        }
        return false;
    }

    protected boolean isStringStart() {
        return (chr == C_SINGLE_QUOTE) || (chr == C_DOUBLE_QUOTE);
    }

    protected boolean isIdentifierStart() {
        return JavaScriptSets.isIdentifierStart(chr);
    }

    protected boolean isIdentifierPart() {
        return JavaScriptSets.isIdentifierPart(chr);
    }

    protected boolean isWhiteSpace() {
        return whiteSpacesChar.contains(chr);
    }

    protected boolean isTemplateStart () {
        return mem.inputElementGoal != INPUTELEMENT_TEMPLATETAIL ? (chr == C_BACKTICK) : (C_RBRACE == chr);
    }

    protected boolean isCommentStart() {
        String start = ""+ chr +lookAtChar(1);
        return start.equals(S_MULTILINECOMMENT_START) || start.equals(S_LINECOMMENT_START);
    }

    protected boolean isOperatorStart() {
        return (chr != '/') ?
                operatorsChar.contains(chr) :
                (mem.inputElementGoal == INPUTELEMENT_DIV);
    }

    protected boolean isLineTerminator () {
        return lineTerminatorsChar.contains(chr);
    }

    protected boolean isNumberStart() {
        char c = chr;
        if (c == '.' && Character.isDigit(lookAtChar(1))) return (dotSeen = true);
        return (c >= '0' && c <= '9');
    }

    protected boolean isNumberPart() {
        char c = chr;
        if (c >= '0' && c <= '9') return true;
        if (!dotSeen && c == '.') return dotSeen = true;
        if (!expSeen && (c == 'e' || c == 'E')) {
            char d = lookAtChar(1);
            if (d == '+' || d == '-') signSeen = true;
            return expSeen = true;
        }
        return false;
    }

    protected Token lexNumber () throws IOException {
        StringBuilder number = new StringBuilder();
        char c = chr;
        if (c == '0') {
            char d = lookAtChar(1);
            switch (d) {
            case 'X':
            case 'x':
                number.append(""+c+d);
                nextCharacter(2);
                while (hexDigits.contains(c = chr)) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.HEXLITERAL);
            case 'b':
            case 'B':
                number.append(""+c+d);
                nextCharacter(2);
                while (binaryDigits.contains(c = chr)) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.BINARYLITERAL);
            case 'O':
            case 'o':
                number.append(""+c+d);
                nextCharacter(2);
                while (octalDigits.contains(c = chr)) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.OCTALLITERAL);
            }
        }
        number.append(c);
        nextCharacter();
        while (isNumberPart()) {
            number.append(chr);
            if (expSeen && signSeen) {
                signSeen = false;
                nextCharacter();
                number.append(chr);
            }
            nextCharacter();
        }
        expSeen = false;
        dotSeen = false;
        return new Token(number.toString(), TokenType.NUMERICLITERAL);
    }

    protected Token lexIdentifier () throws IOException {
        TokenType type;
        StringBuilder identifier = new StringBuilder();
        identifier.append(chr);
        nextCharacter();
        while (isIdentifierPart()) {
            identifier.append(chr);
            nextCharacter();
        }
        String id = identifier.toString();
        if (reservedWords.contains(id)) {
            type = TokenType.RESERVEDWORD;
            if (id.equals(S_NULL)) type = TokenType.NULLITERAL;
            else if (id.equals(S_TRUE) || id.equals(S_FALSE)) type = TokenType.BOOLEANLITERAL;
        } else if (futureReservedWords.contains((id))) type = TokenType.FUTURERESERVEDWORD;
        else type = TokenType.IDENTIFIER;
        return new Token(id, type);
    }

    protected Token lexLineTerminator () throws IOException {
        Token t = new Token(String.valueOf(chr), TokenType.LINETERMINATOR);
        nextCharacter();
        return t;
    }

    protected Token lexWhiteSpace () throws IOException {
        StringBuilder ws = new StringBuilder();
        do {
            ws.append(chr);
            nextCharacter();
        } while (isWhiteSpace());
        return new Token(ws.toString(), TokenType.WHITESPACE);
    }

    protected Token lexOperator () throws IOException {
        char c, d;
        StringBuilder operator = new StringBuilder();
        operator.append(chr);
        nextCharacter();
        if (operators.contains(operator.toString() + chr)) {
            operator.append(chr);
            nextCharacter();
            if (operators.contains(operator.toString() + chr)) {
                operator.append(chr);
                nextCharacter();
            }
        } else if (operators.contains(operator.toString() + chr + (d=lookAtChar(1)))) {
            operator.append(chr);
            operator.append(d);
            nextCharacter(2);
        }
        if (operator.equals(S_SIGNED_RIGHTSHIFT) && chr == C_ASSIGN) {
            operator.append(C_ASSIGN);
            nextCharacter();
        }
        return new Token(operator.toString(), TokenType.OPERATOR);
    }

    protected Token lexComment () throws IOException {
        StringBuilder comment = new StringBuilder();
        TokenType type;
        char c;
        String start = ""+ chr + lookAtChar(1);
        if (start.equals(S_MULTILINECOMMENT_START)) {
            comment.append(start);
            nextCharacter(2);
            while (!S_MULTILINECOMMENT_END.equals(""+(c=chr)+lookAtChar(1)) && (int)c != -1) {
                comment.append(c);
                nextCharacter();
            }
            comment.append(chr);
            type = TokenType.MULTILINECOMMENT;
            mem.inputElementGoal = INPUTELEMENT_REGEXP;
            return new Token(comment.toString(), type);
        } else if (start.equals(S_LINECOMMENT_START)) {
            c = chr;
            do {
                comment.append(c);
                nextCharacter();
            } while ((c=chr) != C_NEWLINE && (int)c != -1);
            type = TokenType.LINECOMMENT;
            nextCharacter();
            return new Token(comment.toString(), type);
        }
        return null;
    }

    protected Token lexRegularExpression() throws IOException {
        StringBuilder regex = new StringBuilder();
        regex.append(chr);
        nextCharacter();
        while (chr != C_SLASH) {
            regex.append(chr);
            nextCharacter();
        }
        regex.append(chr);
        nextCharacter();
        while (regExpFlags.contains(chr)) {
            regex.append(chr);
            nextCharacter();
        }
        return new Token(regex.toString(), TokenType.REGULAREXPRESSIONLITERAL);
    }

    protected Token lexTemplateString() throws IOException {
        StringBuilder template = new StringBuilder();
        if ((mem.inputElementGoal != INPUTELEMENT_TEMPLATETAIL)
            && (chr == C_BACKTICK)) {
                mem.inputElementGoal = INPUTELEMENT_TEMPLATETAIL;
                template.append(C_BACKTICK);
                nextCharacter();
                while (lookAt(0) != -1) {
                    template.append(chr);
                    if ((chr == C_DOLLAR) && (lookAtChar(1) == C_LBRACE)) {
                        template.append(C_LBRACE);
                        nextCharacter(2);
                        return new Token(template.toString(), TokenType.TEMPLATEHEAD);
                    } else if (chr == C_BACKTICK) {
                        mem.inputElementGoal = INPUTELEMENT_REGEXP;
                        nextCharacter();
                        return new Token(template.toString(), TokenType.TEMPLATE);
                    }
                    nextCharacter();
                }
        } else if (chr == C_RBRACE) {
            template.append(C_RBRACE);
            nextCharacter();
            while (lookAt(0) != -1) {
                template.append(chr);
                if (chr == C_DOLLAR && lookAtChar(1) == C_LBRACE) {
                    template.append(C_LBRACE);
                    nextCharacter(2);
                    return new Token(template.toString(), TokenType.TEMPLATEMIDDLE);
                } else if (chr == C_BACKTICK) {
                    template.append(chr);
                    nextCharacter();
                    mem.inputElementGoal = INPUTELEMENT_REGEXP;
                    return new Token(template.toString(), TokenType.TEMPLATETAIL);
                }
                nextCharacter();
            }
        }
        return null;
    }

    protected Token lexString () throws IOException {
        StringBuilder string = new StringBuilder();
        char startQuote = chr;
        string.append(startQuote);
        nextCharacter();
        while (chr != startQuote) {
            string.append(chr);
            nextCharacter();
        }
        string.append(startQuote);
        nextCharacter();
        return new Token(string.toString(), TokenType.STRINGLITERAL);
    }

}

junit

Vor ein paar Tagen lief das Listing nicht. Heute kann ich das aus dem Kopf korrigieren. Ist natürlich jetzt nicht besonders schwierig gewesen. Junit hingegen kann ich noch nicht. Aber das ist ein Anfang.

/*

.
Time: 0,025

OK (1 test)

*/
import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import junit.textui.TestRunner;
import junit.framework.TestCase;

public class unit extends TestCase {
	private String str;
	
	public unit(String name) {
	    super(name);
	}
	
	/*
	@BeforeClass public static void setUpAll () {
	}
	*/
	
	@Before public void setUp () {
	    str = "Hallo Welt";
	}	
	@Test public void testThisOne() {
	    assertTrue("String str soll Hallo Welt enthalten", str.equals("Hallo Welt"));
	}
	
	/*
	@Test public void testThisTwo() {
	}
	@Test public void testThisToo() {
	}
	*/
	
	@After public void tearDown () {
	    str = null;
	}
	
	/*
	@AfterClass public static void tearDownAll () {
	}
	*/
	
	public static void main(String args[]) {
	    TestRunner tr = new TestRunner();
	    tr.run(unit.class);
	}
}

Wissenschaftliches Arbeiten

Weil meine Domain so wunderbar unsachlich und voller Fehler, sowohl unbewussten als auch gewollten Fehlern, ist, habe ich mir endlich mal einige Leitfäden und Ratgeber über wissenschaftliches Arbeiten herangezogen.

Die werde ich die nächsten Wochen nun lernen. Dann kann ich den unsachlichen Ratgeber endlich begraben, weil mir die Richtlinien vor Augen sind.

ByteCode generieren mit ASM

Ich habe gerade dieses Listing ausprobiert. Es generiert ein Interface Comparable, was von Mesurable extended. Um es auszuprobieren, habe ich ein leeres interface Mesurable in pkg/ abgespeichert. Und dann eine bloede main Funktion gerufen, die compareTo aufruft. Fakt ist: ES FUNKTIONIERT. Mit ASM kann man Java Klassen schreiben lassen. Also auch ByteCode generieren, für eigene DSLs oder zum Beispiel eine JavaScript Engine. Mein erster generierter Bytecode (aber nur mit Hilfe von ASM.jar).

import org.objectweb.asm.*;
import org.objectweb.asm.signature.*;
import java.io.*;

class asm {
    
    /* selbst von der spec geholt */
    public static final int
    ACC_PUBLIC = 0x0001,
    ACC_STATIC = 0x0008,
  ACC_FINAL = 0x0010,
  ACC_SUPER = 0x0020,
  ACC_INTERFACE = 0x0200,
  ACC_ABSTRACT = 0x0400,
  ACC_SYNTHETIC = 0x1000 ,
  ACC_ANNOTATION = 0x2000,
  ACC_ENUM = 0x4000;

 public static int V1_8 = 52;
 /* selbst ge-erfunden aufgrund von fehlermeldungen */

	public static void main(String args[]) {
	    System.out.println("Testing asm-4.2.jar Listing: writing pkg/Mesurable.class");

            /* OriginalCode aus den ASM 4.2 Docs bis auf V1_8 (V1_5) */ 
            ClassWriter cw = new ClassWriter(0);
            cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,"pkg/Comparable", null, "java/lang/Object",new String[] { "pkg/Mesurable" });
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I", null, new Integer(-1)).visitEnd();
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I", null, new Integer(0)).visitEnd();
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",null, new Integer(1)).visitEnd();
            cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo","(Ljava/lang/Object;)I", null, null).visitEnd();
            cw.visitEnd();
    	    byte[] b = cw.toByteArray();
    	    
    	    /* Nur noch schreiben */
    	    try (FileOutputStream out = new FileOutputStream("pkg/Comparable.class")) {
        	out.write(b);
    	    } catch (IOException e) {
    		e.printStackTrace();
    	    }
	}
}

C++

c++ books filetype:pdf.

Ich finde, es war noch nie leichter, C++ oder überhaupt irgendeine Programmiersprache zu lernen.

Die Stanford Uni unterrichtet C++ glaube ich als Video. Jedenfalls habe ich einen Kurs von. Programming Abstractions heisst der. Ich finde, dass ist sauberes C++ für Anfänger. Wäre froh, das zu beherrschen.

Dann gibt es C++11. auto, new C(std::move(B)), std::future und std::async, std::thread, und vieles vieles mehr... C++11 ist leider nicht auf der Seite mit Büchern vertreten, aber wenn man es eingibt, kommt man auch daran.

Beim bemerken des >> Template Closing Errors von vorher muss ich grinsen, man hatte einen fetten Bug im Parser. Es ist eigentlich, was C++11 macht, das erste >> als schliessend anzusehen, und das Einklammern von Expressions mit >> zu erlauben, von vornherein zu erwarten. Gar nicht auszudenken, dass man in einer Sprache wie C++ jahrelang unbemerkt von der Bevoelkerung ein Extra Space zwischen die zwei schliessenden > > schreiben musste. Gar nicht auszumalen. Einfach unbemerkt geblieben sind sie damit.

#include <iostream>
using std::cout;
using std::endl;
int main() {
    int ints[10];
    for (int i = 0; i < 10; i++) ints[i] = i;
    for (auto j : ints) cout << j << endl;	// auto und for:in ist wohl c++11
}
/* 

g++ -std=gnu++11 test.cpp -o test
./test

0
1
2
3
4
5
6
7
8
9
*/

Refactoring

Ich habe das Buch von M.Fowler im Internet auf einer seiner Seiten gefunden.

Und ich habe noch andere Bücher gefunden. Zum Beispiel "Models of Computation", ein streng mathematisches Buch, mit vielen Bekannten Lektionen, in dessen Inhalt man am MIT schon beim Prof. Demaine, ich glaube in 6.006 Introduction to Algorithms, reinhören konnte..

Oder noch mehr über Design Patterns. Dabei sind mir einige der Bücher, die am KIT in Softwaretechnik 1 empfohlen werden aufgefallen. Die Lektionen werden immer klarer. Dafür, dass mir alles ausser der Vorlesungsaufzeichnung fehlt, ganz guter Fortschritt.

Darunter ist ein Buch, was älter als GOF ist und noch andere Patterns bespricht, die man in der Systemprogrammierung findet.

@FunctionalInterface / forEach(java.util.function.Consumer< ?> f)

Ja, fast schon wie JavaScript.

Gerade aeusserte ich mir eine Vermutung um das lexical Scoping bei Java. Das this muesste ueberprueft werden. Das scoping entspricht vielleicht dem fat arrow in JavaScript.

/*
Edward zeigt das lexikalische this
*/
class finf5 {
	public static final String name = "Edward zeigt das lexikalische this";
	
	@FunctionalInterface
	public static interface Callback {
	    public final String name = "Edward zeigt, dass das this nicht ins Interface zeigt.";
	    public T call();
	}
	
	public static void main(String args[]) {	
	    Callback< Callback< String>> f = () -> () -> name;
	    
	    System.out.println(f.call().call());
	}
}

Gerade aeusserte ich mir eine Vermutung um das lexical Scoping bei Java. Die Umgebung, in der die Lambda Expression erzeugt wird, ist die Umgebung, in der die Variablen gesucht werden.


/*
Edward zeigt lexical scoping
*/

class finf4 {
	@FunctionalInterface
	public static interface Callback {
	    public T call();
	}
	
	public static void main(String args[]) {
	    String name = "Edward zeigt lexical scoping";
	
	    Callback< Callback< String>> f = () -> () -> name;
	    
	    System.out.println(f.call().call());
	}
}

Aha, das @FunctionalInterface fuer Consumer< ?> befindet sich in java.util.function; Das gute an den modernen IDEs ist die automatische Vervollständigung, die sofort anzeigt, in welchem Paket das abstrakte Teil steckt.

/*
fn1
fn2
Visit: Edward Gerhold
Visit: Edwin Hold
Consumer: Edward Gerhold
Consumer: Edwin Hold
forEach: Edward Gerhold
forEach: Edwin Hold
*/

import java.util.List;
import java.util.LinkedList;
import java.util.function.Consumer;

class finf3 {
	@FunctionalInterface
	public static interface Callback {
	    public void call();
	}
	@FunctionalInterface
	public static interface Visitor< T> {
	    public void visit(T o);
	}	
	public static class Person {
	    public String first, last;
	    public Person(String first, String last) {
		this.first = first; this.last = last;
	    }
	    public String toString() {
		return this.first + " " + this.last;
	    }
	}

	public static void main(String args[]) {
	    List< Person> list = new LinkedList< Person>();
	    Callback one = () -> System.out.println("fn1");	    
	    Callback two = () -> System.out.println("fn2");
	    Consumer< Person> q = (Person p) -> { System.out.println("Consumer: "+p); };
	    Visitor< Person> v = (Person p) -> { System.out.println("Visit: "+p); };
	    list.add(new Person("Edward", "Gerhold"));
	    list.add(new Person("Edwin", "Hold"));	    

	    one.call();
	    two.call();
	    for (Person p : list) {
		v.visit(p);
	    }
	    list.forEach(q);
	    list.forEach((Person p) -> System.out.println("forEach: "+p));
	}
}

Java.util.function.* hat mehrere Interfaces zur Verfügung, die man unterschiedlich einsetzen kann. Das Predicate< ?> zum Beispiel hat eine boolean test( t); Funktion, die true oder false returnt..Ein "Prädikat" eben.

Memento(?) State(!?) Keins(?!)

Experiment Entkopplung des Lexer Zustands

Ich habe den Zustand des Lexers experimentell vom Lexer getrennt. Dabei lege ich wieder allerhand Extra-Dateien und Lexer1, Lexer2, ... an. Hier versuche ich das erste mal mit dem Memento zu arbeiten. Sofern es das ist ;).

1. Im weiteren Verlauf entscheide ich mich erstmal, es in "State" umzutaufen, weil der Name schon eindeutig ist. Es ist etwas anders, als im Head First, wo die Transitionsfunktionen gekapselt wurden. Das muss hier nicht geschehen, da nextCharacter und nextToken so bleiben koennen, wie sie sind, und alle Hilfsfunktionen ebenfalls.

2. Hinterher benenne ich es wieder in Memento um, weil die Restaurierbarkeit, wie bei abspeicherbaren Spielständen, oder wieder einsetzbarem Lexerzustand im Vordergrund steht.

3. Am Ende des zweiten Tages des Samples mache ich daraus keins von beiden und demonstriere einfach die Entkopplung. Beim stoebern in Fowlers Argumenten ist ein "Move Method" fuer zum Beispiel lookAtChar gut. Weiterhin sollte ich das aktuelle gelesene Zeichen nicht aus mem holen, sondern im Lexer halten. Das wird beim Austauschen gesetzt. Damit spare ich mir die ewigen mem.chr Abfragen. Beim Hashcode und Equals Vergleich habe ich schon gezeigt, wie man sich irren kann.

Weil ich den Lexer flexibel für mehrere Inputs benötige, die mittendrin gestoppt und wieder gestartet werden wollen, und die Variablen immer rumkopieren muesste, hilft dieses Muster dabei, die Zustandsvariablen zu entkoppeln.

package de.linux_swt.jsparse;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;

public class JavaScriptLexerState {

    public int k;
    public int[] lookahead;
    public int p;
    public Token token;
    public int inputElementGoal;
    public long lastLine;
    public long lastColumn;
    public long lineNumber;
    public long columnNumber;
    public InputStreamReader inputStream;
    public String inputString;
    public BufferedReader buffer;
    public List< Token> tokens; // das hier ist eine Ergebnisliste, die wegfallen kann
				// oder in einer allgemeinen API co-existieren sollte.
				// Terrence Parr vermittelt einen CommonTokenStream, der
				// in etwa einer var tokens = [] (nur als Java Stream)
				// entspricht. Ist der Lexer im Parser integriert, wird
				// die Liste nicht benoetigt. Mit dem Stream Konzept liesse
				// sich das rausfaktorieren, oder ein decorator produziert
				// eine Liste dabei. 
    protected JavaScriptLexerState(int k) {
        this.tokens = new LinkedList<>();
        this.lineNumber = 1;
        this.columnNumber = 0;
        this.p = 0;
        this.inputMode = 1;
        this.inputElementGoal = 1;
        this.lookahead = new int[k];
        this.k = k;
    }
    public JavaScriptLexerState(InputStream input, int k) {
        this(k);
        this.inputMode = 1;
        this.inputStream = new InputStreamReader(input);
        this.buffer = new BufferedReader(this.inputStream);
    }
    public JavaScriptLexerState(String input, int k) {
         this(k);
         this.inputMode = 2;
         this.inputString = input;
    }
}

Um auf die Zustandsdaten anzusprechen, wird das Objekt nun angesprochen. Update: Weiter oben kommentiere ich bereits, Fowler wuerde hier unter anderem "move method" vorschlagen und sie dem Objekt zuordnen.

Ich denke, getter und setter Operationen werden optimiert und ersetzt, aber dran gewöhnen muss ich mich erst. Generieren kann ich die jetzt auch auf Knopfdruck.

package de.linux_swt.jsparse;
import java.io.IOException;
import static de.linux_swt.jsparse.JavaScriptSets.*;
import java.util.LinkedList;

public class JavaScriptLexer2 {

    public final Token END_TOKEN = new Token(S_EMPTY, TokenType.EOF);
    public final int   EOF = -1;
    
    public JavaScriptLexerState mem;	// State steht fuer Zustand, nicht das Muster

    public void setState(JavaScriptLexerState mem) {
        this.mem = mem;
    }

    public JavaScriptLexer2(InputStream in) throws IOException {
        setState(new JavaScriptLexerState(in, 4));
        mem.inputElementGoal = INPUTELEMENT_REGEXP;
        for (int i = 1; i <= mem.k; i++) consumeCharacter();
    }

    public char lookAtChar(int i) {
        return (char) mem.lookahead[(mem.p + i) % mem.k];
    }

    public int lookAt(int i) {
        return mem.lookahead[(mem.p + i) % mem.k];
    }

   /*
	isOperationen()
	lexOperationen()
	nextCharacter()
	nextToken()
	
	alle Variablen sind jetzt mit mem. zu erreichen
	
	Jetzt kann man den Zustand austauschen
	
	Und kann was anderes lexen, dann das wieder austauschen
	und das andere weiter-lexen, dann was anderes zwischendurch,
	dann was anderes lexen.
	
	In ECMA-262 zum Beispiel wird der Parser oefter gebraucht.
	Und der braucht wiederum einen freien Lexer.
   */

}

Es ist noch nicht perfekt. Beziehungsweise muss ich mich daran gewoehnen, den Zustand des Lexers entkoppelt von der Hauptinstanz zu betrachten.

Dieses Muster ermoeglicht einen schnellen Wechsel der Eingabe. Wie viel der State access addiert kann ich allerdings nicht sagen. Ca 1x member aufloesen pro zugriff, wenn der compiler nicht ´ne bessere Idee hat. In der Richtung bin ich noch nicht weit genug gekommen.

class field {
	public static class A {
	    public int b;
	    public A() { b = 10; }
	}
	public static void main(String args[]) {
	    A a = new A();
	    System.out.println(a.b);
	}
}

Compiled from "field.java"
class field {
  field();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class field$A
       3: dup           
       4: invokespecial #3                  // Method field$A."":()V
       7: astore_1      
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1       
      12: getfield      #5                  // Field field$A.b:I
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V 
      18: return        
}

Da gibt es wohl getfield für. Und ich denke, man muss halt noch A aufloesen. Irgendwann werde ich das mal gelesen haben, steht in der Java Spezifikation, was genau passiert. Aber soweit erstmal.

Operation

Unser Kaninchen ist am Freitag erfolgreich operiert worden, nachdem wir ihm die Woche über einen Arzt gesucht haben. Er ist bereits wieder putzmunter. Wir kümmern uns sehr gut um ihn. Der Arzt hat allerdings was richtig gutes vollbracht. Ihm gebührt der Dank, den Pünktchen richtig operiert und wieder vernäht zu haben. Jetzt geht´s regelmässig zur Nachbehandlung.

Design Patterns

Ich hab mir das Head First Buch bei der PSB drucken können. Jetzt lerne ich fleissig Entwurfsmuster, weil ich den Text besser lesen kann, als beim durchscrollen. Und ausserdem kann ich so unterwegs lesen.

Ich habe bereits ein dutzend Entwurfsmuster verstanden, die ich direkt in meinem vorhandenen Code anwenden kann, und ich denke, da laesst sich noch mehr machen. Das Gute ist, vieles überschneidet sich so mit meinen Einfällen, dass ich sie im zweiten Anlauf immernoch nutzen kann.

Toll, endlich auf die kommunizierbare Ebene zu wechseln. Ich werde auch syntax.js nachträglich auf Gleichnamigkeit und Baugleichheit mit einigen Entwurfsmustern umformulieren. Um die Arbeit hinterher einfach zu machen. Ich habe aus den Fehlern an syntax.js aber noch viel mehr als das gelernt.

Parser Generator

Terrence Parr schreibt geniale Bücher über sein Tool ANTLR und über allerhand Language Implementation Patterns.

SourceLocation 2

Ich denke, ich gehe so vor: public long[] loc in class ParseNode und die SourceLocation Klasse nur auf Anfrage.

Allerdings habe ich long[] wieder in int umgewandelt, weil der Ueberlauf erst bei zwei Millarden und hundertsiebenundvierzig Millionen stattfindet. Das sind mehr Lines und Columns als das groesste Projekt der Erde bislang hat.

import java.lang.Math;

class intg {
	public static void main(String args[]) {
	    // Noch kein Ueberlauf
	    int i = (int)Math.pow(2,31);
	    System.out.println(i);
	    
	    // Ueberlauf
	    int j = (int)Math.pow(2,31) + 1;
	    System.out.println(j);
	}
}
/*
2147483647
-2147483648
*/

SourceLocation

Jeder Parser speichert irgendwo Line und Column ab, man nennt es zum Beispiel "SourceLocation".

Erst überlegte ich, jeder Value eine einfache Property zu geben. Dann kürzte ich zu einem Array, der bei makeLoc() an eine neue SourceLocation übergeben wird und als solcher gespeichert wird.

package de.linux_swt.jsparse;
import java.util.Arrays;

/**
 * Created by root on 12.03.14.
 */
public class SourceLocation {
    public final long [] loc;
    public SourceLocation(int[] loc) {
        this.loc = Arrays.copyOf(loc, loc.length);
    }
    public SourceLocation(int[] loc1, int[] loc2) {
        this.loc = new int[]{loc1[0],loc1[1],loc2[0],loc2[1]};
    }
    public int[] startLocation() {
        return new int[]{loc[0],loc[1]};
    }
    public int[] endLocation() {
        return new int[]{loc[2],loc[3]};
    }
    public String toString () {
        return "[SourceLocation: line "+loc[0]+", column "+loc[1]+"/ line "+loc[2]+", column "+loc[3]+"]";
    }    
}

Den ToString kann man als final slocString abspeichern, anstatt ihn jedesmal neu zu bauen.

War kurz vorher, mit ein wenig edit, dass ich den Array nehmen will...vorher waren das nur startLine, startColumn, endLine, endColumn.

package de.linux_swt.jsparse.nodes;

/**
 * Created by root on 11.03.14.
 */

public class SourceLocation1 {
    public long startLine;
    public long startColumn;
    public long endLine;
    public long endColumn;
    // int[] loc;
    public SourceLocation1() {    }
    public SourceLocation1(long startLine, long startColumn, long endLine, long ec) {
        setStart(startLine, startColumn);
        setEnd(endLine,ec);
    }
    public SourceLocation1(long startLine, long startColumn) {
        setStart(startLine, startColumn);
    }
    public void setStart (long line, long column) {        
        startLine = line;
        startColumn = column;
    }
    public void setEnd (long line, long column) {
        endLine = line;
        endColumn = column;
    }
    public int[] getStart () {
        return new int[]{startLine, startColumn};
    }
    public int[] getEnd () {
        return new int[]{endLine, endColumn};
    }
    public int[] getArray() {
        return new int[]{startLine,startColumn,endLine,endColumn};
    }
}
    

Sieht übel aus, nicht wahr?

junit.jar

Das setzen des CLASSPATHs habe ich dann auch verstanden, nach einigen Fehlschlägen. Die jars kann ich also manuell angeben oder in ein Verzeichnis der jre kopieren oder als Parameter mit angeben. Ich hab jetzt ersteres gemacht und mir junit zum CLASSPATH addiert.

linux-dww5:/llkg/src # javac TestLexer.java 
linux-dww5:/llkg/src # java TestLexer
.F
Time: 0,003
There was 1 failure:
1) warning(junit.framework.TestSuite$1)junit.framework.AssertionFailedError: Class TestLexer is not public
        at TestLexer.main(TestLexer.java:31)

FAILURES!!!
Tests run: 1,  Failures: 1,  Errors: 0

linux-dww5:/llkg/src # mcedit TestLexer.java

linux-dww5:/llkg/src # javac TestLexer.java 
linux-dww5:/llkg/src # java TestLexer
.
Time: 0,044

OK (1 test)

linux-dww5:/llkg/src # 

Sah noch was billig aus..Unsicherheit die kompilierbar ist.

package de.linux_swt.jsparse.tests;

import org.junit.*;
import org.junit.Test;
import junit.runner.*;
import junit.textui.*;
import junit.framework.*;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import de.linux_swt.jsparse.JavaScriptLexer;
import de.linux_swt.jsparse.TokenType;
import de.linux_swt.jsparse.Token;

public class TestLexer extends TestCase {
    public JavaScriptLexer lexer;

    public TestLexer () {}
    public TestLexer (String name) {
        super(name);
    }

    public void setUp() {
        try {
            lexer = new JavaScriptLexer(new FileInputStream("/llkg/src/testcode.js"));
            System.out.println("setUp() lexer instance created.");
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
            System.exit(0);
        } catch (IOException ex) {
            ex.printStackTrace();
            System.exit(0);
        } finally {

        }
    }

    public void tearDown() {
        lexer = null;
        System.out.println("set lexer to zero");
    }

    @Test
    public void testLexer1() throws IOException {
        lexer.nextToken();
        assertTrue("erstes token sollte 'let' sein", lexer.token.name.equals("let"));
    }

}

Jetzt kann ich das Buch von dem Herrn Westphal lesen.

Damit schaffe ich es, das Projekt auch von Anfang an zu testen.

Für setUp und tearDown kann man heute @Before und @After annotieren, habe ich im SWT1 von KIT gesehen. Apache Maven und POM.XML darf ich als nächstes verstehen lernen. Noch failen Tests bei mir, oder ich wirke was hilflos mit, weil ich mit den Maven Docs noch nicht durch bin. Allerdings habe ich aus syntax.js gelernt, nie wieder, wenn ich weiterprogrammieren will, einfach was zu schreiben, sondern bitte alles einzusetzen, was andere auch lernen müssen.

Memento Design Pattern

Die parseGoal(String goalSymbol, String inputString) Funktion verlangt einen Parser im Parser, was bedeutet, dass der Zustand des Parsers auf den Stack muss. Schlimm ist, wenn man die Variablen jedesmal erst zusammenstellen muss.

Darum kann man all die Variablen in ein separates austauschbares Objekt einordnen. Das Memento Pattern war glaube ich das richtige. Sonst gibt es noch mehrere für austauschbare, restaurierbare Zustände.

In dem wird der Zustand des Objekts in einem separatem Objekt gekapselt. Es gibt Operationen zum wiedereinsetzen und entnehmen des State Objekts.

Man koennte natuerlich auch mehrere parser erzeugen, aber ob man für jedes rufen von parseGoal einen neuen Parser will? Wenn ich eine Funktion baue, die parse, muss ich dann für jeden Parameter, wenn das Arguments Objekt erstellt wird einen neuen Parser erzeugen, darum denke ich mal lieber nicht.

Etwas anderes ist ein zweiter Parsing Mode. Der eine ist STREAM, der andere sollte LIST sein, denn wenn ich CoverParenthesizedExpressionAndArrowParameterList geparsed habe, dann habe ich eine Liste unverarbeiteter Token. Dann nehme ich das Memento und lege den Parser beiseite, prozessiere die Liste und setze das Memento wieder ein.

Plan

Diesmal baue ich einen statischen Type Verifizierer ein.

Plan A

Die JS Objekte sind diesmal direkt byte[] und keine class Objekte. Wenn ich zu Objekten und Apis komme, werde ich direkt eine auf dem ByteCodeArray arbeitende Maschinerie schreiben, die ich bei syntax.js nun mühselig nachträglich Schritt für Schritt einbaue, während ich Code, den ich austauschen muss schreiben muss, um es am laufen zu halten, mit dem AST und den JS Objekten.

Skip

    public void skip(String symbol) throws IOException {
        if (currentToken.name.equals(symbol)) nextToken();
    }

Factory Methods

Aus

   protected FunctionDeclaration parseFunctionDeclaration () throws IOException {
        match(S_FUNCTION);        
        Identifier identifier = parseIdentifier();        
        match(S_LPAREN);
        FormalParameterList formals = parseFormalParameterList();
        match(S_RPAREN);        
        StatementList functionBody = parseFunctionBody();        
        FunctionDeclaration fdecl = new FunctionDeclaration();
        fdecl.identifier = identifier;
        fdecl.formalParameterList = formals;
        fdecl.functionBody = functionBody;
        return fdecl;
    }
;

Wird, weil die Entwurfsmustererfinder recht haben, das wird ein Chaos in der Funktion, erzeuge ich da auch noch die Nodes.

   protected FunctionDeclaration parseFunctionDeclaration () throws IOException {

        match(S_FUNCTION);        
        Identifier identifier = parseIdentifier();        
        match(S_LPAREN);
        FormalParameterList formals = parseFormalParameterList();
        match(S_RPAREN);        
        StatementList functionBody = parseFunctionBody();      

        return ParseNodeFactory.createFunctionDeclaration(identifier, formals, functionBody);        
    }
;

return parseNodeFactory.createFunctionDeclaration(identifier, formals, functionBody);. Das Fabrik Muster mit FabrikMethoden. Aus Head First Design Patterns gelernt, das Interview mit den beiden (Factory Method und AbstractFactory im Interview) war klasse, ich habe gelacht. Das Buch ist top.

Parsing angefangen...

   protected StatementList parseFunctionBody () throws IOException {
        StatementList functionBody = new StatementList();
        Statement s;
        match(S_LBRACE);
	while (!lookAtTokenName(0).equals(S_RBRACE)) {
            s = parseStatement();
            functionBody.add(s);
        } 
        match(S_RBRACE);
        return functionBody;
    }

Vereinfachte Darstellung ohne weitere Static Semantic Tests (zusätzliche properties)

Die ein paar Abschnitte weiter angedeuteten Versuche ".hashCode()" einzusetzen kommt mir gleich zugute. Ich habe sie ja bereits definiert (ich habe alle S_ mit einem RegExp gematcht und die gleich mit H_$1 = S_$1.hashCode() generiert). Im Parser kann ich sie im Switch gebrauchen. Ich bin mir allerdings nicht so sicher, wieviel unterschied zwischen den beiden generierten Codes besteht. Meine Aufgabe ist wohl, beides kurz zu verfassen, zu kompilieren und zu disassemblieren, um zu sehen, wo weniger code benoetigt wird. So ist das reine Spekulation.

// Anstatt
    protected Statement parseStatement() {
        ...
        if (token.name.equals(S_FUNCTION)) return parseFunctionDeclaration();
        if (token.name.equals(S_...)) return parse...();
        if (token.name.equals(S_---)) return parse---();
        ...
    }

Allerdings ist noch nicht geprüft, ob der HashCode nicht mit einem anderen HashCode kollidiert. Normalerweise ist der Tokenizer so freundlich, die Token schon klassifiziert zu haben, dass ich beim Testen von RESERVEDWORD vom richtigen HashCode ausgehen kann, weil die einzig sein sollten.

// Lieber

    protected Statement parseStatement() {
        
        if (token.type.equals(TokenType.RESERVEDWORD))
        switch(token.name.hashCode()) {        

        case H_FUNCTION:
            return parseFunctionDeclaration();
        case H_VAR:
            return parseVariableDeclaration();
        case H_CONST:
        case H_LET:
            return parseVariableDeclaration();
        case H_CLASS:
             return parseClassDeclaration();
        case H_MODULE:
             return parseModuleDeclaration();        
        // ...
        case H_THROW:
             return parseThrowStatement();
        case H_RETURN:
             return parseReturnStatement();
        case H_BREAK:
             return parseBreakStatement();             
        case H_CONTINUE:
             return parseContinueStatement();                          
            // ...
        default:
            return null;
        }

        return parseExpressionStatement();
    }
    

Im Tokenizer von gestern werde ich den int[] lookahead ausbauen (entfernen) und nur lookahead = lookahead1; lookahead1 = nextCharacter(); einsetzen. Weiter muss nicht gescannt werden, und immer lookAtChar(0) rufen ersetze ich mit lookahead und lookAtChar(1) mit lookahead1; eine p = (p+1)%k; Operation wird damit nicht mehr jedes Token verlangt. Allerdings ist das noch lange nicht das, was optimieren heisst, da darf ich die Zeichen, so wie sie definiert sind, in ihrer Reihenfolge hardcoden. Das ist fast (schnell).

Tokenizing fortgesetzt...

Hier tausche ich die 4->3->2->1 Methode mit einer 1->(2->3|3)->4 Methode. Die optimale Methode ist hardcoded und bewegt sich exakt durch die vorgegebenen Operatorzeichen mit verschachtelten switch Statements. Damit erzeuge ich dann eine perfekte 1->2->3->4

    // heute.
    public Token lexOperator () throws IOException {
        char c, d;
        StringBuilder operator = new StringBuilder();
        operator.append(lookAtChar(0));
        nextCharacter();
        if (operators.contains(operator.toString() + (c=lookAtChar(0)))) {
            operator.append(c);
            nextCharacter();
            if (operators.contains(operator.toString() + (c=lookAtChar(0)))) {
                operator.append(c);
                nextCharacter();
            }
        } else if (operators.contains(operator.toString() + c + (d=lookAtChar(1)))) {
            operator.append(c);
            operator.append(d);
            advanceCharacters(2);
            return new Token(operator.toString(), TokenType.OPERATOR);
        }
        if (operator.equals(S_SIGNED_RIGHTSHIFT) && lookAtChar(0) == C_ASSIGN) {
            operator.append(C_ASSIGN);
            nextCharacter();
        }
        return new Token(operator.toString(), TokenType.OPERATOR);
    }

    // gestern
    public Token lexOperator1 () throws IOException {
        String assumption;
        debug("operator");
        assumption = "" + lookAtChar(0) + lookAtChar(1) + lookAtChar(2) + lookAtChar(3);
        if (operators.contains(assumption)) {
            advanceCharacters(4);
            return new Token(assumption, TokenType.OPERATOR);
        }
        assumption = assumption.substring(0,3);
        if (operators.contains(assumption)) {
            advanceCharacters(3);
            return new Token(assumption, TokenType.OPERATOR);
        }
        assumption = assumption.substring(0,2);
        if (operators.contains(assumption)) {
            advanceCharacters(2);
            return new Token(assumption, TokenType.OPERATOR);
        }
        assumption = assumption.substring(0,1);
        if (operators.contains(assumption)) {
            nextCharacter();
            return new Token(assumption, TokenType.OPERATOR);
        }
        return null;
    }

Wie ich unten sah, war [ (C_LBRACK) nicht gefunden worden. Ich hab nachgeguckt. Es war zufällig als 'E' definiert. ;-)

Token{name='let', type=IDENTIFIER}
Token{name='x', type=IDENTIFIER}
Token{name='=', type=OPERATOR}
Token{name='10', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='var', type=RESERVEDWORD}
Token{name='y', type=IDENTIFIER}
Token{name='=', type=OPERATOR}
Token{name='x', type=IDENTIFIER}
Token{name='=>', type=OPERATOR}
Token{name='x', type=IDENTIFIER}
Token{name='*', type=OPERATOR}
Token{name='x', type=IDENTIFIER}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='const', type=RESERVEDWORD}
Token{name='z', type=IDENTIFIER}
Token{name='=', type=OPERATOR}
Token{name='{', type=OPERATOR}
Token{name='a', type=IDENTIFIER}
Token{name=':', type=OPERATOR}
Token{name='1', type=NUMERICLITERAL}
Token{name=',', type=OPERATOR}
Token{name='b', type=IDENTIFIER}
Token{name=':', type=OPERATOR}
Token{name='2', type=NUMERICLITERAL}
Token{name='}', type=OPERATOR}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='7.555', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='.777', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='10e-23', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='10.667e-25', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='0o777', type=OCTALLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='0b1111', type=BINARYLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='4', type=NUMERICLITERAL}
Token{name='+', type=OPERATOR}
Token{name='4', type=NUMERICLITERAL}
Token{name='+', type=OPERATOR}
Token{name='4', type=NUMERICLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='C', type=IDENTIFIER}
Token{name='++', type=OPERATOR}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='[', type=OPERATOR}
Token{name='1', type=NUMERICLITERAL}
Token{name=',', type=OPERATOR}
Token{name='2', type=NUMERICLITERAL}
Token{name=',', type=OPERATOR}
Token{name='3', type=NUMERICLITERAL}
Token{name=']', type=OPERATOR}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='function', type=RESERVEDWORD}
Token{name='f', type=IDENTIFIER}
Token{name='(', type=OPERATOR}
Token{name='a', type=IDENTIFIER}
Token{name=',', type=OPERATOR}
Token{name='b', type=IDENTIFIER}
Token{name=',', type=OPERATOR}
Token{name='{', type=OPERATOR}
Token{name='c', type=IDENTIFIER}
Token{name=',', type=OPERATOR}
Token{name='d', type=IDENTIFIER}
Token{name='}', type=OPERATOR}
Token{name=')', type=OPERATOR}
Token{name='{', type=OPERATOR}
Token{name='return', type=RESERVEDWORD}
Token{name='[', type=OPERATOR}
Token{name='...', type=OPERATOR}
Token{name='c', type=IDENTIFIER}
Token{name=']', type=OPERATOR}
Token{name=';', type=OPERATOR}
Token{name='}', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='let', type=IDENTIFIER}
Token{name='template', type=IDENTIFIER}
Token{name='=', type=OPERATOR}
Token{name='`Edward ${', type=TEMPLATEHEAD}
Token{name='ist', type=IDENTIFIER}
Token{name='} ein ${', type=TEMPLATEMIDDLE}
Token{name='toller', type=IDENTIFIER}
Token{name='} Name``', type=TEMPLATETAIL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='/(sfsdf)*/g', type=REGULAREXPRESSIONLITERAL}
Token{name=';', type=OPERATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='
', type=LINETERMINATOR}
Token{name='
', type=LINETERMINATOR}

Hatte ich doch gestern den String vergessen. Und ausserdem noch eine NullPointerException in der TemplateMiddle.

Heute mache ich dann ein wenig weiter..Zwischen Wartezimmer, U-Bahn und zu guter letzt dem Wohnzimmer.


    public boolean isStringStart() {
        char c = lookAtChar(0);
        return (c == C_SINGLE_QUOTE || c == C_DOUBLE_QUOTE);
    }

    public Token lexString () throws IOException {
        StringBuilder string = new StringBuilder();
        char startQuote = lookAtChar(0);
        char c;
        string.append(startQuote);
        nextCharacter();
        while ((c=lookAtChar(0)) != startQuote) {
            string.append(c);
            nextCharacter();
        }
        string.append(startQuote);
        nextCharacter();
        return new Token(string.toString(), TokenType.STRINGLITERAL);
    }

Den Identifier habe ich verbessert. Denn null, true und false koennen mit in die reservedWord Klammer, anstatt jedesmal mitgetestet zu werden.

    public Token lexIdentifier () throws IOException {
        debug("identifier");
        TokenType type;
        StringBuilder identifier = new StringBuilder();
        identifier.append(lookAtChar(0));
        nextCharacter();
        while (isIdentifierPart()) {
            identifier.append(lookAtChar(0));
            nextCharacter();
        }
        String id = identifier.toString();
        if (reservedWords.contains(id)) {
            type = TokenType.RESERVEDWORD;
            if (id.equals(S_NULL)) type = TokenType.NULLLITERAL;
            else if (id.equals(S_TRUE)) type = TokenType.BOOLEANLITERAL;
            else if (id.equals(S_FALSE)) type = TokenType.BOOLEANLITERAL;
        } else if (futureReservedWords.contains((id))) type = TokenType.FUTURERESERVEDWORD;
        else type = TokenType.IDENTIFIER;
        return new Token(id, type);
    }

Die Numeric Literale habe ich auch erweitert um den 0[xbo] Fall.

    public Token lexNumber () throws IOException {
        debug("number");
        StringBuilder number = new StringBuilder();
        char c = lookAtChar(0);
        if (c == '0') {
            char d = lookAtChar(1);
            switch (d) {
            case 'X':
            case 'x':
                number.append(""+c+d);
                advanceCharacters(2);
                while (hexDigits.contains(c = lookAtChar(0))) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.HEXLITERAL);
            case 'b':
            case 'B':
                number.append(""+c+d);
                advanceCharacters(2);
                while (binaryDigits.contains(c = lookAtChar(0))) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.BINARYLITERAL);
            case 'O':
            case 'o':
                number.append(""+c+d);
                advanceCharacters(2);
                while (octalDigits.contains(c = lookAtChar(0))) {
                    number.append(c);
                    nextCharacter();
                }
                return new Token(number.toString(), TokenType.OCTALLITERAL);
            }
        }
        number.append(c);
        nextCharacter();
        while (isNumberPart()) {
            number.append(lookAtChar(0));
            if (expSeen && signSeen) {
                signSeen = false;
                nextCharacter();
                number.append(lookAtChar(0));
            }
            nextCharacter();
        }
        expSeen = false;
        dotSeen = false;
        return new Token(number.toString(), TokenType.NUMERICLITERAL);
    }

Das inputElementGoal muss diesmal vom Parser aus geschaltet werden, oder ich programmiere optimistisch etwas Intelligenz rein, wie in syntax.js und prüfe, ob nach Verlauf der Token gerade eine LeftHandSide starten kann. Das geht nach jedem LineTerminator, Semikolon, }, nach Rechenzeichen.

    public boolean isRegularExpressionStart () {
        if (inputElementGoal == INPUTELEMENT_REGEXP) {            
            return lookAtChar(0) == '/' && !regExpNoneStarters.contains(lookAtChar(1));
        }
        return false;
    }

    public Token lexRegularExpression() throws IOException {
        StringBuilder regex = new StringBuilder();
        char c;
        debug("lexRegex");
        regex.append(lookAtChar(0));
        nextCharacter();
        while ((c = lookAtChar(0)) != '/') {
            regex.append(c);
            nextCharacter();
        }
        regex.append(c);
        nextCharacter();
        while (regExpFlags.contains(c=lookAtChar(0))) {
            regex.append(c);
            nextCharacter();
        }
        return new Token(regex.toString(), TokenType.REGULAREXPRESSIONLITERAL);
    }

Hin und weder fehlen ein paar Kleinigkeiten im Detail noch. Die Escape Character, Unicode Escape Sequenzen, die erkannt werden muessen.

    public Token lexTemplateString() throws IOException {
        StringBuilder template = new StringBuilder();
        char c;
        if (inputElementGoal != INPUTELEMENT_TEMPLATETAIL) {
            if (lookAtChar(0) == '`') {
                inputElementGoal = INPUTELEMENT_TEMPLATETAIL;
                template.append('`');
                nextCharacter();
                while (lookAt(0) != -1) {
                    template.append(c = lookAtChar(0));
                    if (c == '$' && lookAtChar(1) == '{') {
                        template.append('{');
                        advanceCharacters(2);
                        debug("templatehead");
                        return new Token(template.toString(), TokenType.TEMPLATEHEAD);
                    } else if (c == '`') {
                        debug("no substitution template");
                        inputElementGoal = INPUTELEMENT_REGEXP;
                        nextCharacter();
                        return new Token(template.toString(), TokenType.TEMPLATE);
                    }
                    nextCharacter();
                }
            }
        } else {
            if (lookAtChar(0) == '}') {
                template.append('}');
                nextCharacter();
                while (lookAt(0) != -1) {
                    template.append(c=lookAtChar(0));
                    if (c == '$' && lookAtChar(1) == '{') {
                        debug("template middle");
                        template.append("{");
                        advanceCharacters(2);
                        return new Token(template.toString(), TokenType.TEMPLATEMIDDLE);
                    } else if (c == '`') {
                        debug("template tail");
                        template.append(c);
                        nextCharacter();
                        inputElementGoal = INPUTELEMENT_REGEXP;
                        return new Token(template.toString(), TokenType.TEMPLATETAIL);
                    }
                    nextCharacter();
                }
            }
        }
        return null;
    }

Ergebnis ist gut...Es laeuft.

Wie gesagt. Alle '\\' escapesequences und \u muessen noch erkannt werden und ausserdem habe ich die MultiLineStringLiterals, die mit Backslash fortgesetzt werden noch nicht drin. In syntax.js habe ich einen perfekten Escape-Detection Algorithmus, den ich zu Rate ziehen kann.

/*
testcode.js:

let x = 10;
var y = x => x*x;
const z = { a:1, b: 2 };
7.555;
.777;
10e-23;
10.667e-25;
0o777;
0b1111;
4+4+4;
C++;
[1,2,3];
function f(a,b,{c,d}) { return [...c]; }
let template = `Edward ${ist} ein ${toller} Name`;
/(sfsdf)*/g;

Resultat:

debug lookahead[p] = l
debug lookahead[p] = e
debug lookahead[p] = t
debug lookahead[p] =  
debug identifier
debug lookahead[p] = x
debug lookahead[p] =  
debug lookahead[p] = =
Token{name='let', type=IDENTIFIER}
debug whitespace
debug lookahead[p] =  
debug identifier
debug lookahead[p] = 1
Token{name='x', type=IDENTIFIER}
debug whitespace
debug lookahead[p] = 0
debug operator
debug lookahead[p] = ;
Token{name='=', type=OPERATOR}
debug whitespace
debug lookahead[p] = 

debug number
debug lookahead[p] = v
debug lookahead[p] = a
Token{name='10', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = r
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] =  
Token{name='
', type=LINETERMINATOR}
debug identifier
debug lookahead[p] = y
debug lookahead[p] =  
debug lookahead[p] = =
Token{name='var', type=RESERVEDWORD}
debug whitespace
debug lookahead[p] =  
debug identifier
debug lookahead[p] = x
Token{name='y', type=IDENTIFIER}
debug whitespace
debug lookahead[p] =  
debug operator
debug lookahead[p] = =
Token{name='=', type=OPERATOR}
debug whitespace
debug lookahead[p] = >
debug identifier
debug lookahead[p] =  
Token{name='x', type=IDENTIFIER}
debug whitespace
debug lookahead[p] = x
debug operator
debug lookahead[p] = *
debug lookahead[p] = x
Token{name='=>', type=OPERATOR}
debug whitespace
debug lookahead[p] = ;
debug identifier
debug lookahead[p] = 

Token{name='x', type=IDENTIFIER}
debug operator
debug lookahead[p] = c
Token{name='*', type=OPERATOR}
debug identifier
debug lookahead[p] = o
Token{name='x', type=IDENTIFIER}
debug operator
debug lookahead[p] = n
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = s
Token{name='
', type=LINETERMINATOR}
debug identifier
debug lookahead[p] = t
debug lookahead[p] =  
debug lookahead[p] = z
debug lookahead[p] =  
debug lookahead[p] = =
Token{name='const', type=RESERVEDWORD}
debug whitespace
debug lookahead[p] =  
debug identifier
debug lookahead[p] = {
Token{name='z', type=IDENTIFIER}
debug whitespace
debug lookahead[p] =  
debug operator
debug lookahead[p] = a
Token{name='=', type=OPERATOR}
debug whitespace
debug lookahead[p] = :
debug operator
debug lookahead[p] = 1
Token{name='{', type=OPERATOR}
debug whitespace
debug lookahead[p] = ,
debug identifier
debug lookahead[p] =  
Token{name='a', type=IDENTIFIER}
debug operator
debug lookahead[p] = b
Token{name=':', type=OPERATOR}
debug number
debug lookahead[p] = :
Token{name='1', type=NUMERICLITERAL}
debug operator
debug lookahead[p] =  
Token{name=',', type=OPERATOR}
debug whitespace
debug lookahead[p] = 2
debug identifier
debug lookahead[p] =  
Token{name='b', type=IDENTIFIER}
debug operator
debug lookahead[p] = }
Token{name=':', type=OPERATOR}
debug whitespace
debug lookahead[p] = ;
debug number
debug lookahead[p] = 

Token{name='2', type=NUMERICLITERAL}
debug whitespace
debug lookahead[p] = 7
debug operator
debug lookahead[p] = .
Token{name='}', type=OPERATOR}
debug operator
debug lookahead[p] = 5
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 5
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 5
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = .
debug lookahead[p] = 7
Token{name='7.555', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = 7
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 7
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 1
debug lookahead[p] = 0
Token{name='.777', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = e
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = -
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 2
debug lookahead[p] = 3
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 1
debug lookahead[p] = 0
Token{name='10e-23', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = .
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 6
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 6
debug lookahead[p] = 7
debug lookahead[p] = e
debug lookahead[p] = -
debug lookahead[p] = 2
debug lookahead[p] = 5
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 0
debug lookahead[p] = o
Token{name='10.667e-25', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = 7
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 7
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 7
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 0
debug lookahead[p] = b
Token{name='0o777', type=OCTALLITERAL}
debug operator
debug lookahead[p] = 1
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 1
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 1
debug lookahead[p] = 1
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 4
debug lookahead[p] = +
Token{name='0b1111', type=BINARYLITERAL}
debug operator
debug lookahead[p] = 4
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = +
Token{name='
', type=LINETERMINATOR}
debug number
debug lookahead[p] = 4
Token{name='4', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = ;
Token{name='+', type=OPERATOR}
debug number
debug lookahead[p] = 

Token{name='4', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = C
Token{name='+', type=OPERATOR}
debug number
debug lookahead[p] = +
Token{name='4', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = +
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = ;
Token{name='
', type=LINETERMINATOR}
debug identifier
debug lookahead[p] = 

Token{name='C', type=IDENTIFIER}
debug operator
debug lookahead[p] = [
debug lookahead[p] = 1
Token{name='++', type=OPERATOR}
debug operator
debug lookahead[p] = ,
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = 2
Token{name='
', type=LINETERMINATOR}
Unknown character [
debug lookahead[p] = ,
debug number
debug lookahead[p] = 3
Token{name='1', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = ]
Token{name=',', type=OPERATOR}
debug number
debug lookahead[p] = ;
Token{name='2', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = 

Token{name=',', type=OPERATOR}
debug number
debug lookahead[p] = f
Token{name='3', type=NUMERICLITERAL}
debug operator
debug lookahead[p] = u
Token{name=']', type=OPERATOR}
debug operator
debug lookahead[p] = n
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = c
Token{name='
', type=LINETERMINATOR}
debug identifier
debug lookahead[p] = t
debug lookahead[p] = i
debug lookahead[p] = o
debug lookahead[p] = n
debug lookahead[p] =  
debug lookahead[p] = f
debug lookahead[p] = (
debug lookahead[p] = a
Token{name='function', type=RESERVEDWORD}
debug whitespace
debug lookahead[p] = ,
debug identifier
debug lookahead[p] = b
Token{name='f', type=IDENTIFIER}
debug operator
debug lookahead[p] = ,
Token{name='(', type=OPERATOR}
debug identifier
debug lookahead[p] = {
Token{name='a', type=IDENTIFIER}
debug operator
debug lookahead[p] = c
Token{name=',', type=OPERATOR}
debug identifier
debug lookahead[p] = ,
Token{name='b', type=IDENTIFIER}
debug operator
debug lookahead[p] = d
Token{name=',', type=OPERATOR}
debug operator
debug lookahead[p] = }
Token{name='{', type=OPERATOR}
debug identifier
debug lookahead[p] = )
Token{name='c', type=IDENTIFIER}
debug operator
debug lookahead[p] =  
Token{name=',', type=OPERATOR}
debug identifier
debug lookahead[p] = {
Token{name='d', type=IDENTIFIER}
debug operator
debug lookahead[p] =  
Token{name='}', type=OPERATOR}
debug operator
debug lookahead[p] = r
Token{name=')', type=OPERATOR}
debug whitespace
debug lookahead[p] = e
debug operator
debug lookahead[p] = t
Token{name='{', type=OPERATOR}
debug whitespace
debug lookahead[p] = u
debug identifier
debug lookahead[p] = r
debug lookahead[p] = n
debug lookahead[p] =  
debug lookahead[p] = [
debug lookahead[p] = .
debug lookahead[p] = .
Token{name='return', type=RESERVEDWORD}
debug whitespace
debug lookahead[p] = .
Unknown character [
debug lookahead[p] = c
debug operator
debug lookahead[p] = ]
debug lookahead[p] = ;
debug lookahead[p] =  
Token{name='...', type=OPERATOR}
debug identifier
debug lookahead[p] = }
Token{name='c', type=IDENTIFIER}
debug operator
debug lookahead[p] = 

Token{name=']', type=OPERATOR}
debug operator
debug lookahead[p] = l
Token{name=';', type=OPERATOR}
debug whitespace
debug lookahead[p] = e
debug operator
debug lookahead[p] = t
Token{name='}', type=OPERATOR}
debug lineterminator
debug lookahead[p] =  
Token{name='
', type=LINETERMINATOR}
debug identifier
debug lookahead[p] = t
debug lookahead[p] = e
debug lookahead[p] = m
Token{name='let', type=IDENTIFIER}
debug whitespace
debug lookahead[p] = p
debug identifier
debug lookahead[p] = l
debug lookahead[p] = a
debug lookahead[p] = t
debug lookahead[p] = e
debug lookahead[p] =  
debug lookahead[p] = =
debug lookahead[p] =  
debug lookahead[p] = `
Token{name='template', type=IDENTIFIER}
debug whitespace
debug lookahead[p] = E
debug operator
debug lookahead[p] = d
Token{name='=', type=OPERATOR}
debug whitespace
debug lookahead[p] = w
debug lookahead[p] = a
debug lookahead[p] = r
debug lookahead[p] = d
debug lookahead[p] =  
debug lookahead[p] = $
debug lookahead[p] = {
debug lookahead[p] = i
debug lookahead[p] = s
debug lookahead[p] = t
debug lookahead[p] = }
debug templatehead
Token{name='`Edward ${', type=TEMPLATEHEAD}
debug identifier
debug lookahead[p] =  
debug lookahead[p] = e
debug lookahead[p] = i
Token{name='ist', type=IDENTIFIER}
debug lookahead[p] = n
debug lookahead[p] =  
debug lookahead[p] = $
debug lookahead[p] = {
debug lookahead[p] = t
debug lookahead[p] = o
debug template middle
debug lookahead[p] = l
debug lookahead[p] = l
Token{name='} ein ${', type=TEMPLATEMIDDLE}
debug identifier
debug lookahead[p] = e
debug lookahead[p] = r
debug lookahead[p] = }
debug lookahead[p] =  
debug lookahead[p] = N
debug lookahead[p] = a
Token{name='toller', type=IDENTIFIER}
debug lookahead[p] = m
debug lookahead[p] = e
debug lookahead[p] = `
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = /
debug template tail
debug lookahead[p] = (
Token{name='} Name``', type=TEMPLATETAIL}
debug operator
debug lookahead[p] = s
Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = f
Token{name='
', type=LINETERMINATOR}
debug lexRegex
debug lookahead[p] = s
debug lookahead[p] = d
debug lookahead[p] = f
debug lookahead[p] = )
debug lookahead[p] = *
debug lookahead[p] = /
debug lookahead[p] = g
debug lookahead[p] = ;
debug lookahead[p] = 

debug lookahead[p] = 

debug lookahead[p] = 

Token{name='/(sfsdf)*/g', type=REGULAREXPRESSIONLITERAL}
debug operator
debug lookahead[p] = 

Token{name=';', type=OPERATOR}
debug lineterminator
debug lookahead[p] = ï¿¿
Token{name='
', type=LINETERMINATOR}
debug lineterminator
debug lookahead[p] = ï¿¿
Token{name='
', type=LINETERMINATOR}
debug lineterminator
debug lookahead[p] = ï¿¿
Token{name='
', type=LINETERMINATOR}
debug lineterminator
debug lookahead[p] = ï¿¿
Token{name='
', type=LINETERMINATOR}


*/

i18n

Das folgende Beispiel ist nicht von mir, sondern direkt aus dem JavaTutorial von Oracle rauskopiert.

Hier sieht man wie man ein ResourceBundle laden kann. Man erzeugt ein locale Objekt und uebergibt es dem Konstruktor. Das ResourceBundle wird in der entsprechenden Sprache, falls vorhanden geladen und falls nicht, dann das Default Bundle.

Dann kann man mit getString("key") die msg der zeile "key = msg" aus dem Bundle holen. Das ist eine einfache Datei mit name=wert Paaren.

import java.util.*;

public class I18NSample {

    static public void main(String[] args) {

        String language;
        String country;

        if (args.length != 2) {
            language = new String("en");
            country = new String("US");
        } else {
            language = new String(args[0]);
            country = new String(args[1]);
        }

        Locale currentLocale;
        ResourceBundle messages;

        currentLocale = new Locale(language, country);

        messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
        System.out.println(messages.getString("greetings"));
        System.out.println(messages.getString("inquiry"));
        System.out.println(messages.getString("farewell"));
    }
}
/*
To compile and run this program, you need these source files:

    I18NSample.java
    MessagesBundle.properties
    MessagesBundle_de_DE.properties
    MessagesBundle_en_US.properties
    MessagesBundle_fr_FR.properties

*/

HashSet < Character>

Allerdings, weil mir das rufen von set.contains(String.valueOf(lookAtChar(0))) zu aufwändig ist, habe ich ein Character Set für die Anfangszeichen und 1. Operatoren erzeugt.

Allerdings brauche ich HashSet< int> und HashSet< char> und weil es die nicht gibt, weil die eingebauten HashSets den Typ Object wollen, muss ich mir die mal selbst schreiben. Die Listen kann ich dann auch ersetzen.

Eigene Strings sollte man zur Bequemlichkeit fuer += und = mit .append() und .replace() bereichern, um einfach den Zuweisungsoperator zu ersetzen, aber mit toString() intensive Strings basteln zu koennen.

Apropos Strings basteln. Dafür sind StringBuilder. Die sind mutable (veraenderlich). Die nutzen auch .append(). Damit sind eigene Strings dann automatisch anwenderfreundlich definiert.

Tokenizing

Operatoren schnell gelext. Am einfachsten ist es wieder mit den Vierbuchstaben operatoren anzufangen. So ist die Version roh und unoptimiert, und fragt bei jedem Operator erstmal nach, ob er vielleicht vier Zeichen lang ist. Von dem es nur >>>= gibt, was auf '>' zurueckgeht. Anderseits sind '...' und '!==' oder '>>=' oder '>>>' wieder drei lang. Und mit zweien gibt es auch genug. Weil sich jedesmal vier Zeichen zu kopieren per Funktion aber zuviel ist, wo es nur bei '>' bis '>>>=' geht, sollte je ein Set für die Alternativen reichen.

    public Token lexOperator () throws IOException {
        String assumption;
        debug("operator");
        assumption = "" + lookAtChar(0) + lookAtChar(1) + lookAtChar(2) + lookAtChar(3);        
        if (operators.contains(assumption)) {
            advanceCharacters(4);
            return token = new Token(assumption, TokenType.OPERATOR);
        }
        assumption = assumption.substring(0,3);
        if (operators.contains(assumption)) {
            advanceCharacters(3);
            return token = new Token(assumption, TokenType.OPERATOR);
        }        
        assumption = assumption.substring(0,2);
        if (operators.contains(assumption)) {
            advanceCharacters(2);
            return token = new Token(assumption, TokenType.OPERATOR);
        }
        assumption = assumption.substring(0,1);
        if (operators.contains(assumption)) {
            nextCharacter();
            return token = new Token(assumption, TokenType.OPERATOR);
        }
        return null;
    }

Hier versuche ich schonmal das NumericLiteral zu parsen. Wenn ich aber jetzt darueber nachdenke, habe ich das 0[xX], 0[bB], 0[oO] zu Beginn des Literals vergessen. Allerdings werden ., Exponent und Zeichen direkt nach dem Exponent geprueft und bereits korrekt blockiert, dass sie unmoeglich doppelt geparsed werden koennen.

    protected boolean dotSeen;

    public boolean isNumberStart() {
        char x = lookAtChar(0);
        if (x == '.' && Character.isDigit(lookAtChar(1))) return dotSeen = true;
        return (x >= '0' && x <= '9');
    }
    protected boolean expSeen;
    protected boolean signSeen;

    public boolean isNumberPart() {
        char c = lookAtChar(0);
        if (c >= '0' && c <= '9') return true;
        if (!dotSeen && c == '.') return dotSeen = true;
        if (!expSeen && (c == 'e' || c == 'E')) {
            char d = lookAtChar(1);
            if (d == '+' || d == '-') signSeen = true;
            return expSeen = true;
        }        
        return false;
    }

    public Token lexNumber () throws IOException {
        debug("number");
        StringBuilder number = new StringBuilder();
        char c = lookAtChar(0);
        number.append(c);
        nextCharacter();
        while (isNumberPart()) {
            number.append(lookAtChar(0));            
            if (signSeen) {
                signSeen = false; 
                nextCharacter();               
                number.append(lookAtChar(0));
            }
            nextCharacter();
        }
        expSeen = false;        
        dotSeen = false;
        return token = new Token(number.toString(), TokenType.NUMERICLITERAL);
    }


Besonders unpraktisch finde ich die Stellen, wo man mehrere Ifs benutzt. Zum Beispiel muss man hier die Worte unterscheiden.

    public Token lexIdentifier () throws IOException {
	// ...
	// erst einlesen, dann pruefen 
	// ...
        String id = identifier.toString();
        if (reservedWords.contains(id)) type = TokenType.RESERVEDWORD;
        else if (futureReservedWords.contains((id))) type = TokenType.FUTURERESERVEDWORD;
        else if (id.equals(S_NULL)) type = TokenType.NULLLITERAL;
        else if (id.equals(S_TRUE)) type = TokenType.BOOLEANLITERAL;
        else if (id.equals(S_FALSE)) type = TokenType.BOOLEANLITERAL;
        else type = TokenType.IDENTIFIER;
        return token = new Token(id, type);
    }

Was hier noch nicht beachtet wird, aber weiter auffaellt, ist, dass die mit \\ escapten Character nicht beachtet werden.

Aufgrund dieses Codes sollte man verstehen koennen, was das InputElementGoal ist, und warum man InputElementTemplateTail benoetigt. Die Templatemiddles und das Templatetail beginnen mit }. Da das Template vor dem Block } beendet werden muss, gehoert das dazu. Aber man muss dem Tokenizer das sagen, dass hier nicht der Block zu Ende ist, bzw. es einfach ein } ist. Ebenso macht man das mit dem '/'. Bei einer LeftHandSide kann ein RegExp geparsed werden.

    public Token lexTemplateString() throws IOException {
        StringBuilder template = new StringBuilder();
        char c;
        if (inputElementGoal != INPUTELEMENT_TEMPLATETAIL) {
            if (lookAtChar(0) == '`') {
        	inputElementGoal = INPUTELEMENT_TEMPLATETAIL;
                template.append('`');
                nextCharacter();
                while (lookAt(0) != -1) {
                    template.append(c = lookAtChar(0));
                    if (c == '{') {
                        debug("templatehead");
                        return new Token(template.toString(), TokenType.TEMPLATEHEAD);
                    } else if (c == '`') {
                        debug("no substitution template");
                        inputElementGoal = INPUTELEMENT_REGEXP;
                        return new Token(template.toString(), TokenType.TEMPLATE);
                    }
                    nextCharacter();
                }
            }
        } else {
            if (lookAtChar(0) == '}') {
                template.append('}');
                nextCharacter();
                while (lookAt(0) != -1) {
                    template.append(c=lookAtChar(0));
                    if (c == '$' && lookAtChar(1) == '{') {
                        debug("template middle");
                        template.append("{");
                        advanceCharacters(2);
                        return new Token(template.toString(), TokenType.TEMPLATEMIDDLE);
                    } else if (c == '`') {
                        debug("template tail");
                        nextCharacter();
                        inputElementGoal = INPUTELEMENT_REGEXP;
                        return new Token(template.toString(), TokenType.TEMPLATETAIL);
                    }
                    nextCharacter();
                }
            }
        }
        return null;
    }

Hier gucke ich mit einer Operation kurz, ob der lookahead der Produktion entspricht. Der Tokenizer ist recursive descent und die lexOperationen kuemmern sich um das advancen.

Ich denke tokens.add(token) und token = koennte in die jeweiligen Funktionen. Allerdings haette ich das dann oefter zu schreiben. Wuerde hier aber die Code Blocks in einen Call dessen Resultat eine Condition beantwortet und einen TailCall verwandelt.

Tja, die Erfahrung fehlt, was hinterher besser kompiliert ist.

    public Token nextToken () throws IOException {

        while (lookAt(0) != -1) {
            if (isWhiteSpace()) {
                lexWhiteSpace();
                continue;
            }
            if (isTemplateStart()) {
                token = lexTemplateString();
                tokens.add(token);
                return token;
            }
            if (isLineTerminator()) {
                token = lexLineTerminator();
                tokens.add(token);
                return token;
            }
            if (isCommentStart()) {
                token = lexComment();
                tokens.add(token);
                continue;
            }
            if (isIdentifierStart()) {
                token = lexIdentifier();
                tokens.add(token);
                return token;
            }
            if (isNumberStart()) {
                token = lexNumber();
                tokens.add(token);
                return token;
            }
            if (isOperatorStart()) {
                token = lexOperator();
                tokens.add(token);
                return token;
            }
            System.out.format("Unknown character %s\n", lookAtChar(0));
            nextCharacter();
        }
        return END_TOKEN;
    }

Java Spec

Hab ich gerade angefangen durchzublaettern. Um einige Dinge geklaert zu kriegen, zum Beispiel, ob ich durch implementieren von .intValue(), .charValue(), etc. ebenfalls implizite Umwandlungen erreichen kann (aehnlich Operator Overloading, nur halt durch rufen einer vorhandenen Methode), oder ob es das nicht gibt.

Auffaellig sind die verwandten Grammatiken von JavaScript und Java. Ein Parsergenerator, der die alte ES5 Grammatik umwandeln kann, kann die auch umwandeln. Nicht ganz, da Template < > Klammern, (Type|void) Typenangaben und [throws Name] statt [lookahead !E= 'x'] hinzukommen, aber ganz allgemein, wie alle anderen Grammatiken sind die praktisch gleich zu lesen.

Ne, warum ich das poste. Ich finde die Dokumente sehr übersichtlich.

Parsen mit HashCodes

Heute hatte ich eine neue Idee. Nimm doch die HashCodes zum parsen, die sind alle int.

    public final String S_FUNCTION = "function";
    public final int H_FUNCTION = S_FUNCTION.hashCode();

Allerdings kaempft man auch hier mit Problemen: HashSet< Integer> kostet 4*4 = 16 Bytes, statt 4 Bytes pro int, weil in ein Integer gewrappt werden muss, weil die List nur Object annimmt. Abhilfe: Eigenes HashSet schreiben, dass primitive ints speichern kann.

Erste primitive nichtsaussagende (von der Programmierweise her zu billig) Benchmark, die zeigt, dass es kaum unterschiede zwischen den Ergebnissen von equals und hashCode gibt. Ich teste gegen S_FUNCTION ("function") und H_FUNCTION (S_FUNCTION.hashCode()) aus einer Zufallsmenge. Die sollte ich vielleicht noch gegen eine fixe Iteration ersetzen, [ getRandom() dachte ich eher, um den Optimierer ums wegoptimieren zu bringen, ] um die Genauigkeit zu bringen. Aber so tut sich noch kaum was vernuenftig messbares mit dem Code auf, um zu gucken, was besser geht.

/*
comparing 2 strings: 656ms (998422 matches)
comparing 1 string.hashCode with hashCode: 654ms (1001909 matches)
comparing 2 strings: 3238ms (4998511 matches)
comparing 1 string.hashCode with hashCode: 3256ms (5000483 matches)
comparing 2 strings: 1290ms (1998550 matches)
comparing 1 string.hashCode with hashCode: 1297ms (1998509 matches)
comparing 2 strings: 645ms (999825 matches)
comparing 1 string.hashCode with hashCode: 649ms (999814 matches)
comparing 2 strings: 3225ms (5001595 matches)
comparing 1 string.hashCode with hashCode: 3245ms (4998071 matches)
comparing 2 strings: 1292ms (2000608 matches)
comparing 1 string.hashCode with hashCode: 1299ms (2000036 matches)
comparing 2 strings: 646ms (1000974 matches)
comparing 1 string.hashCode with hashCode: 650ms (999220 matches)
comparing 2 strings: 3224ms (5001622 matches)
comparing 1 string.hashCode with hashCode: 3244ms (4999530 matches)
comparing 2 strings: 1290ms (1999165 matches)
comparing 1 string.hashCode with hashCode: 1298ms (2002031 matches)
*/
import static de.linux_swt.jsparse.JavaScriptSets.*;
import java.lang.Math;

public class hash {
	public final static int num = 10;
	public final static String[] tests = {
	    "function",
	    "class",
	    "module",
	    "var",
	    "let", 
	    "const",
	    "{",
	    "+",
	    "-",
	    "switch"
	};
	public static int funcs0, funcs1;
	public static String getRandom() {
	    return tests[(int)(Math.random()*10 % num)];
	}
	public static long startTime[], stopTime[];
	public static void measure(double times) {
	    startTime = new long[2];
	    stopTime = new long[2];
	    funcs0 = 0;
	    funcs1 = 0;
	    startTime[0] = System.currentTimeMillis();
	    for (int i = 0; i < times; i++) {
		String token = getRandom();
		if (token.equals(S_FUNCTION)) {
		    ++funcs0;
		}
	    }
	    stopTime[0] = System.currentTimeMillis();
	    startTime[1] = System.currentTimeMillis();
	    for (int i = 0; i < times; i++) {
		String token = getRandom();
		if (token.hashCode() == H_FUNCTION) {
		    ++funcs1;
		}
	    }
	    stopTime[1] = System.currentTimeMillis();
	    System.out.format("comparing 2 strings: %dms (%d matches)\n",  stopTime[0]-startTime[0], funcs0);
	    System.out.format("comparing 1 string.hashCode with hashCode: %dms (%d matches)\n", stopTime[1]-startTime[1], funcs1);
	}
	
	public static void main(String args[]) {
	    measure(10e6);
	    measure(5e7);
	    measure(20e6);
	    measure(10e6);
	    measure(5e7);
	    measure(20e6);	    
	    measure(10e6);
	    measure(5e7);
	    measure(20e6);
	}
}

Mal schauen, wo das noch hinführt.

Digitaltechnik

Endlich komm ich dazu, mir nebenbei die "Digitaltechnik und Entwurfsverfahren" durchzunehmen beizubringen. Das ist ein ganzes Semester auf Deutsch vom KIT. Schaltungen habe ich bislang nur aus Büchern auf Deutsch und auf Englisch als Semester gemacht, darum werde ich noch was raus lernen. Was mir besonders auffällt: Beim Zuhören

Zirkulaerer Lookahead Puffer

Die Technik ist vom Terrence Parr. Was mir auffaellt, ich hatte erstmal meine Bedenken, jedesmal einen lookAtChar(0) call durchzufuehren, oder mir das Token zu Beginn der Funktion zu speichern, oder ueberall, wo es mehrmals vorkommt. Ich weiss nicht, was der Compiler raus macht, aber es muss jedesmal ein Funktionsaufruf + MOD Division extra durchgefuehrt werden, bei jedem Zugriff, wenn man einen Ringpuffer verwendet. Es ist ein Minioverhead. Aber bei zehntausenden oder hunderttausenden Zeichen und Token wird das bereits messbar auffallen. An meinem PIII kann man dann bei der Ausgabe zugucken und so Sachen machen. Naja, kleine unwichtige Bedenken eines Anfängers.

Fakt ist, ein einfaches concatten des lookaheads endet nun in einem concatten von mehreren lookAtChar(pos) calls. Da die Indizes 2,3,0,1 heissen koennen, geht String.valueOf(lookahead) zum Beispiel nicht mehr..

    // ...

    final int k = 4;
    public int[] lookahead;
    char current;
    int p;

    public JavaScriptLexer(InputStream in) throws IOException {
        buffer = new BufferedReader(new InputStreamReader(in));
        lookahead = new int[k];
        p = 0;
        for (int i = 0; i < k; i++) consumeCharacter();
    }

    public char lookAtChar(int i) {		
        return (char)lookahead[ (p + i) % k ];
    }

    public int lookAt(int i) {		
        return lookahead[ (p + i) % k ];
    }

    public void consumeCharacter () throws IOException {	
        lookahead[p] = buffer.read();
        p = (p+1) % k;
    }

    public int nextCharacter() throws IOException {	
       consumeCharacter();
       current = lookAtChar(0);
       return lookAt(0);       
    }
    
    // ...

Das ent- und unterscheiden zwischen int und char ist dann mittendrin nicht mehr 100% gelungen, ich war erst unsicher, und kurz vorm umbauen, doch nicht lookAtChar(x) durchgehend nutzen zu wollen, wodurch (int)(current = (char)lookAtChar(0)) und ein Zeiger auf den lookAtChar(0) entstanden. Aber wenn der lookAtChar(0) call mir schon zu viel sind, dann sind die zwei Castings das auch. Der Code smellt schon hier ;-)

Ein paar Klassen und Verzeichnisse weiter ...

Hier kommt bald das Interpreter Pattern wiederholt vor. Diesmal sorge ich aber zuerst für den Compiler, und dann für den Interpreter. Dennoch hatte ich wieder mal Lust auf Interfacedefinition für eine Minute.

public class JavaScriptInterpreter {
    JavaScriptParser parser;
    public JavaScriptInterpreter(JavaScriptParser p) {
        this.parser = p;
    }
    public Completion interprete(StatementList list) {
        Completion V, R;
        for (Statement s : list) {
            if ((V = interprete(s)).isAbrupt()) return V; // ReturnIfAbrupt muss sein
            R = V;
        }
        return R;
    }
    public Completion interprete(FunctionDeclaration d) {}
    public Completion interprete(VariableDeclaration d) {}
    public Completion interprete(AssignmentExpression s) {}
    public Completion interprete(BinaryExpression b) {
	Completion left = interprete(b.left);
	Completion right = interprete(b.right);
	if (left.isAbrupt()) return left;    // koennen beide von
	if (right.isAbrupt()) return right;  // von performBinOp ausgefuehrt werden, bei und isAbrupt von der return werden
					    // und ich mir die zwei zeilen sparen und in performBinary(Completion l, byte op, Completion r) annehmen.
	return performBinaryOperation(left.getValue(), b.operator, right.getValue());
    }
    public Completion interprete(Statement s) {}
    public Completion interprete(Script s) {
        Completion R = interprete(s.scriptBody);
        return R;
    }
}

Ewige Wiederholung

Zum x-ten male schreibe ich sie ab :-).


import java.lang.Character;
import java.util.HashSet;

/**
 * Created by root on 08.03.14.
 */

public class JavaScriptSets {

    public static final HashSet< String> reservedWords = new HashSet< String>();
    public static final HashSet< String> futureReservedWords = new HashSet< String>();
    public static final HashSet< String> operators = new HashSet< String>();

    public static boolean isIdentifierStart (String w) { return Character.isUnicodeIdentifierStart(w.charAt(0)); }
    public static boolean isIdentifierPart (String w) { return Character.isUnicodeIdentifierPart(w.charAt(0)); }
    public static boolean isIdentifierStart (char c) { return Character.isUnicodeIdentifierStart(c); }
    public static boolean isIdentifierPart (char c) { return Character.isUnicodeIdentifierPart(c); }
    public static boolean isReservedWord (String w) { return reservedWords.contains(w); }
    public static boolean isFutureReservedWord (String w) { return futureReservedWords.contains(w); }
    public static boolean isOperator (String w) { return operators.contains(w); }
    public static boolean isOperator (char c) { return operators.contains(String.valueOf(c)); }

    public static final String
            S_BREAK = "break",
            S_DO = "do",
            S_IN = "in",
            S_TYPEOF = "typeof",
            S_CASE = "case",
            S_ELSE = "else",
            S_INSTANCEOF = "instanceof",
            S_VAR = "var",
            S_CATCH = "catch",
            S_EXPORT = "export",
            S_NEW = "new",
            S_VOID = "void",
            S_CLASS = "class",
            S_EXTENDS = "extends",
            S_RETURN = "return",
            S_WHILE = "while",
            S_CONST = "const",
            S_FINALLY = "finally",
            S_SUPER = "super",
            S_WITH = "with",
            S_CONTINUE = "continue",
            S_FOR = "for",
            S_SWITCH = "switch",
            S_YIELD = "yield",
            S_DEBUGGER = "debugger",
            S_FUNCTION = "function",
            S_THIS = "this",
            S_DEFAULT = "default",
            S_IF = "if",
            S_THROW = "throw",
            S_DELETE = "delete",
            S_IMPORT = "import",
            S_TRY = "try";

    public static final String
            S_ENUM = "enum";

    public static final String
            S_IMPLEMENTS = "implements",
            S_PACKAGE = "package",
            S_PROTECTED = "protected",
            S_INTERFACE = "interface",
            S_PRIVATE = "private",
            S_PUBLIC = "public";

    public static final String
            S_LPAREN = "(",
            S_RPAREN = ")",
            S_LBRACK = "[",
            S_RBRACK = "]",
            S_DOT = ".",
            S_SEMICOLON = ";",
            S_COMMA = ",",
            S_LESS = "<",
            S_GREATER = ">",
            S_PLUS = "+",
            S_MINUS = "-",
            S_MULTIPLY = "*",
            S_MOD = "%",
            S_BITWISE_AND = "&",
            S_BITWISE_OR = "|",
            S_XOR = "^",
            S_NOT = "!",
            S_INVERT = "~",
            S_QUESTIONMARK = "?",
            S_COLON = ":",
            S_ASSIGN = "=",
            S_LESS_EQUAL = "<=",
            S_GREATER_EQUAL = ">=",
            S_EQUAL = "==",
            S_NOT_EQUAL = "!=",
            S_INCREMENT = "++",
            S_DECREMENT = "--",
            S_LEFTSHIFT = "<<",
            S_RIGHTSHIFT = ">>",
            S_LOGICAL_AND = "&&",
            S_LOGICAL_OR = "||",
            S_PLUS_ASSIGN = "+=",
            S_MINUS_ASSIGN = "-=",
            S_MULTIPLY_ASSIGN = "*=",
            S_MOD_ASSIGN = "%=",
            S_BITWISE_AND_ASSIGN = "&=",
            S_BITWISE_OR_ASSIGN = "|=",
            S_XOR_ASSIGN = "^=",
            S_FAT_ARROW = "=>",
            S_TRIPLE_DOT = "...",
            S_STRICT_EQUAL = "===",
            S_STRICT_NOT_EQUAL = "!==",
            S_LEFTSHIFT_ASSIGN = "<<=",
            S_RIGHTSHIFT_ASSIGN = ">>=",
            S_SIGNED_RIGHTSHIFT = ">>>",
            S_SIGNED_RIGHTSHIFT_ASSIGN = ">>>=",
            S_DIV ="/",
            S_DIV_ASSIGN = "/=",
            S_RBRACE = "}"
            ;

    static {

        reservedWords.add(S_BREAK);
        reservedWords.add(S_DO);
        reservedWords.add(S_IN);
        reservedWords.add(S_TYPEOF);
        reservedWords.add(S_CASE);
        reservedWords.add(S_ELSE);
        reservedWords.add(S_INSTANCEOF);
        reservedWords.add(S_VAR);
        reservedWords.add(S_CATCH);
        reservedWords.add(S_EXPORT);
        reservedWords.add(S_NEW);
        reservedWords.add(S_VOID);
        reservedWords.add(S_CLASS);
        reservedWords.add(S_EXTENDS);
        reservedWords.add(S_RETURN);
        reservedWords.add(S_WHILE);
        reservedWords.add(S_CONST);
        reservedWords.add(S_FINALLY);
        reservedWords.add(S_SUPER);
        reservedWords.add(S_WITH);
        reservedWords.add(S_CONTINUE);
        reservedWords.add(S_FOR);
        reservedWords.add(S_SWITCH);
        reservedWords.add(S_YIELD);
        reservedWords.add(S_DEBUGGER);
        reservedWords.add(S_FUNCTION);
        reservedWords.add(S_THIS);
        reservedWords.add(S_DEFAULT);
        reservedWords.add(S_IF);
        reservedWords.add(S_THROW);
        reservedWords.add(S_DELETE);
        reservedWords.add(S_IMPORT);
        reservedWords.add(S_TRY);

        futureReservedWords.add(S_ENUM);
        futureReservedWords.add(S_IMPLEMENTS);
        futureReservedWords.add(S_PACKAGE);
        futureReservedWords.add(S_PROTECTED);
        futureReservedWords.add(S_INTERFACE);
        futureReservedWords.add(S_PRIVATE);
        futureReservedWords.add(S_PUBLIC);

        operators.add(S_LPAREN);
        operators.add(S_RPAREN);
        operators.add(S_LBRACK);
        operators.add(S_RBRACK);
        operators.add(S_DOT);
        operators.add(S_SEMICOLON);
        operators.add(S_COMMA);
        operators.add(S_GREATER);
        operators.add(S_LESS);
        operators.add(S_PLUS);
        operators.add(S_MINUS);
        operators.add(S_MULTIPLY);
        operators.add(S_MOD);
        operators.add(S_BITWISE_AND);
        operators.add(S_BITWISE_OR);
        operators.add(S_COLON);
        operators.add(S_ASSIGN);
        operators.add(S_LESS_EQUAL);
        operators.add(S_GREATER_EQUAL);
        operators.add(S_NOT_EQUAL);
        operators.add(S_INCREMENT);
        operators.add(S_DECREMENT);
        operators.add(S_LEFTSHIFT);
        operators.add(S_RIGHTSHIFT);
        operators.add(S_LOGICAL_AND);
        operators.add(S_LOGICAL_OR);
        operators.add(S_PLUS_ASSIGN);
        operators.add(S_MINUS_ASSIGN);
        operators.add(S_MULTIPLY_ASSIGN);
        operators.add(S_MOD_ASSIGN);
        operators.add(S_BITWISE_AND_ASSIGN);
        operators.add(S_BITWISE_OR_ASSIGN);
        operators.add(S_XOR_ASSIGN);
        operators.add(S_FAT_ARROW);
        operators.add(S_TRIPLE_DOT);
        operators.add(S_STRICT_EQUAL);
        operators.add(S_STRICT_NOT_EQUAL);
        operators.add(S_LEFTSHIFT_ASSIGN);
        operators.add(S_RIGHTSHIFT_ASSIGN);
        operators.add(S_SIGNED_RIGHTSHIFT);
        operators.add(S_SIGNED_RIGHTSHIFT_ASSIGN);
        operators.add(S_DIV);
        operators.add(S_DIV_ASSIGN);
        operators.add(S_RBRACE);

    }
}

Reservierte Worte oder nicht?

ES6 Grammatik, letzte Wochen

Die Behandlung von "let" hat sich in den letzten Entwürfen verändert.

Eine IdentifierReference wird erstmal als NonResolvedIdentifier eingelesen.

Wenn der NonResolvedIdentifier in einem strict code block gelesen wurde, und einem der folgenden Worte "implements, interface, let, package, private, protected, public, static" entspricht, ist ein SyntaxError zu werfen.

yield ist ein SyntaxError, wenn [~Yield] yield, d.h. der [Yield] Parameter nicht referenziert wird, und im strict code auftaucht.

Entsprechend den Angaben habe ich meinen Code ebenfalls anzupassen, genau bei den Worten den SyntaxError deswegen zu werfen, weil die im strict mode contained sind und nicht als Identifier verwendet werden koennen.

12.1.2 Identifier Reference

IdentifierReference [Yield] :
NonResolvedIdentifier [?Yield]

NonResolvedIdentifier [Yield] :
Identifier
[~Yield] yield

12.1.2.1 Static Semantics: Early Errors
IdentifierReference : NonResolvedIdentifier
ï‚·
It is a Syntax Error if StringValue of NonResolvedIdentifier does not statically resolve to a declarative
environment record binding.

NonResolvedIdentifier : Identifier
ï‚·
It is a Syntax Error if this NonResolvedIdentifier is contained in strict code and if the StringValue of
Identifier is: "implements", "interface", "let", "package", "private", "protected",
"public", "static", or "yield".
It is a Syntax Error if this NonResolvedIdentifier has a [Yield] and the StringValue of Identifier is "yield".

NonResolvedIdentifier : yield
ï‚·
It is a Syntax Error if the IdentifierReference is contained in strict code.
NOTE
Unlike a BindingIdentifier (13.2.1.1), an IdentifierReference in strict code may be the Identifier eval or arguments.

ByteCode

Als drittes noch ein C++ Quasiäquivalent. Das ist die Sprache, die ich dann gleich als naechstes in Angriff nehmen muss, noch einmal ganz zu beherrschen. Hier ist schonmal das Ding, was ich noch in ASM Instruktionen an der Stelle von "+" und "*" umwandeln muss, um zu zeigen, wie das dann geht. Was noch keiner Optimierung entspricht. Es gibt da feste Techniken, die man in Internettutorials lernen kann.

/*
2 * 3 = 6
1 + 6 = 7
final result = 7

RUN FINISHED; exit value 0; real time: 0ms; user: 0ms; system: 0ms
(schnell mit netbeans 7.4 debuggt, Makefile hinzugefuegt, importiert, geht zu Builden und zu runnen)
*/


#include < list>
#include < iostream>
using namespace std;


static const short OPERAND = -127;
static const short OPERATOR = -126;
static const short code[]  = { OPERAND, 0,0,0,1, 
				OPERAND, 0,0,0,2, 
				OPERAND, 0,0,0,3, 
				OPERATOR, (short)'*',
				OPERATOR, (short)'+' };

class InterpreterDemo {
public:
    static const int len = 19;
    
    list< double> *operands;    
    int pc;		

    InterpreterDemo () {
	operands = new list< double>();
    }

    double interprete () {
	    short opcode;
	    double result;
            double operand;	               
	    pc = -1;
	    while (pc <  len) {
		opcode = (short)code[++pc];
		switch (opcode) {
		    case OPERAND:
			operand = 0.0;
			// kleiner irrtum:
			// doubles sind 8 bytes
			// das sind doch nur vier und 32 bit 
			// aber das faellt schon (jetzt) auf
			operand += code[++pc] <<  24;
			operand += code[++pc] <<  16;
			operand += code[++pc] <<  8;
			operand += code[++pc] <<  0;
			operands->push_back(operand);
		    break;		
		    case OPERATOR:
			short _operator = code[++pc];
			double op2 = (double)operands->back();
                        operands->pop_back();
			double op1 = (double)operands->back();
                        operands->pop_back();
                        
	    		switch (_operator) {
			    case '+': 
				operands->push_back(result = op1+op2);                                
				cout <<  op1 <<  " " <<  (char)_operator <<  " " <<  op2 <<  " = " <<  result <<  endl;
				break;
			    case '*':
				operands->push_back(result = op1*op2);
				cout <<  op1 <<  " " <<  (char)_operator <<  " " <<  op2 <<  " = " <<  result <<  endl;
				break;
			}	
		    break;
		}
	    }
            result = operands->back();
            operands->pop_back();
            return result;
	}
}; 

int main(int argc, char **argv) {
	InterpreterDemo *ip = new InterpreterDemo();
	double result = ip->interprete();
	cout <<  "final result = " <<  result <<  endl;
	return 0;
}
/*
2 * 3 = 6
1 + 6 = 7
final result = 7
*/

Das hier ist Postfix ByteCode. Soweit ich weiss, wurde im ersten JavaScript ByteCode bereits Postfix generiert. Postfix ist eine der Varianten Infix, Prefix, Postfix, wo die Operatoren hinter den Operanden stehen. Bei Infix stehen sie zwischen den Zahlen, was uns von Mathe gelaeufig ist. Hier wird dann mit einer speziellen Vorrangregelelung geparsed. Man liest, bis man einen hoeheren Operator trifft, und bevorzugt dann den, indem man den anderen erstmal auf den Stack packt. In dem Bytecode liest sich das jetzt wieder so, dass * vor + kommt, im Original steht da 1+2*3. Auf dem Stack (in der Liste) wäre + unten (1) und * oben (2). Hinzu zu dem Beispiel kommt noch der Operandenstack, der ebenfalls gelaeufig ist. Den Postfix Bytecode allerdings kann man so ausführen, wie er ist, er hat wie man liest die gleiche Reihenfolge.

/*
2.0 * 3.0 = 6.0
1.0 + 6.0 = 7.0
final result = 7.0
*/
import java.util.LinkedList;

class bytecode2 {
    public static class InterpreterDemo {
	public final byte OPERAND = -127;
	public final byte OPERATOR = -126;
	public byte[] code  = { OPERAND, 0,0,0,1, 
				OPERAND, 0,0,0,2, 
				OPERAND, 0,0,0,3, 
				OPERATOR, '*',
				OPERATOR, '+'
				};
				
	public final LinkedList< Double> operands;
	public int pc;	// der program counter
	public InterpreterDemo () {
	    operands = new LinkedList< Double>();
	}
	public double interprete () {
	    byte opcode;
	    double result;
	    int len = code.length -1;
	    pc = -1; 
	    while (pc < len) {
		opcode = code[++pc];
		switch (opcode) {
		    case OPERAND:
			double operand = 0.0;
			// doubles haben aber nicht nur 4 bytes
			// das oben ist integer breiter bytecode :-)
			// allerdings koennte man angeben, welcher typ
			// und damit entsprechend das hier..:
			operand += code[++pc] << 24;
			operand += code[++pc] << 16;
			operand += code[++pc] << 8;
			operand += code[++pc] << 0;
			// klar, wie man typen unterschiedlicher groesse baut?
			operands.addLast(operand);
		    break;		
		    case OPERATOR:
			byte operator = code[++pc];
			double op2 = (double)operands.removeLast();
			double op1 = (double)operands.removeLast();
	    		switch (operator) {
			    case '+': 
				operands.addLast(result = op1+op2);
				System.out.format("%s %s %s = %s\n", op1, (char)operator, op2, result);
				break;
			    case '*':
				operands.addLast(result = op1*op2);
				System.out.format("%s %s %s = %s\n", op1, (char)operator, op2, result);
				break;
			}	
		    break;
		}
	    }
	    return operands.removeLast();
	}
    }    
    public static void main(String args[]) {
	InterpreterDemo ip = new InterpreterDemo();
	double result = ip.interprete();
	System.out.format("final result = %s\n", result);
    }
}

Nebenbei werde ich mal eine der schnellsten Stackmaschinen der Welt schreiben.

Das hier ist kein Postfix ByteCode, sondern mein erster Versuch und eine einfache Anordnung von Bytes, die ich so ausführen konnte, wie ich da gerade getan hatte.

/*

1.0 + 2.0 = 3.0
3.0 * 3.0 = 9.0
final result = 9.0


*/
import java.util.LinkedList;

class bytecode {
    public static class InterpreterDemo {
	
	public byte[] code  = { 0,0,0,1, 0,0,0,2, (byte)'+', 0,0,0,3, (byte)'*' };
	public final LinkedList< Double> operands;
	public int pc;
	public InterpreterDemo () {
	    operands = new LinkedList< Double>();
	}
	
	public static boolean isOperator(byte b) {
	    return b == '+' || b == '*';
	}
	public double interprete () {
	    byte op;
	    double result;
	    pc = -1;
	    while (pc < code.length-1) {
		op = code[++pc];
		if (isOperator(op)) {
		    double op2 = (double)operands.removeLast();
		    double op1 = (double)operands.removeLast();
		    switch (op) {
			case '+': 
			    result =  op1+op2; 
			    System.out.format("%s + %s = %s\n", op1,op2, result);
			    operands.addLast(result);
			    break;
			case '*':
			    result =  op1*op2; 
			    System.out.format("%s * %s = %s\n", op1,op2, result);
			    operands.addLast((double)result);
			    break;
		    }
		} else {
		    double operand = 0.0;
		    operand += op << 24;
		    operand += code[++pc] << 16;
		    operand += code[++pc] << 8;
		    operand += code[++pc] << 0;		    
		    operands.addLast(operand);
		}
		
	    }
	    return operands.removeLast();
	}
    }    
    public static void main(String args[]) {
	InterpreterDemo ip = new InterpreterDemo();
	double result = ip.interprete();
	System.out.format("final result = %s\n", result);
    }
}

JUnit Testen, aber wie? Ein Buch..

Noch bin ich dabei, mir junit beizubringen. Ich glaube, jetzt habe ich die finale Quelle entdeckt, die mir das ermoeglichen wird, ebenfalls junit zu verwenden.

http://frankwestphal.de/MeinBuchzumDownload.html bietet das Buch "Testgetriebene Entwicklung mit JUnit und FIT". Wie er schon bemerkt, auch sieben Jahre spaeter ist er mit dem Text ueberraschend einverstanden.

Das Notebook ist handlich zum scrollen von Dokumenten. Am PIII/933 bin ich fast kaputt gegangen beim umblaettern. Bis sich die Seite aufgebaut hat, ist was Zeit vergangen, die war erst weiss, dann kommt zwei Sekunden spaeter die Grafik. Ein Tablet waer handlicher, aber ich komm besser klar als vorher.

JavaScripture .com [/ConstructorName]

Eine sehr interessante JavaScript Referenz habe ich hier entdeckt. http://www.javascripture.com.

Besonders angetan war ich ich vom http://www.javascripture.com/EventListener Beispiel. Als ich drauf klickte, fragte ich mich, "mit handleEvent", und dachte, wohl informiert zu sein. Als es erschien, erschien dort handleEvent. Aber anders, als ich vermutete. Ich vermutete einen Hinweis auf das "abstrakte Interface" EventListener und dessen Operation handleEvent (interface EventListener { void handleEvent(Event e); } in Java). Stattdessen bekam ich eine Demo, die ich nie erwartet haette. Es wird ein Objekt listener (eine EventListener Instanz) erzeugt und an addEventListener uebergeben. Das habe ich noch nie gesehen, noch nie probiert. Die Demo auf der Seite zeigt, das funktioniert. Das Objekt hat eine .name Property und das this Argument loest zu dem Objekt listener auf. Bislang war this === myButton und nicht this === listener, auch das ist neu für mich. myButton.addEventListener("click", { name: "foo", handleEvent: function (e) { /*...*/ } }, false); ruft die handleEvent Operation des EventListener Objekts auf und wird als thisValue eingesetzt. Wer wusste das noch? Ich jedenfalls nicht. Die nehmen doch alle nur functions als Parameter. Ein paar Sachen muessen da wohl noch dokumentiert werden, ist ja auch Arbeit, wofür man nicht immer die Zeit hat, selbst wenn man gerne würde, aber ein paar interessante Beispiele sind da noch zu finden.

Netbeans IDE

Ich habe mich das 200 AldiTalk-MB kosten lassen und die gesamte NetBeans 7.4 IDE gezogen. Fakt ist - das geilste Presetting von allen IDEs. Hat Java, C++ und HTML5 Templates für alle Belange und kann neue und existierende Projekte öffnen. Und gleich verschiedene Gleichzeitig, was sie zum Favoriten für meine ES6/7 Engine macht, weil ich gleich die Java und C++ Version neben dem JavaScript Tool entwickeln kann, ohne die IDE zu verlassen. Das macht Gleichnamigkeit möglich.

Serializable

Objekte abspeichern wollte ich auch mal versuchen. Wie ich sehe sind die Streams ganz einfach zu benutzen. Nebenbei implementieren die das AutoClosable Interface im JDK8, was diesen seltsamen try Block ermoeglicht. Die Resource in den runden Klammern wird hinterher automatisch .close().

import java.io.*;

class objwrite {

	public static class Person implements Serializable {
	    public String first;
	    public String last;
	    public Person(String f, String l) {
		first = f; last = l;
	    }
	    public String toString () {
		return "Name: "+first+" "+last;
	    }
	
	}
	public static void main(String args[]) {
	    
	    Person p1 = new Person("Edward", "Gerhold");
	    Person p2 = new Person("This", "That");
	    Person p3 = new Person("Abra", "Kadabra");
	    
	    try (FileOutputStream fos = new FileOutputStream("objwrite.s")) {
		ObjectOutput oo = new ObjectOutputStream(fos);
		oo.writeObject(p1);
		oo.writeObject(p2);
		oo.writeObject(p3);
		System.out.format("Data for write:\n%s\n%s\n%s\n", p1, p2, p3);
	    } catch (Exception ex) {
		ex.printStackTrace();
		System.exit(0);
	    }

	    Person q1,q2,q3;
	    try (FileInputStream fis = new FileInputStream("objwrite.s")) {
		ObjectInput oi = new ObjectInputStream(fis);
		q3 = (Person)oi.readObject(); // wie man an der ausgabe sieht vertu ich mich hier in der reihenfolge
		q2 = (Person)oi.readObject();
		q1 = (Person)oi.readObject();
		System.out.format("Data after read:\n%s\n%s\n%s\n", q1, q2, q3);
	    } catch (Exception ex) {
		ex.printStackTrace();
		System.exit(0);
	    }
	        

	}
}
/*
Data for write:
Name: Edward Gerhold
Name: This That
Name: Abra Kadabra
Data after read:
Name: Abra Kadabra
Name: This That
Name: Edward Gerhold
*/
// objwrite.s
¬ísrobjwrite$PersonÈŒѨ.›VLfirsttLjava/lang/String;Llastq~xptEdwardtGerholdsq~tThistThatsq~tAbratKadabra

lambda nich da?

Da wollte ich gerade die mir von JavaScript so vertrauten funktionalen Iterationen durchprobieren, da fehlen den Listen die Funktionen. Nur forEach ist da. Komisch. Im Video klappt das ;-)

import java.util.List;
import java.util.LinkedList;

class lambda {
	public static class Person {
	    public String name;
	    public boolean istErleuchtet() { return false; }
	}
	public static class EPerson extends Person {
	    public EPerson (Person p) { this.name = p.name; }
	    public boolean istErleuchtet() { return true; }
	}
	public static void main(String args[]) {
	    LinkedList< Person> list = new LinkedList< Person>();
	    
	    for (int i = 0; i < 10; i++) {
		Person person = new Person();
		person.name = ""+ (char)((int)'A'+i);
		list.addLast(person);
	    }

	    list.forEach((p) -> System.out.format("name: %s, erleuchtet: %s\n", p.name, p.istErleuchtet()));
	    
	    /*
	    list
	    .map((p) -> new EPerson(p))
	    .into(new LinkedList())
	    .forEach((ep) -> System.out.format("name: %s, erleuchtet: %s", p.istErleuchtet()));
	    */
	    
	    
	}
}
/*
name: A, erleuchtet: false
name: B, erleuchtet: false
name: C, erleuchtet: false
name: D, erleuchtet: false
name: E, erleuchtet: false
name: F, erleuchtet: false
name: G, erleuchtet: false
name: H, erleuchtet: false
name: I, erleuchtet: false
name: J, erleuchtet: false
*/

Technische Java Details (Oracle Learning Library)

Auf YouTube habe ich mal nach Java geguckt. Besonders passend finde ich die Oracle Learning Library. Hier gibt es zum Beispiel Videos über das Memory Management von Java. "From Java Code to Java Heap: Understanding the Memory Usage" heisst das brilliante Video, was verrät, was die Objekte so an Speicher kosten.

Ich habe gestern nicht mehr alles gehoert, ich hatte Zahnschmerzen gekriegt. Aber ich weiss so ungefaehr, wie viel Speicher was braucht, dass 64 bit doppelt so viel Speicher frisst wie 32 Bit, es aber nur 4GB max. bei 32GB gibt, wieviele Pointer die Objekte speichern und wieviele Bytes sie allozieren. Uebrigens die "ArrayList< T>" faengt mit einem new T[10] an, und managed dann ihre Vergroesserung und Verkleinerung selbst. In etwa wie ich vermutet habe, ist die ArrayList erstmal mit zehn Elementen am Start. Wenn man grundsaetzlich nur eine handvoll Elemente hat, ist sie bereits zu gross. HashMaps und -Sets werden mit 16 Elementen per Default eingerichtet, man hat Speicher fuer 16 Elemente alloziert. Das und vieles mehr, das wollte ich darueber wissen. Das wird dort besprochen.

Noch nicht gekommen bin ich zu "JVM ByteCode for Dummies" und was es da noch gibt.

"Do You really get Class Loaders?" heisst ein Video, was ich mir heute angucken werde. Danach werde ich wissen, wie der Class Loader funktioniert, und wie ich Gebrauch von mache.

Hier gibt es noch mehr von und die werde ich alle probieren.

WebStorm IDE

Nachdem Java Spass mit IDEA, habe ich mir mal die 30 Tage Trial Version von WebStorm runtergeladen. Weil die IDEA den Code nach Fehlern abscannt, kann ich syntax.js so ein wenig nach Auge untersuchen und Duplikate finden und anderen Kram. Will jetzt den Compiler und Heap machen. Das sind zwei separate Projekte, die hinterher, naja, die Runtime unterstützen. Jetzt verwandel ich erstmal den AST in einen Array. Danach mache ich dann den Typed Array raus. Aber zur Vereinfachung, bevor die Objekte serialisiert sind, erstmal einen Array.

RegExp

Kurz aus dem Tutorial ausprobiert. Nebenbei habe ich dann endlich mal .format ausprobiert, die es dann wohl auch bei System.out gibt, jedenfalls hat der Compiler nichts gesagt. In C++ hatte ich mehr Fehler ;-) (hier mit Java praktisch gar nichts, ausser mangelnder Langzeitpraxis und dazugehörige Entwurfsstärke).

/*
scanning aacvbfgjgyvsdflksjdlassdddddjslkljfsfdd for (d)+
Got d at 12 until 13
Got d at 18 until 19
Got ddddd at 23 until 28
Got dd at 37 until 39
found 4 matches
*/

import java.util.regex.Pattern;
import java.util.regex.Matcher;
class regex {
	public static void main(String args[]) {
	    String expr = "(d)+";
	    String sourceText = "aacvbfgjgyvsdflksjdlassdddddjslkljfsfdd";
	    Pattern pattern = Pattern.compile(expr);
	    Matcher matcher = pattern.matcher(sourceText);
	    int count = 0;
	    System.out.format("scanning %s for %s \n", sourceText, expr);
	    while (matcher.find()) {
		++count;
		System.out.format("Got %s at %d until %d\n",
		    matcher.group(),
    		    matcher.start(),
		    matcher.end()
		);
	    }
	    System.out.format("found %d matches\n", count);
	}
}

Schnell begriffen - Programming to a supertype

Im Head first Design Patterns Buch wird die Phrase "Programming to a supertype" verwendet.

Dadurch wird die besondere Dynamik in einem sonst strengen Typensystem geschaffen.

Was jetzt Programming to a supertype meint? In der Regel wird fuer alles die Oberklasse angegeben, weil sie ueber wohldefinierte Schnittstellen jeglicher Art verfuegt, ob Methoden, oder Attribute, bei nur Methoden koennen es auch Interfaces sein, statt Klassen, aber der spezielle Typ wird dann nur noch benutzt, wenn er gebraucht wird.

Das ist ein sehr wichtiges Konzept.

java.util.Properties

Das Properties Objekt kann Konfigurationsdaten speichern. Das Gute ist. Gleich auch in XML, welches in xml-beliebige Editoren geladen werden kann, oder halt problemlos elektronisch verwertet. Per default schreibt Properties aber auch Schluessel-Werte Paare auf Disk. Kann man gut gebrauchen.

/*

properties.txt:

#This was written with my static comment
#Sun Mar 02 16:58:17 CET 2014
additional=7
first=Edward
last=Gerhold

bash:

linux-dww5:~ # java props
first = Edward
last = Gerhold
additional = 4
linux-dww5:~ # java props
first = Edward
last = Gerhold
additional = 5
linux-dww5:~ # java props
first = Edward
last = Gerhold
additional = 6
linux-dww5:~ # java props
first = Edward
last = Gerhold
additional = 7
*/
import java.util.Properties;
import java.io.*;

class props {
    public static void main(String args[]) {
    	    InputStream in;
    	    OutputStream out;
    	    
    	    String first, last;
    	    int additional;
    	    
    	    Properties props = new Properties();
    	    try {
    		in = new FileInputStream("properties.txt");
    		props.load(in);
    	    } catch (IOException ex) {
    		System.out.println("can not load properties.txt - creating");
    	    }
    	    
    	    String additional_str = props.getProperty("additional");
    	    if (additional_str == null) additional = 1;
    	    else additional = (new Integer(additional_str)).intValue() + 1;
    	    
    	    first = props.getProperty("first");
    	    last = props.getProperty("last");
    	    if (first == null) first = "Edward";
    	    if (last == null) last = "Gerhold";
	    props.setProperty("additional", String.valueOf(additional));
	    props.setProperty("first", first);
	    props.setProperty("last", last);
	    System.out.println("first = "+first);
	    System.out.println("last = "+last);
	    System.out.println("additional = "+additional);
	    
	    
    	    try {
    		out = new FileOutputStream("properties.txt");
    		props.save(out, "This was written with my static comment");
    	    } catch (IOException ex) {
    		System.out.println("can not save properties.txt");
    	    }
    }
}

Reflect

Sollte man auch lernen. Laufzeitinformationen ueber die aktuelle Klasse sind das wie das A und O ein praktisches E, I, oder U wert. Hier habe ich das Java Tutorial benutzt um schneller hinter die Methoden zu kommen. Die kann man sich allerdings sehr leicht merken.

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.List;
import java.util.ArrayList;

class printobj {

    static abstract class Person {
    }

    public static interface Speaking {
	public void sprich();
    }

    static class ExaminablePerson extends Person implements Speaking {
	protected String first;
	protected String last;  
	public String image;
	public ExaminablePerson(String first, String last) {
	    this.first = first;
	    this.last = last;
	}
	public void sprich() {
	    System.out.println("Ich habe was zu sagen!");
	}
	public String toString () {
	    return "Person: "+first+" "+last;
	}
    }
    
    static public class ExtendedPerson extends ExaminablePerson {
	public String besonderes;
	public ExtendedPerson(String first, String last) {
	    super(first, last);
	}
	@Override
	public void sprich() {
	    System.out.println("Ich habe ebenfalls was zu sagen!");
	}
	public String toString () {
	    return "Jemand: "+first+" "+last;
	}
    }
    
    
    
    public static void examine(Object obj) {
	Class c = obj.getClass();
	System.out.println("*** Examining Object: ***");


	System.out.println("1. Klasse: ");
	System.out.println(c.getCanonicalName());
	
	System.out.println("2. Modifizierer:");
	System.out.println("\t"+Modifier.toString(c.getModifiers()));
    
    	System.out.println("3. Vererbungspfad:");
	List< Class> l = new ArrayList< >();
	for (Class cl: l) {
    	    System.out.print("\t"+cl.getCanonicalName());
	}
	if (l.size() > 0) System.out.println();
	
	System.out.println("3. Type Parameters:");
	Type[] tp = c.getTypeParameters();
	if (tp.length > 0) System.out.print("\t");
	for (Type p: tp) {
	    System.out.print(p.toString()+", ");
	}
	
	System.out.println("4. Interfaces:");
	Type[] ifs = c.getGenericInterfaces();
	for (Type i: ifs) {
	    System.out.println("\tinterface "+i.toString());
	}

	
	System.out.println("5. Annotationen:");
	Annotation[] annos = c.getAnnotations();
	for (Annotation a: annos) {
	    System.out.println("  " + a.toString());
	}
	
	System.out.println("6. Deklarierte Felder: ");
	Field[] declfs = c.getDeclaredFields();
	for (Field d: declfs) { 
	    System.out.println(d.toGenericString());
	}
	
	System.out.println("6. Deklarierte Methoden: ");
	Method[] ms = c.getDeclaredMethods();
	for (Method m: ms) { 
	    System.out.println(m.toGenericString());
	    
	}
	
	System.out.println("7. Deklarierte Konstruktoren: ");
	
	Constructor[] cs = c.getDeclaredConstructors();
	for (Constructor ctor: cs) {
	    System.out.println(ctor.toGenericString());
	}
    }
    
    public static void main(String args[]) {
	ExaminablePerson p1 = new ExaminablePerson("John", "Doe");
	p1.image = "gut";
	ExaminablePerson p2 = new ExaminablePerson("Jane", "Doe");
	p2.image = "weniger gut";
	ExaminablePerson p3 = new ExtendedPerson("Irgend", "Wer");
	p3.image = "unbeschreiblich";
	((ExtendedPerson)p3).besonderes = "nur p3 hat das attribut";
	examine(p1);
	examine(p2);
	examine(p3);
    }
}
/*
*** Examining Object: ***
1. Klasse: 
printobj.ExaminablePerson
2. Modifizierer:
	static
3. Vererbungspfad:
3. Type Parameters:
4. Interfaces:
	interface interface printobj$Speaking
5. Annotationen:
6. Deklarierte Felder: 
protected java.lang.String printobj$ExaminablePerson.first
protected java.lang.String printobj$ExaminablePerson.last
public java.lang.String printobj$ExaminablePerson.image
6. Deklarierte Methoden: 
public void printobj$ExaminablePerson.sprich()
public java.lang.String printobj$ExaminablePerson.toString()
7. Deklarierte Konstruktoren: 
public printobj$ExaminablePerson(java.lang.String,java.lang.String)
*** Examining Object: ***
1. Klasse: 
printobj.ExaminablePerson
2. Modifizierer:
	static
3. Vererbungspfad:
3. Type Parameters:
4. Interfaces:
	interface interface printobj$Speaking
5. Annotationen:
6. Deklarierte Felder: 
protected java.lang.String printobj$ExaminablePerson.first
protected java.lang.String printobj$ExaminablePerson.last
public java.lang.String printobj$ExaminablePerson.image
6. Deklarierte Methoden: 
public void printobj$ExaminablePerson.sprich()
public java.lang.String printobj$ExaminablePerson.toString()
7. Deklarierte Konstruktoren: 
public printobj$ExaminablePerson(java.lang.String,java.lang.String)
*** Examining Object: ***
1. Klasse: 
printobj.ExtendedPerson
2. Modifizierer:
	public static
3. Vererbungspfad:
3. Type Parameters:
4. Interfaces:
5. Annotationen:
6. Deklarierte Felder: 
public java.lang.String printobj$ExtendedPerson.besonderes
6. Deklarierte Methoden: 
public void printobj$ExtendedPerson.sprich()
public java.lang.String printobj$ExtendedPerson.toString()
7. Deklarierte Konstruktoren: 
public printobj$ExtendedPerson(java.lang.String,java.lang.String)
*/

Zeitverschwendung

Manchmal kann ich es nicht lassen, damit anzufangen, weil ich es mal zuende bringen will.

http://dom.spec.whatwg.org

package de.linux_swt.dom;

/**
 * Created by root on 02.03.14.
 */

public class DOMError {
    
    protected String name;
    protected String message;
    public DOMError(String name, String message) {
        this.name = name;
        this.message = message;
    }
    public String getName() {
        return name;
    }
    public String getMessage() {
        return message;
    }
    
}

Tokenizer

Ich hab den StreamTokenizer fuer den LL(k) Generator weiter programmiert. Jetzt kann er schon TERMINAL ('while' 'for' '(') , NONTERMINAL (Expression, AssignmentExpression), PARAMETER ([Yield]), IFPARAMETER ([?Yield])

State

Inzwischen ist mir aber klar, dass das State Pattern noch richtiger ist. Wenn ich bedenkte, dass ein Automat auf verschiedene Knoepfe anders reagiert (delta), _je nachdem, ob was eingeworfen ist, oder nicht_ (state)

Panne ist, dass man in "meinem" Modell im naechsten Beispiel die Zuweisung state = State.XXX gleich durch func = xxx; ersetzen kann. Womit man den Hashtable Zugriff sparen kann. Womit man die func Variable neu zuweisen muss, um die Funktion zu aendern. Der state = State.XXX ist hier eigentlich zuviel des Guten.

Viel interessanter ist gerade dass ich beim ueben von Composites und Factories bin. Bei ersteren denke ich an den AST, der als Composite geradezu perfekt zu traversieren ist. Wozu der Visitor passt. Mit den Factorymethods stelle ich die in einer AbstactFactory zusammen, damit ich in einer anderen konkreten Fabrik kompiliert wieder ausgeben kann. Oder so aehnlich. Hier warten noch Stunden auf mich.

Automaten

Der von gestern abend, weiter unten. Nur korrigiert. Die delta Funktion musste wieder raus. Wie mir das gestern schon einfiel, habe ich unten stehen lassen. Ich bin dann schlafen gegangen. Sie hat auch schon geschlafen.

import java.lang.Thread;
import java.util.LinkedList;
import java.util.HashMap;

class automaton {

    protected enum State {
        START,
        NEXT,
        IDENTIFIER,
        NUMBER,
        OPERATOR,
        WHITESPACE,
        STOP,
        FINALIZE,
        INVALID,
        ERROR
    }

    @FunctionalInterface
    static protected interface TransitionFunction {
        public void call();
    }

    static public class FirstConcreteAutomaton implements Runnable {
        protected State state;
        protected HashMap< State, TransitionFunction> transitionHelpers;
        protected LinkedList< String> token;
        protected LinkedList< Character> invalidChars;
        protected String input;
        protected int length;
        protected Character c;
        protected int pos;
        protected StringBuilder current;
        protected boolean mustFinalize;
        protected String errorMsg;

        public FirstConcreteAutomaton(String input) throws Exception {
            if (input == null) {
                throw new Exception("invalid input");
            }
            this.input = input;
            token = new LinkedList< >();
            invalidChars = new LinkedList< >();
            transitionHelpers = new HashMap< State, TransitionFunction>();
            transitionHelpers.put(State.NEXT, evaluate);
            transitionHelpers.put(State.IDENTIFIER, word);
            transitionHelpers.put(State.OPERATOR, operator);
            transitionHelpers.put(State.NUMBER, number);
            transitionHelpers.put(State.INVALID, invalid);
            transitionHelpers.put(State.ERROR, error);
            transitionHelpers.put(State.STOP, stop);
            transitionHelpers.put(State.WHITESPACE, whitespace);
            transitionHelpers.put(State.FINALIZE, finalizer);
            transitionHelpers.put(State.START, start);
        }

        protected void next () {
            if (mustFinalize) return;
            if (pos < length) ++pos;
            if (pos == length) {
                c = null;
                mustFinalize = true;
                state = state.FINALIZE;
                return;
            }
            c = new Character(input.charAt(pos));
        }

        protected TransitionFunction start = () -> {
            mustFinalize = false;
            length = input.length();
            pos = -1;
            System.out.println("System gestartet");
    	    
    	    next();
            state = State.NEXT;
        };

        protected TransitionFunction whitespace = () -> {
            System.out.print(".");
            current = new StringBuilder(); // damit kein alter current da steht
            while (Character.isWhitespace(c.charValue())) {
                next();
                if (state.equals(State.FINALIZE)) break;
            }
            state = State.NEXT;
        };

        // temp
        static boolean isOperator(char c) {
            return c == '+' || c == '-' || c == '{' || c == '}' || c == '(' || c == ')' || c == ';' || c == '*' || c == '/' || c == '=';
        }

        protected TransitionFunction evaluate = () -> {
            if (state.equals(State.FINALIZE)) return;
            char ch = c.charValue();
            if (Character.isUnicodeIdentifierStart(ch)) state = State.IDENTIFIER;
            else if (Character.isDigit(ch)) state = State.NUMBER;
            else if (Character.isWhitespace(ch)) state = State.WHITESPACE;
            else if (isOperator(ch)) state = State.OPERATOR;
            else state = State.INVALID;
        };

        protected TransitionFunction number = () -> {
            current = new StringBuilder();
            current.append(c.charValue());
            next();
            while (Character.isDigit(c.charValue())) {
                current.append(c.charValue());
                next();
                if (state.equals(State.FINALIZE)) return;
            }
            token.addLast(current.toString());
            state = State.NEXT;
        };

        protected TransitionFunction word = () -> {
            current = new StringBuilder();
            current.append(c.charValue());
            next();
            while (Character.isUnicodeIdentifierPart(c.charValue())) {
                current.append(c.charValue());
                next();
                if (state.equals(State.FINALIZE)) return;
            }
            token.addLast(current.toString());
            state = State.NEXT;
        };

        protected TransitionFunction operator = () -> {
            current = new StringBuilder();
            while (isOperator(c.charValue())) {
                current.append(c.charValue()); 
                next();
                if (state.equals(State.FINALIZE)) return;
            }
            token.addLast(current.toString());
            state = State.NEXT;
        };

        protected TransitionFunction invalid = () -> {
            System.out.println("Invalid character: "+c.toString());
            invalidChars.addLast(c.charValue());
            next();
            state = State.NEXT;
        };

        protected TransitionFunction stop = () -> {

            System.out.println("System gestoppt");
            System.out.println("Token gelesen");
            for (String t: token) {
                System.out.println(t);
            }
            System.out.println("invalide Zeichen:");
            for (Character c : invalidChars) {
                System.out.print(c.toString());
                System.out.print(" ");
            }
        };

        protected TransitionFunction error = () -> {
            if (errorMsg != null) System.out.println("ERROR: "+errorMsg);
            else System.out.println("ERROR: Unknown");
        };

        protected TransitionFunction finalizer = () -> {
            if (mustFinalize == true) {
                token.addLast(current.toString());
                mustFinalize = false;
            }
            System.out.println("Finalizing step");
            state = State.STOP;
        };

        public void run () {
            state = State.START;
            TransitionFunction func;
            while (state.equals(State.STOP) == false) {
                func = transitionHelpers.get(state);
                if (func != null) func.call();
            }
            if (state.equals(State.STOP)) {
        	func = transitionHelpers.get(state);
        	if (func != null) func.call();
            }
        }
    }

    public static void main(String args[]) throws Exception {
        String sourceText = "var x = 10; x + y + z; 1+2+3; 10*10*10; function x() {}";
        FirstConcreteAutomaton myAutomaton = new FirstConcreteAutomaton(sourceText);
        Thread t = new Thread(myAutomaton);
        t.start();
    }
}
/*
System gestartet
.............Finalizing step
System gestoppt
Token gelesen
var
x
=
10
;
x
+
y
+
z
;
1
+
2
+
3
;
10
*
10
*
10
;
function
x
()
{}
invalide Zeichen:
*/

Perfekt, oder? Ich finde, die Idee mit den FunktionsInterfaces ist gelungen.

Die Ãœbergangsfunktionen in einer HashMap sind dynamisch assoziierbar und man kann dem key on the Fly auch eine andere Funktion zuweisen, womit der Automat programmierbar waere.

März

Wir haben für fünf Euro einen DVD Player auf dem Flohmarkt auf dem Marktplatz gekauft, was den Laptop diesen Monat vielleicht entlastet. Wir haben den Februar täglich Videos geguckt und ich habe den normalen Programmierplan, den ich mir vorgestellt hatte, gar nicht mehr einhalten können und durch die Unterbrechungen sogar mehr schleifen lassen als erwartet..

LL(k) Generator

Ich denke, den werde ich probieren. Weil ich die Grammatik gerne mal in ein Programm einlesen würde und dann generieren.

Es geht weiter mit GrammarParser mit Rule und RuleType, wobei man der Lefthandside ihre Nonterminals und Terminals zuweist.

Dann gilt es die wieder zu transformieren. In Lexer und Parser. In FIRST und FOLLOW.

Ich fang schonmal an.

import de.linux_swt.llkg.*;

public class Main {

    public static void main(String[] args) {

        GrammarTokenizer p = new GrammarTokenizer(args[0]);
	Thread t = new Thread(p);
	t.start();
    }
}


package de.linux_swt.llkg;
/*
**
* *
*
*
*
*
*
*
*
 */

import java.io.*;
import java.util.*;

/**
 * Created by root on 01.03.14.
 */


public class GrammarTokenizer implements Runnable {

    FileReader reader;
    BufferedReader bufferedReader;
    StreamTokenizer tokenizer;

    public Token token;
    public Token lookahead;
    public Iterator it;
    public LinkedList< Token> tokenList;
    public HashMap< String, Rule> ruleTable;

    public final HashMap< TokenType, String> parameterTypes;
    public final HashMap< String, TokenType> parameterNames;
    public final HashMap< TokenType, String> separatorTypes;
    public final HashMap< String, TokenType> separatorNames;

    /*
     * Ein Token mit name und type.
     * und mit parameterListe
     * und mit codeBlock
     */

    public static class Token {
        public String name;
        public TokenType type;
        public StringBuilder codeBlock;
        public LinkedList< Token> parameterList;
        public boolean hasCode() { return codeBlock != null; }
        public String toString () {
            return "< GrammarToken: "+name + ", "+type+">";
        }
        public boolean hasParameters () { return false; }
    }

    /*
     * A righthandside can have parameters. I set them here.
     */

    public static void addParameter(Token t, Token parameter) {
        if (t.parameterList == null) t.parameterList = new LinkedList();
        t.parameterList.addLast(parameter);
    }

    /*
     * Eine Rule
     * Hat die Lefthandside mit Namen, Symbol
     * Einen RuleType (SEMANTIC, LEX, SUPPL.)
     * Hat eine Liste mit der Reihe die Rechts steht.
     * Eine Liste mit Reihen, da sie mit | getrennt stehen koennen.
     * Einen moeglichen CodeBlock hat jedes Token
     */

    public static class Rule extends Token {
        public String name;
        public RuleType type;
        public LinkedList< Token> parameterList;    // Liste mit Parametern der LHS
        public LinkedList< LinkedList< Token>> rules; // Liste mit Liste mit Regeltoken
        public LinkedList< Token> last;
        public StringBuilder codeBlock;
        public boolean hasParameters() { return parameterList.size() > 0; }

        public Rule () {
            rules = new LinkedList<  LinkedList< Token>>();
            newRuleList();
        }
        public void newRuleList() {
            last = new LinkedList< Token>();
            rules.addLast(last);
        }
        public void addToList(Token t) {
            last.addLast(t);
        }
        public String toString () {
            return toString(0);
        }
        public String toString (int indent) {
            StringBuilder s = new StringBuilder();
            String tabs = "";
            for (int i = 0; i < indent; i++) tabs = tabs + "\t";
            s.append("< GrammarRule: ");
            s.append(name);
            s.append("\n");
            for (LinkedList< Token> l : rules) {
                s.append(tabs+"| ");
                for (Token t : l) {
                    if (t instanceof Rule) {
                        Rule r = (Rule)t;
                        s.append(r.toString(indent+1));
                    } else {
                        s.append(t.toString());
                    }
                }
            }
            return s.toString();
        }
    }


    public static enum TokenType {
        LEXICAL_SEP,        // :=
        SEMANTIC_SEP,       // ::=
        SUPPLEMENTAL_SEP,   // :::=
          ALTERNATIVE_SEP,    // | separator
        NONTERMINAL,        // Identifier
        TERMINAL,           // Literal
        NUMBER,             // Number
        EOF,                // End Token
        EOL,                // LineTerminator

        PARAMETER,          // [parm]
        DEPPARAMETER,        // [?parm]
        NOTREFPARAMETER,    // [~parm]
        REFPARAMETER,       // [+parm]

        SEMICOLON,          // ;
        UNKNOWN            // unknown character
    }

    public static enum RuleType {
        LEXICAL,
        SEMANTIC,
        SUPPLEMENTAL
    }
    
    
    public void nextFromTokenizer() {
        try {
            tokenizer.nextToken();
        } catch (IOException ex) {
            ex.printStackTrace();
            System.exit(0);
        }
    }
    
    protected void nextLexicalToken () {
        if (StreamTokenizer.TT_EOF == tokenizer.ttype) return;
        // rohes token mit den streamtokenizer preparieren
        nextFromTokenizer();


        // und in richtiges token verwandeln mit den folgenden bloecken
        Token token = new Token();



        if (tokenizer.ttype == StreamTokenizer.TT_EOF) {
            System.out.println("End of file! at line number" + tokenizer.lineno());
            token.name = tokenizer.sval;
            token.type = TokenType.EOF;

        } else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
            System.out.println("string = " + tokenizer.sval);
            token.name = tokenizer.sval;
            token.type = TokenType.NONTERMINAL;

        } else if (tokenizer.ttype == StreamTokenizer.TT_EOL) {
            System.out.println("finished line number" + tokenizer.lineno());
            token.name = tokenizer.sval;
            token.type = TokenType.EOL;

        } else if (tokenizer.ttype == StreamTokenizer.TT_NUMBER) {
            System.out.println("number = " + tokenizer.nval);
            token.name = ""+tokenizer.nval;
            token.type = TokenType.NUMBER;

        } else {
            /*
                parsen der operatoren
             */
            System.out.println("operator = " +(char)tokenizer.ttype);
            switch (tokenizer.ttype) {
                case '[':
                    /*
                     * [Param] - die Parameter beginnen mit [
                     */
                    nextFromTokenizer();
                    if (tokenizer.ttype == '?') {
                        token.type = TokenType.DEPPARAMETER;
                        nextFromTokenizer();
                    } else if (tokenizer.ttype == '~') {
                        token.type = TokenType.NOTREFPARAMETER;
                        nextFromTokenizer();
                    } else {
                        token.type = TokenType.PARAMETER;
                    }
                    token.name = tokenizer.sval;
                    nextFromTokenizer();
                    nextFromTokenizer();
                    break;
                case ':':
                    /*
                     * Lefthandside Separator / Production Symbol
                     * := (Lexikalisch) ::= (Semantisch) :::= (Supplemental)
                     */
                    token.name = ":";
                    token.type = TokenType.LEXICAL_SEP; // (:) von :=
                    nextFromTokenizer();

                    if ((char) tokenizer.ttype == ':') { // ::= (::)
                        token.name += ":";
                        token.type = TokenType.SEMANTIC_SEP;
                        nextFromTokenizer();
                    }
                    if ((char) tokenizer.ttype == ':') { // :::= (:::)
                        token.name += ":";
                        token.type = TokenType.SUPPLEMENTAL_SEP;
                        nextFromTokenizer();
                    }

                    if ((char) tokenizer.ttype == '=') { // (:::)=
                        token.name += (char)tokenizer.ttype;

                    } else {
                        token.name += (char)tokenizer.ttype; // ::* ETC
                        token.type = TokenType.UNKNOWN;
                    }
                    break;
                case '\'':
                    /*
                     * 'for'
                     * 'while'
                     * 'if'
                     * konstante Identifier oder als Strings betrachtet
                     * stehen in einfachen Gaensefuesschen und sind durch
                     * tokenizer.quoteChar('\'') nach sval eingelesen worden.
                     */
                    token.name = tokenizer.sval;
                    token.type = TokenType.TERMINAL;
                    break;
                case '|':
                    /*
                     * Ein | trennt eine Zeile von Regeln voneinander.
                     */
                    token.name = "|";
                    token.type = TokenType.ALTERNATIVE_SEP;
                    break;
                case ';':
                    /*
                     * Ein Semikolon beendet die Regel
                     *
                     */
                    token.name = ";";
                    token.type = TokenType.SEMICOLON;
                    break;
                case '{':
                    /*
                     * In einem { Block } sollte Code angegeben werden koennen
                     * der dann passend zur Produktion ausgeführt wird.
                     */
                default:
                    token.name += (char)tokenizer.ttype; // = expected
                    token.type = TokenType.UNKNOWN;
                    break;
            }

        }
        /*
            Zum Schluss fuegt nextFromTokenizer() das token in die Liste ein.
            Das gilt fuer alle switch cases.
         */
        tokenList.add(token);
    }

    public void tokenize () {
        while (tokenizer.ttype != StreamTokenizer.TT_EOF) nextLexicalToken();
        System.out.println("tokenization of grammar done");
        for (Token t: tokenList) {
            System.out.println(t);
        }
    }

    public void nextTokenForRule() {
        if (it.hasNext()) {
            token = lookahead;
            lookahead = (Token)it.next();
        } else {
            token = null;
            lookahead = null;
        }
    }


    public Rule parseRule() throws IOException {
        /*
         * Erwartung: Token ist auf das erste Token eingestallt
         * Beendet daher so : Stellt auf das naechste erste Token ein.
         */
        Rule rule;
        System.out.println("lhs" + token);
        /*
         * Wenn Nonterminal nehme es als erstes LHS Symbol an.
         * Dann versuche den Separator zu lesen. (Kein lookahead noetig.)
         */

        if (token.type.equals(TokenType.NONTERMINAL)) {

            rule = (Rule)ruleTable.get(token.name);
            if (rule == null) {
                System.out.format("+ adding new rule: %s\n", token.name);
                rule = new Rule();
                rule.name = token.name;
                ruleTable.put(token.name, rule);
            } else {
                throw new IOException("Error: Rule '"+token.name+"' already exists in ruleTable");
            }
            nextTokenForRule();
        } else {
            throw new IOException("Error: In parsing lefthandside symbol");
        }

        /*
         * moegliche [params] der lhs[params] :=
         */
        while (parameterTypes.containsKey(token.type) == true) {
            System.out.println("+ parameter: " + token);
            rule.parameterList.addLast(token);
            nextTokenForRule();
        }
        /*
         * Der LeftHandSide Separator
         * :=, ::= oder :::=
         */
        System.out.println("+ separator: " + token);

        if (token.type.equals(TokenType.LEXICAL_SEP)) {
            rule.type = RuleType.LEXICAL;
        } else if (token.type.equals(TokenType.SEMANTIC_SEP)) {
            rule.type = RuleType.SEMANTIC;
        } else if (token.type.equals(TokenType.SUPPLEMENTAL_SEP)) {
            rule.type = RuleType.SUPPLEMENTAL;
        } else {
            throw new IOException("Error: In parsing lefthandside separator "+token.type);
        }
        nextTokenForRule();
        /*
         * parse solange bis maximal "EOF.
         * lese TERMINALS und NONTERMINALS
         * mit und ohne Parameter ein.
         * Addiere sie zu einer Liste mit den RHS token
         *
         * Wenn | dann erzeugte neue Liste fuer RHS token
         * Wenn ; dann gebe das Rule Objekt zurück
         * Wenn EOF gebe das Rule Object zurück
         */
        int rules = 1;

        while (!token.type.equals(TokenType.EOF)) {

            System.out.println("+ (rule "+rules+") symbol "+token);

            if (token.type.equals(TokenType.ALTERNATIVE_SEP)) {
                System.out.println("| ends current rule");
                nextTokenForRule();
                rule.newRuleList();
                ++rules;
                continue;
            }

            if (token.type.equals(TokenType.SEMICOLON)) {
                System.out.println("; ends last rule of definitions for "+rule.name);
                rule.last.addLast(token);
                nextTokenForRule();
                return rule;
            }

            if (token.type.equals(TokenType.NONTERMINAL)) {
                Token pt = token;
                nextTokenForRule();
                while (parameterTypes.containsKey(token.type)) {
                    addParameter(pt, token);
                    nextTokenForRule();
                }
                rule.last.addLast(token);
                continue;
            }

            if (token.type.equals(TokenType.TERMINAL)) {
                rule.last.addLast(token);
                nextTokenForRule();
                continue;
            } else {
                throw new IOException("error parsing righthandsides");
            }
        }
        return rule;
    }

    public void parse() {
        Rule rule;
        it = tokenList.iterator();
        token = null;
        lookahead = (Token)it.next();
        nextTokenForRule();
        do {
            try {
                rule = parseRule();
                if (rule != null) ruleTable.put(rule.name, rule);
            } catch (IOException ex) {
                ex.printStackTrace();
                System.out.println("error parsing rule");
                System.exit(0);
            }
        } while (it.hasNext());
    }

    public void run () {
        tokenize();
        parse();
        print();
    }

    public void print() {
        Set entries = (Set)ruleTable.entrySet();
        Iterator it = entries.iterator();
        while (it.hasNext()) {
            Map.Entry< String, Rule> e = (Map.Entry< String, Rule>)it.next();
            System.out.println("key= " + e.getKey().toString() +",value= "+e.getValue().toString());
        }
    }

    public void usage () {
        System.out.println("usage: llkg grammar.es6");
        System.out.println("first argument has to be a valid grammar filename");
    }

    public GrammarTokenizer(String filename) {
        try {
            reader = new FileReader(filename);
        } catch (FileNotFoundException e) {
            usage();
            e.printStackTrace();
            System.exit(0);
        }
        bufferedReader = new BufferedReader(reader);
        tokenizer = new StreamTokenizer(bufferedReader);
        tokenizer.ordinaryChar('/');
        tokenizer.quoteChar('\'');  // 'while' 'for' in single quotes.
        tokenizer.slashSlashComments(true);
        tokenizer.slashStarComments(true);
        /*
         * tokenList - speichert die Token der Grammatik
         *
         */
        tokenList = new LinkedList< Token>();
        /*
         * ruleTable - speichert die aus den Token erstellten Regeln
         */
        ruleTable = new HashMap< String, Rule>();
        /*
         * Associate Parameter Letter with a Type
         */
        parameterNames = new HashMap< String, TokenType>(5);
        parameterNames.put("", TokenType.PARAMETER);
        parameterNames.put("?", TokenType.DEPPARAMETER);
        parameterNames.put("~", TokenType.NOTREFPARAMETER);
        parameterNames.put("+", TokenType.REFPARAMETER);
        /*
         * Associate the Parameter Type with a Letter
         */
        parameterTypes = new HashMap< TokenType, String>(5);
        parameterTypes.put(TokenType.PARAMETER, "");
        parameterTypes.put(TokenType.DEPPARAMETER, "?");
        parameterTypes.put(TokenType.REFPARAMETER, "+");
        parameterTypes.put(TokenType.NOTREFPARAMETER, "~");

        separatorNames = new HashMap< String, TokenType>(5);
        separatorTypes = new HashMap< TokenType, String>(5);
    }
}
/*
string = InputElementDiv
operator = :
string = Identifier
operator = |
string = WhiteSpace
operator = |
string = NumericLiteral
operator = |
string = BooleanLiteral
operator = ;
string = ForStatement
operator = :
operator = '
operator = '
string = Expression
operator = '
string = Statement
operator = ;
string = GeneratorDeclaration
operator = [
operator = :
operator = '
operator = '
string = BindingIdentifier
operator = [
operator = ;
string = Test
operator = [
operator = :
string = A
operator = [
string = B
operator = [
string = C
operator = ;
End of file! at line number16
tokenization of grammar done
< GrammarToken: InputElementDiv, NONTERMINAL>
< GrammarToken: :=, LEXICAL_SEP>
< GrammarToken: Identifier, NONTERMINAL>
< GrammarToken: |, ALTERNATIVE_SEP>
< GrammarToken: WhiteSpace, NONTERMINAL>
< GrammarToken: |, ALTERNATIVE_SEP>
< GrammarToken: NumericLiteral, NONTERMINAL>
< GrammarToken: |, ALTERNATIVE_SEP>
< GrammarToken: BooleanLiteral, NONTERMINAL>
< GrammarToken: ;, SEMICOLON>
< GrammarToken: ForStatement, NONTERMINAL>
< GrammarToken: ::=, SEMANTIC_SEP>
< GrammarToken: for, TERMINAL>
< GrammarToken: (, TERMINAL>
< GrammarToken: Expression, NONTERMINAL>
< GrammarToken: ), TERMINAL>
< GrammarToken: Statement, NONTERMINAL>
< GrammarToken: ;, SEMICOLON>
< GrammarToken: GeneratorDeclaration, NONTERMINAL>
< GrammarToken: Yield, PARAMETER>
< GrammarToken: :=, LEXICAL_SEP>
< GrammarToken: function, TERMINAL>
< GrammarToken: *, TERMINAL>
< GrammarToken: BindingIdentifier, NONTERMINAL>
< GrammarToken: Yield, PARAMETER>
< GrammarToken: ;, SEMICOLON>
< GrammarToken: Test, NONTERMINAL>
< GrammarToken: Yield, PARAMETER>
< GrammarToken: :=, LEXICAL_SEP>
< GrammarToken: A, NONTERMINAL>
< GrammarToken: Yield, DEPPARAMETER>
< GrammarToken: B, NONTERMINAL>
< GrammarToken: Yield, NOTREFPARAMETER>
< GrammarToken: C, NONTERMINAL>
< GrammarToken: ;, SEMICOLON>
< GrammarToken: null, EOF>
lhs< GrammarToken: InputElementDiv, NONTERMINAL>
+ adding new rule: InputElementDiv
+ separator: < GrammarToken: :=, LEXICAL_SEP>
+ (rule 1) symbol < GrammarToken: Identifier, NONTERMINAL>
+ (rule 1) symbol < GrammarToken: |, ALTERNATIVE_SEP>
| ends current rule
+ (rule 2) symbol < GrammarToken: WhiteSpace, NONTERMINAL>
+ (rule 2) symbol < GrammarToken: |, ALTERNATIVE_SEP>
| ends current rule
+ (rule 3) symbol < GrammarToken: NumericLiteral, NONTERMINAL>
+ (rule 3) symbol < GrammarToken: |, ALTERNATIVE_SEP>
| ends current rule
+ (rule 4) symbol < GrammarToken: BooleanLiteral, NONTERMINAL>
+ (rule 4) symbol < GrammarToken: ;, SEMICOLON>
; ends last rule of definitions for InputElementDiv
lhs< GrammarToken: ForStatement, NONTERMINAL>
+ adding new rule: ForStatement
+ separator: < GrammarToken: ::=, SEMANTIC_SEP>
+ (rule 1) symbol < GrammarToken: for, TERMINAL>
+ (rule 1) symbol < GrammarToken: (, TERMINAL>
+ (rule 1) symbol < GrammarToken: Expression, NONTERMINAL>
+ (rule 1) symbol < GrammarToken: ), TERMINAL>
+ (rule 1) symbol < GrammarToken: Statement, NONTERMINAL>
+ (rule 1) symbol < GrammarToken: ;, SEMICOLON>
; ends last rule of definitions for ForStatement
lhs< GrammarToken: GeneratorDeclaration, NONTERMINAL>
+ adding new rule: GeneratorDeclaration
+ parameter: < GrammarToken: Yield, PARAMETER>

Exception in thread "Thread-0" java.lang.NullPointerException
        at de.linux_swt.llkg.GrammarTokenizer.parseRule(GrammarTokenizer.java:338)
        at de.linux_swt.llkg.GrammarTokenizer.parse(GrammarTokenizer.java:418)
        at de.linux_swt.llkg.GrammarTokenizer.run(GrammarTokenizer.java:430)
        at java.lang.Thread.run(Thread.java:744)
*/

Die ersten zehn, fünfzehn Minuten habe ich bereits gemacht. Ich habe gerade das erste mal ein paar Reader aus dem Kopf gewrappt und den StreamTokenizer nur mit der autocompletion der IDE (steht ja alles drin) programmiert, weil ich mich noch an die Stunde am KIT erinnern kann, in Lektion 13. Da kam der dran. Ohne zu gucken habe ich´s hingekriegt.

Ich hab nochmal darauf den else Fall korrigiert. Ich suchte nach dem Feld, fand es nicht, dachte dann, dass ttype dann das Zeichen enthaelt, musste int nach char casten, im String Constructor geht es nicht, aber mit ""+(char)tokenizer.ttype problemlos von int nach char zu string.

= Lastenheft
== llkg LL(k) Generator

Das Programm soll aus einer PLALR(k) Grammatik, namentlich der von JavaScript,
einen LL(k) Parser generieren. Mit Tokenizer und Parser.

Dazu muss zum einen die := Grammatik und zum anderen die ::= Grammatik, sowie
die :::= Grammatik interpretiert werden können.

Der Generator soll einen Regelsatz aus der eingelesenen Grammatik ableiten.
Dazu wird die Eingabedatei in Token zerteilt. Diese werden durchlaufen und 
zraus wird eine Regel Lefthandside abgeleitet.

Die Regeln muessen in einer einfachen Tabelle, zum Beispiel im Hash gespeichert
sein, da sie rekursiv wiederholt drankommen werden. Dabei wird irgendwann die
Lefthandside definiert. Es sollte ein und dasselbe Objekt sein, was mehrmals als
Righthandside vorkommen kann und dann als Lefthandside definiert wird.

Der Schreiber soll die Moeglichkeit haben, für jede Regel eine Sprache 
austauschbar zu haben, so dass man zum Beispiel einen Java Parser und einen
C++ Parser generieren kann, sowie einen JavaScript Parser in JavaScript.

Zusätzlich zum generieren des Recursive Descent Parsing Codes sollte man die
Moeglichkeit haben, eigenen Code gleich zur Grammatik zu definieren. 

Man sollte die Moeglichkeit haben, die geparsten Daten zu verarbeiten.
Man sollte die Moeglichkeit haben, eigenen Code anzugeben, der die
geparsten Daten in Variablen verarbeiten kann, der dann mit dem Parser
Code generiert wird. Um zum Beispiel eigene IR Formate erzeugen zu koennen,
oder halt direkt den Interpreter schreiben zu können.

Orientieren werde ich mich an der ECMA-262 Spezifikation, dass der 
Generator exakt die Anforderungen abdeckt, diese Programmiersprache
so, wie sie ist, von der Spezifikation abschreiben zu können. Die
Algorithmen müssen in die Zielsprache transformiert werden können, 
sollen aber 1:1 unter der Produktion stehen koennen, wie in der Spec.

Was unter anderem bedeutet, dass Rekursion durch Wiederholung ausgedrueckt
wird, und eine Process ::= List ListElement solange wiederholt wird mit List ListElement, 
List ListElement, List ListElement, bis nur noch ListElement uebrig ist. Ist
im Prinzip das gleich wie for listElement of list, schreibt sich und liest sich nur anders
und wird immer mit dem String "List ListElement" ausgefuehrt statt mit list[i].
Diese Sprache der ECMA-262 soll der Generator beherrschen.

Ich habe mir gerade dazu geraten, mit diesen Projektdateien sofort zu beginnen. In syntax.js muss ich das nachholen, was ein wenig anstrengend ist. Wenn auch nicht schwerer, weil man das Programm besser kennt. Ich muss es nur mal machen, die Dokumente nachher anzufertigen.

Parallele Parser

Man kann mehrere serielle (sequentielle) Parser nebeneinander laufen lassen, oder die Eingabe teilen. Mir faellt auf, ich habe ja nur einen Core 2 Duo. Aber ich habe damit zwei Hardwarefäden. Bedeutet, wenn ich mehr als eine CPU habe kann ich doch einen Teil des Parsers und Compilers in dem einen Faden starten und dann an den anderen uebergeben, waehrend der Parser im anderen Faden schonmal weiterparsed und in dem anderen danach dann weiter kompiliert. Damit verschiebt sich das ein wenig nebeneinander und ich kann was Zeit sparen. Das ist kein Teile+Herrsche fuer Additionen, was teurer ist, sondern zwei bewusst genutzte Hardwarefaeden. (Was man vielleicht, oder gewiss noch ein wenig skalieren kann, jedenfalls denke ich die Woche schon dran, die Maschine mit einem parallelen Parser und Compiler auszustatten. Ich habe verstanden, wann wir jetzt sind und denke, ich kann mir das so bewusst machen.

Automaten

Gerade versuche ich mir einen Automaten zu hacken. Hier ist er. Er ist natuerlich einer, der auf das parsen ausgelegt ist, und unterstuetzt meine Versuche, die Character Klasse zu verwenden. Das ist die, die ich in JavaScript gerne haette, um Zeichen zu testen. Wenn das mal jemand championen koennte bis ES7, waer geil.

import java.util.LinkedList;
import java.util.HashMap;

class automaton {

    // Noch nicht fertig.
    // Gerade habe ich argList entfernt, weil ich bis auf eine Errormsg
    // keine argList brauche, und die kann ich als Zustandbestandteil setzen.
    
    // Aehm. Nullpointerexception kommt gleich. Ich hatte alle c!=null wieder
    // entfernt, weil ich dachte, next macht das mit mustFinalize klar, dass
    // beim letzten Zeichen, _egal wo wir sind_, dann zur finalisierung gegangen
    // wird. 
    
    // Mein erster Java Automat wird nichts mehr vor 22 Uhr. Aber ich habe
    // Lust heute noch was zu posten :-)
    
    // Nachtrag: Der wird heute gar nicht mehr fertig. Ich darf mir den nochmal durchlesen,
    // denn der laeuft hinterher weiter. Character.isISOControl() ist nicht die richtige
    // Funktion fuer die Operatoren. Die muss durch eine korrekte ersetzt werden.
    // Nebenher reicht es NICHT aus, einfach auf State.FINALIZE zu schalten. Denn wenn das 
    // in einem Loop passiert, wird der noch nicht korrekt beendet. Das muss jedem Loop
    // wohl hinzugefuegt werden. Hier sind ausserdem wohl zu viele deltas auf
    // dem Callstack.
    // Die Nullpointerexception hier liegt dran, dass transitionHelpers(State.WHITESPACE, whitespace)
    // nicht definiert ist, es geht aber mit Fehler weiter, dass ISOControl suckt und nicht
    // für Operatoren ist. Habe gerade keine Tabelle zum ablesen parat, brauchen wir aber wohl langsam.
    // Dann ist die Sache, dass nach FINALIZE erstmal next() returnt, der Loop abgebrochen
    // werden muss, falls wir collecten.
    // Wie man hieraus einen Automaten macht, das werde ich aber noch hinkriegen...
    
    
    // Das andere ist, ich sollte vielleicht lieber den Automat so gestalten, dass er
    // immer nur ein Zeichen nimmt, wenn er lext, und immer nur ein Token nimmt, wenn er parsed,
    // allerdings kriegt er einen Lookahead
    
    // Und wenn ich schonmal dabei bin, werde ich den Lexer/Parser paralellisieren auf
    // den zwei Hardware Threads und gucken, ob ich es schneller kriege als sequentiell,
    // oder ob das notify() viel zu viel kostet.
    // Ob ich ein oder mehrere Token im voraus lexe um dann den Parser auf Leitung zwei
    // loslegen zu lassen ? Wenn er out of token ist wartet er auf notify oder eine end Nachricht?
    // Mal schauen, da fällt mir bestimmt was ein.
    
    
    // Achso: delta(State.NEXT) sollte nicht
    // ans Ende der TransitionFunction.
    
    // Es sollte lieber ein "while (state != State.STOP) { /* evaluate character on new state */ }  sein
    // Damit wird verhindert, dass der Callstack tiefer geht als je 1x Transition.
    // (Hier waechst er wahrscheinlich proportional zur Anzahl der Token...)
    // Hinzugezogen muss man den Parser wieder auf Zeichenweise Transition umstellen
    // weil current.append() und next() dann lieber nicht gerufen werden sollten,
    // das macht der while Loop dann automatisch.
    // Vielleicht kennt ihr die Loesung jetzt schon (eine der moeglichen Loesungen)
    // hier setzt man immer die state property
    // Und identifierstart und identifierpart kriegen zwei zustände
    
    protected enum State {
        EVAL,
        START,
        IDENTIFIER,
        NUMBER,
        OPERATOR,
        WHITESPACE,
        STOP,
        FINALIZE,
        INVALID,
        ERROR
    }

    @FunctionalInterface
    static protected interface TransitionFunction {
        public void call();
    }

    /*
	static public abstract class AbstractAutomaton {
	    protected State state;
	    protected void delta (State newState, Object ...args);
	    public void start();
	}
    */


    static public class FirstConcreteAutomaton {

        protected State state;
        protected HashMap< State, TransitionFunction> transitionHelpers;

        public FirstConcreteAutomaton(String input) throws Exception {
            if (input == null) {
                throw new Exception("invalid input");
            }
            this.input = input;
            token = new LinkedList< >();
            invalidChars = new LinkedList< >();
            length = input.length();
            mustFinalize = false;

            transitionHelpers = new HashMap< State, TransitionFunction>();
            transitionHelpers.put(State.EVAL, evaluate);
            transitionHelpers.put(State.IDENTIFIER, word);
            transitionHelpers.put(State.OPERATOR, operator);
            transitionHelpers.put(State.NUMBER, number);
            transitionHelpers.put(State.INVALID, invalid);
            transitionHelpers.put(State.ERROR, error);
            transitionHelpers.put(State.STOP, stop);
            transitionHelpers.put(State.FINALIZE, finalizer);
            transitionHelpers.put(State.START, start);

        }


        protected LinkedList< String> token;
        protected LinkedList< Character> invalidChars;
        protected String input;
        protected int length;
        protected Character c;
        protected int pos;
        protected StringBuilder current;
        protected boolean mustFinalize;
        protected String errorMsg;


        protected void next () {
            if (pos < length) ++pos;
            if (pos == length) {
                mustFinalize = true;
                delta(State.FINALIZE);
            }
            c = new Character(input.charAt(pos));
        }

        final protected TransitionFunction start = () -> {
            length = input.length();
            pos = -1;
            next();
            System.out.println("System gestartet");
            delta(State.EVAL);

        };

        final protected TransitionFunction whitespace = () -> {
            while (c.isWhitespace(c.charValue())) next();
        };

        final protected TransitionFunction evaluate = () -> {
            if (pos == length) delta(State.FINALIZE);
            char ch = c.charValue();
            if (c.isUnicodeIdentifierStart(ch)) delta(State.IDENTIFIER);
            else if (c.isDigit(ch)) delta(State.NUMBER);
            else if (c.isWhitespace(ch)) delta(State.WHITESPACE);
            else if (c.isISOControl(ch)) delta(State.OPERATOR);
            else delta(State.INVALID);
            if (pos < length) {
                next();
                delta(State.EVAL);
            }
        };

        final protected TransitionFunction number = () -> {
            current = new StringBuilder();
            current.append(c.charValue());
            next();
            while (c.isDigit(c.charValue())) { current.append(c.charValue()); next(); }
            token.addLast(current.toString());
            delta(State.EVAL);
        };

        final protected TransitionFunction word = () -> {
            current = new StringBuilder();
            current.append(c.charValue());
            next();
            while (c.isUnicodeIdentifierPart(c.charValue())) { current.append(c.charValue()); next(); }
            token.addLast(current.toString());
            delta(State.EVAL);
        };

        final protected TransitionFunction operator = () -> {
            current = new StringBuilder();
            current.append(c.charValue());
            next();
            if (c.equals('{')) current.append(c.charValue());
            else if (c.equals('}')) current.append(c.charValue());
            else if (c.equals('(')) current.append(c.charValue());
            else if (c.equals(')')) current.append(c.charValue());
                // Operatoren muessten genau getestet werden und nicht nur concat
            else while (c.isISOControl(c.charValue())) { current.append(c.charValue()); next(); }
            token.addLast(current.toString());
            delta(State.EVAL);
        };


        final protected TransitionFunction invalid = () -> {
            System.out.println("Invalid character: "+c.toString());
            invalidChars.addLast(c.charValue());
            next();

            delta(State.EVAL);
        };


        final protected TransitionFunction stop = () -> {
            System.out.println("System gestoppt");
        };

        final protected TransitionFunction error = () -> {
            if (errorMsg != null) System.out.println("ERROR: "+errorMsg);
            else System.out.println("ERROR: Unknown");
        };



        final protected TransitionFunction finalizer = () -> {
            if (mustFinalize == true) { token.addLast(current.toString()); mustFinalize = true; }
            System.out.println("Finalizing step");

            delta(State.STOP);

            System.out.println("Token");
            for (String t: token) {
                System.out.println(t);
            }
        };

        protected void delta (State newState) {
            // Mal anders mit Funktion aus der Hashmap
            state = newState;
            TransitionFunction func = transitionHelpers.get(state);
            func.call();
		    /*
		    // Typisches automaten switch statement
		    switch(newState) {
		    case START:
		        next();
		    }*/
        }

        public void start() {
            delta(State.START);
        }
    }

    public static void main(String args[]) throws Exception {

        String sourceText = "var x = 10; x + y + z; 1+2+3; 10*10*10; function x() {}";

        FirstConcreteAutomaton myAutomaton = new FirstConcreteAutomaton(sourceText);
        myAutomaton.start();

    }
}
/*
System gestartet
Exception in thread "main" java.lang.NullPointerException
        at automaton$FirstConcreteAutomaton.delta(automaton.java:180)
        at automaton$FirstConcreteAutomaton.lambda$new$2(automaton.java:102)
        at automaton$FirstConcreteAutomaton$$Lambda$3/1554547125.call(Unknown Source)
        at automaton$FirstConcreteAutomaton.delta(automaton.java:180)
        at automaton$FirstConcreteAutomaton.lambda$new$4(automaton.java:126)
        at automaton$FirstConcreteAutomaton$$Lambda$5/1159190947.call(Unknown Source)
        at automaton$FirstConcreteAutomaton.delta(automaton.java:180)
        at automaton$FirstConcreteAutomaton.lambda$new$2(automaton.java:100)
        at automaton$FirstConcreteAutomaton$$Lambda$3/1554547125.call(Unknown Source)
        at automaton$FirstConcreteAutomaton.delta(automaton.java:180)
        at automaton$FirstConcreteAutomaton.lambda$new$0(automaton.java:89)
        at automaton$FirstConcreteAutomaton$$Lambda$1/1523554304.call(Unknown Source)
        at automaton$FirstConcreteAutomaton.delta(automaton.java:180)
        at automaton$FirstConcreteAutomaton.start(automaton.java:190)
        at automaton.main(automaton.java:199)
*/

BUG vor 22 Uhr. Ich poste das jetzt trotzdem, obwohl ich es kurz reparieren koennte.

@FunctionalInterface

Mit dieser Annotation kann man in Java 8 die Interfaces für die Lamda Expression vorkonfigurieren. So weiss der Compiler, dass er die Syntax dieses Interfaces abgekuerzt interpretieren kann, oder so aehnlich. Was ich mir vor drei oder vier Tagen mit ein paar Saetzen durchlas, habe ich wieder vergessen, wie man lesen kann, und ich schrieb zuerst @FunctionInterface, statt @FunctionalInterface, aber wie man VarArgs verarbeitet und korrekt castet, zusammen mit einer Lambda Expression fuer ein eigenes Interface, kann man hier sehen. @SafeVarargs habe ich übrigens auch nicht probiert, und ebenso wieder vergessen. Zumindest hat´s was mit den Varargs (...args) zu tun..

/*
argument 1 toString = Ich
argument 2 toString = bin
argument 3 toString = toll
Ich bin toll
*/

import java.lang.StringBuilder;

public class finf {

	@FunctionalInterface
	public static interface Callback {
	    public Object call(Object ...args);
	}

	public static void main(String args[]) {
	
	    Callback func = (argList) -> {
		int i = 0;
		final StringBuilder concat = new StringBuilder();
		final String space = " ";
		final int numArgs = argList.length;
		for (Object arg: argList) {
		    ++i;
		    System.out.println("argument "+i+" toString = " + arg.toString());
		    concat.append(arg);
		    if (i < numArgs) concat.append(space);
		}
		return concat.toString();
	    };
	    String s = (String)func.call("Ich", "bin", "toll");
	    System.out.println(s);
	}
}

Was mich aergerte war, dass ich args in argList umbenennen musste, weil main args bereits definierte. Eigentlich bin ich gewohnt, auf der Zeile, wo die Lamda Expression steht in der ParameterListe neue Variablen fuer den Scope der Funktion zu definieren. Das gute ist, die sind hier wohl trotzdem zusammengehoerig und Closure. Die genaue Handhabung kann ich mir ja noch beibringen, hehe.

import static

In Java kann man also nicht nur Klassen importieren, sondern auch statische Attribute und Methoden einer Klasse. Das vereinfacht mir es natürlich ungemein, da ich die Klassennamen vor den Operationen weglassen kann. Was mir im EcmaScript API gelegen kommt.

// pkg/X.java
package pkg;
public class X {
    public static void x () { System.out.println("static method"); }
}

// Y.java
import static pkg.X.x;

class Y {
    public static void main(String args[]) {
	x();		// Anstatt nur per X.x() kann man nach import static das Symbol direkt haben.
    }
}

/*
static method
*/

Passendes Zitat aus dem Java Tutorial file:///tutorial/java/package/usepkgs.html:

Note: Use static import very sparingly. Overusing static import can result in code that is difficult to read and maintain, because readers of the code won't know which class defines a particular static object. Used properly, static import makes code more readable by removing class name repetition.

Konvertieren von Hex und Oct nach Binaer

/*

 Konvertieren von Basis 8 oder Basis 16 nach Basis 2 ist besonders einfach.
 Man tauscht diese einfach mit dem Bitstring aus und concatted diese Stuecke.
 Das ist mathematisch einwandfrei.
 
 Leider kann man in EcmaScript 5 wohl keine Binärzahlen mit parseFloat parsen,
 zumindest akzeptiert parseFloat keine Basis 2, während parseInt es tut,
 so dass die Stellen nach dem . nicht mehr angezeigt werden. 
 Man kann die Zahlen allerdings manuell konvertieren. Die unten gezeigte 
 Funktion binToTen ist nicht dafuer geeignet, man wuerde eine Potenz von
 Zwei pro Position addieren, ergibt aber das selbe Ergebnis.
 
 Herausgefunden habe ich das nicht, sondern von einem Studenten gelernt, der
 das in einem Screencast klargestellt hattte. Die Serie heisst "Converting .. to .."
 und ist ueber Youtube erhaeltlich, wie tausende andere ebenso lehrreiche Videos.
 Die JavaScript Fassung allerdings ist wieder Müll von mir.

 Zur Vereinfachung und Verdeutlichung nutze ich hier Strings.
 
 FEHLER:
 
 Vor einigen Stunden gepostet hatte ich natürlich keine Probe gemacht. Als
 ich die im Kopf machte und die Buchstaben nochmal durchging, fiel mir auf,
 dass meine Kommainterpretation falsch war, dass parseInt ungeeignet ist, 
 um einfach mit "." concatted zu werden. Ich habe das eben korrigiert und
 kommentiert.
 
 
*/

var hexTable = {
    "0": "0000",
    "1": "0001",
    "2": "0010",
    "3": "0011",
    "4": "0100",
    "5": "0101",
    "6": "0110",
    "7": "0111",
    "8": "1000",
    "9": "1001",
    "A": "1010",
    "B": "1011",
    "C": "1100",
    "D": "1101",
    "E": "1110",
    "F": "1111",
};

var octTable = {
    "0": "000",
    "1": "001",
    "2": "010",
    "3": "011",
    "4": "100",
    "5": "101",
    "6": "110",
    "7": "111"
};

var dot = ".";

function octToBin(num) {
    num = num.toString();
    bin = "";
    for (var i = 0, j = num.length; i < j; i++) {
    	var n = num[i];
    	if (n == dot) {
    	    bin += dot;
    	    continue;
    	} 
    	bin += octTable[n];
    }
    return bin;
}


function hexToBin(num) {
    num = num.toString();
    bin = "";
    for (var i = 0, j = num.length; i < j; i++) {
    	var n = num[i];
    	if (n == dot) {
    	    bin += dot;
    	    continue;
    	} 
    	bin += hexTable[n];
    }
    return bin;
}

function floatingBinToTen(bin) {
    bin = ""+bin;
    var index;
    if ((index = bin.indexOf("."))>-1) {
	var left = parseInt(bin.substr(0, index), 2);

	// Zuerst war ein Bug drin. parseInt denkt die Zahl waere > 0
	// var right = parseInt(bin.substr(index+1, bin.length-1), 2);

	// Hier ist die Korrektur, wir rechnen mit negativen Potenzen (dividieren) hinter dem Komma.
	var right = 0;
	for (var i = index+1, j = bin.length, k = 1; i < j; ++i, ++k) {
	    right += +bin[i] * Math.pow(2, -k);
	}
	return left+right;
    }
    return parseInt(bin, 2);
}



console.log("octToBin for FF and F and D.1E:");
console.log(hexToBin("FF"));
console.log(hexToBin("F"));
console.log(hexToBin("D.1E"));
console.log("wieder umgewandelt in Basis 10");
console.log(parseInt(hexToBin("FF"), 2));
console.log(parseInt(hexToBin("F"), 2));
console.log(floatingBinToTen(hexToBin("D.1E")));

console.log("octToBin for 72 and 4 and 51.2:");
console.log(octToBin("72"));
console.log(octToBin("4"));
console.log(octToBin("51.2"));
console.log("wieder umgewandelt in Basis 10");
console.log(parseInt(octToBin("72"), 2));
console.log(parseInt(octToBin("4"), 2));
console.log(floatingBinToTen(octToBin("51.2")));

/*
octToBin for FF and F and D.1E:
11111111
1111
1101.00011110
wieder umgewandelt in Basis 10
255
15
13.1171875
octToBin for 72 and 4 and 51.2:
111010
100
101001.010
wieder umgewandelt in Basis 10
58
4
41.25

*/

Zirkulaerer Lookahead

Gerade lese ich ein interessantes Kapitel ueber zirkulaeren Lookahead fuer LL(1) Parser. Durch einen Ringpuffer (eine mod Operation zur Indizierung) kann man mehrere Token lookahead addressieren. In JavaScript wird das fuer 4 Zeichen Operator gebraucht, ich z.B. nehme mir speziell in der Funktion punctuation() einen erweiterten lookahead raus. Durch diesen mod N Trick kann man einen Puffer von ein paar Zeichen oder Token ohne Aufwand vom runterkopieren der Position halten. Schlauer Einfall, mit modularer Arithmetik. Noch schlauer ist das Buch dazu von Terrence Parr, das ist der, der ANTLR geschrieben hat, und dort ANTLR benutzt. Das heisst Language Implementation Patterns. Mir hat es geklaert, was ich wissen wollte. In jedem Fall.

Eddies Schmierzettel in JavaScript

Hier sind ein paar Files, die ich zwischendurch mal kurz gemacht habe, um auszuprobieren, was ich machen muss. Also Nummern konvertieren. Bits setzen und lesen. gescheite Rechenwege finden.

Oh, weil ich die erst im /discover Verzeichnis hatte, hatte ich die auf english kommentiert.

/*
 Decimal to Hexadecimal seen in "Digitaltechnik und Entwurfsverfahren"
 In Lecture 1 vom KIT http://webcast.informatik.kit.edu
    Euclidean Algorithm.
    Get the highest power of 16 being less or equal than the number.
    Divide by that power. You get a letter.
    Take the rest. 
    Take one off the power. Divide by that power. You get the next letter.
*/

var hexLett = {0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,10:"A",11:"B",12:"C",13:"D",14:"E",15:"F"};
var hexVals = {0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15};
var decVals = {0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9};

function string10ToBase16(base10string) {
    var number = +base10string; // stringToBase10(base10string)
    var remainder;
    var sval = "";
    var i = 0;
    while (Math.pow(16, i+1) <= number) ++i;
    while (Math.pow(16, i) > number) --i;
    remainder = number;
    while (remainder > 10e-8) {
	number = Math.floor(remainder / Math.pow(16, i));
	remainder = remainder % Math.pow(16, i);
    	sval += hexLett[number];
	i = i - 1;
    }
    return sval;
}

console.log(string10ToBase16("1"));
console.log(string10ToBase16("15"));
console.log(string10ToBase16("16"));
console.log(string10ToBase16("32"));
console.log(string10ToBase16("33"));
console.log(string10ToBase16("255"));
console.log(string10ToBase16("65565"));
console.log(string10ToBase16("65564"));
console.log(string10ToBase16("65563"));
/*
1
F
1
2
21
FF
1001D
1001C
1001B
*/

Bits setzen.

/*

    This file shows a principle for setting a property descriptors boolean
    attributes as bits (a multiple of two) by adding or subtracting it´s 
    value (or using | and and ^ xor). Testing with &. 

*/

var desc = { flags: 0 };

function setEnumerable(descriptor, value) {
    if (value) {
	if ((descriptor.flags & 4) === 0) descriptor.flags |= 4; /* += 4 */
    } else {
	if ((descriptor.flags & 4) === 4) descriptor.flags ^= 4; /* -= 4 */
    }
}
function isEnumerable(descriptor) {
    return (descriptor.flags & 4) === 4;
}

function setConfigurable(descriptor, value) {
    if (value) {
	if ((descriptor.flags & 2) === 0) descriptor.flags += 2; /* |= 2 */
    } else {
	if ((descriptor.flags & 2) === 2) descriptor.flags -= 2; /* ^= 2 */
    }
}
function isConfigurable(descriptor) {
    return (descriptor.flags & 2) === 2;
}	

setEnumerable(desc, false);
console.log(isEnumerable(desc));
setEnumerable(desc, true);
console.log(isEnumerable(desc));
setConfigurable(desc, false);
console.log(isConfigurable(desc));
setConfigurable(desc, true);
console.log(isConfigurable(desc));
setEnumerable(desc, false);
setConfigurable(desc, false);
console.log(isEnumerable(desc));
console.log(isConfigurable(desc));
setEnumerable(desc, true);
setConfigurable(desc, false);
console.log(isEnumerable(desc));
console.log(isConfigurable(desc));
setEnumerable(desc, false);
setConfigurable(desc, true);
console.log(isEnumerable(desc));
console.log(isConfigurable(desc));
setEnumerable(desc, true);
setConfigurable(desc, true);
console.log(isEnumerable(desc));
console.log(isConfigurable(desc));
setEnumerable(desc, false);
setConfigurable(desc, false);
console.log(isEnumerable(desc));
console.log(isConfigurable(desc));

Jetzt fehlt die Ausgabe. Aber ihr koennt sicher sein, sie entspricht der Eingabe wie erwartet.

Ausgegraben eine alter Garbage Collector fuer einen TypedArray (erster Versuch, 2013)

/*
    Wer Bytecode will muss leiden (ueben).
*/

function Heap (space) {

    "use strict";

    space = space || 2048;
    var from = new_space(space);
    var to;

    function new_space (space) {
	return {
	    space: space,
	    heap: new ArrayBuffer(space),
	    sp: 0,
	    root: Object.create(null)
	};
    }

    function collect() { // Mark and Sweep frei nach 6.172 Lektion 10
	"use strict";
	var v;
	var Q = [];	
	var r,e,v,o,p,q,b,i,j;
	var root = from.root;
	var bytes = 0;
	
	// erstmal alle live objects suchen  
	for (e in root) {
	
	    v = root[e];
	    if (v.mark === 1) {
		Q.push(v); 
		r = v.view;
		bytes += r.byteLength; // ob ich resizen will
	    } else {
		v.view = null;
		v.ptr.offset = null;
	    }
	}
	
	// resize bei genug belegung
	if (bytes > (space / 2)) { 
	    space += Math.floor(space * 2);
	    if ((b=space % 8) !== 0) space += 8-b;
	} else if (bytes < (space / 4)) {
	    space -= Math.floor(space / 2);
	    if ((b=space % 8) !== 0) space -= b;
	}
	
	// neue to space
	to = new_space(space);
	
	// umkopieren in den to space und pointer updaten
	i=0, j=Q.length;
	while (i < j) {
	    v = Q[i]; // Billiger als O(n) Q.shift()
	    i += 1; 
    	    p = store(to, load(v.ptr)); // neue records fuer to wurden in store erzeugt
	    if (p) {
	        o = v.ptr.offset;
	        root[o].view = v.view; // an alle vorherigen besitzer
		root[o].ptr.offset = p.offset; // die neuen daten per reference type (der record bleibt)
		root[p.offset] = root[o];
		root[o] = null;
		delete root[o];
	    }
	}
        // space wechseln
	delete from.root;// = null; 
	delete from.sp;// = null;
	delete from.heap; //= null;
        from = to;
	to = null;
    }
    
    /*
	resize: Loesche den alten View, nachdem ein neuer alloziert wurde
    */

    function resize(ptr, new_size) {
	"use strict";
	var rec = from.root[ptr.offset];
	var view = rec.view;
	var map = rec.map;

	var new_rec = store(load(ptr));
	
	rec.mark = 0;
	rec.view = null;
	rec.ptr.offset = null;
	rec.map = null;
    }
    
    function alloc (size) {
	var offset = to8(buf.sp);
	size = to8(size);
	from.sp += size;
	if (offset > space) {
	    collect();
	    offset = to8(buf.sp);
	    size = to8(size);
	    from.sp += size;
	}
	var view = new FloatArray(from.heap, offset, size);
	return { offset:offset, size:size, view: view };
    }

    function free(ptr) {
	"use strict";
	var rec = from.root[ptr.offset];
	rec.mark = 0;
	if (rec.view) rec.view = null;
	if (rec.map) for (var k in rec.map) free(rec.map[k]);
    }

    function load(ref) {
	"use strict";
	var data, view, view2, map, k, obj, f, key, keys, m,n,l,j,i;
	var rec = from.root[ref.offset];
	if (rec) {
	    if (rec.type == "object") {
		
		map = rec.map;
		view = rec.view;
		view2 = new Uint16Array(from.heap, ref.offset, view.length);
		keys = view[0];
		i = 8;
		console.log("got keys="+keys);
		
		for (j=0; j < keys; j++) {
		    key = "";
		    offset = view[i];
		    n = view[i+1];
		    i += 8;
		    for (m=0; m < n; m++) { 
		    	key += String.fromCharCode(view2[Math.floor(i/4)+m]);
		    }
		    console.log("key restauriert? "+key);
		    i += to8(n); 
		}
		obj = {};
		for (k in map) obj[k] = load(map[k]);
		return obj;
	    } else if (rec.type == "array") {
		map = rec.map;
		obj = [];
		for (k in map) obj[k] = load(map[k]);
		return obj;
	    } else if (rec.type === "function") {
		view = rec.view;
		data = "";
		for (var i=0, j=view.length; i < j; i++) {
		    data += String.fromCharCode(view[i]);
		}
		f = new Function("return "+data);
		f = f();
		return f; 
	    } else if (rec.view) {
    		view = rec.view;
    		if (view instanceof Uint16Array) {
		    data = "";
		    for (var i=0, j=view.length; i < j; i++) {
			data +=  String.fromCharCode(view[i]);
		    }
		    return data;
		} else if (view instanceof Float64Array) {
		    return view[0];
		}
	    }
	} 
    }

    /*
	Speichern auf der Halde (die Wdh darf ich noch DRYen, schoen fuer extractMethods())
    */
    
    function storedouble(view, data, off)  {
	off = off| 0;
	view[off] = data;
    }
    
    function storestring(view, data, off) {
	    off = off| 0;
	    for (var i = 0; i < data.length; i++) {
		view[i+off] = data.charCodeAt(i);
    	    }
    }
    
    function to8 (n) {
	var m = n % 8;
	if (m === 0) return n;
	else return n + 8-m;
    }

    function store(buf, data, size) {
	"use strict";
	
	var offset, view, view2, bpe;
	var type, map, i,j,k,l,m,n;
	var rec;
	var heap = buf.heap;
	
	var t = typeof data;
	
	
	if (t === "string") {
	    type = "string";
	    bpe = Uint16Array.BYTES_PER_ELEMENT;	
	    if (size === undefined) size = data.length;
	    offset = buf.sp + (bpe - (buf.sp % bpe)); // align
	} else if (t === "number") {
	    type = "number";
	    bpe = Float64Array.BYTES_PER_ELEMENT;
	    size = 1;
	    offset = buf.sp + (bpe - (buf.sp % bpe)); // align
	} else if (t === "function") {
	    type = "function";
	    data = data.toString();
	    bpe = Uint16Array.BYTES_PER_ELEMENT;	
	    if (size === undefined) size = data.length;
	    offset = buf.sp + (bpe - (buf.sp % bpe)); // align
	} else if (t === "object") {
	    if (Array.isArray(data)) {
		type = "array";
		map = Object.create(null);
		for (i = 0, j = data.length; i < j; i++) {
		    map[i] = store(buf, data[i]);
		}    
		
	    } else {
		var keys = 0;
		size = 8;
		type = "object";
		map = Object.create(null);
		for (k in data) {
		    ++keys;
		    map[k] = store(buf, data[k]);
		    size+= k.length;
		    size+= 16; // for offset + len
		}
		j = size%8;
		if (j !== 0) size += 8-j;
		bpe = Float64Array.BYTES_PER_ELEMENT;	
		for (k in data) {
		offset = map[k].offset + "MAP"; // ein trick
		break;
		}
	    }
	}
	
	
	// 2. Moeglicher Collect
	if ((offset + (size*bpe)) > space) {
	    collect();
	    buf = from;
	    offset = buf.sp + (bpe - (buf.sp % bpe)); // align again
	}
	    
	
	// 3. Daten im richtigen Space speichern.
	if (t === "string") {
	    view = new Uint16Array(buf.heap, offset, size);
	    storestring(view, data);
    	
	} else if (t === "number") {
	    view = new Float64Array(buf.heap, offset, size);
	    storedouble(view, data);
	} else if (t === "function") {
	    view = new Uint16Array(buf.heap, offset, size);
	    storestring(view, data);
	} else if (t === "object") {
		view = new Float64Array(buf, offset, size);
		view2 = Uint16Array(buf, offset, size);
		view[0] = keys;
		console.log("saved keys="+keys);
		i = 1;
		for (k in map) {
		    console.log("encode "+k);
		    view[++i] = offset;
		    view[++i] = (n=k.length);
		    for (m=0; m < n; m++) { 
		    	view2[Math.floor(i/4)+m] = k.charCodeAt(m);
		    }
		    i += to8(n);
		}
	}
	
	

	if (view) buf.sp = offset + view.byteLength - 1;
 	
 	if (view || map) {
	    
	    buf.root[offset] = rec = {
		mark: 1,
		view: view ? view : undefined,
		type: type,
		map: map ? map : undefined,
		ptr: {
		    offset: offset
		}
	    };
	    
	    return rec.ptr;	// distributed kann ich mir hier bereits promises vorstellen 
	}
    }

    return {
	get byteLength () {
	    return from.space;
	},
	get bytesUsed () { 
	    return from.sp;
	},
	store: function (data, size) { 
	    return store(from, data, size); 
	},
	load: function (ptr) { 
	    return load(ptr); 
	},
	resize: function (ptr, size) { 
	    return resize(ptr, size); 
	},
	free: function (ptr) { 
	    return free(ptr); 
	},
	alloc: function (size) {
	    return alloc(size);
	},
	gc: function () {
	    return collect(from); 
	},
	heap: function () {
	    return from.heap;
	}
    };

}

exports.Heap = Heap;



var heap = new Heap(2048);
var ptr = [];
for (var i = 0, j = 100; i < j; i++) {
    var s = "string "+i;
    ptr.push(heap.store(s, s.length));
    console.log(heap.bytesUsed + " (bytes used)");
}
var p;
while (p=ptr.shift()) {
    var s = heap.load(p);
    console.log("retrieved "+s);
    
    heap.free(p);
    console.log("freed ptr");
}
console.log(heap.byteLength + " (heap.byteLength)");
console.log(heap.bytesUsed + " (bytes used)");

heap.gc();

console.log(heap.byteLength + " (heap.byteLength)");
console.log(heap.bytesUsed + " (bytes used)");

/*
17 (bytes used)
33 (bytes used)
49 (bytes used)
65 (bytes used)
81 (bytes used)
97 (bytes used)
113 (bytes used)
129 (bytes used)
145 (bytes used)
161 (bytes used)
179 (bytes used)
197 (bytes used)
215 (bytes used)
233 (bytes used)
251 (bytes used)
269 (bytes used)
287 (bytes used)
305 (bytes used)
323 (bytes used)
341 (bytes used)
359 (bytes used)
377 (bytes used)
395 (bytes used)
413 (bytes used)
431 (bytes used)
449 (bytes used)
467 (bytes used)
485 (bytes used)
503 (bytes used)
521 (bytes used)
539 (bytes used)
557 (bytes used)
575 (bytes used)
593 (bytes used)
611 (bytes used)
629 (bytes used)
647 (bytes used)
665 (bytes used)
683 (bytes used)
701 (bytes used)
719 (bytes used)
737 (bytes used)
755 (bytes used)
773 (bytes used)
791 (bytes used)
809 (bytes used)
827 (bytes used)
845 (bytes used)
863 (bytes used)
881 (bytes used)
899 (bytes used)
917 (bytes used)
935 (bytes used)
953 (bytes used)
971 (bytes used)
989 (bytes used)
1007 (bytes used)
1025 (bytes used)
1043 (bytes used)
1061 (bytes used)
1079 (bytes used)
1097 (bytes used)
1115 (bytes used)
1133 (bytes used)
1151 (bytes used)
1169 (bytes used)
1187 (bytes used)
1205 (bytes used)
1223 (bytes used)
1241 (bytes used)
1259 (bytes used)
1277 (bytes used)
1295 (bytes used)
1313 (bytes used)
1331 (bytes used)
1349 (bytes used)
1367 (bytes used)
1385 (bytes used)
1403 (bytes used)
1421 (bytes used)
1439 (bytes used)
1457 (bytes used)
1475 (bytes used)
1493 (bytes used)
1511 (bytes used)
1529 (bytes used)
1547 (bytes used)
1565 (bytes used)
1583 (bytes used)
1601 (bytes used)
1619 (bytes used)
1637 (bytes used)
1655 (bytes used)
1673 (bytes used)
1691 (bytes used)
1709 (bytes used)
1727 (bytes used)
1745 (bytes used)
1763 (bytes used)
1781 (bytes used)
retrieved string 0
freed ptr
retrieved string 1
freed ptr
retrieved string 2
freed ptr
retrieved string 3
freed ptr
retrieved string 4
freed ptr
retrieved string 5
freed ptr
retrieved string 6
freed ptr
retrieved string 7
freed ptr
retrieved string 8
freed ptr
retrieved string 9
freed ptr
retrieved string 10
freed ptr
retrieved string 11
freed ptr
retrieved string 12
freed ptr
retrieved string 13
freed ptr
retrieved string 14
freed ptr
retrieved string 15
freed ptr
retrieved string 16
freed ptr
retrieved string 17
freed ptr
retrieved string 18
freed ptr
retrieved string 19
freed ptr
retrieved string 20
freed ptr
retrieved string 21
freed ptr
retrieved string 22
freed ptr
retrieved string 23
freed ptr
retrieved string 24
freed ptr
retrieved string 25
freed ptr
retrieved string 26
freed ptr
retrieved string 27
freed ptr
retrieved string 28
freed ptr
retrieved string 29
freed ptr
retrieved string 30
freed ptr
retrieved string 31
freed ptr
retrieved string 32
freed ptr
retrieved string 33
freed ptr
retrieved string 34
freed ptr
retrieved string 35
freed ptr
retrieved string 36
freed ptr
retrieved string 37
freed ptr
retrieved string 38
freed ptr
retrieved string 39
freed ptr
retrieved string 40
freed ptr
retrieved string 41
freed ptr
retrieved string 42
freed ptr
retrieved string 43
freed ptr
retrieved string 44
freed ptr
retrieved string 45
freed ptr
retrieved string 46
freed ptr
retrieved string 47
freed ptr
retrieved string 48
freed ptr
retrieved string 49
freed ptr
retrieved string 50
freed ptr
retrieved string 51
freed ptr
retrieved string 52
freed ptr
retrieved string 53
freed ptr
retrieved string 54
freed ptr
retrieved string 55
freed ptr
retrieved string 56
freed ptr
retrieved string 57
freed ptr
retrieved string 58
freed ptr
retrieved string 59
freed ptr
retrieved string 60
freed ptr
retrieved string 61
freed ptr
retrieved string 62
freed ptr
retrieved string 63
freed ptr
retrieved string 64
freed ptr
retrieved string 65
freed ptr
retrieved string 66
freed ptr
retrieved string 67
freed ptr
retrieved string 68
freed ptr
retrieved string 69
freed ptr
retrieved string 70
freed ptr
retrieved string 71
freed ptr
retrieved string 72
freed ptr
retrieved string 73
freed ptr
retrieved string 74
freed ptr
retrieved string 75
freed ptr
retrieved string 76
freed ptr
retrieved string 77
freed ptr
retrieved string 78
freed ptr
retrieved string 79
freed ptr
retrieved string 80
freed ptr
retrieved string 81
freed ptr
retrieved string 82
freed ptr
retrieved string 83
freed ptr
retrieved string 84
freed ptr
retrieved string 85
freed ptr
retrieved string 86
freed ptr
retrieved string 87
freed ptr
retrieved string 88
freed ptr
retrieved string 89
freed ptr
retrieved string 90
freed ptr
retrieved string 91
freed ptr
retrieved string 92
freed ptr
retrieved string 93
freed ptr
retrieved string 94
freed ptr
retrieved string 95
freed ptr
retrieved string 96
freed ptr
retrieved string 97
freed ptr
retrieved string 98
freed ptr
retrieved string 99
freed ptr
2048 (heap.byteLength)
1781 (bytes used)
1024 (heap.byteLength)
0 (bytes used)

*/

Ob´s das richtige Horner Schema ist, weiss ich nicht. Aber in Verbindung gebracht wurde es. Und wiederholen darf ich es auch nochmal.

/*
 "Horner Schema" seen in "Digitaltechnik und Entwurfsverfahren"
 The Lecture 1 (c) KIT http://webcast.informatik.kit.edu
    The formula i can read:
    Xb is the epsilon sum of z[i] times b^i from i=0 to n.

    Taken from the Slide with help of:
    15741 = (((1*10+5)*10+7)*10+4)*10+1
*/

var decVals = {0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9};

function stringToBase10(string) {
    var nval = decVals[string[0]];
    for (var i = 1, j = string.length; i < j; i++) {
	nval *= 10, nval += decVals[string[i]];
    }
    return nval;
}
console.log(stringToBase10("1234567890"));
console.log(stringToBase10("1000000"));

Hier ist eine Variante von MV (Mathematic Value)

// this version has no rounding errors.
// i do not add pieces under zero, i shift the .
// and raise by the exponent.
// by that way, it saves time in the function, too.

var decVals = { 0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9 };
var hexVals = { 0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,E:14,e:14,f:15,F:15 };
var octVals = { 0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:NaN,9:NaN};
var binVals = { 0:0,1:1 }; // undefined coerces to NaN
var isX = {x:true,X:true};
var isO = {o:true,O:true};
var isB = {b:true,B:true};
var isExp = {e:true, E:true};
var isDot = {".":true};
var isSign = {"+":true,"-":true};
var isExpOrSign = {e:true,E:true,"+":true,"-":true};
var signVal = {"+":1,"-":-1};

function MV (string, type) {
	var nval = 0;
	var v, w, x = 0, y;
	var base, vals;
	var exp = 0;
	var expSign = 1;
	var strlenExp = 0;
	var sign = 1;
	w = string[0];
	if (isSign[w]) sign = signVal[w], x = 1;
	w = string[x+1];
	if (isX[w]) type = "hex", x += 2;
	else if (isB[w]) type = "bin", x += 2;
	else if (isO[w]) type = "oct", x += 2;
	if ((x > 1) && string[x-2] != "0") throw new SyntaxError("invalid start of a numeric literal");
	if (type === undefined || type === "dec") {
		base = 10;
		for (var i = string.length-1, j = 0, k = 0; i >= j; i--, k++) {
			if ((w = string[i]) !== ".") {
			    if (!isExpOrSign[w]) {
			    	if ((w=decVals[w]) === undefined) throw new SyntaxError("invalid character "+w+" in numeric literal of type decimal");
			    	nval += Math.pow(base, k) * w;
			    } else {		
			    	if (!isSign[w]) {
			    		exp = nval - expSign;
			    		nval = 0;
			    		k = 0;
			    	} else {
			    		strlenExp = string.length - i - 1;
			    		expSign = signVal[w];
			    		k -= 1;
			    	}
			    }
			} else y = i+1, k-=1;
		}
		var exp2; 
		if (exp != undefined) exp2 = expSign * exp;
		if (y != undefined) exp2 = exp2 - (string.length-strlenExp-y);
		nval *= Math.pow(base, exp2);	// der dot.
		return sign * nval;
	} else if (type === "bin") {
	    base = 2;
	    vals = binVals;
	} else if (type === "oct") {
	    base = 8;
	    vals = octVals;
	} else if (type === "hex") {
    	base = 16;
    	vals = hexVals;
	}
	for (var i = string.length-1, j = 0+x, k = 0; i >= j; i--, k++) {
		if ((v = vals[string[i]]) === undefined) throw new SyntaxError("invalid character "+v+" in numeric literal of type "+type);
		nval += Math.pow(base, k) * v;
	}
	return sign * nval;
}


var numbers = [
	"1234", "12.34", "115134", "121.354", "8748", "4845.457", "10e1","10e3","10e4","10e6","1e7","123.123e45",
	"10e-1","10e-3","10e-4","10e-6","1e-7","123.123e-45"
];
numbers.forEach(function (nStr) {
	console.log(nStr+":" + MV(nStr)+ " = "+parseInt(nStr,10) + " ("+parseFloat(nStr,10)+")");
});

var octNumbers = [
	"0o167", "0o123", "0o366", "0o235", "0o235", "0o252"
];
octNumbers.forEach(function (nStr) {
	console.log(nStr+":" + MV(nStr, "oct") + " = "+parseInt(nStr.substr(2),8));
});

var hexNumbers = [
	"0xff", "0x123abcde", "0xcdefabcd", "0x012345", "0xf", "0xa", "0x7", "0xab", "0xfe"
];
hexNumbers.forEach(function (nStr) {
	console.log(nStr+":" + MV(nStr, "hex") + " = "+ parseInt(nStr.substr(2), 16));
});

var binNumbers = [
	"0b1", "0b11", "0b10", "0b1111", "0b1001", "0b1000", "0b1010", "0b1111111"
];
binNumbers.forEach(function (nStr) {
	console.log(nStr+":" + MV(nStr, "hex") + " = "+ parseInt(nStr.substr(2), 2));
});

/*
1234:1234 = 1234 (1234)
12.34:12.34 = 12 (12.34)
115134:115134 = 115134 (115134)
121.354:121.354 = 121 (121.354)
8748:8748 = 8748 (8748)
4845.457:4845.457 = 4845 (4845.457)
10e1:100 = 10 (100)
10e3:10000 = 10 (10000)
10e4:100000 = 10 (100000)
10e6:10000000 = 10 (10000000)
1e7:10000000 = 1 (10000000)
123.123e45:1.23123e+44 = 123 (1.23123e+47)	// <..
10e-1:1 = 10 (1)
10e-3:0.01 = 10 (0.01)
10e-4:0.001 = 10 (0.001)
10e-6:0.000009999999999999999 = 10 (0.00001)	// <..
1e-7:1e-7 = 1 (1e-7)
123.123e-45:1.23123e-45 = 123 (1.23123e-43)			// <..
0o167:119 = 119
0o123:83 = 83
0o366:246 = 246
0o235:157 = 157
0o235:157 = 157
0o252:170 = 170
0xff:255 = 255
0x123abcde:305839326 = 305839326
0xcdefabcd:3455036365 = 3455036365
0x012345:74565 = 74565
0xf:15 = 15
0xa:10 = 10
0x7:7 = 7
0xab:171 = 171
0xfe:254 = 254
0b1:1 = 1
0b11:3 = 3
0b10:2 = 2
0b1111:15 = 15
0b1001:9 = 9
0b1000:8 = 8
0b1010:10 = 10
0b1111111:127 = 127
*/
/*
    Trying to work with Unicode characters
    
    in the /discover corner of syntax.js

*/

PLALR(k) Hilfsklassen

Wer hat die neue ECMA-262 Edition 6 Grammatik seit letztem Oktober schonmal gelesen? Es sind PLALR(k) "Produktions-Parameter" hinzugekommen. Damit wird auf Parametersatz die Grammatik um Produktionen erweitert oder beschränkt und in Abhängigkeit auf andere Produktionen können diese Parameter auch geschaltet werden. In 5.13 Notational Conventions findet man was darüber und eine JavaScript Version in syntax.js (welche ebenfalls noch in Entwicklung ist) und ein Docfile zu 5.13 und der JavaScript Version im /docs Verzeichnis von "syntax".


/*
true
false
true
false
true
null
*/

import AST.JavaScriptProductionParameters;
/**
 * Created by root on 24.02.14.
 */
public class ParameterTest {

    public static void main(String args[])  {
        JavaScriptProductionParameters testParms = new JavaScriptProductionParameters();

	// simuliere drei "Funktionen" nested und immer ist der Parameter anders.
        testParms.newScopeForParameter(testParms.DEFAULT, true);
        System.out.println(testParms.getParameter(testParms.DEFAULT));
        testParms.newScopeForParameter(testParms.DEFAULT, false);
        System.out.println(testParms.getParameter(testParms.DEFAULT));
        testParms.newScopeForParameter(testParms.DEFAULT, true);
        System.out.println(testParms.getParameter(testParms.DEFAULT));

	// und aus den drei Funktionen raus
        testParms.oldScopeForParameter(testParms.DEFAULT);
        System.out.println(testParms.getParameter(testParms.DEFAULT));
        testParms.oldScopeForParameter(testParms.DEFAULT);
        System.out.println(testParms.getParameter(testParms.DEFAULT));
        testParms.oldScopeForParameter(testParms.DEFAULT);
        System.out.println(testParms.getParameter(testParms.DEFAULT));

	// ist kein Parameter mehr auf dem Stack kommt sogar null raus
	// ohne Exception. Ist doch wunderbar.
    }
}

/* Diese Klasse managed die PLALR(k) Parameter wie StatementList[?Return] etc. */

/* Fakt ist, dass ich es nochmal schreib und eine fixe Variante bevorzugen werde,
damit ich das getStack() aus der HashMap wieder entfernen kann. Und dafür wahrscheinlich
für jeden Parameter gleich mehrere Methoden definiere. Noch habe ich kein Muster dafuer.
Aber die mache ich ja auch erst seit...tada...GESTERN.
 */

package AST;

/**
 * Created by root on 24.02.14.
 */
 
public class JavaScriptProductionParameters extends GenericProductionParameters {

    public static final String
            DEFAULT = "Default",
            IN = "In",
            GENERATORPARAMETER = "GeneratorParameter",
            NOREFERENCE = "NoReference",
            RETURN = "Return",
            YIELD = "Yield";

    public JavaScriptProductionParameters() {
        addParameterName("Default");
        addParameterName("In");
        addParameterName("GeneratorParameter");
        addParameterName("NoReference");
        addParameterName("Return");
        addParameterName("Yield");
    }

}

/* Diese hier ist sehr generisch, dass man x-beliebige PLALR(k) mit unterstützen kann. */

package AST;

import Types.List;
import Types.Stack;
import java.util.HashMap;
import java.util.HashSet;

/**
 * Created by root on 24.02.14.
 */

public class GenericProductionParameters {

    private final HashMap< String, Stack< Boolean>> stacks = new HashMap<>();
    private final HashSet< String> availableParameters = new HashSet<>();

    public void addParameterName(String name) {
        if (!availableParameters.contains(name)) {
            Stack< Boolean> stack = new Stack< Boolean>();
            stacks.put(name, stack);
            availableParameters.add(name);
        }
    }
    public boolean hasParameterName(String name) {
        return availableParameters.contains(name);
    }

    public void newScopeForParameter(String name, Boolean value) {
        Stack< Boolean> stack = stacks.get(name);
        if (stack != null) {
            stack.push(value);
        }
    }

    public void oldScopeForParameter(String name) {
         Stack< Boolean> stack = stacks.get(name);
        if (stack != null) {
            stack.pop();
        }
    }

    public Boolean getParameter(String name) {
        Stack< Boolean> stack = stacks.get(name);
        if (stack != null) {
            return stack.top();
        }
        return null;
    }
}

Mit dieser Facility hier kann man die Parameter entsprechend dem Wunsch sie zu setzen oder zu entfernen halt mit einem Stack schalten. Ich werde das aber nochmal hardcoden, und einen Stack als einzelnes Attribut (so wie ich die Design Patterns probiere halt mit has-a-ParameterStack-names-ParameterName) als eigene Variable mit eigenen Methoden FIXCODEN. Diese Demonstration ist rein generisch und macht durch die Stacks jedes Nesting mit, dafür ruft man newScope und oldScope. Man kann den aktuellen Scope mit getParameter auslesen. Mehr braucht man nicht, um auf Parameter dann Produktionen hinzuzurufen (wenn getParameter("Return") dann probiere ausserdem methode xxxxReturn() usw, geht das praktisch).

Einwurf... Generische Methoden

Spezial Syntax für template Methoden. Hier steht der Template-Typ vor dem RueckgabeTyp der Methode. Das kann allerdings rekursiv mit T definiert werden. Allerdings scheint man nicht < String>getMe(str) schreiben zu koennen, sondern muss gen_meth.< String>getMe(str) schreiben.

 
class gen_meth {

	static < T> T getMe(T v) {
	    return v;
	}
	public static void main(String args[]) {
	    System.out.println(gen_meth.< String>getMe("what else"));
	}
}
/*
what else
*/

Liste mit Set als Decorator (has-a list)

Da die Override Geschichte fast das gleiche ist, habe ich die super Relation dann noch in ein has-a verwandelt.

/*
Edward
Gerhold
java.lang.Exception: Edward is a duplicate in the set
        at Types.DupeFreeListDecorator.pushFront(DupeFreeListDecorator.java:15)
        at NoDupesDec.main(NoDupesDec.java:16)
Edward
Gerhold
java.lang.Exception: Edward is a duplicate in the set
        at Types.DupeFreeListDecorator.pushFront(DupeFreeListDecorator.java:15)
        at NoDupesDec.main(NoDupesDec.java:23)
*/

import Types.List;
import Types.DupeFreeListDecorator;

/**
 * Created by root on 23.02.14.
 */
public class NoDupesDec {
    public static void main(String args[]) {
	List< String> list = new List<>();
        DupeFreeListDecorator< String> noDupes = new DupeFreeListDecorator<>(list);
        try {
            noDupes.pushFront("Edward");
            noDupes.pushBack("Gerhold");
            List.ListIterator it = list.iterator(); // 1. achtung: der decorator implementier den iterator nicht?
            while (it.hasNext()) System.out.println(it.next());
            noDupes.pushFront("Edward");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            List.ListIterator it = noDupes.iterator();	// 2. doch ich habe ihn nach lesen der oberen zeile addiert.
            while (it.hasNext()) System.out.println(it.next());
            noDupes.pushFront("Edward");
        } catch (Exception ex) {
            ex.printStackTrace();
        }						// 3. nachdem ich ihn für diesen Test erst wieder entfernte
    }							// 4. nachdem der erste kompilierfehler der fehlende iterator war.
}


package Types;
import java.util.HashSet;

public class DupeFreeListDecorator< T> {

    protected final List< T> list;
    protected final HashSet< T> set = new HashSet< T>();
    protected final String DUPE_MSG = " is a duplicate in the set"; // hier fehlen i18n Patterns!!! Vorher!!!

    public DupeFreeListDecorator(List< T> list) {
	this.list = list;
    }

    public void pushFront(T v)  throws Exception { 
        if (set.contains(v)) throw new Exception(v+DUPE_MSG);
        list.pushFront(v);
        set.add(v);
    }

    public void pushBack(T v)  throws Exception { 
        if (set.contains(v)) throw new Exception(v+DUPE_MSG);
        list.pushBack(v);
        set.add(v);
    }

    public T popBack() throws Exception { // PLUS: hier muss ich in LIST aber keine Exception addieren
        T value = list.popBack();
        if (value != null) set.remove(value);
        return value;
    }
    public T popFront() throws Exception { // PLUS: is-a saved my life bezueglich nachzuholender "throws" decl.
        T value = list.popFront();
        if (value != null) set.remove(value);
        return value;
    }

    public List.ListIterator iterator() { // Der sollte dabei sein, weil man den von der Liste verlangt.
	return list.iterator();
    }
    
}

/* Hier waere die Liste als "interface" vorentworfen quasi optimal als fixe Struktur fuer beide zu nutzen um sich die Methoden zu merken..Sprich kombiniere.  */

Liste mit Set (kurz davor)

/*
Edward
Gerhold
Edward
Gerhold
java.lang.Exception: Edward is a duplicate in the set
        at Types.DupeFreeList.pushFront(DupeFreeList.java:11)
        at NoDupes.main(NoDupes.java:15)
java.lang.Exception: Edward is a duplicate in the set
        at Types.DupeFreeList.pushFront(DupeFreeList.java:11)
        at NoDupes.main(NoDupes.java:22)
        
*/
	
import Types.List;	// no go but de.linuxswt.types, aber ein anderes mal. 
import Types.DupeFreeList;

/**
 * Created by root on 23.02.14.
 */
public class NoDupes {
    public static void main(String args[]) {
        DupeFreeList< String> noDupes = new DupeFreeList<>();
        try {
            noDupes.pushFront("Edward");
            noDupes.pushBack("Gerhold");
            List.ListIterator it = noDupes.iterator();
            while (it.hasNext()) System.out.println(it.next());
            noDupes.pushFront("Edward");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            List.ListIterator it = noDupes.iterator();
            while (it.hasNext()) System.out.println(it.next());
            noDupes.pushFront("Edward");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

package Types;
import java.util.HashSet;

public class DupeFreeList< T> extends List< T> {

    protected final HashSet< T> set = new HashSet< T>();
    protected final String DUPE_MSG = " is a duplicate in the set"; // hier fehlen i18n Patterns!!! Vorher!!!

    @Override
    public void pushFront(T v) throws Exception { // issue kein Override ohne in der List throws zu deklarieren.
        if (set.contains(v)) throw new Exception(v+DUPE_MSG);
        super.pushFront(v);
        set.add(v);
    }
    @Override
    public void pushBack(T v) throws Exception { // man koennte jetzt null returnen lassen, aber ich wollte die Static Semantics von JS impl., darum no way!
        if (set.contains(v)) throw new Exception(v+DUPE_MSG);
        super.pushBack(v);
        set.add(v);
    }
    @Override
    public T popBack() throws Exception {			// ich hab dann throws Exception zur Liste hinzugefuegt
        T value = super.popBack();
        if (value != null) set.remove(value);
        return value;
    }
    @Override
    public T popFront() throws Exception {			// ich denke hier sollte ich konsistent bleiben
        T value = super.popFront();
        if (value != null) set.remove(value);
        return value;
    }

}

Has-a ist besser als Is-a

Ist ein Argument in dem Buch, was ich mir haette laengst vornehmen sollen. Irgendwie ist es aber nicht so oft gefallen.

Bedeutet nicht, dass es immer so ist. Aber die Faelle, wann, interessieren mich.

Head First Decorator

Jau, das Buch macht Spass. Gerade war der Observer dran. Diesmal noch war ich zu faul, weil ich den wohl als einziges Pattern wirklich bereits ausgelutscht habe, von EventTarget bis EventEmitter. Aber das hier habe ich noch nie gemacht. Die Methode einer "wrapped" Property aufgerufen, die den gleichen Namen hat, wie meine Methode.

Man die uebrigens auch ohne extends Beziehung Programmieren, dann muss man die gewrappten Typen aber explizit speichern. Ein SubjectDecorator nimmt dann aussschliesslich ein Subject und ein SubjectDecoratorDecorator nur einen SubjectDecorator. Das habe ich gleich mit ausprobiert.

public class decorator_pattern {
	
	static class Subject {
	    public String method () {
		return "Was mich umgibt 'dekoriert' mich symbolisch. Es koennten Berechnungen sein. Oder Features.";
	    }
	}

	static class SubjectDecorator extends Subject {
	    private Subject wrapped;
	    SubjectDecorator(Subject w) { wrapped = w; }
	    public String method() {
		return "[[[" + wrapped.method() + "]]]";
	    }
	}

	static class SubjectDecoratorDecorator extends Subject {
	    private Subject wrapped;
	    SubjectDecoratorDecorator(Subject w) { wrapped = w; }
	    public String method() {
		return "***" + wrapped.method() + "***";
	    }
	}
	
	public static void main(String args[]) {
		Subject s = new Subject();
		SubjectDecorator sd = new SubjectDecorator(s);
		SubjectDecoratorDecorator sdd = new SubjectDecoratorDecorator(sd);
		// Durch die extends Beziehung ist die Ãœbergabe am Konstruktor besonders dynamisch
		SubjectDecorator invers = new SubjectDecorator(new SubjectDecoratorDecorator(new Subject())); 
		
		System.out.println(sdd.method());
		System.out.println(sd.method());
		System.out.println(s.method());
		System.out.println(invers.method());
	}

}

/*
***[[[Was mich umgibt 'dekoriert' mich symbolisch. Es koennten Berechnungen sein. Oder Features.]]]***
[[[Was mich umgibt 'dekoriert' mich symbolisch. Es koennten Berechnungen sein. Oder Features.]]]
Was mich umgibt 'dekoriert' mich symbolisch. Es koennten Berechnungen sein. Oder Features.
[[[***Was mich umgibt 'dekoriert' mich symbolisch. Es koennten Berechnungen sein. Oder Features.***]]]
*/

Habe isch ihn je schonmal ´enutzt? Aber noch nie versucht auch nur zu verstehen. Hier habe ich mir gerade einen Teil des Kapitels durchgelesen und meine ersten zwei primitiven konkreten Dekoratoren implementiert. Sie wrappen den String, den method() ausgibt.

Head First Design Patterns

Erich Gamma et al. verfolge ich mit Spannung.

Gerade habe ich nach "Head First Design Patterns" gesucht. Und das scheint es direkt bei den Autoren zu geben. Und das Cover kenn ich! Wieder 654 Seiten. Na, dann wollen wir mal lernen.

Das Buch wird in den Vorlesungen vom KIT empfohlen.

class behaviour_patterns {

/* Das Objekt */

public static abstract class TypeWithBehaviour< T> {
    protected T value;
    protected TypeBehaviour behave;
    public String toString () {
	return behave.toString(value);
    }
    public Double toNumber () {
	return behave.toNumber(value);
    }
}

public static class JSString extends TypeWithBehaviour< String> {
    JSString(String v) { value=v; behave = stringBehaviour; }
}
public static class JSNumber extends TypeWithBehaviour< Double> {
    JSNumber(Double v) { value=v; behave = numberBehaviour; }
}

/* Und das Verhaltensmuster */

public static interface TypeBehaviour< T> {
    public Double toNumber(T value);
    public String toString(T value);
}
public static class StringBehaviour implements TypeBehaviour< String> {
    public Double toNumber(String value) {
	return Double.valueOf(value);
    }
    public String toString(String value) {
	return value;
    }
}
public static class NumberBehaviour implements TypeBehaviour< Double> {
    public Double toNumber(Double value) {
	return value;
    }
    public String toString(Double value) {
	return value.toString();
    }
}

public final static NumberBehaviour numberBehaviour = new NumberBehaviour();
public final static StringBehaviour stringBehaviour = new StringBehaviour();

/**/

	public static void main(String args[]) {
		JSString s = new JSString("Hallo");
		JSNumber n = new JSNumber(123.4567e-3);
		JSString t = new JSString("123");
		System.out.println("Beispiel 1" + (t.toNumber() + n.toNumber()) );
		System.out.println("Beispiel 2" + s);
	}
}
/*
Beispiel 1123.1234567
Beispiel 2Hallo
*/

Es faengt mit einem quackenden Beispiel an. Hier wird das Verhaltensmuster als lose gekoppeltes Verhalten betrachtet. Nicht woertlich... Das Buch muss interessant sein und ich habe das gerade nach dem Ueberfliegen und halb lesen des ersten Kapitels probiert. Das sollte in etwa das gleiche sein, nur ist das Buch natürlich besser im erklaeren.

Java fehlt #define

An dieser Stelle würde ich mir #define wünschen, weil ich dann mit #define MAKE_STRINGLIST_AND_STACK und MAKE_STRINGLIST_AND_STACK(currentBoundNames), etc. alles mit einem Codeschnipsel erzeugen könnte. So muss ich jedes einzeln schreiben, was nicht schlimm ist. Ein JavaScript Parser sollte über diese Speicher verfügen. Listen sind ok, weil ich sie nur durchiteriere. Für Duplikate von Identifiern allerdings ist eine HashMap ein HashSet zu empfehlen. Beziehungsweise: Man kann Hash und Liste kombinieren, indem die Entries gleichzeitig verlinkt werden. Eine enstsprechende Kombi muss ich noch schreiben. Von der ich noch meine eigene schreiben muss, zur Zeit nehme ich die aus java.util, was eine Menge zusätzlichen Ramsch bedeutet.


    /* Update comment: I have a newer, better idea, than this for the same.
      Demos soon.
       */

    public StringList currentBoundNames;
    public Stack< StringList> currentBoundNamesStack;
    public void newBoundNames () {
        currentBoundNamesStack.push(currentBoundNames);
        currentBoundNames = new StringList();
    }
    public void oldBoundNames () {
        currentBoundNames = currentBoundNamesStack.pop();
    }

    public StringList currentLexNames;
    public Stack< StringList> currentLexNamesStack;

    public StringList currentVarNames;
    public Stack< StringList> currentVarNamesStack;

    public ParseNodeList currentVarDecls;
    public Stack currentVarDeclsStack;

    public ParseNodeList currentLexDecls;
    public Stack currentLexDeclsStack;

    public ParseNodeList currentFuncsToInit;
    public Stack currentFuncsToInitStack;

Den Stack braucht man, warum ist wohl klar, wegen der Rekursion beziehungsweise in einer Programmiersprache halt der Klammerung und beliebigen Inhalte um entsprechend der Klammer eingangs neu und ausgangs wieder mit dem richtigen Satz zu arbeiten.

Anders als in Syntax.js kann ich den Mozilla AST hier crunchen wie ich will. Und da sind mir ein paar diffizile Stellen aufgefallen, wo der AST besser direkt an die Grammatik angepasst wäre. Diese LexNames und Decls Geschichte gehört dazu, weil der Mozilla AST gar keine Listen dafür definiert. Beziehungsweise, die .declarations des der VariableDeclaration, die VariableDeclarator[] enthaelt, ist die einzige. Für jedes var und jedes let bekommt man eine eigene. Diese Listen hingegen sind blockweit (let) beziehungsweise funktionsweit (var). ECMA 262 Edition 6 verlangt diese eindeutig mit den Static Semantics für VarScopedDeclarations, VarDeclaredNames, LexicalDeclarations, LexDeclaredNames.

Code Coverage

npm install istanbul -g und istanbul cover syntax0.js gibt ein Ergebnis von ca. 15% Statements. Hier sieht man 34%, weil ich syntax0.js ein wenig Code zum Evaluieren gegeben habe. Wie man sieht, bekommt Istanbul das mit und merkt sich, welche zusaetzlichen Funktionen aufgerufen werden.

=============================== Coverage summary ===============================
Statements   : 34.87% ( 6548/18779 )
Branches     : 17.99% ( 1527/8489 )
Functions    : 25.27% ( 420/1662 )
Lines        : 38.55% ( 6387/16567 )
================================================================================

Sieht schlimm aus, aber wenn man sich die Ergebnisse durchliest, sind saemtliche Funktionen der Standard API nicht gelesen, die Tokenizer Loops sind nicht gecovert, weil zum Beispiel kein Kommentar geparsed wurde, kein TemplateString, kein RegExp. Bei blossem Start des Programms kommen recht niedrige Werte raus. Also um es genau zu untersuchen, braucht man auch entsprechende Coverage Tests, die bestimmte Teile abdecken.

Entwurfsmuster

Ich hab das Buch schonmal ausgegraben. Zwar elektronisch, aber früher konnte ich mir das Gamma nicht leisten. Ich hatte so schon Krach wegen den Büchern, weil man sich bei uns zuhause (abgesehen von mir) nicht bildete.

Interessant sind die Beispiele wie "Visitor" oder "Interpreter", die sich mit Compilern beschäftigen. Aber das sind nicht die einzigen. Ich werde das Buch jetzt erstmal auseinandernehmen, die Muster genau durchgehen, weil ich mittlerweile viel Code geschrieben habe, daß ich mir einen Reim drauf machen kann.

JavaScript und Java

"...haben nichts gemeinsam ausser der gemieteten Marke."

Wer´s glaubt wird selig. Die Ähnlichkeiten im Design sind unübersehbar und auch in den nachfolgenden Jahren ist Java ebenfalls weiter mit eingeflossen. Ob Namen von Methoden oder gar ganze Konzepte. Selbst die Art, wie die lexikalische Grammatik geschrieben ist, ist sehr auffällig verwandt. Offiziel ist Java ja auch eine der Wurzeln. Aber so genau wie jetzt, habe ich natürlich noch nicht drauf geachtet, oder es bemerkt.

Lambda Expressions

Wie auch immer. Zeit fürs JDK8, ich hab seit ein paar Tagen echt Spaß damit. Mit den Lambda Expressions kann man Callback interfaces kürzer schreiben als nur mit anonymen Klassen.

public class lambda_expressions {
    public static void main(String args[]) {
	    Runnable r = () -> { System.out.println("Hallo Leute"); };
	    // new Thread(r);
	    // ... 
	    quitButton.addActionListener((e) -> { /* Event Code */ });
	    //...
	    //
    }
}

Und das ist noch nichtmal das, was mich daran so anmacht. Die Idee, einen ClassWriter zu verwenden um JVM Code zu generieren, der JavaScript darstellt. Das schon eher.

Heiliger Geburtstag - im Wohnheim hat´s gebrannt!

Da wollte ich gerade Pünktchen und Racker abholen, um mit ihnen zum Tierarzt zu fahren, da komm ich um die Ecke, die Zeit war schon knapp, weil wir um 11:12 einen Regionalzug nach Teltow nehmen müssten, da steht da Feuerwehr, Krankenwagen, Reporter aus dem Radio und dem Fernsehen, das Haus ist gesperrt.

Es hat gebrannt - bei unseren Nachbarn - die sind in den Nachrichten.

Ich war nach Absprache mit der Feuerwehr in der Wohnung und habe nach Pünktchen und Racker gucken können. Gottseidank ist die Wohnung vom Rauch nicht betroffen gewesen, obwohl wir, nur durch Gang und Tür getrennt gegenüber wohnen.

Mir tut das echt leid für die Leute. Habe nicht schlecht gestaunt und meine Freundin ist aus den Latschen gekippt als ich sie von der Haltestelle abholte, wo sie kurz warten wollte, bis ich mit den Kaninchen wieder da bin.

Qual der Wahl - UML

IntelliJ IDEA hat keine UML Unterstützung in der Community Edition. IDEA und Eclipse Projekte sind nicht kompatibel. Einen direkten Import gibt es nicht. Zumindest habe ich bereits beide IDEs auf ein Verzeichnis gelinkt. Dennoch möchte ich, wo ich Java verwende auch auf UML zugreifen. ArgoUML und IDEA haben Plugins (ArgoUML habe ich 2004 bereits benutzt), im ersten Anlauf versagte es aber. Eine gute Umgebung muss aber sein, denn ich habe keine Lust diesmal die Docs nach der Programmierung zu schreiben, wie ich gerade bei syntax.js muß. Ich habe nicht nur alle Regeln der Softwaretechnik ignoriert, auch wenn empirisch klar ist, daß ich mir so einen Extraaufwand mache. Ich habe mir dieses auch gleich mit bewiesen.

Design Patterns

Bei der Vorbereitung und dem heraussuchen einiger Tutorials zu Entwurfsmustern stolpere ich über gemeinsame Trampelpfade. Im Syntaxhighlighter Teil von Syntax.js wird die GUI praktisch nach dem "Builder" Pattern gebaut. Nur nicht konkret in der Absicht. Aber so wie es erbauer.baueTuer(), erbauer.baueWand(), etc. gibt, gibt es createEvalButton(), createASTButton(), createTokenStatsButton(), etc. Aber genau so, wie der Erbauer funktioniert. Hier gibt es in Zukunft was zu überarbeiten. Danach verstehe ich auch Addy Osmanis Essential JavaScript Design Patterns anders, nicht, wie ich sie vor zwei Jahren mal überflog und eigentlich mal pauken wollte.

FIRST(x)

Wie generiert man eigentlich FIRST Sets für einen LL(1) Parser? Um eine FIRST("Expression") Menge zu generieren, nimmt man die Grammatik für Expression und addiert alle Terminals (sprich Atome) mit denen eine Expression je anfangen kann. Allen Non-Terminals, die weiter in ihre Atome zerfallen generiert man genau so ihre FIRST Sets. Nur fügt man all deren startende Terminals, mit denen die Non-Terminals beginnen auch in das uebergeordnete FIRST Set der Expression (die Terminals "bubblen up"). So einfach generiert man FIRST Sets aus einer Grammatik. Damit kann man (der Automat) dann leicht und in konstanter Zeit (sprich O(1)) determinieren, wie die Expression zu parsen ist.

Aus Gruner, Jacobs "Parsing Techniques" (gedruckt), vorhin im Bus gelesen.

Type.of(Value v)

Ah, ich glaube so geht das ganz gut..Hier ist die offizielle Grundversion der Operation Type(V) aus der ECMA-262 Spezifikation für meine Java Bibliothek. So einfach ist das in Wirklichkeit. Mit Type.of(x) verbringen wir die meiste Zeit im Interpreter, beim verifizieren. In praktisch jeder Funktion wird damit bestimmt. In asm.js wird dieses Laufzeit-Typen-Geteste wieder abgeschaltet, womit man in etwa die gleiche Performance hat, wie ein streng getyptes Programm.

package Types;
public enum ESType {
    Boolean,
    Null,
    Number,
    Object,
    Reference,
    Symbol,
    String,
    Undefined
}

package Types;
/**
 * Created by root on 19.02.14.
 */
public class Type {
    public static ESType of(Value v) {
        if (v instanceof CompletionRecord)
            return Type.of(((CompletionRecord)v).getValue());
        if (v instanceof StringType)
            return ESType.String;
        if (v instanceof NumberType)
            return ESType.Number;
        if (v instanceof ReferenceType)
            return ESType.Reference;
        if (v instanceof SymbolType)
            return ESType.Symbol;
        if (v instanceof ObjectType)
            return ESType.Object;
        if (v instanceof UndefinedType)
            return ESType.Undefined;
        if (v instanceof NullType)
            return ESType.Null;
        if (v instanceof BooleanType)
    	    return ESType.Boolean;
        return null;
        
    }
}

/* 

    //Oder schneller. Was ich bevorzugen werde.
    //Dazu hat jeder Typ eine final type Property, die
    //bei der Konstruktion des Objekts gesetzt wird.
    

public class Type {
    public static ESType of(Value v) {
	if (v instanceof CompletionRecord) 
            return Type.of(((CompletionRecord)v).value);
        return v.type;
    }
}

*/



/**
 * Created by root on 19.02.14.
 */
import Types.Value;
import Types.ESType;
import Types.Type;
import Types.OrdinaryObject;
import Types.OrdinaryFunction;
import Types.StringType;
import Types.SymbolType;
import Types.UndefinedType;
import Types.NullType;
import Types.NumberType;
import Types.ReferenceType;
import Types.BooleanType;

public class TypeOfTest {


    public static void test(Value v) {

        ESType t = Type.of(v);

        switch(t) {
            case Object: System.out.println("Das ist ein Object"); break;
            case Null:System.out.println("Das ist Null"); break;
            case Undefined:System.out.println("Das ist Undefined"); break;
            case String:System.out.println("Das ist ein String"); break;
            case Number:System.out.println("Das ist eine Number"); break;
            case Symbol:System.out.println("Das ist ein Symbol"); break;
            case Reference:System.out.println("Das ist eine Reference "); break;
            case Boolean:System.out.println("Das ist ein Boolean"); break;
        }
    }

    public static void main (String args[]) {
        OrdinaryObject o    = new OrdinaryObject();
        OrdinaryFunction f  = new OrdinaryFunction();
        NumberType n        = new NumberType(200.1005);
        StringType s        = new StringType("Hallo Welt");
        ReferenceType r     = new ReferenceType("num", n, true);
        SymbolType y        = new SymbolType("@@create");
        NullType nu         = NullType.getInstance();
        UndefinedType un    = UndefinedType.getInstance();
        BooleanType b       = new BooleanType(true);
        test(o);
        test(f);
        test(n);
        test(s);
        test(r);
        test(y);
        test(nu);
        test(un);
        test(b);
    }
}

/*
Das ist ein Object
Das ist ein Object
Das ist eine Number
Das ist ein String
Das ist eine Reference 
Das ist ein Symbol
Das ist Null
Das ist Undefined
Das ist ein Boolean
*/

JavaScript to Java ByteCode Compiler

Darf ich wohl erstmal lernen mit ASM umzugehen - wenn ich das richtig gelesen habe, nutzt nashorn den auch. Wenn ich das ausserdem gelesen habe ist er lightweight und fast, und muss nicht erst komplette Objekthierarchien aufbauen, obwohl es das Visitor Pattern einsetzt. Wie auch immer. 154 Seiten Anleitung warten auf mich.

A Java bytecode engineering library AST 4.2 Guide

asm.ow2.org

Java Script JavaScript Interfaces...

Aha, es gibt also JSR-000223 und javax.script.* enthaelt eine Rhino Version in JDK6 und Nashorn im JDK8. Mit jjs kann ich eine Shell starten, die bei "this" "[object Global]" ausgibt, statt das ganze globale Objekt intern anzuzeigen, wie der debugging Stand bei syntax.js ist.

import java.io.*;
import javax.script.*;
class scr {
	public static void main(String args[]) {
	    try {
    		// Hier muss ich mich also anmelden
		ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
		for (String arg: args) {
		// Mit Bindings kann man Variablen im Scope belegen
		    Bindings bindings = new SimpleBindings();
		    bindings.put("test1", 123.435);
		    bindings.put("test2", "Das soll also rocken!");
		    FileReader fr = new FileReader(arg);
		// Das Compilable Interface ist für den Compiler
		    if (engine instanceof Compilable) {
			System.out.println("compiling...");
			Compilable compEngine = (Compilable)engine;
			CompiledScript cs = compEngine.compile(fr);
			cs.eval(bindings);
		    } else {
		// Hier wird die IR interpretiert, oder was auch immer
    			engine.eval(fr, bindings);
		    }
		}
		// Es kann natürlich wieder was passieren.. darum
	    } catch (IOException ex) {
		ex.printStackTrace();
	    } catch (ScriptException ex) {
		ex.printStackTrace();
	    }
	}
}


/*

 scr.js (nutzt nashorns print function)
 
*/

print(test1);
print(test2);

/*
 Ergebnis:
 
linux-dww5:~ # javac scr.java 
linux-dww5:~ # java scr scr.js 
compiling...
123.435
Das soll also rocken!
*/


/* exc.js */

throw "Das ist eine Exception";

/*
Ergebnis

javax.script.ScriptException: Das ist eine Exception in  at line number 1 at column number 0
        at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:562)
        at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:546)
        at jdk.nashorn.api.scripting.NashornScriptEngine.access$200(NashornScriptEngine.java:77)
        at jdk.nashorn.api.scripting.NashornScriptEngine$4.eval(NashornScriptEngine.java:585)
        at javax.script.CompiledScript.eval(CompiledScript.java:92)
        at scr.main(scr.java:19)
Caused by: :1:0 Das ist eine Exception
        at jdk.nashorn.internal.scripts.Script$\^eval\_.runScript(:1)
        at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:498)
        at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:207)
        at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:378)
        at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:544)
        ... 4 more

*/

Nach ersten Überlegungen, was es genau ist, weiss ich es durch Lesen der ersten vierzig Seiten und blättern der restlichen Spezifikation dazu. Es ist eine allgemeine Schnittstelle für Scripts in Java und ausserdem für Java Objekte in diesen Scripts. Zum Beispiel var str = new java.lang.String("fun");

JSR-000223 Scripting for the Java™ Platform (Final Release)

Design Patterns (Entwurfsmuster)

Was Algorithmen für das Programmieren im Kleinen sind, sind Entwurfsmuster für das Programmieren im Großen (neben der vollständigen bis teilweisen Automatisierung durch moderne Entwicklungswerkzeuge).

Ein Jahr, nachdem ich mit CS61B Data Structures & Algorithms (2006), MIT 6.006 Introduction to Algorithms (2011) und MIT 6.046j Design and Analyisis of Algorithms (2005) begann, beginne ich dann das nächste Kapitel mit den Entwurfsmustern. Einige davon sind mir ein alltäglicher Begriff, wie Observer, Adapter, Iterator oder Singleton. Ein paar andere kenne ich aber noch gar nicht. Ich bin ja kein Programmierer. Ich hatte Rabeneltern.

get-a-get-a-get-a-get

Gag am Rande. Liest sich witzig.

    public Value getGet() {
    public void setGet(Value get) {
    public Value getSet() {
    public void setSet(Value set) {

Aus dem AccessorDescriptor.

Besonders gut finde ich an den JavaIDEs (und der Softwaretechnik Disziplin) die automatische Generierung und Refaktorierung von Code. Was ich in Syntax.JS mühsam mit dem Texteditor (mcedit) auf 933 MHz von Hand wiederholt habe, geht mir hier locker durch Maschine voran von der Hand, so daß ich mich auf die schwierigeren Sachen konzentrieren kann..

Sichtbarkeit: private

Wenn man zwei Instanzen der gleichen Klasse hat, können die gegenseitig auf ihre private Felder zugreifen. Private ist also nicht privat für die Instanz einer Klasse. Sondern privat für die ganze Klasse. Was bedeutet, dass innerhalb der Klassendefinition andere Objekte der selben Klasse ohne getter und setter ihre private Attribute zur Verfügung stellen. Bislang hatte ich private für die ultimative Instanzkapselung gehalten, dass nur die instanz selbst drauf zugreift und protected halt auch die Unterklasse. Die Sichtbarkeit ist aber Klassenspezifisch, und heisst, wenn ich str2 an str1 uebergebe und beide vom Typ Str sind, kann ich in "str1" auf "str2".private zugreifen, ohne Getter und Setter.

Top!

/*
     Ich-mich-selbst-Update zum Thema Sichtbarkeit:
     
     Sichtbarkeit innerhalb einer Klasse bedeutet auch, auf die
     privaten Felder einer anderen Instanz der selben Klasse zugreifen
     zu können.
     
     Bisherige Vermutung:
     Instanzen einer Klasse sind für sich gekapselt und private bedeutet,
     dass nur die Instanz darauf zugreift.
     
     Resultat:
     Widerlegt, kann str1 den str2 als Parameter übergeben und innerhalb
     der Funktion auf "private" Felder zugreifen.

*/
class Friends {
	
	public static class Str {

	    private int len;
	    private char [] value;

	    public Str() {
		this.len = 0;
		this.value = new char[len];
	    }
	    
	/* Das ganze Beispiel ist für diese Funktion geschrieben worden:
	    Hier greife ich auf das private Feld eines anderen Objekts
	    (der selben Klasse) ohne Getter und Setter zu.
	    
								    */
	    public Str(Str original) {
		this.value = original.value;
		this.len = original.len;
	    }
	    
    /* Bislang dachte ich, str1.value ist private für str1 und str2.value
    für str2 und have das noch nie ausprobiert.

    Ist mir beim Lesen der String Impl aufgefallen und musste ausprobiert
    werden. */


	    public Str(String s) {
		len = s.length();
		this.value = new char[len];
		for (int i = 0; i < len; i++) {
    		    this.value[i] = s.charAt(i);
		}
	    }
	    public int length() {
		return len;
	    }
	    
	    public String toString () {
		// ist jetzt egal ob das anders geht...
		return new String(this.value);
		// finde ich bis demnächst heraus...
	    }
	}
	
	public static void main(String args[]) {
	    Str str1 = new Str("abcdefg");
	    Str str2 = new Str("hijklmnopqrstuvwxyz");	    
	    System.out.println(str1);
	    System.out.println(str2);
	    
	    // Das Ding warum ich das Beispiel schrieb ist folgendes:
	    // len und value sind private
	    // Aber in java.lang.String greift String1 auf String2 zu,
	    // und zwar auf die privaten Felder.
	    
	    Str str3 = new Str(str1);
	    Str str4 = new Str(str2);
	    System.out.println(str3);
	    System.out.println(str4);
	    
	    // Fakt ist, das war schon immer so. Und meine Beschränkung
	    // auf die Instanz zu beschränkt
	    // Ich glaube nichtmal, dass ich der einzige bin, der das
	    // falsch verstand. Jedenfalls von den Selbstlernern.
	    
	}
}

/*

abcdefg
hijklmnopqrstuvwxyz
abcdefg
hijklmnopqrstuvwxyz

*/

(Edward korrigiert hier gerade einen fünfzehnjährigen Irrtum)

Static Type System

Ein wenig am Hampeln bin ich noch, aber dank des geschulten Auges, das Casting zu erkennen, habe ich ein wunderbares Typsystem implementieren können, was ich ausbauen werde. Es gibt jetzt NumberType, StringType, ReferenceType, CompletionRecord, SymbolType, NullType, UndefinedType, ObjectType, die alle von Value abstammen. Man könnte auch "Object" lassen, aber ich finde den Parametertyp und Returntyp "Value" leserlicher. Wer weiss, vielleicht lass ich mich noch zu Object hinreissen. Jedenfalls habe ich bereits toNumber und ToString (man kann Completion Records übergeben, die wenden toNumber und toString dann auf ihre Value an) ausprobiert. Ich werde mich jetzt ans eingemachte machen, die API so erstmal auf die Beine zu stellen.

/**
 * Created by root on 17.02.14.
 */
import Types.*;
import api.Conversions;

public class TestValue {
    public static void main(String args[]) {

        NumberType number;
        StringType string;
        CompletionRecord cpl1;
        CompletionRecord cpl2;
        ReferenceType ref;
        OrdinaryObject o;

        // Cpl(Ref(Obj)))
        o = new OrdinaryObject();
        o.setInternalSlot("Extensible", false);
        ref = new ReferenceType("propName", o, false);
        cpl1 = new CompletionRecord(CompletionRecord.RETURN, ref, CompletionRecord.EMPTY);
        if (cpl1.getValue() instanceof ReferenceType) {
            ReferenceType ref2 = (ReferenceType)cpl1.getValue();
            if (ref2.getBase() instanceof OrdinaryObject) {
                OrdinaryObject o2 = (OrdinaryObject)ref2.getBase();
                System.out.println("Erfolg in Implementation 1");
            }
        } else {
            System.out.println("Fehler in Implementation 1");
        }


        // Cpl(number) -> ToString
        number = new NumberType(123.456);
        cpl2 = new CompletionRecord(number);
        if (cpl2.getValue() instanceof NumberType) {
            StringType string2 = Conversions.toString(cpl2);
            System.out.println("Converted to String:" + string2.getValue());
        } else {
            System.out.println("Conversion Cpl(number) toString did not work");
        }

    }
}

/*
/opt/jdk1.8.0/bin/java [...] TestValue
Erfolg in Implementation 1
Converted to String:123.456

Process finished with exit code 0


*/

Man sieht eindeutig, dass CompletionRecord eine isNumber, isString, isReference Kombination verdient, aber wenn ich schon dabei bin, geht das an Value() selbst, dass man die auf jeder Value rufen kann, wobei ich wieder am meckern bin, einer kleinen Struktur mehrere Funktionszeiger zu montieren, aber ich denke, das ist es Wert. Und ausserdem sind die anderen Libs mindestens so "klobig" (eher wenig) wie das dann ist.

Ein wenig Performance und Memory Gain

Anstatt das (hier werden jedesmal neue Strings erzeugt)

    if (token.equals("=")) {
    
    }
    
    do {
    
    } while (token.equals(","));
    pass(";");

lieber keine neuen Strings und lieber Konstanten (mit final)

    public static final String ASSIGN = "=";
    if (token.equals(ASSIGN)) {
    
    }
    
    public static final String SEMICOLON = ";";
    do {
    
    } while(token.equals(COMMA));
    pass(SEMICOLON);

Autoboxing

Also Java wandelt wenn notwenig Primitive Typen in Objekte um und nennt das "Boxing", und wandelt Objekte wenn notwendig in Primitive Typen um und nennt das "Unboxing".

    // Beispiel 1: Boxing
    // hier wird automatisch ein Integer Objekt für i, um in die Liste eingefügt zu werden, erzeugt.

    public List<Integer> list;
    for (int i = 0; i < 10; i++) list.pushBack(i);
    
    
    // Beispiel 2: Unboxing
    // hier wird automatisch die .intValue() Methode gerufen, um das Objekt mit sum verrechnen zu können.
    // Das ist in Java pro Typ gelöst, in JavaScript gibt es nur "valueOf" und "toString"
    
    int sum = 0;
    public List<Integer>.ListIterator it = list.iterator();
    while (it.hasNext()) {
	Integer i = it.next();
	sum += i;
    }

In JavaScript wird zum Beispiel eine primitive Zahl in eine Instanz von Number verwandelt, wenn die "ToObject" Methode aufgerufen wird. Da wird ein Objekt erzeugt, mit dem Prototype Number.prototype, zum Beispiel durch obj = Number.[[Construct]]([primitive_num]), dem Objekt der interne Slot obj.[[NumberData]] = primitive_num; gesetzt und das obj zurück gegeben. Das ist das Boxing.

// In der ES-262 VM

function ToObject(V) {
//...
    if (typeof V === "number") {
	return callInternalSlot("Construct", NumberConstructor, [V]);
    }
//..
}

Die [[NumberData]] wird in JavaScript dann beim automatischen Aufruf von "valueOf" extrahiert und zurückgegeben. Damit wird dann praktisch das automatische Unboxing betrieben.

// In der ES-262 VM

es6> Number.prototype.valueOf.toString()
// [[Builtin Function native JavaScript Code]]
function (thisArg, argList) {
                var x = thisNumberValue(thisArg);
                return NormalCompletion(x);
        }
es6> 

Die hier sichtbare Operation thisNumberValue extrahiert die [[NumberData]] property. Bei Number.prototype.valueOf.call(null) kommt zum Beispiel heraus "thisNumberValue: value is not a number".

Up und downcasting wiederholt

Also wie ich bereits festgestellt habe, geht vernünftiges Casting so, dass man den Typen einer Superklasse Opa zum übergeben verwendet. Das übergebe Objekt obj kann irgendein Opa extendendes sein. Das Objekt wird dort, wo es in seiner ursprünglichen Form benötigt wird mit "instanceof" getestet und dann mit RichtigerEnkel re = (RichtigerEnkel)obj; gecastet. Verlustfrei.

Beispiel 2: Verschiedene Objekte verschiedener Klassen aus einer einzigen Funktion zurückgeben:
/**
 * Created by root on 17.02.14.
 */

public class CastingTest2 {

    static class Opa {
        protected int type; // enum nehmen
        public Opa() { this.type = -1; }
        public Opa(int type) { this.type = type; }
    }

    static class A extends Opa {
        public A() { super(0); }
        public void a() {
            System.out.print("a");
        }
    }
    static class B extends Opa {
        public B() { super(1); }
        public void b() {
            System.out.print("b");
        }
    }
    static class C extends Opa {
        public C() { super(2); }
        public void c() {
            System.out.print("c");
        }
    }
    public static Opa test(int index) {
        // gebe superklasse zurück
        // ..würde da A stehen gehen B und C nicht..
        switch (index) {
            case 0: return new A();
            case 1: return new B();
            case 2: return new C();
        }
        return null;
    }
    public static void main(String args[]) {
        // 1.Beispiel:
        // durch das Argument index für test, weiss
        // ich bereits was rauskommt. und caste Opa direkt,
        // wenn er test verlässt.

        A a = (A) test(0);
        B b = (B) test(1);
        C c = (C) test(2);
        // Wäre das durcheinander bzw. falsch gecastet worden
        // z.B. B b = (A)test(2) hätten wir jetzt Fehlermeldungen, dass
        // die eine Methode nicht am anderen Objekt ist

        a.a();
        b.b();
        c.c();


        // Noch ein Beispiel
        // Hier muss ich den Typen erstmal prüfen
        // Der eine Weg geht mit instanceof
        // wie im Kurs Programmieren
        // Alternativ habe ich hier noch eine type Property.

        Opa o; // Hier schreibe ich Opa erstmal auf.

        o = test(0);
        if (o.type == 0 /* o instanceof A */) {
            a = (A)o;
            a.a();
        }

        o = test(1);
        if (o.type == 1 /* o instanceof B */) { 
            b = (B)o;
            b.b();
        }

        o = test(2);
        if (o.type == 2 /* o instanceof C */) { 
            c = (C)o;
            c.c();
        }

        // type kann auch "Number", "Object", "Symbol", "String", "Null", "Undefined" sein.
    }
}

/*

abcabc

*/

Wenn man versucht anderswohin zu casten geht das nicht. Hierbei zum Beispiel habe ich in der Regel den grössten Fehler die ganze Zeit gemacht, mir da nicht sicher zu sein, was ich wohin casten darf. Dabei ist die Regel kinderleicht und übrigens zu 100% gleich der C++ Regel. Auch dort kann man Superklassen übergeben und zurückgeben und den benötigten richtigen Typen durch typeid() oder durch eine type_property in der Superklasse bestimmen.

Neue alte Freunde: null

Nach undefined und null in JavaScript ist nun null mein Freund.

null kann man überall zurückgeben, wo ein Objekt zurueckkommt womit while ((s = Statement()) != null) {} und andere Konstrukte leicht zu arrangieren sind.

Das korrekte Casting zwei Absätze tiefer bringt mich auf die Idee, doch eine Klasse BaseValue zu nutzen. Die InstanceOf Tests kann man auch mit einer Type Property oder einer Set-Zugehörigkeit ersetzen, sofern man 100% sicher sein kann, das richtige Objekt zu casten. Die genannte Methode ist Verlustfrei. Man kann eine Superklasse als Argument erwarten, dann prüft man, was es wirklich für eine Unterklasse ist und castet in die richtige Unterklasse. Kapitel 9 Dynamische Binding / 10 Generics der Lesung "Programmieren" vom KIT hat mir das gezeigt. Ich habe einfach mal aufgepasst und mir die Regel zusammengesetzt.

Und nochmal

Ich komme mir so vor, als dürfte ich das wieder von vorne anfangen. Aber gut so. Um so besser wird das Ding in den kommenden Jahren.

Der Tokenizer ist diesmal übrigens direkt im Parser integriert, und der Parser prozessiert keinen Array, der für den Syntaxhighlighter gedacht war, sondern kriegt die Token direkt aus dem next().

Noch ist die Implementation aber austauschbar, weil ich das korrekte Verarbeiten von Code Points und Unicode an und für sich noch raussuchen muss, dass ich auf die enstprechenden Reader und Builder umstellen kann. Dieses Ding läuft jetzt mit HashSet< String> und Strings, was nicht die intelligenteste oder halt schnellste Lösung ist, aber aufgrund der konstanten Accesses, niedriger Konstante bei linearem Durchlauf, garantiert guten Durchschnitt erreicht.

Nach Lektion 13 von "Programmierung", wo es auch ums Parsen ging, ging bei mir das Javafieber los. Auf einmal kriegte ich Lust es endlich auch zu tun. Jetzt habe ich meinen eigenen Parser wieder nachempfunden. Da habe ich auch eine Weile drüber gebrütet, und gelernt, wie ein RD funktioniert, und wie ein guter RD funktioniert. Darum will ich lieber meinen nehmen.

/opt/jdk1.8.0/bin/java [...] ParserTest
parser: var x = 10; let y = x + x + x;
parser debug, at=null, lookahead=var
parser debug, at=var, lookahead= 
parser debug, at= , lookahead=x
parser debug, at=x, lookahead= 
parser debug, at= , lookahead==
parser debug, at==, lookahead= 
parser debug, at= , lookahead=10
parser debug, at=10, lookahead=;
parser debug, at=;, lookahead= 
parser debug, at= , lookahead=let
parser debug, at=let, lookahead= 
parser debug, at= , lookahead=y
parser debug, at=y, lookahead= 
parser debug, at= , lookahead==
parser debug, at==, lookahead= 
parser debug, at= , lookahead=x
parser debug, at=x, lookahead= 
parser debug, at= , lookahead=+
parser debug, at=+, lookahead= 
parser debug, at= , lookahead=x
parser debug, at=x, lookahead= 
parser debug, at= , lookahead=+
parser debug, at=+, lookahead= 
parser debug, at= , lookahead=x
parser debug, at=x, lookahead=null
parser debug, at=null, lookahead=null
parser: /* comment */ const z = 200;;
parser debug, at=null, lookahead=/* comment */
parser debug, at=/* comment */, lookahead= 
parser debug, at= , lookahead=const
parser debug, at=const, lookahead= 
parser debug, at= , lookahead=z
parser debug, at=z, lookahead= 
parser debug, at= , lookahead==
parser debug, at==, lookahead= 
parser debug, at= , lookahead=200
parser debug, at=200, lookahead=;
parser debug, at=;, lookahead=null
parser debug, at=null, lookahead=null

Process finished with exit code 0
/opt/jdk1.8.0/bin/java [...] TokenizerTest
tokenizer: var x = 10; let y = x + x + x;
[tokenizer]: pos=0, len=30, ch=v, lookahead=a, last token=null
[tokenizer]: pos=3, len=30, ch= , lookahead=x, last token=var
[tokenizer]: pos=4, len=30, ch=x, lookahead= , last token= 
[tokenizer]: pos=5, len=30, ch= , lookahead==, last token=x
[tokenizer]: pos=6, len=30, ch==, lookahead= , last token= 
[tokenizer]: pos=7, len=30, ch= , lookahead=1, last token==
[tokenizer]: pos=8, len=30, ch=1, lookahead=0, last token= 
[tokenizer]: pos=10, len=30, ch=;, lookahead= , last token=10
[tokenizer]: pos=11, len=30, ch= , lookahead=l, last token=;
[tokenizer]: pos=12, len=30, ch=l, lookahead=e, last token= 
[tokenizer]: pos=15, len=30, ch= , lookahead=y, last token=let
[tokenizer]: pos=16, len=30, ch=y, lookahead= , last token= 
[tokenizer]: pos=17, len=30, ch= , lookahead==, last token=y
[tokenizer]: pos=18, len=30, ch==, lookahead= , last token= 
[tokenizer]: pos=19, len=30, ch= , lookahead=x, last token==
[tokenizer]: pos=20, len=30, ch=x, lookahead= , last token= 
[tokenizer]: pos=21, len=30, ch= , lookahead=+, last token=x
[tokenizer]: pos=22, len=30, ch=+, lookahead= , last token= 
[tokenizer]: pos=23, len=30, ch= , lookahead=x, last token=+
[tokenizer]: pos=24, len=30, ch=x, lookahead= , last token= 
[tokenizer]: pos=25, len=30, ch= , lookahead=+, last token=x
[tokenizer]: pos=26, len=30, ch=+, lookahead= , last token= 
[tokenizer]: pos=27, len=30, ch= , lookahead=x, last token=+
[tokenizer]: pos=28, len=30, ch=x, lookahead=null, last token= 
[tokenizer]: pos=29, len=30, ch=null, lookahead=null, last token=x
[tokenizer]: pos=29, len=30, ch=null, lookahead=null, last token=x
tokenizer: /* comment */ const z = 200;;
[tokenizer]: pos=0, len=29, ch=/, lookahead=*, last token=null
[tokenizer]: pos=13, len=29, ch= , lookahead=c, last token=/* comment */
[tokenizer]: pos=14, len=29, ch=c, lookahead=o, last token= 
[tokenizer]: pos=19, len=29, ch= , lookahead=z, last token=const
[tokenizer]: pos=20, len=29, ch=z, lookahead= , last token= 
[tokenizer]: pos=21, len=29, ch= , lookahead==, last token=z
[tokenizer]: pos=22, len=29, ch==, lookahead= , last token= 
[tokenizer]: pos=23, len=29, ch= , lookahead=2, last token==
[tokenizer]: pos=24, len=29, ch=2, lookahead=0, last token= 
[tokenizer]: pos=27, len=29, ch=;, lookahead=null, last token=200
[tokenizer]: pos=28, len=29, ch=null, lookahead=null, last token=;
[tokenizer]: pos=28, len=29, ch=null, lookahead=null, last token=;
Process finished with exit code 0

Java-Casting

So funktionierts richtig, Superklasse übergeben, Typ testen, richtigen auswählen. Umwandeln. Methode aufrufen. Wird nicht abstürzen. Anders unmöglich.

Beispiel 1: Verschiedene Objekte verschiedener Klassen in eine einzige Funktion übergeben:

/**
 * Created by root on 14.02.14.
 */
public class CastingTest {
    static class Opa {
    }
    static class A extends Opa {
        public void a() {
            System.out.print("a");
        }
    }
    static class B extends Opa {
        public void b() {
            System.out.print("b");
        }
    }
    static class C extends Opa {
        public void c() {
            System.out.print("c");
        }
    }
    public static void test(Opa o) {
        if (o instanceof A) {
            A a = (A)o;
            a.a();
        } else if (o instanceof B) {
            B b = (B)o;
            b.b();
        } else if (o instanceof C) {
            C c = (C)o;
            c.c();
        }
    }
    public static void main(String args[]) {
        A a = new A();
        B b = new B();
        C c = new C();
        test(a);
        test(b);
        test(c);
    }
}
/*
abc
Process finished with exit code 0
*/

ByteCode

Wie ich ein paar Absätze weiter erwähne, muss ich noch herausfinden, wie ich die ausführen kann, dann kann die Konvertierung des ASTs auch gleich losgehen. Inzwischen kann ich die schonmal abschreiben und durchlesen.

package JVM;

/**
 * Created by root on 14.02.14.
 */
 
// From http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html JVM Instruction Set

public class JavaOpCodes {
    static final int aaload_0 = 0,
    aaload = 0x32,
    aastore = 0x53,
    aconst_null = 0x1
    
    
    ;
}

Schon mecker ich wegen Dalvik. Dann lese ich bereits von "ART" der Android Runtime. Soviel zur Transformation des Opcodes zur Dalvik VM. Ist wie mit C++. Einmal Assembler für ARM, einmal Assembler für Intel. Einmal Assembler für den ...

Java Bäumchen

Zeit die alte CS61B zu wiederholen, die von 2006. Dran kommt der SibTree. Der hat eine SibTreeNode mit parent, firstChild und nextSibling. Die Funktionen gehören nicht dazu, die habe ich zur Vereinfachung geschrieben, denn es lohnt sich nicht, den zu schreiben, ohne direkt das DOM4 Papier zu nehmen.

/**
 * Created by root on 14.02.14.
 */

public class SibTree {

    public static abstract class SibTreeVisitor {
        public abstract void visit(Object item);
    }

    public static class SibTreeNode {
        Object item;
        public SibTreeNode parent;
        public SibTreeNode nextSibling;
        public SibTreeNode firstChild;
        public SibTreeNode () {
            parent = null;
            nextSibling = null;
            firstChild = null;
        }
    }

    public SibTreeNode root;
    public long size;
    SibTree () {
        root = null;
        size = 0;
    }
    // Eine einfache Funktion zum erzeugen von Nodes
    // Den sogenannten Sibtree werde ich sehr bald mit DOM4 ersetzt haben, darum kurz und knapp.
    public SibTreeNode addChild(SibTreeNode node, Object item) {
        if (node == null) {
            if (root == null) {
                root = new SibTreeNode();
                root.item = item;
                return root;
            }else {
                node = new SibTreeNode();
                node.firstChild = root;
                node.firstChild.parent = node;
                root = node;
                return root;
            }
        }
        if (node.firstChild != null) {
            node = node.firstChild;
            while (node.nextSibling != null) node = node.nextSibling;
            node.nextSibling = new SibTreeNode();
            node.nextSibling.parent = node;
            node.nextSibling.item = item;
            return node.nextSibling;
        } else {
            node.firstChild = new SibTreeNode();
            node.firstChild.parent = node;
            node.firstChild.item = item;
            return node.firstChild;
        }
    }
    // Simple und kostspielige Funktion die in O(n) nach dem item sucht
    public SibTreeNode getNode(Object item, SibTreeNode node) {
        if (node == null) return null;
        if (node.item == item) return node;
        while (node != null) {
            node = getNode(item, node.firstChild);
            if (node != null) return node;
            node = node.nextSibling;
        }
        return null;
    }
    public void inorderTraverse(SibTreeNode node, SibTreeVisitor visitor) {
        visitor.visit(node.item);
        if (node.firstChild != null) inorderTraverse(node.firstChild, visitor);
        if (node.nextSibling != null) inorderTraverse(node.nextSibling, visitor);
    }

    public void inorder(SibTreeVisitor visitor) {
        if (root != null) inorderTraverse(root, visitor);
    }

    public static void main(String[] args) {
        
        SibTree tree = new SibTree();
        SibTree.SibTreeNode root = tree.addChild(null, "root item");
        for (int i = 0; i < 10; i++) {
            SibTree.SibTreeNode child = tree.addChild(root, "child "+i);
            for (int j = 3; j > 0; j--) {
                tree.addChild(child, "grandchild "+j+" is child of "+i);
            }
        }
        tree.inorder(new SibTree.SibTreeVisitor() {
           public void visit(Object item) {
               System.out.println("** "+item);
           }
        });
    }
}
/*
** root item
** child 0
** grandchild 3 is child of 0
** grandchild 2 is child of 0
** grandchild 1 is child of 0
** child 1
** grandchild 3 is child of 1
** grandchild 2 is child of 1
** grandchild 1 is child of 1
** child 2
** grandchild 3 is child of 2
** grandchild 2 is child of 2
** grandchild 1 is child of 2
** child 3
** grandchild 3 is child of 3
** grandchild 2 is child of 3
** grandchild 1 is child of 3
** child 4
** grandchild 3 is child of 4
** grandchild 2 is child of 4
** grandchild 1 is child of 4
** child 5
** grandchild 3 is child of 5
** grandchild 2 is child of 5
** grandchild 1 is child of 5
** child 6
** grandchild 3 is child of 6
** grandchild 2 is child of 6
** grandchild 1 is child of 6
** child 7
** grandchild 3 is child of 7
** grandchild 2 is child of 7
** grandchild 1 is child of 7
** child 8
** grandchild 3 is child of 8
** grandchild 2 is child of 8
** grandchild 1 is child of 8
** child 9
** grandchild 3 is child of 9
** grandchild 2 is child of 9
** grandchild 1 is child of 9

Process finished with exit code 0
*/

VM

Mittlerweile habe ich den ByteCode selbst entdeckt, aber wie ich ihn mit der JVM aufrufen kann, noch nicht. Wenn ich das herausfinde, koennte ich die ES6 API so gestalten, nach dem Syntaxbaum JVM Instruktionen raus zu machen.

Wenn ich herausfinde, wie ich den ByteCode einspeisen kann, fange ich sofort an, die Opcodes abzuschreiben, Instruktionen zu disassemblieren und implementiere die codegen Interfaces für den AST.

class numbers {
    public static void main(String args[]) {
	int i1 = 2_000_000_000;
	int i2 = 2_000_000_000;
	int i3 = i1+i2;
	long l1 = 2_000_000_000;
	long l2 = 2_000_000_000;
	long l3 = l1+l2;
	System.out.println(i3);
	System.out.println(l3);
    }
}Compiled from "numbers.java"

Mit javap -c disassembliert

class numbers {
  numbers();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return        

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // int 2000000000
       2: istore_1      
       3: ldc           #2                  // int 2000000000
       5: istore_2      
       6: iload_1       
       7: iload_2       
       8: iadd          
       9: istore_3      
      10: ldc2_w        #3                  // long 2000000000l
      13: lstore        4
      15: ldc2_w        #3                  // long 2000000000l
      18: lstore        6
      20: lload         4
      22: lload         6
      24: ladd          
      25: lstore        8
      27: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      30: iload_3       
      31: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      34: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      37: lload         8
      39: invokevirtual #7                  // Method java/io/PrintStream.println:(J)V
      42: return        
}

VarArgs in Java

Also mit method(Object ...args) bekomme ich einen Object[] args den ich mit for (Object arg: args) durchlaufen kann. Damit habe ich jetzt eine "native" Funktion erzeugt. Ich kann damit zum Beispiel DefineOwnProperty, GetOwnProperty eines Objects definieren und mit setInternalSlot() im HashTable speichern, anstatt sie als Klassenmethode fest zu definieren. Wobei ich mit "java.lang.reflect" liebaeugel. Ich werd mir jetzt erstmal syntaxmaessig alle Kitlektionen wiederholen um ein paar robuste Grundlagen zu nutzen. Mittlerweile gucke ich ausserdem SWT1. Ich will jetzt sehen, wie ich die UML noch reinkriege, und ein paar Tools zum Generieren und Refaktorieren einsetze, die ich bei syntax.js mit sublime noch nicht im Blick hatte.

package Types;

/**
 * Created by root on 12.02.14.
 */
public abstract class Callback {
    public abstract Object call(Object ...args);
}
package Types;

/**
 * Created by root on 13.02.14.
 */
public class CallbackTest {
    public static Callback callback1;
    public static Callback callback2;
    static {
        callback1 = new Callback() {
            public Object call (Object ...args) {

               for (Object arg: args) {
                    System.out.println("a: "+arg);
               }
                return null;
            }
        };
        callback2 = new Callback() {
            public Object call (Object ...args) {

                for (Object arg : args) {
                    System.out.println("b: "+arg);
                }

                return new Object () {
                    public String toString () {
                        return "[object Edward]";
                    }
                };
            }
        };
    }
    public static void main(String args[]) {
        callback1.call(new Integer(100), new Double(2345.2342), "Ihr seid alle hohl");
        System.out.println(callback2.call(new String("abcde"), 'a', 'z', 12));
    }
}
/*
/opt/jdk1.8.0/bin/java -Didea.launcher.port=7532 -Didea.launcher.bin.path=/opt/idea/bin -Dfile.encoding=UTF-8 -classpath /opt/jdk1.8.0/jre/lib/resources.jar:/opt/jdk1.8.0/jre/lib/jce.jar:/opt/jdk1.8.0/jre/lib/plugin.jar:/opt/jdk1.8.0/jre/lib/javaws.jar:/opt/jdk1.8.0/jre/lib/jfr.jar:/opt/jdk1.8.0/jre/lib/jfxswt.jar:/opt/jdk1.8.0/jre/lib/rt.jar:/opt/jdk1.8.0/jre/lib/deploy.jar:/opt/jdk1.8.0/jre/lib/charsets.jar:/opt/jdk1.8.0/jre/lib/jsse.jar:/opt/jdk1.8.0/jre/lib/management-agent.jar:/opt/jdk1.8.0/jre/lib/ext/sunec.jar:/opt/jdk1.8.0/jre/lib/ext/jfxrt.jar:/opt/jdk1.8.0/jre/lib/ext/localedata.jar:/opt/jdk1.8.0/jre/lib/ext/cldrdata.jar:/opt/jdk1.8.0/jre/lib/ext/sunjce_provider.jar:/opt/jdk1.8.0/jre/lib/ext/dnsns.jar:/opt/jdk1.8.0/jre/lib/ext/nashorn.jar:/opt/jdk1.8.0/jre/lib/ext/sunpkcs11.jar:/opt/jdk1.8.0/jre/lib/ext/zipfs.jar:/es6/out/production/es6:/opt/idea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain Types.CallbackTest
a: 100
a: 2345.2342
a: Ihr seid alle hohl
b: abcde
b: a
b: z
b: 12
[object Edward]

Process finished with exit code 0
*/

Visitor

Hey, die IDE hat das beim Speichern und laden in (key, value) -> Syntax refaktoriert. Nicht schlecht. Die IDE kann überhaupt ganz schön viel.

import Types.BinarySearchTree;
import Types.PriorityQueue;
/**
 * Created by root on 13.02.14.
 */
public class BinarySearchTreeTest {

    public static void main(String args[]) {

        BinarySearchTree< String> tree = new PriorityQueue< String>();

        tree.insert(10, "denkt");
        tree.insert(7, "Eddie");
        tree.insert(20, "geht");
        tree.insert(16, "das");
        tree.insert(21, "?");

        tree.inorder(new BinarySearchTree.BinaryTreeVisitor< String>() {
            public void visit(double key, String value) {
                System.out.println("key="+String.valueOf(key)+" value="+value);
            }
        });
    }
}
/*
key=7.0 value=Eddie
key=10.0 value=denkt
key=16.0 value=das
key=20.0 value=geht
key=21.0 value=?

Process finished with exit code 0
*/

Für die TaskQueues "PromiseTasks" und "LoaderTasks" reicht wohl ein einfacher FIFO. Für Timeout Events sollte man aber einen PriorityQueue haben, womit man das kleinste Element immer in konstanter Zeit erreichen können sollte. In Java gibt es die bestimmt, aber dank der geilen Algorithmenlektionen von Prof. Shewchuck von der Berkeley oder Prof. Demaine und Prof. Devadas vom MIT und einigen anderen, schreibe ich die aber gerne kurz und knapp selbst. Der BinarySearchTree< T> nutzt eine HashMap< Double, T> für O(1) find statt O(lg n), da der Key einfach noch zusätzlich in der Hashmap assoziiert wird. Hab ich in JavaScript gelernt.

import Types.BinarySearchTree;
import Types.PriorityQueue;
/**
 * Created by root on 13.02.14.
 */
public class BinarySearchTreeTest {
    public static void main(String args[]) {
        BinarySearchTree< String> tree = new PriorityQueue< String>();
        tree.insert(10, "denkt");
        tree.insert(7, "Eddie");
        tree.insert(16, "das");
        tree.insert(20, "geht");
        tree.insert(21, "?");
        tree.inorder(new BinarySearchTree.BinaryTreeVisitor< String>() {
            public void visit(double key, String value) {
                System.out.println("key="+String.valueOf(key)+" value="+value);
            }
        });
}
/*
key=7.0 value=Eddie
key=10.0 value=denkt
key=16.0 value=das
key=20.0 value=geht
key=21.0 value=?

Process finished with exit code 0
*/

Und hier hatte ich noch einen Kompilierfehler bei der anonymen Klasse, die ich seit dem ActionLister gestern das zweite mal ausprobiert habe, die ich wie man hier sieht, wohl öfter brauchen werde. Hier sieht man das Beispiel von dem ich dann die Syntax ableiten konnte, indem derNname der extendenen Klasse weggelassen wird, der Superklassenkonstruktor aufgerufen wird und einfach die Methoden überschrieben werden.

//packge Types;
import Types.BinarySearchTree;
import Types.PriorityQueue;

/**
 * Created by root on 13.02.14.
 */
public class BinarySearchTreeTest {

    public static class Visitor extends BinarySearchTree.BinaryTreeVisitor< String> {
        public void visit(double key, String value) {
            System.out.println("key="+String.valueOf(key)+" value="+value);
        }
    }
    public static void main(String args[]) {
        BinarySearchTree< String> tree = new PriorityQueue< String>();
        tree.insert(10, "denkt");
        tree.insert(7, "Eddie");
        tree.insert(16, "das");
        tree.insert(20, "geht");
        tree.insert(21, "?");
        tree.inorder(new Visitor());
    }
}
/*
key=7.0 value=Eddie
key=10.0 value=denkt
key=16.0 value=das
key=20.0 value=geht
key=21.0 value=?

Process finished with exit code 0
*/

Kurz antesten

ObjectBindings haelt mir die HashMaps und die setInternalSlot Routinen und EssentialObjectMethods liefert das Interface für Funktionen, die zum Beispiel für die HashMaps implementiert werden können. So habe ich ein einfaches Object mit dynamischen Slots und gewissermassen etwas Komplexität (weil es ein klobiges von java.lang.Object Object ist mit allen Funktionen von Object)

Spanned oder nützlich scheinen auf jeden Fall die Serializable, Clonable Schnittstellen zu sein. Dem Symbol werde ich intern entweder hashCode überschreiben oder die "uniqueSymbolID" wie ich sie in ES5 benutze für die (getrennte) HashMap benutzen. _Ein_ Objekt trägt somit 3 Hashtables in sich, wie fett das jetzt am Ende wäre, weiss ich nicht. Nur, wenn ich das jetzt so implementiere, und dann auf byte[] und selbstgeschriebene Strukturen auf diesem byteArray umsteigen will, habe ich den gleichen "Refactoringhazard" den ich jetzt schon habe, wo ich die syntax.js Objekte nachträglich alle auf lose gekoppelte TypedArraySequenzen umstellen will. Da müssen alle Interfaces neu umgeschrieben werden, weil der Object.prototype entfaellt und man rufe(obj) schreiben muss. Ein o.hasInternalSlot(name) wäre damit als slots.hasInternal(o, name) zu schreiben. Oder man hat viel zu ändern.

"Geil" ist, daß diese IntellijIDEA IDE einige Generierungsfunktionen besitzt, wie Interface Implementierungen, getter und setter Generierungen, und und und. Und automatische Code-Analyse!! Da spare ich eine Menge Zeit. Sehr viel Zeit.

package Types;
/**
 * Created by root on 12.02.14.
 */
public class OrdinaryObject extends ObjectBindings implements EssentialObjectMethods {
    
    public OrdinaryObject () {
        super();
        setInternalSlot("Extensible", true);
    }
    
    public PropertyDescriptor getOwnProperty (String P) {
        return null;
    }
    public void defineOwnProperty (String P, PropertyDescriptor desc) {
    }
}

/**
 * Created by root on 12.02.14.
 */
import Types.*;

public class OrdinaryObjectTest {
    public static void main (String args[]) {
        OrdinaryObject o;
        o = new OrdinaryObject();
        o.setInternalSlot("SpecialObject", "yes");

        System.out.println("Internal slots:");
        System.out.println("o.[[Extensible]]: "+o.getInternalSlot("Extensible"));
        System.out.println("o.[[SpecialObject]] self defined in main: " +o.getInternalSlot("SpecialObject"));

        System.out.println("o.[[xyz]] hasInternalSlot returns " + o.hasInternalSlot("xyz"));
        System.out.println("o.[[Extensible]] hasInternalSlot returns " + o.hasInternalSlot("Extensible"));
    }
}

/*
/opt/jdk1.8.0/bin/java [...]:/opt/idea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain OrdinaryObjectTest
Internal slots:
o.[[Extensible]]: true
o.[[SpecialObject]] self defined in main: yes
o.[[xyz]] hasInternalSlot returns false
o.[[Extensible]] hasInternalSlot returns true

Process finished with exit code 0
*/

Schmierereien

Hab mal kurz nachgeguckt, ob getter und setter, oder direkter Propertyaccess. Man kann wohl auch Ausnahmen machen und die classes einfach wie structs behandeln. Was es nicht gibt: Default Arguments, dafür Überladung. Muss ich mich kurz dran gewöhnen, jede Funktion mit mehreren Einstiegsfunktionen und einer Hauptroutine zu denken.

public class ExecutionContext {
    public Stack codeEvalState;
    public EnvironmentRecord variableEnvironment;
    public EnvironmentRecord lexicalEnvironment;
    public Object generator;
    public ExecutionContext() {
        codeEvalState = new Stack();
    }
}

Ich habe gerade ein JFrame gezeichnet, und eine anonyme Klasse für den Listener implementiert. Ich bin ganz fasziniert auf einmal, mich da für eine Stunde am Tag reinzuhängen.

DeclarativeEnvironment

Hier sind die ersten Zeilen der neuen DeclarativeEnvironment. Diesmal kam ich auf die Idee, es mit einem Interface zu versuchen. Und weil ich nicht ganz doof bin und die java.class.Hierarchie mit Object starten sehe, mir temporär erstmal "Object" als "Value" zu nehmen, hinterher kann ich "Value" nehmen. Gerade habe ich mich geweigert, weil ich den java String nicht wrappen wollte. Interessant für mich ist, daß ich versucht habe, die EnvironmentRecord Operationen als Interface zu definieren. Nachdem ich in einer Vorlesung "Programmieren" bemerkt hatte, dass man die auch als Typ angeben kann, war das alles geritzt.

Wenn ich wüsste, ob ich die JavaKlassen als Objekte behalte, oder mir einen eigenen Speicher anlege, wie ich es in der Syntax.js Fassung vorhabe? Dann wüsste ich mehr, ob ich "interface" nehmen kann, weil ich richtige Klassen nutze, oder ob ich Methoden einer Statischen Klasse nutze, die meinen ByteCode als Argument kriegen, wie syntax.js bekommen wird?

Wenn ich jetzt Objekte als minimale Klassen organisiere mit internal Slots und properties, sowie symbols, dann ist das immernoch aufwändiger, als ein byte[], dessen Organisation ich kenne. Aber was will ich? Java Garbage Collection, oder einen grossen Puffer? Richtige Java Objekte als Objekte oder virtuelle Objekte? Diesmal muss ich das vorher wissen. Aber noch sind es eh noch einige Zeilen und der Parser ist noch nicht fertig. In den paar Tagen kann ich mich entscheiden.

    /* Ausschnitt aus der neuen /workspace/es6/ */

    // ich hab das gestern ohne Draft geschrieben.

package Types;
/**
 * Created by root on 11.02.14.
 */
public class BindingRecord {    
    public boolean initialized;
    public boolean writable;
    public boolean deletable;
    public java.lang.Object value;
    public BindingRecord(boolean writable, boolean deletable) {
        this.initialized = false;
        this.writable = writable;
        this.deletable = deletable;
    }
}

package Types;
/**
 * Created by root on 10.02.14.
 */
public interface EnvironmentRecord {
    public void createMutableBinding(String name, boolean deletable);
    public void createImmutableBinding(String name, boolean deletable);
    public void initialiseBinding(String name, java.lang.Object value);
    public java.lang.Object getBindingValue(String name);
}

package Types;
import java.util.HashMap;
/**
 * Created by root on 10.02.14.
 */
public class DeclarativeEnvironment implements EnvironmentRecord {
    private HashMap< String, BindingRecord> bindings;
    public DeclarativeEnvironment () {
        bindings = new HashMap< String, BindingRecord>();
    }
    public void createMutableBinding(String name, boolean deletable) {
        boolean writable = true;
        BindingRecord newBinding = new BindingRecord(writable, deletable);
        bindings.put(name, newBinding);
    }
    public void createImmutableBinding(String name, boolean deletable) {
        boolean writable = false;
        BindingRecord newBinding = new BindingRecord(writable, deletable);
        bindings.put(name, newBinding);
    }
    public void initialiseBinding(String name, java.lang.Object value) {
        BindingRecord rec = bindings.get(name);
        if (rec != null) rec.value = value;
        rec.initialized = true;
    }
    public java.lang.Object getBindingValue(String name) {
        BindingRecord rec = bindings.get(name);
        if (rec != null) return rec.value;
        return null;
    }
}

/*

    Ausprobieren

    */

/**
 * Created by root on 11.02.14.
 */
import Types.DeclarativeEnvironment;

public class DeclEnvTest {
    public static void main (String args[]) {
        DeclarativeEnvironment env = new DeclarativeEnvironment();
        env.createMutableBinding("Eddie", true);
        env.createMutableBinding("denkt", true);
        env.initialiseBinding("Eddie", "Gerhold speichert einen String");
        env.initialiseBinding("denkt", 12332542L);
        System.out.println("getBindingValue Eddie: "+env.getBindingValue("Eddie"));
        System.out.println("getBindingValue denkt: "+env.getBindingValue("denkt"));
    }
}

/*
/opt/jdk1.8.0/bin/java -Didea.launcher.port=7533 [...] DeclEnvTest
getBindingValue Eddie: Gerhold speichert einen String
getBindingValue denkt: 12332542

Process finished with exit code 0
*/

Das wird jetzt jeden Tag ein wenig mehr, bis es irgendwann läuft. So kann man mit den BindingRecords hinterher "lokale Variablen" speichern.

Multicore

Aber noch kein Multithreading. Für meine ES6+7 Maschine mit Java 7/8 werde ich mir das aber beibringen, denn ich möchte in de r Java Version die float32x4 Funktionen vom Parallel JavaScript (wohl von Intel, bereits als complete bezeichnet, steht auf der ES7 Roadmap) einbauen und ausprobieren.

class Thread extends java.lang.Thread {
    static int threads;
    static {
	threads = 0;	// cool ist, daß ich static {} bemerkt habe. 
			// womit man alles was static in der Klasse
			// ist initialisieren kann
			// wenn ich "static HashSet< String> operators" habe
			// kann ich operators.add("+") in static {}
			// durchführen. Also bloss nicht vergessen.
    }
    private int id;
    Thread () {
	this.id = ++threads;
    }
    public void run () {
	System.out.println("Thread "+id+" running!");
    }
    public static void main (String args[]) {
	for (int i = 0, j = 10; i < j ; i++) {
	    Thread t = new Thread();
	    t.start();
	}
    }
}
/*
Thread 1 running!
Thread 2 running!
Thread 3 running!
Thread 4 running!
Thread 5 running!
Thread 6 running!
Thread 7 running!
Thread 8 running!
Thread 10 running!
Thread 9 running!
*/

Wie man sieht, führen die Dinger durcheinander aus. Das heisst, jetzt synchronized zu lernen. Und danach nochmal, was volatile für Variablen heisst.

Github tat gut

Auch wenn man am Projekt sieht, daß ich noch keine Ahnung von Github habe, und auch keine Ahnung mehr, wie ein guter Projektbaum ausschaut, hat mir die Registrierung einer broken Version auf Github die Augen dafür geöffnet. Inzwischen sind die Bugs auch drastisch reduziert und ich habe fast wieder Durchlauf auf den Testdokumenten. Dennoch, seit Dezember ist das Ding schon im Eimer, einiges habe ich immer noch nicht gemacht. Letztes Jahr wollte ich fertig sein damit. Aber da hätte ich täglich ein paar Stunden abtippen dürfen, und das geht mit Frau nicht immer. Jedenfalls nicht mit jeder. Mit meiner nicht. Die hat was anderes für mich im Sinne und ich bin erstmal die nächsten Stunden woanders. So sind Lücken entstanden. Und jede 5. ist von mir selbst gewollt, weil ich einfach mal nicht mehr konnte. Jetzt werde ich das Ding long term maintainen. Und die noch vorhandenen "Kinderteile" nach und nach immer mal ausbauen.

//
// This module starts a real REPL for syntax.js
// If you start node syntax0.js a simple shell starts
// The repl has been developed a lot longer
// 
var syntaxjs = require("./syntax0.js").syntaxjs;
var repl = require("repl");
var net = require("net");
function evalWithSyntaxJs(cmd, context, filename, callback) {
    var result = syntaxjs.eval(cmd, true);
    callback(null, result);
}
var port = +process.argv[2];
if (!port) {
// node repl.js starts the repl
    repl.start({
        prompt: "es6> ",
        input: process.stdin,
        output: process.stdout,
        useColors: true,
        eval: evalWithSyntaxJs
    });
} else {
// node repl.js  starts the server
    net.createServer(function(socket) {
	repl.start({
    	    prompt: "es6:"+port+"> ",
            input: socket,
	    output: socket,
    	    useColors: true,
            eval: evalWithSyntaxJs
	}).on("exit", function () {
	    socket.end();
	});
    }).listen(port);
}

node syntax0.js startet das Tool bereits mit if (!module.parent) syntaxjs.nodeShell(); im Shell Modus. Der hat allerdings keine REPL Features. Pfeil hoch und runter hat das benutzte READLINE Interface wohl auch. Der handgeschriebene Klammer Scan, der automatisch auf die nächste Zeile springt (multiline input, falls Klammer offen) scheint nicht dabei zu sein, wie bei der Shell oder bei node. Vielleicht ist es auch nur noch ein Flag. Der REPL hat zum Beispiel Farbe. Und mit net kombiniert geht er als Server. Darum werde ich dieses REPL Interface zum Ersatz der Readline Shell machen und eventuell noch was kombinieren.

Java-Script

Ne Liste habe ich auch schnell in unter zehn Minuten eingegeben. Eine unsafe Operation ist drin, der Code funktioniert trotzdem. Die Fehlermeldung muss ich erst noch verstehen, den angezeigt wird nichts. new ListNode(null) ist es oder so, wenn ich -Xlint angebe. (Ich muss noch herausfinden, was der Linter da sagt, anderseits, hat IntellijIDEA zum Beispiel eine Code-Analyse eingebaut, die habe ich bereits bemerkt. Was mich an Java freuen wird, ist die super Unterstützung durch Tools. Auf Java wurde nicht nur studiert, es wurde 20 Jahre drauf gearbeitet. Und langsam ist Java schon lange nicht mehr. Vor 15 Jahren habe ich drüber gelacht. Selbst schuld. Heute lache ich darüber, das getan zu haben.

// package Types;

/**
 * Created by root on 10.02.14.
 */
public class List< T> {
    private class ListNode< T> {
        public ListNode< T> next;
        public ListNode< T> prev;
        public T value;
        ListNode(T value) { this.value = value; this.next = null; this.prev = null; }
        ListNode(T value, ListNode< T> prev, ListNode< T> next) {
            this.next = next;
            this.prev = prev;
            prev.next = this;

            next.prev = this;
            this.value = value;
        }
    }
    public class ListIterator {
        ListNode< T> current;
        ListIterator() {
            current = sentinel;
        }
        public boolean hasNext() {
            return (current.next != sentinel);
        }
        public T next () {
            if (hasNext()) {
                current = current.next;
                return current.value;
            }
            return null;
        }
    }
    private ListNode< T> sentinel;
    private long nodeCounter;
    public List() {
        nodeCounter = 0;
        sentinel = new ListNode(null);
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }
    public void pushFront (T v) {
       new ListNode< T>(v, sentinel, sentinel.next);
        ++nodeCounter;
    }
    public void pushBack (T v) {
        new ListNode< T>(v, sentinel.prev, sentinel);
        ++nodeCounter;
    }
    public T popFront () {
        T value = sentinel.next.value;
        sentinel.next = sentinel.next.next;
        sentinel.next.prev = sentinel;
        --nodeCounter;
        return value;
    }
    public T popBack () {
        T value = sentinel.prev.value;
        sentinel.prev = sentinel.prev.prev;
        sentinel.prev.next = sentinel;
        --nodeCounter;
        return value;
    }
    public T nth(long i) throws IndexOutOfBoundsException {
        if (i < 0 || i > nodeCounter-1) throw new IndexOutOfBoundsException("nth is out of range");
        long j = 0;
        ListNode< T> node = sentinel.next;
        while (j++ != i) node = node.next;
        return node.value;
    }
    public long size() {
        return nodeCounter;
    }
    public ListIterator iterator () {
        return new ListIterator();
    }
    public static void main(String[] args) {
        List< String> list = new List< String>();
        for (int i = 0; i < 10; i++) list.pushBack("Eintrag #"+i);
        for (int i = 0; i < 10; i++) System.out.println(list.nth(i));
        System.out.println("size(): "+list.size());
        for (int i = 0; i < 5; i++) list.pushBack(list.popFront());
        for (int i = 0; i < 10; i++) System.out.println(list.nth(i));
        System.out.println("size(): "+list.size());
        for (int i = 0; i < 5; i++) list.pushBack(list.popFront());
        System.out.println("testing iterator");
        List< String>.ListIterator it = list.iterator();
        String str;
        while (it.hasNext()) {
            str = it.next();
            System.out.println("iterator got = "+str);
        }
    }
}
/*
Eintrag #0
Eintrag #1
Eintrag #2
Eintrag #3
Eintrag #4
Eintrag #5
Eintrag #6
Eintrag #7
Eintrag #8
Eintrag #9
size(): 10
Eintrag #5
Eintrag #6
Eintrag #7
Eintrag #8
Eintrag #9
Eintrag #0
Eintrag #1
Eintrag #2
Eintrag #3
Eintrag #4
size(): 10
testing iterator
iterator got = Eintrag #0
iterator got = Eintrag #1
iterator got = Eintrag #2
iterator got = Eintrag #3
iterator got = Eintrag #4
iterator got = Eintrag #5
iterator got = Eintrag #6
iterator got = Eintrag #7
iterator got = Eintrag #8
iterator got = Eintrag #9
*/

Stacks kann man in drei Minuten schreiben. Der hier kennt nur Top und einen prev Link seiner StackNode. Damit kann man den Callstack herstellen. Oder einen CodeEvaluationStack.

Alles in allem probiere ich gerade aus. Mittlerweile habe ich herausgefunden, daß man mit einem vernünftigen Editor wunderbar leere Interfaces erzeugen kann. Wenn man die schon mal geschrieben hat, wird das einfacher.

// package Types;

/**
 * Created by root on 10.02.14.
 */
public class Stack< T> {

    private class StackNode< T> {
        public T value;
        public StackNode< T> prev;
        StackNode(T v, StackNode< T> p) {
            value = v;
            prev = p;
        }
    }
    private StackNode< T> top;
    private long nodeCounter;

    Stack() {
        top = null;
        nodeCounter = 0;
    }
    public void push(T v) {
        top = new StackNode< T>(v, top);
        ++nodeCounter;
    }
    public T pop() {
        if (top == null) return null;
        T value = top.value;
        top = top.prev;
        --nodeCounter;
        return value;
    }
    public T top() {
        if (top == null) return null;
        return top.value;
    }
    public long size () {
        return nodeCounter;
    }

    public static void main (String args[]) {
        // vorher package oben disablen
        Stack< String> s = new Stack< String>();

        for (int i = 0, j = 10; i < j; i++) {
            s.push("Eintrag #"+i);
            System.out.println("Stack wächst auf " + s.size());
        }
        System.out.println("Auf dem Top des Stacks liegt der String: " + s.top());

        for (int i = 0, j = 10; i < j; i++) {
            String str = s.pop();
            System.out.println("Stack schrumpfte auf "+s.size());
            System.out.println("Herausgeholt wurde: "+str);
        }

    }
}
/*
Stack wächst auf 1
Stack wächst auf 2
Stack wächst auf 3
Stack wächst auf 4
Stack wächst auf 5
Stack wächst auf 6
Stack wächst auf 7
Stack wächst auf 8
Stack wächst auf 9
Stack wächst auf 10
Auf dem Top des Stacks liegt der String: Eintrag #9
Stack schrumpfte auf 9
Herausgeholt wurde: Eintrag #9
Stack schrumpfte auf 8
Herausgeholt wurde: Eintrag #8
Stack schrumpfte auf 7
Herausgeholt wurde: Eintrag #7
Stack schrumpfte auf 6
Herausgeholt wurde: Eintrag #6
Stack schrumpfte auf 5
Herausgeholt wurde: Eintrag #5
Stack schrumpfte auf 4
Herausgeholt wurde: Eintrag #4
Stack schrumpfte auf 3
Herausgeholt wurde: Eintrag #3
Stack schrumpfte auf 2
Herausgeholt wurde: Eintrag #2
Stack schrumpfte auf 1
Herausgeholt wurde: Eintrag #1
Stack schrumpfte auf 0
Herausgeholt wurde: Eintrag #0
*/

JavaScript Value?

Ich glaube, hier habe ich den Completion Record neu erfunden. Eigentlich haelt das Template ein paar Values. Doch in Java (und später in C++) ist das noch einfacher.

Man kann einfach eine Superklasse angeben und alle Unterklassen übergeben und... man kann das auch mit Interfaces machen, die man dann innerhalb der Funktionen und Loops nutzen kann, denen man die übergibt.

Hier stolper ich nochmal über meine uralte Idee, die Value neu zu erfinden (er meint den "any" Typen).

/**
 * Created by root on 10.02.14.
 */
import java.lang.String;

public class Value< T> {

    private T value;

    public Value(T v) {
        value = v;
    }
    public T getValue() {
        return value;
    }
    /*public void setValue(T v) {
        value = v;
    }*/
    public boolean isString () {
        return value instanceof String;
    }
    public boolean isNumber () {
        return value instanceof Number;
    }
    public boolean isObject() {
        return value instanceof OrdinaryObject;
    }
    public static void main(String args[]) {
        Value< String> s = new Value< String>("Hallo Welt");
        Value< Number> d = new Value< Number>(123.456);
        Value< OrdinaryObject> o = new Value< OrdinaryObject>(new OrdinaryObject());
        System.out.println("s.isString() = " + s.isString());
        System.out.println("s.isNumber() = " + s.isNumber());
        System.out.println("s.isObject() = " + s.isObject());
        System.out.println("d.isString() = " + d.isString());
        System.out.println("d.isNumber() = " + d.isNumber());
        System.out.println("d.isObject() = " + d.isObject());
        System.out.println("o.isString() = " + o.isString());
        System.out.println("o.isNumber() = " + o.isNumber());
        System.out.println("o.isObject() = " + o.isObject());
        System.out.println("s.getValue() = " + s.getValue());
        System.out.println("d.getValue() = " + d.getValue());
        System.out.println("o.getValue() = " + o.getValue());
    }
}
/*
s.isString() = true
s.isNumber() = false
s.isObject() = false
d.isString() = false
d.isNumber() = true
d.isObject() = false
o.isString() = false
o.isNumber() = false
o.isObject() = true
s.getValue() = Hallo Welt
d.getValue() = 123.456
o.getValue() = OrdinaryObject@6d06d69c
*/

Debuggen des Loaders

Heute vormittag habe ich den Computer einfach mitgenommen und unterwegs und im Bus einfach Bugs im Loader gefixt, beziehungsweise Typos entfernt.

Sinn macht das in etwas so viel, daß es hinterher weniger zu tun gibt. Diese Woche habe ich aber überhaupt nicht so viel geschafft. Einiges was ich mir vornehme schiebe ich vor mir her, weil ich die Zeit nicht richtig eingeteilt kriege. Naja, sollte vorübergehend so sein. Hoffen wir mal.

linux-dww5:~ # es6 m.js 
-evaluating m.js-
loaderprotodefine
get options
get options
PromiseOfStartLoadPartWayThrough: start
AsyncStartLoadPartwayThrough: start
promisenew
allocatePromise
initializePromise: start
AsyncStartLoadPartwayThrough_Call
createlinkset
newpromisecap
loaderprototypeimport
loadmodule
get options
PromiseOfStartLoadPartWayThrough: start
AsyncStartLoadPartwayThrough: start
promisenew
allocatePromise
initializePromise: start
AsyncStartLoadPartwayThrough_Call
loaderprototypeget
undefined
undefined
undefined
undefined
es6> 

Yield als Identifier

In Funktionen ist yield als Variable erlaubt. Ob das richtig ist?

es6> Function("yield 1")()
1
es6> Function("yield 1").toString()
function anonymous () {
    yield;
    1;
}
es6> 

Scheint so. Der Yield und 1 scheinen als Identifier und als NumericLiteral erkannt worden zu sein. Und die codegen Funktion der StatementList (codegen erzeugt einen JavaScript-String aus den AST-Nodes) hat die Semikola eingefügt.

Was mich aber wundert. Das yield; nicht throwt. Und 1 anstatt die Exception zurückkommt. Denn wenn ich yield auf dem Prompt eingebe, erhalte ich die entsprechende Exception von GetValue(ref). Was mich wundert? Ein Bug.

Java-Script

Klein faengt man an. Die Funktion MV verwandelt die Strings in Werte. Hier wird meist mit der Basis multipliziert. Niedrige Komplexitaet ist aber von Noeten, darum ist das hier noch nicht der beste Ansatz. (Dazu muss man sich den direkt in Maschinensprache als moeglichst wenig Instruktionen zusammenbauen. Oder halt die Hochsprache so knapp es geht, aber so robust wie vorgeschrieben, zum Einueben einer Regel). Es sind 3 Vergleiche pro Iteration und immer der Else Branch für die Ziffer selbst. Das ist nicht gut. Wenn die Ziffern schon in Einzelteilen an MV übergeben werden und Teile ausgewertet werden, wäre das schneller zu lösen. Da müsste der Tokenizer bereits korrekte Token erzeugen, wo Exponent und Komma getrennt sind, oder 0x und Co vom Rest der Nummer. (Was der EcmaScript Grammatik nicht wiedersprechen würde. Nur dem Esprima-like Parser, der einen Array mit Token wiedergibt, die Type und Value enthalten, nein, das wäre der Grammatik sogar gerecht, weil sie durchaus kleinere Teile unter dem Literal zusammenfasst...Tip auch an mich, meine "Token" (langfristig) zu verbessern.)

Hehe. In Java gibt es dumme Funktionen wie .valueOf und .toString, wie in JavaScript auch. Gut ist es, sich die bereits angewöhnt zu haben. Da machen .hashCode und .equals direkt mehr Spass. Ich habe mir die Docs bereits lokal installiert und browse und merke.

/**
 * Created by root on 06.02.14.
 */
import java.lang.String;

public class MV {
		
    public static double eval (String s) {
        int lenPrefix = 0;
        int base = 10;
        int dot = 0;
        double exp = 0.0;
        double value = 0.0;
        int c;
        if (s.charAt(0) == '0') {
            c = s.charAt(1);
            lenPrefix = 2;
            if (c == 'x') base = 16;
            else if (c == 'o') base = 8;
            else if (c == 'b') base = 2;
            else {
                base = 10;
                lenPrefix = 0;
            }
        }
        for (int i = s.length()-1, j = lenPrefix, k = 0; i >= j; i--, k++) {
            if ((c = s.charAt(i)) == '.') {
                dot = i;
                k--;
            } else if (c == 'e') {
                exp = value;
                value = 0.0;
                k = 0;
            } else {
                value +=  (c-48) * Math.pow(base, k);
            }
        }
        if (dot != 0) value *= Math.pow(base, -dot);
        if (exp != 0) value *= Math.pow(base, exp-1);
        return value;
    }

    public static void main(String args[]) {
        System.out.println(MV.eval("10"));
        System.out.println(MV.eval("123.456"));
        System.out.println(MV.eval("10e6"));
        System.out.println(MV.eval("12.34e6"));

    }
}
/*
10.0
123.456
1.0E7
1.234E7
*/

Das noch nicht benoetigte 0x und 0b und Co in dieser Funktion kann auch entfallen, wenn der Tokenizer klueger ist und die Funktion, die die Werte holt. So testet das Ding, obs mit 0 beginnt und ein Zeichen kommt, womit die Basis eingerichtet wird. Für Hexzahlen muss noch ein Mapping für 0-9 und A-F erstellt werden. Und der Character 'e' ist noch case sensitive. Noch..

Bitstrings

Das korrekte Verknüpfen mit | will auch ausprobiert werden. Und ganz besonders extensiv genutzt. Hier probiere ich die JavaScript Alternative mit Ziffern zu rechnen, sich die Bits aber mit Number.prototype.toString(2) anzugucken.

// Left padding. http://wiki.ecmascript.org/doku.php?id=strawman:string_extensions hat String.prototype.lpad 

function lpad(s) {
    var lpad = "";
    var len = 52-s.length;
    var i = 0;
    while (i < len) {
	lpad += "0"
	i++;
    }
    return lpad + s;
}

// Macht einen 52 Bit String aus dem Base-2 toString durch left padding mit 000
function bitstr(n) {
    return lpad((+n).toString(2));
}

// Ausprobieren

console.log("ausgabe");
console.log(bitstr(128));
console.log(bitstr(127));
console.log(bitstr(128 | 127));

/*

 Bits verknüpft man mit |
 
 Wenn man alle Bits setzen will, muss man sie ODERn.
 
*/
console.log("oder");
console.log(bitstr(1 | 2 | 4 | 8 | 16 | 32 | 64 | 128)); // whole byte set
console.log(bitstr(1 | 32));				// 0(1) und 5(6) set
console.log(bitstr(1 | 2 | 32));			// 0,1,5 set
console.log(bitstr(1 | 2 | 4 | 32));			// 0,1,2,5 set

console.log((1 | 2 | 4 | 32) === 39)			// 39 is the number
/*
 Bits testet man mit &

 Werden die Bits benötigt, um die Zahl darzustellen,
 kommt die gleiche Zahl bei der Verknüpfung raus

*/
console.log("und");
console.log(bitstr(127 & 32)); // enthaelt 32
console.log(bitstr(128 & 32)); // enthaelt 32 nicht
console.log(bitstr(127 & 64)); // enthaelt auch 64 
console.log(bitstr(128  & 64)); // enthaelt 64 nicht

console.log((127 & 64) === 64); // enthaelt auch 64 
console.log((128  & 64) === 0); // enthaelt 64 nicht
/*
ausgabe
0000000000000000000000000000000000000000000010000000
0000000000000000000000000000000000000000000001111111
0000000000000000000000000000000000000000000011111111
oder
0000000000000000000000000000000000000000000011111111
0000000000000000000000000000000000000000000000100001
0000000000000000000000000000000000000000000000100011
0000000000000000000000000000000000000000000000100111
true
und
0000000000000000000000000000000000000000000000100000
0000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000001000000
0000000000000000000000000000000000000000000000000000
true
true
*/

Das sollte selbsterklärend sein. Aber ich hatte auch schonmal bessere Zeiten, vor einem Jahr zum Beispiel, da erklärte ich auch, was ich schrieb. Nun dann. Ich will die Seite ja auch seit Januar schon neu gemacht haben.

Java-Script

Zeit für "ES6 for Java" API

Seit einigen Zeilen Code hacke ich mir gedanklich, wie auch zukünftig eine Java-JavaScript-API zusammen. Hier kann ich das Beste vom Besten probieren und fast alle Datenstrukturen kommen dran.

Dabei werde ich versuchen, mich in die Java API einzugewöhnen, dann fällt mir das Android SDK in den kommenden Jahren um so leichter. Der einzige Grund, warum Java in meinen Augen überleben wird. Naja, der andere ist der, daß sich die Sprache optimal für Schüler eignet, weil sie streng objektorientiert ist, aber die Schwierigkeiten mit den Zeigern nicht bringt, sondern Dynamic Allocation und Garbage Collection für Ahnungslose bietet, darum eignet sie sich als Vorlesungssprache sehr wohl sehr gut. Es wird mir Spass machen und ich werde für dieses Projekt die Sprache für mich entdecken.

Ich werde jetzt erstmal ein paar Datenstrukturen rausfinden oder selbst schreiben. Ich brauche viele, viele Stacks, oder wenigstens Listen, wo ich das letzte Element an- und abhaengen kann, und viele Hashtabellen um in konstanter Zeit viele ifs (deren tests sonst auch linear summiert werden) zu sparen.

Das KIT hat ausserdem SWT1 veröffentlicht. Da kann ich nach Entwurfsmustern gucken, und mir die UML wieder reinziehen. Ich hab mir schonmal ArgoUML besorgt. Das hatte ich vor ca. 10 Jahren schon mal. Dann habe ich mir bereits Apache Maven als Build Tool besorgt, weil es im KIT Kurs verwendet wird. Das funktioniert mit IDEA, darum richte ich mir wohl auch wirklich meine pom.xml ein und nutze es dann zusammen mit git..

// package Tables;
import java.util.HashMap;
import java.util.HashSet;
import java.lang.String;

public class Tables {

    public  HashSet< String> identifierStart;
    public  HashSet< String> keywords;
    public  HashSet< String> operators;
    public  HashSet< String> assignmentOperators;
    public  HashSet< String> binaryOperators;
    public  HashSet< String> unaryOperators;

   
    public  boolean isKeyword(String word) {
        return keywords.contains(word);
    }
    public  boolean isOperator(String operator) {
        return operators.contains(operator);
    }
    public  boolean isAssignmentOperator(String operator) {
        return operators.contains(operator);
    }
    public  boolean isBinaryOperator(String operator) {
        return binaryOperators.contains(operator);
    }
    public  boolean isUnaryOperator(String operator) {
        return unaryOperators.contains(operator);
    }
    public boolean isIdentifierStart(char ch) {
        return identifierStart.contains(ch);
    }

    public Tables () {
            keywords = new HashSet< String>();
            operators = new HashSet< String>();
            assignmentOperators = new HashSet< String>();
            binaryOperators = new HashSet< String>();
            unaryOperators = new HashSet< String>();
            identifierStart = new HashSet< String>();

            identifierStart.add("$");
            identifierStart.add("_");
            identifierStart.add("A");
            identifierStart.add("a");
            identifierStart.add("B");
            identifierStart.add("b");
            // gen. chr(i);
            identifierStart.add("Z");
            identifierStart.add("z");
            
            keywords.add("case");
            keywords.add("class");
            keywords.add("const");
            keywords.add("do");
            keywords.add("delete");
            keywords.add("default");
            keywords.add("function");
            keywords.add("if");
            keywords.add("for");
            keywords.add("in");
            keywords.add("let");
            keywords.add("module");
            keywords.add("return");
            keywords.add("switch");
            keywords.add("var");
            keywords.add("void");
            keywords.add("while");
            keywords.add("yield");

            operators.add("+");
            operators.add("++");
            operators.add("-");
            operators.add("--");
            operators.add("=");
            operators.add("==");
            operators.add("===");
            operators.add("!=");
            operators.add("!==");

            assignmentOperators.add("=");
            assignmentOperators.add("+=");
            assignmentOperators.add("-=");
            assignmentOperators.add("*=");
            assignmentOperators.add("/=");
            assignmentOperators.add("~=");
            assignmentOperators.add("^=");
            assignmentOperators.add("&=");
            assignmentOperators.add("|=");
            assignmentOperators.add("<<=");
            assignmentOperators.add(">>=");
            assignmentOperators.add(">>>=");

            binaryOperators.add("+");
            binaryOperators.add("-");
            binaryOperators.add("*");
            binaryOperators.add("/");
            binaryOperators.add("&");
            binaryOperators.add("|");
            binaryOperators.add("&&");
            binaryOperators.add("||");
            binaryOperators.add("<<");
            binaryOperators.add(">>");
            binaryOperators.add(">>>");

            unaryOperators.add("+");
            unaryOperators.add("-");
            unaryOperators.add("!");
            unaryOperators.add("!!");
            unaryOperators.add("~");
            unaryOperators.add("delete");
            unaryOperators.add("typeof");
            unaryOperators.add("void");
    }

    public static void main(String args[]) {
        Tables t = new Tables();
        System.out.println("var is keyword? "+ t.isKeyword("var"));
        System.out.println("+++ is operator? "+ t.isOperator("+++"));
        System.out.println("++ is operator? "+ t.isOperator("++"));
        System.out.println("ded is keyword? "+ t.isKeyword("ded"));
    }
}

/*
linux-dww5:~/workspace/ES6Parser/src/Tables # javac Tables.java
linux-dww5:~/workspace/ES6Parser/src/Tables # java Tables
var is keyword? true
+++ is operator? false
++ is operator? true
ded is keyword? false
linux-dww5:~/workspace/ES6Parser/src/Tables #
*/

Für Version 2 meiner JavaScript Interpreter Reihe versuche ich mich in der Programmiersprache Java. Hier kann ich objektorientierte Programmierung üben.

Ich denke, hier sollte eine Operation codeGen() pro Node eingefügt werden, das Resultat ist dann ein Objekt ByteCode, was ein Array bytes sein kann.

  public ParseNode parseScriptItemList() {
    	ParseNode scriptItemList = new StatementList();
    	ParseNode statement;
    	while (statement = parseStatement()) {
    	    scriptItemList.pushBack(statement);
    	}
    	return scriptItemList;
    }
    public ParseNode parseScript () {
        ParseNode script = new ScriptParseNode();
        script.body = parseScriptItemList(script);
        return script;
    }

Wenn ich die Java Variante dann verk*** und hinter mir habe, und wir schon bei ES7 sind, hole ich meinen Johannes, meine nach Maschinencode kompilierende C++ Fassung, raus.

undefined
es6> let [a,b] = ["y", "n"];
undefined
es6> a
y
es6> b
n
es6> function f({ a, b }) { return a + b; }
undefined
es6> f({a:10,b:20}
...> );
30
es6> 

Nicht kaputt zu kriegen. Aber auch nur die Nr.2 in Sachen Architektur, wenn sie fertig ist. Danach muss sie nochmal überarbeitet werden. Den meisten Spass beim Ändern werde ich dort haben, wo ich generische Records, die mit { } erzeugt wurden über ein Interface erzeugen muss, all ihre Propertyzugriffe [\w]+\.([\w]+) in get(o, n) und set(o, n, v) aendern muss, und alle arrays, array.length und .push() sowie alle [] muessen hinter ein neues Interface, bzw. muss push(A, value) aus .push gemacht werden und length(A) aus A.length. Hier habe ich bis auf Promise, Loader und Realm bislang nur geringfügig Records in Listen speichern müssen, darum ist das noch überschaubar. Wird aber noch einige Stunden extra in Anspruch nehmen. Und zu tun habe ich auch noch genug dran. :-) Hatte schon gedacht, letztes Jahr mit fertig zu sein. (Abzueglich aller Tage, wo nichts geschehen ist, würde das wohl auch noch letztes Jahr sein, aber in der Wirklichkeit sind wir schon im Februar...)

Februar!

Die Seite schreit mittlerweile nach Neugestaltung.

Lektion 13 Programmieren

Recursive Descent mit Java

Ich habe gerade mal die erste Hälfte der Lektion 13 von KIT gemacht. Hier geht´s um´s parsen. Und ich dachte, wow, das sieht ja aus wie Esprima. Die haben wohl beide die gleichen Lehrer gehabt. Heheh. Weil ich den Code einfach gut finde, weil man daraus leicht die Grundlagen für einen richtigen JavaScript Parser ableiten kann, Komplexität von Collections im Auge behaltend, dachte ich mir, poste ich den abgeschriebenen Code einfach mal. Wenn man java Calculator 2 "1+2*3" startet, kommt auch 7.0 raus. Besonders einfach finde ich hier die Form, wie die Additive Expression getrennt von der Multiplikativen in einer Schleife geparsed wird. Hier wird die Präzedenz ganz einfach gelöst. Für die Grammatik einfach das Originalvideo gucken. :)

Hier geht´s zum Original: Lektion 13 Programmieren.

/*
    Calculator2.java
    Ein einfacher recursive-descent Parser mit dem StreamTokenizer.
    2. Datei mit direkter Evaluation des geparsten Ausdrucks (was mitunter keine Loesung ist ;)).
    (c) nicht von mir
     Source Code is vom Karlsruher Institut für Technologie, webcast.informatik.kit.edu
     Gehört zur Vorlesung "Programmieren WS 2013/2014, Lektion 13 Parsen, Suchen, Sortieren"

*/
import java.io.StreamTokenizer;
import java.io.IOException;
import java.io.StringReader;
import java.io.Reader;

public class Calculator2 {
	private int lookahead;
	private StreamTokenizer tokenizer;
	private void next () throws IOException {
		lookahead = tokenizer.nextToken();
	}
	private void match (int expected) throws IOException {
		if (lookahead != expected) {
				error("expected '"+tokenToString(expected)+"', got '"+tokenToString(lookahead)+"'");
		} else {
			next();
		}
	}
	private double parseExpr () throws IOException {
		double v, w;
		int op;
		v = parseTerm();
		while ((op=lookahead) == '+' || lookahead == '-') {
			next();
			w = parseTerm();
			v = (op == '+') ? v+w : v-w;
		}
		return v;
	}
	private double parseTerm () throws IOException {
		double v, w;
		int op;
		v = parseFact();
		while ((op=lookahead) == '*' || lookahead == '/') {
			next();
			w = parseFact();
			v = (op == '*') ? v*w : v/w;
		}
		return v;
	}
	private double parseFact () throws IOException {
		double v = 0.0;
		if (lookahead == '(') {
			match('(');
			v = parseExpr();
			match(')');
		} else if (lookahead == StreamTokenizer.TT_NUMBER) {
			v = tokenizer.nval;
			next();
		} else {
			error("unexpected symbol '"+tokenToString(lookahead)+"'");
		}
		return v;
	}
	private static String tokenToString(int token) {
		if (token == -2) { return "NUMBER"; }
		else if (token == -1) { return "EOF"; }
		else if (token == StreamTokenizer.TT_WORD) {
			return "WORD";
		}
		else return String.valueOf((char)token);
	}
	private void error (String message) {
		System.err.println("syntax error: "+message);
		System.exit(0);
	}
	public void parse(String s) throws IOException {
		Reader r = new StringReader(s);
		tokenizer = new StreamTokenizer(r);
		tokenizer.ordinaryChar('/');
		double v;
		next();
		v = parseExpr();
		if (lookahead != StreamTokenizer.TT_EOF) {
			error("unexpected symbol '"+tokenToString(lookahead)+"'");
		} else {
			System.out.println("arithmetic expression is well formed");
			System.out.println("result is " + v);
		}
	}
	public static void main(String args[]) throws IOException {
		Calculator2 c = new Calculator2();
		if (args.length < 1) {
			System.err.println("usage: java Calculator2 \"expression\"");
			return;
		}
		c.parse(args[0]);
	}
}

Floating Point

Dann wieder zu meinen Übungen zurück

Weil ich ohne mathematische Funktionen hier nicht mehr weiterkomme, mein Mathestudium steht ja immernoch auf der Liste und ist nur um ein paar Videos und unzählige Informatikvorträge unterbrochen worden, muss ich mich jetzt zusammenraufen und den IEEE Floating Point richtig lernen. Wenn ich das nächstemal zum Drucken komme, werde ich mir das Papier auch mal vornehmen. Das ist in der Hand besser zu lesen. Und ein digitales Gerät ist schlechter zu blättern und ausserdem mit Hartz IV nicht einfach so drin, auch wenn die Omas damit im Zu lesen.

Also Infinity und Infinity haben den Exponenten voller 1en, das Vorzeichen entsprechend, und in der Mantisse nur Nullen. Und bei NaN. Da ist der Exponent voll, aber die Mantisse nicht 0, und das Vorzeichen egal, da gibt beides NaN. Das wollte ich mir mal eben merken.

Vernünftige bitweise & und | werde ich mir nun als nächstes reinfahren.

Ich werde jetzt allerhand Tutorials über Dezimal-, Octal-, Hexadezimal-, Dualkonvertierungen machen und mich den Funktionen MV und SV in drei Programmiersprachen widmen. Damit sollte ich dann eine vernünftige Konvertierung programmiert haben, die sich gleichzeitig, da es sich um SV und MV handelt in die ES Konfiguration einbauen lässt.

inlinefiles.js

Mit dem Tool zerlege ich syntax.js in kleine rekursiv ladbare Teile. Ich hätte von Anfang an Macros und einen Präprozessor nehmen sollen, aber ich wollte ja nicht faul sein.

Es ersetzt #include "name.js"; mit dem Inhalt der Datei name.js und falls diese Includeanweisungen enthält, werden sie ebenfalls ersetzt.

/* node inlinefile.js top-level.js [out-file.js] wird top-level.js rekursiv nach #include "name.js" absuchen und die Dateien einbinden */

(function main () {
    "use strict";
    
    function inlineFiles(filename) {
        return makePromise(function (resolve, reject) {        
    	    fs.readFile(filename, "utf8", function (err, data) {
        	if (err) reject(err);
                else resolve(data);
            });
        });
    }
    
    function transFormSync(input) {
        if (input)
        return input.replace(include, function (all, filename) {
            var content = fs.readFileSync(filename, "utf8");
            content = transFormSync(content);
            return content;
        });
        else return "";
    }
    
    function writeFile(toDrive, data) {
        fs.writeFile(toDrive, data, "utf8", function (err) {
            if (err) console.dir(err);

            else console.log(toDrive + " successfully written.");
        });
        return data;
    }
    
    function logOrWrite (data) {
    var toDrive = process.argv[3];
    if (typeof toDrive === "string") {
            if (fromFile === toDrive) console.log("input "+fromFile+" and output "+toDrive+" are the same");
            else writeFile(toDrive, data);    
        } else {
            console.log(data);
        }
        return data;
    }
    
    var include = /(?:\#include "([^\"]+)";)/g;
    var makePromise = require("./promise.js").makePromise;
    var fs = require("fs");
    var fromFile = process.argv[2];
    if (!fs.existsSync(fromFile)) throw new TypeError(fromFile + " does not exist");
    
    var ausgabeP = inlineFiles(fromFile).then(transFormSync).then(logOrWrite);
    console.log("A promise - Diese Zeile wird vor der Zeile ausgegeben, die wir soeben riefen. - the turn.");
    
}());

Java-Script

public static void main(String args[]) {
    // for (let arg of args) 
    for (String arg: args) {
	System.out.println(arg);
    }
}

Javascript

Die Bugs vom September sind wieder da!!! Endlich wieder alte bekannte Bugs, die sich hinter anderen verstecken. Das ist ja bei der Language so, dass sie auch exakt richtig gemacht werden muss, und knapp daneben ist auch vorbei. var z = arguments[0] ergibt undefined, var z; z = argument[0]; nicht. WIE IM SEPTEMBER. Das sollte also zu retten sein.

es6> function f() { (function g() { (function (d) { throw new TypeError(d); }("mine")); }()); }
undefined
es6> f()
mine
An exception has been thrown!
exception.name: TypeError
exception.message: mine
exception.stack: 1. (new) TypeError at Identifier  at line 1, column 67 [caller g @ (node.js interpreter)]
2. g at Identifier  at line 1, column 73 [caller f @ (node.js interpreter)]
3. f at Identifier  at line 1, column 16 [caller {ScriptItemList} @ (node.js interpreter)]
4. {ScriptItemList} at Identifier  at line 1, column 0 [caller {Script} @ (node.js interpreter)]

Vorher habe ich noch kurz den Callstack korrigiert.

He´s back - a new ES6 draft

Hooray! Bootet die IDE schonmal. Ein neues Draft ist da. Promises und der Loader haben es reingeschafft. Mitunter. Ich werde jetzt erstmal mit 6.6k abspeichern und dann werde ich abtippen, was ich kann :-).

http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#january_20_2014_draft_rev_22

The first change is done while reading the changelog, und ich addierte "return" zum constructor string. var defaultClassConstructorFunctionBody = parseGoal("FunctionBody", "return super(...args);"); Dabei habe ich das schlechter gewählte classHeritageConstructorCode in defaultClassConstructorFunctionBody umbenannt.

Nebenbei habe ich 5.1.5 Grammar Notation zum Teil kopiert, um ein Dokument raus zu machen. (Update mit kleinem nativen JS Beispiel den LL(1) ParameterStack wie ich ihn mir ausgedacht habe, zu zeigen. Oh, hier könnte ich die Symboltabelle und Contains hinzufügen.)

linux-dww5:~ # es6
es6> import Object from "@std";
undefined
es6> 
Have a nice day.
Uptime: 2447ms

Das undefined sollte dann bald der Vergangenheit angehören. Und meine Promise Implementation kann ich jetzt auch vollenden.

Ich denke, es ist die richtige Zeit, diese Seite nun nochmal von vorne zu beginnen.

Januar

Weil die Seite wieder recht langweilig aussieht, gedenke ich, die sinnlosen Texte zu löschen. Die alten Tests unter "2013" zu speichern, die Seite von vorne zu beginnen und neue Tests zu schreiben. Um die interessant zu machen, stelle ich einfach EcmaScript 6 damit vor. Wenn man die Seite runter scrollt und die PASS und FAIL Meldungen liest, zeige ich einen selbstgeschriebenen ES6 Interpreter, den ich mit ES5 hergestellt habe. Ich arbeite zur Zeit immernoch dran. Und denke auch, daß es ein Langzeitprojekt wird. [Und auf einmal ist es auf Github.] - Schon hat eddie vor, die Bugs etwas schneller zu machen und sucht bereits.

(Wer ueber das Singlefile lacht, wird es nicht mehr lange tun.)

linux-dww5:~ # es6
es6> throw new SyntaxError("sdfsd");
sdfsd
An exception has been thrown!
exception.name: SyntaxError
exception.message: sdfsd
exception.stack: 
(new) SyntaxError (StringLiteral)  at line 1, column 22 [called from {Script}]

Wie ich gerade herausfand, das der ErrorConstructor keinen stack erzeugt. Wie ich fand, dass der Funktionsname bei den Konstruktoren fehlt. Wie ich noch einiges anderes fand. :-) Bald im Changelog.

Das Foto habe ich in Berlin am 4.11.2010 von einer e-info Stelle an meine Adresse geschickt. Habe ich längst vergessen gehabt, aber als ich es sah, wusste ich wieder, wie der Pulli sich anfühlte, und wie ich drauf war. So ein halbes Jahr. Am Ku'damm an der U-Bahn an der Bushaltestelle ist so ein Infoteil und da e_info_kranzler draufsteht, denke ich, da habe ich das gemacht. Schnappschuss des Tages.

Noch mehr Code

es6> function f(
...> ) { 
...> return true; }
undefined
es6> f()
true
es6> 
Habe nun Unterstützung für mehrzeilige Inputs eingebaut. Genaugenommen kostet es O((lastCode+code).length) Zeichen scanning, ob alle [], (), {} richtig geschlossen sind mit nehmen des Zeichens, gucken, ob´s ne Klammer ist, pushen auf den Stack, wenn sie sich oeffnet, poppen, wenn der Vergleich isCloseParen[ch] stimmt. Wenn ein nesting Error ist, wird das Ding throwen. --- Strings "" sind jetzt nicht inbegriffen. Sollte alles auf einer Zeile eingegeben sein, oder per file als 3. parameter nach node syntax0.js kommen, sorgt der normale Scanner für den Check. Leider ist das Tool kein Zeileninterpreter.

Das neue Draft und ich Updates

Was ist das? "CreateFromConstructor", "Construct". Ich hatte "OrdinaryConstruct" und "OrdinaryCreateFromConstructor". CreateFromConstructor ruft OrdinaryCreateFromConstructor und es ist wohl kein Tipfehler, weil erstere nur ein Argument hat, und die andere zwei.

    function CreateFromConstructor(F) {
        var creator = Get(F, $$create);
        if ((creator = ifAbrupt(creator)) && isAbrupt(creator)) return creator;
        if (creator === undefined) return undefined;
        if (IsCallable(creator) === false) return withError("Type", "CreateFromConstructor: creator has to be callable");
        var obj = callInternalSlot("Call", creator, F, []);
        if ((obj = ifAbrupt(obj)) && isAbrupt(obj)) return obj;
        if (Type(obj) !== "object") return withError("Type", "CreateFromConstructor: obj has to be an object");
        return obj;
    }

    function Construct(F, argList) {
        Assert(Type(F) === "object", "essential Construct: F is not an object");
        var obj = OrdinaryCreateFromConstructor(F);
        if ((obj = ifAbrupt(obj)) && isAbrupt(obj)) return obj;
        if (obj === undefined) {
            obj = OrdinaryCreateFromConstructor(F, "%ObjectPrototype%");
            if ((obj = ifAbrupt(obj)) && isAbrupt(obj)) return obj;
            if (Type(obj) !== "object") return withError("Type", "essential Construct: obj is not an object");
        }
        var result = callInternalSlot("Call", F, obj, argList);
        if ((result = ifAbrupt(result)) && isAbrupt(result)) return result;
        if (Type(result) === "object") return result;
        return obj;
    }
    

Ich habe NewFunctionEnvironment ebenso verändert, wie vorher IteratorNext. Nur bei NewFunctionEnvironment fiel mir der neue [[NeedsSuper]] Slot auf, sowie der wohl von [[Scope]] in [[Environment]] umbenannte Slot.


    function NewFunctionEnvironment(F, T) {
        Assert(getInternalSlot(F, "ThisMode") !== "lexical", "NewFunctionEnvironment: ThisMode is lexical");
        var env = new FunctionEnvironment(F, T); // ist Lexical Environment and environment record in eins
        env.thisValue = T;
        if (getInternalSlot(F, "NeedsSuper") === true) {
            var home = getInternalSlot(F, "HomeObject");
            if (home === undefined) return withError("Reference", "NewFunctionEnvironment: HomeObject is undefined");
            env.HomeObject = home;
            env.MethodName = getInternalSlot(F, "MethodName");
        } else {
            env.HomeObject = undefined; // spec. says Empty. I would have one singleton {} reference called "empty". I prefer undefined here.
        }
        env.outer = getInternalSlot(F, "Scope"); // noch in [[Environment]] umbenennen
        return env;
    }

Das Gute ist, sowohl die Funktionen, als auch das Erzeugen von Objekten funktioniert [noch] weiter. Nach Abschreiben von PromiseNew und IrgendwasVonPromiseCapability (wohl das interne "Deferred" Objekt) bin ich bei CreateRealm, die mir noch fehlte. Hinzu kommen im [[CodeRealm]] neue Properties für [[directEvalHook, directEvalFallback, indirectEvalHook und Function]], die auch im Realm Constructor für´s [object Realm] eine Rolle spielen, ein Options Objekt mit GetOption(options, name), wie es Millionen schon benutzten, hat es damit auch in den Entwurf geschafft. Ich denke mit der CreateRealm Funktion und dem, was ich noch entdecke, schaffe ich es, einige Unsicherheiten, wie ich mein Multirealm-System beschaffen wollte, aus der Welt zu schaffen.

[Einen Kommentar über "Compiler, separate Programmierung aller Speicher-Funktionen zuerst, und fehlende getNodeOrCode(code, NODE_BODY) in der Runtime, um sie weiter zu verwenden, während der rest setInternalSlot etc. hat", habe ich hier entfernt.]

    function Invoke(O, P, args) {
        var obj;
        Assert(IsPropertyKey(P), "Invoke: expecting property key");
        if (!Array.isArray(args)) args = [];
        obj = ToObject(O);
        if ((obj = ifAbrupt(obj)) && isAbrupt(obj)) return obj;
        var func = callInternalSlot("Get", obj, P, O);
        if (!IsCallable(func)) return withError("Type", "Invoke: expected function is not callable");
        if ((func = ifAbrupt(func)) && isAbrupt(func)) return func;
        return callInternalSlot("Call", func, O, args);
    }

Invoke hatte ich unterwegs auch geändert. Erst jetzt entfällt bei mir dadurch die MOP [[Invoke]] an den OrdinaryObject.prototypes, weil sie in meiner längst veralteten Fassung noch gerufen wurde.

    function IteratorNext(itr, val) {
        var result;
        if (arguments.length === 1) result = Invoke(itr, "next", []);
        else result = Invoke(itr, "next", [val]);
        if ((result = ifAbrupt(result)) && isAbrupt(result)) return result;
        if (Type(result) !== "object") return withError("Type", "IteratorNext: result is not an object");
        return result;
    }

Die Invarianten für [[getProtypeOf]], [[PreventExtensions]] und alle anderen MOP (Meta Objekt Protokoll), den zu den Objekten gehörigen Metafunktionen wie [[DefineOwnProperty]], [[GetOwnProperty]] sind neu geschrieben worden. Wenn [[PreventExtensions]] true zurückliefert muss [[IsExtensible]] ab dann false zurückliefern. Das muss man zusichern können.

CreateBuiltinFunction wurde nun von einer gesprochenen abstrakten Version, ich habe den Namen auch aus dem Draft, zu einer richtigen Funktion. Sie hat die Parameter realm, steps. Wobei steps in meiner Variante die Funktion (thisArg, argList) ist. Ich habe sie noch ein wenig ausgestattet, um die selbstdefinierte setFunctionLength und die spezifizierte SetFunctionName zu rufen. Die neue native Funktion habe ich dann habe ich dann reihenweise mit DefineOwnProperty addiert gehabt und den Code vermüllt (ich das DRY-Prinzip verletzt). Inzwischen hat sich das ein wenig entwickelt und LazyDefineBuiltinFunction (obj, name, length, steps) wurde raus, die ruft dann DefineOwnProperty. Es gibt Abwandlungen für die PropertyDeskriptoren (globale properties, prototype funktionen, prototype konstanten). Jetzt kann ich sie um REALM erweitern. Und ehrlichgesagt. Das Problem hatte ich gerade bei meiner createRealm() und createIntrinsics(), ich wollte das Realm nicht umstellen, um es setzen zu lassen mit getRealm(). Da ist die Loesung: Parameter "realm".

    function MakeMethod (F, methodName, homeObject) {
        Assert(IsCallable(F), "MakeMethod: method is not a function");
        Assert(methodName === undefined || IsPropertyKey(methodName), "MakeMethod: methodName is neither undefined nor a valid property key");
        var hoType = Type(homeObject);
        Assert(hoType === "undefined" || hoType === "object", "MakeMethod: HomeObject is neither undefined nor object.");
        setInternalSlot(F, "NeedsSuper", true);
        setInternalSlot(F, "HomeObject", homeObject);
        setInternalSlot(F, "MethodName", methodName);
        return NormalCompletion(undefined);
    }

Inzwischen lese ich MakeMethod, wie es in FunctionDeclarations, -Expressions, MethodDefinitions bei ReferencesSuper gerufen wird. Weil noch eine alte rekursive O(n) wo n = Anzahl der Nodes im FunctionBody, was sehr teuer ist, Funktion läuft, habe ich mir was neues einfallen lassen. Zweimal implementiert und wieder explementiert: Die currentFunctionNode auf dem currentFunctionNodeStack. Wenn ich eine SuperExpression entdecke, kann ich genau DER currentFunctionNode ein .referencesSuper oder .needsSuper drauftaggen, beziehungsweise ein Bit setzen. Ich hatte curFunc oder curNode oder wie auch immer bestimmt 2x drin und wieder raus, weil ich sie noch nicht brauchte. Aber ich schwöre, das ist eine Standardreferenz in einem solchen Language System und bestimmt auch in den anderen Sprachen auf die Art verwirklicht, ein aktueller Pointer zum aktuellen Hauptknoten. Damit reduziere ich ReferencesSuper auf O(1) mit einem push und pop der currentFunctionNode zu Beginn und zu Ende der jeweiligen Funktion (FDecl, FExpr, Method, ) und das Muster lässt sich bei den anderen Semantics ebenfalls anwenden. Bei den VarNames nehme ich jetzt die SymbolTabelle, um die Listen zu erzeugen und die statischen Duplikatchecks zu machen, für Contains eine ähnliche Folge von Objekten mit Stack. Sowas wie für die currentFunctionNode. Einen LL(1) kann man wunderbar mit Stacks ausstatten, finde ich. Nur manchmal muss ich mich zusammenreissen, dass dann auch zu tun, weil ich Anfänger bin und das nicht mein Job ist ;-).

    function cloneFunction (func) {
        var newFunc = OrdinaryFunction();
        setInternalSlot(newFunc, "Scope", getInternalSlot(func, "Scope"));  // heisst jetzt "Environment" muss noch global ersetzt werden
        setInternalSlot(newFunc, "Code", getInternalSlot(func, "Code"));
        setInternalSlot(newFunc, "FormalParameterList", getInternalSlot(func, "FormalParameterList"));
        setInternalSlot(newFunc, "ThisMode", getInternalSlot(func, "ThisMode"));
        setInternalSlot(newFunc, "FunctionKind", getInternalSlot(func, "FunctionKind"));
        setInternalSlot(newFunc, "Strict", getInternalSlot(func, "Strict"));
        return newFunc;
    }

    function CloneMethod(func, newHome, newName) {
        Assert(IsCallable(func), "CloneMethod: function has to be callable");
        Assert(Type(newHome) == "object", "CloneMethod: newHome has to be an object");
        Assert(Type(newName) === "undefined" || IsPropertyKey(newName), "CloneMethod: newName has to be undefined or object");
        var newFunc = cloneFunction(func); 
        if (getInternalSlot(func, "NeedsSuper") === true) { // compareInternalSlot(obj, name, value) habe ich darauf erfunden
            setInternalSlot(newFunc, "HomeObject", newHome);
            if (newName !== undefined) setInternalSlot(newFunc, "MethodName", newName);
            else setInternalSlot(newFunc, "MethodName", getInternalSlot(func, "MethodName"));
        }
        return NormalCompletion(newFunc);
    }

Kommentar über noch vorhandenes interAMD Objekt entfernt.

Ich werde gleich fluchen, wenn ich lese, wie ich den Loader anpassen muss. Das Promise scheine ich ja auch das dritte mal schreiben zu können, hehe. Aber viele kleine Änderungen. Die %intrinsic% Tabelle ist immernoch nicht voll. Die currentFunctionNode werde ich auch bei InTailPosition nutzen, damit kann ich bei return oder der letzten Code Zeile die ausgeführt wird currentFunctionNode.tailPosition taggen. Ich will auch meinen CallStackArray stellvertretend für den TailCall um einen Context reduzieren, ist ja wohl logisch.

Von wegen...

        var FunctionPrototype_toMethod = function (thisArg, argList) {
            var superBinding = argList[0];
            var methodName = argList[1];
            if (!IsCallable(thisArg)) return withError("Type", "this value is not callable");
            if (Type(superBinding) !== "object") return withError("Type", "superBinding is not an object");
            if (methodName !== undefined) {
                methodName = ToPropertyKey(methodName);
                if ((methodName = ifAbrupt(methodName)) && isAbrupt(methodName)) return methodName;
            }
            return CloneMethod(thisArg, superBinding, methodName)
        };

        LazyDefineBuiltinFunction(FunctionPrototype, "toMethod", 1, FunctionPrototype_toMethod /*, realm  !!!*/);

Symbole waren auch wieder mal an der Reihe. Die besprochenen for und keyFor Funktionen. Die Kommentare am Rand habe ich hier auf die Seite geschrieben.

        var GlobalSymbolRegistry = Object.create(null); // Das ist meine Liste. Ein Hash, sofern ein Stringkey vorhanden ist. Und ich die schnell xsen will

        var SymbolFunction_keyFor = function (thisArg, argList) {
            var sym = argList[0];
            if (Type(sym) !== "symbol") return withError("Type", "keyFor: sym is not a symbol");
            var key = getInternalSlot(sym, "Description");
            var e = GlobalSymbolRegistry[key];
            if (SameValue(e.symbol, sym)) return NormalCompletion(e.key);                               // ich koennte direkt key nehmen wegen dem hash
            Assert(GlobalSymbolRegistry[key] === undefined, "GlobalSymbolRegistry must not contain an entry for sym");
            return NormalCompletion(undefined);
        };

        var SymbolFunction_for = function (thisArg, argList) {
            var key = argList[0];
            var stringKey = ToString(key)
            if ((stringKey = ifAbrupt(stringKey)) && isAbrupt(stringKey)) return stringKey;
            var e = GlobalSymbolRegistry[key];
            if (e !== undefined && SameValue(e.key, stringKey)) return NormalCompletion(e.symbol);
            Assert(e === undefined, "GlobalSymbolRegistry must currently not contain an entry for stringKey");
            var newSymbol = SymbolPrimitiveType();
            setInternalSlot(newSymbol, "Description", stringKey);
            GlobalSymbolRegistry[stringKey] = { key: stringKey, symbol: newSymbol }; 
            return NormalCompletion(newSymbol); // There is a Typo newSumbol in the Spec. 
        };

        LazyDefineBuiltinFunction(SymbolFunction, "for", 1, SymbolFunction_for);
        LazyDefineBuiltinFunction(SymbolFunction, "keyFor", 1, SymbolFunction_keyFor /* ,realm */);

/*
es6> Symbol.for("Hallo");
{ Description: 'Hallo',
  Bindings: {},                 // Issue: Hat noch Binding Records
  Symbols: {},                  // Issue: Hat noch Symbol Bindings
  Prototype: null,  
  Extensible: false,        
  Integrity: 'frozen',          // Das ist von Beginning A.D. 
  uniqueSymbolID: 9977.562215400394 }   // Der geheime Propertykey für ES5.
es6> let s = Symbol.for("Hallo");   // gibt das soeben erzeugte Symbol zurück. (Man kann mit fuzzy keys also den Speicher vollmüllen, weil das nicht collected wird, oder???)
undefined
es6> Symbol.keyFor(s) === "Hallo"
true
es6> 
*/

Und noch vieles mehr gibt es zu entdecken. Ich bin noch nicht durch.

es6> f(1.2234)
1.2234
es6> var f = (a) => a+1;
undefined
es6> f(1.2234)
2.2234
es6> var f = a => a+1;
undefined
es6> f(1.2234)
2.2234
es6> var f = a => a+2;
undefined
es6> f(1.2234)
3.2234
es6> 
undefined
es6> let g = x => x+x;
undefined
es6> g(100)
200
es6> 
Funktioniert immernoch. Ich dachte ich hätte letztens ne Exception gesehen.

Nochmal ComputedPropertyName

Für die ComputedPropertyNames (weiter unten get [s]() und [s]: value und [s]() {} und set [s](v) {}) ist mir eingefallen, daß ich ComputedPropertyName wie erklärt auch in PropertyKey() anwenden muss. Dazu muss parser.PropertyKey() auch in parser.MethodDefinition() gerufen werden, wo der Name gelesen wird. Damit kann ich die synchronisieren. Beim ersten Refaktorierungsversuch vor einigen Stunden, als ich zuhause war, entfernte das zwei Blocks und mehrere ifs. Das wollte ich auch zuerst richtig machen, musste vor dem vorhandenen Code aber erstmal resignieren und hatte es dann so eingebaut, wie es gerade unmittelbar möglich war.

Doch als ich es gerade vollenden wollte, wurde es problematisch. Ich habe die Änderung wieder zurückgespult, um sie morgen erneut dranzunehmen.

String.prototype.repeat

Kurz vor dem Draft...

linux-dww5:/www/syntax # es6
es6> let s = "abcde";
undefined
es6> s.repeat(10)
abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde
es6> 

String.prototype.repeat(count) wiederholt den String count mal. Auch diese ist eine der neuen ES6 Funktionen.

backwards progressive

Heute habe ich ein Dokument namens es6libraryextensions-3-28-2012.pdf geöffnet und ein paar Funktionen abgetippt. Darunter String.prototype.toArray. Die Funktion ist im Draft vom 8. November 2013 nicht mehr enthalten. Ein kurzer Blick ins Internet bringt hervor, die Funktion ist auch bereits gedroppt. Es gibt da Pullrequests zu und ich bin mir nicht sicher, daß sogar selbst mitbekommen zu haben. Jedenfalls ist es auch kein Verlust. Das Verhalten toArray() kann man problemlos mit s.split(""); (mit leerem String ε) herbeiführen. Hier sehen wir die Funktion, wie sie einen Array ergibt, der mit join wieder zu einem String zusammengebaut wird. Join habe ich vor einer Weile schon von der Spezifikation abgeschrieben gehabt und weiter unten öfter ausprobiert, weil ich damit Strings aus Array erzeugen kann. Wobei Array.prototype.toString() übrigens sonst eine kommaseparierte Liste ergibt, wenn man die interne toString Funktion eines Arrays ruft.

linux-dww5:/www # node syntax0.js
es6> let s = "abcdef";
undefined
es6> s.toArray().join("-");
a-b-c-d-e-f
es6> 

Und wieder forwards progressive. Die neuen String Funktionen startsWith, endsWith und Contains. Bequemer als /^()/ und /()$/ sowie /sdf/.test(str) und für alle, die eh keine Regexp können. Und besonders useful, wenn Variablen im Spiel sind, und keine Regulaeren Ausdruecke moeglich. Ausserdem gut ("http://").startsWith("http://") usw. Damit kann man schnell mal name@host.com auf name oder auf host.com prüfen. Besser als vorher.

let s = "ich bin edward";
console.log(s.startsWith("ich"));     // true
console.log(s.startsWith("ch", 1));   // true
console.log(s.startsWith("bin", 4));  // true
console.log(s.contains("bin"));       // true
console.log(s.contains("xx"));        // false
console.log(s.contains(" "));         // true
console.log(s.endsWith("edward"));    // true
console.log(s.endsWith("edwar",13));  // true
console.log(s.endsWith("edwar",12));  // false
/*
-evaluating str.js-
true
true
true
true
false
true
true
true
false
undefined
es6> 
Have a nice day.
Uptime: 153ms
*/

Refactoring time

        setInternalSlot(MathObject, "MathTag", true);
        setInternalSlot(MathObject, "Prototype", ObjectPrototype);
        // [...]
        LazyDefineBuiltinFunction(MathObject, "imul", 2, MathObject_imul);        // Das sind die nativen [[Call]] Funktionen, das _call spare ich mir.
        LazyDefineBuiltinFunction(MathObject, "log",  1, MathObject_log);          // Intern wird DefineOwnProperty mit einer CreateBuiltinFunction gerufen
        LazyDefineBuiltinFunction(MathObject, "log1p", 1, MathObject_log1p);
        LazyDefineBuiltinFunction(MathObject, "max", 0, MathObject_max);
        LazyDefineBuiltinFunction(MathObject, "min", 0, MathObject_min);
        LazyDefineBuiltinFunction(MathObject, "pow", 2, MathObject_pow);
        LazyDefineBuiltinFunction(MathObject, "sin", 1, MathObject_sin);
        LazyDefineBuiltinFunction(MathObject, "tan", 1, MathObject_tan);
        LazyDefineBuiltinFunction(MathObject, "random", 0, MathObject_random);
        LazyDefineBuiltinConstant(MathObject, "PI", PI);
        LazyDefineBuiltinConstant(MathObject, "LOG2E", LOG2E);
        LazyDefineBuiltinConstant(MathObject, "SQRT1_2", SQRT1_2);
        // [...]

Heute fiel mir auf, anstatt von NumberConstructor.Construct([...args]) oder callInternalSlot("Construct", NumberConstructor, args) (bytecodefaehig) kann ich doch OrdinaryConstruct(NumberConstructor, args); rufen, wo ich es brauche. Da wird mal der String-, mal der Boolean-, mal der Number-, mal der ErrorConstructor von mir gerufen und ich nutze die Funktion für @@create und [[Construct]] doch sowieso nach Vorschrift, da kann ich sie doch direkt rufen. Blindfisch.

Wie auch immer, den NumberConstructor und NumberPrototype habe ich ebenfalls sortiert und die Funktionen umgewandelt. Math.clz(x) habe ich gleich miteingebaut und var x = ToNumber(argumentsList[0]); ReturnIfAbrupt(x); für das Argument gewählt.

Das mache ich, weil ich gleich toPrecision, toFixed und toExponential vom Number.prototype addieren wollte.

clz

Just for fun

(Mathematische Korrektheit ohne Gewähr, noch ist die keine Alltagsfunktion für mich. (Noch nicht.))

Gerade guckte ich, was es-discuss bietet. Gerade ging es darum, ob CLZ von Number nach Math soll. Gute Idee. Und ich habe dadurch herausgefunden, was CLZ heisst. Count Leading Zeros. Und das sind die Nullen, die links von der hoechstwertigen 1 der Repräsentation (im Dualsystem, binär) stehen. Erstmal habe ich die fehlende Funktion zu syntax.js addiert. Danach habe ich sie noch um den Fall if (n === 0) korrigiert, als ich darauf eine zweite Version mit x >> i schrieb, was O(lg x) Shifts (um lg lg x Bits?) erfordert. Kürzen kann man das auf lg x Bits mit x >>= 1. Am darauffolgenden Tag las ich morgens im Bus in der Spezifikation, daß ich NOTE überlas, wo der Fall 0 die Zahl 32 und der Fall MSB = 1 (Vorzeichen negativ) die Zahl 0 zurückgeben soll.

Inzwischen habe ich gelesen, es gibt count leading zeros, count trailing zeros, count leading and trailing ones, und noch ein paar solcher Funktionen. Ich glaube, auch darum ist die Idee, Math.bitlen zu definieren ganz gut. Aber die Männer wissen da sicherlich mehr aus Erfahrung.

 DefineOwnProperty(NumberPrototype, "clz", {
            configurable: true,
            enumerable: false,
            value: CreateBuiltinFunction(function (thisArg, argList) {
                var x = thisNumberValue(thisArg);   // Let x be thisNumberValue(this value)
                if ((x = ifAbrupt(x)) && isAbrupt(x)) return x; // ReturnIfAbrupt(x) wg. thisArg
                var n = ToUint32(x);                // Let n be ToUint32(x);
                if ((n=ifAbrupt(n)) && isAbrupt(n)) return n; // ReturnIfAbrupt(n)
                if (n < 0) return 0;		// NOTE if the most significant bit is set (das heisst, negative Zahl) return 0;
                if (n === 0) return 32;		  // Fix für n.toString(2).length === 1 bei 0
                var bitlen = Math.floor(Math.log(Math.pow(n, Math.LOG2E))) + 1;  // Logarithmus zur Basis 2 von X ist eine rationale Zahl
                var p = 32 - bitlen;                                          // Führende Nullen bei 32 bit ausrechnen.
                return NormalCompletion(p);       
            }, 0, "clz"),
            writable: true
  });

Refaktoriert wird´s in NumberPrototype_clz = function (thisArg, argList) {} und LazyDefineBuiltinFunction(NumberPrototype, "clz", 0, NumberPrototype_clz); Ich habe gerade nur eine leere "" Funktion auf dem NumberPrototype die schon da war ausgefüllt. Beziehungsweise, hinterher kann ich sie nach MathObject_clz transferieren.

// Count Leading Zeros heisst die Funktion, ich denke, sie wird in der Arithmetik der Computerspiele häufiger vorkommen, daß sie in ES6 hinzukommt.

function clz(x) {
    var i = 0;
    if (x < 0) return 0;      // NOTE (b) if MSB set return 0
    while ((x >> i) != 0) {	  // Anstatt (+n).toString(2)
	     i++;
    }
    return 32 - i;
}
// let clz = (x) => { var i = 1; if (x < 0) return 0; if (x === 0) return 32; while ((x >>= 1) != 0) ++i; return 32 - i; };

console.log(clz(-1));
console.log(clz(0));
console.log(clz(1));
console.log(clz(2));
console.log(clz(4));
console.log(clz(7));
console.log(clz(8));
/*
0
32
31
30
29
29
28
*/

Direkt den Logarithmus zu nehmen sieht wie die beste Loesung aus. Wobei mit dem Shift das direkt ausgerechnet wird. Was die Funktionen spart. Ich finde beide gut.

function clz(x) {
    return x < 0 ? 0 : 
            x === 0 ? 32 : 
              32 - Math.floor(Math.log(Math.pow(x, Math.LOG2E))) + 1;     // clz = 32 - Boden(Log von x zur Basis 2) + 1
}
console.log(clz(-1));
console.log(clz(0));
console.log(clz(1));
console.log(clz(2));
console.log(clz(4));
console.log(clz(7));
console.log(clz(8));
/*
0
32
31
30
29
29
28
*/

get [symbol] () {...}

ES6 hat Symbols. Damit kann man praktisch private oder besser halbprivate Properties erzeugen. Ich schrieb erst unsichtbar, doch das sind sie nicht, es gibt zum Beispiel Object.getOwnPropertySymbols, um ihre Descriptions auszulesen. Der Besitzer eines Symbols, es kann weitergegeben werden, kann sie benutzen. Man braucht keine Objekte mehr mit ._name Properties, die darauf hindeuten sollen, daß sie (die _unterstrichNamen) nicht einfach geändert werden sollen und zu einer API gehören, man kann einfach ein Symbol nehmen. Beim Erweitern der Methoddefinition um getter und setter, die ich dafür aus dem losen Code entfernt habe, fiel mir dann auch auf, daß der PropertyName ein ComputedPropertyName sein kann. Das ist die neue { [symbol]: AssignmentExpression } Syntax. Die gilt aber auch für Methoden { [symbol]() { } } und damit auch für { get [symbol] () {} } und { set [symbol] (v) {} }, weil der PropertyName aus { PropertyName: AssignmentExpression } oder { MethodDefinition }, MethodDefinition := get|set? PropertyName ( FormalParameters ) { FunctionBody } auch eine in [] gewrappte AssignmentExpression sein kann welche dann ComputedPropertyName heisst und zu einem Symbol evaluieren muss. Die Regel, daß sie zum String werden kann, also normal { ["s"]: AssignmentExpression }) gibt es noch nicht. GeneratorMethod bezeichnet übrigens * PropertyName ( FormalParameters ) { FunctionBody }, darum gilt die Computed Property natürlich auch bei Generatoren.

let s = Symbol("test");
let obj = {
    v: 1000,
    get [s] () { return this.v; },    // [s] ist der ComputedPropertyName, ein PropertyName, 
    set [s] (v) { this.v = v; }       // der zur Zeit entweder zu einem Symbol evaluieren muss, oder man throwt.
};

obj.v = 100;
console.log(obj[s]);
obj[s] = 20;
console.log(obj[s]);
console.log(Object.getOwnPropertyDescriptor(obj, s).get.name);
console.log(Object.getOwnPropertyDescriptor(obj, s).set.name);

/*
linux-dww5:~ node syntax0.js sym2.js
-evaluating sym2.js-
100
20
get [test]
set [test]
undefined
es6> 
Have a nice day.
Uptime: 2484ms
*/

Ich habe mich der Aufgabe gerade angenommen und erstmal ein wenig Chaos gemacht, weil ich parser.PropertyDefinition, parser.MethodDefinition, sowie evaluation.PropertyDefinition und evaluation.MethodDefinition erstmal anpassen musste. Cool finde ich die direkte Zusammenarbeit mit SetFunctionName, die [symbol] Klammern gibt es for free. Es wird die Symbol.[[Description]] genommen, falls vorhanden ansonten der leere String "" plus die eckigen Klammern rum als Zeichen für das Symbol. Da bei der MemberExpression im AST node.computed für obj[s] existiert, habe ich damit die Property .computed an der MethodDefinition eingeführt, damit ich das nicht umständlich prüfen muss, obs ein String oder ein Symbol ist.

let s = Symbol("method");
let obj = {
    v: 1000,
    [s] (x) { return this.v + x; }
};
obj.v = 100;
console.log(obj[s](100));
console.log(Object.getOwnPropertyDescriptor(obj, s).value.name);

/*
-evaluating sym.js-
200
[method]
undefined
es6> 
Have a nice day.
Uptime: 345ms
*/

Ich sehe, ich muß die Seite dringend mal neu machen.

Method Definitions

Montag: Vormittags konnte ich wieder in der Spec lesen und mich aufwärmen. Ich hoffe mich jetzt einfach wieder täglich ranzumachen, denn ich habe das Notebook genug erkundet und im Dezember zu wenig getan, weil wir abgelenkt waren.

Abends habe ich die MethodDefintion für die ObjectExpression neu definiert und dabei die komplette PropertyDefinitionEvaluation überarbeitet. Ergebnis: Concise Methods und get name() und set name(v) Funktionen, die "get name" und "set name" heissen und korrekt arbeiten. Desweiteren habe ich das re-definieren von PropertyDeskriptoren mit Object.defineProperty probiert und konnte dabei noch weitere Bugs entfernen, wie, dass nach einem [[Set]] nur der valueDesc { value: newValue } geblieben war, jetzt werden die anderen Props wieder übernommen.

let obj = {
    f: function () { return 10; },
    g() { return 20; }
};
console.log(obj.f()+obj.g());
let dict = { 
    get a () { return obj.f(); },
    get b() { return obj.g(); },
    set c(v) { this.v = v; }
};
console.log(dict.a);
console.log(dict.b);
console.log(dict.c=20);
console.log(dict.v);

/*
linux-dww5:~ node syntax0.js obj.js
-evaluating obj.js-
30
10
20
20
20
undefined 
es6> 
Have a nice day.
Uptime: 536ms
*/

Dann habe ich noch einen dicken Bug in den Arguments aus den letzten Tagen entfernt. Erst ging der Setter nicht mehr, weil keine Argumente mehr kopiert wurden. Jetzt laufen alle Funktionen wieder. Ich hoffe mich jetzt wieder täglich ranzuhalten.

Gerade habe ich weiter refaktoriert, die DefineOwnProperty/CreateBuiltinFunktion in LazyDefineBuiltin zu ändern. Weil globale und Builtins nicht enumerable sind, und (fast) alle praktisch die gleichen Deskriptoren haben - aber grundsätzlich um aufzuräumen und wegen dem Realm, dass ich die Call Funktionen nur 1x generieren muss und immer nur passen, aber nicht jedesmal neu erzeugen muss. Mit mehreren Realms spielen wir später noch genug.

 
linux-dww5:/www # node syntax.js
es6> (new Loader({})).toString() + "!!!";
[object Loader]!!!
es6> (function () { return 10; }());
10
es6> (function () { return 10; })();
10
es6> (function () { return 10; })() +10;
20
es6> 

THREE.js

THREE.js soll ja für Dummies sein. Eine solide und besonders einfache Library. Ich hoffe das ist jetzt nicht schwerer für mich, weil ich mich doof stellen soll. Man nehme Strg-Pfeil zum Bewegen der Kamera. Shift-Pfeil zum drehen des Würfels. Und ALT-Pfeil für den Text. Dafür muss man erstmal ´ne Font laden. Erstmal.

Mit return hört man eine Snare mit der Webaudio API, sofern vorhanden. Mit dem Oscillator sollte ich Töne generieren lernen.

Es gibt einen Scene-Editor (beta) im /editor Verzeichnis und alle /examples ebenfalls. Im THREE.js zip File enthalten. In ein paar Wochen bin ich dann fit.

Abt. Leere Versprechen am Donnerstag

Ohne promises-unwrapping und promisesaplus.com wird das nichts mehr, ich bin out of knowledge hier und muss die Regeln für´s Aplus erstmal lesen.

124 passing (4s)
35 failing

Ohne [[Resolve]](promise, x) abzuschreiben wird das nichts.

133 passing (5s)
68 failing

Donnerstag. Heute dachte ich mir, eine Shell im Browser muß her. Und verschwendete etwas Zeit damit. Eigentlich nur 10 Minuten. Gespielt habe ich eine Stunde mit.

test262

Mittwochs

test262-discuss Mailing Liste.

Test262 bei Google eingegeben gibt gerade mal 18.100 Einträge.

Dabei gibt es eine Domain. test262.ecmascript.org Und es gibt 80MB ES5 Tests, die man downloaden kann. Ich hab die schon lange auf Platte. Und noch nie probiert. Aber heute...

Im tools/packaging Verzeichnis liegt "test262.py" mit dem ich mit "./test262.py --command='node syntax0.js' --tests=../../" wohl die Tests gestartet habe.

...
ch07/7.6/7.6.1/7.6.1-3-7 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-3-8 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-3-9 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-1 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-2 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-3 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-4 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-5 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-4-6 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-8-8 passed in non-strict mode
ch07/7.6/7.6.1/7.6.1-8-9 passed in non-strict mode
...
=== ch07/7.6/7.6.1/7.6.1.2-1gs was expected to fail in strict mode, but didn't ===
=== ch07/7.6/7.6.1/S7.6.1_A1.1 was expected to fail in non-strict mode, but didn't ===
=== ch07/7.6/7.6.1/S7.6.1_A1.2 was expected to fail in non-strict mode, but didn't ===
...
ch07/7.6/7.6.1/7.6.1.2/7.6.1-18-s passed in strict mode
ch07/7.6/7.6.1/7.6.1.2/7.6.1-19-s passed in strict mode

Hier habe ich zwischen jedem Test CTRL-D gedrückt, das syntax.js Shell Skript ist zur Zeit debugging konfiguriert, nach ausführen einer Datei auf dem Prompt zu landen. Ich frage mich, ob das alles stimmt und werde dem nachgehen. Denn die Sprache macht mir wirklich auch Spaß.

1/2 Promise

// promises-aplus-tests-adapter
exports.deferred = makePromise;
exports.resolve = function (value) { return makePromise(function (resolve) { resolve(value); }); };
exports.reject = function (reason) { return makePromise(function (resolve, reject) { reject(reason); }); };

Das folgende kam bei dem mittlerweile wieder gelöschten Code raus. Promises/A+, über den Link der Spec kommt man auch zum Github Repo mit den Tests.

// node cli.js /www/prom/promise.js

94 passing (14s)
106 failing
...
  1) 2.2.1: Both `onFulfilled` and `onRejected` are optional arguments. 2.2.1.1: If `onFulfilled` is not a function, it must be ignored. applied to a promise rejected and then chained off of `onFulfilled` is `undefined`:
     Error: timeout of 200ms exceeded
      at null. (/usr/local/lib/node_modules/promises-aplus-tests/node_modules/mocha/lib/runnable.js:165:14)
      at Timer.listOnTimeout (timers.js:107:15)
...
  106) 2.3.3: Otherwise, if `x` is an object or function, 2.3.3.3: If `then` is a function, call it with `x` as `this`, first argument `resolvePromise`, and second argument `rejectPromise` 2.3.3.3.1: If/when `resolvePromise` is called with value `y`, run `[[Resolve]](promise, y)` `y` is not a thenable `y` is `undefined` `then` calls `resolvePromise` asynchronously via return from a fulfilled promise:
     Error: timeout of 200ms exceeded
      at null. (/usr/local/lib/node_modules/promises-aplus-tests/node_modules/mocha/lib/runnable.js:165:14)
      at Timer.listOnTimeout (timers.js:107:15)

neues gebrauchtes notebook

Ich arbeite mich in mein neues second hand notebook ein. Ich hab jetzt Sublime Text statt mcedit. Mit Multimedia habe ich mich auch versorgt. Ich kann meiner Hip-Hop Berufung wieder nachkommen.

Gestern habe ich mit der WebAudio API angefangen, und schonmal gelernt, die Samples zu laden, zu dekodieren, und abzuspielen.

Gerade schreibe ich am ES6 [[Loader]] weiter. Ich bin ganz unten auf Seite 40 bei LinkDeclarativeModules. Das ist eine der längsten Funktionen, und ist die, die auf die Static Semantics zugreift und damit die Schnittstelle ins letzte EcmaScript 6 Draft ist. Mit dem neuen Editor ist das tippen viel angenehmer und die Tastatur klemmt nicht mehr.

961.783 Bytes

Dezember

Extrem Geil: 100 Uebungsaufgaben zu Grundlagen der Informatik mit Videolesungen.

Inzwischen schreibe ich die Builtins auf eine neue Art und Weise.

Mehr Realm und Loader Infos.

https://gist.github.com/dherman/7568885, https://gist.github.com/dherman/7568080.


// nur 1x erzeugen
var PromiseConstructor_all = function (thisArg, argumentsList) { ... };
var PromiseConstructor_cast = function (...) { ... };
var PromiseConstructor_race = function (...) { ... };
var PromiseConstructor_reject = function (...) { ... };

// jedes Realm erzeugbar
var PromiseConstructor = OrdinaryFunction();
var PromisePrototype = OrdinaryObject();
MakeConstructor(PromiseConstructor, true, PromisePrototype);
LazyDefineBuiltinFunction(PromiseConstructor, "all", 1, PromiseConstructor_all);
LazyDefineBuiltinFunction(PromiseConstructor, "cast", 1, PromiseConstructor_cast);
LazyDefineBuiltinFunction(PromiseConstructor, "race", 1, PromiseConstructor_race);
LazyDefineBuiltinFunction(PromiseConstructor, "reject", 1, PromiseConstructor_reject);

// noch was vereinfacht
function LazyDefineBuiltinFunction(O, name, arity, fproto) {
    return callInternal("DefineOwnProperty", O, name, {
	value: CreateBuiltinFunction(fproto, arity, name),
	enumerable: false,
	writable: true,
	configurable: true
    });
}

Zeit, die Promises A+ Spec fortzusetzen. Der Loader ist die erste Komponente, die sie einsetzt. Und wie mir beim Lesen auffaellt, sie ist seit letztens ueberarbeitet worden und voellig anders!!!

js6> (new Loader({ "realm": new Realm() })).toString()
[object Loader]
js6> 

Besonders interessant: Der Loader nutzt anders als die anderen Funktionen in ES6 eine bestimmte Technik, die meisten Objekte als JavaScript Objekte zu erzeugen, und dafuer eine Menge native Funktionen zu dedizieren, die einen oder mehrere extra [[Slots]] haben, deren Daten sie zum Beispiel zurueckgeben wie bei F.[[ConstantValue]] einer ConstantGetterFunction. Das hebt sich von der restlichen Spezifikation ab, da diese Technik noch nicht drankommt. Bzw. Function.prototype.bind erzeugt (fast) als einzige Funktion so eine Funktion mit mehreren [[Slots]].

js6> .print module "m" { export let x = 10; }
{
    "type": "Program",
    "body": [
        {
            "type": "ModuleDeclaration",
            "strict": true,
            "id": "m",
            "body": [
                {
                    "type": "ExportStatement",
                    "exports": {
                        "type": "LexicalDeclaration",
                        "declarations": [
Neben den Runtime Semantics, die ich jetzt endlich entdeckt habe, muss aber auch die ParseNode stimmen.

Dienstag: Aha, herausgefunden. Im js-loaders Repository auf Github liegen die Dokumente mit dem ES6 Draft Text vom 2. Dezember. Und ich wundere mich seit Wochen, wo das Update vom 8. November bleibt. Ich fange sofort an...meine Liebste hat da kein Problem mit.

Montag: Waren wir bei Frank Zanders Weihnachtsfeier fuer Obdachlose und hilfsbeduerftige Menschen. Vorher habe ich mit dem %Loader% angefangen, genaugenommen mit dem %Realm%.

Sonntag: Habe meinen [[Enumerate]] gerade verbessert. Ich hatte noch ein Array.indexOf drin. Nun laeuft er mit O(n).

In "Digitaltechnik und Entwurfsverfahren" (geil) wird der IEEE 754 durchgenommen. Richtig ausfuehrlich, dass man aus dem Kopf rechnen kann. Zufaellig brauche ich den gerade.

Hab ausserdem "Grune, Jacobs: Parsing Techniques - A practical approach" letzten Dienstag bei der PSB gedrueckt und lese fleissig. Im Internet gibt es Reihen, wie Prof. Aikens Compilers, die man sich vorher angucken sollte.

Habe gestern mit %TypedArray% weiter gemacht.

Das Modul fuer kompletten Unicode Support ist (nur) 1MB gross.

Ich habe das Realm refaktoriert und createRealm() raus gemacht, um fuer jedes Realm einen Satz Intrinsics mit createIntrinsics() liefern zu koennen. War schon immer so in der Spec definiert, dass ein Realm einen Satz Intrinsics kriegt.

Ich lacke immernoch in js-loaders und der Implementation des Modulsystems. Im Draft vom 8. November sind offiziell die static Semantics drin. Den Rest suche ich noch.

Der letzte diff ist 12804 Zeilen lang und nicht komplett.

Ich muss ausserdem nochmal mindestens eine ganze Stunde mit dem Draft abgleichen, wozu ich noch nicht gekommen war.

PASS: assert: actual=undefined, expected=undefined; f(10, 20);
PASS: notEquals: actual=undefined, expected=10; a;
PASS: notEquals: actual=undefined, expected=20; b;

Ich hab die Shell mit ins Skript geholt, und das Exportieren und Starten der Anwendung ueberholt.

Ich habe drei Tage gar nichts gemacht. (Und danach CS75 von der Harvard Uni und ein PHP Blog angefangen, was wohl im Januar anstelle dieser Seite hier steht.)

Aber auch alle parser.* in this.* getauscht und ein paar Counter Funktionen darauf dadurch ausprobiert.

Ich hab so einiges inzwischen ueberholt.

Yes, i also fixed my ValidateAndApplyPropertyDescriptor(O,P,extensible,Desc,current) in the next version.

November

linux-qc59:/es6/asm # ./a.out 
1+1= -1217527820
2+1= -1217527820
1+2= -1217527820
100+200= -1217527820
200+100= -1217527820
Abb. 1 Typische Rechnung des Jobcenters was eine Fortbildung fuer Herrn Gerhold fuer das Jobcenter bedeute. Da hat man wortwoertlich die falschen Register gezogen.

Die Fortbildung zum Linux-Administrator wurde abgelehnt, obwohl realistisch die Notwendigkeit besteht. Ich habe diesmal vor, mich anwaltlich beraten zu lassen. Nach SGB I habe ich einen Anspruch darauf, sei die Notwendigkeit mangels Ausbildung (und zusaetzlich durch Notsituationen wie fuer 20 Euro Tagessatz im Wohnheim leben, liebes Jobcenter,) doch gegeben. Ich habe schon immer gesagt, diesen Laden muesste man eigentlich verklagen.

(Beim debuggen)

Startup: 1900ms
js6> break;
Break is not allowed in the outer script body
SyntaxError: Break is not allowed in the outer script body
    at EarlyErrors (/se/syntax.js:3546:45)
    at Program (/se/syntax.js:6775:17)
    at CreateTheAST (/se/syntax.js:7077:34)
    at ExecuteTheCode (/se/syntax.js:23044:57)
    at evaluate (/se/shell.js:42:17)
    at /se/shell.js:111:6
    at Interface._onLine (readline.js:192:5)
    at Interface._line (readline.js:523:8)
    at Interface._ttyWrite (readline.js:798:14)
    at ReadStream.onkeypress (readline.js:98:10)
    at ReadStream.EventEmitter.emit (events.js:106:17)
    at emitKey (readline.js:1135:12)
    at ReadStream.onData (readline.js:878:14)
    at ReadStream.EventEmitter.emit (events.js:103:17)
    at readableAddChunk (_stream_readable.js:156:16)
    at ReadStream.Readable.push (_stream_readable.js:123:10)
    at TTY.onread (net.js:508:20)
js6> 
Abb. 2 Die neue Fassung von Early Errors benutzt parallel zur SymbolTabelle einen Hash statt eine rekursive Contains Funktion, um fruehe Fehler in konstanter statt baumhoher und -breiter Zeit (n) abzufangen.

Man kann mit der uebrigens lexNames und varNames mitgenerieren.

Zu dem mutierten Parser-API-AST beabsichtige ich was zu schreiben, um meine paar selbstdefinierten Nodes mal offiziell zu definieren.

function f(a,b) {
    return;
}

Jetzt habe ich den Bug gefunden. Das ist der, warum die Instanz c der Klasse C weiter unten keine noDefaults Methode mehr ruft. Weil der Parameter c die Instanz ueberschrieben hat. Doch der gehoert zu method(). Und aus selbem Grund funktioniert der Automat mit Fkt. Delta ganz unten nicht. Oder function recur(10) die 79 und nicht 7 ergeben muss.

let el = "#testreport50";
function F () {
    this.good = true;
}
class C extends F {
    method() {
	return "Methode";
    }
    static method() {
	return "Statische Methode";
    }
}
console.html(el,C.method());
let c = new C();
console.html(el,c.method());
console.html(el,c.good);
class D extends C {
    method() {
	return "overwritten method";
    }
}
let d = new D();
console.html(el,d.method());
let el = "#testreport47";
console.html(el, Object.toString());
console.html(el, Symbol.toString());
let e = new Emitter(); 
let el = "#testreport46";
let first = name => console.html(el,"name = "+name);
e.on("first", first);
e.emit("first", "Eddie");
e.emit("first", "Puff Daddy");
e.once("first", name => {
    console.html(el,"einmal sollte ich nur aufgerufen werden mit Mr. Eins. Hier ist: "+name);
});
e.emit("first", "Mr. Eins");
e.emit("first", "Mr. Zwei");
e.remove("first", first);
e.emit("first", "nach remove von 'first'");
let second = (name) => {
    console.html(el,"2. = "+name);
};
e.once("second", second);
e.on("second", second);
e.on("second", second);
e.emit("second", "dreimal");
e.emit("second", "zweimal");
e.emit("second", "nochmal zweimal");
e.removeAll("second");
console.html(el,"Job Done -- jetzt sollte nichts mehr emittiert werden.. achtung test:");
e.emit("first", "val");
e.emit("second", "val");
let el = "#testreport45";
switch (10) {
case 10:
case 15: 
    console.html(el,"1. Richtig");
case 11: 
    console.html(el,"1.1 Richtig durchgefallen");
    break;
case 12:
    console.html(el, "Falsch durchgefallen");
    break;
default:
    console.html(el, "Unmoegliche Ausgabe");
    break;
}
switch (10) {
case 10:
    console.html(el,"2. Richtig");
    break;
case 5:
    console.html(el, "Falsch");
    break;
}
    
switch (10.1) {
case 10.2: 
    console.html(el, "Wrong!");
    break;
default:
    console.html(el, "3. Default ist richtig");
    break;
case 5:
    console.html(el, "Kann nicht sein.");
    break;
}

Ich brauche kein halbes Jahr dafuer, ich mache nur nichts den Tag. ;-) (Wie auch immer, drei Monate sind um.)

js6> function f(...rest) { let [y,...less] = rest; consume(y); return less; }
...
js6> f.toString()
function f(...rest) {
    let [y, ...less] = rest;
    consume(y);
    return less;
}

Ich hab heute erstmal was anderes gemacht. TemplateStrings, GetTemplateCallSite, SubstitutionEvaluation, ArgumentListEvaluation.

let a = 10, b = 22, c = 43; 
let s = String.raw(`${a} + ${b} + ${c}`);
let name = "Edward", job = "macht Strings.";
let t = String.raw(`${name} ${job}`);
let raw = String.raw;
let u = raw`Edward sagt, "${s}".`;

Sowie Pflichtarbeiten, die schon laengst haetten erledigt sein koennen.

let s = encodeURI("Edward ist dumm");

Wenn das laeuft, ist der ueberarbeitete Parser upgeloaded.

class C {
    constructor(...args) {
	this.name = "Klasse C";
	this.pupil = "Eddie";
	this.sum = 0;
	args.forEach(a => this.sum+=a);
    }
}
let c = new C(1,2,3);
console.html("#cc",c.name);
console.html("#cc",c.pupil);
console.html("#cc",c.sum);

Sehr Langsam ist der Interpreter zur Zeit.

Bevor ich weitermache, ueberhole ich erstmal den Parser.

let el = "#stringiterator";
let s = "Edward ist toll";
let v = { "E":true, "a": true, "i":true, "o":true };
for (x of "adsjlskdfjlsdf") console.html(el, x);
let a = [for (x of s) if (v[x]) x];
let b = [for (x of "adsjlskdfjlsdf") if (v[x]) x];

Die letzten Tage ist mal wieder nichts passiert, hab das Wochenende nichts geschafft.

js> 1;;;var x = 10;;;;
1

Im letzten Draft sind revocable Proxies eingegangen, jetzt kann man die Verbindung wieder kappen.

let s = Symbol();
let handler = {
    [s]: "#output2",
    invoke (prx, p, a, r) {
    	console.html(this[s], "invoke accessing property "+p);
	return target[p].apply(r,a);
    }
};
let target = { f() { return 100; } };
let {proxy:o,revoke} = Proxy.revocable(target, handler);

Ich vergass das ! zu implementieren. Ich guckte gerade, warum der "transpilierte Generator" darunter nicht (auf dem Emulator) ausfuehrt.

let b = false;
let a = true;
let c = !undefined;

Schon ewig drin, aber noch nicht getestet. Das with Statement.

let a, b, c;
let o = {
    aaa: 100,
    bbb: 200,
    ccc: 300
};
let arr = ["a","b","c","d"];
with (o) {
    a = aaa;
    aaa = 10;
    b = bbb;
    bbb = 20;
    c = ccc;
    ccc = 30;
    o.ddd = 400;
    try {
	eee = 500;
    } catch (ex) {
	o.eee = ex.message;
    }
};
let v, w;
with (arr) {

    try { 
	values = "Array.prototype.values ist mit Array.prototype[Symbol.unscopables]['values'] blockiert/unscopable."
    } catch (ex) {
	v = ex.message;
    }
    
    try {
	entries = "Array.prototype.entries ist mit Array.prototype[Symbol.unscopables]['entries'] blockiert/unscopable."
    } catch (ex) {
	w = ex.message;
    }
}
let el = "#iftests";
let x = 10, y = 9;

if (x*2 < y*y) {
    console.html(el, "10*2 ist kleiner 9*9. Und die Precedene funktioniert soweit auch.");
} else {
    console.html(el, "Wenn dieser Satz angezeigt wird, muss der Programmierer noch mehr ueben (muss er eh).");
}
let z = (x > y) ? 100+100+100 : 200+200*2;
let zPrime = (x <= y) ? 100+100+100 : 200+200*2;

Achtung, die Maschine ist gerade besonders langsam.


let ex = new TypeError("Mein Fehler natuerlich."), m, el = "#blockwins";
try { throw ex; } catch (ex) { m=ex.message; } finally { console.html(el, m); } // yields Mein Fehler natuerlich
let {xyz} = { xyz: "ich bin xyz" };
let abc;
let o = { abc: "zwei blockstatements falls nicht parenthesised" };
({abc} = o);
let def;
[def] = ["ich bin aber auch kein bug"];
ready> ;
Parsed a top-level expr
ready> 1+2+3;
ready> Parsed a top-level expr
ready> 10&10;
ready> Parsed a top-level expr
ready> Error: unknown token when expecting an expression
ready> Parsed a top-level expr

^ Ausgabe aus einem LLVM Tutorial http://llvm.org/releases/3.2/docs/tutorial/LangImpl2.html.

linux-qc59:~ # clang++ h.cpp
linux-qc59:~ # ./a.out 
Hallo
linux-qc59:~ # 

Etwas lahm ist das Ding gerade. Ich weiss aber auch warum. Es ist geradezu absichtlich so schwer geworden. Ich habe genug Code zum Aufraeumen und Optimieren. Und ueberhaupt. Das Definieren des Programms faengt erst noch an.

let s = Symbol();
let target = { f() { return 100; } };
let handler = {
    [s]: "#output",
    get: function (prx, p) {
        console.html(this[s], "get accessing property "+p);
	return target[p];
    },
    set: function (prx, p, v) {
	console.html(this[s], "set accessing property "+p);
	target[p] = v;
	return true;
    },
    invoke: function (prx, p, a, r) {
    	console.html(this[s], "invoke accessing property "+p);
	return target[p].apply(r,a);
    },
    getOwnPropertyDescriptor () {
    
    }
};
let o = new Proxy(target, handler);

Hier wunderte ich mich ueber einen extra Zugriff, dann kam raus, ich hatte den ersten Parameter der Handler Funktionen (gibt den Proxy) mit dem target verwechselt.

yield kann seit Rev20 als Identifier im ScriptBody, in FunctionDeclarations, in BlockStatements stehen. Im Generator wird yield zum ReservedWord.

// es gibt jetzt Identifier[yield, default] oder wie die heissen. ;-)
let yield = 100;
let default = 300;
switch (10) { default: console.html("#report3", "Default Clause wurde ausgefuehrt, im Switch ist default reserviertes Wort und kein Identifier. Doch leider gehoert default zu exports default AssignmentExpression und ich liege mit dem Beispiel erstmal noch falsch."); }

Parameter gibt es einige neue in der ECMA Grammatik. Anstatt NoIn gibt es jetzt In, dann gibt es Yield, Default, GeneratorParameter, Return. Die Erlaeuterung zu Beginn des Drafts wurde ueberarbeitet. Doch leider fehlt noch die Zusammenfassung hinten, wenn man schnell lernen will, wie die Parameter ein- und ausgeschaltet werden, implementieren kann man die im LL(1) problemlos mit einem Stack, wie alles, was irgendwie verschachtelt sein kann.

let ast = Reflect.parse("let me = 42.2;");
var expr = Reflect.parse("obj.foo + 42").body[0].expression; // Habe dafuer extra das ExpressionStatement bei list.length === 1 eingeschaltet.

Die Zukunft dieses Programms: http://llvm.org/releases/3.2/docs/CodeGenerator.html#code-generator. http://llvm.org/releases/3.2/docs/LangRef.html. Naechste Woche kann ich wieder an den Drucker.

Neues ES6 Draft. http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts#november_8_2013_draft_rev_21. Ich bin mit Rev20 noch nicht fertig. Naechste Woche kann ich neu drucken (Rev 16 ist mein Druck, den ich mittlerweile (nur nicht wortwoertlich) auswendig kann, und 17-20 sind, wenn aufgefallen teils eingebaut, aber ein Druck-Update waere fuer mich richtig), ich habe extra etwas gewartet.

	// Ueberarbeitete Type Funktion in meinem Code
	function Type (O) {
	    var type = typeof O;
	    var tostring;
	    if (type === "object") {
		if (O === null) return "null";
	    	tostring = O.toString();
		if (tostring === "[object CompletionRecord]") return Type(O.value);
		return object_tostring_to_type_table[tostring] || "object";
	    }  
	    return type; // return primitive_type_string_table[type];
	} 
	// Vorher 20 schnell hingeschmierte instanceof Tests

Neue static Semantics gibt es wie hier.

14.6.2 Static Semantics
With parameter nonTerminal

14.6.2.1 Statement Rules
StatementList : StatementList StatementListItem
1. Let has be HasProductionInTailPosition of StatementList with argument nonterminal.
2. If has is true, then return true.
3. Return HasProductionInTailPosition of StatementListItem with argument nonterminal.

Anderes - http://wiki.ecmascript.org/doku.php?id=harmony:observe - Spezifikation von Object.observe.
http://wiki.ecmascript.org/doku.php?id=harmony:observe_spec_changes - Aenderungen an den essential internal Methods fuer Object.observe.
In ES6 hat man aber bereits eine andere starke Idee, um Objekte zu ueberwachen, oder hinter einer Membrane zu verwalten: Proxies.

let el = "#consola2";
try {
    throw "Edward muss noch switch retten.";
} catch (ex) {
    console.html(el, ex);
} finally {
    console.html(el, "Muss in jedem Fall ausgefuehrt werden.");
}
try {
    console.html(el, "Nothing happens");
} catch (ex) {
    console.html(el, ex.message);
} finally {
    console.html(el, "Muss in jedem Fall ausgefuehrt werden.");
}

Geht gleich weiter.

http://github.com/domenic/promises-unwrapping (Promises/A+)

let el = "#consola";
let i = 0;
let s = "";
while (i < 10) { s+=i; i++; }
console.html(el, s);
s = "";
for (let i = 20, j = 12; i>j; i--) s+=i;
console.html(el, s);
try {
    throw "Edward muss noch switch retten.";
} catch (ex) {
    console.html(el, ex);
}
let a = ["E","d","w","a","r","d"];
for (x of a) console.html(el, x);
a.reverse().forEach((a,b,c) => console.html(el, b+"="+a));

Geht gleich weiter.

function *g() {
    let name;
    yield 20;
    yield 30;
    name = yield 40;
    yield name;
    for (let i = 0; i < 2; i++) { 
	yield (5+i)*10;
    } 
}

ResumableEvaluation.

// Das funktioniert aber noch nicht. Beim naechsten next() meldet sich mein debugging Kommentar.#
function *g() {
    yield 20;
}

Der instanceOfOperator muss auch noch berichtigt werden.

function F() { this.name = "Edward"; }
let o = new F();

Ich habe Montag endlich llvm, clang und emscripten installiert. Von den ersten kompilierten C/C++ Codes, den Demos von Lua und Python, und der hohen Qualitaet der Codes bin ich jedenfalls begeistert.

Map und Set stehen seit dem Wochenende auf meiner To Do Liste. Ob ich sie durchs anprangern schneller schreibe?

// Muss repariert werden. Habe uebrigens O(1) R/W eingebaut
let M1 = new Map();
M1.set(12, "my world");
// if (iter === undefined) return NormalCompletion(map) vor dem iter loop vergessen? ;-)
let M2 = new Map();
let o = { name: "Edward" };
let a = [1,2,3];
M2.set(o, "Objekt als Key");
M2.set(a, "Array als Key");
// Das ist noch nicht abgeglichen und der iterable Parameter ging gerade nicht.
let S1 = new Set();
S1.set(true);
S1.set(false);
S1.set(undefined);
S1.set("true");

Kurz den strict mode angetestet. Ob der durchhaelt ist fraglich (ohne Testsuite wird das nichts). Aber erkannt wird er hier schonmal.

function HatAuchStrictMode () {
    "use strict";
    this.abc = 100;
    this.def = 200;
}

let o = new HatAuchStrictMode(); // erzeugt o.abc und o.def wie zu erwarten
let p;	
let message;

try {
    p = HatAuchStrictMode(); // clobber undefined
} catch (ex) { // Parameter destructuring muss noch repariert werden sehe ich hier aber wieder
    message = ex.message;
}

function HatAuchGlobal () {
    this.xxx = 22;
    this.yyy = 33;
}

HatAuchGlobal();	// erzeugt global.xxx und global.yyy 

var s = "";
for (let i = 0; i < 1000; i++) s+=i;

Rev 20 definiert DefineMethod, warum die Klasse nicht mehr geht. Liegt nicht am Draft (ich werds verschlingen), sondern an mir. Zum einen Mr. Typo, und nebenbei noch Mr. Logic. Zwischenzeitlich habe ich den mit seinen static und Prototype Methoden nochmal berichtigt, habe aber noch zu tun. Warum recur(10) 7 statt 79 anzeigt? Muss ich rausfinden, scheint ein Seiteneffekt eines anderen Edits zu sein.

let ex1, ex2;
let el = "#report";

try {
    console.html(el, "Perfectly fine");
} catch (ex) {
    console.html(el, "Wenn das printet ist es ein Bug!");
}

try {
     throw "error"; 
} catch(ex) { 
    ex1 = ex;
    console.html(el, ex1); 
}

try { y; } catch(ex) { 
    ex2 = ex; console.html(el, ex.message); 
}

let j = 10; 

do { 
    --j; //console.html(el,j); 
} while (j >= 0)

let i = 0; 
while (i < 10) { 
    i++; //console.html(el,i); 
}

Endlich habe ich bemerkt, dass While und DoWhile versucht hatten eine StatementList (Array) statt ein Statement (Block) auszuwerten und ohne die Auswertung dessen keinen Zaehler inkrementierte und ewig lief. Der Block hat jetzt naetuerlich auch Block Scope.

function *g() {
    console.html("#report2", "Debuggen macht Spass. Und allein die Ausgabe hilft mir ungemein zu verstehen, warum der Iterator nicht will.");
    yield 20;
}

Anhand von console.html ist es heute aufgefallen. Es wird kein iterator zurueckgegeben, sondern direkt der Code ausgefuehrt. Das darf natuerlich nicht sein. Und ist der gleiche Fehler, der bei GeneratorFunction passiert. Es ist eine falsche Interpretation meinerseits. Ist aber auch schon einige Wochen her, und mittlerweile gehen die Iteratoren, und ich verstehe den Ablauf jetzt auch. Bin zuversichtlich.

class B extends Array { 
    method () { return this.length; } 
}
let b = new B();

Wie oben gesagt, die MethodDefinition der Klassen darf ich reparieren.

Oktober

function f () {
    var el = "#logg";
    console.html(el, "x:"+x, "y:"+y, "z:"+z, "a:"+a);
    var z;
    var x = 10;
    console.html(el, "x:"+x, "y:"+y, "z:"+z, "a:"+a);
    var a;
    z = 04;
    console.html(el, "x:"+x, "y:"+y, "z:"+z, "a:"+a);
    var y = 20;
    a = 33;
    console.html(el, "x:"+x, "y:"+y, "z:"+z, "a:"+a);
    var has;
    has = "has bugs";
    return has;
}
function f () {
    var el = "#debug1";
    var name = "immernoch edward";
    console.html(el, name);
    var fn = function () {console.html(el, "inside fn");return name;};
    console.html(el, fn);
    return fn;
}

Das lexikalische this will wohl nicht. Oder das Symbol. Jede Funktion f muesste sein eigenes Symbol haben, weil jeder Aufruf von one genau eine f zurueckgibt, die auf einen slot this[s] Zugriff hat. Zumindest wenn´s laeuft ;-)

const obj = {
    count: 0,
    el: "#debug2",
    one() {
	let s = Symbol();
	console.html(this.el, typeof s); 
	this.count += 1;
	this[s] = "privater slot #" + this.count;
	let f = () => this[s];
	console.html(this.el, f);
	return f;
    }
};
let g, h;
function f(a=1,b=2,c=3) { 
    return a+b*c; 
}
let a = new ArrayBuffer(10);
class MyArray extends Array {
    
    constructor(...args) {
	super(...args);
    }
    
    push(...args) {
	return super(...args);
    }
    
    pop(...args) {
	return super(...args);
    }

    top() {
	return this[this.length-1];
    }
    
}
let instance = new MyArray("E","d","w");
let a = [for (x of [1,2,3,4,5,6,7]) if (x>4) x+1];
let b = [for (x of [1,2,3,4,5,6,7]) if (x<4) x+1];
let $ = "#myElement";
let aa = ["E","d","w","a","r","d"];
let entries = aa.entries();
let s = "";
console.html($, "console.html sollte es angenehmer machen:");
for (let [k,v] of entries) {
    let x = "key="+k+", value="+v+"; ";
    console.html($, x);
    s += x;
}

Die folgenden Tests auf dieser Seite zeigen syntax(error).js, einen noch nicht ganz reifen Prototypen eines ES6 Interpreters. Den aktuellen Entwurf zu ECMA-262 Edition 6 kann man per http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts beziehen.

Der Parser hat immernoch einen Off-By-One Bug. Wenn auch nur ein Statement anders advanced, als die anderen aus der gleichen Produktion, kommt es dazu, dass der Lesekopf verrutscht. Da die unbearbeiteten Nodes aber immer weniger werden, denke ich, irgendwann demnaechst auf den grossen Bug zu treffen.

let a = Array.of("Ed", "war", "d");
let y = { "Ed":"ward", "war": "", "d": "a" };
let b = a.map(x => x + y[x]);
var a = JSON.parse("[1,2,3]");

Ein Wunder? Ein wenig -zu fehlerfreundlich- ist der Parser auf jeden Fall. Das letzte JSON.stringify wurde ohne schliessende Klammer ausgefuehrt. Ein Detail welches unbedingt zu beachten ist, um eine Strategie auszukluegeln, wie man den Parser 100% strikt kriegt. Das uebersehen von Fehlern war uerspruenglich fuer den Syntaxhighlighter gedacht. Dann gefiel mir der Syntaxchecker vorm Highlighten aber gar besser. Uebersehen kann er sie aber noch immer.

var iterator = Reflect.ownKeys(Object);
let compr = [for (x of [1,2,3]) x+1];
const n = new Number(4); 
//let gcompr = (for (x of [1,2,3]) x+1);
let keys = Object.keys({ a:1,b:2,c:3,d:4,e:5 });
let string = '0';
for (let value of [1,2,3,4,5]) {
    string += value;
}
let string = '';
for (let key in Object) {
    string += key;
}
let compr = [for (x of [10,20,30,40,50]) x-10];
let joined = compr.join(";");

(*) Kein Wort über den Compiler? Sicher ist der AST nicht gleichzeitig auch die letzte Fassung. Ob ich mir Vorwuerfe mache, obwohl ich einen Heap mit Garbage Collector schon kurz hiervor ausprobierte? Ein wenig, aber ich finde beide Versionen angenehm. Ich wuerde das gerne einueben. Dauert natuerlich noch. Aber ich glaube, dass es mir auch in Zukunft Spass machen wird.

// Das ist Zeugs zum abtesten, kein Programm, halt ein bisschen Schrott, von der Idee zum ersten Eingetippten

var name = "Edward";
var es6 = "ist toll";
var tmpl = `${name} findet es6 ${es6}!`;
var template = "Eine Korrektur am Ablauf ist noch vorzunehmen.";
var idiot = `${template}`;

var ii = 0;
var jj = 10;
var s = "";

for (let i = 0, j = 10; i < j; i++) {
    s = s+i;
}
function F(a) {
    return a;
}
F[Symbol.create] = Function[Symbol.create];

const C = 1024;
const O = {
    name: "Eddie",
    count: 0,
    [Symbol.toStringTag]: "Eddie",
    member() { return this.name; }
};
var fun_expr = () => "ein Statement wert";
var _content = `${O.member()} sagt auch es6 sei ${fun_expr()}!`;
let a = 0,b = 0,
c = 0,d=0;
var w,x,y,z;
function objup () {
    return O.count += 1;
}
function objsub () { return O.count -= 1; }
function objmul () { return O.count *= O.count; }
function objdiv () { return O.count /= O.count; }
function objrest (n) { return O.count % n; }
function f() {
    ++a;
}
function args (a,b,c,d) {
    return arguments;
}
function recur(i) {
    ++b;
    if (i > 0) recur(i-1);
}
function testInstantiateFunctionDeclaration(x,y,...z) {
    var a,b,c,d;
    const e,f,g,h;
    let i,j,k,l;
    a=x;
    e=y;
    i=z;
    return [a,e,...i];
}
let dest_help = () => O;
let apropos = (a,b,c) => [a,b,c];
let rest_dazu = (...rest) => rest; 
let test_cover_by_defaults = (a=1,b=2,c=3) => [a,b,c];
let [gg,hh,...missing] = [1,2,3,4,5,6,7,8,9,10,"und den Rest in die lhs",11,12,13,14,15,16,17,18,19,20];
let aa = [1,2,3];
let it1 = aa.keys();
let it2 = aa.values();
let it3 = aa.entries();
let r1 = it1.next();
let r2 = it2.next();
let r3 = it3.next();

muss mir erstmal was neues einfallen lassen

var s = "";
for (let i = 0, j = 10; i < j; i++) {
    s = s+i;
}

const C = 1024;
let a = 0,b = 0,
c = 0,d=0;
var w,x,y,z;

const obj = {
    count: 0,
    up (x=1) {
	this.count += x;
    },
    dn (x=1) {
	this.count -= x;
    }
};

function f() {
    ++a;
}


function args (a,b,c,d) {
    var hmm;
    hmm = arguments;
    return hmm;
}


function args_evaluation_before_args_questionmark_to_me_period (a,b,c,d) {
    var hmm = arguments; // bug, ergibt Reference Error per GetValue: hmmm ist unresolvable reference.
    return hmm;
}


function recur(i) {
    ++b;
    if (i > 0 && i % 2) recur(i-2);
    if (i > 0) recur(i-1);
}

function r(...rest) {
    return rest;
}

/*
function p({x,y}, [z,d]) { // muss den parser nochmal updaten. 
    return [x,y,z,d];
}
*/
/*
function add([a,b]) {
    return a + b;
}*/
/*
function pp({x,y}) { 
    return {x,y};
}
*/

Ok, hier habe ich eine wunderbare TO-DO-Liste fuer das Wochenende oder die naechste Woche.


var a=0,b=0,c=0,d=0;
let e=0,f=0,g=0,h=0;
const k =3,l=4;
var obj = { name: "edward", job: "gazillionaire" };
let data = ["a","b", "c", "d"];

var s;
s = "";
for (let i = 0, j = data.length; i < j; i++) {
    s += data[i];
}    

Ich bin mir eigentlich sicher, dass man mehrere Var Statements haben kann. Ich habe die Funktionen InstantiateGlobalDeclaration und DeclarativeEnvironment.HasBinding sowie GlobalEnvironment kontrolliert. Sowie die Zuweisungen des ersten globalen Contexts. Auf dem ersten Blick sollte die mehrfache Var Declaration eigentlich moeglich sein. Let und Const natuerlich nicht. Soll mir fuer jetzt auch egal sein, heute vormittag war ich nicht zuhause, darum gibt es nichts neues.

Richtig. Hier standen andere Messages, weil der oberste Code ausgefuehrt wurde. Stimmt schon wie ich dachte.

(Men at work...)

module "m" {
    export function x (a) { return a; }
}
import { x as y } from "m";
y(10);

Vom September

Anfang Oktober stieg ich vom normalen Value passen auf den Reference Type um, hatte dabei aber mehrere Sachen parallel editiert, so dass ich nach einigen neuen Funktionen, einigen umgeschriebenen Funktionen, und einem anders orientierten Ablauf im Rest der Evaluation erstmal ueber einige Bugs stolperte, die ich, da ich nicht ausprobierte, waehrend ich schrieb, sondern erst am naechsten Tag oder dem darauf, dan anhand einer Seite mit dicken roten Exceptions bemerkte, die ich dann erstmal aufloesen musste. Natuerlich habe ich mich da erst so nach und nach dran gemacht, dass ich erstmal eine Woche verloren hatte.

Die Ergebnisse dieser Tests stehen zur Zeit noch nicht für eine korrekte Implementation.

Funktionieren werden die nicht alle.

September

Habe ich als september-2013.html archiviert. Hier gibt es das gleiche wie im Oktober. Ich versuche mich durch das Draft zu arbeiten und muss viel lernen. Weil man das erst lernen muss, bevor man es wiedergeben kann.

	class C {
	    method(a,b,c=3) {
		return a+b+c;
	    }
	    noDefaults(a,b) {
		return a+b;
	    }
	}
	let c = new C();
	c.method(1,2);
    
let x,y,z;
function f(a,b,c=3) {
    return [a,b,c];
}
[x,y,z] = f(1,2);
	let f = (a,b) => a+b; 
	f(1,2);
    

August

Im august-2013.html sind dann die Fortschritte seit Juli festgehalten. Einige Tage habe ich nur dreissig Minuten mit dem Rechner verbracht, einige aber auch ein paar Stunden. Mitte August hatte ich dann endlich das ES-262 Draft vom 15. Juli gedruckt bekommen. Seitdem lese ich es. Bis zum Ende des Monats war das Projekt "ES6-virtuelle-Maschine-mit-ES5" auf ueber 10,000 Zeilen oder 300,000 Bytes angewachsen. Hat zur Folge, dass ich einige Stellen bereits verstaendlich erklaeren kann.

 

let canvas = document.querySelector("#can");

Juli

Habe ich als juli-2013.html archiviert. Den Rest des Monats habe ich mit Lesen im ECMA Draft verbracht, was ganz schön anstrengend ist, wenn man dafür vor dem Bildschirm sitzen muss und keinen Desktop mehr frei hat und der Computer selbst total ungeeignet zum Arbeiten ist. Alles in allem habe ich den Syntaxhighlighter aber aufgeräumt und gut auf die neuen Aufgaben vorbereitet. (*)

(*) Am schlimmsten war der alte Schrott Parser, ich habe drei Monate, von August bis November einen fetten Fehler im RD Algorithmus mitgeschleppt. Identifier, Literal, Co hatten nicht aufs naechste Token gestellt, ich blieb auf dem letzten und ein aeusserer Loop machte das next. Als ich den damals entfernen wollte, oder mich verbessern, blieb der Algorithmus gemischt. Jetzt habe ich die Zeilen endlich entfernt. Es waren doppelte Pruefungen und ein if (X==I) next() Pflaster, falls das Teil stehen bleibt. Hinterher waren die Loops kurz und enthielten nur noch das Komma. Und der Zeiger immer auf dem naechsten.

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.

Oktober

Nach einer Woche mit einem heftigen Bug, da ich endlich den Referenztyp einbaute, der Interpreter ging mal mit selbstgemachten Code los, fing ich mich dann so nach und nach und schaffte doch noch ein paar Sachen. Besonders intelligent war es, mehr Tests zu loggen, um zu bemerken, dass es intelligenter waere, wenn es sehr viele mehr davon gaebe. Uebrigens, Type() gab Object bei Environment Records zurueck, wodurch die Referenz (auf einen EnvironmentEintrag) nicht aufgeloest wurde, sondern ein PropertyAccess probiert wurde, was natuerlich nicht geht. Nur das "typeof" gefunden zu haben. Das hat ein paar Suchen gedauert gehabt. Den wie so oft, war das nicht sofort zu entdecken. Bis ich dann im Bus drauf kam, das mal zu pruefen, ob da nicht auch Object rauskommt. Anderseits offensichtlich, wenn man drueber nachdenkt, aber erst seitdem weiss ich, wie die Referenz funktioniert.

November

Ein Nebenprodukt, nach Mitternacht noch ein wenig Ullman zu gucken, während sie gemütlich im Bett liegt. Eine Art von Automat.

Das Program laeuft nicht, weil der Bug f(a,b) {} f(10,20); a === 10; b ==== 20; die Funktion delta stoert. Bis nachher irgendwann.

#0 { Word => abcdef };
#0 { Word => abcdef };
#1 { Number => 123434 };
#0 { Word => abcdef };
#1 { Space =>   };
#2 { Number => 123434 };
#0 { Word => abcdef };
#1 { Space =>   };
#2 { Number => 123434 };

Das war die Ausgabe.

Das ist natuerlich kein Automat. Der Formalismus der Transitionstabellen und akzeptierenden Zustaende macht den Code hinterher noch viel besser. Aber ich hab mich gefreit, dass das auch problemlos den String zerlegt. Hier habe ich aber bereits eine Idee, wie die Aufgabe anders aussieht. Ein Punctuator braucht 4 Zeichen und man muss mit dem groessten moeglichen Operator anfangen. Also. Hole bei "operator" 3 weitere Zeichen und gucke, lasse eins weg, gucke, lasse eins weg , akzeptiere ein Zeichen Operator, gehe zu "start". Jetzt habe ich die Loesung fuer den unteren nicht formalen Automaten verraten. Wie der wohl richtig formal aussieht?

Seltsam, das laeuft nicht mit dem Interpreter. Schade, dann muss den Interpreter eben debuggen. Dann kann er das.

var el = "#testreport000"; 

/* Meine Alphabete */
var alpha = Object.create(null);
var abc = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","r","s","t","u","v","w","x","y","z"];
abc.forEach(function (a) { alpha[a] = 1; /*alpha[a.toUpperCase()] = 1; */ });
var num = [1,2,3,4,5,6,7,8,9,0];
var digit = Object.create(null);
num.forEach(function (d) { digit[d] = 1; });
var space = Object.create(null);
var spc = [" ", "\t", "\n" ];
spc.forEach(function (s) { space[s] = 1; });

/* Die Variablen fuer den aktuellen Textzeiger, das Token, der Array mit den Token */
console.html(el, "Zwischenstop");

var pos = -1;
var ch;
var lookahead;
var token, tokens;
var text;

/* Alle Variablen resetten, ein Zeichen voranschreiten, parse nimmt source und returnt token */

function reset(t) {
    text = t;
    pos = 0;
    ch = undefined;
    lookahead = undefined;
    token = undefined;
    tokens = [];
}

function advance() {
    ch = text[pos++];
    lookahead = text[pos];
    return ch;
}

function parse (t) {
    reset(t);
    return delta("start", advance());
}

console.html(el, "Zwischenstop 2");

/* Der Name ist an die Transitionsfunktion angelehnt. Der Rest ist Marke Eigenbau. */

function delta(state, ch) {
	switch (state) {
	case "start":
	    if (ch === undefined) state = "exit";
	    else if (alpha[ch]) state = "alpha";
	    else if (digit[ch]) state = "number";
	    else if (space[ch]) state = "space";
	    else state = "err";
	    token = "";
	    return delta(state, ch);
	break;
	case "alpha":
	    token += ch;
	    if (!alpha[lookahead]) { // falls kein weiterer buchstabe kommt, aendere zustand auf start
		state = "start"
		tokens.push({ type: "Word", value: token });
	    }
	    return delta(state, advance()); // ansonsten wiederhole den zustand
	break;
	case "number":
	    token += ch;
	    if (!digit[lookahead]) {
		state = "start";
		tokens.push({ type: "Number", value: token });
	    }
	    return delta(state, advance());    
	break;
	case "space":
	    token += ch;
	    if (!space[lookahead]) {
		state = "start"
		tokens.push({ type: "Space", value: token });
	    }
	    return delta(state, advance());
	break;
	case "err": 
	    console.html(el, "invalid character");
	    state = "exit";
	    return delta(state, undefined);
	break;
	case "exit":
	    return tokens;
	break;
    }
}

/* Erinnert mich daran, dass HTML5 einen Automaten vorschreibt. */

let print = (token, num) => console.html(el, "#"+num+" { "+token.type+" => "+token.value+" };");

/* Aber erstmal sollte ich lernen, wie der formal richtige Automat aussieht. */

console.html(el, "Zwischenstop 3");

try {
let a1 = parse("abcdef");
a1.forEach(print);
let a2 = parse("abcdef123434")
a2.forEach(print);
let a3 = parse("abcdef 123434")
a3.forEach(print);
let a4 = parse("abcdef 123434")
a4.forEach(print);
} catch (ex) {
console.html(el, "["+ex.name+"]: "+ex.message+" - "+ex.stack);
}
console.html(el, "Zwischenstop 4");

/* Danach kann ich ihn immernoch verbessern */

/* Gottseidank weiss ich jetzt, dass der Code nicht funktioniert, obwohl er funktionert. */
function f() {
    function g() {
	function h() {
	    function i() {
		return 10;
	    }
	    return i();
	}
	return h();
    }
    return g();
}
function f(x) {
    function g(x) {
	function h(x) {
	    function i(x) {
		return x;
	    }
	    return i(x);
	}
	return h(x);
    }
    return g(x);
}

Fun zwischendurch

// Beim MIT in 6.004 zwischendurch aufgeschnappt, als die Wahrheitstabelle dran war.

// ZERO
function zero(a,b) {
    return false;
}
// AND
function  and(a,b) {
    if (a && b) return true;
    return false;
}
// A > B
function agtb(a,b) {
    if (a && !b) return true;
    return false;
}
// A
function a(a,b) {
    if (a) return true;
    return false;
}
// B > A
function bgta(a,b) {
    if (b && !a) return true;
    return false;
}
// B
function b(a,b) {
    if (b) return true;
    return false;
}
// XOR
function xor(a,b) {
    if (a && !b) return true;
    if (!a && b) return true;
    return false;
}
// OR
function or(a,b) {
    if (!a && !b) return false;
    return true;
}
// NOR
function nor(a,b) {
    if (!a && !b) return true;
    return false;
}
// XNOR
function xnor(a,b) {
    if (!a && !b) return true;
    if (a && b) return true;
    return false;
}
// NOT B
function notb(a,b) {
    if (b) return false;
    return false;
}
// WENN A dann B
function bifa(a,b) {
    if (b && !a) return false;
    return true;
}
// NOT A 
function nota(a,b) {
    if (a) return false;
    return true;
}
// WENN B dann A
function aifb(a,b) {
    if (a && !b) return false;
    return true;
} 
// NAND
function nand(a,b) {
    if (a && b) return false;
    return true;
}
// ONE
function one(a,b) {
    return true;
}

32-bit now 64-bit powered (seriously)