Calc - A Programmable Calculator

Users Manual

Written by David Hanak <dhanak@inf.bme.hu>
February 23., 2000

0 Contents

1 Introduction
2 Examples
3 Full Syntax Description
3.1 Literal Values
3.2 Operators
3.3 Parentheses, Braces, Brackets
3.4 Variables
3.5 Expression Sequences
3.6 Built-in Functions
3.7 User Defined Functions
3.8 Controlling Structures
3.9 Precedences
3.10 A Simple Example
4 Error Reporting

1 Introduction

This is Calc, a programmable calculator with GTK+ interface for X. It enables simple calculations as well as using variables and many built-in functions, and also definitions of user functions. It's main purpose is to provide a substitution for calculator programs that look like a real calculator but which are inconvenient to use.

You may enter expressions iteractively, but you can read files into the interpreter as well. The special file called .calc in your home directory is read automatically at startup, if it exists.

2 Examples

Evaluation of standard mathematical formulae is very simple with Calc: you just type in the expression in the entry on the bottom part of the main window and press Enter. The result will be calculated and printed immediately. If an error occurs, a short description of the error together with its position is reported. (Examples 1 and 2.)

Several built-in mathematical functions may be used, like sin and cos. There are also two predefined constants, namely E, and PI. (Mind the case! See example 3.)

Variables are also available, they can be used without any prior declaration. (Their default value is 0, when they're being used uninitialized.) Values can also be strings, these can also be assigned to variables just like numbers. (Example 4 and 5)

Function declaration is also very simple - although somewhat different than that in C. In Calc, function bodies also are expressions which are evaluated each time the function is called. Positional arguments of functions are evaluated exactly once for every function call, no matter how many times do they appear in the function body. Function calls are exactly like in C or like for built-in functions. (Examples 6 and 7)

Figure 1: Expression examples
#
Exression
Result
1 2 + 3 * 5 ^ 2
77
2 2 * ( 2-3 }
parse error at char 11
3 cos(E) + sin(PI^2)
-1.34204
4 my_var = 12.2 / 5
2.44
5 hello_world = "Hello World!"
"Hello World!"
6 fun max(a,b) = if (a > b) a else b
0
7 max(1,2)
2

Figure 2: Snapshot of the example run
Example run

If you write something that causes infinite loop, don't worry - you can always terminate the evaluation with the Abort button which becomes sensitive during evaluations. With Calc, you can also keep track of all existing variables and their values, and of functions, user defined and built-in, through Variable List and Function List windows.

Figure 3: Variable and Function Lists
Variable List Function List

3 Full Syntax Description

3.1 Literal Values

Literal values are either numbers or strings. Numbers may be entered using the well known IEEE scientific notation, e.g., 12.34, 1.234e1, 1234E-2 etc. Hexadecimal numbers are also accepted if they start with the generally used prefix 0x, e.g., 0xA1F2. Strings can be entered between double quotes. Four escape characters are also accepted: \n, the newline character, \t, the tabulator, \", a double quote and \\, a normal backslash character. Newline is only useful if you would like to display a multi-line message with the built-in function msg. When boole values are needed, numbers are expected, where 0 is FALSE, everything else is TRUE. In some places numbers are expected, elsewhere strings. Numbers are automatically converted to strings whenever necessary, on the other hand, strings are only converted to numbers if they contain an IEEE formed number, and nothing else. Otherwise an error is generated. Conversion might also be forced with tostr and todbl. Sometimes a calculation might result special values like INF (infinity) and NAN (not a number). These values can be tested with isinf and isnan functions. There is also a third value type, namely invalid value. This is an internal type, and is rarely noticable by the user.

3.2 Operators

Standard binary and unary mathematical operators, like +, - (negation and subtraction), *, /, ^ (power), % (modulo) can be used as usual. Comparison operators, like <. >, <=, >=, != (not equal), == (is equal) behave exactly like they do in C. Logical negation (!), conjunction (&&) and disjunction (||) also behave like in C, and are lazily evaluated.

3.4 * PI / E > 01
a == 1 && b > 0 || !cdepends on a, b and c

3.3 Parentheses, Braces, Brackets

Parentheses are used to raise precendece of a subexpression, like in standard mathematics. They are also grouping expression sequences into single expressions (in fact, this does nothing else, than raises the precedence of the semicolon (;) operator). Braces have an additional framing side effect: every variable created inside the brackets is freed automatically at the end of the frame (the closing brace). Therefore braces are often used when defining user functions for enclosing the function body to enable the use of local variables. (This is in fact unnecessary, since a function definiton creates a frame inevitably, even without braces.)

2 * (3 + 4) / (E + 1)3.76518
fun add_two(a) = { __locvar = a + 2; __locvar }0

Brackets currenlty behave exactly like parentheses do, but this will change in the near future with the introduction of arrays. Until then, you are kindly asked not to use brackets.

3.4 Variables

Variables are names starting with a letter and containing only alphabetical characters, digits and underscores. They can be used without prior declaration: first value assignment will create a new variable with the given name in the current frame. Value assignment works just like in C using =, +=, -=, etc. operators. Increment and decrement operators (++, --) also exist both in prefix and postfix form.

If needed, variables might also be specifically declared with the var keyword. In that case, a new variable is forced to be created under the given name, even when another one already exists. This is useful when writing user functions to force local variables to be really local. The var keyword's syntax is the following:

  1. var <varname> declares one variable, equivalent to var (<varname>)
  2. var (<var1>, <var2>, ...) declares several variables at the same time.

A special syntax in connection with variables is also allowed: a literal number followed immediately (optionally separated by whitespaces) by a variable has the same effect as writing the multiplication operator (*) between them. Note that this doesn't apply to the built-in constants, E and PI.

foo = 55
bar = 3; bar *= 2.146.42
myvar = 2; 3.14 myvar6.28
a = 5; { a = 2 }; a2
a = 5; { var a; a = 2 }; a5

3.5 Expression Sequences

An expression may consist of several subexpressions in succession if they are separated by a semicolon (;). In this case, the value of the whole expression is the value of the last expression in the sequence. This means that sequencing is meaningful only when all the expressions but the last have a side effect. On the other hand, this is a fundamental tool when writing user defined functions.

Please note, that the semicolon (;) separates the subexpressions, not like in C where it terminates statements! It can't be added anywhere freely! If you get a parse error message, always check the semicolons first.

a = 2; a *= 3; a -= E; a ^ 210.7697
fun foo(a,b) = { a += 2; b *= 3; a/b }0

3.6 Built-in Functions

Built-in functions are always called using the following syntax:
<funname> (<exp1>, <exp2>, ...)
where funname is the name of the function and expi is the expression to be evaluated as the ith argument. The following built-in functions exist currently:
Figure 4: Msg, Read and Ask dialogs
msg window query window query window

3.7 User Defined Functions

The syntax of user function definition is the following:
fun <funname> (<arg1>, <arg2>, ...) = <bodyexp>
Such a declaration is also an expression naturally, it always evaluates to 0. User defined functions are identified by their names alone, number of arguments doesn't count. Therefore if you define a function with two arguments, and then define another one with the same name, but three arguments, the second definition overrides the first one. User defined functions are called exactly like built-in functions. The function body of a user function is reevaluated whenever the evaluation of a corresponding user function call is requested. Therefore functions shouldn't be considered as macros that expand at the time of parsing but as pointers to an address which pointer is only resolved at execution time. This also means, that when defining multiple functions that rely on each other, they don't have to be defined in strict call order.

At a user function call a new variable frame is created, expressions specified as arguments are evaluated exactly once, and local variables given in the function header with the corresponding expressions' results as initial values are created. At function exit, the frame is dropped. Since arguments are stored in ordinary variables, these variables may be freely modified in a function body. There is a special expression which is only meaningful in function bodies. The syntax is the following:

return <retval>
With this expression, you can skip right out of the current function body with return value retval. The behaviour of this command outside function calls is undefined.

3.8 Controlling Structures

Controlling structures are not entirely unlike C structures, but there are minor differences. Mind the syntax of the for cycle and the switch especially!
if (<condexp>) <thenexp> [ else <elseexp> ]
condexp is evaluated. If it's value is not 0, thenexp is evaluated, and the also result of the whole expression is the result of thenexp. If condexp yields 0, then either elseexp is evaluated or 0 is returned. The following use of a condition perhaps looks weird to a C programmer, but completely accepted in Calc:
     Result = if Test then a^2 else sqrt(b)
while (<condexp>) <doexp>
Creates a loop. In each cycle, condexp is evaluated. If it returns 0, the loop is broken, otherwise doexp is executed (its result is dropped) and the loop is restarted. This expression always returns 0.
do <doexp> while (<condexp>)
Creates a loop, similar to the previous one, the only difference that condexp is evaluated after the execution of doexp. Always returns 0. Has the same effect as
     <doexp>; while (<condexp>) <doexp>
for (<iniexp>, <testexp>, <stepexp>) <exp>
Although the behaviour is identical to that of a C for cycle, the syntax is somewhat different: there are commas (,) instead of semicolons (;) in the head. This is so, because in Calc, semicolons are used to separate expressions from each other. iniexp is evaluated exactly once at the beginning of the cycle. The cylcle is continued as long as testexp evaluates to a value different than 0. In each cycle first exp, then stepexp is evaluated. The return value of the whole expression is 0. Has the same effect as
     <iniexp>; while (<testexp>) (<exp>; <stepexp>)
Note that in this equivalent form, the parentheses are necessary, otherwhise stepexp would be evaluated only once, after the complete cycle has finished.
switch (<exp>) { case <testexp1> : <bodyexp1> case <testexp2> : <bodyexp2> ... [ default: <defaultbody> ] }
This syntax resembles the C switch syntax very much, but as the syntax so the behaviour differs a little. Here test expressions can also be arbitary expressions - they are not limited to literal constants. exp is evaulated exactly once, and its value is compared to the freshly evaluated testexps. When there's a match, the corresponding bodyexp is evaluated and the result is returned as the result of the whole expression. The default case always matches any values, therefore there is no meaning in putting more cases after it. The type of the test expressions must match the type of the switching expression (i.e., number or string). Please note, that the braces used in switch do not create a new variable frame! The following use of a switch perhaps looks weird to a C programmer, but completely accepted in Calc:
     monthname = switch (month) {
                   case  1: "January"
                   case  2: "Februray"
		   case  3: "March"
                   case  4: "April"
                   case  5: "May"
                   case  6: "June"
                   case  7: "July"
                   case  8: "August"
                   case  9: "September"
                   case 10: "October"
                   case 11: "November"
                   case 12: "December"
                   default: error("unknown month")
                 }

3.9 Precedences

The precedences of the operators and linguistical terms is summarised in the following table. It is beginning with the lowest (weakest) precedence and ended with the highest (strongest).

Lowest;
 return
 =, +=, -=, *=, /=, %=, ^=
 if, do, while, for, switch, var, fun
 ||
 &&
 !
 ==, != <, >, <=, >=
 +, - (subtraction)
 *, /, %
 - (negation)
 ^
Highest++, --

It is important to notice, that the semicolon (;) has the lowest precedence, therefor expression sequences must almost always be grouped with parentheses or braces, except where unambiguous.

3.10 A Simple Example

This function calculates the square root of a number with a given precision using Newton's method. Even in such a simple example you may notice many advanced features of Calc.
     fun n_sqrt(num, prec) =
     {
       var (root, prev);
     
       root = num/2;
       do {
     	 prev = root;
     	 root = (num/root + root) / 2
       } while (abs(prev - root) > prec);
       root
     }

4 Error Reporting

If a parsing error occurs (you have written something with the wrong syntax, or you assumed operators to have precendences other than their real one), it is reported immediately when the expression is parsed, e.g., when you press enter or when you open a file. In the former case, the position is specified by telling at which character did the error occur, in the latter case, filename and line number are also reported. When an error occurs during a user function call, it is reported as though it occured in the file the function was originally defined in. If the function was defined in the main entry, a pseudo filename toplevel is used.

If there is an error which is recognised only at runtime, it is reported when the critical piece of code is evaluated. From that point, the error propagates straight up, meaning that none of the following expressions is evaluated, cycles are broken and functions are returned from. If the error is caught with an onerror or iserr call, it is dropped, otherwise it is brought to toplevel, and reported to the user. It is only important to understand the propagation of errors if you want to catch them, although this is often unavoidable.

When you press the Abort button during an evaluation, all calculations are stopped immediately, and "user break" is reported. Abort propagates like errors do, only user breaks can't be caught or tested for.

The possible error messages are summarised in the following table, but some of them should not occur at all (another error should occur somewhat earlier instead or it is caused by internal faults), these are marked with an asterisk.

"parse error"
This occurs during evaluation parsing. Always means a syntax error. Check for semicolons and precedences first!
"lval required"
At the specified position you have given a compound expression where a variable was expected.
"name clash"
In a user function definition you used the same name twice in the parameter list. Use another name instead.
"undefined fun"
You have written something that looks like a function call, but no function under that name was found.
"argcnt error"
You have specified an invalid number of arguments in a function call.
"double expected"
A number is expected where most probably a string was found, which couldn't be transformed to a number automatically.
 
 
"invalid test" *
In a switch case, the type of the given expression is "invalid value".
"fundef error" *
Invalid function defintion - internal error. You have found a bug!