'
' 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