newLISP in a browser

Started by Lutz, January 02, 2014, 01:43:18 PM

Previous topic - Next topic

HPW

#15
Hello Marc,



Nice Editor, maybe you should link the indexed doc:



http://www.newlisp.org/downloads/manual_frame.html">http://www.newlisp.org/downloads/manual_frame.html



You may also choose better Colors. It is difficult to see selected text and the Output area.



Edit: Also tested on the Android Handy of my kids. Works fine



Regards
Hans-Peter

Lutz

#16
Marc's editor looks great! When all is done, I will try to deliver a compressed version of newlisp-js-lib.js which loads faster and decompresses client-side. It should be around 500K.



newlisp-js now has a eval-string-js function. Enter any JavaScript call or statement to evaluate in the parent browser:


(eval-string-js "window.prompt('Enter some text:')")

(eval-string-js "6 * 7") => "42


The statement would pop up a dialog and return the string entered.



http://www.newlisp.org/downloads/development/inprogress/">http://www.newlisp.org/downloads/develo ... nprogress/">http://www.newlisp.org/downloads/development/inprogress/



So now with  eval-string-js anything is possible in a browser for newLISP :)

HPW

#17
Hello Lutz,



Thanks again for the improvements!



You may change the sample to avoid the 'undefined' in the promp-window:

(eval-string-js "window.prompt('Enter some text:','')")


Is there a max length for the return of the js-call?

If so, it would be difficult to use it for loading Lisp-sources from the web-Server.

(As long it is not supported directly in newlisp-js)



Regards
Hans-Peter

hilti

#18
(eval-string-js) is very cool!



Just played around with it:



(setq number (eval-string-js "window.prompt('Enter a number to divide by 2:','')"))
(setq result (div (int number) 2))
(setq alert (append "alert(" (string result) ")"))
(eval-string-js alert)


Getting the browser window height or width is easy now.



(eval-string-js "$(window).height();")
(eval-string-js "$(window).width();")


I do my best in making the Browser-IDE more advanced.
--()o Dragonfly web framework for newLISP

http://dragonfly.apptruck.de\">http://dragonfly.apptruck.de

Lutz

#19
What browser and version are you using? I don't get any "undefined" messages when using only one parameter for the window.prompt call. I am using:



Firefox 26.0

Chrome 31.0

Safari 7.01



Also, all the code from Emscripten uses windows.prompt() with only one string argument.



Regarding the the size of the returned string from eval-string-js:

It seems that JavaScript is allocating the memory for the returned string and then is also managing it for GC. newLISP makes a copy of it and manages that copy. I tested for memory leaks when invoking eval-string-js and it seems that memory is managed well by JavaScript and newLISP for this call. I haven't tested for the max memory allowed as return value but believe, there will be no problems loading code source.



Talking about memory in general:

At this moment newlis-js-lib.js is limited to about 16MB of heap memory, which is the default Emscripten allocates and probably a sensible (conservative) choice for browser based applications. This value can be changed in a configuration file and perhaps I will bump it up to 32MB or even more. There are many other limits which can be configured before compilation and as things move along, I will experiment. At the moment I go with the defaults until we know more.



Ps: thanks for the great examples Marc. Do you also get this "undefined" message HPW is talking about? And what browser version do you run?



Ps2: I am getting a "undefined" as return value from (eval-string-js "alert(6)"), but believe this is Ok, "undefined" is simply the return value from the JavaScript alert() function in this case, perhaps in JavaScript alert is "void alert()".



Ps3: Note, that using $(window).width(); also would need jQuery installed.

HPW

#20
QuoteWhat browser and version are you using? I don't get any "undefined" messages when using only one parameter for the window.prompt call.


Ok, it is only on IE 11.

Chrome 31 is also Ok for me without the second parameter.

But when you use it it does not hurt and you can set a default-text into the promt-window.



Regards
Hans-Peter

HPW

#21
Hello,



sys-info on newlisp-js shows 203 for me.

Does it mean UTF-8 and what else?



Regards
Hans-Peter

hilti

#22
QuotePs: thanks for the great examples Marc. Do you also get this "undefined" message HPW is talking about? And what browser version do you run?


Yes, I do.



I'm running on OSX Mavericks



1. Safari 7.01

2. Chrome 31.0.1650.63





P.S. Reddit seems to be interested, because I get some load on my server ...

http://www.reddit.com/r/lisp">//http://www.reddit.com/r/lisp
--()o Dragonfly web framework for newLISP

http://dragonfly.apptruck.de\">http://dragonfly.apptruck.de

Lutz

#23
The second argument is now included in the example together with some other cleanup in index.html and some more instructions in README.txt:



http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip">http://www.newlisp.org/downloads/develo ... 10.5.7.zip">http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip



I am very excited about Marc's editor, which looks better each time I load it. Fortunately once the same newlisp-js-lib.js is loaded it goes pretty quick as it is cached by the browser, but there seems to be some load on your site at times.

Lutz

#24
Here is a quick way to compress newlisp-js-lib.js for faster deliever, but this is only usable for delivery on HTTP 1.1 compliant web servers like Apache. It will not work when loading locally from a file or with newLISP in server mode, which only implements HTTP 1.1 partially.



On Unix compress doing:
gzip newlisp-js-lib.js # this produces a newlisp-js-lib.js.gz
Now edit index.html replacing instances of newlisp-js-lib.js with newlisp-js-lib.js.gz:

// replace
<script async type="text/javascript" src="newlisp-js-lib.js"></script>
// with
<script async type="text/javascript" src="newlisp-js-lib.js.gz"></script>

HPW

#25
Slightly modified html with disabled Gui until downloading is finished:



<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>newLISP 10.5.7 compiled to JavaScript</title>
    <style type="text/css" media="screen">
    .emscripten {
        padding-right: 0; margin-left: auto; margin-right: auto; display: block;
        }
    .button {
        color: #AA0000;
        font-size: 110%;
        }
    textarea {
        font-family: Andale Mono, "Bitstream Vera Sans Mono", Monaco, Courier;
        font-size: 15px;
        width: 100%;
        }
    body, p {
        font-family: Lucida Sans, Helvetica, sans-serif;
        }
    </style>
  </head>

  <body>
    <div class="emscripten" id="status">Downloading...</div>
    <div class="emscripten">
      <progress value="0" max="100" id="progress" hidden=1></progress>  
    </div>

    <p>Input&nbsp;&nbsp;<input type="button" value="eval" class="button" id="evalinput"
       onclick="newlispEvalStr(document.getElementById('input').value.trim())" disabled>
       &nbsp;<input type="button" value="clear" id="clearinput" class="button"
       onclick="document.getElementById('input').value = '';" disabled><br><br>
     <textarea class="emscripten" id="input" rows="14" disabled>;
; wait for "Downloading ..." to finish, then click eval
;
(println "Hello World")
;; up to 1023 characters can be passed to string in eval-string-js
;; no limit on size of returned string
(eval-string-js "window.prompt('Enter some text:','')")
     </textarea></p>
    <p>Output&nbsp;&nbsp;<input type="button" value="clear" class="button" id="clearoutput"
       onclick="document.getElementById('output').value = '';" disabled><br><br>
    <textarea class="emscripten" id="output" rows="12" disabled></textarea></p>

    <script type='text/javascript'>
      // download newlisp-js-lib.js and show progress of downloading
      var Module = {
        preRun: [],
        postRun: [(function() {
          newlispEvalStr = Module.cwrap('newlispEvalStr', 'number', ['string']); })],
        print: (function() {
          var element = document.getElementById('output');
          element.value = ''; // clear browser cache
          return function(text) {
            text = Array.prototype.slice.call(arguments).join(' ');
            element.value += text + "n";
            element.scrollTop = 99999; // focus on bottom
          };
        })(),
        printErr: function(text) {
          text = Array.prototype.slice.call(arguments).join(' ');
          if (0) { // XXX disabled for safety typeof dump == 'function') {
            dump(text + 'n'); // fast, straight to the real console
          } else {
            console.log(text);
          }
        },
        setStatus: function(text) {
          if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
          if (text === Module.setStatus.text) return;
          var m = text.match(/([^(]+)((d+(.d+)?)/(d+))/);
          var now = Date.now();
          if (m && now - Date.now() < 30) return; // if progress update, skip it if too soon
          var statusElement = document.getElementById('status');
          var progressElement = document.getElementById('progress');
          if (m) {
            text = m[1];
            progressElement.value = parseInt(m[2])*100;
            progressElement.max = parseInt(m[4])*100;
            progressElement.hidden = false;
          } else {
            progressElement.value = null;
            progressElement.max = null;
            progressElement.hidden = true;
            document.getElementById('evalinput').disabled = false;
            document.getElementById('clearinput').disabled = false;
            document.getElementById('clearoutput').disabled = false;
            document.getElementById('input').disabled = false;
            document.getElementById('output').disabled = false;
          }
          statusElement.innerHTML = text;
        },
        totalDependencies: 0,
        monitorRunDependencies: function(left) {
          this.totalDependencies = Math.max(this.totalDependencies, left);
          Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) +
                        '/' + this.totalDependencies + ')' : 'All downloads complete.');
        }
      };
      Module.setStatus('Downloading...');
    </script>

    <script async type="text/javascript" src="newlisp-js-lib.js"></script>

    <center>
    <font size="-1">Copyright &copy; 2014, <a href="http://newlisp.org">newlisp.org</a></font>
    </center>
  </body>
</html>

<!-- eof -->
Hans-Peter

Lutz

#26
The latest update adds automatic row-sizing of input and output windows on load and a button to switch layout from horizontal two-column to a vertical one-column layout.



http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip">http://www.newlisp.org/downloads/develo ... 10.5.7.zip">http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip







Ps: This also has HPW's addition of disabling the GUI while downloading.

Lutz

#27
Now files can be loaded from the local file system into the edit box. Also JavaScript code can be evaluated directly without using the (eval-string-js ...) expression but using the JS button.



http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip">http://www.newlisp.org/downloads/develo ... 10.5.7.zip">http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip

Lutz

#28
A better layout on mobile devices and an info button for a help page:



http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip">http://www.newlisp.org/downloads/develo ... 10.5.7.zip">http://www.newlisp.org/downloads/development/inprogress/newlisp-js-10.5.7.zip

HPW

#29
Modified html with additional doc button with keyword-input.

(Selection of a keyword in one of the textareas put it into the keyword-input)

Opens either the indexed doc or context-sensitive doc for the keyword.



<!doctype html>
<html lang="en-us">
  <head>
    <meta charset="utf-8"/>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <title>newLISP in a browser</title>
    <style type="text/css" media="screen">
    .emscripten {
        padding-right: 0; margin-left: auto; margin-right: auto; display: block;
        }
    .button {
        color: #000000;
        font-size: 1.0em;
        right: 0px;
        margin-bottom: 3px;
        margin-top: 3px;
        }
    .red-button {
        color: #AA0000;
        font-size: 1.0em;
        margin-bottom: 3px;
        margin-top: 3px;
        }
    .two-column {
        position: relative;
        margin: 1%;
        float: left;
        width: 48%;
        }
    .one-column {
        position: relative;
        margin: 1%;
        width: 98%;
        }
    textarea {
        font-family: Andale Mono, Monaco, Courier;
        font-size: 1.00em;
        width: 100%;
        resize: none;
        }
    body, p {
        font-family: Lucida Sans, Helvetica, sans-serif;
        }
    body {
        background-color: #EEEEEE;
        margin: 0;
        padding: 0;
        }
    </style>
  </head>

  <body>
    <output id="list"></output>
    <body onmouseup="GetSelectedText ()">

    <div class="emscripten" id="status">Downloading...</div>
    <div class="emscripten">
      <progress value="0" max="100" id="progress" hidden=1></progress>
    </div>

    <div class="two-column" id="editor">
    Editor&nbsp;<input type="button" value="eval" class="red-button" id="evalinput"
      onclick="newlispEvalStr(document.getElementById('input').value.trim())" disabled />

    <input type="button" value="JS" id="evalinputjs" class="red-button"
      onclick="newlispEvalStr('(eval-string-js {' + document.getElementById('input').value.trim() + '})' )" disabled />

    <input type="button" value="clear" id="clearinput" class="button"
      onclick="document.getElementById('input').value = '';" disabled /><br/>

    <textarea id="input" disabled>;
; wait for "Downloading ..." to finish
; then click eval
;

(println "Hello World")
(eval-string-js
    "window.prompt('Enter some text:','')")
     </textarea><br/>
     <input type="file" class="button" id="files" name="load" disabled /><br/>
    </div>

    <div class="two-column" id="eval-out">
    Console&nbsp;<input type="button" value="clear" class="button" id="clearoutput"
      onclick="document.getElementById('output').value = '';" disabled />
    &nbsp;&nbsp;&nbsp;&nbsp;
    <input type="button" value="layout" class="button" onclick="switchLayout();" />
    <input type="button" value="info" id="info" class="button"
      onclick="window.open('README.html','MsgWindow');" disabled />
    <input type="button" value="doc" id="doc" class="button"
      onclick="OpenDoc();"/>
    <input type="text" id="keywordtext">
    <textarea  id="output" disabled></textarea><br/>
    <center><font size="-1">Copyright &copy; 2014, <a href="http://newlisp.org">newlisp.org</a>
    </font></center>
    </div>

    <script type='text/javascript'>
<!--
      function resizeTextarea(mode) {
            var nrows;
            if(mode == "two-column") {
                nrows = ((window.innerHeight || document.body.clientHeight)  - 130)  / 18;
                } else {
                nrows = ((window.innerHeight || document.body.clientHeight)  - 180)  / 36;
                }
            document.getElementById('input').rows = nrows;
            document.getElementById('output').rows = nrows;
            }

      function switchLayout() {
            if(document.getElementById('editor').className == "two-column") {
                document.getElementById('editor').className = "one-column";
                document.getElementById('eval-out').className = "one-column";
                resizeTextarea("one-column");
                } else {
                document.getElementById('editor').className = "two-column";
                document.getElementById('eval-out').className = "two-column";
                resizeTextarea("two-column");
                }
            }

      function handleFileSelect(evt) {
            var files = evt.target.files; // FileList object
            var textFile = files[0]

            // file info:
            // textFile.name
            // textFile.type
            // textFile.size
            // textFile.lastModifiedDate[.toLocaleDateString()]

            //Only process text files.
            if (textFile.type.match('image.*')) {
                alert(textFile.type + 'is an invalid file type');
                return;
                }

            var reader = new FileReader();

            // read file asynchronous and fill editor box
            reader.onload = (function(theFile) {
                return function(e) {
                    var editBox = document.getElementById('input');
                    editBox.value = ''; // clear editbox
                    editBox.value = e.target.result;
                    };
                })(textFile);

            // Read in the text file
            reader.readAsText(textFile);
            Module.print('loading: ' + textFile.name + ' - size:', textFile.size + ' bytes');
            }

        function GetSelectedText () {
            var selText = "";
            if (window.getSelection) {  // all browsers, except IE before version 9
                if (document.activeElement &&
                        document.activeElement.tagName.toLowerCase () == "textarea")
                {
                    var text = document.activeElement.value;
                    selText = text.substring (document.activeElement.selectionStart,
                                              document.activeElement.selectionEnd);
                }
                else {
                    var selRange = window.getSelection ();
                    selText = selRange.toString ();
                }
            }
            else {
                if (document.selection.createRange) {       // Internet Explorer
                    var range = document.selection.createRange ();
                    selText = range.text;
                }
            }
            if (selText !== "") {
                document.getElementById('keywordtext').value = selText;
//                alert(selText);
            }
        }

        function OpenDoc () {
           if (document.getElementById('keywordtext').value.trim() !== "") {
            window.open('http://www.newlisp.org/downloads/newlisp_manual.html#'+document.getElementById('keywordtext').value.trim(),'MsgWindow');
           }
           else {
            window.open('http://www.newlisp.org/downloads/manual_frame.html','MsgWindow');
           }
        }

      document.getElementById('files').addEventListener('change', handleFileSelect, false);

      // EMSCRIPTEN loading newlisp-js-lib.js showing progress of downloading
      // importing newlispEvaklStr and output to the console
      var Module = {
        preRun: [],
        postRun: [(function() {
          newlispEvalStr = Module.cwrap('newlispEvalStr', 'number', ['string']); })],
        print: (function() {
          var element = document.getElementById('output');
          element.value = ''; // clear browser cache
          return function(text) {
            text = Array.prototype.slice.call(arguments).join(' ');
            element.value += text + "n";
            element.scrollTop = 99999; // focus on bottom
          };
        })(),
        printErr: function(text) {
          text = Array.prototype.slice.call(arguments).join(' ');
          console.log(text);
        },
        setStatus: function(text) {
          if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
          if (text === Module.setStatus.text) return;
          var m = text.match(/([^(]+)((d+(.d+)?)/(d+))/);
          var now = Date.now();
          if (m && now - Date.now() < 30) return; // if progress update, skip it if too soon
          var statusElement = document.getElementById('status');
          var progressElement = document.getElementById('progress');
          if (m) {
            text = m[1];
            progressElement.value = parseInt(m[2])*100;
            progressElement.max = parseInt(m[4])*100;
            progressElement.hidden = false;
          } else {
            progressElement.value = null;
            progressElement.max = null;
            progressElement.hidden = true;
            document.getElementById('evalinput').disabled = false;
            document.getElementById('evalinputjs').disabled = false;
            document.getElementById('clearinput').disabled = false;
            document.getElementById('clearoutput').disabled = false;
            document.getElementById('input').disabled = false;
            document.getElementById('output').disabled = false;
            document.getElementById('files').disabled = false;
            document.getElementById('info').disabled = false;
            resizeTextarea("two-column");
          }
          statusElement.innerHTML = text;
        },
        totalDependencies: 0,
        monitorRunDependencies: function(left) {
          this.totalDependencies = Math.max(this.totalDependencies, left);
          Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) +
                        '/' + this.totalDependencies + ')' : 'All downloads complete.');
        }
      };
      Module.setStatus('Downloading...');
-->
    </script>

    <script async type="text/javascript" src="newlisp-js-lib.js"></script>
  </body>
</html>

<!-- eof -->
Hans-Peter