Fork me on GitHub
www.linux-swt.de

Edwards Hobby JavaScript seit 2012 Homepage aus dem Armenhaus

Bin erstmal damit beschäftigt, eine neue Website zu entwickeln.

Das ist mein erstes Skript um alte Stylesheets dieser Seite und der älteren zu verändern.

% muss noch in das Tool, und das zur Homepage, für den, der eine Homepageversion fortsetzen will..


// pxtoem.js filename [font-size=16]
// ersetzt alle 10px mit 0.6250000000em auf 10 Stellen
// font-size angegeben kann da was anderes ausgeben

#!/usr/local/bin/node
(function main() {
var fs = require("fs");
var args = process.argv.slice(2);
console.log("// transformed "+args[0]+" with pxtoem.js");
var data = fs.readFileSync(args[0],"utf8");
var output = "";
var fontSize = args[1] || 16;
data = data.replace(/([\d]{1,5}px)/, function (all, match) {
    var integer = +(data.match(/([\d]+)/)[1]); // take +(int) of px
    return (integer / fontSize).toPrecision(10) + "em";
});

console.log(data);

}());

\Framework\Configuration\Driver\Json

Im Buch wird die parse_ini_string oder wie die heisst Funktion zum Lesen von Konfigurationsdateien im .ini Format benutzt. Die kann man lesen lassen, wenn man keine $options = array() an den Konstruktor der jeweiligen von Framework_Base abgeleiteten Klasse übergibt. Ich dachte, man kann ausser .ini auch .json oder .xml lesen. Hier ist eine .json Klasse. Wie man liest, ein anderer Treiber für´s Framework könnte dann .xml Dateien in Objekte umwandeln.



<?php

namespace Framework\Configuration\Driver;
use \Framework\Configuration as Configuration;
use \Framework\Configuration\Exception as Exception;
use \Framework\ArrayMethods as ArrayMethods;

class Json extends Configuration\Driver {

    public function parse($path) {
        if (empty($path)) {
            throw new Exception\Argument("\$path argument is not valid");
        }

        if (!isset($this->_parsed[$path])) {
            $config = new \stdClass();
            ob_start();
                include("{$path}.json");
                $raw = ob_get_contents();
            ob_end_clean();
            try {
                $config = json_decode($raw);
            } catch (\Exception $e) {
                throw new Exception($e)
            }
            $this->_parsed[$path] = $config;
        }
        return $this->_parsed[$path];
    }

    public function toJson($path) {
        if (isset($this->_parsed[$path])) {
            return json_encode($this->_parsed[$path]);
        }
        return "";
    }
} 

application/controllers/Index->index()

Sieht man gar nicht. Sah man nicht. Ist aber nicht mehr aktiv in Index->index() wie man am / sieht. Dafür geben die JavaScript und Stylesheet Treiber das Style und Script inline aus. Das ist praktisch der Übertrag aus dem alten View Template, wo die mit supplant (nem str_replace($str, "{{$name}}", $data[$name]) eingebracht wurden. Mit mit nem old school fgets loop statt mit file_get_contents gelesen. Die Zeiten haben sich ganz schön geändert.

Habe das alte Funktiönchen einfach in die Funktion kopiert. So kann ich an allem anderen arbeiten, ohne daß man was merkt. ;-)

Javascript Driver

Eine tolle Sache im Pro PHP MVC Buch ist zum Beispiel die Treiber Architektur. Die habe ich mir nun angeeignet, womit ich das flexible System so fortsetzen kann. Das MVC Framework von Chris Pitt ist sehr komfortabel, dank des Inspectors und der magischen __call und __get/__set Funktionen. Ersterer ermöglicht zum Beispiel das Model-ORM, was bedeutet, dass man eine Modelklasse erzeugt, die Daten dort entwirft und in den Kommentar darüber schreibt, was die Daten zu bedeuten habe. Das Model baut mit sync($model) dann ein "CREATE TABLE" zusammen, wenn der Table fehlt. Das Model enthält aber auch _validate Handler, die durch @validate [options] im Kommentar definiert werden. Und vieles mehr. Ich denke bald eine kurze Anleitung dafür zu schreiben.

/*
*
* This driver code is no longer up to date
* The extends Element relation has been removed completly.
* Oder anders, ich hab das längst verbessert.
* Nebenbei war hier ne _attr() public wo _ schon protected sagt.
* Muss aber ein Versehen gewesen sein, weil mir das schon von Anfang
* an klar war, wie der Autor das im Buch gemacht hatte.
* Dreimal gucken musste ich, warum die Properties mal mit _ angesprochen 
* wurden und mal mit ohne Unterstrich. Letzteres wäre unmöglich, wäre da
* nicht Base. Und Base hat´s in sich, finde ich, mit dem Inspector. 
* Der liefert wertvolle Informationen für die gesamte Laufzeit
* über Zugriffsrechte und ORM.
*/


// /application/configuration/javascript.ini

javascript.default.type = server

<?php
namespace Framework;

use \Framework\Javascript\Exception as Exception;

class Javascript extends Base {

    /**
     * @readwrite
     */
    protected $_type;

    /**
     * @readwrite
     */
    protected $_options;

    public function __construct($options=array()) {
        parent::__construct($options);
    }

    public function initialize() {
        if (!$this->type) {
            $configuration = Registry::get("configuration");
            if ($configuration) {
                $configuration = $configuration->initialize();
                $parsed = $configuration->parse("configuration/javascript");
                if (!empty($parsed->javascript->default->type)) {
                    // INI: javascript.default.type =
                    // JSON: { "javascript": { "default": { "type": "" } } }
                    $this->type = $parsed->javascript->default->type;
                    $this->options = (array) $parsed->javascript->default;
                }
            }
            throw new Exception\Implementation("invalid javascript driver type");
        }
        switch($this->type) {
            case "server":
                return new Javascript\Driver\Server($this->options);
            default:
                throw new Exception\Implementation("invalid javascript driver type");
        }
    }
}

<?php
/**
 * Created by PhpStorm.
 * User: root
 * Date: 18.07.14
 * Time: 20:25
 */

namespace Framework\Javascript;

use \Framework\Base as Base;
use \Framework\Javascript\Exception as Exception;

class Driver extends Base {

    protected function _getExceptionForImplementation($method) {
	    return new Exception\Implementation("{$method} not implemented");
    }

    public function initialize() {
        return $this;
    }

}<?php
/**
 * Created by PhpStorm.
 * User: root
 * Date: 25.07.14
 * Time: 23:57
 */

namespace Framework\Javascript;

class Exception extends \Framework\Core\Exception {}
<?php
/**
 * Created by PhpStorm.
 * User: root
 * Date: 26.07.14
 * Time: 11:37
 */

namespace Framework\Javascript;

use \Framework\Javascript\Exception as Exception;
use \Framework\Base as Base;
use \Framework\Element as Element;

class Script extends Element {
    /**
     * @read
     */
    protected $_tag = "script";
    /**
     * @readwrite
     */
    protected $_async;
    /**
     * @readwrite
     */
    protected $_defer;
    /**
     * @readwrite
     */
    protected $_type;
    /**
     * @readwrite
     */
    protected $_src;

    /**
     * @readwrite
     */
    protected $_code = "";

    /**
     * @readwrite
     */
    protected $_onload;
    /**
     * @readwrite
     */
    protected $_onerror;

    /**
     * @readwrite
     */
    protected $_file;

    /**
     * @param array $options
     *
     * "file" => "syntax.js"
     * will load the file into the tag
     * to inline
     */
    public function __construct($options = array()) {
        parent::__construct($options);
        if (isset($options["file"]) && file_exists($options["file"])) {
            ob_start();
                include($options["file"]);
                $script = ob_get_contents();
            ob_end_clean();
            $this->code = $script;
        }
    }

    protected function _attrs() {
        $attrs = array();
        if (isset($this->defer)) {
            $attrs[] = "defer";
        }
        if (isset($this->async)) {
            $attrs[] = "async";
        }
        if (isset($this->id)) {
            $attrs[] = sprintf("id='%s'", $this->id);
        }
        if (isset($this->class)) {
            $attrs[] = sprintf("class='%s'", $this->class);
        }
        if (isset($this->src) && empty($this->code)) {
            $attrs[] = sprintf("src='%s'", $this->src);
        }
        if (isset($this->type)) {
            $attrs[] = sprintf("type='%s'", $this->type);
        }
        if (isset($this->onload)) {
            $attrs[] = sprintf("onload='%s'", $this->onload);
        }
        if (isset($this->onerror)) {
            $attrs[] = sprintf("onerror='%s'", $this->onerror);
        }
        return (sizeof($attrs)?" ":"").join(" ", $attrs);
    }

    public function get() {
        $attrs = $this->_attrs();
        $code = $this->code;
        $script = "<script%s>%s</script>";
        return sprintf($script, $attrs, $code);
    }

} <?php

/**
 * Created by PhpStorm.
 * User: root
 * Date: 25.07.14
 * Time: 23:59
 */

namespace Framework\Javascript\Driver;
use \Framework\Javascript as Javascript;
use \Framework\Javascript\Script as Script;

/**
 * Class Server
 * @package Framework\Javascript\Driver
 */
class Server extends Javascript\Driver {
    /**
     * @readwrite
     */
    protected $_scripts;

    public function __construct ($options = array())  {
        parent::__construct($options);
    }

    public function initialize() {
        $this->scripts = array();
        return $this;
    }

    public function add($data) {
        if (is_array($data)) {
            $this->_scripts[] = new Script($data);
        } else if ($data instanceof Script) {
            $this->_scripts[] = $data;
        } else {
            throw new Exception("invalid argument to function add");
        }
        return $this;
    }

    public function get() {
        $scripts = "";
        foreach ($this->scripts as $script) {
            $scripts .= $script->get() . "\n";
        }
        return $scripts;
    }
} 

Framework

Arbeite weiter am Pro PHP MVC Framework. Gestern hatte ich die bereits aus dem Buch abgetippten Files in die Verzeichnisstruktur gebracht.

Heute habe ich soweit debuggt, dass es ausreicht, eine Route zu definieren, und eine Controller Klasse mit einer ensprechenden Action zu erzeugen.

Sowohl durch die Namespaces, den Autoloader und die Verzeichnisstruktur, als auch durch den erfahrenen Code von Chris Pitt, wird die action() ausgeführt.

Sollte nicht mehr lange dauern, bis ich endlich was mit dem Framework gebastelt habe.

Neues ES6 Draft

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

Ich freue mich bereits auf die Spread Implementation für Strings.

July 18, 2014 Draft Rev 26

doc
pdf, no change markup pdf with rev26 change markup pdf with cumulative change markup since rev22
Changes marked as “Rev 26“.

Changes include:

    Terminology change: “Task” is replaced by “Job”.
    GetMethod now treats null and undefined equivalently as meaning no method available.
    Eliminated the ability of Proxy handlers to extend the set of property descriptor attributes they expose vis [[GetOwnProperty]]
    Added invariant checks for Proxy [[OwnPropertyNames]] internal method
    Revisited @@unscopable support in Object Environment Records
    Updated Annex C (strict mode summary) WRT ES 6 changes and extensions.
    Eliminated duplicate property name restrictions on object literals and class definitions
    For-of now throws if iterable value is null or undefined (also reverted comprehensions to throwing for that case)
    Date.prototype.toString now uses NaN as its time value when applied to an object with out a [[DateValue]]
    Function poision-pill caller and arguments properties are now configurable
    await is a FutureReservedWord when parsing and Module is the syntactic grammar goal symbol
    Better integration of O.p.toLocaleString and String.p.toLocaleString with Ecma-402
    Annex B support for function declarations in IfStatmentClauses
    Annex B (and 13.12) support for legacy labelled FunctionDeclarations.
    Another round of updates to 9.2.13 FunctionDeclarationInstanations to fix various scoping bugs.
    Updated Symbol conversions: aSym == “not a symbol” produces false. var s=Symbol(); s==Object(s) produces true. foo”+aSymbol or aSymbol+”foo” throws TypeError. Symbol @@toPrimitive returns the wrappered symbol value. ToNumber(aSymbol) throws.
    Spread now works on strings: var codeUnits = [...”this is a string”];
    yield * now works with strings: function * getchars(str) {yield * str}
    Added name property for bound functions in F.p.bind. Fixed bugs in generating length property in F.p.bind
    Tweaked Script GlobalDeclarationInstantiations to deal with error situations that could arise from misusing proxies for the global object.
    Added an informative generator function based definition for ordinary object [[Enumerate]]
    Changed handling of NaN returned from a sort comparefn to match web reality. See bug https://bugs.ecmascript.org/show_bug.cgi?id=2978
    Generator object return method/For-of/in loops use return method on generators
    Resolved bugs: 3010, 3004-3000, 2992, 2990-2988, 2984-2983, 2981-2980, 2978-2977, 2975, 2971-2948, 2946-2937, 2935-2922, 2919, 2917, 2915, 2910-2891, 2888-2884, 2880, 2875, 2840, 2828, 2813, 2811, 2803, 2799, 2778-2777, 2721-2720, 2718-2717, 2682, 2636, 2634, 2594-2592, 2590-2589, 2587, 2564, 2488, 2411, 2365, 2360, 2324, 2315, 2080, 2053, 2049, 1797, 1789, 1762, 1459, 1444-1443, 1341, 1267-1266, 1142, 977, 944, 522, 519

MVC Framework

Chris Pitt´s Buch "Pro PHP MVC" über ein PHP Model-View-Controller, ist ein unverzichtbarer Band für alle, die mal ein richtiges MVC Rahmenwerk schreiben wollen. Programmieren vorausgesetzt. Von Registry, Configuration, Caching, Routing, Templates, Datenbank, Query, Model, View, Controller, und auch Benutzern, Suche, Photos, Sharing, ist wohl an alles gedacht worden, was ein modernes PHP MVC Framework ausmacht(, bis auf die i18n Funktion ;-)). Ich habe mich dazu entschlossen, das Framework lieber zu übernehmen und abzutippen, als mein eigenes zu entwickeln. Hier spare ich nicht nur viel Zeit, wie als ob ich direkt was fertiges nehme, nein, hier steigert sich auch mein Wissen über die Konzepte an sich. Und so gewöhne ich mich sofort ans konsistente objektorientierte schreiben mit PHP5. Ich hab schon lange nicht mehr an einen neuen Contentmanager gedacht, aber da mir der Platz zu klein wird, und die Automatisierung zu dürftig ist, dachte ich mir, was neues auszuprobieren. Chris Pitt´s Buch ist echt gut und hat mir viel geholfen. Und das beste kommt noch.

Gestern hatte ich glaube ich eine bessere Werbung verfasst. Ich denke, meinen Buchtipp wohl nochmal zu wiederholen. Denn das Buch ist wahrscheinlich nicht nur für mich einmalig.

<?php

/**
 * Created by PhpStorm.
 * User: root
 * Date: 19.07.14
 * Time: 13:31
 */

namespace Homepage;

class Test {

    private static $_tests = array();

    public static function add($callback, $title = "Unnamed Test", $set = "General") {
        self::$_tests[] = array(
            "set" => $set,
            "title" => $title,
            "callback" => $callback
        );
    }

    public static function run($before = null, $after = null) {
        if ($before) {
            $before(self::$_tests);
        }
        $passed = array();
        $failed = array();
        $exceptions = array();

        foreach (self::$_tests as $test) {
            try {
                $result = call_user_func($test["callback"]);

                if ($result) {
                    $passed[] = array(
                        "set" => $test["set"],
                        "title" => $test["title"]
                    );
                } else {
                    $failed[] = array(
                        "set" => $test["set"],
                        "title" => $test["title"]
                    );
                }
            } catch (\Exception $e) {
                $exceptions[] = array(
                    "set" => $test["set"],
                    "title" => $test["title"],
                    "type" => get_class($e)
                );
            }
        }

        if ($after) {
            $after(self::$_tests);
        }

        return array(
            "passed" => $passed,
            "failed" => $failed,
            "exceptions" => $exceptions
        );
    }

}

?>

<?php
include("Test.php");

use Homepage\Test as Test;

Test::add(function () {
    return true;
},
"returning true should pass",
"test"
);


Test::add(function () {
    return false;
},
"returning false should fail",
"test");

Test::add(function () {
    throw new \Exception("catch me");
}, 
"throwing should be caught",
"test"
);

$results = Test::run(function () { echo "**BEFORE**"; }, function () { echo "**AFTER**"; });

print_r($results);

?>

**BEFORE****AFTER**
Array
(
    [passed] => Array
        (
            [0] => Array
                (
                    [set] => test
                    [title] => returning true should pass
                )

        )

    [failed] => Array
        (
            [0] => Array
                (
                    [set] => test
                    [title] => returning false should fail
                )

        )

    [exceptions] => Array
        (
            [0] => Array
                (
                    [set] => test
                    [title] => throwing should be caught
                    [type] => Exception
                )

        )

)

ByteCode

Für meinen Compiler und Bytecode Interpreter habe ich vieles durchgenommen. Unter anderem Assembler, weil das Kompilieren des AST nach dreimaligem Refaktorieren auf eine Maschinencode Syntax trifft, das heisst, dass die node.types, also die verschiedenen Statements, sich in ein paar Assemblerbefehle zerlegen lassen. Problem beim Kodieren von konstanten Zahlen im Code ist, dass ein Float64 nur genau alle acht Bytes anliegen kann. Darum muss er in Bytes konvertiert werden. Das geht einfach so, dass man die beiden Arrays auf einen Buffer legt, die Zahl als Float eingibt und als 8 Bytes rausholt. Signed oder Unsigned. Beides geht. Umgekehrt füllt man die Bytes wieder ein und holt ein Float raus. Sofern die Reihenfolge richtigrum ist. Durch das Zerlegen kann man die direkt an Stelle 2,3,4,5 statt jedes achte Byte unterbringen. Ist schon lange klar, wollte das nur mal kurz zeigen.


var encode = (function () {

    var buf = new ArrayBuffer(8);
    var fbuf = new Float64Array(buf);
    var bbuf = new Int8Array(buf);

    function toBytes(num, bigEndian) {
	fbuf[0] = num;
        if (bigEndian) {
    	    return [bbuf[7], bbuf[6], bbuf[5], bbuf[4],bbuf[3], bbuf[2], bbuf[1], bbuf[0]];
        } else {
            return [bbuf[0], bbuf[1], bbuf[2], bbuf[3],bbuf[4], bbuf[5], bbuf[6], bbuf[7]];	
	}
    }

    function toFloat(bytes, bigEndian) {
	if (bigEndian) {
	    bbuf[0] = bytes[7];
	    bbuf[1] = bytes[6];
	    bbuf[2] = bytes[5];
	    bbuf[3] = bytes[4];
	    bbuf[4] = bytes[3];
	    bbuf[5] = bytes[2];
	    bbuf[6] = bytes[1];
	    bbuf[7] = bytes[0];	    
	    
        } else {
            bbuf[0] = bytes[0];
	    bbuf[1] = bytes[1];
    	    bbuf[2] = bytes[2];
	    bbuf[3] = bytes[3];
	    bbuf[4] = bytes[4];
	    bbuf[5] = bytes[5];
    	    bbuf[6] = bytes[6];
	    bbuf[7] = bytes[7];
	}
	return fbuf[0];
    }

    return {
	toBytes: toBytes,
	toFloat: toFloat
    };

}());


function test(num, bigEndian) {
    bigEndian = !!bigEndian;
    var bytes;
    console.log("test: "+num+", bigEndian: "+bigEndian);
    console.log(bytes = encode.toBytes(num, bigEndian));
    console.log(encode.toFloat(bytes, bigEndian));
}

test(10);
test(100);
test(12.34);
test(0.1);
test(23592095);
test(1235.35667);
test(221.234);

test(10,true);
test(100,true);
test(12.34,true);
test(0.1,true);
test(23592095,true);
test(1235.35667,true);
test(221.234,true);

/*
test: 10, bigEndian: false
[ 0, 0, 0, 0, 0, 0, 36, 64 ]
10
test: 100, bigEndian: false
[ 0, 0, 0, 0, 0, 0, 89, 64 ]
100
test: 12.34, bigEndian: false
[ -82, 71, -31, 122, 20, -82, 40, 64 ]
12.34
test: 0.1, bigEndian: false
[ -102, -103, -103, -103, -103, -103, -71, 63 ]
0.1
test: 23592095, bigEndian: false
[ 0, 0, 0, -16, -55, 127, 118, 65 ]
23592095
test: 1235.35667, bigEndian: false
[ -37, -123, -26, 58, 109, 77, -109, 64 ]
1235.35667
test: 221.234, bigEndian: false
[ 115, 104, -111, -19, 124, -89, 107, 64 ]
221.234
test: 10, bigEndian: true
[ 64, 36, 0, 0, 0, 0, 0, 0 ]
10
test: 100, bigEndian: true
[ 64, 89, 0, 0, 0, 0, 0, 0 ]
100
test: 12.34, bigEndian: true
[ 64, 40, -82, 20, 122, -31, 71, -82 ]
12.34
test: 0.1, bigEndian: true
[ 63, -71, -103, -103, -103, -103, -103, -102 ]
0.1
test: 23592095, bigEndian: true
[ 65, 118, 127, -55, -16, 0, 0, 0 ]
23592095
test: 1235.35667, bigEndian: true
[ 64, -109, 77, 109, 58, -26, -123, -37 ]
1235.35667
test: 221.234, bigEndian: true
[ 64, 107, -89, 124, -19, -111, 104, 115 ]
221.234

*/

Übrigens, in der CPU werden die Operanden Register/Memory/Immediate mit Mod/RM mit ein paar Bits abgebildet und ebenso werde ich selbiges kodieren.

C++ Methoden Überladen für den Assembler

Das Overloading von Funktionen ist insofern trickreich für den Assemblerprogrammierer, weil der Compiler Namen generiert. Die muss man heraussuchen und die Label manuell einsetzen oder unter dem generierten Namen bearbeiten. Das ist aber auch schon alles.

// Die zu überladenden Funktionen

double add(double, double);
int add(int, int);

// können nicht beide das Label add: haben
// darum generiert der Compiler dafür Namen
// Die finde ich jetzt im nächsten File raus

#include <stdio.h>
int main() {
    printf("1+2 = %d\n", add(1,2));
    printf("0.1+0.2 = %lf\n", add(0.1,0.2));
}


;--------------------------------------------------------------------
; Der Compiler generiert:
; _Z3addii für int add(int,int)
; _Z3adddd für double add(double, double)
; Gefunden mit g++ ovld.cc -c --save-temps in ovld.s durch den call
; Aufruf, weil ich keinen anderen Parameter zum Anzeigen wusste.

section .text

global _Z3addii
global _Z3adddd

; double add(double, double)
_Z3adddd:
    addsd xmm0, xmm1
    ret    
; int add(int, int)
_Z3addii:
    add rdi, rsi
    mov rax, rdi
    ret    
/*
yasm -f elf64 ovld.yasm -o ovld.o
g++ ovld.o ovld.cc -o ovld    
./ovld

1+2 = 3
0.1+0.2 = 0.300000
*/

GNU as Handbuch

Gerade bin ich dabei im GNU as Handbuch zu blättern. Ich mache ganz gute Fortschritte, mir das alles einzuprägen.

Hier gucke ich nur den Unterschied zwischen .intel_syntax und .att_syntax. Ich hätte mehr Instruktionen nutzen können. [ebp+4+edi*3] wird zu 4(%ebp,%edi,3) und so Sachen. Das kann man selbst nachlesen. Konstanten fügt man ein $ voran. Den Registern ein %. Die Mnemonics haben Suffixe wie zum Beispiel movq. Die Reihenfolge ist Source, Dest anstatt Dest, Source.

/* gnu assembler
//.intel_syntax test test.S
// direkt mit gcc kompilierbar
// gcc -o test test.S
// ./test
// Das ist ein Test.
*/
.intel_syntax
.section .data
test: .asciz "Das ist ein Test.\n"
.section .text
.globl main
.extern printf
main:
    enter 0,0
    leaq %rdi, [test]
    call printf
    leave
    ret
    
/* gnu assembler
//.att_syntax test.S pendant test2.S
// direkt mit gcc kompilierbar
// gcc -o test2 test2.S
// ./test2
// Das ist ein Test.
*/
.att_syntax
.section .data
test: .asciz "Das ist ein Test.\n"
.section .text
.globl main
.extern printf
main:
    enter $0,$0
    leaq (test), %rdi
    call printf
    leave
    ret

Beide kann man direkt mit gcc ohne direkte Invokation des as kompilieren und linken und kann sich die zwei Befehle mit einem sparen.

Endianess

enum Endianess { LITTLE, BIG };
Endianess endianess() {
    int value = 0x12345678; 
    char *test = (char *)&value;
    switch (test[0]) {
	 case 0x78: return LITTLE;
	 case 0x12: return BIG;	 
    }
}
bool isLittleEndian() {
    int value = 0x12345678; 
    char *test = (char *)&value;
    return test[0] == 0x78;
}
bool isBigEndian() {
    int value = 0x12345678; 
    char *test = (char *)&value;
    return test[0] == 0x12;
}
int endian_convert_int(int value) {
    unsigned char *original = (unsigned char *)&value;
    int converted;
    unsigned char *converter = (unsigned char *)&converted;
    converter[0] = original[3];
    converter[1] = original[2];
    converter[2] = original[1];
    converter[3] = original[0];
    return converted;
}
#include "endian.h"
#include <iostream>
#include <stdio.h>

using std::cout;
using std::endl;

int main() {
    std::cout << "Big Endian: " << (isBigEndian()?"true":"false") << std::endl;
    std::cout << "Little Endian: " << (isLittleEndian()?"true":"false") << std::endl;
    std::cout << "Endianess: ";
    switch (endianess())  {
	case BIG:
	    cout << "BIG";
		break;
	case LITTLE:
	    cout << "LITTLE";
		break;	
    } 
    cout << std::endl;
    cout << "0x12345678 converted is: ";
    printf("0x%x", endian_convert_int(0x12345678));
    cout << endl;
    return isLittleEndian();
}

/*
Big Endian: false
Little Endian: true
Endianess: LITTLE
0x12345678 converted is: 0x78563412
*/

Der Endian Test ist zustande gekommen durch Paul Carters PC Assembler Buch. Der Test zeigt an in welcher Reihenfolge die Bytes in der CPU verarbeitet werden. Intel hat Little Endian, während aber andere auch Big Endian haben. Das konvertieren ist ebenso simpel. Man tausche die Bytes einfach mit ihrer gegenüberliegenden Stelle aus. Einfach die jeweiligen 8 Bits tauschen. Zu meiner Verteidigung, ich habe es gerade ohne Buch und so aus dem Kopf geschrieben und mir die Konversion bereits gemerkt. Während ich den zu cout passenden Formatbefehl wieder vergass und zusätzlich printf zur Ausgabehilfe nahm.

strlen

Ich hab´s nochmal ausprobiert, strlen zu zählen, weil ich dachte, mit scas geht das auch. Ok, zuerst mit cmps. Aber das vergleicht Source und Destination. Mit scas kann man mit al arbeiten und einfach ein Zeichen angeben. In rbx landet die Anzahl der Zeichen stellte ich fest, als ich die von rax bis rdx ausprobierte, bzw. rcx, dann rdx, dann liess ich rax aus und dann rbx und da war die Zahl dann drin. So ist der Code aber kürzer als, siehe weiter unten.

section .text
global strlen
strlen:
    mov al, 0
    cld
    rep scasb
    mov rax, rbx
    ret

PHP Storm

Um mein bei einem Stanford Kurs angefangenes PHP Programm fortzusetzen, habe ich mir PHPStorm von den jetbrains.com downgeloaded und installiert.

Horner mal überflüssig (Nibble-Technik)

Das Einzige was mich daran hindert, eine Dezimal-Zahl mit :16 nach Hex zu konvertieren, ist, daß man die Zahlen gleich binär und nicht dezimal vorliegen hat und daher direkt die 4-bit Nibbles benutzen kann um die linke und die rechte Stelle der 2-stelligen Hexzahl pro Byte herzustellen, da die gleich 1:1 übertragen wird auf das passende Zeichen.

// getdigit.yasm
section .text
hexDigits db "0123456789abcdef", 0
section .data
global getDigit
getDigit:	; int getDigit(int n)
	mov rax, [hexDigits+rdi]
	ret

// getdigit.cc
extern "C" {
	int getDigit(int n);
}
#include <iostream>
using std::cout;
using std::endl;
int main() {
	for (int i = 0; i < 16; i++)
	cout << ((i<10)?"0":"") << i << ": "<< (char)getDigit(i) << endl;
}

00: 0
01: 1
02: 2
03: 3
...
13: d
14: e
15: f

Hier gibt es eine SIMD Operation mit je 2 64-Bit Doubles im 128-Bit XMM0 und XMM1 Register. Die beiden ersten und zweiten Zahlen werden jeweils addiert. Ich speichere das Resultat zwischen, da ich den Befehl für die obere Zahl nicht weiss, und hole mir die per Pointerarithmetik indem ich die 8 bytes raufzähle wieder ins XMM0 runter. Ich denke, hier lässt sich noch was lernen, um das besser zu machen.

section .data
doubles1	dq 0.1, 0.4
doubles2 dq 0.2, 0.4
results dq 0.0, 0.0

fmt db "%lf",0ah, 0

section .text
global main
extern printf

main:

    enter 0, 0

    ; bewege je zwei doubles nach xmm0 und xmm1
    movupd xmm0, [doubles1]	; load 2 doubles into xmm0
    movupd xmm1, [doubles2]	; load 2 doubles into xmm1
    addpd xmm0, xmm1		; xmm0[0]+xmm1[0], xmm0[1]+xmm1[1]
    movupd [results], xmm0	; store the 2 doubles in results

    ; zeige ersten double an aus xmm0
    lea  rdi, [fmt]
    mov rax, 1
    call printf

    ; zeige zweiten double an
    lea  rdi, [fmt]
    movupd xmm0, [results+8]
    mov rax, 1
    call printf    
    
    leave
    ret
/*
0.300000
0.800000
*/

Weiter unten habe ich ein paar peinliche xor rsi, rsi oder xor rax, rax (setzt das jeweilige Register auf 0) gelöscht. Ich war so intelligent, es mal zu versuchen, es mal auf 0 zu setzen, um es einfach leer zu haben. Mittlerweile weiss ich, dass das keinerlei Auswirkung hat, ausser mindestens einen extra Takt zu brauchen. Wieviele exakt darf ich noch aus den Handbüchern lernen.

Fibonacci mit Memoization

Auch Bottom-Up Fibonacci mit Array Lookup.

Mit Memoizer in yasm-Assembler für Linux. Man kompiliert es mit yasm und gcc und danach kann man ein Argument auf der Kommandozeile übergeben. Das wird konvertiert, ein Array alloziert. Dann wird Fibonacci hochgezählt und gespeichert und die eigentliche fib(n) Funktion führt keine Rekursion mehr durch, und holt sich stattdessen die Ergebnisse aus dem Array. Der Memoizer wird interessant, sobald man mit scanf mehrere Eingaben holt und man sollte bei Bedarf die Tabelle kopieren und vergrößern können, so könnte man es weiterentwickeln. Hinzu kommt, daß der erste Überlauf bei fib(93) beginnt. Wahrscheinlich ist das aber erst der Formatstring.

es6> let fib = (n) => n<2?(n<1?0:1):fib(n-1)+fib(n-2);
undefined
es6> fib(2)
1
es6> fib(3)
2
es6> fib(4)
3
es6> fib(5)
5
es6> fib(6)
8
es6> fib(7)
13
es6> fib(10)
55
es6> 

Die JavaScript Variante ist selbst mit Memoizer erschreckend langsam, aber von meinem PIII war ich sowieso gewohnt, beim Ausgeben mitlesen zu können.

section .data
memo dq 0
N dq 0	
SIZE equ 8
result dq 0
result_is db "Das Resultat von fib(%ld) ist %ld", 0ah, 0
args_fmt db "Novizenprobe: Stringargument übergeben ist: %s", 0ah, 0
memo_str db "Memoizing arguments from 0 to the to %ld converted number.", 0ah, 0
usage	db "Usage: './fib n' to create a table from 0 to n and return results of calling fib.",0ah,0
basecase db "This is a base case for fib and fib(%ld) is %ld.", 0ah,0
nonegative db "No negative number arguments will work.",0ah,0
printing_all db "Printing all fib numbers up to n.",0ah,0 
printing_one db "fib(%ld) = %ld",0ah,0
;printing_one db "fib() = ",0ah,0
fmt db "%ld",0

section .text
extern scanf
extern printf
extern malloc
extern atol

global main

alloc:
	enter 0,0
	mov rax, N
	mov rbx, SIZE
	imul rax, rbx
	mov rdi, rax
	call malloc
	mov [memo], rax
	leave
	ret
memoize:
	enter 16,0
	mov rsi, [memo]
	mov qword [rsi], 0	; memo[0] = 0
	add rsi, 8		; to memo[1]
	mov qword [rsi], 1	; memo[1] = 1
	add rsi, 8		; to memo[2]
	mov rcx, 2
	mov qword [rsp], N
.loop:
	mov rax, [rsi-16] 	; memo[n-2]
	mov rdx, [rsi-8]	; memo[n-1]
	add rax, rdx		; add rax, rbx	
	mov qword [rsi], rax	; storen an memo[n]
	cmp rcx, [N]		; == N ? 
	je .done		; dann Ende
	inc rcx			; sonst increasen
	add rsi, 8		; und pointer moven		
	jmp .loop		; und wieder hoch
.done:	
	leave
	ret
fib_just_fetch:			; holt ergebnis direkt aus der tabelle
	enter 0,0
	mov rsi, [memo]
	mov rcx, [N]
	lea rdx, [rsi+rcx*SIZE]
	mov rdx, [rdx]
	mov [result], rdx
	lea rdi, [result_is]
	mov rsi, [N]
	call printf	
	leave
	ret
	
fib:				; rechnet erst einmal fib(n-1) + fib(n-2)
	enter 0,0
	mov rsi, [memo]
	mov rcx, [N]
	sub rcx, 1	; set to n-1
	lea rdx, [rsi+rcx*SIZE]	; get memo[n-1]
	mov rdx, [rdx]
	mov rax, rdx
	sub rcx, 1	; set to n-2
	mov rsi, [memo]
	lea rdx, [rsi+rcx*SIZE]	; get memo[n-2]
	mov rdx, [rdx]
	add rax, rdx	; fib(n-1)+fib(n-2)
	mov [result], rax
	lea rdi, [result_is]
	add rcx, 2
	mov rsi, rcx
	mov rdx, rax
	call printf	
	leave
	ret	
	
fib_all:			; gibt alle drunterliegenden notwendigen summen einmal aus
    enter 16,0
    lea rdi, [printing_all]
    call printf
    mov rcx, 0
.loop:          
    mov [rsp], rcx
    lea rdi, [printing_one]
    mov rsi, [memo]		; source index 
    lea rdx, [rsi+rcx*SIZE]	; zum adressladen
    mov rdx, [rdx]	; lade value
    mov rsi, [rsp]
    call printf
    mov rcx, [rsp]    
    cmp rcx, [N]
    jae .done
    inc rcx
    jmp .loop
.done:
    leave
    ret


main:
	enter 16,0		
	add rsi, 8		; argv[0]->argv[1]
	mov rcx, rsi		;
	mov rsi, [rcx]		; *argv[1]
	cmp rsi, 0		;
	jz .usage
	mov [rsp], rsi		; save arg1 on stack
	lea rdi, [args_fmt]	; Try if i have done
	call printf		; it correctly
	mov rdi, [rsp]		; convert ascii to long	
	call atol		; long atol(char *num);
	mov qword [N], rax	; at N
	cmp rax, 0
	jl .nonegative
	je .basecase
	cmp rax, 1
	je .basecase
	lea rdi, [memo_str]	; let´s check with a 
	mov rsi, [N]		; format string
	call printf		; the converted number
	call alloc		; allocate space for memoizer
	call memoize		; memoize from 0 to N
	call fib_just_fetch	; and just fetch from memo
	call fib		; or try once to calculate
	call fib_all		; try it
	leave			;
	ret			;
.nonegative:
	lea rdi, [nonegative]
	call printf
	leave
	ret
.basecase:
	lea rdi, [basecase]
	mov rsi, rax
	mov rdx, rax
	call printf
	leave
	ret
.usage:
	lea rdi, [usage]	
	call printf		
	leave			
	ret			

Hier habe ich meinen ersten Bug im Debugger gesucht. Ich hab´s mal gecaptured.

Memoizing arguments from 0 to the to 15 converted number.

Program received signal SIGSEGV, Segmentation fault.
0x000000000040077a in fib ()
(gdb) bt
#0  0x000000000040077a in fib ()
#1  0x00007fffffffdc70 in ?? ()
#2  0x0000000000400814 in main ()
(gdb) x 0x00007fffffffdc70 
   0x7fffffffdc70:	add    %al,(%rax)
(gdb) x 0x0000000000400814
   0x400814 :	leaveq 
(gdb) x 0x000000000040077a
=> 0x40077a :	mov    (%rsi,%rbx,1),%rax
(gdb) 

Das war einfach. Ich habe dann rsi und rbx ausgewechselt und es nochmal versucht.

Wenn man yasm -f elf64 -dformat=dwarf2 -o fib.o fib.yasm; g++ -g fib.o -o fib; eingibt und dann ./fib 15 kommt folgendes bei raus. -dformat=dwarf2 sowie -g sind dabei zusätzliche Debugger Optionen, um Hilfstabellen für den GDB zu erzeugen, damit man die Befehle mitlesen kann. dwarf2 und stabs sind nützlich für Linux.

 
Novizenprobe: Stringargument übergeben ist: 15
Memoizing arguments from 0 to the to 15 converted number.
Das Resultat von fib(15) ist 610
Das Resultat von fib(15) ist 610
Printing all fib numbers up to n.
fib(0) = 0
fib(1) = 1
fib(2) = 1
fib(3) = 2
fib(4) = 3
fib(5) = 5
fib(6) = 8
fib(7) = 13
fib(8) = 21
fib(9) = 34
fib(10) = 55
fib(11) = 89
fib(12) = 144
fib(13) = 233
fib(14) = 377
fib(15) = 610

Aus den Übungen

Ich mache auch zwischendurch die Übungsaufgaben aus den Büchern. So muss ich mir nichts ausdenken.

Hier verkaufe ich strlen und strdup in Assembler. In Strlen nehme ich den String an und zähle einfach bis zum 0-Byte durch. Und in strdup zähle ich einmal durch (O(n)) und alloziere n bytes und kopiere den String mit einer wiederholten Stringfunktion (rep movsb) vom Source in den Destination Index. Ich glaube, in den Übungsaufgaben durfte man strdup benutzen, aber ich zähl das zu den Stringübungen.

//strings.cc

#include <iostream>
extern "C" int strlen(char *str);
extern "C" char *strdup(char *str);
using std::cout;
using std::endl;
int main(int argc, char **argv) {
    for (int i = 0; i < argc; i++) {
        cout << i << ". argument: " << argv[i] << endl;     
        cout << "strlen = " << strlen(argv[i]) << endl;
        cout << "strlen = " << strlen(argv[i]) << endl;
        cout << "strdup = " << strdup(argv[i]) << endl;
        cout << "strdup = " << strdup(argv[i]) << endl;
    }
    return 0;
}

;strings.yasm
section .data
extern malloc

global strlen
global strdup

strlen:
    enter 0,0               ; 
    mov rax, 0              ; count = 0
    .count: 
    cmp byte [rdi], 0       ; vgl. character mit \0
    jz .done
    add rdi, 1              ; next char
    inc rax                 ; ++count
    jmp .count              ;
    .done:
    leave                   ;
    ret

strdup:
    enter 32, 0
    mov [rsp], rdi  ; char * ptr
    call strlen     ; count length
    mov rdi, [rsp]  ; restore rdi
    cmp rax, 0
    jz .error       
    mov rdi, rax    ; strlen    
    add rdi, 1      ; + zero byte           
    mov [rsp+8], rax
    call malloc     ; alloc
    cmp rax, 0      ; NULL?
    jz .error   
	mov [rsp+16], rax ; save pointer to new dest
	mov rcx, [rsp+8]  ; set counter to len    
    mov rdi, [rsp+16] ; load destination adress last byte
    mov rsi, [rsp]    ; set to source     
    cld               ; set direction
   ;.lp:
    ;lodsb
    ;stosb
    ;loop .lp
    rep movsb
    mov rax,  [rsp+16] ; der Bug war hier: lea rax, [rsp+16]
    leave
    ret
    .error:
    mov rax, 0 ; return NULL pointer
    leave
    ret

/*
0. argument: ./strings
strlen = 9
strlen = 9
strdup = ./strings
strdup = ./strings
1. argument: dfkjslkjfl
strlen = 10
strlen = 10
strdup = dfkjslkjfl
strdup = dfkjslkjfl
2. argument: ksjflksjfks
strlen = 11
strlen = 11
strdup = ksjflksjfks
strdup = ksjflksjfks
3. argument: skfjskldfasdjfs
strlen = 15
strlen = 15
strdup = skfjskldfasdjfs
strdup = skfjskldfasdjfs
4. argument: sjlkfjslkdf424
strlen = 14
strlen = 14
strdup = sjlkfjslkdf424
strdup = sjlkfjslkdf424
*/


/*

Etwas davor hatte ich noch einen Bug im Listing bemerkt.
Den Fehler habe ich nachdem ich den Loop einmal rückwärts probierte
und das gleiche rauskam dann richtig vermutet. Ich mov den return Wert.

linux-dww5:/c # ./strings abc
0. argument: ./strings
strlen = 9
strlen = 9
strdup = ./string��t�       <--- achso, dann fällt mir auf: das programm hat doch einen bug. hab nicht aufgepasst.
strdup = ./string��t�       <--- es sind acht zeichen im register und hier ist nach 8 bytes ende
							<--- (wisst ihr die Loesung?) es war lea rax, [rsp+16] in der vorvorletzten Zeile
1. argument: abc	    
strlen = 3		   
strlen = 3
strdup = abc
strdup = abc

*/

Hier habe ich erst im zweiten inline Assembler Anlauf bemerkt, dass ich den return Wert wohl implizit übergeben konnte, musste mich nur trauen, die %% in % zu ändern, als ich die unteren Angaben und das %0 Register entfernte. Da musste ich keinen return double mehr ausführen, die Bits werden unverändert vom xmm zum rax bewegt, denke ich mal, und es geht nur um die Fliesskommaberechnung an sich.


// kurze version
double add(double x, double y) {    
    __asm__(   	 
	"addsd %xmm1, %xmm0;"
	"movq %xmm0, %rax;"		
    );    
}

// lange version
double add1(double x, double y) {
    double d;
    __asm__(   	 
	"addsd %%xmm1, %%xmm0;"
	"movq %%xmm0, %0;"	
	:"=r"(d)
    );
    return d;
}

#include <iostream>
using std::cout;
using std::endl;
int main() {     
    double d1 = 0.1;
    double d2 = 0.2;
    double n1 = -0.1;
    double n2 = -0.2;
    cout << add(d1,d2) << endl;
	cout << add(n1,n2) << endl;
	cout << add(0.3344,0.555) << endl;
	cout << add(0.111,0.222) << endl;
	cout << add1(d1,d2) << endl;
	cout << add1(n1,n2) << endl;
	cout << add1(0.3344,0.555) << endl;
	cout << add1(0.111,0.222) << endl;
}

/*
0.3
-0.3
0.8894
0.333
0.3
-0.3
0.8894
0.333
*/

Das geht in einem externen Assembler File noch einfacher, da xmm0 implizit als return register untersucht wird, wenn die Funktion zurückkehrt.


; add.cc
#include <iostream>
using std::cout;
using std::endl;
extern "C" double addfp(double, double);
int main() {
    cout << add(0.1,0.2) << endl;
    cout << add(-0.1,-0.2) << endl;
}

; add.yasm
section .text
global addfp
; add.yasm
addfp:
    enter 0,0
    addsd xmm0, xmm1        
    leave
    ret

/*
0.3
-0.3
*/

Logik

Die primitive Variante meines Webinterfaces kann noch keine boolesche Algebra parsen und nur einen Operator zwischen die Bits legen. Das hat allerdings auch nur 20 Minuten gedauert mit HTML Template, JavaScript und CSS Styling, laden, speichern, ausprobieren und allen anderen Eingaben und Navigationsprozessen. Meine Webstorm Evaluation läuft erstmal aus.

Anzahl der Eingänge: 3
Art der Logikfunktion: |
Generate
Combining 3 inputs to 8 possible configurations
0 ^ 0 ^ 0 = 0
0 ^ 0 ^ 1 = 1
0 ^ 1 ^ 0 = 1
0 ^ 1 ^ 1 = 0
1 ^ 0 ^ 0 = 1
1 ^ 0 ^ 1 = 0
1 ^ 1 ^ 0 = 0
1 ^ 1 ^ 1 = 1

JavaScript

Hab mir heute die GDB Dokumentation gedruckt und kann schon eine ganze Reihe Befehle und vor allem Zusammenhänge. Der Debugger wird nun auch mein Spielzeug. Wollte bis ES7 wieder in C-land sein.

// g++ -g a.cc			; -g ist hier mein neuer Trick aus der gdb Doku für eben den
// ./a.out
// $?
// 1: cmd not found
// bedeutet, das Programm gibt den Return Code $? == 1

int main () {
    int result;
    __asm__ (
    "movl $1, %0;"
    :"=r"(result)
    );
    return result;
}

// g++ -g a.cc
// gdb 
// (gdb) set write on
// (gdb) file a.out
// (gdb) break main
// Breakpoint 1 at 0x4005e4: file a.cc, line 6.
// (gdb) run
// Starting program: /root/a.out 
//
// Breakpoint 1, main () at a.cc:6
// 6           );
// (gdb) p result
// $0 = 0
// (gdb) s
// 7    return result;
// (gdb) p result
// $1 = 1 

// (gdb) set result = 2
// (gdb) print result
// $2 = 2
// (gdb) s
// 8 }
// (gdb) s
// 0x00007ffff722dbe5 in __libc_start_main () from /lib64/libc.so.6
// 

Das Program ist direkt auf die movl Anweisung gesprungen. Wie man sieht ist bei der nächsten Zeile, wo result zurück gegeben wird result bereits eins, während es auf der Zeile, wo die mov Anweisung ausgeführt wird noch 0 ist. In Zeile 7 ist sie bereits 1 und return wird zurückgegeben. Der Debugger eignet sich nicht nur für Assembler, sondern auch für C Code und einige andere Sprachen. Der set write on ist ein gag und normal nutzt man ihn, um das file zu patchen. Wichtiger ist zum Beispiel erstmal set args .... um mit Parametern zu starten. Und immer dran denken, nicht anstatt des Compilers Assembler schreiben, ausser man ersetzt langsame Parts oder generiert selbst Code. Der Compiler hat Optimierpässe die normal nicht zu übertreffen sind.

Gerade probiere ich CPUID Code auf einem alten 30-Euro Pentium III. Der hat sogar nur exakt eine extended Funktion 80000001h. So alt ist der.

#include <QtGui/QApplication>
#include <QtGui/QMainWindow>

int main (int argc, char **argv) {
    QApplication app(argc, argv);
    QMainWindow *mw = new QMainWindow();
    mw->setGeometry(1,1,800,600);
    mw->show();
    app.setMainWindow(mw);
    return app.exec();
}

g++ qt_Test.cc -o qt_Test -lQtGui; ./qt_Test zeigt ein Fenster.

Binaere Arithmetik

Durch Ray Seyfarth´s Introduction to x86-64 Assembly konnte ich in den Anfangskapiteln die binaere Addition und Multiplikation verstehen.

es6> let f = (x,...r) => r.reduce(((x,y)=>x+y), x)
undefined
es6> f(1,2,3,4)
10
es6> 

Die Multiplikation ist sehr einfach. Man kann sie schriftlich mit einem einfachen Trick berechnen. Wo im oberen Faktor eine 1 steht, wird die untere Zahl hingeschrieben. Die letzte Ziffer an die 1. Wo die 0 steht, wird eine 0 hingeschrieben. Kommt wieder eine 1 im oberen Faktor, wird der untere Faktor mit der letzten Zahl an der 1 wieder hingeschrieben. Wenn man alle Stellen untereinander geschrieben hat. Dann addiert man die Zahlen. Das ist doch wohl super einfach, oder?

        1001
    *   1111
------------
	1111
	   0
	  00
+    1111000
------------
    10000111

Probe: 
    1001 = 9; 
    1111 = 15; 
    9 * 15 = 135 
    135 = 128 + 4 + 2 + 1 = 10000111

Die binaere Addition ist auch sehr einfach. 1+0=1; 1+1=10; 1+1+1=11; 1+1+1+1 = 100; Bedeutet. Wenn man eine 1 und eine 0 addiert, kommt eine 1 hin. Hat man 1 + 1 schreibt man eine 0 hin und hat eine 1 Übertrag. Steht an naechster Stelle eine 0, schreibt man eine 1 hin. Steht dort eine 1, hat man wieder 1+1 und einen Übertrag und schreibt eine 0 hin und nimmt die 1 mit. Das macht man solange, bis man die letzte 1 allen voran geschrieben hat.

positiv				
       01111      		
    +  01111      		
------------			
    = 011110    		

Probe: 
    1111 = 15;			
    1111 = 15;			
    15 + 15 = 30;		
    11110 = 30;			

Wie habe ich das gelesen? 1+1 ergibt 0, einen Übertrag. 1+1 sind 0 plus ein Übertrag sind 1, also 1 hin und Übertrag mit. 1+1+Übertrag sind 1+Übertrag, 1+1+Übertrag = 1+Übertrag, 1+1+Übertrag = 1+Übertrag, bleibt der Übertrag, also eine 1 vorne ran. Heraus kommt bei 1111+1111 dann 11110, also 30.

Maschinensprache pauken

Um eine virtuelle Maschine für syntax.js zu entwickeln, die, mit einem ArrayBuffer als Heap, einen ByteCode- statt AST-Interpreter bietet, und aufgrund ihres auf Zahlen geschrumpften Formats hoffentlich die Performance wieder reinholt, die mit dem AST unmöglich zu erreichen ist.

; Author Jeff Dunteman aus seinem Buch "Assembly Language Step by Step"
; 32bit code amateurhaft von mir angepasst (pushad,popad) um auf x64 linux zu laufen
section .bss
bufflen equ 10
buff resb bufflen
section .data
dumplin db " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
dumplen equ $-dumplin
asclin db "|................|", 10
asclen equ $-asclin
fulllen equ $-dumplin
hexdigits db "0123456789ABCDEF"
dotxlat:	; einziges copy und paste opfer
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 20h,21h,22h,23h,24h,25h,26h,27h,28h,29h,2Ah,2Bh,2Ch,2Dh,2Eh,2Fh
db 30h,31h,32h,33h,34h,35h,36h,37h,38h,39h,3Ah,3Bh,3Ch,3Dh,3Eh,3Fh
db 40h,41h,42h,43h,44h,45h,46h,47h,48h,49h,4Ah,4Bh,4Ch,4Dh,4Eh,4Fh
db 50h,51h,52h,53h,54h,55h,56h,57h,58h,59h,5Ah,5Bh,5Ch,5Dh,5Eh,5Fh
db 60h,61h,62h,63h,64h,65h,66h,67h,68h,69h,6Ah,6Bh,6Ch,6Dh,6Eh,6Fh
db 70h,71h,72h,73h,74h,75h,76h,77h,78h,79h,7Ah,7Bh,7Ch,7Dh,7Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
db 2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh,2Eh
section .text
clearline:
    push rdx
    push rbx
    push rcx
    push rdx
    mov edx, 15
.poke: mov eax, 0
    call dumpchar
    sub edx, 1
    jae .poke
    pop rax
    pop rcx
    pop rbx
    pop rax
    ret
dumpchar:
    push rbx
    push rdi
    mov bl, byte [dotxlat+eax]
    mov byte [asclin+edx+1], bl
    mov ebx, eax
    lea edi, [edx*2+edx]
    and eax, 0000000Fh
    mov al, byte [hexdigits+eax]
    mov byte [dumplin+edi+2], al
    and ebx, 000000F0h
    shr ebx, 4
    mov bl, byte [hexdigits+ebx]
    mov byte [dumplin+edi+1], bl
    pop rdi
    pop rbx
    ret
printline:
    ;pushad existiert nicht mehr und
    push rdx
    push rbx
    push rcx
    push rdx
    mov eax, 4
    mov ebx, 1
    mov ecx, dumplin
    mov edx, fulllen
    int 80h
    ;popad und ich muss erstmal ersetzen bzw. entfernen lernen
    pop rax 
    pop rcx
    pop rbx
    pop rax
    ret
loadbuff:
    push rax
    push rbx
    push rdx
    mov eax, 3
    mov ecx, buff
    mov edx, bufflen
    int 80h
    mov ebp, eax
    xor ecx, ecx
    pop rdx
    pop rbx
    pop rax
    ret
global _start
_start:
    nop	; gdb trick (wohl weit verbreitet oder auch einer der Standardtricks)
    nop
    xor esi, esi
    call loadbuff
    cmp ebp, 0
    jbe exit
scan: 
    xor eax, eax
    mov al, byte [buff+ecx]
    mov edx, esi
    and edx, 0000000Fh
    call dumpchar
    
    inc esi
    inc ecx
    cmp ecx, ebp
    jb .modtest
    call loadbuff
    cmp ebp, 0
    jbe done
.modtest:
    test esi, 0000000Fh
    jnz scan
    call printline
    call clearline
    jmp scan
done:
    call printline
exit: mov eax, 1
    mov ebx, 0
    int 80h
/*
öskjfklsjdflksjdfkslfjsklfjlskdf
 6A 6C 64 66 73 0A 0A C3 B6 73 6B 6A 66 6B 6C 73 |jldfs....skjfkls|
 6A 64 66 6C 6B 73 6A 64 66 6B 73 6C 66 6A 73 6B |jdflksjdfkslfjsk|
sldfjslkjflksjdflkdsjflksdjf
 6C 66 6A 6C 73 6B 64 66 0A 73 6C 64 66 6A 73 6C |lfjlskdf.sldfjsl|
 6B 6A 66 6C 6B 73 6A 64 66 6C 6B 64 73 6A 66 6C |kjflksjdflkdsjfl|
sdf
sdfkjsldfjslkfjlskdfjlksjdflskdf
 6B 73 64 6A 66 0A 73 64 66 0A 73 64 66 6B 6A 73 |ksdjf.sdf.sdfkjs|
 6C 64 66 6A 73 6C 6B 66 6A 6C 73 6B 64 66 6A 6C |ldfjslkfjlskdfjl|
sklfjsklfjlsjfjlskdjfsdf
 6B 73 6A 64 66 6C 73 6B 64 66 0A 73 6B 6C 66 6A |ksjdflskdf.sklfj|
 73 6B 6C 66 6A 6C 73 6A 66 6A 6C 73 6B 64 6A 66 |sklfjlsjfjlskdjf|
södfjslkdfjslkfjsdf
 73 64 66 0A 73 C3 B6 64 66 6A 73 6C 6B 64 66 6A |sdf.s..dfjslkdfj|
*/

Lokale Variablen vom Stack.

section .text
global main
extern printf
main:    
    push rbp
    mov rbp, rsp
    call fn
    pop rbp	
    ret		; comment out and run the code below
fn:
.t1 equ 0	; just the index
.t2 equ 8	; for rsp + index
.t3 equ 16	; for 32 stack bytes
.t4 equ 24	; .. that we don´t need to write numbers and can change in one place
push rbp	; establish
mov rbp, rsp	; a stackframe
sub rsp, 32	; allocate four variables a eight byte
mov qword [rsp+.t1], 100
mov qword [rsp+.t2], 200
mov qword [rsp+.t3], 300
mov qword [rsp+.t4], 400 ; give these four times eight bytes some values
lea rdi, [fmt]
mov rsi, [rsp+.t1]	; lea rsi, [rsp+.t1] loads the wrong address
call printf
lea rdi, [fmt]
mov rsi, [rsp+.t2]	
call printf
lea rdi, [fmt]
mov rsi, [rsp+.t3]
call printf
lea rdi, [fmt]
mov rsi, [rsp+.t4]
call printf
leave		; clean up stackframe
ret		; return from subroutine;
section .data
fmt db "local variable has value %i", 0dh, 0ah, 0

/*
local variable has value 100
local variable has value 200
local variable has value 300
local variable has value 400
*/
// Wenn man das ret in main auskommentiert, kommt raus:
/*
local variable has value 100
local variable has value 200
local variable has value 300
local variable has value 400
local variable has value 100
local variable has value 200
local variable has value 300
local variable has value 400
*/

Hier wie mir einfaellt, wie man den Debugger im Interpreter unterbringe.

    let mainloop = (stdlib, foreign, wtf) => {
	"use asm";
	while (1) {
	    // this is a real javascript processor
	    code = [[abstract fetch]];
	    switch (code) {
		case MOV:
		// decode
		case NOP:
		    // execute "EmptyStatement"
		    break;
		default:
		    predefinedInstructionError();
		    break;
	    
	    }
	    // debugger implementation here
	    if (debugger) {	// being unspecial
		updateDebuggerFrame();
	    }
	    // dont forget to increment the program counter
	}
    }	
    // can arrows become asm functions? es6 will tell.

Solange, Schwächen zeigen.

;
; stupid program to enter a loop limit
; to execute a loop to the count of limit
; on negative input you may retry
; on strings or scanf returning 0 it exits
; on input of 0 it just doesnt loop and exits
;
section .data
fmtInteger db "%ld", 0
fmtLoopCnt db "LoopCnt is at %ld",0dh,0ah,0
fmtEnterLimit db "Enter Loop Limit: ", 0
fmtYouEntered db "You entered: %ld",0dh,0ah,0
inputError db "Error: scanf returned 0",0dh, 0ah, 0
negativeError db "Error: no negative numbers",0dh, 0ah, 0
zeroInputError db "Error: no loop with zero iterations",0dh,0ah,0

section .text
global main
extern printf, scanf, malloc
main:
    i  equ 0
    cnt equ 8
    push rbp
    mov rbp, rsp
    sub rsp, 16
retry:
    lea rdi, [fmtEnterLimit]    
    call printf
    lea rdi, [fmtInteger]
    lea rsi, [rsp+i]    
    call scanf    
    cmp rax, 0			; bei eingabe von strings z.b. 
    je input_error		; 
    mov rax, 0
    cmp [rsp+i], rax
    je zero_input_error
    jl negative_error		
    lea rdi, [fmtYouEntered]
    mov rsi, [rsp+i]    
    call printf
    mov qword [rsp+cnt], 0
looper:
    lea rdi, [fmtLoopCnt]
    mov rsi, [rsp+cnt]
    call printf    
    inc qword [rsp+cnt]
    mov rcx,  [rsp+cnt]
    cmp rcx,  [rsp+i]
    jb looper
    jmp exit
input_error:
    lea rdi, [inputError]    
    call printf    
    jmp exit
negative_error:
    lea rdi, [negativeError]    
    call printf
    jmp retry
zero_input_error:
    lea rdi, [zeroInputError]    
    call printf
    jmp exit
exit:
    leave
    ret

;Enter Loop Limit: You entered: 5
;LoopCnt is at 0
;LoopCnt is at 1
;LoopCnt is at 2
;LoopCnt is at 3
;LoopCnt is at 4
;
;
;Enter Loop Limit: -1
;Error: no negative numbers
;Enter Loop Limit: 0
;Error: no loop with zero iterations
;
;Enter Loop Limit: errtrrr
;Error: scanf returned 0

Hier gibt ein Unterprogramm dem anderen die Daten auf dem Stack mit.

section .data
parm1 db "Das ist ein %s. %s %s. saved rbp at r11=%p, actual rbp=%p.", 0dh, 0
parm2 db "Test", 0
parm3 db "Ich push die Parameter auf den Stack.", 0
parm4 db "Und subtrahiere vom rbp statt zum rsp zu addieren.", 0

section .text
global main
extern printf
main:
    enter 0,0
    push parm1
    push parm2
    push parm3
    push parm4
    call take
    leave
    ret
    
take:
    mov r11, rbp
    enter 32,0		; Take 32 local bytes (unused)
    mov rdi, [r11-8]	; Vom rbp wird subtrahiert
    mov rsi, [r11-16]	; zum rsp wird addiert um die 
    mov rdx, [r11-24]	; Speicherstellen zu adressieren
    mov rcx, [r11-32]	; würde ich mal sagen
    mov r8, r11
    mov r12, rbp
    mov r9, r12  
    call printf
    leave
    ret
    
;Das ist ein Test. Ich push die Parameter auf den Stack. Und subtrahiere vom rbp statt zum rsp zu addieren.. saved rbp at r11=0x7fff12345940, actual rbp=0x7fff12345910.
;Das ist ein Test. Ich push die Parameter auf den Stack. Und subtrahiere vom rbp statt zum rsp zu addieren.. saved rbp at r11=0x7fff7485c060, actual rbp=0x7fff7485c030.
;Das ist ein Test. Ich push die Parameter auf den Stack. Und subtrahiere vom rbp statt zum rsp zu addieren.. saved rbp at r11=0x7fffab0ef000, actual rbp=0x7fffab0eefd0.

Der Zugriff auf nested scopes, oder "den Stackframe 0-31" drunter, kann man mit enter einrichten. Die Adressen der drunterliegenden Stackframes werden entsprechend dem zweiten Parameter von enter kopiert und man kann die als Basis in ein Register laden.


; scope.yasm
; demonstriert enter und leave
; enter alloziert speicher und kopiert 0..31 drunterliegende basepointer
; damit kann man dynamisches scoping realisieren
; das ist anders als in hochsprachen, wo lexikalisch gebunden wird, wo der
; funktionskörper steht. die hier sind gebunden, wo die funktion läuft.
; leave setzt die base- und stackpointer wieder zurück

section .data
data1a	db "abcdefg",0
data1b	db "hijklmn",0
data2a  db "opqrstu",0
data2b  db "vwxyz12",0
fmt     db "accessing from other scope: %s", 0ah, 0
testfmt     db "testing local variable: %s", 0ah, 0
t1 	equ 0
t2	equ 8

section .text
global main
extern printf

main:
    enter 16,0
    call basescope1
    call basescope2
    leave
    ret

basescope1:
    enter 32, 0
    mov qword [rsp+t1], data1a
    mov qword [rsp+t2], data1b
    lea rdi, [testfmt]
    mov rsi, [rsp+t1]    
    call printf
    lea rdi, [testfmt]
    mov rsi, [rsp+t2]    
    call printf
    call scope_accessor
    leave 
    ret
    
basescope2:
    enter 100, 0
    mov qword [rsp+t1], data2a
    mov qword [rsp+t2], data2b    
    lea rdi, [testfmt]
    mov rsi, [rsp+t1]    
    call printf
    lea rdi, [testfmt]
    mov rsi, [rsp+t2]    
    call printf
    call scope_accessor
    leave 
    ret

scope_accessor:
    enter 16, 2	
    mov r10, [rsp+48]	 ; pointer zu basescope
    lea rdi, [fmt]
    lea rsi, [r10+t1]    
    mov [rsp], r10	; push r10 in allozierten bereich
    push r10
    call printf
    mov r10, [rsp]	; pop r10	
    lea rdi, [fmt]
    lea rsi, [r10+t2]    
    call printf
    leave
    ret
/*
testing local variable: abcdefg
testing local variable: hijklmn
accessing from other scope: abcdefg
accessing from other scope: hijklmn
testing local variable: opqrstu
testing local variable: vwxyz12
accessing from other scope: opqrstu
accessing from other scope: vwxyz12
*/    

Mit cpuid kann man einige Stunden verbringen, der Befehl ist sehr umfangreich. Ich hatte zwischendurch meine ersten Versuche mit cpuid gepostet und denke, das kann ich machen, wenn ich die ganzen fünfzehn Befehle plus zehn extended Befehle durch habe. Sie haben je 4 Register mit 4 Ints oder 16 Zeichen.

; eine ausgabe von cpuid, und zwar fkt. 80000002h..80000004h 
; der processor brand string. in 32 bit registern was auf meinem
; pentium iii was anderes ergibt, und zwar
section .data
processor_brand db 48
processor_brand_end db 0

section .text
extern printf
global main
main:
enter 0,0
mov eax, 80000002h
cpuid
mov [processor_brand], eax
mov [processor_brand+4], ebx
mov [processor_brand+8], ecx
mov [processor_brand+12], edx

mov eax, 80000003h
cpuid
mov [processor_brand+16], eax
mov [processor_brand+20], ebx
mov [processor_brand+24], ecx
mov [processor_brand+28], edx

mov eax, 80000004h
cpuid
mov [processor_brand+32], eax
mov [processor_brand+36], ebx
mov [processor_brand+40], ecx
mov [processor_brand+44], edx

lea edi, [processor_brand]
call printf
leave
ret

Intel(R) Core(TM)2 Duo CPU     T8100  @ 2.10GHz
; Demo of conditional moves 
; Achtung: Es ist nicht korrekt mehrere cmov untereinander zu schreiben
; Das Ergebnis hängt vom letzten Befehl, also dem cmov davor ab
; Darum sollte man das nur tun, wenn man genau weiss, was da rauskommen wird.
; So wie hier.
section .data
minus equ -20
plus equ 20
zero equ 0
hundred equ 100
ninetynine equ 99
fmt db "die value ist: %ld",0ah,0

section .text
extern printf
global main

main:
    enter 32,0

    ; pads
    mov qword [rsp], hundred
    mov qword [rsp+8], ninetynine
    mov qword [rsp+16], minus
    mov qword [rsp+24], plus
    mov qword [rsp+32], zero


    ; 100
    lea rdi, [fmt]
    mov rax, hundred
    cmovge rsi, [rsp]
    cmovl rsi, [rsp+8]
    call printf

    ; 100
    lea rdi, [fmt]
    mov rax, ninetynine
    cmovge rsi, [rsp] ; 100
    cmovl rsi, [rsp+8] 
    call printf

    ; 99
    lea rdi, [fmt]
    mov rax, zero
    cmovge rsi, [rsp+8]	; 99
    cmovl rsi, [rsp]
    call printf

    ; 20
    lea rdi, [fmt]
    mov rax, plus
    cmovl rsi, [rsp+16]
    cmovge rsi, [rsp+24]	; 20
    call printf


    ; -20
    lea rdi, [fmt]
    xor rax, rax
    mov rax, [rsp+16]
    ; ab hier vermutete ich,
    ; dass zwei cmov sich beeinflussen können
    ; wenn der erste mov gemacht wird und mit
    ; deren ergebnis der zweite true ist
    cmovle rsi, [rsp+16]	
    cmovg rsi, [rsp+24]
    call printf

    ; 0
    lea rdi, [fmt]
    xor rax, rax
    mov rax, zero
    cmovz rsi, [rsp+32]	; 0
    cmovnz rsi, [rsp+24]
    call printf

    leave
    ret

Bei CPUID und SIMD probiere ich viel mit extern "C" Funktionen und sowohl gelinktem als auch inline gesetztem Assembler.

section .data
str1 db "Ich bin zu kopieren", 0
strlen equ $-str1
fmtout db "output: %s", 0ah, 0
str2 times strlen db 0

section .text
global main
extern printf ; scanf
    
%macro print 1   
    lea rdi, [fmtout]
    mov rsi, %1
    call printf
%endmacro

main:
    enter 0,0
    mov rsi, str1
    mov rdi, str2
    mov rcx, strlen
    cld
    rep movsb
    print str2
    leave
    ret
    
; output: Ich bin zu kopieren

Das ist eine der Stringfunktionen gewesen, kopieren eines Strings mit rep movsb. von SI nach DI und zwar CX Bytes.

Das dümmste bei meinen ersten Listings war, dass ich eine Reihe von xor rax,rax und mal xor rsi, rsi vor printfs da hatte, warum auch immer, in rax landet die Anzahl der Floating Point Argumente und das andere war wohl irgendein Bug der mich zu zwang. Ich habe das aus dem Code entfernt, weil´s so peinlich war :)

Single Static Assignment und Mozillas Parser API

Das Kompilieren des ASTs sollte man auch zum Optimieren benutzen, wenn man den AST zum Kompilieren übergibt, kann ihn ein vorheriger Pass bereits verkürzt haben. Eine Form, Ausdrücke in eine bessere Form zu bringen ist der Three-Address-Code, der aus op1 = op2 op op3 besteht und mit der Spezialform SSA Static Single Assignment in der Compiler- und Interpreterwelt oft vertreten ist. Anstatt eine neue IR zu erfinden, denke ich mir, dass noch mit dem AST zu machen und ihn dann zu kompilieren.

Performanceerwartung. Das Kompilieren dauert länger als das direkte Evaluieren des ASTs um zu starten, aber das kompilierte Programm läuft hinterher schneller, dass es mir Recht sein sollte.

Expression:

Als Beispiel nehme ich mal einen einfachen Ausdruck. Hieraus erzeuge ich jetzt einmal einen AST-Zweig und dann eine IR mit der Einmalzuweisung.

a + b * c + d 

Hier kommt der AST.

ast expression:

{
    type: "Program",
    body: [
    {
    type: "VariableDeclaration",
    kind: "var",
    declarations: [
	{ type: "VariableDeclarator", id: "a" },
	{ type: "VariableDeclarator", id: "b" },
	{ type: "VariableDeclarator", id: "c" },
	{ type: "VariableDeclarator", id: "d" }
    ],
},
{
    type: "BinaryExpression",
    operator: "+",
    left: "d",
    right: {
	type: "BinaryExpression"
        operator: "+",
	left: { type: "Identifier", name: "a" },
        right: {
    	    type: "BinaryExpression",
    	    operator: "*",
    	    left:    { type: "Identifier", name: "b" },
	    right:    { type: "Identifier", name: "c" }
	}
    }]
}    

Und als nächstes Beispiel folgt die SSA. Hier werden verschachtelte Ausdrücke in eine Reihenfolge gebracht und mit Einweg-Variablen versehen und entsprechend an anderer Stelle ersetzt. Sieht nicht nur ordentlich aus, lässt sich auch scannen und optimieren.

SSA (Single Static Assignment)

t1 = b * c
t2 = a + t1
t3 = t2 + d

Da ich einen Mozilla Parser API Parser habe, und keine Lust habe, noch eine IR (Intermediate Representation, Zwischensprache) zu entwickeln, zu parsen und zu interpretieren, muss die Verwandlung ins SSA Format auch im Mozilla AST Format stattfinden.

Dazu erzeuge ich neben den reduzierten Ausdrücken mit der Einmalzuweisung gleich noch eine Reihe von Variablen.

AST mit SSA:

{
type: "Program" 
body: [
{
    type: "VariableDeclaration",
    kind: "var",
    declarations: [
	{ type: "VariableDeclarator", id: "a" },
	{ type: "VariableDeclarator", id: "b" },
	{ type: "VariableDeclarator", id: "c" },
	{ type: "VariableDeclarator", id: "d" },
	{ type: "VariableDeclarator", id: "t1" },
	{ type: "VariableDeclarator", id: "t2" },
	{ type: "VariableDeclarator", id: "t3" }
    ]
},
{
    type: "AssignmentExpression",
    operator: "=",
    left: { type: "Identifier", name: "t1" },
    right: { type: "BinaryExpression", 
	     operator: "*",
	     left: {type: "Identifier", name: "b" },
	     right: { type: "Identifier", name: "c" }
	    }
},
{
    type: "AssignmentExpression",
    operator: "+",
    left: { type: "Identifier", name: "t2" },
    right: { type: "BinaryExpression", 
	     operator: "+",
	     left: {type: "Identifier", name: "a" },
	     right: { type: "Identifier", name: "t1" }
	    }
},
{
    type: "AssignmentExpression",
    operator: "=",
    left: { type: "Identifier", name: "t3" },
    right: { type: "BinaryExpression", 
	     operator: "+",
	     left: { type: "Identifier", name: "t2" },
	     right: {type: "Identifier", name: "d" }
	    }
}
]
}

Algorithmus?

Wenn man auf eine BinaryExpression trifft, zum Beispiel a + b + c entnimmt man ihre rechte Seite (b+c) und weist sie einem temp zu. t1 = b + c. Dann ersetzt man in a + b + c das b + c mit a + t1 t2 ergibt sich danach aus t2 = a + t1. Aus zwei BinExps mit drei Identifiern werden 2 Assignments mit je einer BinExp und insgesamt fünf Identifiern. Hinzu zu a, b, c kommen t1 und t2.

Vorab Fazit

Ohne einen weiteren Optimierschritt ist das Programm jetzt sogar länger als vorher und braucht mehr Variablen. Allerdings folgt jetzt im nächsten Durchgang bereits die Vereinfachung mit der ich mich nun ausserdem beschäftigen darf.

Aber vorher noch ein paar Fallstricke beim Vereinfachen der Expressions. Zum Beispiel die Argumente im Call. Diese muessen vor den Funktionsaufruf geschrieben werden.

call:
-----
f(a+b, c+d);

wird zu:
--------
t1 = a+b
t2 = c+d
f(t1,t2);

Ah..neues Material

May 22, 2014 Draft Rev 25

Die letzte ES6 Spezifikation.

Allen Wirfs-Brock, der Editor vom Draft, zeigt gehobenes Softwareengineering. Anforderungsermittlung, verbal und in Pseudocode. Sowie das Auflösen von Bugs aus dem Bugtracker. Ich hoffe was für´s Leben raus zu lernen, denn besser kann man das eigentlich nicht mehr machen.

Mysteries of JavaScript

Frage: Warum wirft eine undefinierte Variable eigentlich keinen Fehler, wenn man sie mit typeof prüft?

> x
ReferenceError: can not resolve identifier x
> typeof x
"undefined"

Antwort: Wenn man den Identifier x wie eine normale Variable einsetzt, wird Reference.GetValue(x) gerufen. Die Operation ist dazu angehalten, sollte Reference.IsUnresolvableReference(x) true sein, eine Completion("throw", new ReferenceError(message)) zu returnen. Hingegen ist die Semantik des typeof Ausdrucks so definiert, dass, sollte Reference.IsUnresolvableReference(x) === true sein, einfach ein return NormalCompletion("undefined") erfolgt.

Darum kann man mit typeof nicht existente Variablen auf "undefined" testen, ohne daß sie definiert sein muessen, waehrend an jeder anderen Stelle schon blosses niederschreiben des Namens bereits einen ReferenceError ausloest.

EcmaScript 6 - Best of - Coole Features - Mini Demos

Class Declaration

Was sie hier sehen ist die neue ES6 "class" Deklaration, wie sie von einem Array erbt. Laesst man den Klassenkonstruktor weg, wird implizit constructor(...args) { super(...args); } aufgerufen. Der String wird von syntax.js übrigens intern mit parseGoal("MethodDefinition", "constructor(...args){super(...args)}"); eingelesen, womit man gezielt einzelne Produktionen lesen kann.

class C extends Array{
    method() {
	return this.join('');
    }
}
class D extends C {
    method() {
	return "Der " + super();
    }
}
let d = new D("E","d","w","a","r","d");

In ES7 wird es wohl defensible classes geben. Hier die konstante Klasse, die komplett immutable (unveränderlich) ist.

const class C {
    method (a) { return a; }
}
let c = new C;

Wie man sieht, method kann nicht ueberschrieben werden. Neue properties werden nicht mehr erzeugt. Die gesamte Klasse, Konstruktor, Prototype, ist eingefroren. Jede Instanz wird eingefroren. Die Instanzen sind absolut unveränderlich. Der Entwurf geht aber noch weiter.

Template Literal

Und hier noch das neue Template Literal. Wenn man es in Backticks an String.raw uebergibt, mit oder ohne runde Klammern, werden die Variabelnamen oder Ausdruecke zwischendrin ersetzt. Gibt man den TemplateString an eine Variable, muss man die Variablen als Argumente hinter dem Template an String.raw übergeben.

    let name = "Edward"
    let attr = "hohl";
    let string = String.raw`${name} nur etwas ${attr}!`;
    let tmpl = `${name} nur etwas ${attr}!`;
    let string2 = String.raw`${ (function() { return "Eddie"; }()) } war hier.`;

for-of

Hier noch die neue for-of Iteration. Einmal als String Iterator, damit wird jeder Charakter ausgegeben. Und einmal mit dem Array.prototype.entries() Iterator, der ein Paar [key, value] pro Index wiedergibt.

    let x = 10;
    for (let x of "Edward") {
	console.html('#console3', x);
    }
    for (let [k,v] of ["E","d","w","a","r","d"].entries()) {
	console.html('#console3', "position "+k+" enthaelt "+v);
    }

Array Comprehension

Eine gemütliche Variante um Arrays zu erzeugen ist die neue Array Comprehension. Man schliesst in die gewohnten [] einen oder mehrere for-of´s und Konditionen ein, und aggregiert die am Ende nicht gefilterten Werte in einen Array.

    let array = [for (x of "E-d-w-a-r-d") for (x of "G=e=r=h=o=l=d") if (x != "-") if (x != "=") "*"+x+"*"];

Destructuring

Besonders bequem wird´s ab ES6 mit dem Destrukturieren von Objekten und Arrays. Mit Defaultparametern, Aliasnamen und Rest- und Spreadoperationen hat man eine ganze Palette an neuen Moeglichkeiten, besseren Code zu schreiben. Allerdings möchte ich hinweisen, das hin- und herübertragen von [...rest] = [...spread] nur so zum Spass bedeutet, jeweils den Array zu durchlaufen, also eine Reihe von O(n) Operationen. Das sollte man nicht vergessen. Richtig eingesetzt ist die Destrukturierung ein unverzichtbares neues Werkzeug.

// Object Destructuring
function f({a, b}) {
    return a+b;
}

let sum1 = f({a:6, b:4});

// Array Destructuring und Rest Argument
function g([a,b], ...rest) {
    return a+b + rest.reduce((x,y)=>x+y); // arrow
}
let sum2 = g([10,10],10,10);

// Objektproperties einzelnen Variablen zuweisen.
let {a,b,c} = { a: "Eddie", b: "ist", c: "toll" };
let string1 = a + " " + b + " " + c;

// Man kann die Properties beim Destrukturieren mit : auch umbenennen.
let {a:alias1, b:alias2, c:alias3} = { a: "Edward", b: "hat´s", c: "hingekriegt" };
let string2 = alias1 + " " + alias2 + " " + alias3;

// Arrays kann man bequem in Elemente und Rest zerlegen. 
let [d,e,...rest] = [8,9,10,11,12];
let sum3 = d + e + rest.reduce((x, y) => x+y);

// Eine Funktion, die auch in Wirklichkeit alle Argumente im Rest sammelt und danach in einen neuen Array gespreaded zurueckgibt
function h(...args) {
    return [...args];
}

// Hier sieht man, dass die Elision ,, auch die Defaultargumente triggert
let [x='Def',y='ault',...array] = [,,...h(1,2,3,4,5,6)];
let sum4 = array.reduce((x,y)=>x+y);
let sum5 = x+y;

Die Operation { x: LeftHandSideExpression } wurde vergessen und folgt in Kürze;

Arrow Expression

Die neuen Lightweight Pfeilfunktionen koennen mit wenig Tipparbeit geschrieben werden. Ausserdem bieten sie ein lexikalisches this. Also das this des umschliessenden Scopes, kein eigenes oder gar globales Objekt. Mit arrows lassen sich tolle Funktionen schreiben, und der Funktionskörper kann eine Expression sein oder ein { block }.

// single identifier argument braucht keine Klammern
let square = x => x*x; 

// kann aber Klammern haben
let identity = (x) => x;

// default argumente, und auch rest.
let add = (x=10,y=20) => x+y;
let sum1 = add();

let collect = ...rest => rest;
let six = collect(1,2,3).reduce(add);

// ein unreelles Beispiel, das aber das lexikalische this zeigt. es zeigt auf demo.
let demo = ({
    method() {
	let help = (x,y) => this[x] = y;
	for (let [x,y] of ["E","d","w","a","r","d"].entries())  help(x,y);
	this.length = 6;
	return this;
    }
}).method();
for (let x of Array.prototype.values.call(demo)) console.html('#console6', x);

// mit Block und notwendigem return Statement
let fx = (a,b,c) => { a*=2, b*=3; c*=4; return a+b+c; }

ArrayBuffer

Wieder mit von der Partie sind die TypedArrays. Zu ES6 sind die StructTypes nicht fertig geworden, ein Jahr später, zu ES7 wird es StructTypes, oder TypedObjects geben. Eine Kombination aus mehreren TypDeskriptoren. Sie sind mit den vorhandenen Mitteln, ArrayBuffer, DataView und *Array herstellbar, darum wird es in Zukunft auch eine StructType Implementation im Syntax.js geben.

let ab = new ArrayBuffer(1000);
let dv = new DataView(ab);
dv.setInt32(0,32756);
dv.setFloat64(10,164.33);
dv.setFloat32(18,171233.21);
dv.setUint8(23, 125);
dv.setInt8(25, 200);

Die Tests sind übrigens nicht verfälscht. Wenn man hinsieht, sieht man, dass setFloat32 und setInt8 einen anderen Wert setzen, als sie erhalten. Ich habe es mit ES5 und node (also mit V8) überprüft, es kommt das exakt gleiche Ergebnis raus.

function

Die Funktion makePerson zeigt ein neues EcmaScript 6 Feature, die Object Literal Shorthand Syntax, wo { name: name, age: age } zu { name, age } wird.

function makePerson(name, age) { return { name, age } }
let eddie = makePerson("Edward", 108);

Hier gucke ich noch einmal, ob die Regel "return [lookahead != LineTerminator] Expression" eingehalten wird. Das ein ObjektLiteral auf der naechsten Zeile mit einem LabelledStatement in einem Block verwechselt wird, und so throwt, dass ich zur Demo einen Array nehmen muss, ist bei den anderen Engines auch so. Sieht gut aus.

function lineTerminatorAhead() {
    return
    [1,2,3,4];
}
function noLineTerminatorAhead() {
    return [1,2,3,4];
}

Generator Functions

Generatoren sind aehnlich den Iteratoren. Sie schreiten immer ein yield weiter, solange, bis der Code komplett abgearbeitet ist. Wenn yield auftaucht, wird die Funktion unterbrochen. (*)

function *gen() { yield 1; }
let it = gen();
let record = it.next();

(*) Generatorfunktionen werden in der aktuellen Version von syntax.js noch nicht korrekt unterstützt.

Wer weiß warum? Richtig. Die rekursiv absteigende Evaluation des Syntaxbaumes hat kein Gedächtnis. Und ein Original Parser API AST hat keine parent-Zeiger, dass man den Baum wieder hoch kann. Es ist also eine One-Way Non-Stop Traversion.

Ich habe mehrere mehr oder weniger komplizierte Lösungen gefunden. Die schwerste und teuerste. Top Down Analyse. Die zweite, Parent Pointer adden, rauf und rechts. Und durch einen Resuming-State die Rekursion manuell kontrollieren und wiederholt fortsetzen, sobald man die aktuelle Liste abgeschlossen hat, und zur nächsten zurückwill. Die dritte, man nimmt einfach den Index der Liste und tut in auf einen Stack, wie die anderen Indexe der Liste. Spart Nodes. Viertes, das gleiche mit zusätzlichem Speichern der Node, da der Parent Pointer fehlt und man die Node sonst nicht erreicht. Fünftens, man labelt die Nodes mit ID und kann sie aus einer Liste navigieren.

Doch die beste Lösung ist die sechste, die bringt Resume for free mit. Man tauscht die rekursiv absteigende Evaluierung mit einer stackbasierten Iteration aus und kann die auf dem Stapel verbleibenden Knoten problemlos abarbeiten, wenn man wiederkehrt.

Alle Versuche Eins bis Fünf sind gescheitert, weil ich sie mittendrin unterbrochen hatte, oder sie mich mittendrin unterbrochen hatte. Die letzte allerdings wird und muss fertig werden.

Rest und Spread

Der neue "..." Operator hat zwei Bedeutungen. Als Angabe von Funktionsparametern oder im Destructuring Array Pattern ist es ein RestParameter, er nimmt alle Parameter ab der Position auf, wo er angegeben wird, also den Rest, der noch kommt. Gibt man ihn allerdings vor einem Array oder einem Iterable (mit dem neuen @@iterator) an, und zwar bei einem Funktionsaufruf, oder in einem Array, halt dort, wo man 0,1,2,3,4,5 Zaehlen kann, dann breitet er den Array oder das Iterable entsprechend ab der Position aus und verteilt die einzelnen Elemente auf die aktuelle und die der aktuellen Position folgenden Positionen.

function take(a,b,c,d) {
    return [a,b,...[c,d]]; 	// === [a,b,c,d], der Array [c,d] wird mit Spread in [a,b] ab dritter Stelle eingefügt.
}
function take2(...rest) {	// Rest
    return [...rest];		// Spread
}
let array = ["A","B","C","D"];
let copy1 = take(...array);	// Der Spread Operator verteilt den array auf a,b,c,d.
let copy2 = take2(...array);   // Der Rest Parameter von take2 nimmt die gespreadeten Elemente wieder auf.
let lowercase = [for (x of copy1) x.toLowerCase()]; // mit der Comprehension erzeuge ich einen Array kleiner Buchstaben
let join = lowercase.join('+') + copy2.join("-"); // Das ist ein String, mit Array.prototype.join erzeugt, mit einem + zwischen den kleinen und - zwischen den grossen Buchstaben.

Function.prototype.toMethod(superBinding, methodName)

Da man in der neuen ES6 Syntax auch super() Aufrufe hat, wird aktiv daran gearbeitet, Funktionen mit super() nutzbar zu machen. Dazu stellt ECMA-262 Edition 6 Function.prototype.toMethod(home, method) bereit, die aehnlich zu Function.prototype.bind ist, und zuerst sogar kurz bindSuper hiess, womit man auf einfache Weise eine Methode klonen kann, um sie mit super zu nutzen.

let obj = {
    method(name) {
	return "Ich bin "+name;
    }
};
function eddie() {
    return super('Edward');
}
function eddie2(m) {
    return super(m);
}
function eddie3(p) {
    return super(p);
}
let m = eddie.toMethod(obj, "method");
let n = eddie2.toMethod(obj, "method");
let q = eddie3.toMethod(obj, "method");

Promises

Die A+ Promises haben es ins native ES6 geschafft, wie auch in die neuen Browser ins DOM. Das untere ist jetzt das Promise in syntax.js, womit ich die NextTask(result, queue) Operation testen kann, die laueft, wenn der Callstack leer ist. Promises sind Objekte mit Logik, die es ermoeglichen, asynchrone Operationen so zu verketten, dass sie aussehen, wie normal synchrone Operationen. Jede then Operation erzeugt ein neues Promise, das mit dem Wert aufgeloest wird, den der then Handler davor, bis zum resolver, per return zurueck gegeben hat. Damit kann man wunderbar Befehle verketten, deren Ausführungszeitpunkt ungewiss ist, die aber voneinander abhängig sind..

let result;
let p = new Promise((resolve, reject) => /*done some stuff and..*/resolve("Das ist die Loesung"));
let p_strich = p.then(value => { result = value; console.html('#console13', value); return "chaining"; })
p_strich.then((v)=>console.html('#console13', v))

ES7 Object.getOwnPropertyDescriptors

Auf der es-discuss MailingListe habe ich gelesen, dass eine Pluralversion von Object.getOwnPropertyDescriptor für ES7 auf die Agenda gekommen ist. Man kann die Methode analog zu Object.defineProperties, Object.getOwnPropertyDescriptor und eben Object.defineProperty sehen. Ein Spezifikationstext hat dazu auch bereits existiert, darum habe ich den vor einer Weile mal abgeschrieben gehabt.


let object = { a: 1, b: "Hallo", c() { return this.c; } };
let other = {};

Object.defineProperties(other, Object.getOwnPropertyDescriptors(object));

EcmaScript 6 for fun

Computed Properties

Im neuen ES6 kann man endlich auch berechnete Eigenschaftsnamen mit der ObjektLiteralSyntax definieren. Fast jeder hätte doch gerne schonmal dynamische Namen im Literal generiert anstatt sie nachträglich zuzuweisen, wobei unter anderem auch die Reihenfolge beeinflusst wird. Ab ES6 sind sie wahr und alltäglich. Zuerst las ich, das würde nur mit Symbolen funktionieren, dann sah ich auf einer Folie von be (dem JavaScript Erfinder), dass ich mich wohl vertan habe und aenderte IsSymbol schnell in IsPropertyKey. Bald gibt es berechnete Eigenschaften bereits im ObjektLiteral.

let s = Symbol();
let obj = {
    [s](a) {
	return a;
    },
    ["a"+"b"+"c"]: "abc"
};

Method Definition

Gerade funktionierte die Method Definition nicht. Unerwarteterweise. Hätte ich genau hingeguckt, hätte ich gesehen, woran es liegt. Ich vergass obj. voranzustellen.

Damit nochmal kurzgefasst. Methodendefinitionen sind die elegante Kurzschreibweise, Methoden nun ohne function zu schreiben. Anstatt von key: function () {} kann man jetzt in Objekten und Klassen key() {} schreiben. Concise Methods, das ist ein alternativer Name für die Methoden.

let obj = {
    m(a=1, b=2) { return a+b; },
    n(a,b) { return a+b; },
    o(a,b) { var c = a, d = b; return c+d; },
    p(a=1,b=2) { var c = a, d = b; return c+d; }    
};

Maps und Sets

In ES6 gibt es die schon lange implementierten Datenstrukturen Map, Set, WeakMap und WeakSet, von denen letztere lose an das Objekt gebunden sind, dass sie, wenn das Objekt mit der Garbage Collection weggeräumt werden kann, auch ihre Verbindung auflösen. In syntax.js ist das vorübergehend nicht möglich, solange normale JavaScript Objekte verwendet werden, weil ich so keinen Einfluss auf die Garbage Collection habe.

Maps assoziieren einen Schlüssel mit einem Wert, nur einmalig je Schlüssel. Sets sind einfache Mengen, ohne Schlüssel und man ist Element von, oder nicht.

let key1 = {};
let key2 = {};
let key3 = "a";
let key4 = "b";
let s = new Set([key1, key2, key3, key4]);
let m = new Map();
m.set(key1, "value1");
m.set(key2, "value2");
m.set(key3, "value3");
m.set(key4, "value4");
let value1 = m.get(key1);
let value2 = m.get(key2);
let value3 = m.get(key3);
let value4 = m.get(key4);

Konstruktorentest

Ohne genügend Tests ist das Ding absolut nicht instand zu halten. Man editiert hier, dann da. Man macht hier ´nen Fehler, da. Man übersieht da noch einen, dann den. Hier probiere ich mal eine Reihe von Konstruktoren und das Ergebnis der toString Funktionen aus, sofern sie eine haben.

const object = new Object();
const array = new Array(5);
const proxy = new Proxy({},{});
const set = new Set();
const map = new Map();
const arraybuffer = new ArrayBuffer(24);
const dataview = new DataView(arraybuffer);
const regexp = new RegExp("abc");
const i8 = new Int8Array(arraybuffer);
const ui8 = new Uint8Array(arraybuffer);
const ui8c = new Uint8ClampedArray(arraybuffer);
const i16 = new Int16Array(arraybuffer);
const ui16 = new Uint16Array(arraybuffer);
const i32 = new Int32Array(arraybuffer);
const ui32 = new Uint32Array(arraybuffer);
const f32 = new Float32Array(arraybuffer);
const f64 = new Float64Array(arraybuffer);
const number = new Number(1);
const boolean = new Boolean(true);
const string = new String("abc");
const symbol = Symbol();
const func = new Function("a", "return a");
const date = new Date();
const loader = new (Reflect.Loader);
const realm = new (Reflect.Realm);
const promise = new Promise((r,rej) => r('accepted'));
const error = new Error("e");
const typeerror = new TypeError("e");
const referror = new ReferenceError("e");
const evalerror = new EvalError("e");
const syntaxerror = new SyntaxError("e");
const urierror = new URIError("e");

Java und JavaScript

Die alte Seite abgespeichert.