// 30.07.2025 working version tested with almost all BASIC command
#include <Arduino.h>
#include <math.h>
// --- Configuration ---
#define STACK_SIZE 100
#define MAX_PROGRAM_LINES 50
#define MAX_LOOP_DEPTH 8
// --- Constants ---
#define PI_CONST 3.141592653589793
#define E_CONST 2.718281828459045
// =================================================================
// Global Variables for BASIC Interpreter
// =================================================================
double variableValues[26];
String programLines[MAX_PROGRAM_LINES];
int lineNumbers[MAX_PROGRAM_LINES];
int programSize = 0;
enum ProgramState { IDLE, RUNNING, AWAIT_INPUT };
ProgramState programState = IDLE;
int pc = 0;
int awaitingInputForVarIndex = -1;
struct ForLoopState {
int variableIndex;
double endValue;
double stepValue;
int returnAddress;
};
ForLoopState forLoopStack[MAX_LOOP_DEPTH];
int forLoopStackPtr = -1;
// --- Forward Declarations & Helper Functions ---
void newProgram();
void listProgram();
void startProgram();
bool executeLine(String line, bool isConditional);
void reportError(String message, int lineNumber);
void processInput(String input);
bool isInteger(String str);
String readNextCommand(); // New dedicated input handler
bool isVariable(char c) {
char uc = toupper(c);
return uc >= 'A' && uc <= 'Z';
}
int getVariableIndex(char c) {
if (!isVariable(c)) return -1;
return toupper(c) - 'A';
}
// =================================================================
// Stacks & Calculator Class
// =================================================================
class CharStack{public:CharStack(int s=STACK_SIZE){maxSize=s;stackArray=new String[maxSize];top=-1;}~CharStack(){delete[] stackArray;}void push(String v){if(!isFull())stackArray[++top]=v;}String pop(){return!isEmpty()?stackArray[top--]:"";}String peek(){return!isEmpty()?stackArray[top]:"";}bool isEmpty(){return top==-1;}bool isFull(){return top==maxSize-1;}private:String*stackArray;int top,maxSize;};
class FloatStack{public:FloatStack(int s=STACK_SIZE){maxSize=s;stackArray=new double[maxSize];top=-1;}~FloatStack(){delete[] stackArray;}void push(double v){if(!isFull())stackArray[++top]=v;}double pop(){return!isEmpty()?stackArray[top--]:NAN;}private:double*stackArray;int top,maxSize;bool isEmpty(){return top==-1;}bool isFull(){return top==maxSize-1;}};
class Calculator{public:String toPostfix(String i);double evaluatePostfix(String p,bool isRad);private:int getPrecedence(String o);bool isFunc(String t);String handleUnary(String i);double power(double b,double e);};
String Calculator::handleUnary(String i){i.replace(" ","");i.replace("(+","(");i.replace("*+","*");i.replace("/+","/");i.replace("++","+");i.replace("-+","-");i.replace("%+","%");i.replace("^+","^");if(i.startsWith("+"))i=i.substring(1);if(i.startsWith("-"))i="0"+i;i.replace("(-","(0-");return i;}
int Calculator::getPrecedence(String o){if(o=="!")return 5;if(isFunc(o))return 4;if(o=="^")return 3;if(o=="*"||o=="/"||o=="%")return 2;if(o=="+"||o=="-")return 1;return 0;}
bool Calculator::isFunc(String t){const char*f[]={"SIN","COS","TAN","ASIN","ACOS","ATAN","SIND","COSD","TAND","SINH","COSH","TANH","ASINH","ACOSH","ATANH","LN","LOG","EXP","SQRT"};for(const char*i:f){if(t.equalsIgnoreCase(i))return true;}return false;}
String Calculator::toPostfix(String i){i=handleUnary(i);String p="";CharStack o;bool lastTokenWasNumber=false;for(int k=0;k<i.length();){char c=i.charAt(k);if(lastTokenWasNumber&&(isDigit(c)||isAlpha(c)||c=='('||(uint8_t)c==0xCF)){while(!o.isEmpty()&&o.peek()!="("&&getPrecedence("*")<=getPrecedence(o.peek())){p+=o.pop()+" ";}o.push("*");}if(isDigit(c)||c=='.'){String n="";while(k<i.length()&&(isDigit(i.charAt(k))||i.charAt(k)=='.'))n+=i.charAt(k++);p+=n+" ";lastTokenWasNumber=true;continue;}if((uint8_t)c==0xCF&&k+1<i.length()&&(uint8_t)i.charAt(k+1)==0x80){p+="pi ";k+=2;lastTokenWasNumber=true;continue;}if(isAlpha(c)){String token="";while(k<i.length()&&isAlpha(i.charAt(k)))token+=i.charAt(k++);String upperToken=token;upperToken.toUpperCase();if(isFunc(upperToken)){o.push(upperToken);lastTokenWasNumber=false;}else if(upperToken=="PI"){p+="pi ";lastTokenWasNumber=true;}else if(upperToken=="E"){p+="e ";lastTokenWasNumber=true;}else if(upperToken.length()==1&&isVariable(upperToken.charAt(0))){p+=upperToken+" ";lastTokenWasNumber=true;}continue;}if(c=='('){o.push("(");k++;lastTokenWasNumber=false;continue;}if(c==')'){while(!o.isEmpty()&&o.peek()!="(")p+=o.pop()+" ";if(o.isEmpty())return"ERROR:Unbalanced parentheses";o.pop();if(!o.isEmpty()&&isFunc(o.peek()))p+=o.pop()+" ";k++;lastTokenWasNumber=true;continue;}if(String("+-*/%^!").indexOf(c)!=-1){String o1=String(c);while(!o.isEmpty()&&o.peek()!="("&&getPrecedence(o1)<=getPrecedence(o.peek()))p+=o.pop()+" ";o.push(o1);k++;lastTokenWasNumber=false;continue;}k++;}while(!o.isEmpty()){if(o.peek()=="(")return"ERROR:Unbalanced parentheses";p+=o.pop()+" ";}p.trim();return p;}
double Calculator::evaluatePostfix(String p,bool r){if(p.startsWith("ERROR:"))return NAN;FloatStack v;int c=0;while(c<p.length()){int n=p.indexOf(' ',c);if(n==-1)n=p.length();String t=p.substring(c,n);c=n+1;if(t.length()==0)continue;if(isDigit(t.charAt(0))||(t.charAt(0)=='-'&&t.length()>1))v.push(t.toDouble());else if(t.equalsIgnoreCase("pi"))v.push(PI_CONST);else if(t.equalsIgnoreCase("e"))v.push(E_CONST);else if(t.length()==1&&isVariable(t.charAt(0)))v.push(variableValues[getVariableIndex(t.charAt(0))]);else if(isFunc(t)){double val=v.pop(),res=0;String ut=t;ut.toUpperCase();if(ut=="SIN")res=r?sin(val):sin(val*PI_CONST/180.0);else if(ut=="COS")res=r?cos(val):cos(val*PI_CONST/180.0);else if(ut=="TAN")res=r?tan(val):tan(val*PI_CONST/180.0);else if(ut=="ASIN")res=r?asin(val):asin(val)*180.0/PI_CONST;else if(ut=="ACOS")res=r?acos(val):acos(val)*180.0/PI_CONST;else if(ut=="ATAN")res=r?atan(val):atan(val)*180.0/PI_CONST;else if(ut=="SIND")res=sin(val*PI_CONST/180.0);else if(ut=="COSD")res=cos(val*PI_CONST/180.0);else if(ut=="TAND")res=tan(val*PI_CONST/180.0);else if(ut=="SINH")res=sinh(val);else if(ut=="COSH")res=cosh(val);else if(ut=="TANH")res=tanh(val);else if(ut=="ASINH")res=log(val+sqrt(val*val+1.0));else if(ut=="ACOSH")res=log(val+sqrt(val*val-1.0));else if(ut=="ATANH")res=0.5*log((1.0+val)/(1.0-val));else if(ut=="LN")res=log(val);else if(ut=="LOG")res=log10(val);else if(ut=="EXP")res=exp(val);else if(ut=="SQRT")res=sqrt(val);v.push(res);}else if(t=="!"){double val=v.pop();if(val<0||floor(val)!=val){v.push(NAN);}else{double result=1.0;for(double i=1;i<=val;i++){result*=i;}v.push(result);}}else{double v2=v.pop(),v1=v.pop();if(t=="+")v.push(v1+v2);else if(t=="-")v.push(v1-v2);else if(t=="*")v.push(v1*v2);else if(t=="/")v.push(v1/v2);else if(t=="%")v.push(fmod(v1,v2));else if(t=="^")v.push(power(v1,v2));}}return v.pop();}
double Calculator::power(double b,double e){if(e==0)return 1;if(b==0)return 0;if(floor(e)==e&&e>0){long i_e=(long)e;double r=1.0;while(i_e>0){if(i_e%2==1)r*=b;b*=b;i_e/=2;}return r;}return pow(b,e);}
Calculator calculator;
bool isRadiansMode = true;
// =================================================================
// BASIC Interpreter Implementation
// =================================================================
void reportError(String message, int lineNumber){programState=IDLE;Serial.print("\nError");if(lineNumber>0){Serial.print(" in line ");Serial.print(lineNumber);}Serial.print(": ");Serial.println(message);}
int findLineIndex(int lineNum){for(int i=0;i<programSize;i++){if(lineNumbers[i]==lineNum)return i;}return-1;}
void storeLine(String line){int spacePos=line.indexOf(' ');if(spacePos==-1&&line.toInt()>0){spacePos=line.length();}else if(spacePos==-1){return;}int lineNum=line.substring(0,spacePos).toInt();if(lineNum==0)return;String code=line.substring(spacePos);code.trim();int existingIndex=findLineIndex(lineNum);if(existingIndex!=-1){if(code.length()==0){programSize--;for(int i=existingIndex;i<programSize;i++){lineNumbers[i]=lineNumbers[i+1];programLines[i]=programLines[i+1];}}else{programLines[existingIndex]=code;}}else{if(code.length()==0)return;if(programSize>=MAX_PROGRAM_LINES){Serial.println("Program memory full!");return;}int insertPos=0;while(insertPos<programSize&&lineNumbers[insertPos]<lineNum)insertPos++;for(int i=programSize;i>insertPos;i--){lineNumbers[i]=lineNumbers[i-1];programLines[i]=programLines[i-1];}lineNumbers[insertPos]=lineNum;programLines[insertPos]=code;programSize++;}}
void newProgram(){programSize=0;for(int i=0;i<26;i++)variableValues[i]=0;programState=IDLE;forLoopStackPtr=-1;Serial.println("OK");}
void listProgram(){for(int i=0;i<programSize;i++){Serial.print(lineNumbers[i]);Serial.print(" ");Serial.println(programLines[i]);}}
void startProgram(){if(programSize>0){pc=0;programState=RUNNING;forLoopStackPtr=-1;}}
bool isInteger(String str){if(str.length()==0)return false;for(unsigned int i=0;i<str.length();i++){if(!isDigit(str.charAt(i)))return false;}return true;}
bool executeLine(String line, bool isConditional) {
String command,args;int firstSpace=line.indexOf(' ');if(firstSpace!=-1){command=line.substring(0,firstSpace);args=line.substring(firstSpace+1);}else{command=line;args="";}command.toUpperCase();args.trim();int currentLineNum=isConditional?-1:lineNumbers[pc];
if(command=="REM"){return false;}
if(command=="END"){programState=IDLE;return true;}
if(command=="PRINT"){while(args.length()>0){int semiPos=args.indexOf(';');String token;if(semiPos!=-1){token=args.substring(0,semiPos);args=args.substring(semiPos+1);}else{token=args;args="";}token.trim();if(token.startsWith("\"")&&token.endsWith("\"")){Serial.print(token.substring(1,token.length()-1));}else if(token.length()>0){String postfix=calculator.toPostfix(token);if(postfix.startsWith("ERROR:")){reportError(postfix.substring(6),currentLineNum);return true;}double result=calculator.evaluatePostfix(postfix,isRadiansMode);if(isnan(result)){reportError("Mathematical error",currentLineNum);return true;}Serial.print(result,15);}}Serial.println();return false;}
if(command=="INPUT"){awaitingInputForVarIndex=getVariableIndex(args.charAt(0));if(awaitingInputForVarIndex!=-1){programState=AWAIT_INPUT;Serial.print("? ");}else{reportError("Invalid variable for INPUT",currentLineNum);}return true;}
if(command=="GOTO"){int newPc=findLineIndex(args.toInt());if(newPc!=-1){pc=newPc;}else{reportError("GOTO target line not found",currentLineNum);}return true;}
if(command=="IF"){String uArgs=args;uArgs.toUpperCase();int thenPos=uArgs.indexOf("THEN");if(thenPos==-1){reportError("IF without THEN",currentLineNum);return true;}String conditionStr=args.substring(0,thenPos);String op;int opPos=-1;if((opPos=conditionStr.indexOf("<>"))!=-1)op="<>";else if((opPos=conditionStr.indexOf("!="))!=-1)op="!=";else if((opPos=conditionStr.indexOf("<="))!=-1)op="<=";else if((opPos=conditionStr.indexOf(">="))!=-1)op=">=";else if((opPos=conditionStr.indexOf('<'))!=-1)op="<";else if((opPos=conditionStr.indexOf('>'))!=-1)op=">";else if((opPos=conditionStr.indexOf('='))!=-1)op="=";if(opPos==-1){reportError("Invalid operator in IF",currentLineNum);return true;}double lhs=calculator.evaluatePostfix(calculator.toPostfix(conditionStr.substring(0,opPos)),isRadiansMode);double rhs=calculator.evaluatePostfix(calculator.toPostfix(conditionStr.substring(opPos+op.length())),isRadiansMode);bool conditionMet=false;if(op=="<")conditionMet=lhs<rhs;else if(op==">")conditionMet=lhs>rhs;else if(op=="<=")conditionMet=lhs<=rhs;else if(op==">=")conditionMet=lhs>=rhs;else if(op=="=")conditionMet=lhs==rhs;else if(op=="<>"||op=="!=")conditionMet=lhs!=rhs;if(conditionMet){String actionStr=args.substring(thenPos+4);actionStr.trim();if(isInteger(actionStr)){int newPc=findLineIndex(actionStr.toInt());if(newPc!=-1){pc=newPc;}else{reportError("IF target line not found",currentLineNum);}return true;}else{return executeLine(actionStr,true);}}return false;}
if(command=="FOR"){String uArgs=args;uArgs.toUpperCase();int eqPos=uArgs.indexOf('=');int toPos=uArgs.indexOf("TO");int stepPos=uArgs.indexOf("STEP");if(eqPos==-1||toPos==-1){reportError("Invalid FOR syntax",currentLineNum);return true;}int varIdx=getVariableIndex(uArgs.charAt(0));String startExpr=args.substring(eqPos+1,toPos);String endExpr,stepExpr;if(stepPos!=-1){endExpr=args.substring(toPos+2,stepPos);stepExpr=args.substring(stepPos+4);}else{endExpr=args.substring(toPos+2);stepExpr="1";}if(forLoopStackPtr>=MAX_LOOP_DEPTH-1){reportError("Loop depth exceeded",currentLineNum);return true;}forLoopStackPtr++;forLoopStack[forLoopStackPtr].variableIndex=varIdx;forLoopStack[forLoopStackPtr].endValue=calculator.evaluatePostfix(calculator.toPostfix(endExpr),isRadiansMode);forLoopStack[forLoopStackPtr].stepValue=calculator.evaluatePostfix(calculator.toPostfix(stepExpr),isRadiansMode);forLoopStack[forLoopStackPtr].returnAddress=pc;variableValues[varIdx]=calculator.evaluatePostfix(calculator.toPostfix(startExpr),isRadiansMode);return false;}
if(command=="NEXT"){int varIdx=getVariableIndex(args.charAt(0));if(forLoopStackPtr<0||forLoopStack[forLoopStackPtr].variableIndex!=varIdx){reportError("NEXT without FOR",currentLineNum);return true;}variableValues[varIdx]+=forLoopStack[forLoopStackPtr].stepValue;bool loopContinues=(forLoopStack[forLoopStackPtr].stepValue>=0)?(variableValues[varIdx]<=forLoopStack[forLoopStackPtr].endValue+0.0001):(variableValues[varIdx]>=forLoopStack[forLoopStackPtr].endValue-0.0001);if(loopContinues){pc=forLoopStack[forLoopStackPtr].returnAddress+1;return true;}else{forLoopStackPtr--;return false;}}
String uLine=line;uLine.toUpperCase();if(uLine.startsWith("LET ")){line=line.substring(4);}int eqPos=line.indexOf('=');if(eqPos!=-1&&isVariable(line.charAt(0))){int varIdx=getVariableIndex(line.charAt(0));String expression=line.substring(eqPos+1);String postfix=calculator.toPostfix(expression);if(postfix.startsWith("ERROR:")){reportError(postfix.substring(6),currentLineNum);return true;}double result=calculator.evaluatePostfix(postfix,isRadiansMode);if(isnan(result)){reportError("Mathematical error",currentLineNum);return true;}variableValues[varIdx]=result;return false;}else{reportError("Syntax error",currentLineNum);return true;}
}
void processInput(String input) {
input.trim();
if (input.length() > 0) {
String firstToken = input; int spacePos = input.indexOf(' '); if (spacePos != -1) firstToken = input.substring(0, spacePos);
if (isInteger(firstToken)) { storeLine(input); }
else if (firstToken.equalsIgnoreCase("RUN")) { startProgram(); }
else if (firstToken.equalsIgnoreCase("LIST")) { listProgram(); }
else if (firstToken.equalsIgnoreCase("NEW")) { newProgram(); }
else if (firstToken.equalsIgnoreCase("BREAK")) {}
else { String command = firstToken; command.toUpperCase(); if (command == "PRINT" || command == "IF" || command == "GOTO" || command == "FOR" || command == "NEXT" || isVariable(command.charAt(0))) { executeLine(input, true); } else { String postfix = calculator.toPostfix(input); if (postfix.startsWith("ERROR:")) { reportError(postfix.substring(6), -1); } else { double res = calculator.evaluatePostfix(postfix, isRadiansMode); if (isnan(res)) { reportError("Mathematical error", -1); } else { Serial.println(res, 15); } } } }
}
}
// =================================================================
// Main Setup and Loop
// =================================================================
void setup() {
Serial.begin(9600);
while (!Serial) { ; }
newProgram();
Serial.println("\n--- Arduino BASIC Scientific Calculator ---");
Serial.println("Commands: RUN, LIST, NEW, LET, PRINT, INPUT, GOTO, IF/THEN, FOR/NEXT, REM, END");
Serial.println("Constants: e, pi, \xcf\x80");
Serial.println("To stop a running program, type BREAK and press Enter");
Serial.println("-------------------------------------");
Serial.print("Select angle mode - (r)adians or (d)egrees: ");
while (Serial.available() == 0) {}
char mode = Serial.read(); while (Serial.available() > 0) Serial.read();
if (mode == 'd' || mode == 'D') { isRadiansMode = false; Serial.println("Degree Mode."); }
else { isRadiansMode = true; Serial.println("Radian Mode."); }
Serial.println("-------------------------------------");
Serial.print("> ");
}
String readNextCommand() {
String cmd = "";
while (true) {
while (Serial.available() == 0) {
// Wait for character
}
char c = Serial.read();
// Handle command terminators
if (c == '\n' || c == ',') {
Serial.println(); // Finish the line on the user's screen
cmd.trim();
return cmd;
}
// Handle special characters
if (c == '\r') { /* Ignore carriage return */ }
else if (c == 8 || c == 127) { // Backspace
if (cmd.length() > 0) {
cmd.remove(cmd.length() - 1);
Serial.print("\b \b"); // Erase character from terminal
}
}
// Handle printable characters
else if (c >= 32) {
cmd += c;
Serial.print(c); // Live echo
}
}
}
void loop() {
if (programState == RUNNING) {
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim();
Serial.println(input);
if (input.equalsIgnoreCase("BREAK")) {
programState = IDLE;
Serial.println("\n*** PROGRAM HALTED BY USER ***");
while (Serial.available() > 0) Serial.read();
}
}
if (programState == RUNNING) {
if (pc >= programSize) {
programState = IDLE;
if (forLoopStackPtr != -1) reportError("FOR without NEXT", -1);
} else {
bool jumped = executeLine(programLines[pc], false);
if (!jumped) {
pc++;
}
}
}
if (programState == IDLE) {
Serial.print("> ");
}
} else { // IDLE or AWAIT_INPUT
String input = readNextCommand();
if (programState == AWAIT_INPUT) {
if (awaitingInputForVarIndex != -1) {
variableValues[awaitingInputForVarIndex] = input.toDouble();
}
programState = RUNNING;
pc++; // CRITICAL FIX: Advance program counter after input
} else { // programState is IDLE
processInput(input);
Serial.print("> ");
}
}
}