Tutorial for local js debugging / Tip for easy readline() emulation

This information could be useful to beginner and intermediate programmers so I decided to share it with you.

Scenario:

Sometimes with more complex problems it is useful to switch from coding directly in the browser IDE to running the code locally.

One of the best reasons of doing so is arguably the ability to debug your program, which is of course not possible on server side run code.
When I say debug your code I mean not simply printing out statements and figuring out what is wrong but using breakpoints and inspecting the states on a step by step basis.

We are going to use javascript in this example for simplicity but this should be possible in any language. Some requiring more initial effort than others, depending on whether or not you get generators out of the box, but it’s of course possible however not too trivial to roll your own.

So as a first step we’ve just slapped a copypasta of our code in the browser IDE between two tags in a local file and saved it, then loaded the file in the browser.

Problem:

The first problem is the function “print”, but this is easily solved by aliasing it to console.log, as well as printErr:

const print = console.log.bind(console);
const printErr = print;

Now the real “problem” is that readline() function.
We could just replace each readline() call with hardcoded strings, which is what I’ve been doing initially.
But I got tired of this quickly and there is of course a better way:
Generators.

Explaining generator functions is out of the scope of this tutorial and there already exists enough information to be found in the webs.
In a nutshell they can halt at arbitrary points after "yield"ing a value and return that value to the caller as normal functions would, but they persist after returning that value and keep track of the internal state and context and where the execution halted so they can carry on when they are called the next time. If that sounds a bit like an iterator to you then thumbs up! Generators are iterators that iterate over values which can be generated on the fly.

Solution

Cutting to the chase, here is the required code to emulate readline (Example input from the Hidden word puzzle)

function* __readline(){
	let lines = `2
		BAC
		BOB
		3 3
		BAC
		BOB
		RED`.split(/\n\s*/)
	while(lines.length) yield lines.shift()
}
const _readline = __readline();
function readline(){
	return _readline.next().value
}

Now on the first readline call, 2 is returned, then BAC, then BOB, then 3 3, and so on until there is no line left in the array.

The nice thing about this is that now all we need to do for new tests is to update the lines variable with a new block of lines or array, which can easily be extracted for copy+paste from the browser IDE with a few lines of code and also once finished the whole thing can be used 1:1 minus the monkey patching stuff and it will work without requiring any post-editing.

Let me know if that was useful, you spot any typos or you have any other comments, cheers.

3 Likes

The real problem comes when you want to use a multiplayer javascript AI with a local arena like brutaltester (or any other local arena). You have to read from the standard input.

In NodeJS, reading the standard input is asynchronous. So you can’t expect to code a readline function easily. The console.log in NodeJS is asynchronous too.

In the end, the solution is simple. Do not use NodeJS to do that :smiley: Just use SpiderMonkey.

You can’t use readSync on stdin?

That doesn’t resolve the console.log asynchronous problem :frowning:
And last time i tried, reading stdin with a readSync does not work.

But switching to SpiderMonkey resolve everything (and you don’t have to recode print and readline). So i didn’t search for long.

For undelayed input it should definitely work with sync.

node -e 'console.log(require("fs").readFileSync("/dev/stdin", "utf8"))' <<< $'hello\nworld'

But this is a different issue. What my original post is about is much simpler, it’s just about having a comfortable way to import puzzles to run in the local browser so you can use “debugger;” and don’t have to change any code around between the server and local environment, no node or other external javascript runtime/engine needed.

Will you tell us more about setting up SpiderMonkey in the context of Bot challenges?

I got fairly excited with running the code locally until I faced issues you describe with node’s line reading approach. Unfortunately it doesn’t look as trivial to setup SpiderMonkey as it was to setup the brutal-test branch to run with Eclipse!

i modified the code so that it actually reads from stdin.

const fs = require('fs');
function* __readline() {
    let lines = fs.readFileSync(0).toString().split(/\n/);
    while(lines.length) yield lines.shift();
}
const _readline = __readline();
function readline() {
    return _readline.next().value;
}

readline() returns one line at a time. but stdin is read by fs.readFileSync(0) all at once.

for the practice puzzles with just a one time input this should work well. for puzzles with input per turn this should not work at all. i tried but still do not understand how to read stdin line by line in node.js. i do not have much experience in node.js yet.

here is more info: https://stackoverflow.com/questions/20086849/how-to-read-from-stdin-line-by-line-in-node

How to handle when there are some spaces at the front (or even at the end) of some input lines, and we want to retain these spaces?

Nice one. I tried it with your code, worked for me:

const fs = require('fs');
function* __readline() {
    let lines = fs.readFileSync(0).toString().split(/\n/);
    while(lines.length) yield lines.shift();
    return false;
}
const _readline = __readline();
function readline() {
    return _readline.next().value;
}

let line, nr = 0;
while((line = readline()) !== false){
    if(line) console.log(nr++, line)    
}

And then:

$ node sync_read_stdin.js <<< $’ foo \n bar ’
0 ’ foo ’
1 ’ bar ’

Edit - I realised your issue is with the runs.
To have multiple runs i’d just have another delimiter for the runs. Or would that not solve your problem? Something like:

function* __readline() {
    let runs = fs.readFileSync(0).toString().split(/\n\n/);
    let lines = runs.map(s => s.split(/\n/));
    while(lines.length) yield lines.shift(); //or however you prefer to receive your run inputs
    return false;
}

$ node sync_read_stdin.js <<< $‘foo1 \n bar1 \n\n foo2 \n bar2’
0 [ 'foo1 ', ’ bar1 ’ ]
1 [ ’ foo2 ‘, ’ bar2’, ‘’ ]

(BTW those are single quotes, the code block didnt work and the quote block replaced the single quotes)

Should retain spaces, see post above. It’s eating your spaces up?