Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
MiguelEXE committed Oct 5, 2022
1 parent 0efbb25 commit af322d1
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
yarn*
build/
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build/
yarn*
node_modules/
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Basic.JS

A [BASIC](https://en.wikipedia.org/wiki/BASIC) parser and interpreter

Not finished
171 changes: 171 additions & 0 deletions interpreter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
process.title = "Basic.JS";
const {parse_line,parse_line2} = require("./parser.js");
const readline = require("readline-sync");
const repl = require("repl");
const code = [],variables = {},functions = {
ABS(args){
const val = Math.abs(replace_args(args)[0]);
if(isNaN(val)) throw new TypeError("INVALID VALUE");
return val;
},
CHAR(args){
const parsed_args = replace_args(args);
let str = "";
for(const value of parsed_args){
if(typeof value != "number") throw new TypeError("INVALID VALUE");
if(value < 0) throw new RangeError("VALUE < 0");
str += String.fromCharCode(value);
}
return str;
},
ADD(args){
const parsed_args = replace_args(args);
if(typeof parsed_args[0] != "number") throw new TypeError("INVALID VALUE");
if(typeof parsed_args[1] != "number") throw new TypeError("INVALID VALUE");
return parsed_args[0] + parsed_args[1];
},
SUB(args){
const parsed_args = replace_args(args);
if(typeof parsed_args[0] != "number") throw new TypeError("INVALID VALUE");
if(typeof parsed_args[1] != "number") throw new TypeError("INVALID VALUE");
return parsed_args[0] - parsed_args[1];
}
};
/**
*
* @param {any[]} args
* @returns {string[] | number[]}
*/
function replace_args(args){
let newArgs = [];
for(const val of args){
if(typeof val == "object"){
if(val.variable !== undefined){
newArgs.push(variables[val.variable]);
}else{
newArgs.push(functions[val.functionName](val.arguments));
}
}else{
newArgs.push(val);
}
}
return newArgs;
}
let lineNumber = 10,interval,end,codeStr = {};
function toCode(str){
const parsed = parse_line(str);
code.push({line: parsed.line, instruction: parsed.instruction, arguments: parsed.arguments});
}
const instructions = {
LET(args){
variables[args[0]] = args[1];
},
GOTO(arg){
lineNumber = arg;
},
END(){
if(!end){
return process.exit(0);
}
clearInterval(interval);
end?.();
end = undefined;
},
LIST(){
for(const key in codeStr){
console.log(`${key}${codeStr[key]}`);
}
},
PRINT(args){
if(!args) throw new TypeError("NO ARGUMENTS");
const pargs = replace_args(args);
for(const arg of pargs){
if(arg === undefined) throw new TypeError("EMPTY VARIABLE");
process.stdout.write(arg.toString());
}
console.log();
},
HELP(){
console.log(`LET KEY = VAL\nGOTO LINENUM\nEND\nLIST\nPRINT ...MESSAGE;\nHELP\nREM ...\nINPUT MSG;VAR...;\nRUN\nEDITOR\nASM\n!\nIF CHECK1 = CHECK2 THEN COMMAND\n\nADD(INT,INT)\nSUB(INT,INT)\nCHAR(INT...)\nABS(INT)`);
},
REM(){},
INPUT(args){
if(!args) throw new TypeError("NO ARGUMENTS");
for(const arg of args){
if(typeof arg != "object"){
process.stdout.write(arg);
}else{
const val = readline.question("");
variables[arg.variable] = val;
}
}
},
RUN(){
return new Promise(r => {
lineNumber = code[0]?.line;
if(!lineNumber){
console.log("NO CODE.");
return r();
}
end = r;
interval = setInterval(step,0);
});
},
async EDITOR(){
await new Promise(async r => {
while(true){
const val = readline.question("EDITOR) ");
if(val === "DONE") return r();
try{
const parsed = parse_line(val);
codeStr[parsed.line] = val.slice(parsed.lineOffset);
}catch(e){
console.log(e.message.toUpperCase());
}
}
});
for(const key in codeStr){
console.log(`${key}${codeStr[key]}`);
toCode(`${key}${codeStr[key]}`);
}
console.log("READY.");
},
ASM(){
return new Promise(r => {
const server = repl.start();
server.once("exit", () => r());
});
},
IF(args){

}
};
instructions["!"] = instructions.ASM;
async function step(){
const index = code.findIndex(c => c.line == lineNumber);
const instruction = code[index];
const nextInstruction = code[index+1];
lineNumber = nextInstruction ? nextInstruction.line : lineNumber;
await instructions[instruction.instruction](instruction.arguments);
if(code.findIndex(c => c.line == lineNumber) < 0){
instructions.END();
}
}

async function runOne(str){
const instruction = parse_line2(str);
await instructions[instruction.instruction](instruction.arguments);
}

console.log("BASIC.JS V1.0.0");
(async function(){
while(true){
const val = readline.question(") ");
if(!val) continue;
try{
await runOne(val);
}catch(e){
console.log(e.message.toUpperCase());
}
}
})();
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "basic.js",
"version": "1.0.0",
"main": "parser.js",
"license": "MIT",
"bin": "interpreter.js",
"author": {
"name": "TeamCM",
"url": "https://github.com/TeamCM"
},
"devDependencies": {
"pkg": "^5.8.0"
},
"optionalDependencies": {
"readline-sync": "^1.4.10"
},
"scripts": {
"build": "pkg . --targets node16-win-x64,node16-linux-x64 --out-path build"
},
"homepage": "https://github.com/TeamCM/Basic.JS"
}
154 changes: 154 additions & 0 deletions parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const functions = [
"ADD","SUB","EXP","CHAR"
];
/**
* @param {String} str
* @returns {Array<number, any>}
*/
function parseByType(str){
let index = 0;
let val;
str = str.trim();
if(str[0] == "\""){
index++; // string open
val = "";
let backslashFound = false;
while((str[index] != "\"") || backslashFound){
if(str[index] === undefined) break; // avoid while true bugs
if(str[index] == "\\"){
backslashFound = true;
}else{
val += str[index];
}
index++;
}
index++; // string close
}else if(str.trimStart()[0] == "-" || parseInt(str.trimStart()[0])){
val = str.trimStart()[0] == "-" ? "-0" : "0";
if(str.trimStart()[0] == "-")
index = str.indexOf("-") + 1;
while(true){
let newVal = val + str[index++];
if(isNaN(Number(newVal))) break;
else{
val = newVal;
}
}
val = parseInt(val);
index--;
}else{
if(str[index] == ")") return;
val = {variable:""};
while(str[index] && str[index] != "("){
val.variable += str[index++];
}
if(str[index] == "("){
index++;
val.functionName = val.variable;
delete val.variable;
val.arguments = [];
while(str[index] != ")"){
let [i,v] = parseByType(str.slice(index));
index += i;
while(str[index] == " ") index++; // remove spaces
if(v.variable) index--; // fix index
let sep = str[index];
if(sep === ")"){
index++;
val.arguments.push(v);
break;
}
if(sep === ","){
index++;
val.arguments.push(v);
}else throw new SyntaxError("NO SEP");
while(str[index] == " ") index++; // remove spaces
}
}
}
//console.log(val.arguments);
return [index,val];
}
const keywordsParser = {
LET(args){
const [key,value] = args.split("=");
const tkey = key.trim();
const tval = parseByType(value.trim());
return {key: tkey, value: tval};
},
GOTO(args){
return parseInt(args);
},
PRINT(args){
let index = 0;
let arg = [];
while(args[index]){
let [i,val] = parseByType(args.slice(index));
index += i;

while(args[index] == " ") index++; // remove spaces
let sep = args[index];
if(!sep){
arg.push(val);
return arg;
}
if(sep == ";" || sep == ","){
index++;
arg.push(val);
}else throw SyntaxError("NO SEP");
while(args[index] == " ") index++; // remove spaces
}
}
};
keywordsParser["!"] = keywordsParser.ASM = keywordsParser.EDITOR = keywordsParser.RUN = keywordsParser.REM = keywordsParser.HELP = keywordsParser.LIST = keywordsParser.END = function(){}
keywordsParser.INPUT = keywordsParser.PRINT;
/**
*
* @param {String} args
*/
keywordsParser.IF = function(args){
const [condition,...command] = args.split("THEN");
const realCommand = command.join("THEN");
const [check1, check2] = condition.split("=");

return {command: parse_line2(realCommand.trimStart()), confition: {
check1: parseByType(check1.trimEnd())[1], check2:parseByType(check2.trimStart())[1]
}};
}


/**
* @param {String} str
* @returns {{line: number, instruction: string, arguments: any, lineOffset: number}}
*/
function parse_line(str){
const [instructionLine, instruction, ...arguments] = str.split(" ");
const line = parseInt(instructionLine);
const parserFun = keywordsParser[instruction];
if(!parserFun){
throw new SyntaxError("INVAL_INSTRUCTION");
}
if(!line) return;
return {
line,
instruction,
arguments: parserFun(arguments.join(" ")),
lineOffset: str.indexOf(" ")
};
}
/**
* @param {String} str
* @returns {{instruction: string, arguments: any}}
*/
function parse_line2(str){
const [instruction, ...arguments] = str.split(" ");
const parserFun = keywordsParser[instruction];
if(!parserFun){
throw new SyntaxError("INVAL_INSTRUCTION");
}
return {
instruction,
arguments: parserFun(arguments.join(" "))
};
}
module.exports = {parse_line,parse_line2};

0 comments on commit af322d1

Please sign in to comment.