Welcome to the Second Life Forums Archive

These forums are CLOSED. Please visit the new forums HERE

Scientific Calculator

Zindorf Yossarian
Master of Disaster
Join date: 9 Mar 2004
Posts: 160
06-03-2005 17:03
I just finished a nice calculator script with many nice capabilites, such as trigonometry, parenthesis, and user-defined variables. See the beginning of the script for more details. I saw Xylor's Xy Calc, and though it has a bit more function than this, it's also a lot bigger and more complicated! :p

So, here goes:
CODE
// Calculator
// By Zindorf Yossarian (Please include my name with any version of this script - I worked hard on it.)
//
// CAPABILITES
//
// My aim with this calculator was to make it capable of most of the basic functions of a scientific calculator. Its capablilies are as follows:
// Basics: All the functions addition, subtraction, multiplication, division, exponents, and radicals are supported, as well as negative numbers.
// Parentheses: Any number of parentheses may be used in an expression.
// Trigonometry: Sine, Cosine, Tangent, Arcsine, Arccosine, and Arctangent are supported.
// Angle modes: Answers can computed using degrees or radians.
// pi and e are preset constants, and can be used in your expressions.
// Variables: The user can define and use variables.
// Answer Memory: The calculator will remember the answer to the last problem, and the term "ans" can be used in the next expression to refer to the last answer.
//
// USING THE CALCULATOR
//
// To compute an expression, type the expression, followed by an equals "=" sign. Spacing does not matter.
// ex. "2+2 =" will return 4. "3^3 =" will return 9. "3*(4/-2)=" will return -6.
// For radicals, use sqrt. It will take the square root of the value directly following it.
// ex. "sqrt 4 =" will return 2. "sqrt (20*5) =" will return 10.
// Trigonometric functions too will use the value directly following it.
// The trigonometric functions this calculator uses are sin, cos, tan, asin, acos, & atan.
// asin, acos, and atan are arcfunctions, or 1/sin, 1/cos, and 1/tan. They are the opposite of the sin, cos, and tan.
// ex. "sin 90 =" will return 1. "tan 45 =" will return 1. "atan(tan 30) =" will return 30.
// Angle mode can be toggled between degree and radian mode. To change the mode, say "degree mode" or "radian mode".
// The default mode is degree.
// To store variable, say "store", followed by the desired variable name, followed by the equals "=" sign, followed by the desired variable value.
// The variable name will appear in hovertext, followed by its value. You may also use mathematical expressions to define variables.
// ex. "store foo = 6" will store foo as six, for later use. Text will appear with "foo = 6".
// you may then use foo in an expression: "foo ^ 2 =" will return 36.
// then you may say, "store bar = foo + 3". bar will be stored as 9, and the text will add another line with "bar = 9".
// To delete a variable, say "delete" follwed by the variable name.
//


list storedVariables = ["ans", 0, "pi", 3.141593, "e", 2.718282]; // This list is used to store the user-defined variables, and always has three variables for
// the last answer "ans" variable, pi, and e. It is strided, so it has the variable name followed by that variable's
// value in the list. For now, the value of "ans" is 0, but it will change when expressions are solved.

list variableNames = ["ans", "pi", "e"]; // This list holds only the names of the user-defined variables, as well as "ans". It does not include their values.
integer angleMode = 1; // angleMode can be either 1 or 0, 1 being degrees mode, and 0 being radian mode. For now we are in degree mode.

debug(string debugInput) // This function exists as a convienient way to debug the script.
{ // All you need to do is pass a string to it, and it will say the string to the owner.
llOwnerSay(debugInput); // This is preferable to many decentralized ownersays, because with this you can stop
} // the debugs by commenting out one line.

update_text() // This function updates the hovertext which displays your variables.
{
list unstridedVars; // This list will hold in each entry a string consisting of the variable name, then an equals sign, then the value. We use this list for
// setting the hovertext that tells the value of each variable.
integer indexCounter; // The indexCounter is used as we scan the strided storedVariables list, and put the names of the variables into the unstridedVars list.

// Remember that list just for holding the variable names? Here's where we put in all those names. First we reset the list to get rid of old, possibly deleted names.
variableNames = ["ans", "pi", "e"];

// We start this loop with the counter at 6, the index in the storedVariables list where the first user-defined variable name is. (remember the first six entries
// are for stored answers and constants) the counter is incremented by 2 each time, so that we go to the next name in the strided list, and skip the numbers.
for (indexCounter = 6; indexCounter < llGetListLength(storedVariables); indexCounter = indexCounter + 2)
{
// We get a list comprising of the variable name and the value after it from the storedVariables list, which is made elsewhere.
// Then we convert those two name and value entries to a string, and insert an "=" between them.
// That string with "name = value" is added to the unstridedVars list.
unstridedVars += [llDumpList2String(llList2List(storedVariables, indexCounter, indexCounter + 1), " = ")];

// Grab the variable name entry from the storedVariables list, and add it to the variableNames list.
variableNames += llList2List(storedVariables, indexCounter, indexCounter);
}

// Finally, the loop finishes, so we can display our variables. The unstridedVars list (with the "name = value" strings) is converted into a string, and "\n" newlines are
// inserted to put each variable on its own line.
llSetText(llDumpList2String(unstridedVars, "\n") + "\n \n \n ", <.7, 0, 0>, 1);

// "What about the variableNames list?", you ask, "You never used it!" Don't worry, we just piggybacked off of this function to set it up. It's used a bit later, in both
// the solving_initiator and the variables_handler functions.
}

// Here is where it all begins. When the calculator recognizes some speech as a mathematical expression being put in, that expression gets sent here, in raw string form.
list solving_initiator(string initiatorInput)
{
// First things first. We have to convert that big string to something the calculator can understand, like a list. llParseString2List is perfect for this. We can
// look at our string, and make list entries for each mathy thing, while throwing out spaces. But due to some limitations in the number of things the function can parse
// a string into all at once, we divide this parsing business into three parts, converting to strings in between each parsing.

// The first think that gets put into list form is the variables. We parse the string, throwing out nothing, and list-o-fying all the variable names.
list math = llParseString2List(initiatorInput, [], variableNames);
// Now our list probably looks something like this: ["mathy stuff, like 6+2 * sin 3", "some variable name NOT its value.", "some more mathy stuff", "another variable name"]

// The list is put into the variables_handler function, which converts all the variable names to their numerical values. That list is then made into a string, with ", "
// between each element.
string CSVmath = llList2CSV(variables_handler(math));
// And we parse the string again, this time for operators and parenthesis. And we throw out commas, spaces, and that equals sign you put at the end of the expression.
math = llParseString2List(CSVmath, [",", " ","="], ["(", ")", "+", "-", "*", "/", "^"]);
// Now the list looks like this: ["number", "operator", "another number", "another operator", "a variable"]
CSVmath = llList2CSV(math); // Again, the list is converted to a string with ", " between each element.
// Now for the final parsing, for all the trigonometric functions.
math = llParseString2List(CSVmath, [",", " ", "="], ["sqrt", "asin", "acos", "atan", "sin", "cos", "tan"]);

// Yay! we have a list with all the the parts of the expression separated, and with actual numbers instead of variable names.
// It's time to do some math.

// We look for parenthesis in the math list. If there are parentheses, we need to deal with them. If not, we can skip a step and just do the math.
if(llListFindList(math, ["("]) != -1)
{
// Yes, there are parenthesis. Go to the parentheses_handler function, and save whatever it spits back.
math = parentheses_handler(math);
}
else
{
// Nope, no parenthesis. Go to the math_handler function, and save whatever it spits back.
math = math_handler(math);
}

return math; // Finally, return that math list. By now, it should be only one number: The solution.
}

// This function is for finding the variable names in a list, and replacing them with their value. The function is always called from the solving_initiator,
// and is supplied with a list with the variable names parsed out. You will notice that through this function, I just modify the input variable, variablesInput,
// until it is what I want to output. Yes, I know this is kinda lame, since most the time it isn't really the input, but why waste extra variables when you already
// have a perfectly good one. I do this a lot with input variables, so be aware of it.
list variables_handler(list variablesInput)
{
integer tempInteger; // We use this mostly in the for loop, to look at each part of the input list, checking for variables.
// The loop will consecutively go through each element in the input list.
for (tempInteger = 0; tempInteger < llGetListLength(variablesInput); tempInteger ++)
{
// We look for the current element in the input list, checking to see whether it is in the variables list.
// llListFindList returns -1 if the thing looked for does not exist.
integer tempIndex = llListFindList(variableNames, llList2List(variablesInput, tempInteger, tempInteger));
if (tempIndex != -1)
{
// The index was not -1, so there was a variable! Replace the name with its value.
variablesInput = llListReplaceList(variablesInput, llList2List(storedVariables, tempIndex*2 + 1, tempIndex*2 + 1), tempInteger, tempInteger);
}
}

// The entire input list has been scanned through, so we return the result to the solving_initiator.
return variablesInput;
}

// The parentheses_handler. It is the backbone of this calculator, and also happens to be the most complicated part. It systematically goes through the expression,
// finding sets of parentheses, and handing the contents of them over to the math_handler to be solved. The answer it gets from the math_handler then replaces that
// set of parentheses, and it looks for the next set. This is called from the solving_initiator, and takes a fully parsed list with the mathematical expression.
list parentheses_handler(list parenInput)
{
while (TRUE) // We just keep going through this loop over and over until all the parentheses are gone.
{
// We find the index of the first forward parentheses, and the first backward parentheses.
integer forwardParenIndex = llListFindList(parenInput, ["("]);
integer backwardParenIndex = llListFindList(parenInput, [")"]);
if (forwardParenIndex != -1 && forwardParenIndex < backwardParenIndex) // If a forward parenthesis occurs before the backward one.
{
// This finds the backwards parentheses opposite to the forward one.
integer corrParenIndex = corresponding_paren_finder(parenInput, forwardParenIndex, backwardParenIndex);

// Everything after the forward parentheses is sent to the parentheses_handler, and the result, which will be a single number, is put into the original
// list, in place of the set of parentheses. Yes, this does call upon itself. Another copy of itself. And that copy might call on another copy, and so on.
// But each time, everything before the first forward parentheses, and the forward parentheses itself is cut off. Eventually, the backwards
// parentheses will come first, or there will be no more forward parentheses. And then that bit of math inside the parentheses is solved, and returned.
// And the next copy solves and returns and the next copy solves and returns until we are all the way back here. If all that hurts your head, don't worry:
// It hurts mine too.
parenInput = llListReplaceList(parenInput, parentheses_handler(llList2List(parenInput, forwardParenIndex + 1, -1)), forwardParenIndex, corrParenIndex);
}
else if (backwardParenIndex != -1 && (forwardParenIndex == -1 || forwardParenIndex > backwardParenIndex)) // There is a backward parentheses, and it is either in
{ // front of the forward one, or there is no forward one.
parenInput = llDeleteSubList(parenInput, backwardParenIndex, -1); // Everything behind and including the backwards parenthesses is deleted.
}
else // If neither of the above two ifs are true, there are no parenthesis. The list is now ripe for the solving.
{
parenInput = math_handler(parenInput); // And solved it is, with the math_handler.
return parenInput; // The solved thing is returned. It should be a single number.
}
}

return ["Something really exploded here."]; // This is just here for the compiler. Obviously, it will never get triggered, there is an "always" loop just above it!
}

// This function takes a list with a forward parentheses, and finds the backward one that corresponds, then returns the corresponding parentheses' index.
// It is also passed the forward and backward parentheses indexes, as found in the parentheses_handler.
integer corresponding_paren_finder(list corrParenInput, integer forwardParenIndex, integer backwardParenIndex)
{
// Since llListFindList only gets the index of the first occurance of something, like our parentheses, we need to replace those parentheses with something else,
// so we can find the next one. Here, we replace forward parentheses with "f" and backwards ones with - you guessed it! - "b". So let's replace the first forward
// parentheses with an "f"...
corrParenInput = llListReplaceList(corrParenInput, ["f"], forwardParenIndex, forwardParenIndex);
forwardParenIndex = llListFindList(corrParenInput, ["("]); // ... and find the next one.
integer level = 0; // "level" is used to keep track of the level or layer of parenthesis we are in, in case parentheses are nested.
do
{
if (forwardParenIndex != -1 && forwardParenIndex < backwardParenIndex) // There is a forward parentheses, and it comes before the backward one. There is a
{ // nested set of parenthesis!
corrParenInput = llListReplaceList(corrParenInput, ["f"], forwardParenIndex, forwardParenIndex); // Replace that forward parentheses with "f"...
forwardParenIndex = llListFindList(corrParenInput, ["("]); // ... find the next forward parentheses...
level ++; // ... and increase the level
}
else if (level > 0 && (forwardParenIndex == -1 || backwardParenIndex < forwardParenIndex)) // There is a backwards parenthesis before any forward ones, and
{ // the level is greater than 0. We need to check the level so that if
// this is the last backwards parentheses we don't replace it, thereby
// losing the index.
corrParenInput = llListReplaceList(corrParenInput, ["b"], backwardParenIndex, backwardParenIndex); // Replace the parentheses with a "b"...
backwardParenIndex = llListFindList(corrParenInput, [")"]); // ... find the next one...
level --; // ... and decrease the level.
}
} while (level > 0); // Now, after all that is done, the level is checked. If is is more than zero, it repeats, or else it breaks from the loop.

return(llListFindList(corrParenInput, [")"])); // Return the index of the corresponding backwards parentheses.
}

// Ahh, the math_handler! So good for doing... math. This will take a list, which is by now free of parenthesis and variables, and is just numbers and operators,
// and computes the expression's value, using the proper order of operations.
list math_handler(list mathInput)
{
integer tempIndex; // tempIndex is used a lot, in the many "for" loops here, as the index of an operator.
float tempSolution; // the solution to each small bit of the expression is put here for about .001 seconds, and then is moved into the main mathInput list, which
// slowly but surely gets solved.

// Each part of the math_handler represents a different calculator function. They all use "for" loops to check for the existence and location of the operator
// or trig function. Many are almost exactly similar to each other. The stuff inside the loop will only happen if the operator it is looking for exists.

// negatives - this looks for each "-", and then checks out what comes before it to see whether it should be a negative number or a minus sign.
for (tempIndex = llListFindList(mathInput, ["-"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["-"]))
{
// If the "-" appears before anything else, or an operator appears before it, it must be a negative.
if (tempIndex == 0 || -1 != llListFindList(["(", "*", "/", "^", "sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "+", "-"], llList2List(mathInput, tempIndex - 1, tempIndex - 1)))
{
tempSolution = (-1 * (float)llList2String(mathInput, tempIndex + 1)); // So multiply the number after the "-" by negative one...
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1); // ... and replace both the "-" and the number with that negative.
}
else // It's a minus sign, but we still replace it with "m", to distinguish it from negatives. Remember, that "for" loop checks for "-" each time. If we don't
{ // replace the "-" with something else, it will loop endlessly.
mathInput = llListReplaceList(mathInput, ["m"], tempIndex, tempIndex);
}
}
// sines
for (tempIndex = llListFindList(mathInput, ["sin"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["sin"]))
{
// This is a compressed if/else statement, to save space. It uses an if/else to provide an answer in radians or degrees, depending on the mode.
// If the value of mode is 1, then input is converted to degrees before being processed. Or, if mode equals 0, no conversion occurs.
// The sin of the list element after the "sin" in the list is found, and that answer is put into the mathInput list. The next 5 trig functions
// are all roughly the same, so I won't bother explaining them.
if (angleMode) { tempSolution = llSin((float)llList2String(mathInput, tempIndex + 1) * DEG_TO_RAD); }
else { tempSolution = llSin((float)llList2String(mathInput, tempIndex + 1)); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// cosines
for (tempIndex = llListFindList(mathInput, ["cos"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["cos"]))
{
if (angleMode) { tempSolution = llCos((float)llList2String(mathInput, tempIndex + 1) * DEG_TO_RAD); }
else { tempSolution = llCos((float)llList2String(mathInput, tempIndex + 1)); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// tangents
for (tempIndex = llListFindList(mathInput, ["tan"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["tan"]))
{
if (angleMode) { tempSolution = llTan((float)llList2String(mathInput, tempIndex + 1) * DEG_TO_RAD); }
else { tempSolution = llTan((float)llList2String(mathInput, tempIndex + 1)); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// arcsines
for (tempIndex = llListFindList(mathInput, ["asin"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["asin"]))
{
if (angleMode) { tempSolution = llAsin((float)llList2String(mathInput, tempIndex + 1)) * RAD_TO_DEG; }
else { tempSolution = llAsin((float)llList2String(mathInput, tempIndex + 1)); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// arccosines
for (tempIndex = llListFindList(mathInput, ["acos"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["acos"]))
{
if (angleMode) { tempSolution = llAcos((float)llList2String(mathInput, tempIndex + 1)) * RAD_TO_DEG; }
else { tempSolution = llAcos((float)llList2String(mathInput, tempIndex + 1)); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// arctangents
for (tempIndex = llListFindList(mathInput, ["atan"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["atan"]))
{
if (angleMode) { tempSolution = llAtan2((float)llList2String(mathInput, tempIndex + 1), 1) * RAD_TO_DEG; }
else { tempSolution = llAtan2((float)llList2String(mathInput, tempIndex + 1), 1); }
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1);
}
// squareroots
for (tempIndex = llListFindList(mathInput, ["sqrt"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["sqrt"]))
{
tempSolution = llSqrt((float)llList2String(mathInput, tempIndex + 1)); // This finds the squareroot of the number after "sqrt",
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex, tempIndex + 1); // and puts it into the mathInput list.
}
// exponents
for (tempIndex = llListFindList(mathInput, ["^"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["^"]))
{
tempSolution = llPow((float)llList2String(mathInput, tempIndex - 1) , (float)llList2String(mathInput, tempIndex + 1)); // This finds the number before "^"
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex - 1, tempIndex + 1); // to the power of the number after,
} // and puts it into the mathInput list.
// multiplication
for (tempIndex = llListFindList(mathInput, ["*"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["*"]))
{
tempSolution = (float)llList2String(mathInput, tempIndex - 1) * (float)llList2String(mathInput, tempIndex + 1); // Multiplies the number before "*"
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex - 1, tempIndex + 1); // by the number after, and puts it into
} // the mathInput list.
// division
for (tempIndex = llListFindList(mathInput, ["/"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["/"]))
{
tempSolution = (float)llList2String(mathInput, tempIndex - 1) / (float)llList2String(mathInput, tempIndex + 1); // Follows the same format as multiplication
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex - 1, tempIndex + 1);
}
// addition
for (tempIndex = llListFindList(mathInput, ["+"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["+"]))
{
tempSolution = (float)llList2String(mathInput, tempIndex - 1) + (float)llList2String(mathInput, tempIndex + 1); // Follows the same format as division
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex - 1, tempIndex + 1);
}
// subtraction
for (tempIndex = llListFindList(mathInput, ["m"]); tempIndex != -1; tempIndex = llListFindList(mathInput, ["m"]))
{
tempSolution = (float)llList2String(mathInput, tempIndex - 1) - (float)llList2String(mathInput, tempIndex + 1); // Follows the same format as addition
mathInput = llListReplaceList(mathInput, [tempSolution], tempIndex - 1, tempIndex + 1);
}
// We have now gone through the list and solved everything. There is one number left in the list.
return mathInput;
}

// That's all the functions. Now into the friendly old default state.
default
{
state_entry()
{
llSetText("", <0,0,0>, 0); // Clears all hovertext with the variables.
llListen(0, "", llGetOwner(), ""); // Listens to the owner.
}
listen(integer channel, string name, key id, string message)
{

if (llGetSubString(message, -1, -1) == "=") // If an "=" sign comes last in the message, the user wants us to compute something!
{
list math = solving_initiator(message); // Send the message all the way up to the solving_initiator, where it begins its long journey to solvedom.
// After said long journey, the answer is put into the math list.

llWhisper(0, llDumpList2String(math, " ")); // Whisper the answer. (You could use ownersay, but then you can't show off the calculator.)

storedVariables = llListReplaceList(storedVariables, [llList2Float(math, 0)], 1, 1); // We just got a new answer, so put that in for the value of "ans"

}
else if (llGetSubString(message, 0, 4) == "store") // The user wants to store something.
{
integer equalsIndex = llSubStringIndex(message, "="); // Get the index of the equals sign.

// Get whatever comes after the equals sign, and solve it. Then store that solution to the variableValue list.
list variableValue = solving_initiator(llGetSubString(message, equalsIndex + 2, -1));
integer tempIndex = llListFindList(storedVariables, [llGetSubString(message, 6, equalsIndex - 2)]); // See if the variable is already stored.
if (tempIndex == -1) // If it is not...
{
storedVariables = storedVariables + [llGetSubString(message, 6, equalsIndex - 2), llList2String(variableValue, 0)]; // Add the variable and its value to the list.
update_text(); // Update the hovertext. (and the variableNames list too.)
}
else if (tempIndex > 7) // The variable was already in the list, and is not "ans" or a constant...
{
// So we just replace the old value with the new one
storedVariables = llListReplaceList(storedVariables, [llList2String(variableValue, 0)], tempIndex + 1, tempIndex + 1);
}
}
else if (llGetSubString(message, 0, 5) == "delete") // The user wants to delete a variable.
{
integer tempIndex = llListFindList(storedVariables, [llGetSubString(message, 7, -1)]); // Find where that variable is
if (tempIndex > 5) // If the variable is in the list, and is not the answer variable or a constant...
{
storedVariables = llDeleteSubList(storedVariables, tempIndex, tempIndex + 1); // Delete the name and value of the variable
update_text(); // And update the text and variableNames
}
}
else if (llGetSubString(message, -4, -1) == "mode") // The user wants to change the mode.
{
if (llGetSubString(message, 0, -6) == "radian") // And he wants radian mode.
{
llWhisper(0, "Calculator set to radian mode."); // Acknowledge the change, so the user knows the calculator heard him.
angleMode = 0; // And change the mode integer to 0. Since radians are used by the LSL functions, no conversion will occur
}
else if (llGetSubString(message, 0, -6) == "degree") // He wants degree mode.
{
llWhisper(0, "Calculator set to degree mode."); // Acknowledge the change.
angleMode = 1; // Change the mode integer so that the angles will be converted.
}
}
}
}

// Whew! All done commenting.


edit: Fixed a problem with variable deletion by changing the number 7 in line 360 to a 5. The first stored variable will now be deleteable.
_____________________
Badass Ninja Penguin: Killing stuff it doesn't like since sometime in May 2004.
Nada Epoch
The Librarian
Join date: 4 Nov 2002
Posts: 1,423
Discussion thread
06-04-2005 08:21
/54/6c/49085/1.html
_____________________
i've got nothing. ;)