swahili-lang
Version:
A new programming language with semantics borrowed from the Swahili language to help teach programming concepts to Swahili speaking students.
157 lines (137 loc) • 4.24 kB
JavaScript
const args = process.argv.slice(2);
const colors = require('colors');
const readline = require('readline');
const info = require('../package.json');
const print = require('./utils/print');
const run = require('./interpreter/run');
const checkFile = require('./utils/checkFile');
/** set up terminal interface */
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
/**
* Print the output to the console (or not)
* @param {*} result program output
* @param {*} error any errors that occurred
* @param {Boolean} visualOutput whether to show implicit program output
*/
function handleOutput(result, error, visualOutput = false) {
if (error) {
print(colors.red(error.toString()), true);
} else if (result) {
let output = result;
if (result.elements.length === 1) {
output = result.elements[0];
}
if (visualOutput) print(output, true);
}
}
/** Prompt user for input in the terminal */
function getInput() {
rl.question(`${colors.brightMagenta('swahili')} > `, (text) => {
if (text) {
// handle input
const [result, error, callbackQueue] = run('<stdin>', text);
handleOutput(result, error, true);
startEventLoop(callbackQueue);
} else {
// keep prompting until they manually terminate the process
getInput();
}
});
}
// server status
let serverRunnning = false;
let serverCallback = null;
// exit event handler
rl.on('SIGINT', () => {
print(''); // empty line before output
if (serverRunnning) {
serverCallback.serverRef.close();
} else {
print('Kwaheri Mwanaprogramu!', true);
process.exit(0);
}
});
// help info
const printHelp = () => {
print('Usage: swahili <option> <filename>', true);
print('where <option> is one of:');
print('-h, --help');
print(' Print this help', true);
print('-l, --load');
print(' Load a script at <filename> and run Swahili REPL', true);
print('-v, --version');
print(' Print the installed version of swahili-lang', true);
print(
'swahili [-l|--load] <filename>\tScript at <filename> will be executed and loaded into the current context'
);
print(
'swahili <filename>\tScript at <filename> will be executed and the program will exit'
);
print('swahili\t\t\tRun Swahili REPL');
};
/**
* monitors the callback queue and terminates the program or prompts for input when the queue is empty
* @param {[]} queue the callback queue
* @param {boolean} preRepl whether the watch is being called before the repl starts or not
* @param {boolean} loadIn whether the script being watched is a load-in or not
*/
function startEventLoop(queue, preRepl = false, loadIn = false) {
let interval = setInterval(() => {
let q = queue ? queue.filter((tm) => !tm._destroyed) : [];
serverCallback = q.find((i) => i.isServer === true);
serverRunnning = !!serverCallback;
if (!q.length) {
clearInterval(interval);
if (preRepl && !loadIn) {
process.exit(0);
} else {
if (loadIn)
print(
`Script ${colors.green(
'"' + fileName + '"'
)} was successfully loaded.`,
true
);
getInput();
}
}
}, 1);
}
let fileName;
let load = false;
if (args.length) {
fileName = args[0];
let script = null;
if (['-v', '--version'].includes(args[0])) {
print(colors.brightMagenta(`Swahili v${info.version}`));
process.exit(0);
} else if (['-h', '--help'].includes(args[0])) {
printHelp();
process.exit(0);
} else if (['-l', '--load'].includes(args[0])) {
load = true;
} else if (args[0].startsWith('-')) {
print(colors.red(`${args[0]} is not a valid option`), true);
printHelp();
process.exit(1);
}
try {
if (load) fileName = args[1];
[fileName, script] = checkFile(fileName, true);
} catch (err) {
let error = `Failed to load script "${fileName}"\n` + err.toString();
handleOutput(null, error);
process.exit(1);
}
// process the file
const [result, error, callbackQueue] = run(fileName, script, !load);
handleOutput(result, error);
startEventLoop(callbackQueue, true, load);
} else {
console.clear();
getInput();
}