sh.js/interpreter.js

190 lines
3.8 KiB
JavaScript
Raw Normal View History

2022-04-06 19:59:55 +00:00
"use strict";
(function(){
var variables = [];
var functions = [];
var file = "[anonymous]";
function executeCommand(args, std) {
2022-04-06 20:23:41 +00:00
console.log(`args: ${args}, std: ${std}`);
2022-04-06 19:59:55 +00:00
}
function astSequence() {
let commands = [];
return {
add: c => commands.push(c),
execute: (std) => commands.forEach(c => c.execute(std)),
}
}
function astCommand() {
let args = [];
return {
add: a => args.push(a),
2022-04-06 20:23:41 +00:00
size: a => args.length,
2022-04-06 19:59:55 +00:00
execute: (std) => {
let evaluatedArgs = args.map(a => a.evaluate());
executeCommand(evaluatedArgs, std);
},
};
}
2022-04-06 20:23:41 +00:00
function astAssignment(name) {
let value = astValueString("");
return {
setValue: v => { value = v; },
execute: (std) => {
variables[name] = value.evaluate();
}
};
}
2022-04-06 19:59:55 +00:00
function astValueCompound() {
let components = [];
return {
add: c => components.push(c),
evaluate: () => components.map(c => c.evaluate()).join(""),
};
}
function astValueString(str) {
return {
evaluate: () => str,
};
}
function astValueVar(name) {
return {
// add support for multiple arguments in one variable
evaluate: () => variables[name],
}
}
function astValueCommandSubstitution(ast) {
return {
evaluate: () => {
// TODO
},
};
}
function astValueProcessSubstitution(ast) {
return {
evaluate: () => {
// TODO
},
};
}
function panic(line, message) {
throw `${file}: line ${line}: panic: ${message}`;
}
function syntaxError(line, message) {
throw `${file}: line ${line}: syntax error: ${message}`;
}
function parseCommands(content) {
let line = 1;
2022-04-06 20:23:41 +00:00
const PS_INIT = 0;
const PS_COMMENT = -1;
const PS_COMMAND = 1;
const PS_ASSIGN = 2;
2022-04-06 19:59:55 +00:00
let astRoot = astSequence();
let current = null;
let state = PS_INIT;
let buffer = "";
const length = content.length;
for (let i = 0; i < length; i++) {
const c = content[i];
switch(state) {
case PS_INIT:
if (['\n', '\r', ' ', '\t', ';'].includes(c)) {
// continue
} else if (c == '#') {
state = PS_COMMENT;
} else {
current = astCommand();
buffer = c;
state = PS_COMMAND;
}
break;
case PS_COMMENT:
if (c == '\n') {
state = PS_INIT;
}
break;
case PS_COMMAND:
// check for escape and quotes
if (c == ';' || c == '\n') {
if (buffer) {
2022-04-06 20:23:41 +00:00
if (buffer[0] == '$') {
current.add(astValueVar(buffer.substring(1)));
} else {
current.add(astValueString(buffer));
}
2022-04-06 19:59:55 +00:00
buffer = "";
}
astRoot.add(current);
current = null;
state = PS_INIT;
} else if (c == ' ' || c == '\t') {
if (buffer) {
2022-04-06 20:23:41 +00:00
if (buffer[0] == '$') {
current.add(astValueVar(buffer.substring(1)));
} else {
current.add(astValueString(buffer));
}
2022-04-06 19:59:55 +00:00
buffer = "";
}
2022-04-06 20:23:41 +00:00
} else if (c == '=' && current.size() == 0) {
current = astAssignment(buffer);
state = PS_ASSIGN;
buffer = "";
} else {
buffer += c;
}
break;
case PS_ASSIGN:
// check for escape and quotes
if (c == ';' || c == '\n') {
if (buffer[0] == "$") {
current.setValue(astValueVar(buffer.substring(1)));
} else {
current.setValue(astValueString(buffer));
}
buffer = "";
astRoot.add(current);
current = null;
state = PS_INIT;
} else if (c == ' ' || c == '\t') {
// would normale set exported variable for command but we don't support that anyway
// current.setValue(astValueString(buffer));
buffer = "";
current = null;
state = PS_INIT;
2022-04-06 19:59:55 +00:00
} else {
buffer += c;
}
break;
default:
panic(line, "unknown parse state");
break;
}
if (c == '\n') {
line++;
}
}
return astRoot;
}
window.sh = {
parse: parseCommands,
};
})();