' ' S I M P L E C A L C U L A T O R ' ' ' This program reads a simple mathematic expression and breaks it down ' into basic elements and solves it. The user is to enter an expression ' such as: ' ' (9 * 8) / 2 - (3 - 100) * 8 ' ' and the program has to solve the expression using the right order of ' operation. Written by Zsolt Nagy-Perge in Jan 2022 ' DEFINT A-Z DECLARE FUNCTION NOSPACE (S$) ' Deletes spaces from a string and counts the operators DECLARE FUNCTION SOLVE$ (E$, OP$) ' Solves a simple expression: 4 - 5 * 3 DECLARE FUNCTION REPLACE$ (S$, A$, B$) ' Replaces every occurrence of string A$ with B$ DECLARE FUNCTION COMMIFY$ (N$) ' Formats a number as ##,###,###.##### CLS OPEN "EVAL.BAS" FOR INPUT AS #1 COLOR 14 FOR I = 1 TO 14 LINE INPUT #1, L$ PRINT " "; MID$(L$, 2) NEXT I COLOR 7 PRINT "Pleas enter an expression: "; LINE INPUT E$ CLS PRINT "You've entered: "; E$ PRINT ' Here are some other test cases: ' ' E$ = " ((3,400)) - 10 * (514 - 52) / 5 + ( 4 * (7 + (111 + 2))) - 191" ' E$ = " (9 * 8) / 2 - (3 - 100) * 8" ' E$ = " (2,000,000 * 2 + 1,214 * 23) / 2 " ' ORIGINAL$ = E$ SLOW = 0 ' 0=Fast 1=Pause after each step TEMP$ = "" ' We will use this to store the contents of E$ temporarily. SC = 0 ' Counts how many steps we have done so far MAXSTEP = 99 ' This prevents the main LOOP from going on forever. DO SC = SC + 1 IF SC > MAXSTEP THEN ' KNOWN ISSUES: ' When the final answer is a negative number, the program ' thinks that there's still an unsolved subtraction somewhere, ' so it keeps trying and trying and ends up in a forever loop. ' Of course, we kill it eventually, but there's got to be ' a better solution than this. ' ' The program thinks that -1 - 100 = +99 ' Again, that's a result of not being able to handle ' some negative numbers correctly. ' ' Also, according to the program, 3.4 * 2 = 3.8 LOL ' So, there are some bugs still... ' IF TODO = 1 THEN ' Oh, let me guess, if there's only one operator left ' and the program cannot solve it no matter what, then ' we just call it a day. EXIT DO ELSE COLOR 12 PRINT "Something went wrong. Giving up! Sorry, I can't solve: " PRINT PRINT ORIGINAL$ PRINT PRINT "This is how far I got:" PRINT PRINT E$ SYSTEM END IF END IF PRINT "STEP"; SC ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' First, we get rid of all the spaces and commas in the expression ' and at the same time, we count how many "operators" are left. TODO = NOSPACE(E$) ' Let's simplify some things... E$ = REPLACE(E$, "--", "+") E$ = REPLACE(E$, "-+", "-") E$ = REPLACE(E$, "+-", "-") E$ = REPLACE(E$, "++", "+") ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' PRINT "Expression: "; E$ ' Now, we will start solving the expression starting with ' whatever is enclosed inside the innermost parentheses. ' We evaluate everything inside the parentheses, and then ' we replace it with the result. So, we overwrite ' the expression as we go... BEGIN = 0 ' Pointer to the most recent "(" character ' The initial value 0 means that there ' aren't any OR we haven't started looking yet. FOR I = 1 TO LEN(E$) C$ = MID$(E$, I, 1) IF C$ = "(" THEN BEGIN = I IF C$ = ")" THEN IF BEGIN = 0 THEN COLOR 12 PRINT PRINT "There is an error in the expression." PRINT "A closing parenthesis was found, but opening parenthesis is missing:" PRINT PRINT " "; E$ PRINT SPACE$(I) + "^" COLOR 7 SYSTEM END IF TEMP$ = E$ ' Save our work so far. ' OK. Now, we grab whatever is ' enclosed inside the parentheses and solve it. COLOR 10 PRINT SPACE$(BEGIN + 11) + STRING$(I - BEGIN + 1, "^") COLOR 7 PRINT SPACE$(BEGIN + 12); E$ = MID$(E$, BEGIN + 1, I - BEGIN - 1) EXIT FOR END IF NEXT I DO BEFORE = LEN(E$) E$ = SOLVE$(E$, "*") E$ = SOLVE$(E$, "/") LOOP UNTIL LEN(E$) = BEFORE DO BEFORE = LEN(E$) E$ = SOLVE$(E$, "+") E$ = SOLVE$(E$, "-") LOOP UNTIL LEN(E$) = BEFORE PRINT E$ IF BEGIN > 0 THEN ' If we are here, it means we have just evaluated an enclosure ' so that means we have saved our original expression in TEMP$ ' Now, it's time to restore it. So, here we replace the ' parentheses with the result. E$ = MID$(TEMP$, 1, BEGIN - 1) + E$ + MID$(TEMP$, I + 1) ELSE PRINT END IF IF SLOW THEN SLEEP 1 LOOP WHILE TODO > 0 COLOR 10 PRINT "THAT'S THE RESULT -> "; COMMIFY$(E$) SYSTEM ' This function strictly formats a number as #,###,###.####### ' by inserting a comma after every third group before the ' decimal point. The number must be given as a string. ' The output shall only contain the following characters: ' - . 0 1 2 3 4 5 6 7 8 9 ' FUNCTION COMMIFY$ (N$) ' First, we extract the digits (0-9) and discard everything else. ' At the same time, we make sure to discard any insignificant ' zeros that come before the decimal point. ' We must also remember the position of the decimal point and ' whether the number was positive or negative. X$ = "" ' Output number NEG = 0 ' Is it a negative number? DEC = -1 ' This is a pointer to the decimal point FOR I = 1 TO LEN(N$) C$ = MID$(N$, I, 1) CH = ASC(C$) IF CH > 47 AND CH < 58 THEN ' Skip leading zeros IF LEN(X$) THEN X$ = X$ + C$ ELSE IF CH > 48 THEN X$ = C$ ELSEIF DEC < 0 THEN ' Remember the position of the decimal point IF CH = 46 THEN ' Add a zero if no digits so far. ' We shouldn't end up with a number such as -.5 IF LEN(X$) = 0 THEN X$ = "0" DEC = LEN(X$) END IF ' If there's a negative sign, it must appear ' before any digits and the decimal point. ' Otherwise the number is treated as a positive number. IF CH = 45 AND DEC < 0 AND LEN(X$) = 0 THEN NEG = 1 END IF NEXT I ' Now X$ contains digits only. ' If there are no digits, we return zero. IF LEN(X$) = 0 THEN COMMIFY$ = "0": EXIT FUNCTION G$ = "" ' We will store the output in this Z = LEN(X$) IF DEC >= 0 THEN Z = DEC ' If there's a decimal point somewhere, ' then skip digits after the decimal point. FOR I = Z TO 1 STEP -1 C$ = MID$(X$, I, 1) IF (LEN(G$) AND 3) = 3 THEN G$ = "," + G$ ' Add comma G$ = C$ + G$ NEXT I ' Add digits that come after the decimal point. IF DEC >= 0 THEN G$ = G$ + "." + MID$(X$, DEC + 1) ' Add negative sign when needed. IF NEG THEN G$ = "-" + G$ COMMIFY$ = G$ END FUNCTION ' This function deletes all characters from a string ' whose ASCII value is less than 33. This includes ' spaces, tabs, and new line characters. ' In addition, it also deletes all commas. ' At the same time, this function also counts the number ' of operators or signs in the expression and returns that number. FUNCTION NOSPACE (S$) TODO = 0 ' Count the operators and stuff to do J = 1 ' I=Source pointer, J=Destination pointer FOR I = 1 TO LEN(S$) C$ = MID$(S$, I, 1) IF INSTR("*/+-()", C$) > 0 THEN TODO = TODO + 1 IF NOT C$ = "," AND ASC(C$) > 32 AND I >= J THEN MID$(S$, J, 1) = C$ J = J + 1 END IF NEXT I S$ = MID$(S$, 1, J - 1) ' Reduce the length of string, because ' we just removed a bunch of spaces. NOSPACE = TODO END FUNCTION ' This function replaces every instances of string A$ with string B$. ' Returns a new string. The replacement is case sensitive. FUNCTION REPLACE$ (S$, A$, B$) LA = LEN(A$) LB = LEN(B$) ' If the pattern is longer than the string, there's ' no way it's going to match, so we might as well exit. IF LA = 0 OR LEN(S$) < LA THEN REPLACE$ = S$: EXIT FUNCTION ' Is the new string going to be longer or shorter ' than the original? G will hold the answer: G = LB - LA ' length Gain F = 1 ' Start searching for pattern From this position X$ = S$ ' Make a copy of the original string LB = LB - 1 DO F = INSTR(F, X$, A$) IF F = 0 THEN EXIT DO N = F + LB IF G < 1 THEN MID$(X$, F) = B$ IF G < 0 THEN X$ = MID$(X$, 1, N) + MID$(X$, F + LA) IF G > 0 THEN X$ = MID$(X$, 1, F - 1) + B$ + MID$(X$, F + LA) F = N LOOP REPLACE$ = X$ END FUNCTION ' This function looks for a particular operator in the expression ' and solves the first one it finds by either + - * / the two ' numbers around it. Finally, it returns the same expression ' with the result substituted in place of that single operation. ' Example: SOLVE$("3 + 2 * 5 + 7", "*") ' Here we do one multiplication ' returns: "3 + 10 + 7) ' FUNCTION SOLVE$ (M$, OP$) X = NOSPACE(M$) NUM$ = "" N = 0 M$ = M$ + " 0" ' Add an extra digit to cause the loop ' to run one more time and process the ' last number in the expression. FOR I = 1 TO LEN(M$) C$ = MID$(M$, I, 1) CH = ASC(C$) IF CH > 47 AND CH < 58 OR CH = 46 THEN ' Is it a number? IF N = 0 THEN N = I ELSE IF N THEN PREV$ = NUM$ NUM$ = MID$(M$, N, I - N) IF LEN(PREV$) > 0 AND LEN(NUM$) > 0 AND LEN(O$) > 0 THEN A# = VAL(PREV$) B# = VAL(NUM$) ' Here is where the magic happens. ' Here we decide which operator does what. IF O$ = "*" THEN RESULT# = A# * B# IF O$ = "/" THEN RESULT# = A# / B# IF O$ = "+" THEN RESULT# = A# + B# IF O$ = "-" THEN RESULT# = A# - B# ' Then we insert the result back into the equation. M$ = MID$(M$, 1, START - 1) + STR$(RESULT#) + MID$(M$, I) EXIT FOR END IF START = N N = 0 O$ = "" END IF IF INSTR(OP$, C$) THEN O$ = O$ + C$ END IF NEXT I X = NOSPACE(M$) ' Remove the extra zero we added earlier. M$ = MID$(M$, 1, LEN(M$) - 1) SOLVE$ = M$ END FUNCTION