<commit_before>Backbone.actAs = Backbone.actAs || {};
Backbone.actAs.Memento = (function(){

	var diff = function( originalMemento, comparedMemento, mapFunction ){
		if( !( originalMemento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');
		if( !( comparedMemento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');

		var mem = comparedMemento.memento(),
			_keys = _( originalMemento.memento() )
					.chain()
					.map(mapFunction, comparedMemento.memento())
					.compact(),
			result = {};

		if( _keys.size().value() > 0 ){
			_keys.each(function(key){
				result[key] = this[key];
			}, originalMemento.memento());
		}
		return result;
	};

	var diffNonExistMap = function(value, key){return ( typeof this[key] == 'undefined' )?key:'';},
		diffChangedMap = function(value, key){return (_.isEqual( value, this[key] )||(typeof this[key] == 'undefined'))?'':key;};

	return Backbone.Model.extend({

		defaults:{
			type: 'Memento',
			memento: ''
		},

		equal: function( memento ){
			if( !( memento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');
			return _.isEqual( this.toJSON(), memento.toJSON() );
		},

		diffAdded: function(memento){
			return diff( memento, this, diffNonExistMap );
		},

		diffChanged: function( memento ){ // No type comparison here!
			return diff( memento, this, diffChangedMap );
		},

		diffDeleted: function(memento){
			return diff( this, memento, diffNonExistMap );

		},

		diff: function( memento ){
			return {
				changed:	this.diffChanged(memento),
				added:		this.diffAdded(memento),
				deleted:	this.diffDeleted(memento)
			};
		},

		memento: function(memento){
			if( typeof memento != 'undefined' ) this.set({memento:_.clone(memento)});
			return this.get('memento');
		},

		type: function(type){
			if( typeof type != 'undefined' ) this.set({type:_.clone(type)});
			return this.get('type');
		}

	});

})();

Backbone.actAs.MementoCollection = Backbone.Collection.extend({
	model: Backbone.actAs.Memento
});

Backbone.actAs.Mementoable = (function(){

	var deepClone = function(obj, depth) { // thanks to https://github.com/kmalakoff for this codeю

		if (!obj || (typeof obj !== 'object')) return obj; // by value
		else if (_.isString(obj)) return String.prototype.slice.call(obj);
		else if (_.isDate(obj)) return new Date(obj.valueOf());
		else if (_.isFunction(obj.clone)) return obj.clone();

		var clone;

		if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
		else if (obj.constructor!=={}.constructor) return obj; // by reference
		else clone = _.extend({}, obj);

		if (!_.isUndefined(depth) && (depth > 0)) {
			for (var key in clone) {
				clone[key] = deepClone(clone[key], depth-1);
			}
		}

		return clone;
	};

	return {

		lastMemento: false,

		initMementoable: function(){
			if( typeof this._saveMemento == 'function' ){
				this.on('memento:save', this._saveMemento, this);
			}
			if( typeof this._beforeRestoreMemento == 'function' ){
				this.on('memento:before-restore', this._beforeRestoreMemento, this);
			}
			if( typeof this._afterRestoreMemento == 'function' ){
				this.on('memento:after-restore', this._afterRestoreMemento, this);
			}
			return this;
		},

		getStoredMemento: function(){
			return this.lastMemento;
		},

		storeMemento: function( memento ){
			this.lastMemento = memento || this.saveMemento();
			return this;
		},

		saveMemento: function(){
			var memento = new Backbone.actAs.Memento({memento: deepClone(this.toJSON(), 100) });
			this.trigger('memento:save', memento);
			return memento;
		},

		restoreMemento: function( memento ){
			var memento = memento || this.lastMemento;
			if(!memento) return this; //?
			this.trigger('memento:before-restore', memento);
			if( this instanceof Backbone.Model ) {
				this.set(memento.get('memento'));
			}else{
				this.reset(memento.get('memento'));
			};
			this.trigger('memento:after-restore', memento);
			return this;
		}

	};
})();<commit_msg>Adding a `diffKeys` method to mementos<commit_after>Backbone.actAs = Backbone.actAs || {};
Backbone.actAs.Memento = (function(){

	var diff = function( originalMemento, comparedMemento, mapFunction ){
		if( !( originalMemento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');
		if( !( comparedMemento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');

		var mem = comparedMemento.memento(),
			_keys = _( originalMemento.memento() )
					.chain()
					.map(mapFunction, comparedMemento.memento())
					.compact(),
			result = {};

		if( _keys.size().value() > 0 ){
			_keys.each(function(key){
				result[key] = this[key];
			}, originalMemento.memento());
		}
		return result;
	};

	var diffNonExistMap = function(value, key){return ( typeof this[key] == 'undefined' )?key:'';},
		diffChangedMap = function(value, key){return (_.isEqual( value, this[key] )||(typeof this[key] == 'undefined'))?'':key;};

	return Backbone.Model.extend({

		defaults:{
			type: 'Memento',
			memento: ''
		},

		equal: function( memento ){
			if( !( memento instanceof Backbone.actAs.Memento ) ) throw Error('Trying to compare non-Memento object.');
			return _.isEqual( this.toJSON(), memento.toJSON() );
		},

		diffAdded: function(memento){
			return diff( memento, this, diffNonExistMap );
		},

		diffChanged: function( memento ){ // No type comparison here!
			return diff( memento, this, diffChangedMap );
		},

		diffDeleted: function(memento){
			return diff( this, memento, diffNonExistMap );

		},

		diff: function( memento ){
			return {
				changed:	this.diffChanged(memento),
				added:		this.diffAdded(memento),
				deleted:	this.diffDeleted(memento)
			};
		},

		diffKeys: function( memento ){
			return _.flatten([
				_.keys( this.diffChanged(memento) ),
				_.keys( this.diffAdded(memento)   ),
				_.keys( this.diffDeleted(memento) )
			]);
    },

		memento: function(memento){
			if( typeof memento != 'undefined' ) this.set({memento:_.clone(memento)});
			return this.get('memento');
		},

		type: function(type){
			if( typeof type != 'undefined' ) this.set({type:_.clone(type)});
			return this.get('type');
		}

	});

})();

Backbone.actAs.MementoCollection = Backbone.Collection.extend({
	model: Backbone.actAs.Memento
});

Backbone.actAs.Mementoable = (function(){

	var deepClone = function(obj, depth) { // thanks to https://github.com/kmalakoff for this codeю

		if (!obj || (typeof obj !== 'object')) return obj; // by value
		else if (_.isString(obj)) return String.prototype.slice.call(obj);
		else if (_.isDate(obj)) return new Date(obj.valueOf());
		else if (_.isFunction(obj.clone)) return obj.clone();

		var clone;

		if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
		else if (obj.constructor!=={}.constructor) return obj; // by reference
		else clone = _.extend({}, obj);

		if (!_.isUndefined(depth) && (depth > 0)) {
			for (var key in clone) {
				clone[key] = deepClone(clone[key], depth-1);
			}
		}

		return clone;
	};

	return {

		lastMemento: false,

		initMementoable: function(){
			if( typeof this._saveMemento == 'function' ){
				this.on('memento:save', this._saveMemento, this);
			}
			if( typeof this._beforeRestoreMemento == 'function' ){
				this.on('memento:before-restore', this._beforeRestoreMemento, this);
			}
			if( typeof this._afterRestoreMemento == 'function' ){
				this.on('memento:after-restore', this._afterRestoreMemento, this);
			}
			return this;
		},

		getStoredMemento: function(){
			return this.lastMemento;
		},

		storeMemento: function( memento ){
			this.lastMemento = memento || this.saveMemento();
			return this;
		},

		saveMemento: function(){
			var memento = new Backbone.actAs.Memento({memento: deepClone(this.toJSON(), 100) });
			this.trigger('memento:save', memento);
			return memento;
		},

		restoreMemento: function( memento ){
			var memento = memento || this.lastMemento;
			if(!memento) return this; //?
			this.trigger('memento:before-restore', memento);
			if( this instanceof Backbone.Model ) {
				this.set(memento.get('memento'));
			}else{
				this.reset(memento.get('memento'));
			};
			this.trigger('memento:after-restore', memento);
			return this;
		}

	};
})();<|endoftext|>


<commit_before>
import React, { Component }  from "react"
import PropTypes             from 'prop-types'
import ReactDOM              from "react-dom"
import SplitPane             from 'react-split-pane'
import { NotificationStack } from 'react-notification'
import { throttle }          from 'throttle-debounce'
import FileSaver             from 'file-saver'

import $script               from 'scriptjs'

import ToolbarContainer from './ToolbarContainer'
import Editor           from '../Components/Editor'
import Footer           from '../Components/Footer'

import {
    isRequreStdlib,
    getCompilerVersion,
    CompilerDescriptions,
    CompileModes,
    formatCode,
    formatSize
} from '../Common/Common'

import { OrderedSet } from 'immutable'

const AutoCompilationDelay = 800; //ms
const MaxPrintingErrors = 8;

export default class EditorContainer extends Component {
    static defaultProps = {
        compiler: 'AssemblyScript'
    }

    static propTypes = {
        compiler: PropTypes.string
    }

    constructor(props) {
         super(props);
         this.state = {
             version:           '0.0.0',
             compiler:          props.compiler,
             compileMode:       CompileModes[0],
             compilerReady:     false,
             compileFailure:    false,
             compileSuccess:    false,
             additionalStatusMessage: '',
             inputEditorWidth:  '100%',
             outputEditorWidth: '100%',
             editorsHeight:     '750px',
             input:             CompilerDescriptions[props.compiler].example.trim(),
             output: {
                 text:   '',
                 binary: null
             },
             outputType:        'text',

             // settings
             validate:          true,
             optimize:          true,
             //stdlib:            false,
             longMode:          false,
             unsafe:            true,

             annotations:       OrderedSet(),
             notifications:     OrderedSet(),
             notificationCount: 0
         };

         this._errorCounts       = 0;
         this._lastTextInput     = '';
         this._compileTimerDelay = null;
         this._cachedClientRect  = null;
    }

    componentDidMount() {
        this.updateWindowDimensions();
        window.addEventListener("resize", this.updateWindowDimensions);
        this.changeCompiler();
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.updateWindowDimensions);
    }

    updateWindowDimensions = () => {
        this._cachedClientRect = null;
        this.handleSize();
    }

    _clearCompileTimeout() {
        this._compileTimerDelay && clearTimeout(this._compileTimerDelay);
        this._compileTimerDelay = null;
    }

    updateCompilationWithDelay = (delay = 5000) => {
        this._clearCompileTimeout();
        this._compileTimerDelay = setTimeout(() => {
            this.updateCompilation();
            this._compileTimerDelay = null;
        }, delay);
    }

    updateCompilation = () => {
        if (!this.inputEditor) return;

        //console.clear();

        // clean errors and messages
        this.removeAllNotification();
        this.removeAllAnnotation();
        this.setState({
            additionalStatusMessage: ''
        });

        const {
            compiler,
            longMode,
            validate,
            optimize,
            unsafe
        } = this.state;

        const inputCode = this.inputEditor.state.value;

        if (this.toolbar && this.toolbar.compileButton) {
            this._errorCounts = 0;
            this.toolbar.compileButton.startCompile();
            this.setState({
                compileSuccess: false,
                compileFailure: false
            });
        }

        setImmediate(() => {
            try {
                switch (compiler) {
                    case 'AssemblyScript':
                        const stdlib = isRequreStdlib(inputCode);
                        this.compileByAssemblyScript(inputCode, { stdlib, validate, optimize, longMode });
                        break;

                    case 'TurboScript':
                        this.compileByTurboScript(inputCode);
                        break;

                    case 'Speedy.js':
                        this.compileBySpeedyJs(inputCode, {
                            unsafe,
                            optimizationLevel: optimize ? 3 : 0,
                            saveWast: true
                        });
                        break;

                    default: console.warn('Compiler not supported');
                }

            } catch (e) {

                this.setState({
                    compileSuccess: false,
                    compileFailure: true
                });

                this._errorCounts = 1;

                const message = '<' + compiler + '> internal error:\n';
                this.addNotification(message + e.message);
                console.error(message, e);

                this.setState({
                    additionalStatusMessage: message
                });

            } finally {
                if (this.toolbar && this.toolbar.compileButton)
                    this.toolbar.compileButton.endCompile();
            }
        });
    }

    compileByAssemblyScript(code, { stdlib, validate, optimize, longMode }) {

        const as = window.assemblyscript;
        const module = as.Compiler.compileString(
            code, {
                silent: true,
                uintptrSize: longMode ? 8 : 4,
                noLib: !stdlib
            }
        );

        setImmediate(() => {
            if (!module) {
                this.setState({
                    compileSuccess: false,
                    compileFailure: true
                });

                const diagnostics = as.Compiler.lastDiagnostics;
                this._errorCounts = diagnostics.length;

                for (let i = 0; i < diagnostics.length; i++) {
                    let errorMessage = as.typescript.formatDiagnostics([diagnostics[i]]);

                    if (i <= MaxPrintingErrors) {
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        this.addAnnotation(errorMessage);
                    } else {
                        errorMessage = `Too many errors (${diagnostics.length})`;
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        break;
                    }
                }

            } else {
                setImmediate(() => {
                    if (validate) {
                        if (!module.validate()) {
                            let notValid = 'Code validation error';
                            console.error(notValid);
                            this.addNotification(notValid);
                            this._errorCounts = 1;
                            this.setState({
                                compileSuccess: false,
                                compileFailure: true,
                                additionalStatusMessage: notValid
                            });
                            return;
                        }
                    }

                    if (optimize)
                        module.optimize();

                    this._errorCounts = 0;

                    setImmediate(() => {
                        this.setState({
                            compileSuccess: true,
                            compileFailure: false,

                            output: {
                                text:   module.emitText(),
                                binary: module.emitBinary()
                            }
                        });

                        module.dispose();
                    });
                });
            }
        });
    }

    compileByTurboScript(code, options) {
        const turbo = window.turboscript;
        const result = turbo.compileString(code, {
            target:   turbo.CompileTarget.WEBASSEMBLY,
            silent:   true,
            logError: true
        });

        if (!result.success) {
            this.setState({
                compileSuccess: false,
                compileFailure: true
            });
            setImmediate(() => {
                let diagnostic = result.log.first;
                let errorCount = 0;
                let errorMessage;
                while (diagnostic != null) {
                    const location = diagnostic.range.source.indexToLineColumn(diagnostic.range.start);
                    errorMessage = `[${location.line + 1}:${location.column + 1}] `;
                    errorMessage += diagnostic.kind === turbo.DiagnosticKind.ERROR ? "ERROR: " : "WARN: ";
                    errorMessage += diagnostic.message + "\n";

                    if (errorCount <= MaxPrintingErrors) {
                        this.addNotification(errorMessage);
                        let annotations = this.state.annotations;
                        this.setState({
                            annotations: annotations.add({row: location.line, type: "error", text: errorMessage})
                        });
                    }

                    errorCount++;
                    this._errorCounts++;
                    diagnostic = diagnostic.next;
                }

                if (errorCount > MaxPrintingErrors) {
                    errorMessage = `Too many errors (${errorCount})`;
                    console.error(errorMessage);
                    this.addNotification(errorMessage);
                }
            });

        } else {
            setImmediate(() => {
                this._errorCounts = 0;
                this.setState({
                    compileSuccess: true,
                    compileFailure: false,
                    output: {
                        text: result.wast,
                        binary: result.wasm
                    }
                });
            });
        }
    }

    compileBySpeedyJs(code, options) {
        CompilerDescriptions['Speedy.js'].compile(code, options)
        .then(response => {
            this.setState({
                compilerReady:  true
            });

            if (response.length) {
                const output = response[0];
                if (output.exitStatus !== 0) {
                    this.setState({
                        compileSuccess: false,
                        compileFailure: true
                    });

                    // compiled failure
                    const diagnostics = output.diagnostics;
                    this._errorCounts = diagnostics.length;

                    for (let i = 0; i < diagnostics.length; i++) {
                        let errorMessage = diagnostics[i];

                        if (i <= MaxPrintingErrors) {
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            this.addAnnotation(errorMessage);
                        } else {
                            errorMessage = `Too many errors (${diagnostics.length})`;
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            break;
                        }
                    }
                } else {

                    this._errorCounts = 0;

                    // compiled successfully
                    this.setState({
                        compileSuccess: true,
                        compileFailure: false,

                        output: {
                            text:   output.wast || '',
                            binary: new Uint8Array(output.wasm)
                        }
                    });
                }
            }
        })
        .catch(error => {
            this.setState({
                compileSuccess: false,
                compileFailure: true
            });

            this._errorCounts = 1;

            const message = '<' + this.state.compiler + '> Service not response';
            this.addNotification(message);
            console.error(message);
        });
    }

    onInputChange = value => {
        // skip compilation if possible
        value = value.trim();
        if (this._lastTextInput === value)
            return;

        this._lastTextInput = value;
        const mode = this.state.compileMode;

        if (mode === CompileModes[0]) { // Auto
            this.updateCompilationWithDelay(AutoCompilationDelay);
        }
    }

    onDownloadBinary = () => {
        const { output, compiler } = this.state;
        var blob = new Blob([output.binary], { type: "application/octet-stream" });
        FileSaver.saveAs(blob, `${compiler.toLowerCase()}.module.wasm`);
    }

    changeCompiler = compiler => {
        this._errorCounts       = 0;
        this._lastTextInput     = '';
        this._compileTimerDelay = null;

        compiler = compiler || this.state.compiler;

        const description = CompilerDescriptions[compiler];

        this.setState({
            compiler,
            input: description.example,
            compilerReady: false
        });

        if (description.offline) {
            if (!description.loaded) {
                $script.order(description.scripts, () => {
                    this.setState({ compilerReady: true }, () => {
                        description.loaded = true;
                        getCompilerVersion(compiler, version => this.setState({ version }));
                        this.updateCompilation();
                    });
                });
            } else {
                this.setState({ compilerReady: true }, () => {
                    getCompilerVersion(compiler, version => this.setState({ version }));
                    this.updateCompilation();
                });
            }
        } else {
            this.setState({ compilerReady: true }, () => {
                console.log('loaded');

                getCompilerVersion(compiler, version => this.setState({ version }));
                this.updateCompilation();
            });
        }
    }

    onScriptLoad = () => {
        console.log('Compiler', window.assemblyscript);
        this.setState({ compilerReady: true }, () => {
            this.changeCompiler();
        });
    }

    onScriptError = () => {
        console.error('Script not load');
        this.setState({
            compilerReady: false
        });
    }

    onSplitPositionChange = size => {
        this.handleSize(size);
    }

    onCompileButtonClick = mode => {
        this._clearCompileTimeout();

        if (mode === CompileModes[0] || // Auto
            mode === CompileModes[1]) { // Manual

            this.updateCompilation();

        } else if (CompileModes[2]) { // Decompile
            // Decompile not supported yet
        }
    }

    onSettingsOptionChange = (key, value) => {
        if (!this.state.compilerReady) return;
        this.setState({ [key]: value }, this.updateCompilation );
    }

    handleSize = throttle(8, size => {
        if (this.splitEditor) {
            if (!this._cachedClientRect) {
                this._cachedClientRect = ReactDOM.findDOMNode(this.splitEditor).getBoundingClientRect();
            }
            const { width, height } = this._cachedClientRect;
            const gripWidth = 4;

            this.setState({
                inputEditorWidth:  size ? size : '100%',
                outputEditorWidth: size ? width - size - gripWidth : '100%',
                editorsHeight:     height - 160
            });
        }
    })

    addNotification = (message) => {
        // skip notifications for Auto compile mode
        if (this.state.compileMode === CompileModes[0]) { //Auto
            return;
        }

    	const { notifications, notificationCount } = this.state;

        const id = notifications.size + 1;
        const newCount = notificationCount + 1;
        return this.setState({
        	notificationCount: newCount,
        	notifications: notifications.add({
                id,
        		message,
        		key: newCount,
        		action: '✕',
        		dismissAfter: 5000,
                actionStyle: {
                    borderRadius: 0,
                    paddingLeft: '1.5rem',
                    paddingRight: '0.6rem',
                    fontSize: '1.8rem',
                    color: '#fff'
                },
        		onClick: () => this.removeAllNotification()
        	})
        });
    }

    addAnnotation = (message, type = 'error') => {
        const rowRegex = /\(([^)]+)\)/;
        const matches = rowRegex.exec(message);
        if (matches && matches.length === 2) {
            var row = ((matches[1].split(','))[0] >>> 0) - 1;
            let annotations = this.state.annotations;
            this.setState({ annotations:
                annotations.add({ row, type, text: message })
            });
        }
    }

    removeAllAnnotation = () => {
        this.setState({ annotations: OrderedSet() });
    }

    removeNotification = index => {
        const { notifications } = this.state;
        return this.setState({
            notifications: notifications.filter(n => n.key !== index)
        })
    }

    removeAllNotification = () => {
        return this.setState({
            notificationCount: 0,
            notifications: OrderedSet()
        });
    }

    render() {
        const {
            version,
            compiler,

            compilerReady,
            compileSuccess,
            compileFailure,
            notifications,
            annotations,
            additionalStatusMessage,

            inputEditorWidth,
            outputEditorWidth,
            editorsHeight,

            input,
            output,
            outputType

        } = this.state;

        function notificationStyle(index, style, notification) {
            return {
                zOrder: 999,
                color: '#fff',
                background: '#f00',
                fontSize: '1.5rem',
                padding: '1.6rem',
                paddingLeft: '2.1rem',
                borderRadius: 0,
                left: '74px',
                bottom: `${6.6 + (index * 5)}rem`
            };
        }

        const errorNotifications = notifications ? (<NotificationStack
            activeBarStyleFactory={ notificationStyle }
            notifications={ notifications.toArray() }
            onDismiss={ notification => this.setState({
                notifications: this.state.notifications.delete(notification)
            }) }
        />) : null;

        const canBinaryDownload   = compilerReady && compileSuccess && output.binary;
        const compilerDescription = CompilerDescriptions[compiler];

        let busyState = 'busy';

        if (compilerReady) {
            // TODO change this to compileStatus
            if (!compileSuccess && compileFailure) {
                busyState = 'failure';
            } else if (compileSuccess && !compileFailure) {
                busyState = 'success';
            }
        }

        return (
            <div>
                <ToolbarContainer
                    ref={ self => this.toolbar = self }
                    version={ version }
                    compiler={ compiler }
                    compileDisabled={ !compilerReady }
                    onCompilerChange={ this.changeCompiler }
                    onCompileClick={ this.onCompileButtonClick }
                    onCompileModeChange={ mode => {
                        this._clearCompileTimeout();
                        this.setState({ compileMode: mode });
                        if (mode === CompileModes[0]) { // Auto
                            this.updateCompilationWithDelay(AutoCompilationDelay);
                        }
                    }}
                    onSettingsOptionChange={ this.onSettingsOptionChange }
                    onOutputSelect={ type => this.setState({ outputType: type }) }
                />

                <SplitPane
                    ref={ self => this.splitEditor = self }
                    split="vertical"
                    minSize={ 200 }
                    defaultSize="62%"
                    onChange={ this.onSplitPositionChange }
                    style={{
                        margin: '12px'
                    }}
                >
                    <Editor
                        focus
                        id="input"
                        ref={ self => this.inputEditor = self }
                        width={ inputEditorWidth }
                        height={ editorsHeight }
                        code={ input }
                        annotations={ annotations.toArray() }
                        onChange={ this.onInputChange }
                    >
                    </Editor>
                    <Editor
                        readOnly
                        id="output"
                        mode={ outputType === 'text' ? 'wast' : 'typescript' }
                        ref={ self => this.outputEditor = self }
                        width={ outputEditorWidth }
                        height={ editorsHeight }
                        code={ formatCode(output[outputType]) }
                    />
                </SplitPane>

                <Footer
                    errorCount={ this._errorCounts }
                    busyState={ busyState }
                    binarySize={ output.binary ? formatSize(output.binary.length) : '' }
                    onDownloadPressed={ this.onDownloadBinary }
                    downloadDisabled={ !canBinaryDownload }
                    errorMessage={ additionalStatusMessage }
                />

                { errorNotifications }
            </div>
        );
    }
}
<commit_msg>update AssemblyScript options<commit_after>
import React, { Component }  from "react"
import PropTypes             from 'prop-types'
import ReactDOM              from "react-dom"
import SplitPane             from 'react-split-pane'
import { NotificationStack } from 'react-notification'
import { throttle }          from 'throttle-debounce'
import FileSaver             from 'file-saver'

import $script               from 'scriptjs'

import ToolbarContainer from './ToolbarContainer'
import Editor           from '../Components/Editor'
import Footer           from '../Components/Footer'

import {
    isRequreStdlib,
    getCompilerVersion,
    CompilerDescriptions,
    CompileModes,
    formatCode,
    formatSize
} from '../Common/Common'

import { OrderedSet } from 'immutable'

const AutoCompilationDelay = 800; //ms
const MaxPrintingErrors = 8;

export default class EditorContainer extends Component {
    static defaultProps = {
        compiler: 'AssemblyScript'
    }

    static propTypes = {
        compiler: PropTypes.string
    }

    constructor(props) {
         super(props);
         this.state = {
             version:           '0.0.0',
             compiler:          props.compiler,
             compileMode:       CompileModes[0],
             compilerReady:     false,
             compileFailure:    false,
             compileSuccess:    false,
             additionalStatusMessage: '',
             inputEditorWidth:  '100%',
             outputEditorWidth: '100%',
             editorsHeight:     '750px',
             input:             CompilerDescriptions[props.compiler].example.trim(),
             output: {
                 text:   '',
                 binary: null
             },
             outputType:        'text',

             // settings
             validate:          true,
             optimize:          true,
             //stdlib:            false,
             longMode:          false,
             unsafe:            true,

             annotations:       OrderedSet(),
             notifications:     OrderedSet(),
             notificationCount: 0
         };

         this._errorCounts       = 0;
         this._lastTextInput     = '';
         this._compileTimerDelay = null;
         this._cachedClientRect  = null;
    }

    componentDidMount() {
        this.updateWindowDimensions();
        window.addEventListener("resize", this.updateWindowDimensions);
        this.changeCompiler();
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.updateWindowDimensions);
    }

    updateWindowDimensions = () => {
        this._cachedClientRect = null;
        this.handleSize();
    }

    _clearCompileTimeout() {
        this._compileTimerDelay && clearTimeout(this._compileTimerDelay);
        this._compileTimerDelay = null;
    }

    updateCompilationWithDelay = (delay = 5000) => {
        this._clearCompileTimeout();
        this._compileTimerDelay = setTimeout(() => {
            this.updateCompilation();
            this._compileTimerDelay = null;
        }, delay);
    }

    updateCompilation = () => {
        if (!this.inputEditor) return;

        //console.clear();

        // clean errors and messages
        this.removeAllNotification();
        this.removeAllAnnotation();
        this.setState({
            additionalStatusMessage: ''
        });

        const {
            compiler,
            longMode,
            validate,
            optimize,
            unsafe
        } = this.state;

        const inputCode = this.inputEditor.state.value;

        if (this.toolbar && this.toolbar.compileButton) {
            this._errorCounts = 0;
            this.toolbar.compileButton.startCompile();
            this.setState({
                compileSuccess: false,
                compileFailure: false
            });
        }

        setImmediate(() => {
            try {
                switch (compiler) {
                    case 'AssemblyScript':
                        const stdlib = isRequreStdlib(inputCode);
                        this.compileByAssemblyScript(inputCode, {
                             stdlib,
                             validate,
                             optimize,
                             longMode
                         });
                        break;

                    case 'TurboScript':
                        this.compileByTurboScript(inputCode);
                        break;

                    case 'Speedy.js':
                        this.compileBySpeedyJs(inputCode, {
                            unsafe,
                            optimizationLevel: optimize ? 3 : 0,
                            saveWast: true
                        });
                        break;

                    default: console.warn('Compiler not supported');
                }

            } catch (e) {

                this.setState({
                    compileSuccess: false,
                    compileFailure: true
                });

                this._errorCounts = 1;

                const message = '<' + compiler + '> internal error:\n';
                this.addNotification(message + e.message);
                console.error(message, e);

                this.setState({
                    additionalStatusMessage: message
                });

            } finally {
                if (this.toolbar && this.toolbar.compileButton)
                    this.toolbar.compileButton.endCompile();
            }
        });
    }

    compileByAssemblyScript(code, { stdlib, validate, optimize, longMode }) {

        const as = window.assemblyscript;
        const module = as.Compiler.compileString(
            code, {
                silent: true,
                uintptrSize: longMode ? 8 : 4,
                noLib: !stdlib,
                malloc: stdlib,
                exportMalloc: false
            }
        );

        setImmediate(() => {
            if (!module) {
                this.setState({
                    compileSuccess: false,
                    compileFailure: true
                });

                const diagnostics = as.Compiler.lastDiagnostics;
                this._errorCounts = diagnostics.length;

                for (let i = 0; i < diagnostics.length; i++) {
                    let errorMessage = as.typescript.formatDiagnostics([diagnostics[i]]);

                    if (i <= MaxPrintingErrors) {
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        this.addAnnotation(errorMessage);
                    } else {
                        errorMessage = `Too many errors (${diagnostics.length})`;
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        break;
                    }
                }

            } else {
                setImmediate(() => {
                    if (validate) {
                        if (!module.validate()) {
                            let notValid = 'Code validation error';
                            console.error(notValid);
                            this.addNotification(notValid);
                            this._errorCounts = 1;
                            this.setState({
                                compileSuccess: false,
                                compileFailure: true,
                                additionalStatusMessage: notValid
                            });
                            return;
                        }
                    }

                    if (optimize)
                        module.optimize();

                    this._errorCounts = 0;

                    setImmediate(() => {
                        this.setState({
                            compileSuccess: true,
                            compileFailure: false,

                            output: {
                                text:   module.emitText(),
                                binary: module.emitBinary()
                            }
                        });

                        module.dispose();
                    });
                });
            }
        });
    }

    compileByTurboScript(code, options) {
        const turbo = window.turboscript;
        const result = turbo.compileString(code, {
            target:   turbo.CompileTarget.WEBASSEMBLY,
            silent:   true,
            logError: true
        });

        if (!result.success) {
            this.setState({
                compileSuccess: false,
                compileFailure: true
            });
            setImmediate(() => {
                let diagnostic = result.log.first;
                let errorCount = 0;
                let errorMessage;
                while (diagnostic != null) {
                    const location = diagnostic.range.source.indexToLineColumn(diagnostic.range.start);
                    errorMessage = `[${location.line + 1}:${location.column + 1}] `;
                    errorMessage += diagnostic.kind === turbo.DiagnosticKind.ERROR ? "ERROR: " : "WARN: ";
                    errorMessage += diagnostic.message + "\n";

                    if (errorCount <= MaxPrintingErrors) {
                        this.addNotification(errorMessage);
                        let annotations = this.state.annotations;
                        this.setState({
                            annotations: annotations.add({row: location.line, type: "error", text: errorMessage})
                        });
                    }

                    errorCount++;
                    this._errorCounts++;
                    diagnostic = diagnostic.next;
                }

                if (errorCount > MaxPrintingErrors) {
                    errorMessage = `Too many errors (${errorCount})`;
                    console.error(errorMessage);
                    this.addNotification(errorMessage);
                }
            });

        } else {
            setImmediate(() => {
                this._errorCounts = 0;
                this.setState({
                    compileSuccess: true,
                    compileFailure: false,
                    output: {
                        text: result.wast,
                        binary: result.wasm
                    }
                });
            });
        }
    }

    compileBySpeedyJs(code, options) {
        CompilerDescriptions['Speedy.js'].compile(code, options)
        .then(response => {
            this.setState({
                compilerReady:  true
            });

            if (response.length) {
                const output = response[0];
                if (output.exitStatus !== 0) {
                    this.setState({
                        compileSuccess: false,
                        compileFailure: true
                    });

                    // compiled failure
                    const diagnostics = output.diagnostics;
                    this._errorCounts = diagnostics.length;

                    for (let i = 0; i < diagnostics.length; i++) {
                        let errorMessage = diagnostics[i];

                        if (i <= MaxPrintingErrors) {
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            this.addAnnotation(errorMessage);
                        } else {
                            errorMessage = `Too many errors (${diagnostics.length})`;
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            break;
                        }
                    }
                } else {

                    this._errorCounts = 0;

                    // compiled successfully
                    this.setState({
                        compileSuccess: true,
                        compileFailure: false,

                        output: {
                            text:   output.wast || '',
                            binary: new Uint8Array(output.wasm)
                        }
                    });
                }
            }
        })
        .catch(error => {
            this.setState({
                compileSuccess: false,
                compileFailure: true
            });

            this._errorCounts = 1;

            const message = '<' + this.state.compiler + '> Service not response';
            this.addNotification(message);
            console.error(message);
        });
    }

    onInputChange = value => {
        // skip compilation if possible
        value = value.trim();
        if (this._lastTextInput === value)
            return;

        this._lastTextInput = value;
        const mode = this.state.compileMode;

        if (mode === CompileModes[0]) { // Auto
            this.updateCompilationWithDelay(AutoCompilationDelay);
        }
    }

    onDownloadBinary = () => {
        const { output, compiler } = this.state;
        var blob = new Blob([output.binary], { type: "application/octet-stream" });
        FileSaver.saveAs(blob, `${compiler.toLowerCase()}.module.wasm`);
    }

    changeCompiler = compiler => {
        this._errorCounts       = 0;
        this._lastTextInput     = '';
        this._compileTimerDelay = null;

        compiler = compiler || this.state.compiler;

        const description = CompilerDescriptions[compiler];

        this.setState({
            compiler,
            input: description.example,
            compilerReady: false
        });

        if (description.offline) {
            if (!description.loaded) {
                $script.order(description.scripts, () => {
                    this.setState({ compilerReady: true }, () => {
                        description.loaded = true;
                        getCompilerVersion(compiler, version => this.setState({ version }));
                        this.updateCompilation();
                    });
                });
            } else {
                this.setState({ compilerReady: true }, () => {
                    getCompilerVersion(compiler, version => this.setState({ version }));
                    this.updateCompilation();
                });
            }
        } else {
            this.setState({ compilerReady: true }, () => {
                console.log('loaded');

                getCompilerVersion(compiler, version => this.setState({ version }));
                this.updateCompilation();
            });
        }
    }

    onScriptLoad = () => {
        console.log('Compiler', window.assemblyscript);
        this.setState({ compilerReady: true }, () => {
            this.changeCompiler();
        });
    }

    onScriptError = () => {
        console.error('Script not load');
        this.setState({
            compilerReady: false
        });
    }

    onSplitPositionChange = size => {
        this.handleSize(size);
    }

    onCompileButtonClick = mode => {
        this._clearCompileTimeout();

        if (mode === CompileModes[0] || // Auto
            mode === CompileModes[1]) { // Manual

            this.updateCompilation();

        } else if (CompileModes[2]) { // Decompile
            // Decompile not supported yet
        }
    }

    onSettingsOptionChange = (key, value) => {
        if (!this.state.compilerReady) return;
        this.setState({ [key]: value }, this.updateCompilation );
    }

    handleSize = throttle(8, size => {
        if (this.splitEditor) {
            if (!this._cachedClientRect) {
                this._cachedClientRect = ReactDOM.findDOMNode(this.splitEditor).getBoundingClientRect();
            }
            const { width, height } = this._cachedClientRect;
            const gripWidth = 4;

            this.setState({
                inputEditorWidth:  size ? size : '100%',
                outputEditorWidth: size ? width - size - gripWidth : '100%',
                editorsHeight:     height - 160
            });
        }
    })

    addNotification = (message) => {
        // skip notifications for Auto compile mode
        if (this.state.compileMode === CompileModes[0]) { //Auto
            return;
        }

    	const { notifications, notificationCount } = this.state;

        const id = notifications.size + 1;
        const newCount = notificationCount + 1;
        return this.setState({
        	notificationCount: newCount,
        	notifications: notifications.add({
                id,
        		message,
        		key: newCount,
        		action: '✕',
        		dismissAfter: 5000,
                actionStyle: {
                    borderRadius: 0,
                    paddingLeft: '1.5rem',
                    paddingRight: '0.6rem',
                    fontSize: '1.8rem',
                    color: '#fff'
                },
        		onClick: () => this.removeAllNotification()
        	})
        });
    }

    addAnnotation = (message, type = 'error') => {
        const rowRegex = /\(([^)]+)\)/;
        const matches = rowRegex.exec(message);
        if (matches && matches.length === 2) {
            var row = ((matches[1].split(','))[0] >>> 0) - 1;
            let annotations = this.state.annotations;
            this.setState({ annotations:
                annotations.add({ row, type, text: message })
            });
        }
    }

    removeAllAnnotation = () => {
        this.setState({ annotations: OrderedSet() });
    }

    removeNotification = index => {
        const { notifications } = this.state;
        return this.setState({
            notifications: notifications.filter(n => n.key !== index)
        })
    }

    removeAllNotification = () => {
        return this.setState({
            notificationCount: 0,
            notifications: OrderedSet()
        });
    }

    render() {
        const {
            version,
            compiler,

            compilerReady,
            compileSuccess,
            compileFailure,
            notifications,
            annotations,
            additionalStatusMessage,

            inputEditorWidth,
            outputEditorWidth,
            editorsHeight,

            input,
            output,
            outputType

        } = this.state;

        function notificationStyle(index, style, notification) {
            return {
                zOrder: 999,
                color: '#fff',
                background: '#f00',
                fontSize: '1.5rem',
                padding: '1.6rem',
                paddingLeft: '2.1rem',
                borderRadius: 0,
                left: '74px',
                bottom: `${6.6 + (index * 5)}rem`
            };
        }

        const errorNotifications = notifications ? (<NotificationStack
            activeBarStyleFactory={ notificationStyle }
            notifications={ notifications.toArray() }
            onDismiss={ notification => this.setState({
                notifications: this.state.notifications.delete(notification)
            }) }
        />) : null;

        const canBinaryDownload   = compilerReady && compileSuccess && output.binary;
        const compilerDescription = CompilerDescriptions[compiler];

        let busyState = 'busy';

        if (compilerReady) {
            // TODO change this to compileStatus
            if (!compileSuccess && compileFailure) {
                busyState = 'failure';
            } else if (compileSuccess && !compileFailure) {
                busyState = 'success';
            }
        }

        return (
            <div>
                <ToolbarContainer
                    ref={ self => this.toolbar = self }
                    version={ version }
                    compiler={ compiler }
                    compileDisabled={ !compilerReady }
                    onCompilerChange={ this.changeCompiler }
                    onCompileClick={ this.onCompileButtonClick }
                    onCompileModeChange={ mode => {
                        this._clearCompileTimeout();
                        this.setState({ compileMode: mode });
                        if (mode === CompileModes[0]) { // Auto
                            this.updateCompilationWithDelay(AutoCompilationDelay);
                        }
                    }}
                    onSettingsOptionChange={ this.onSettingsOptionChange }
                    onOutputSelect={ type => this.setState({ outputType: type }) }
                />

                <SplitPane
                    ref={ self => this.splitEditor = self }
                    split="vertical"
                    minSize={ 200 }
                    defaultSize="62%"
                    onChange={ this.onSplitPositionChange }
                    style={{
                        margin: '12px'
                    }}
                >
                    <Editor
                        focus
                        id="input"
                        ref={ self => this.inputEditor = self }
                        width={ inputEditorWidth }
                        height={ editorsHeight }
                        code={ input }
                        annotations={ annotations.toArray() }
                        onChange={ this.onInputChange }
                    >
                    </Editor>
                    <Editor
                        readOnly
                        id="output"
                        mode={ outputType === 'text' ? 'wast' : 'typescript' }
                        ref={ self => this.outputEditor = self }
                        width={ outputEditorWidth }
                        height={ editorsHeight }
                        code={ formatCode(output[outputType]) }
                    />
                </SplitPane>

                <Footer
                    errorCount={ this._errorCounts }
                    busyState={ busyState }
                    binarySize={ output.binary ? formatSize(output.binary.length) : '' }
                    onDownloadPressed={ this.onDownloadBinary }
                    downloadDisabled={ !canBinaryDownload }
                    errorMessage={ additionalStatusMessage }
                />

                { errorNotifications }
            </div>
        );
    }
}
<|endoftext|>


<commit_before>
import React, { Component }  from 'react';
import ReactDOM              from 'react-dom';
import PropTypes             from 'prop-types';

import FileSaver             from 'file-saver';
import { NotificationStack } from 'react-notification';
import { OrderedSet }        from 'immutable';
import SplitPane             from 'react-split-pane';
import { throttle }          from 'throttle-debounce';

import Editor                from '../Components/Editor';
import ToolbarContainer      from './ToolbarContainer';
import FooterContainer       from './FooterContainer';

import {
    CompileMode,
    CompilerDescriptions,
    formatCode,
    formatSize,
    getCompilerVersion,
    isRequreStdlib,
} from '../Common/Common';

const AutoCompilationDelay = 800; //ms
const MaxPrintingErrors = 8;
const SplitGripWidth = 5;

export default class EditorContainer extends Component {
    static defaultProps = {
        compiler: 'AssemblyScript'
    }

    static propTypes = {
        compiler: PropTypes.string
    }

    constructor(props) {
         super(props);
         this.state = {
             version:           '0.0.0',
             compiler:          props.compiler,
             compileMode:       CompileMode.Auto,
             compilerReady:     false,
             // compileFailure:    false,
             compileState:      'pending',
             // compileSuccess:    false,
             splitPosition:     0.62,
             additionalStatusMessage: '',
             inputEditorWidth:  '100%',
             outputEditorWidth: '100%',
             editorsHeight:     '750px',
             input:             CompilerDescriptions[props.compiler].example.trim(),
             output: {
                 text:   '',
                 binary: null
             },
             outputType:        'text',

             // settings
             validate:          true,
             optimize:          true,
             longMode:          false,
             unsafe:            true,
             base64:            false,

             annotations:       OrderedSet(),
             notifications:     OrderedSet(),
             notificationCount: 0,

             inputCursorPosition: [1, 1]
         };

         this._errorCount       = 0;
         this._lastTextInput     = '';
         this._compileTimerDelay = null;
         this._cachedClientRect  = null;
    }

    componentDidMount() {
        this.updateWindowDimensions();
        window.addEventListener("resize", this.updateWindowDimensions);
        this.changeCompiler();
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.updateWindowDimensions);
    }

    updateWindowDimensions = () => {
        this._cachedClientRect = null;
        this.handleSize();
    }

    _clearCompileTimeout() {
        this._compileTimerDelay && clearTimeout(this._compileTimerDelay);
        this._compileTimerDelay = null;
    }

    updateCompilationWithDelay = (delay = 5000) => {
        this._clearCompileTimeout();
        this._compileTimerDelay = setTimeout(() => {
            this.updateCompilation();
            this._compileTimerDelay = null;
        }, delay);
    }

    updateCompilation = () => {
        if (!this.inputEditor) return;

        //console.clear();

        // clean errors and messages
        this._errorCount = 0;
        this.removeAllNotifications();
        this.removeAllAnnotation();
        this.setState({
            additionalStatusMessage: ''
        });

        const {
            compiler,
            longMode,
            validate,
            optimize,
            unsafe
        } = this.state;

        const inputCode = this.inputEditor.state.value;

        if (this.toolbar && this.toolbar.compileButton) {
            this.toolbar.compileButton.startCompile();
            this.setState({ compileStatus: 'compiling' });
        }

        setImmediate(() => {
            try {
                switch (compiler) {
                    case 'AssemblyScript':
                        const stdlib = isRequreStdlib(inputCode);
                        this.compileByAssemblyScript(inputCode, {
                             noMemory: !stdlib,
                             longMode,
                             validate,
                             optimize
                         });
                        break;

                    case 'Speedy.js':
                        this.compileBySpeedyJs(inputCode, {
                            unsafe,
                            optimizationLevel: optimize ? 3 : 0,
                            saveWast: true
                        });
                        break;

                    default: console.warn('Compiler not supported');
                }

            } catch (e) {

                this.setState({ compileStatus: 'failure' });
                this._errorCount = 1;

                const message = '<' + compiler + '> internal error: ';
                this.addNotification(message + e.message);
                console.error(message, e);

                this.setState({
                    additionalStatusMessage: message + e.message
                });

            } finally {
                if (this.toolbar && this.toolbar.compileButton)
                    this.toolbar.compileButton.endCompile();
            }
        });
    }

    compileByAssemblyScript(code, { noMemory, longMode, validate, optimize }) {
        const as = window.assemblyscript;

        const options = as.createOptions();

        as.setTarget(options,        +longMode);
        as.setNoTreeShaking(options, false);
        as.setNoAssert(options,      false);
        as.setNoMemory(options,      noMemory);

        const parser = as.parseFile(code, 'index.ts', null, true);
        const module = as.compile(parser, options);

        const checkDiagnostics = (parser) => {
          let diagnostic;
          let hasErrors = false;

          while ((diagnostic = as.nextDiagnostic(parser)) != null) {
            console.error(as.formatDiagnostic(diagnostic, false, true));
            if (as.isError(diagnostic)) {
              hasErrors = true;
            }
          }

          return hasErrors;
        }

        setImmediate(() => {
            if (checkDiagnostics(parser)) {
              this.setState({ compileStatus: 'failure' });
              return;
            }

            if (!module) {
                this.setState({ compileStatus: 'failure' });

                // const diagnostics = as.Compiler.lastDiagnostics;
                // this._errorCount = diagnostics.length;

                /* for (let i = 0; i < diagnostics.length; i++) {
                    let errorMessage = as.typescript.formatDiagnostics([diagnostics[i]]);

                    if (i <= MaxPrintingErrors) {
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        this.addAnnotation(errorMessage);
                    } else {
                        errorMessage = `Too many errors (${diagnostics.length})`;
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        break;
                    }
                }*/

                return;
            }

            setImmediate(() => {
                if (validate) {
                    if (!module.validate()) {
                        let notValid = 'Code validation error';
                        console.error(notValid);
                        this.addNotification(notValid);
                        this._errorCount = 1;
                        this.setState({
                            compileStatus:           'failure',
                            additionalStatusMessage: notValid
                        });
                        module.dispose();
                        return;
                    }
                }

                if (optimize)
                    module.optimize();

                this._errorCount = 0;

                setImmediate(() => {
                    const text = module.toText();
                    const {
                      output: binary,
                    } = module.toBinary();

                    this.setState({
                        compileStatus: 'success',
                        output: { text, binary },
                    });

                    module.dispose();
                });
            });
        });
    }

    compileBySpeedyJs(code, options) {
        CompilerDescriptions['Speedy.js'].compile(code, options)
        .then(response => {
            if (this.state.compiler !== 'Speedy.js') return;

            if (response.length) {
                const output = response[0];
                if (output.exitStatus !== 0) {
                    this.setState({ compileStatus: 'failure' });

                    // compiled failure
                    const diagnostics = output.diagnostics;
                    this._errorCount = diagnostics.length;

                    for (let i = 0; i < diagnostics.length; i++) {
                        let errorMessage = diagnostics[i];

                        if (i <= MaxPrintingErrors) {
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            this.addAnnotation(errorMessage);
                        } else {
                            errorMessage = `Too many errors (${diagnostics.length})`;
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            break;
                        }
                    }
                } else {

                    this._errorCount = 0;

                    // compiled successfully
                    this.setState({
                        compileStatus: 'success',

                        output: {
                            text:   output.wast || '',
                            binary: new Uint8Array(output.wasm)
                        }
                    });
                }
            }
        })
        .catch(error => {
            this.setState({ compileStatus: 'failure' });
            this._errorCount = 1;

            const message = '<' + this.state.compiler + '> Service not response';
            this.addNotification(message);
            console.error(message);
        });
    }

    onInputChange = value => {
        // skip compilation if possible
        value = value.trim();
        if (this._lastTextInput !== value) {
            if (this.state.compileMode === CompileMode.Auto)
                this.updateCompilationWithDelay(AutoCompilationDelay);
            this._lastTextInput = value;
        }
    }

    onInputPositionChange = inputCursorPosition => {
        this.setState({ inputCursorPosition });
    }

    onDownloadBinary = () => {
        const { output, compiler } = this.state;
        var blob = new Blob([output.binary], { type: "application/octet-stream" });
        FileSaver.saveAs(blob, `${compiler.toLowerCase()}.module.wasm`);
    }

    changeCompiler = compiler => {
        this._errorCount        = 0;
        this._lastTextInput     = '';
        this._compileTimerDelay = null;

        compiler = compiler || this.state.compiler;

        const description = CompilerDescriptions[compiler];

        this.setState({
            compiler,
            input: description.example.trim(),
        }, () => {
            if (description.offline) {
                if (!description.loaded && description.scripts && description.scripts.length) {
                    if (description.scripts.length > 1) {
                        window.$script.order(description.scripts.slice(), () => {
                            description.loaded = true;
                            this.onScriptLoad();
                        });
                    } else {
                        window.$script(description.scripts[0], () => {
                            description.loaded = true;
                            this.onScriptLoad();
                        });
                    }

                } else {
                    this.onScriptLoad();
                }
            } else {
                this.onScriptLoad();
            }
        });
    }

    onScriptLoad() {
        this.setState({ compilerReady: true }, () => {
            getCompilerVersion(this.state.compiler, version => this.setState({ version }));
            this.updateCompilation();
        });
    }

    onScriptError = () => {
        console.error('Script not load');
        this.setState({
            compilerReady: false,
        });
    }

    onSplitPositionChange = size => {
        this.handleSize(size);
    }

    onCompileButtonClick = mode => {
        this._clearCompileTimeout();

        if (mode === CompileMode.Auto || mode === CompileMode.Manual)
            this.updateCompilation();
    }

    onSettingsOptionChange = (key, value) => {
        if (!this.state.compilerReady) return;
        this.setState({ [key]: value }, key !== 'base64' ? this.updateCompilation : null);
    }

    handleSize = throttle(8, size => {
        if (this.splitEditor) {
            if (!this._cachedClientRect)
                this._cachedClientRect = ReactDOM.findDOMNode(this.splitEditor).getBoundingClientRect();

            const { width, height } = this._cachedClientRect;
            const pos = (size ? size / width : this.state.splitPosition);
            const primaryWidth = width * pos;

            this.setState({
                inputEditorWidth:  Math.ceil(primaryWidth),
                outputEditorWidth: Math.ceil(width - primaryWidth - SplitGripWidth),
                editorsHeight:     height - 160,
                splitPosition:     pos,
            });

            this.splitEditor.setSize(
                { primary: 'first', size: primaryWidth },
                { draggedSize: primaryWidth }
            );
        }
    })

    addNotification = (message) => {
      // skip notifications for Auto compile mode
      // if (this.state.compileMode === CompileMode.Auto) {
      //    return;
      // }

    	const {
        notifications,
        notificationCount,
      } = this.state;

      const id = notifications.size + 1;
      const newCount = notificationCount + 1;
      return this.setState({
      	notificationCount: newCount,
      	notifications: notifications.add({
          id,
      		message,
      		key:          newCount,
      		action:       '✕',
      		dismissAfter: 5000,
          actionStyle:  {
            borderRadius: 0,
            paddingLeft:  '1.5rem',
            paddingRight: '0.6rem',
            fontSize:     '1.8rem',
            color:        '#fff',
          },
      		onClick: this.removeAllNotifications,
      	})
      });
    }

    addAnnotation = (message, type = 'error') => {
        const rowRegex = /\(([^)]+)\)/;
        const matches = rowRegex.exec(message);
        if (matches && matches.length === 2) {
            var row = ((matches[1].split(','))[0] >>> 0);
            this.setState(({ annotations }) => ({
                annotations: annotations.add({ row, type, text: message }),
            }));
        }
    }

    removeAllAnnotation = () => {
        this.setState({ annotations: OrderedSet() });
    }

    removeNotification = index => {
        const { notifications } = this.state;
        return this.setState({
            notifications: notifications.filter(n => n.key !== index),
        })
    }

    removeAllNotifications = () => {
        return this.setState({
            notificationCount: 0,
            notifications:     OrderedSet(),
        });
    }

    render() {
        const {
            version,
            compiler,

            compilerReady,
            compileStatus,
            notifications,
            annotations,
            additionalStatusMessage,

            splitPosition,
            inputEditorWidth,
            outputEditorWidth,
            editorsHeight,

            input,
            output,
            outputType,
            base64,

            inputCursorPosition,

        } = this.state;

        function notificationStyle(index, style, notification) {
            return {
                zOrder: 999,
                color: '#fff',
                background: '#f00',
                fontSize: '1.5rem',
                padding: '1.2em',
                paddingLeft: '1.8em',
                borderRadius: 0,
                left: '5.3em',
                bottom: `${4.2 + (index * 3.6)}em`
            };
        }

        const errorNotifications = notifications ? (<NotificationStack
            activeBarStyleFactory={ notificationStyle }
            notifications={ notifications.toArray() }
            onDismiss={ notification => this.setState({
                notifications: this.state.notifications.delete(notification)
            }) }
        />) : null;

        const canBinaryDownload = (
          compilerReady &&
          compileStatus === 'success' &&
          output.binary
        );

        let busyState = 'pending';
        if (compilerReady) {
            switch (compileStatus) {
                case 'success': busyState = 'success'; break;
                case 'failure': busyState = 'failure'; break;
                default: break;
            }
        }

        return (
            <div>
                <ToolbarContainer
                    ref={ self => this.toolbar = self }
                    version={ version }
                    compiler={ compiler }
                    compileDisabled={ !compilerReady }
                    onCompilerChange={ this.changeCompiler }
                    onCompileClick={ this.onCompileButtonClick }
                    onCompileModeChange={ mode => {
                        this._clearCompileTimeout();
                        this.setState({ compileMode: mode });
                        if (mode === CompileMode.Auto)
                            this.updateCompilationWithDelay(AutoCompilationDelay);
                    }}
                    onSettingsOptionChange={ this.onSettingsOptionChange }
                    onOutputSelect={ type => this.setState({ outputType: type }) }
                />

                <SplitPane
                    ref={ self => this.splitEditor = self }
                    split="vertical"
                    minSize={ 200 }
                    defaultSize={ splitPosition * 100 + '%' }
                    onChange={ this.onSplitPositionChange }
                    style={{
                        margin: '12px',
                    }}
                >
                    <Editor
                        focus
                        id="input"
                        ref={ self => this.inputEditor = self }
                        width={ inputEditorWidth }
                        height={ editorsHeight }
                        code={ input }
                        annotations={ annotations.toArray() }
                        onChange={ this.onInputChange }
                        onPositionChange={ this.onInputPositionChange }
                    />
                    <Editor
                        readOnly
                        id="output"
                        mode={ outputType === 'text' ? 'wast' : 'typescript' }
                        ref={ self => this.outputEditor = self }
                        width={ outputEditorWidth }
                        height={ editorsHeight }
                        code={ formatCode(output[outputType], base64) }
                    />
                </SplitPane>

                <FooterContainer
                    errorCount={ this._errorCount }
                    busyState={ busyState }
                    binarySize={ output.binary ? formatSize(output.binary.length) : '' }
                    onDownloadPressed={ this.onDownloadBinary }
                    downloadDisabled={ !canBinaryDownload }
                    errorMessage={ additionalStatusMessage }
                    cursorPosition={ inputCursorPosition }
                />

                { errorNotifications }
            </div>
        );
    }
}
<commit_msg>Continue diagnostic implementation<commit_after>
import React, { Component }  from 'react';
import ReactDOM              from 'react-dom';
import PropTypes             from 'prop-types';

import FileSaver             from 'file-saver';
import { NotificationStack } from 'react-notification';
import { OrderedSet }        from 'immutable';
import SplitPane             from 'react-split-pane';
import { throttle }          from 'throttle-debounce';

import Editor                from '../Components/Editor';
import ToolbarContainer      from './ToolbarContainer';
import FooterContainer       from './FooterContainer';

import {
    CompileMode,
    CompilerDescriptions,
    formatCode,
    formatSize,
    getCompilerVersion,
    isRequreStdlib,
} from '../Common/Common';

const AutoCompilationDelay = 800; //ms
const MaxPrintingErrors = 8;
const SplitGripWidth = 5;

export default class EditorContainer extends Component {
    static defaultProps = {
        compiler: 'AssemblyScript'
    }

    static propTypes = {
        compiler: PropTypes.string
    }

    constructor(props) {
         super(props);
         this.state = {
             version:           '0.0.0',
             compiler:          props.compiler,
             compileMode:       CompileMode.Auto,
             compilerReady:     false,
             // compileFailure:    false,
             compileState:      'pending',
             // compileSuccess:    false,
             splitPosition:     0.62,
             additionalStatusMessage: '',
             inputEditorWidth:  '100%',
             outputEditorWidth: '100%',
             editorsHeight:     '750px',
             input:             CompilerDescriptions[props.compiler].example.trim(),
             output: {
                 text:   '',
                 binary: null
             },
             outputType:        'text',

             // settings
             validate:          true,
             optimize:          true,
             longMode:          false,
             unsafe:            true,
             base64:            false,

             annotations:       OrderedSet(),
             notifications:     OrderedSet(),
             notificationCount: 0,

             inputCursorPosition: [1, 1]
         };

         this._errorCount       = 0;
         this._lastTextInput     = '';
         this._compileTimerDelay = null;
         this._cachedClientRect  = null;
    }

    componentDidMount() {
        this.updateWindowDimensions();
        window.addEventListener("resize", this.updateWindowDimensions);
        this.changeCompiler();
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.updateWindowDimensions);
    }

    updateWindowDimensions = () => {
        this._cachedClientRect = null;
        this.handleSize();
    }

    _clearCompileTimeout() {
        this._compileTimerDelay && clearTimeout(this._compileTimerDelay);
        this._compileTimerDelay = null;
    }

    updateCompilationWithDelay = (delay = 5000) => {
        this._clearCompileTimeout();
        this._compileTimerDelay = setTimeout(() => {
            this.updateCompilation();
            this._compileTimerDelay = null;
        }, delay);
    }

    updateCompilation = () => {
        if (!this.inputEditor) return;

        //console.clear();

        // clean errors and messages
        this._errorCount = 0;
        this.removeAllNotifications();
        this.removeAllAnnotation();
        this.setState({
            additionalStatusMessage: ''
        });

        const {
            compiler,
            longMode,
            validate,
            optimize,
            unsafe
        } = this.state;

        const inputCode = this.inputEditor.state.value;

        if (this.toolbar && this.toolbar.compileButton) {
            this.toolbar.compileButton.startCompile();
            this.setState({ compileStatus: 'compiling' });
        }

        setImmediate(() => {
            try {
                switch (compiler) {
                    case 'AssemblyScript':
                        const stdlib = isRequreStdlib(inputCode);
                        this.compileByAssemblyScript(inputCode, {
                             noMemory: !stdlib,
                             longMode,
                             validate,
                             optimize
                         });
                        break;

                    case 'Speedy.js':
                        this.compileBySpeedyJs(inputCode, {
                            unsafe,
                            optimizationLevel: optimize ? 3 : 0,
                            saveWast: true
                        });
                        break;

                    default: console.warn('Compiler not supported');
                }

            } catch (e) {

                this.setState({ compileStatus: 'failure' });
                this._errorCount = 1;

                const message = '<' + compiler + '> internal error: ';
                this.addNotification(message + e.message);
                console.error(message, e);

                this.setState({
                    additionalStatusMessage: message + e.message
                });

            } finally {
                if (this.toolbar && this.toolbar.compileButton)
                    this.toolbar.compileButton.endCompile();
            }
        });
    }

    compileByAssemblyScript(code, { noMemory, longMode, validate, optimize }) {
        const as = window.assemblyscript;

        const options = as.createOptions();

        as.setTarget(options,        +longMode);
        as.setNoTreeShaking(options, false);
        as.setNoAssert(options,      false);
        as.setNoMemory(options,      noMemory);

        const parser = as.parseFile(code, 'index.ts', null, true);
        const module = as.compile(parser, options);

        const checkDiagnostics = (parser) => {
          let diagnostic, index = 0;
          let hasErrors = false;

          while ((diagnostic = as.nextDiagnostic(parser)) != null) {
            let errorMessage = as.formatDiagnostic(diagnostic, false, true);

            if (index <= MaxPrintingErrors) {
              console.error(errorMessage);
              this.addNotification(errorMessage);
              this.addAnnotation(errorMessage);
            } else {
              errorMessage = 'Too many errors';
              console.error(errorMessage);
              this.addNotification(errorMessage);
              hasErrors = true;
              break;
            }

            if (as.isError(diagnostic)) {
              hasErrors = true;
            }

            index++;
            this._errorCount = index;
          }

          return hasErrors;
        }

        setImmediate(() => {
            if (checkDiagnostics(parser)) {
              this.setState({ compileStatus: 'failure' });
              return;
            }

            if (!module) {
                this.setState({ compileStatus: 'failure' });

                // const diagnostics = as.Compiler.lastDiagnostics;
                // this._errorCount = diagnostics.length;

                /* for (let i = 0; i < diagnostics.length; i++) {
                    let errorMessage = as.typescript.formatDiagnostics([diagnostics[i]]);

                    if (i <= MaxPrintingErrors) {
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        this.addAnnotation(errorMessage);
                    } else {
                        errorMessage = `Too many errors (${diagnostics.length})`;
                        console.error(errorMessage);
                        this.addNotification(errorMessage);
                        break;
                    }
                }*/

                return;
            }

            setImmediate(() => {
                if (validate) {
                    if (!module.validate()) {
                        let notValid = 'Code validation error';
                        console.error(notValid);
                        this.addNotification(notValid);
                        this._errorCount = 1;
                        this.setState({
                            compileStatus:           'failure',
                            additionalStatusMessage: notValid
                        });
                        module.dispose();
                        return;
                    }
                }

                if (optimize)
                    module.optimize();

                this._errorCount = 0;

                setImmediate(() => {
                    const text = module.toText();
                    const {
                      output: binary,
                    } = module.toBinary();

                    this.setState({
                        compileStatus: 'success',
                        output: { text, binary },
                    });

                    module.dispose();
                });
            });
        });
    }

    compileBySpeedyJs(code, options) {
        CompilerDescriptions['Speedy.js'].compile(code, options)
        .then(response => {
            if (this.state.compiler !== 'Speedy.js') return;

            if (response.length) {
                const output = response[0];
                if (output.exitStatus !== 0) {
                    this.setState({ compileStatus: 'failure' });

                    // compiled failure
                    const diagnostics = output.diagnostics;
                    this._errorCount = diagnostics.length;

                    for (let i = 0; i < diagnostics.length; i++) {
                        let errorMessage = diagnostics[i];

                        if (i <= MaxPrintingErrors) {
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            this.addAnnotation(errorMessage);
                        } else {
                            errorMessage = `Too many errors (${diagnostics.length})`;
                            console.error(errorMessage);
                            this.addNotification(errorMessage);
                            break;
                        }
                    }
                } else {

                    this._errorCount = 0;

                    // compiled successfully
                    this.setState({
                        compileStatus: 'success',

                        output: {
                            text:   output.wast || '',
                            binary: new Uint8Array(output.wasm)
                        }
                    });
                }
            }
        })
        .catch(error => {
            this.setState({ compileStatus: 'failure' });
            this._errorCount = 1;

            const message = '<' + this.state.compiler + '> Service not response';
            this.addNotification(message);
            console.error(message);
        });
    }

    onInputChange = value => {
        // skip compilation if possible
        value = value.trim();
        if (this._lastTextInput !== value) {
            if (this.state.compileMode === CompileMode.Auto)
                this.updateCompilationWithDelay(AutoCompilationDelay);
            this._lastTextInput = value;
        }
    }

    onInputPositionChange = inputCursorPosition => {
        this.setState({ inputCursorPosition });
    }

    onDownloadBinary = () => {
        const { output, compiler } = this.state;
        var blob = new Blob([output.binary], { type: "application/octet-stream" });
        FileSaver.saveAs(blob, `${compiler.toLowerCase()}.module.wasm`);
    }

    changeCompiler = compiler => {
        this._errorCount        = 0;
        this._lastTextInput     = '';
        this._compileTimerDelay = null;

        compiler = compiler || this.state.compiler;

        const description = CompilerDescriptions[compiler];

        this.setState({
            compiler,
            input: description.example.trim(),
        }, () => {
            if (description.offline) {
                if (!description.loaded && description.scripts && description.scripts.length) {
                    if (description.scripts.length > 1) {
                        window.$script.order(description.scripts.slice(), () => {
                            description.loaded = true;
                            this.onScriptLoad();
                        });
                    } else {
                        window.$script(description.scripts[0], () => {
                            description.loaded = true;
                            this.onScriptLoad();
                        });
                    }

                } else {
                    this.onScriptLoad();
                }
            } else {
                this.onScriptLoad();
            }
        });
    }

    onScriptLoad() {
        this.setState({ compilerReady: true }, () => {
            getCompilerVersion(this.state.compiler, version => this.setState({ version }));
            this.updateCompilation();
        });
    }

    onScriptError = () => {
        console.error('Script not load');
        this.setState({
            compilerReady: false,
        });
    }

    onSplitPositionChange = size => {
        this.handleSize(size);
    }

    onCompileButtonClick = mode => {
        this._clearCompileTimeout();

        if (mode === CompileMode.Auto || mode === CompileMode.Manual)
            this.updateCompilation();
    }

    onSettingsOptionChange = (key, value) => {
        if (!this.state.compilerReady) return;
        this.setState({ [key]: value }, key !== 'base64' ? this.updateCompilation : null);
    }

    handleSize = throttle(8, size => {
        if (this.splitEditor) {
            if (!this._cachedClientRect)
                this._cachedClientRect = ReactDOM.findDOMNode(this.splitEditor).getBoundingClientRect();

            const { width, height } = this._cachedClientRect;
            const pos = (size ? size / width : this.state.splitPosition);
            const primaryWidth = width * pos;

            this.setState({
                inputEditorWidth:  Math.ceil(primaryWidth),
                outputEditorWidth: Math.ceil(width - primaryWidth - SplitGripWidth),
                editorsHeight:     height - 160,
                splitPosition:     pos,
            });

            this.splitEditor.setSize(
                { primary: 'first', size: primaryWidth },
                { draggedSize: primaryWidth }
            );
        }
    })

    addNotification = (message) => {
      // skip notifications for Auto compile mode
      // if (this.state.compileMode === CompileMode.Auto) {
      //    return;
      // }

    	const {
        notifications,
        notificationCount,
      } = this.state;

      const id = notifications.size + 1;
      const newCount = notificationCount + 1;
      return this.setState({
      	notificationCount: newCount,
      	notifications: notifications.add({
          id,
      		message,
      		key:          newCount,
      		action:       '✕',
      		dismissAfter: 5000,
          actionStyle:  {
            borderRadius: 0,
            paddingLeft:  '1.5rem',
            paddingRight: '0.6rem',
            fontSize:     '1.8rem',
            color:        '#fff',
          },
      		onClick: this.removeAllNotifications,
      	})
      });
    }

    addAnnotation = (message, type = 'error') => {
        const posRegex = /[\{\(\[]\s*(\d+)\s*,\s*(\d+)\s*[\]\)\}]$/;
        const matches = posRegex.exec(message);
        if (matches && matches.length === 3) {
            var row = matches[1] >>> 0;
            // var column = matches[2] >>> 0;

            this.setState(({ annotations }) => ({
                annotations: annotations.add({ row, type, text: message }),
            }));
        }
    }

    removeAllAnnotation = () => {
        this.setState({ annotations: OrderedSet() });
    }

    removeNotification = index => {
        const { notifications } = this.state;
        return this.setState({
            notifications: notifications.filter(n => n.key !== index),
        })
    }

    removeAllNotifications = () => {
        return this.setState({
            notificationCount: 0,
            notifications:     OrderedSet(),
        });
    }

    render() {
        const {
            version,
            compiler,

            compilerReady,
            compileStatus,
            notifications,
            annotations,
            additionalStatusMessage,

            splitPosition,
            inputEditorWidth,
            outputEditorWidth,
            editorsHeight,

            input,
            output,
            outputType,
            base64,

            inputCursorPosition,

        } = this.state;

        function notificationStyle(index, style, notification) {
            return {
                zOrder: 999,
                color: '#fff',
                background: '#f00',
                fontSize: '1.5rem',
                padding: '1.2em',
                paddingLeft: '1.8em',
                borderRadius: 0,
                left: '5.3em',
                bottom: `${4.2 + (index * 3.6)}em`
            };
        }

        const errorNotifications = notifications ? (<NotificationStack
            activeBarStyleFactory={ notificationStyle }
            notifications={ notifications.toArray() }
            onDismiss={ notification => this.setState({
                notifications: this.state.notifications.delete(notification)
            }) }
        />) : null;

        const canBinaryDownload = (
          compilerReady &&
          compileStatus === 'success' &&
          output.binary
        );

        let busyState = 'pending';
        if (compilerReady) {
            switch (compileStatus) {
                case 'success': busyState = 'success'; break;
                case 'failure': busyState = 'failure'; break;
                default: break;
            }
        }

        return (
            <div>
                <ToolbarContainer
                    ref={ self => this.toolbar = self }
                    version={ version }
                    compiler={ compiler }
                    compileDisabled={ !compilerReady }
                    onCompilerChange={ this.changeCompiler }
                    onCompileClick={ this.onCompileButtonClick }
                    onCompileModeChange={ mode => {
                        this._clearCompileTimeout();
                        this.setState({ compileMode: mode });
                        if (mode === CompileMode.Auto)
                            this.updateCompilationWithDelay(AutoCompilationDelay);
                    }}
                    onSettingsOptionChange={ this.onSettingsOptionChange }
                    onOutputSelect={ type => this.setState({ outputType: type }) }
                />

                <SplitPane
                    ref={ self => this.splitEditor = self }
                    split="vertical"
                    minSize={ 200 }
                    defaultSize={ splitPosition * 100 + '%' }
                    onChange={ this.onSplitPositionChange }
                    style={{
                        margin: '12px',
                    }}
                >
                    <Editor
                        focus
                        id="input"
                        ref={ self => this.inputEditor = self }
                        width={ inputEditorWidth }
                        height={ editorsHeight }
                        code={ input }
                        annotations={ annotations.toArray() }
                        onChange={ this.onInputChange }
                        onPositionChange={ this.onInputPositionChange }
                    />
                    <Editor
                        readOnly
                        id="output"
                        mode={ outputType === 'text' ? 'wast' : 'typescript' }
                        ref={ self => this.outputEditor = self }
                        width={ outputEditorWidth }
                        height={ editorsHeight }
                        code={ formatCode(output[outputType], base64) }
                    />
                </SplitPane>

                <FooterContainer
                    errorCount={ this._errorCount }
                    busyState={ busyState }
                    binarySize={ output.binary ? formatSize(output.binary.length) : '' }
                    onDownloadPressed={ this.onDownloadBinary }
                    downloadDisabled={ !canBinaryDownload }
                    errorMessage={ additionalStatusMessage }
                    cursorPosition={ inputCursorPosition }
                />

                { errorNotifications }
            </div>
        );
    }
}
<|endoftext|>