What is the correct way to simulate a script with a new language?

41

Suppose I have implemented a new language and have a functional interpreter written in javascript. Something like mylang.interpret(code_as_string) . Now I would like to create an interface where I can insert my language code into <script> tags so that it works exactly like javascript operates. That is:

<script src="mylang.js" type="text/javascript"></script> <!-- o interpretador -->

<script type="text/mylang">
    global a = 5;
</script>
<script type="text/javascript">
    alert(mylang.globals.a); // deve mostrar 5
</script>
<script type="text/mylang">
    a = 6;
</script>

That is, <script> tags would be executed sequentially and could merge with javascript. I know I can for an event in onload to run all scripts with my language (since they are ignored by the browser), but this would not have the behavior I expect from the example above. Another case is if the element is entered via javascript. Is there a callback that is invoked whenever an element appears in the DOM?

Another problem is with loading if the tag comes with the src attribute. I imagined loading via ajax, but with that the scripts will run out of order, not strictly in the order they appear. How to guarantee this order?

    
asked by anonymous 11.02.2014 / 20:48

4 answers

14

This is not a straightforward answer since it does not involve a native callback , but I can think of a solution that would be to create a loader dynamically page code, just like PHP does, for example.

loader would be an interpreter written in JavaScript capable of loading a source code, starting reading in "html mode". Upon finding a <script> tag, it would start executing its code depending on the language. In the case of JavaScript, you could delegate to the browser itself.

Anyway, adding some restrictions to how the page loads, in theory seems to be possible.

Update: Running Python along with Javascript in Browser

Based on the great find of @bfavaretto , MutationObserver , I created a small project to run Python side-by-side with a Javascript page.

First I downloaded Brython , a Python 3 implementation in Javascript for running in the browser.

Then I mounted a class based on the @bfavaretto code.

pyscript.js

//inicializa brython
brython();

// Cria objeto que vai monitorar alterações no DOM
function criaObserver(el) {
    var observer = new MutationObserver(function(mutations) {
        // Loop sobre as mutações detectadas
        mutations.forEach(function(mutation) {

            // Inserção de nós aparecem em addedNodes
            var node = mutation.addedNodes[0];

            // Achou um script
            if(node && node.tagName === 'SCRIPT' && node.type === 'text/pyscript') {
                console.log('encontrado pyscript')
                var $src;
                if(node.src!=='') {
                    // get source code by an Ajax call
                    if (window.XMLHttpRequest){// code for IE7+, Firefox, Chrome, Opera, Safari
                       var $xmlhttp=new XMLHttpRequest();
                    }else{// code for IE6, IE5
                       var $xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                    }
                    $xmlhttp.open('GET',node.src,false)
                    $xmlhttp.send()

                    if($xmlhttp.readyState===4 && $xmlhttp.status===200){
                        $src = $xmlhttp.responseText
                    }
                    if ($src === undefined) { // houston, we have a problem!!!
                        console.log('erro ao carregar script')
                        return;
                     }
                } else {
                    $src = node.textContent || node.innerText;
                }

                // ver https://bitbucket.org/olemis/brython/src/bafb482fb6ad42d6ffd2123905627148e339b5ce/src/py2js.js?at=default

                // Passa o código para ser interpretado
                __BRYTHON__.$py_module_path['__main__'] = window.location.href;
                var $root=__BRYTHON__.py2js($src,'__main__');
                $src = $root.to_js();

                // eval in global scope
                if (window.execScript) {
                   window.execScript($src);
                   return;
                }

                var fn = function() {
                    window.eval.call(window,$src);
                };
                fn();
            } 
        });    
    });

    // Inicia o observer, configurando-o para monitorar inserções de nós em qualquer nível
    observer.observe(el, { childList: true, subtree: true })
    return observer; 
}

var observer = criaObserver(document);

Finally, I was able to successfully execute the page code below:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Python Test</title>
<script type="text/javascript" src="brython.js"></script>
<script type="text/javascript" src="pyscript.js"></script>

<script type="text/javascript">
var variavel = 1;
</script>

<script type="text/pyscript">
print('teste de print!')
print(variavel)
</script>

</head>

<body>
   <div id="content">Conteúdo</div>
</body>

<script type="text/pyscript">
from _browser import doc, alert
alert('Valor da variável: ' + str(variavel))

lista = ['itens', 'da', 'lista']
doc['content'].text = 'Conteúdo colocado com python: ' + ' '.join(lista)
</script>

<script type="text/pyscript" src="teste.js"></script>

</html>

Note that there is an external file ( teste.js ), containing the following python code:

d = { '11': 'um', '22': 'dois' }
for i in d:
    print(i)

On the one hand, there is a limitation of this solution, derived from a Brython limitation: JavaScript code can not access objects created within a Python excerpt.

However, as seen in the example, doing the reverse is simple and straightforward, that is, the Python code has full access to Javascript code.

Functional example in my GitHub account

    
11.02.2014 / 21:06
8

You can monitor DOM loading using a MutationObserver . There are still support restrictions on using this (eg in IE, it was only implemented in version 11), and I have no performance information. However, as a proof of concept, I have constructed a code that locates the script blocks and passes it to the interpreter. You can even get source code for external files in a synchronous way, using XMLHttpRequest.

Here's the heart of the code, to be put before any other script:

// Cria objeto que vai monitorar alterações no DOM
function criaObserver(el) {
    var observer = new MutationObserver(function(mutations) {
        // Loop sobre as mutações detectadas
        mutations.forEach(function(mutation) {

            // Inserção de nós aparecem em addedNodes
            var node = mutation.addedNodes[0];

            // Achou um script
            if(node.tagName === 'SCRIPT' && node.type === 'text/fooscript') {

                // TODO: implementar chamada ajax síncrona (argh!) para pegar o código
                if(node.src) {

                }

                // Passa o código para ser interpretado
                interpreta(node.textContent || node.innerText);
            } 
        });    
    });

    // Inicia o observer, configurando-o para monitorar inserções de nós em qualquer nível
    observer.observe(el, { childList: true, subtree: true })
    return observer; 
}

var observer = criaObserver(document);

At the end of DOM loading (that is, at the end of body , event DOMContentReady , or, finally, window.onload ), you must disconnect observer:

observer.disconnect();

See a demo in jsbin .

    
11.02.2014 / 22:42
4

This is the principle of LESS interpreters and others that use a type that does not exist and is then ignored by the browser.

Here the step by step

  • Use <script> with type that does not exist
  • Add your language processor, which will use javascript to first search all the script tags, for example document.querySelectorAll('script') , and then for each of them, will test if the type is the one you want
  • For each script with your type found, give innerHTML / innerText / textContent to get a string with what's inside it
  • For each string obtained, process it with your interpreter in javascript
  • That's pretty cool. Technically it would be possible even to interpret other languages within a browser just using javascript

    Proof of concept

    Follow PoC. If you do the interpreter I'll implement it here. Take a look. It's a very appealing thing to want your interpreter to be understood at the same time as the Javascript parser, but I have not done so, so the way I made your parser will only be active in onload

    Sample HTML

    <html>
      <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      </head>
      <body>
        <script src="mylang.js" type="text/javascript"></script> <!-- o interpretador -->
    
        <script type="text/mylang">
          global a = 5;
        </script>
        <script type="text/javascript">
          //alert(mylang.globals.a); // deve mostrar 5
        </script>
        <script type="text/mylang">
          a = 6;
        </script>
        <script type="text/javascript">
          // lalala
        </script>
      </body>
    </html>
    

    mylang.js

    var mylang = {};
    mylang.globals = {};
    mylang._scripts_all = []; // Todos os scripts
    mylang._scripts_ml = []; // Somente os "text/mylang"
    window.onload = function () {
        var i;
        function interpretador (string) {
    
            //@todo kung-fu aqui
            mylang.globals.a = null;
            console.log('kung-fu ',  string);
        }
    
        mylang._scripts_all = Array.prototype.slice.call(document.querySelectorAll('script'));
        mylang._scripts_ml = [];
    
        for (i = 0; i < mylang._scripts_all.length; i += 1) {
            if (mylang._scripts_all[i].type && mylang._scripts_all[i].type === "text/mylang") {
    
                mylang._scripts_ml.push(mylang._scripts_all[i]);
            }
        }
    
        for (i = 0; i < mylang._scripts_ml.length; i += 1) {
            interpretador(mylang._scripts_ml[i].innerHTML);
        }
    };
    
    // Console imprime
    //kung-fu  
    //      global a = 5;
    //     mylang.js:11
    //kung-fu  
    //      a = 6;
    

    PS: This is not trivial. I hope that if it is really correct, the staff value it; P

        
    11.02.2014 / 21:26
    2

    In order for this to work the way you requested it, your interpreter should not run your script. I explain: Given the following script tag :

    <script type="text/rubysh">
        def teste
            puts "olá"
        end
    </script>
    

    The script of type text/rubysh would then have to be translated to text/javascript :

    <script type="text/javascript">
        function teste(){
            console.log("olá");
        }
    </script>
    

    Your interpreter / compiler would then override the original tags with those of type text/javascript before to run everything. And then let the browser run everything normally.

    ...
    Another solution would be to do script with type "text / javascript" and call a eval function of your interpreter passing the commands as string ... which is very bad (eval is evil)

        
    27.02.2014 / 13:21