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. :-)

Ende April...

Wird Zeit die Seite mal einzustampfen und die neue CI zu präsentieren.

two´s complement

Man invertiert die Bits und addiert eins und erhaelt die negative Zahl im IEEE 754. Hier sei der Tilde Operator (Bits invertieren) + 1 angewandt und man erhaelt die gleiche Zahl, nur negativ. Das sogenannte Zweierkomplement. Das Einerkomplement ist nur die Invertierung, um es gleich vorweg zu wiederholen.

linux-dww5:/s/test/json # es6
es6> let f = x => { return "whatever "+x; }
undefined
es6> f(!235423)
whatever false
es6> f(~235423)
whatever -235424
es6> f((~235423))
whatever -235424
es6> f((~235423)+1)
whatever -235423
es6> f((~235423)+1)
whatever -235423
es6> f((~235423)+1 == -235423)
whatever true
es6> f((~235423)+1 == -235423)

Spass. Ich hab gerade einen bloeden Bug entfernt, der den Parser aufgehangen hatte. Den habe ich erst vor ein paar Tagen irgendwann eingeführt aber nicht dran gedacht, dass ich da mal rumgewerkelt hab.

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;
}
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1023 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1068
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1040 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1068
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1049 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1068
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1059 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1068

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;
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1115 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1124
FAIL: Exception: ex=syntaxjs.toValue is not a function: stack=@http://127.0.0.1/:1121 call_test@http://127.0.0.1/:20574 run_tests@http://127.0.0.1/:20576 @http://127.0.0.1/:1124

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: ' '



syntax.js exception: SyntaxError
Unexpected token ILLEGAL
StringLiteral@http://127.0.0.1/:18617
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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());
	}
}


syntax.js exception: SyntaxError
Unknown Character: @ at offset 52 at line 7 at column 2
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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"));
    }

}


syntax.js exception: SyntaxError
Unknown Character: @ at offset 1087 at line 44 at column 5
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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}


*/


syntax.js exception: SyntaxError
Unexpected token ILLEGAL
StringLiteral@http://127.0.0.1/:18617
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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);
	}
}


syntax.js exception: SyntaxError
unexpected token illegal in undefined
NumericLiteral@http://127.0.0.1/:18658
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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


syntax.js exception: SyntaxError
Unknown Character: ¬ at offset 14 at line 2 at column 1
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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)
*/


syntax.js exception: SyntaxError
Unknown Character: @ at offset 832 at line 36 at column 2
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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:
*/


syntax.js exception: SyntaxError
Unknown Character: @ at offset 304 at line 20 at column 5
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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)
*/


syntax.js exception: SyntaxError
Unknown Character: @ at offset 3387 at line 71 at column 5
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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);
	}
}


syntax.js exception: SyntaxError
Unknown Character: @ at offset 154 at line 12 at column 2
tokenizeIntoArray@http://127.0.0.1/:18686
tokenizeIntoArrayWithWhiteSpaces@http://127.0.0.1/:18688
highlight@http://127.0.0.1/:20315
highlightElements@http://127.0.0.1/:20339

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