16

I have a C program that takes one argument (a char array / string) via command line and also reads from stdin. I've compiled it to JavaScript using emscripten. This was successful and I can run it just like the normal C program using node.js:

emcc -O2 translate.c
node translate.js "foo" < bar.txt

As you can see, I'm providing the string "foo" as an argument and the contents of bar.txt as stdin. Now I want this to be a self-contained HTML file.

By changing the output to HTML:

emcc -O2 translate.c -o trans.html

I provide the argument by adding arguments: ['foo'], to the definitions in var Module. This works as expected, the program receives the argument correctly.

Now, how do I provide the stdin input to this program? I don't need to do this dynamically. It would be fine to just declare a string somewhere in the HTML with the required stdin content.

ggorlen
  • 44,755
  • 7
  • 76
  • 106
turbo
  • 1,233
  • 14
  • 36
  • Not very familiar with emscripten, but it looks like [`--embed-file`](http://kripken.github.io/emscripten-site/docs/tools_reference/emcc.html#emcc-embed-file) might fit the bill. – nrabinowitz Oct 02 '15 at 17:05
  • @nrabinowitz Just found a solution. Thanks! – turbo Oct 02 '15 at 17:15
  • 5
    @minxomat You should add the solution as an answer (and accept it as correct), and not as an edit to the question – Alvaro Montoro Oct 02 '15 at 20:18
  • @AlvaroMontoro I'm convinced this is in fact not the "right" solution. I'm not proficient enough at JS or emscripten in general to judge the quality of this approach. – turbo Oct 02 '15 at 20:20
  • @mınxomaτ all the more reason to add it as an answer, so others can vote on it along with all of the other answers... I'm going to remove it but you're welcome to re-post it as an answer if you want. – ggorlen Jul 26 '21 at 06:50

3 Answers3

14

A way would be to use the Emscripten Filesystem API, for example by calling FS.init in the Module preRun function, passing custom functions to be used for standard input, output and error.

var Module = {
  preRun: function() {
    function stdin() {
      // Return ASCII code of character, or null if no input
    }

    function stdout(asciiCode) {
      // Do something with the asciiCode
    }

    function stderr(asciiCode) {
      // Do something with the asciiCode
    }

    FS.init(stdin, stdout, stderr);
  }
};

The functions are quite low-level: they each deal with one character at a time as an ASCII code. If you have strings you want to pass in, you would have to iterate over the characters of the string yourself. I suspect charCodeAt would be helpful. To output strings from stdout or stderr, then I suspect fromCharCode would be helpful.

Example (not very well tested!) implementations using each are below.

var input = "This is from the standard input\n";
var i = 0;
var Module = {
  preRun: function() {
    function stdin() {
      if (i < res.length) {
        var code = input.charCodeAt(i);
        ++i;
        return code;
      } else {
        return null;
      }
    }

    var stdoutBuffer = "";
    function stdout(code) {
      if (code === "\n".charCodeAt(0) && stdoutBuffer !== "") {
        console.log(stdoutBuffer);
        stdoutBuffer = "";
      } else {
        stdoutBuffer += String.fromCharCode(code);
      }
    }

    var stderrBuffer = "";
    function stderr(code) {
      if (code === "\n".charCodeAt(0) && stderrBuffer !== "") {
        console.log(stderrBuffer);
        stderrBuffer = "";
      } else {
        stderrBuffer += String.fromCharCode(code);
      }
    }

    FS.init(stdin, stdout, stderr);
  }
};
Motin
  • 4,853
  • 4
  • 44
  • 51
Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
  • Awesome! I cannot get rid of `ReferenceError: FS is not defined` - https://github.com/kripken/emscripten/issues/6935#issuecomment-410000543 Any idea? – loretoparisi Aug 02 '18 at 17:14
  • I confirmed that this works with the test_stdin.c program provided by emscripten itself. Thanks! – Motin Dec 02 '20 at 10:01
2

Rather than editing the output of Emscripten, you could monkey patch the Window object

window.prompt = function() {
  return 'This will appear to come from standard input';
};

Not wonderful, but I would deem this less of a hack than editing the Emscripten-generated Javascript.

Michal Charemza
  • 25,940
  • 14
  • 98
  • 165
  • 1
    Sorry, but there is no reason for blocking this default function. Editing the input handler is the safer and easier way compared to this. This way I can just use `stdInput = read('input');` to pass the whole input. Check out [here](https://github.com/minxomat/Recall/blob/gh-pages/index.html) (Line 86+) how I call the emscripten script. :) – turbo Oct 12 '15 at 09:37
  • 1
    @minxonat I would argue there is a reason: to keep the build less fragile to changes in the Emscripten generated output. I suspect most projects don't use window.prompt, but do need to handle updates to their dependencies. It's a trade off: you lose the ability to use window.prompt as designed, but gain the code standing a better chance of behaving as updates to Emscripten/its configuration could break automatic patching of its output. Note: the "standing a better chance" is based on my own judgement that patching generated code is fragile: I can offer no evidence to back that up. – Michal Charemza Oct 12 '15 at 12:37
  • 1
    I do believe patching a function from the standard JS API would be even more fragile. – Haroldo_OK Jan 13 '17 at 08:17
  • Your script can save `window.prompt` in another variable, then call that for your prompts and let the original work for wasm without modifying it. I agree with Michal that monkeypatching is better than modifying wasm.js, but it's possible to implement `window.readline` or a custom function, then remove or reorder wasm's `prompt` conditional branch in wasm.js. If `readline` was tested first in `get_char`, then it'd be easy to recommend that. – ggorlen Jul 26 '21 at 15:17
1

According the question "Edit" , I made my function , thx a lot.

Just hope the code below can help someone else.

  1. comment run(); in the end of emscript

    // in my emscript 
    
    // shouldRunNow refers to calling main(), not run().
    var shouldRunNow = true;
    if (Module['noInitialRun']) {
        shouldRunNow = false;
    }
    //run(); // << here
    // {{POST_RUN_ADDITIONS}}
    
  2. result = areaInput(); // As the question mentioned

  3. add the code below in your html file to activate run() in emscript

    <script>
    var message;
    var point = -1;
    function getArea(){
        message = document.getElementById('input').value.split('\n');
    }
    function areaInput(){
        if(point >= message.length - 1){
            return null;
        }
        point += 1;
        return message[point];
    }
    function execEmscript(){
        window.console = {
            log: function(str){
                document.getElementById("output").value += "\n" + str;
            }
        }
        getArea();
        run();
    }
    </script>
    
  4. remember io textareas in your html

    <textarea id="input" cols="80" rows="30"></textarea>

    <textarea id="output" cols="80" rows="30"></textarea>

  5. and a button

    <button onclick="execEmscript();">run</button>

nobodyzxc
  • 21
  • 5