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

Suppose I implemented a new language and I have a functional interpreter written in javascript. Something like mylang.interpret(code_as_string). Now I would like to create an interface in which I can insert the code of my language in tags <script> so that it works exactly the same as how 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>

I.e. the tags <script> would be executed sequentially and could interleave with javascript. I know I can by an event in onload to execute 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 inserted via javascript. Is there how to have a callback that is called whenever an element appears in the DOM?

Another problem is with loading if the tag comes with the src attribute. I figured in loading via ajax, but with this the scripts will run out of order, and not strictly in the order in which appear. How to ensure this order?

 43
Author: bfavaretto, 2014-02-11

4 answers

This is not a direct answer because it does not involve a native callback, however, I can think that a solution would be to create a loader capable of dynamically loading and processing the page code, exactly as PHP does, for example.

The 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 run its code, depending on the language. In the case of JavaScript, it could delegate to the browser itself.

Anyway, adding some restrictions to the way the page is loaded, in thesis seems to be possible.


Update: running Python along with Javascript in the browser

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

First I downloaded the Brython, an implementation of Python 3 in Javascript for browser execution.

Then I put together a class based on @bfavaretto's 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 code from the page 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 the inclusion of 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 limitation of the Brython: Javascript code cannot access objects created within a Python snippet.

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

Functional example in my github account

 15
Author: utluiz, 2014-02-12 23:41:51

You can monitor the loading of the DOM using a MutationObserver. There are still support restrictions regarding the use of this (for example, in IE, it was only implemented in Version 11), and I have no performance information. However, as a proof of concept, I have built a code that locates the script blocks and passes it to the interpreter. You can even obtain the source code of external files synchronously, using XMLHttpRequest.

Here goes the heart of the code, to be placed 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 the DOM load (i.e. at the end of the body, in the event DOMContentReady, or, in the last case, in the window.onload), you must disconnect the observer:

observer.disconnect();

See a demo in JSBin.

 8
Author: bfavaretto, 2014-02-11 21:50:19

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

Here the step by step

  1. Use <script> with a type that does not exist
  2. add your language processor, which will use javascript to first search for all script tags, for example document.querySelectorAll('script'), and then for each of them, it will test if the type is the desired
  3. for each script with its type found, give a innerHTML/innerText/textContent to get a string with what's inside it
  4. for each string obtained, process it with your javascript interpreter

That's pretty cool. Technically it would even be possible to do interpreter of other languages within a browser only using javascript

Proof of concept

Follows PoC. If I do the interpreter I implement it here. Take a look. It's a kind of appealing thing to want your interpreter to be understood at the same time as the Javascript parser, it even has how, but I did not do it, so the way I did your parser will only be active in onload

Example 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 something trivial. I hope that if it is really correct, the people value; p

 4
Author: Emerson Rocha, 2014-02-11 21:01:55

For this to work the way you asked, 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 sub-replace the original tags with those of Type text/javascript before Everything is executed. And then let the browser run everything normally.

...
Another solution would be to script with type "text/javascript" and call a function eval from your interpreter by passing the commands as string... what gets ugly (eval is evil)

 2
Author: SparK, 2014-02-27 12:21:10