2018-01-03 13:28:35 +01:00

440 lines
13 KiB
JavaScript

OPERAND_INVALID = -1;
OPERAND_REG = 0;
OPERAND_MEM_IMM = 1;
OPERAND_MEM_REG = 2;
OPERAND_IMM = 3;
//Remove default syntax checker
editor = ace.edit("editor");
editor.session.setOption("useWorker", false);
//Remove message
editor.$blockScrolling = Infinity;
var MarParserSyntax = {
singleCharRegisters : [
'a', 'b', 'c', 'd', 'x', 'y'
],
doubleCharRegisters : [
'bp', 'sp'
],
doubleOperandInstructions : [
'mov', 'add', 'sub', 'and', 'or', 'test', 'cmp', 'shl', 'shr', 'xor', 'rol', 'ror', 'sal', 'sar',
'xchg', 'rcl', 'rcr'
],
singleOperandInstructions : [
'push', 'mul', 'pop', 'div', 'neg', 'call', 'jnz', 'jg', 'jl', 'jge', 'jle', 'hwi', 'hwq', 'jz',
'js', 'jns', 'ret', 'jmp', 'not', 'jc', 'jnc', 'jo', 'jno'
],
zeroOperandInstructions : [
'ret', 'brk', 'nop'
]
};
var MarParserRegExp = {
allRegisters : new RegExp('^(' +
MarParserSyntax.singleCharRegisters.concat(MarParserSyntax.doubleCharRegisters).join('|')
+ ')$'),
singleCharRegisters : new RegExp('^(' + MarParserSyntax.singleCharRegisters.join('|') + ')$'),
doubleCharRegisters : new RegExp('^(' + MarParserSyntax.doubleCharRegisters.join('|') + ')$'),
allInstructions : new RegExp('\\b(?:' +
MarParserSyntax.doubleOperandInstructions.concat(MarParserSyntax.singleOperandInstructions).concat(MarParserSyntax.zeroOperandInstructions).join('|')
+ ')\\b'),
doubleOperandInstructions : new RegExp('\\b(?:' + MarParserSyntax.doubleOperandInstructions.join('|') + ')\\b'),
singleOperandInstructions : new RegExp('\\b(?:' + MarParserSyntax.singleOperandInstructions.join('|') + ')\\b'),
zeroOperandInstructions : new RegExp('\\b(?:' + MarParserSyntax.zeroOperandInstructions.join('|') + ')\\b'),
};
function removeComment(line) {
if (line.indexOf(";") !== -1) {
return line.substring(0, line.indexOf(";"));
} else {
return line;
}
}
function checkForLabel(line, result) {
line = removeComment(line);
var match;
if ((match = /\b\w*\b:/.exec(line)) !== null) {
result.labels.push(match[0].substring(0, match[0].length - 1));
}
}
function checkForSegmentDeclaration(line) {
var tokens = getTokens(line);
return tokens[0] !== undefined && (tokens[0].toLowerCase() === ".data" || tokens[0].toLowerCase() === ".text");
}
function checkForEQUInstruction(line, result, currentLine) {
var tokens = getTokens(line);
if (line.toLowerCase().indexOf(" equ ") !== -1 || tokens[1] !== undefined && tokens[1].toLowerCase() === "equ") {
//Save as a label
var num = Number(tokens[2]);
if (!isNaN(num) && num === Math.floor(num)) {
result.labels.push(tokens[0]);
return true;
} else {
result.annotations.push({
row: currentLine,
column: 0,
text: "Usage: constant_name EQU immediate_value",
type: "error"
});
return true;
}
} else {
return false;
}
}
function getTokens(line) {
var tokens = line.split(/\s+/);
for (var i = 0; i < tokens.length; i++) {
if (tokens[i] === "") {
tokens.splice(i, 1);
}
}
return tokens;
}
function removeLabel(line) {
return line.replace(/\b\w*\b:/, "");
}
function checkForORGInstruction(line, result, currentLine) {
line = removeComment(line);
line = removeLabel(line);
//Split string
var tokens = getTokens(line);
var mnemonic = tokens[0];
if (mnemonic !== undefined && mnemonic.toLowerCase() === "org") {
console.log(tokens);
if (tokens.length > 1) {
var num = Number(tokens[1]);
if (!isNaN(num) && num === Math.floor(num)) {
return true;
} else {
result.annotations.push({
row: currentLine,
column: 0,
text: "Invalid operand: " + tokens[1],
type: "error"
});
return true
}
}
} else {
return false;
}
}
function parseDWInstruction(line, result, currentLine) {
line = line.trim();
if (line.substr(0, 2).toLowerCase() === "dw") {
var values = line.substr(2, line.length).split(/,(?=(?:[^"]*"[^"]*")*[^"]*$)/, -1);
for (var i = 0; i < values.length; i++) {
values[i] = values[i].trim();
var tokens = getTokens(values[i]);
if (tokens.length === 2 && getOperandType(tokens[0], result) === OPERAND_IMM &&
tokens[1].toLowerCase().startsWith("dup(") && tokens[1].endsWith(")") &&
getOperandType(tokens[1].substring(4, tokens[1].indexOf(")")), result) === OPERAND_IMM) {
// console.log("DUp");
} else if (values[i].startsWith("\"") && values[i].endsWith("\"")) {
//Handle string
} else if (getOperandType(values[i], result) === OPERAND_IMM) {
// console.log("is Imm " + values[i]);
} else {
result.annotations.push({
row: currentLine,
column: 0,
text: "Usage: DW IMM, IMM ...",
type: "error"
});
return true;
}
}
return true;
} else {
return false;
}
}
function getOperandType(text, result) {
text = text.trim();
if (text === "") {
return OPERAND_INVALID;
}
//Check IMM
if (!isNaN(Number(text)) && Number(text) === Math.floor(Number(text)) && text.indexOf("o") === -1
&& text.indexOf("0e") !== 0) {
return OPERAND_IMM;
}
//Check REG
if (MarParserRegExp.allRegisters.test(text.toLowerCase())) {
return OPERAND_REG;
}
//Check Label
for (i = 0; i < result.labels.length; i++) {
if (text === result.labels[i]) {
return OPERAND_IMM;
}
}
//Check MEM_*
if (text.startsWith("[") && text.endsWith("]")) {
text = text.replace("[", "").replace("]", "");
//Check MEM_IMM
if (!isNaN(Number(text)) && Number(text) === Math.floor(Number(text))) {
return OPERAND_MEM_IMM;
}
//Check MEM_Label
for (var i = 0; i < result.labels.length; i++) {
if (text === result.labels[i]) {
return OPERAND_MEM_IMM;
}
}
//Check for MEM_REG (+ x)
var expr = "";
if (MarParserRegExp.doubleCharRegisters.test(text.toLowerCase().substring(0, 2).toLowerCase())) {
//Starts with 2-char register
expr = text.substring(2);
} else if (MarParserRegExp.singleCharRegisters.test(text.toLowerCase().substring(0, 1).toLowerCase())) {
//Starts with 1-char register
expr = text.substring(1);
} else {
return OPERAND_INVALID;
}
if (expr.replace(/\s+/g, '') === "") {
//No displacement specified
return OPERAND_MEM_REG;
}
//Remove white space
expr = expr.replace(/\s+/g, '');
//expr should now look like this: '+1' '-3' '+0x02' '+myLabel'
//Check for label
for (i = 0; i < result.labels.length; i++) {
if (expr.substring(1) === result.labels[i]) {
return OPERAND_MEM_REG;
}
}
//Check for number
if (!isNaN(Number(expr)) && Number(expr) === Math.floor(Number(expr))) {
return OPERAND_MEM_REG;
}
}
return OPERAND_INVALID;
}
function parseInstruction(line, result, currentLine) {
line = removeComment(line);
line = removeLabel(line);
var tokens = getTokens(line);
var mnemonic = tokens[0];
if (mnemonic === undefined || mnemonic === "") {
return; //Line is empty
}
if (!parseDWInstruction(line, result, currentLine)) {
if (MarParserRegExp.allInstructions.test(mnemonic.toLowerCase())) {
if (line.indexOf(",") !== -1) {
//2 Operands
var strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length, line.indexOf(','));
var strO2 = line.substring(line.indexOf(',') + 1).trim();
//Validate operand number
if (!MarParserRegExp.doubleOperandInstructions.test(mnemonic.toLowerCase())) {
result.annotations.push({
row: currentLine,
column: 0,
text: mnemonic + " instruction with 2 operands is illegal",
type: "error"
});
return;
}
//Validate operand type
var o1Type = getOperandType(strO1, result);
var o2Type = getOperandType(strO2, result);
if (o1Type === OPERAND_INVALID) {
result.annotations.push({
row: currentLine,
column: 0,
text: "Invalid operand: " + strO1,
type: "error"
});
return;
}
if (o2Type === OPERAND_INVALID) {
result.annotations.push({
row: currentLine,
column: 0,
text: "Invalid operand: " + strO2,
type: "error"
});
return;
}
//Check for illegal operand combos:
if (o1Type === OPERAND_IMM) {
result.annotations.push({
row: currentLine,
column: 0,
text: "Destination operand can't be an immediate value",
type: "error"
});
}
} else if (tokens.length > 1) {
//1 Operand
strO1 = line.substring(line.indexOf(mnemonic) + mnemonic.length).trim();
//Validate operand number
if (!MarParserRegExp.singleOperandInstructions.test(mnemonic.toLowerCase())) {
result.annotations.push({
row: currentLine,
column: 0,
text: mnemonic + " instruction with 1 operand is illegal",
type: "error"
});
return;
}
//Validate operand type
if (getOperandType(strO1, result) === OPERAND_INVALID) {
result.annotations.push({
row: currentLine,
column: 0,
text: "Invalid operand: " + strO1,
type: "error"
});
}
} else {
//No operand
if (!MarParserRegExp.zeroOperandInstructions.test(mnemonic.toLowerCase())) {
//Validate operand number
result.annotations.push({
row: currentLine,
column: 0,
text: mnemonic + " instruction with no operand is illegal",
type: "error"
});
}
}
} else {
result.annotations.push({
row: currentLine,
column: 0,
text: "Unknown mnemonic: " + mnemonic,
type: "error"
});
}
}
}
function parse() {
var text = ace.edit("editor").getValue();
var lines = text.split("\n");
var result = {
labels: [],
annotations: []
};
//Pass 1 of 2: Save label names
for (var currentLine = 0; currentLine < lines.length; currentLine++) {
checkForLabel(lines[currentLine], result);
}
//Pass 2 of 2: Check instructions
for (currentLine = 0; currentLine < lines.length; currentLine++) {
if (!checkForSegmentDeclaration(lines[currentLine]) &&
!checkForEQUInstruction(lines[currentLine], result, currentLine) &&
!checkForORGInstruction(lines[currentLine], result, currentLine)) {
parseInstruction(lines[currentLine], result, currentLine);
}
}
//Set icons
editor.getSession().setAnnotations(result.annotations);
}
function gameClick() {
document.getElementById("editorBtns").setAttribute("style", "display: none");
document.getElementById("gameBtns").setAttribute("style", "");
}
function editorClick() {
document.getElementById("editorBtns").setAttribute("style", "");
document.getElementById("gameBtns").setAttribute("style", "display: none");
}
editor.on("change", parse);