Source: MaiaVM.js

/**
 * @license
 * Copyright 2020 Roberto Luiz Souza Monteiro,
 *                Renata Souza Barreto,
 *                Hernane Borges de Barros Pereira.
 *
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * MaiaScript virtual machine.
 * @class
 */
function MaiaVM() {
    init();

    /**
     * Creates the attributes of the class.
     */
    function init() {
        // Class attributes goes here.
        compiledCode = {
            'xml': '',
            'mil': '',
            'js': ''
        }
    }

    /**
     * Compiles MaiaScript code, contained in <script> tags for JavaScript
     * and inserts the compiled code in a new <script> tag in the <body> tag
     * of the document.
     * 
     * MaiaScript codes must be in <script> tags of type 'text/maiascript',
     * as in the following example:
     * 
     * <script type='text/maiascript'> ... </script>
     * 
     * This method must be called from the onLoad event of the document's
     * <body> tag, as in the following example:
     * 
     * <body onload='maiavm.compile()'> ... </body>
     */
    this.compile = function() {
        var scripts = document.querySelectorAll('script[type="text/maiascript"]');
        for (index in scripts) {
            if (typeof scripts[index].getAttribute != 'undefined') {
                var fileName = scripts[index].getAttribute('src');
                if (typeof fileName != 'undefined') {
                    if (fileName) {
                        compiledCode.maia = '';
                        fetch(fileName)
                            .then(response => response.text())
                            .then(data => {
                                var code = data;
                                if (typeof code != 'undefined') {
                                    if (typeof code == 'string') {
                                        compiledCode.xml = '';

                                        function getXml(data) {
                                            compiledCode.xml += data;
                                        }
                                        var s = new MaiaScript.XmlSerializer(getXml, true);
                                        var maiaScriptParser = new MaiaScript(code, s);
                                        try {
                                            maiaScriptParser.parse_maiascript();
                                        } catch (pe) {
                                            if (!(pe instanceof maiaScriptParser.ParseException)) {
                                                throw pe;
                                            } else {
                                                var parserError = maiaScriptParser.getErrorMessage(pe);
                                                system.log(parserError);
                                                throw parserError;
                                            }
                                        }
                                        var parser = new DOMParser();
                                        var xml = parser.parseFromString(compiledCode.xml, 'text/xml');
                                        var compiler = new MaiaCompiler();
                                        compiledCode.js = compiler.compile(xml);
                                        try {
                                            var script = document.createElement('script');
                                            script.type = 'text/javascript';
                                            script.text = compiledCode.js;
                                            document.body.appendChild(script);
                                        } catch (se) {
                                            var scriptError = se.message;
                                            system.log(scriptError);
                                            throw scriptError;
                                        }
                                    }
                                }
                            });
                    }
                }
            }
            var code = scripts[index].innerHTML;
            if (typeof code != 'undefined') {
                if (typeof code == 'string') {
                    compiledCode.xml = '';

                    function getXml(data) {
                        compiledCode.xml += data;
                    }
                    var s = new MaiaScript.XmlSerializer(getXml, true);
                    var maiaScriptParser = new MaiaScript(code, s);
                    try {
                        maiaScriptParser.parse_maiascript();
                    } catch (pe) {
                        if (!(pe instanceof maiaScriptParser.ParseException)) {
                            throw pe;
                        } else {
                            var parserError = maiaScriptParser.getErrorMessage(pe);
                            system.log(parserError);
                            throw parserError;
                        }
                    }
                    var parser = new DOMParser();
                    var xml = parser.parseFromString(compiledCode.xml, 'text/xml');
                    var compiler = new MaiaCompiler();
                    compiledCode.js = compiler.compile(xml);
                    try {
                        var script = document.createElement('script');
                        script.type = 'text/javascript';
                        script.text = compiledCode.js;
                        document.body.appendChild(script);
                    } catch (se) {
                        var scriptError = se.message;
                        system.log(scriptError);
                        throw scriptError;
                    }
                }
            }
        }
    }

    /**
     * Compiles the MaiaScript code passed as a command line argument
     * and executes the code in the JavaScript interpreter from which
     * this method was invoked.
     */
    this.run = function() {
        // Supports only the Node.js interpreter.
        if (typeof process !== 'undefined') {
            var command = 'node';
            var argv = process.argv.slice();
            compiledCode.xml = '';
            var fs = require('fs');
            var readTextFile = fs.readFileSync;

            function getXml(data) {
                compiledCode.xml += data;
            }

            function read(input) {
                if (/^{.*}$/.test(input)) {
                    return input.substring(1, input.length - 1);
                } else {
                    var content = readTextFile(input, 'utf-8');
                    return content.length > 0 && content.charCodeAt(0) == 0xFEFF ? content.substring(1) : content;
                }
            }

            system.argv = argv.slice();
            system.argc = argv.length;
            var justCompile = false;
            var inputFile;
            var outputFile;
            var outputFileType = 'js';
            var outputContents = '';
            if (argv.length > 2) {
                var i = 2;
                while (i < argv.length) {
                    if (argv[i] == '-c') {
                        justCompile = true;
                        outputFileType = 'js';
                    } else if (argv[i] == '-m') {
                        justCompile = true;
                        outputFileType = 'mil';
                    } else if (argv[i] == '-x') {
                        justCompile = true;
                        outputFileType = 'xml';
                    } else if ((argv[i] == '-h') || (argv[i] == '--help')) {
                        system.log('MaiaScript Command Line Interface (CLI)');
                        system.log('Usage: maiascript [options] [script.maia] [--] [arguments]');
                        system.log('Options:');
                        system.log('-c                          Just compile to JS, don\'t run the script;');
                        system.log('-m                          Just compile to MIL, don\'t run the script;');
                        system.log('-x                          Just compile to XML, don\'t run the script;');
                        system.log('-h     --help               Displays this help message;');
                        system.log('-o     <script.js>          Output file name;');
                        system.log('       --                   End of compiler options.\n');
                    } else if (argv[i] == '-o') {
                        i++;
                        outputFile = argv[i];
                    } else if (argv[i] == '--') {
                        break;
                    } else {
                        inputFile = argv[i];
                        break;
                    }
                    i++;
                }
                system.argv = argv.slice(i);
                system.argc = system.argv.length;
                if (typeof inputFile != 'undefined') {
                    var code = read(String(inputFile));
                    var s = new MaiaScript.XmlSerializer(getXml, false);
                    var maiaScriptParser = new MaiaScript(code, s);
                    try {
                        maiaScriptParser.parse_maiascript();
                    } catch (pe) {
                        if (!(pe instanceof maiaScriptParser.ParseException)) {
                            throw pe;
                        } else {
                            var parserError = maiaScriptParser.getErrorMessage(pe);
                            system.log(parserError);
                            throw parserError;
                        }
                    }
                    var parser = new DOMParser();
                    var xml = parser.parseFromString(compiledCode.xml, 'text/xml');
                    var compiler = new MaiaCompiler();
                    compiledCode.mil = compiler.xmlToMil(xml);
                    compiledCode.js = compiler.compile(xml);
                    if (justCompile) {
                        if (typeof outputFile == 'undefined') {
                            var fileName = inputFile.split('.').shift();
                            if (outputFileType == 'js') {
                                outputFile = fileName + '.js';
                                outputContents = compiledCode.js;
                            } else if (outputFileType == 'mil') {
                                outputFile = fileName + '.mil';
                                outputContents = JSON.stringify(compiledCode.mil);
                            } else if (outputFileType == 'xml') {
                                outputFile = fileName + '.xml';
                                outputContents = compiledCode.xml;
                            } else {
                                outputFile = fileName + '.js';
                                outputContents = compiledCode.js;
                            }
                        } else {
                            outputContents = compiledCode.js;
                        }
                        fs.writeFile(outputFile, outputContents, function(err) {
                            if (err) {
                                throw err;
                            }
                        });
                    } else {
                        try {
                            const vm = require('vm');
                            const script = new vm.Script(compiledCode.js);
                            script.runInThisContext();
                        } catch (e) {
                            var evalError = e.message;
                            system.log(evalError);
                        }
                    }
                } else {
                    system.log('MaiaScript Command Line Interface (CLI)');
                    system.log('Usage: maiascript [options] [script.maia] [--] [arguments]');
                }
            } else {
                var options = {
                    input: process.stdin,
                    output: process.stdout
                }

                // Command prompt.
                const readline = require('readline');
                const rl = readline.createInterface(options);

                rl.setPrompt(': ');
                rl.prompt();

                function runCommand(commandLine) {
                    var res;
                    if (commandLine.trim() == 'exit') {
                        process.exit(0);
                    }
                    try {
                        res = core.eval(commandLine);
                    } catch (e) {
                        var evalError = e.message;
                        system.log(evalError);
                        system.showMessageDialog(evalError);
                    }
                    if (typeof res != 'undefined') {
                        system.log(res);
                    }
                    rl.prompt();
                }

                rl.on('line', runCommand);
            }
        }
    }
}

maiavm = new MaiaVM();

/*
 * Run MaiaScript code if this script has been invoked
 * from the command line.
 */
if (typeof process !== 'undefined') {
    // Emulate DOM.
    const jsdom = require("jsdom");
    const {JSDOM} = jsdom;
    var doc = new JSDOM();
    var DOMParser = doc.window.DOMParser;

    // Emulate Web SQL.
    try {
        const openDatabase = require('websql');
    } catch (e) {
        console.error(e.message);
    }

    var alert = system.log;

    maiavm.run();
}