hindimejs
Version:
A programming language with a desi twist. It uses commonly used hindi words for commands.🫡
402 lines (368 loc) • 12.4 kB
JavaScript
const {
expressionEvaluator,
variableResolver,
checkNameToken,
} = require("../lib/index.js");
/* =========================
* Interpreter (per line)
* ========================= */
function lineInterpreter(tokens, env, functionManager) {
if (tokens.length === 0) return; // empty/comment-only line
const head = tokens[0];
// helper: evaluate a parentheses-style function call appearing as an expression primary
const evaluateFuncCall = (startIndex) => {
const funcName = tokens[startIndex].value;
// Collect the tokens inside the immediate parentheses after FUNC_CALL
// tokens[startIndex + 1] should be LPAREN
let i = startIndex + 1;
if (!tokens[i] || tokens[i].type !== "LPAREN") {
throw new SyntaxError("Function call expected '('");
}
i++; // move past LPAREN
const inner = [];
let depth = 1;
for (; i < tokens.length; i++) {
const t = tokens[i];
if (t.type === "LPAREN") depth++;
else if (t.type === "RPAREN") depth--;
if (depth === 0) break; // matched the original LPAREN
inner.push(t);
}
if (depth !== 0) throw new SyntaxError("Unterminated function call");
// Split inner tokens by top-level commas into argument expressions
const args = [];
if (inner.length > 0) {
let current = [];
let nest = 0;
for (const t of inner) {
if (t.type === "LPAREN" || t.type === "LBRACK") nest++;
if (t.type === "RPAREN" || t.type === "RBRACK") nest--;
if (t.type === "COMMA" && nest === 0) {
if (current.length > 0) args.push(expressionEvaluator(current, env));
current = [];
} else {
current.push(t);
}
}
if (current.length > 0) args.push(expressionEvaluator(current, env));
}
return functionManager.executeFunction(
funcName,
args,
env,
lineInterpreter
);
};
// Handle function calls with parentheses syntax: function_name(...)
if (head.type === "FUNC_CALL") {
const funcName = head.value;
const args = [];
for (let i = 1; i < tokens.length; i++) {
const token = tokens[i];
if (token.type === "NUMBER" || token.type === "STRING") {
args.push(token.value);
} else if (token.type === "WORD") {
args.push(variableResolver(env, token.value));
}
// ignore LPAREN, RPAREN, COMMA
}
functionManager.executeFunction(funcName, args, env, lineInterpreter);
return;
}
// Infix assignment: <name> HAI <expr>
if (
tokens.length >= 3 &&
tokens[0].type === "WORD" &&
tokens[1].type === "WORD" &&
tokens[1].value.toLowerCase() === "hai"
) {
const variableName = checkNameToken("HAI needs a variable name", tokens[0]);
const exprTokens = tokens.slice(2);
let val;
if (exprTokens[0] && exprTokens[0].type === "FUNC_CALL") {
// Support: x HAI someFunc(...)
// Evaluate the function call beginning at index 2 in the full tokens array
val = evaluateFuncCall(2);
} else {
val = expressionEvaluator(exprTokens, env);
}
env.vars[variableName] = val;
return;
}
if (head.type !== "WORD")
throw new SyntaxError("Command must start with a word");
const cmdRaw = head.value;
const cmd = cmdRaw.toUpperCase();
// New Hindi aliases mapping
const is = cmdRaw.toLowerCase() === "hai"; // infix assignment requires custom handling
// HAI assignment: <name> HAI <expr>
if (is) {
const nameTok = tokens[0];
const haiTok = tokens[1];
if (
!haiTok ||
haiTok.type !== "WORD" ||
haiTok.value.toLowerCase() !== "hai"
) {
throw new SyntaxError("Expected HAI after variable name");
}
const variableName = checkNameToken("HAI needs a variable name", nameTok);
const exprTokens = tokens.slice(2);
if (exprTokens.length === 0) throw new SyntaxError("HAI needs a value");
let val;
if (exprTokens[0] && exprTokens[0].type === "FUNC_CALL") {
val = evaluateFuncCall(2);
} else {
val = expressionEvaluator(exprTokens, env);
}
env.vars[variableName] = val;
return;
}
// Handle BOLO (print)
if (cmdRaw.toLowerCase() === "bolo") {
// Special-case: function call as primary, e.g., BOLO square(9)
if (tokens[1] && tokens[1].type === "FUNC_CALL") {
const funcName = tokens[1].value;
// Collect args between the following LPAREN .. matching RPAREN
const argsTokens = tokens.slice(2); // starts with LPAREN
// Extract the content inside top-level parentheses
let depth = 0;
const inner = [];
for (let i = 0; i < argsTokens.length; i++) {
const t = argsTokens[i];
if (t.type === "LPAREN") {
depth++;
if (depth === 1) continue;
}
if (t.type === "RPAREN") {
depth--;
if (depth === 0) break;
}
if (depth >= 1) inner.push(t);
}
// Split inner by top-level commas to expressions
const args = [];
if (inner.length > 0) {
let current = [];
depth = 0;
for (const t of inner) {
if (t.type === "LPAREN" || t.type === "LBRACK") depth++;
if (t.type === "RPAREN" || t.type === "RBRACK") depth--;
if (t.type === "COMMA" && depth === 0) {
if (current.length > 0)
args.push(expressionEvaluator(current, env));
current = [];
} else {
current.push(t);
}
}
if (current.length > 0) args.push(expressionEvaluator(current, env));
}
const ret = functionManager.executeFunction(
funcName,
args,
env,
lineInterpreter
);
console.log(ret);
return;
}
const val = expressionEvaluator(tokens.slice(1), env);
console.log(val);
return;
}
// Handle RETURN via lotaao
if (cmdRaw.toLowerCase() === "lotaao") {
const returnVal =
tokens.length > 1 ? expressionEvaluator(tokens.slice(1), env) : undefined;
env.returnSignal = returnVal;
return;
}
// Handle kaam_karo remains
switch (cmd) {
case "KAAM_KARO": {
if (tokens.length < 2)
throw new SyntaxError("kaam_karo needs a function name");
const funcName = checkNameToken(
"kaam_karo needs a function name",
tokens[1]
);
const argsExpr = tokens.slice(2);
const hasComma = argsExpr.some((t) => t.type === "COMMA");
const args = [];
if (argsExpr.length > 0) {
if (hasComma) {
// Comma-separated: split by commas and eval each chunk as an expression
let current = [];
for (const t of argsExpr) {
if (t.type === "COMMA") {
if (current.length > 0)
args.push(expressionEvaluator(current, env));
current = [];
} else {
current.push(t);
}
}
if (current.length > 0) args.push(expressionEvaluator(current, env));
} else {
// Whitespace-separated: treat each top-level atom/paren group as an arg
let i = 0;
while (i < argsExpr.length) {
const t = argsExpr[i];
if (t.type === "LPAREN") {
// collect balanced parentheses group
let depth = 0;
const group = [];
while (i < argsExpr.length) {
const g = argsExpr[i++];
group.push(g);
if (g.type === "LPAREN") depth++;
else if (g.type === "RPAREN") {
depth--;
if (depth === 0) break;
}
}
args.push(expressionEvaluator(group, env));
} else {
// single-token argument (WORD/NUMBER/STRING)
args.push(expressionEvaluator([t], env));
i++;
}
}
}
}
functionManager.executeFunction(funcName, args, env, lineInterpreter);
return;
}
case "YAAR": {
// Keep legacy: YAAR <name> <number or expr>
const nameTok = tokens[1];
const variableName = checkNameToken(
"YAAR needs a variable name",
nameTok
);
const exprTokens = tokens.slice(2);
const val = expressionEvaluator(exprTokens, env);
env.vars[variableName] = val;
console.log(`🫡 Yaar, ${variableName} is now ${val}`);
return;
}
case "ADD": {
const nameTok = tokens[1];
const variableName = checkNameToken("ADD needs a variable name", nameTok);
if (!(variableName in env.vars))
throw new ReferenceError(`Unknown variable "${variableName}"`);
const delta = expressionEvaluator(tokens.slice(2), env);
env.vars[variableName] += delta;
console.log(
`${variableName} is now ${env.vars[variableName]} ➕ Add kar diya!`
);
return;
}
case "MINUS": {
const nameTok = tokens[1];
const variableName = checkNameToken(
"MINUS needs a variable name",
nameTok
);
if (!(variableName in env.vars))
throw new ReferenceError(`Unknown variable "${variableName}"`);
const delta = expressionEvaluator(tokens.slice(2), env);
env.vars[variableName] -= delta;
console.log(
`${variableName} is now ${env.vars[variableName]} ➖ Minus kar diya!`
);
return;
}
case "MULTIPLY": {
const nameTok = tokens[1];
const variableName = checkNameToken(
"MULTIPLY needs a variable name",
nameTok
);
if (!(variableName in env.vars))
throw new ReferenceError(`Unknown variable "${variableName}"`);
const mult = expressionEvaluator(tokens.slice(2), env);
env.vars[variableName] *= mult;
console.log(
`${variableName} is now ${env.vars[variableName]} ✖️ Multiply kar diya!`
);
return;
}
case "DIVIDE": {
const nameTok = tokens[1];
const variableName = checkNameToken(
"DIVIDE needs a variable name",
nameTok
);
if (!(variableName in env.vars))
throw new ReferenceError(`Unknown variable "${variableName}"`);
const div = expressionEvaluator(tokens.slice(2), env);
if (div === 0) throw new TypeError("Can't divide by zero, yaar!");
env.vars[variableName] /= div;
console.log(
`${variableName} is now ${env.vars[variableName]} ➗ Divide kar diya!`
);
return;
}
case "PRINT": {
if (tokens.length < 2) throw new SyntaxError("PRINT needs a value");
const val = expressionEvaluator(tokens.slice(1), env);
console.log(val);
return;
}
case "BOLO": {
const val = expressionEvaluator(tokens.slice(1), env);
console.log(val);
return;
}
case "BHEJO": {
// BHEJO <name> [expr]
if (tokens.length < 2) throw new SyntaxError("BHEJO needs a name");
const exportName = checkNameToken("BHEJO needs a name", tokens[1]);
if (!env.exports) env.exports = Object.create(null);
if (!env.exports.__fns) env.exports.__fns = [];
if (tokens.length > 2) {
const value = expressionEvaluator(tokens.slice(2), env);
env.exports[exportName] = value;
return;
}
// No expression: export a variable or a function by name
try {
const value = variableResolver(env, exportName);
env.exports[exportName] = value;
} catch (e) {
// If not a variable, check functions
if (
functionManager &&
functionManager.functions &&
functionManager.functions.has(exportName)
) {
env.exports.__fns.push(exportName);
} else {
throw e;
}
}
return;
}
case "BAS_KAR": {
env.breakSignal = true;
return;
}
case "AGLA": {
env.continueSignal = true;
return;
}
default:
// Allow plain expression statements (e.g., function calls handled earlier)
// Fallback to previous arithmetic commands for backward compatibility
// If expression is just a value, evaluate and ignore
try {
expressionEvaluator(tokens, env);
return;
} catch (e) {
throw new SyntaxError(`Unknown command "${cmd}"`);
}
}
}
module.exports = { lineInterpreter };