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

Asked

Viewed 1,480 times

43

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 my language code into tags <script> so that it works exactly the same as how javascript operates. That is to say:

<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, the tags <script> would run sequentially and could merge with javascript. I know I can put an event on 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 above example. Another case is if the element is inserted via javascript. There is a way to have a callback that is called whenever an element appears in the DOM?

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

  • Dude, that’s a cool question! I don’t think I can answer, but I’ve had some questions here. If you use the tag "script" will not give confusion in the browser? You may need to create your own tag and access elements of it via DOM to interpret. Have tried something along these lines?

  • Oops. I just saw in your question that they are ignored by the browser. True. Sorry. :)

  • @Luiz Empirically I noticed that if I use one type which does not exist, the <script> turns into a noop. But I don’t know how true it is. About creating a new tag, it will fall into the same problems I mentioned, won’t it? My goal is to have this operation as transparent as possible

  • Yes. The question is the order, mainly the intercalation with Javascript.

  • @Guilhermebernal answered there. If that’s more or less what you want, I add code that implements it for you. I’m just not going to make a whole interpreter of language because then it’s already forcing friendship. By the way, congratulations on the question. Vai +1 Fav

  • Someone has tried something very similar to what you want to do. See the article below: http://james.padolsey.com/javascript/custom-javascript-with-parsescripts/

  • +1 for the interesting question.

Show 2 more comments

4 answers

15


This is not even a direct answer because it does not involve a callback native, however, I can think that a solution would be the creation of a Loader capable of dynamically loading and processing page code, just like PHP does, for example.

The Loader would be an interpreter written in Javascript able to load a source code, starting reading in "html mode". When finding a tag <script>, it would execute the respective 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 theory it seems possible.


Update: running Python along with Javascript in the browser

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

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

Then I set up 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 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 Brython: Javascript code cannot access objects created within a Python chunk.

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

  • 2

    Meaning, turn the whole page into a string and interpret the whole HTML? It gives me the chills. Yeah, it would work.

  • 1

    @Guilhermebernal I’m going home to rest... who knows I don’t dream of a better solution! rsrs

  • @Guilhermebernal My "solution" can incur many javascript context problems. Chills²!

  • @Guilhermebernal Take a look at this OS reply: http://stackoverflow.com/a/2872454/2896619. I cite some other examples of scripts. Maybe if you poke them, you’ll find out how they do it. :)

  • Regarding placing a Switch to a new DOM object you can use MutationObserver has an example in MSDN

  • 1

    Perfect! It seems to meet all the requirements I had in mind, was about to post a similar implementation based on bfavaretto, but you did it faster. They’re talking about a performance problem, but either way, the bottleneck will be the interpreter. Now I can go to sleep in peace knowing that I won’t have to process html :)

Show 1 more comment

8

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

Here goes 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 the loading of the FOD (i.e., at the end of the body, at the event DOMContentReady, or, in the latter case, in window.onload), it is necessary to disconnect the Observer:

observer.disconnect();

See a demo in jsbin.

  • I didn’t know the MutationObserver. I’ll try it later. Fantastic!

  • One of the reasons I did it with the window.onload simple is that even if you had a way of interpreting external elements while they were loaded, you would have to lock the entire page rendering solely because of this, in the same way as with javascript today. But unlike native js, I don’t know if it is possible to force even modern browsers to stop loading the DOM to wait for the remote script without a loop that would torraria CPU. You can put in Sleep all page creation (JS, CSS, HTML) without frying CPU?

  • @Emersonrochaluiz For my tests, looks like that the Mutationobserver callback runs synchronously. To get remote scripts, it is possible to make a synchronous Xmlhttprequest. Not that I recommend any of these methods...

  • Ignore previous comment as it is not possible to load external js file synchronously. I saw now that you have with a parameter of XMLHttpRequest. I’ve never used this before.

  • 1

    @Emersonrochaluiz Lucky you! : ) This is highly contraindicated

  • Using Ajax to make synchronous request before loading the page (using Mutationobserver) is kind of an unnecessary resource expense (it’s better to embed the script in the page already). Now if you bring the asynchronous script you can use onreadystatechange.

  • --Edit just found this link Xmlhttprequest

  • @Leandroamorim Embed how? In the HTML body? The question talks about the use of the attribute src, and to function as js would need to make such a request.

  • @bfavaretto but src would fit perfectly into your script. Because onload is only called after the "whole page" is loaded, including scripts with src, I may not have understood the need to do a synchronous "Xmlhttprequest" before onload, but this seems to me a strange use for "Xmlhttprequest". When I say embed it would be this or put in the html body or put a src.

  • 1

    @Leandroamorim src only works if the <script> have not type, or if it is a valid type of js. Then the browser downloads and interprets it as js. The intention of the question is to treat it as another language.

Show 5 more comments

4

That’s the principle of interpreters LESS and others who 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 doesn’t 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, will test whether the type is 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 interpreter in javascript

This is pretty cool. Technically it would even be possible to make interpreter of other languages within a browser just using javascript

Proof of concept

Follow Poc. If you make the interpreter I implement it here. Take a look. It’s kind of appealing to want your interpreter to be understood at the same time as the Javascript parser, there’s a way, but I didn’t do it, so the way I did your parser will only be active in onload

HTML example

<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 ;P

  • Although it does not meet all the requirements of the question, I find a good solution.

  • But how to operate strictly in order when interspersed with javascript? So everything will run only after other javascript scripts.

  • Are you talking about create a real-time interpreter within a browser. It has to meet virtually all the requirements of William, some just will not have very good performance. Execute my code by removing from inside the window.onload and note that the following tags will not be ready yet. To meet William’s requirement, the only thing missing from this is to read which Systener is enabled every time HTML is being parsed and interpreted and run scripts that are in the language. In my Poc for simplicity I did considering with onload

  • 2

    @Emersonrochaluiz Yes, you can do this with Mutationobserver, but I have doubts about support and performance.

  • So, that goes from William’s requirements survey. window.onload would make a C++ bold javascript interpreter work well even on IE5. http://caniuse.com/mutationobserver does not have such good support, so you would need to see a whole code just to make it work. Reinforcement, the problem is not to be executed in order, the additional amount of code would be to run in interspersed order "text/mylang" with traditional javascript. That’s what impacts performance and lots of additional code

2

In order 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 the type text/rubysh would then have to be translated into text/javascript:

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

Your interpreter/compiler would then subustituindo the original tags by the type text/javascript before of everything being executed. And then let the browser run everything normally.

...
Another solution would be to script type "text/javascript" and call a function eval of your interpreter passing the commands as string... which looks pretty ugly (Eval is evil)

  • Before everything runs? This is before the interpreter is loaded. And yet I only have access to tags that have already been entered into the DOM. In addition the script tags are immediately executed when they are inserted, which breaks the desired execution order.

  • No, I mean, the interpreter should run before the other tags. Even if he has to remove script tags and add them into an array to return all scripts back in the right order (interpreting the ones that are relevant).

  • If the interpreter runs before, then the other script tags aren’t even in the DOM yet and I can’t read them

  • hmm... hadn’t thought of it. This knocks my whole answer... Does your interpreter really have to be client side? On the server side this would not be a problem (it would be a Pre-processor that would write javascript). Or even you could change the type of Avascripts so they do not run, but it is "xunxo", I do not like this option.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.