Skip to content

Commit af322d1

Browse files
committed
First commit
1 parent 0efbb25 commit af322d1

6 files changed

Lines changed: 357 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
yarn*
3+
build/

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/
2+
yarn*
3+
node_modules/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Basic.JS
2+
3+
A [BASIC](https://en.wikipedia.org/wiki/BASIC) parser and interpreter
4+
5+
Not finished

interpreter.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
process.title = "Basic.JS";
2+
const {parse_line,parse_line2} = require("./parser.js");
3+
const readline = require("readline-sync");
4+
const repl = require("repl");
5+
const code = [],variables = {},functions = {
6+
ABS(args){
7+
const val = Math.abs(replace_args(args)[0]);
8+
if(isNaN(val)) throw new TypeError("INVALID VALUE");
9+
return val;
10+
},
11+
CHAR(args){
12+
const parsed_args = replace_args(args);
13+
let str = "";
14+
for(const value of parsed_args){
15+
if(typeof value != "number") throw new TypeError("INVALID VALUE");
16+
if(value < 0) throw new RangeError("VALUE < 0");
17+
str += String.fromCharCode(value);
18+
}
19+
return str;
20+
},
21+
ADD(args){
22+
const parsed_args = replace_args(args);
23+
if(typeof parsed_args[0] != "number") throw new TypeError("INVALID VALUE");
24+
if(typeof parsed_args[1] != "number") throw new TypeError("INVALID VALUE");
25+
return parsed_args[0] + parsed_args[1];
26+
},
27+
SUB(args){
28+
const parsed_args = replace_args(args);
29+
if(typeof parsed_args[0] != "number") throw new TypeError("INVALID VALUE");
30+
if(typeof parsed_args[1] != "number") throw new TypeError("INVALID VALUE");
31+
return parsed_args[0] - parsed_args[1];
32+
}
33+
};
34+
/**
35+
*
36+
* @param {any[]} args
37+
* @returns {string[] | number[]}
38+
*/
39+
function replace_args(args){
40+
let newArgs = [];
41+
for(const val of args){
42+
if(typeof val == "object"){
43+
if(val.variable !== undefined){
44+
newArgs.push(variables[val.variable]);
45+
}else{
46+
newArgs.push(functions[val.functionName](val.arguments));
47+
}
48+
}else{
49+
newArgs.push(val);
50+
}
51+
}
52+
return newArgs;
53+
}
54+
let lineNumber = 10,interval,end,codeStr = {};
55+
function toCode(str){
56+
const parsed = parse_line(str);
57+
code.push({line: parsed.line, instruction: parsed.instruction, arguments: parsed.arguments});
58+
}
59+
const instructions = {
60+
LET(args){
61+
variables[args[0]] = args[1];
62+
},
63+
GOTO(arg){
64+
lineNumber = arg;
65+
},
66+
END(){
67+
if(!end){
68+
return process.exit(0);
69+
}
70+
clearInterval(interval);
71+
end?.();
72+
end = undefined;
73+
},
74+
LIST(){
75+
for(const key in codeStr){
76+
console.log(`${key}${codeStr[key]}`);
77+
}
78+
},
79+
PRINT(args){
80+
if(!args) throw new TypeError("NO ARGUMENTS");
81+
const pargs = replace_args(args);
82+
for(const arg of pargs){
83+
if(arg === undefined) throw new TypeError("EMPTY VARIABLE");
84+
process.stdout.write(arg.toString());
85+
}
86+
console.log();
87+
},
88+
HELP(){
89+
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)`);
90+
},
91+
REM(){},
92+
INPUT(args){
93+
if(!args) throw new TypeError("NO ARGUMENTS");
94+
for(const arg of args){
95+
if(typeof arg != "object"){
96+
process.stdout.write(arg);
97+
}else{
98+
const val = readline.question("");
99+
variables[arg.variable] = val;
100+
}
101+
}
102+
},
103+
RUN(){
104+
return new Promise(r => {
105+
lineNumber = code[0]?.line;
106+
if(!lineNumber){
107+
console.log("NO CODE.");
108+
return r();
109+
}
110+
end = r;
111+
interval = setInterval(step,0);
112+
});
113+
},
114+
async EDITOR(){
115+
await new Promise(async r => {
116+
while(true){
117+
const val = readline.question("EDITOR) ");
118+
if(val === "DONE") return r();
119+
try{
120+
const parsed = parse_line(val);
121+
codeStr[parsed.line] = val.slice(parsed.lineOffset);
122+
}catch(e){
123+
console.log(e.message.toUpperCase());
124+
}
125+
}
126+
});
127+
for(const key in codeStr){
128+
console.log(`${key}${codeStr[key]}`);
129+
toCode(`${key}${codeStr[key]}`);
130+
}
131+
console.log("READY.");
132+
},
133+
ASM(){
134+
return new Promise(r => {
135+
const server = repl.start();
136+
server.once("exit", () => r());
137+
});
138+
},
139+
IF(args){
140+
141+
}
142+
};
143+
instructions["!"] = instructions.ASM;
144+
async function step(){
145+
const index = code.findIndex(c => c.line == lineNumber);
146+
const instruction = code[index];
147+
const nextInstruction = code[index+1];
148+
lineNumber = nextInstruction ? nextInstruction.line : lineNumber;
149+
await instructions[instruction.instruction](instruction.arguments);
150+
if(code.findIndex(c => c.line == lineNumber) < 0){
151+
instructions.END();
152+
}
153+
}
154+
155+
async function runOne(str){
156+
const instruction = parse_line2(str);
157+
await instructions[instruction.instruction](instruction.arguments);
158+
}
159+
160+
console.log("BASIC.JS V1.0.0");
161+
(async function(){
162+
while(true){
163+
const val = readline.question(") ");
164+
if(!val) continue;
165+
try{
166+
await runOne(val);
167+
}catch(e){
168+
console.log(e.message.toUpperCase());
169+
}
170+
}
171+
})();

package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "basic.js",
3+
"version": "1.0.0",
4+
"main": "parser.js",
5+
"license": "MIT",
6+
"bin": "interpreter.js",
7+
"author": {
8+
"name": "TeamCM",
9+
"url": "https://github.com/TeamCM"
10+
},
11+
"devDependencies": {
12+
"pkg": "^5.8.0"
13+
},
14+
"optionalDependencies": {
15+
"readline-sync": "^1.4.10"
16+
},
17+
"scripts": {
18+
"build": "pkg . --targets node16-win-x64,node16-linux-x64 --out-path build"
19+
},
20+
"homepage": "https://github.com/TeamCM/Basic.JS"
21+
}

parser.js

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
const functions = [
2+
"ADD","SUB","EXP","CHAR"
3+
];
4+
/**
5+
* @param {String} str
6+
* @returns {Array<number, any>}
7+
*/
8+
function parseByType(str){
9+
let index = 0;
10+
let val;
11+
str = str.trim();
12+
if(str[0] == "\""){
13+
index++; // string open
14+
val = "";
15+
let backslashFound = false;
16+
while((str[index] != "\"") || backslashFound){
17+
if(str[index] === undefined) break; // avoid while true bugs
18+
if(str[index] == "\\"){
19+
backslashFound = true;
20+
}else{
21+
val += str[index];
22+
}
23+
index++;
24+
}
25+
index++; // string close
26+
}else if(str.trimStart()[0] == "-" || parseInt(str.trimStart()[0])){
27+
val = str.trimStart()[0] == "-" ? "-0" : "0";
28+
if(str.trimStart()[0] == "-")
29+
index = str.indexOf("-") + 1;
30+
while(true){
31+
let newVal = val + str[index++];
32+
if(isNaN(Number(newVal))) break;
33+
else{
34+
val = newVal;
35+
}
36+
}
37+
val = parseInt(val);
38+
index--;
39+
}else{
40+
if(str[index] == ")") return;
41+
val = {variable:""};
42+
while(str[index] && str[index] != "("){
43+
val.variable += str[index++];
44+
}
45+
if(str[index] == "("){
46+
index++;
47+
val.functionName = val.variable;
48+
delete val.variable;
49+
val.arguments = [];
50+
while(str[index] != ")"){
51+
let [i,v] = parseByType(str.slice(index));
52+
index += i;
53+
while(str[index] == " ") index++; // remove spaces
54+
if(v.variable) index--; // fix index
55+
let sep = str[index];
56+
if(sep === ")"){
57+
index++;
58+
val.arguments.push(v);
59+
break;
60+
}
61+
if(sep === ","){
62+
index++;
63+
val.arguments.push(v);
64+
}else throw new SyntaxError("NO SEP");
65+
while(str[index] == " ") index++; // remove spaces
66+
}
67+
}
68+
}
69+
//console.log(val.arguments);
70+
return [index,val];
71+
}
72+
const keywordsParser = {
73+
LET(args){
74+
const [key,value] = args.split("=");
75+
const tkey = key.trim();
76+
const tval = parseByType(value.trim());
77+
return {key: tkey, value: tval};
78+
},
79+
GOTO(args){
80+
return parseInt(args);
81+
},
82+
PRINT(args){
83+
let index = 0;
84+
let arg = [];
85+
while(args[index]){
86+
let [i,val] = parseByType(args.slice(index));
87+
index += i;
88+
89+
while(args[index] == " ") index++; // remove spaces
90+
let sep = args[index];
91+
if(!sep){
92+
arg.push(val);
93+
return arg;
94+
}
95+
if(sep == ";" || sep == ","){
96+
index++;
97+
arg.push(val);
98+
}else throw SyntaxError("NO SEP");
99+
while(args[index] == " ") index++; // remove spaces
100+
}
101+
}
102+
};
103+
keywordsParser["!"] = keywordsParser.ASM = keywordsParser.EDITOR = keywordsParser.RUN = keywordsParser.REM = keywordsParser.HELP = keywordsParser.LIST = keywordsParser.END = function(){}
104+
keywordsParser.INPUT = keywordsParser.PRINT;
105+
/**
106+
*
107+
* @param {String} args
108+
*/
109+
keywordsParser.IF = function(args){
110+
const [condition,...command] = args.split("THEN");
111+
const realCommand = command.join("THEN");
112+
const [check1, check2] = condition.split("=");
113+
114+
return {command: parse_line2(realCommand.trimStart()), confition: {
115+
check1: parseByType(check1.trimEnd())[1], check2:parseByType(check2.trimStart())[1]
116+
}};
117+
}
118+
119+
120+
/**
121+
* @param {String} str
122+
* @returns {{line: number, instruction: string, arguments: any, lineOffset: number}}
123+
*/
124+
function parse_line(str){
125+
const [instructionLine, instruction, ...arguments] = str.split(" ");
126+
const line = parseInt(instructionLine);
127+
const parserFun = keywordsParser[instruction];
128+
if(!parserFun){
129+
throw new SyntaxError("INVAL_INSTRUCTION");
130+
}
131+
if(!line) return;
132+
return {
133+
line,
134+
instruction,
135+
arguments: parserFun(arguments.join(" ")),
136+
lineOffset: str.indexOf(" ")
137+
};
138+
}
139+
/**
140+
* @param {String} str
141+
* @returns {{instruction: string, arguments: any}}
142+
*/
143+
function parse_line2(str){
144+
const [instruction, ...arguments] = str.split(" ");
145+
const parserFun = keywordsParser[instruction];
146+
if(!parserFun){
147+
throw new SyntaxError("INVAL_INSTRUCTION");
148+
}
149+
return {
150+
instruction,
151+
arguments: parserFun(arguments.join(" "))
152+
};
153+
}
154+
module.exports = {parse_line,parse_line2};

0 commit comments

Comments
 (0)