TweetFollow Us on Twitter

Math Compiler
Volume Number:8
Issue Number:3
Column Tag:C Workshop

A Compiler for Math Equations

We’ve seen mathematical equation interpreters, what about a compiler?

By Kevin Raner, Mt. Waverley, Victoria, Australia

I am a scientist with an interest in Macintosh programming, and I a spend a large proportion of my spare time writing number crunching programs. I set out to write an application to use various algorithms to fit specific mathematical functions to some of my experimental data. I didn’t want to hard code these equations into my application as I thought it would be nice to have one version that would handle any equation you gave it. The first version of my program took the equation as a string of characters and stored it away. Then whenever it evaluated the equation, it parsed the string and did the appropriate calculations as it went along. However, this had one big disadvantage, namely it was very slow! Since what I had written was essentially an equation interpreter, the obvious way to improve things was to write an equation compiler.

This article describes such a compiler for mathematical equations. The code, written in THINK C, is designed to be incorporated into a project that needs to evaluate an equation in order to perform some useful task, e.g., optimization, least squares curve fitting, etc. When invoked, the compiler parses a string of characters and sets up a block of executable code in the heap. In this manner, the code for evaluating the equation can be compiled at run time. Consequently, it runs as quickly as if it had been hard coded in the first place. Hence an application can be developed without prior knowledge of what equation it might have to deal with.

An Example

The following code fragment illustrates the use of the equation compiler:

/* 1 */

#include"EqnCompiler.h"

extended(*eqnFn)(); /* func ptr to code */
Handle  eqnCode; /* handle to code block */

main()
{
 char   eqnText[80];
 int    theErr, index;
 extended x, y, a, b, c, coeff[5];

 /* allocate memory */
 eqnCode = NewHandle(0);
 /* equation text to be compiled */
 strcpy(eqnText, "eqnFn(x) = a*sin(b*x+c)");
 /* compile right hand side of equation */
 index = 11;
 theErr = CompileEqn(eqnText, strlen(eqnText),
 &index, eqnCode);
 HLock(eqnCode); /* lock code block */
 eqnFn = *eqnCode; /* set function ptr */

 coeff[0] = a; /* set coefficients */
 coeff[1] = b;
 coeff[2] = c;
 y = (*eqnFn)(x, coeff); /* call function */
}

The above fragment of code allocates a block of heap space to accommodate the new function. Next it sets up a string of characters that defines the equation as text. CompileEqn() is then called with a pointer to the text, the length of the text, the location of the beginning of the equation, and the handle to a relocatable block on the heap where the new function will reside. CompileEqn() expects the code block to be unlocked so that it may grow in size as the code is compiled. (If the equation text is relocatable, it should be locked in memory so that the pointer remains valid.) The third argument, &index, is the address of an index variable. When the routine is called, index informs the routine where to start parsing (position zero indicates the first character in the buffer). In the above example the first character to be parsed is ‘a’. The text buffer is parsed until the end is reached, a semicolon is found, or a syntax error is encountered. The use of a semicolon to mark the end of the equation allows comments to be appended. On the function’s return, index will have been incremented by the number of characters that were successfully parsed. If the text cannot be compiled due to a syntax error, the nature of the error is returned in theErr and its position is indicated in index. The possible errors that can be returned are listed in the file “EqnCompiler.h”. When the code block has been set up, it is locked and dereferenced to give the function pointer. Thereafter (*eqnFn)(x, coeff) is functionally identical to the following function:

/* 2 */

extendedeqnFn(extended x, extended *coeff)
{
 extended y;

 /* eqnFn(x) = a*sin(b*x+c) */
 y = coeff[0]*sin(coeff[1]*x+coeff[2]);
 return y;
}

The equation compiler always generates code that has the prototype shown above. The new function expects two arguments, x and a pointer to an array of coefficients. CompileEqn() recognizes five coefficients namely a, b, c, d, and e. If these coefficients are referenced in the equation text then their numerical values should be placed in an array before the function is called. A pointer to this array is passed as the second argument, NULL may be passed if you are sure that the equation text does not contain references to the coefficients. If you require more than five coefficients, you can edit the routine ScanFn() to handle extra coefficients.

Equation Syntax

The equation compiler recognizes the following operands: a, b, c, d, e, x, Π, pi, and numeric strings. The numeric strings may be expressed in scientific or non-scientific format. Unary minus and the following binary operators are allowed: + (addition), - (subtraction), * (multiplication), / (division) and ^ (exponentiation). The compiler recognizes the following elementary functions: sin(), cos(), tan() atan(), sqrt(), exp() and log() (natural). Custom unary functions may also be defined, the file “CustomFn.c” implements the functions asin() and acos(). The compiler is not case sensitive and space characters inserted between operands, operators, and functions are ignored. Parentheses may be used to change the priority of operations, and comments may be appended to the end of an equation using the semicolon delimiter, e.g., “2.1*(1-exp(-a*x)); first order growth”. The relative priorities of the operators are shown below.

Priority of Operators

( ) (parentheses)

^ (exponentiation)

- (unary minus)

* / (multiplication, division)

+ - (addition, subtraction)

How it Works

I’m not going to describe this in great detail; however, I will describe the roles of the various routines that make up the compiler. You’ll need to be familiar with assembly language to understand the routines that do the actual coding. The first routine called is CompileEqn(); this initializes the global variables and coordinates the other routines which perform specific tasks. All of these routines notify CompileEqn() of their success by returning an integer error code. CompileEqn() parses the text buffer by calling any of three scanning routines: ScanNum(), ScanFn() and ScanOp(). The global variable mode controls which routine is called next. There are two modes, SCAN_OPERAND and SCAN_OPERATOR. When mode is set to SCAN_OPERAND it expects the current location in the text to contain either a numerical substring; an operand such as x, c, Π etc.; a left hand parenthesis; or a substring denoting an external function, e.g., sin(x). The first situation is handled by ScanNum() which returns the number, in extended format, by address. The variable operand is then set to CONST_OPERAND to indicate that a numerical value has been stored in the variable num. ScanFn() deals with the other possibilities and returns an integer result, by address. If ScanFn() finds an operand, operand is set to either X_OPERAND, COEFF_OPERAND or PI_OPERAND to indicate what was found. If a coefficient was found, its offset from the start of the array coeff is stored in the variable offset (e.g. c is offset 20 bytes from the start of the array). If Π was found, num is set to the value of pi as determined by the SANE function pi(). If ScanFn() finds a unary function, e.g., unary minus, sin(x) etc., the integer result contains information about the unary operation (see below).

If mode is SCAN_OPERATOR, ScanOp() is called. This scans for a binary operator or a right hand parenthesis and returns an integer result, by address. All operations are ranked in an order of priority and are executed in order. The ranking depends on the priority of an operation and on how deeply it’s embedded in parentheses. Details about the pending operations are stored in the array operation[] as long integers. Operation[0] describes the oldest entry in the queue with more recent operations having higher index numbers. The long integer entries in the array operation contain the following information.

Operation Information

bits 31-16 level of parentheses

bits 15-12 relative priority of operation

bits 11-8 function type

bits 7-0 low byte of SANE opword

The parenthesis level is stored in the high word of the operation[] entry. The low word is the result returned by either ScanFn() or ScanOp(). The bits 15-12 contain the relative priority of the operation. All arithmetic operations and elementary functions are performed using the SANE software packages (see The Standard Apple Numerics Manual, 2nd Edition). Bits 11-8 indicate whether the operation will be handled by the fp68k package, elems68k package or by a customized unary function. If the operation is performed by the SANE software, then the least significant byte contains the low byte of the SANE opword. If the operation is a customized unary function, then bits 7-0 contain an index to an array of custom function pointers.

The routines OperandCode(), OperationCode(), UnOpCode(), BinOpCode1(), BinOpCode2(), ReturnCode1() and ReturnCode2() write MC68000 instructions into the block on the heap. OperandCode() sets up code to copy an operand into a stack frame used by the function. OperationCode() coordinates the routines that write instructions that invoke arithmetic or elementary functions. These routines (namely UnOpCode(), BinOpCode1() and BinOpCode2()) push the addresses of the operands onto the stack followed by the SANE opword. The package trap is then called and the mathematical operation is performed. UnOpCode(), BinOpCode1() and BinOpCode2() are commented with the symbolic assembler representations of the instructions that they write to the code block.

The functions ReturnCode1() and ReturnCode2() write instructions that copy the computed result to a location specified by the caller. Usually ReturnCode2() is called which copies the result from the stack frame. The purpose of ReturnCode1() is to write more efficient code for trivial functions such as eqnFn(x) = x. Rather than copy x into the stack frame and then back out to the result address, it copies x directly to the result address without setting up a stack frame.

Custom Unary Functions

Most of the mathematical functions you may need for your project are included in the SANE packages. However, you may need to define your own functions for use with the equation compiler. For example, you may need base-10 logarithm functions, inverse trigonometric or hyperbolic trigonometric functions etc. The file “CustomFn.c” contains two predefined custom functions. (Excuse the phrase “predefined custom”. It’s a bit like “military intelligence”.)

These are included to illustrate how you can customize the equation compiler. To add your own functions to the equation compiler you edit “CustomFn.c” in the following way. First, you must redefine the macro NUM_CF to the total number of custom functions in the file. Second, you need to declare the functions. Your custom functions must have the prototype:

extendedMyFunc(extended);

Third, the function pointers to your custom functions must be added to the array CFPtr[]. Similarly, pointers to the corresponding keyword strings must be included in the array CFKeyword[] with matching index numbers. Finally, you need to include the source code for your custom functions.

When the routine ScanFn() encounters one of your custom keywords it will cause UnOpCode() to write code to call your custom function. The custom functions already included are saneasin() and saneacos(). These are SANE versions of their ANSI counterparts asin() and acos() (see The Standard Apple Numerics Manual, 2nd Edition. Chapter 9). The ANSI functions return zero when they are passed an invalid argument whereas the SANE functions return a NAN (Not-A-Number) code.

SANE, the Code and the MC68881 Option

For complete compatibility with all machines, the code that is assembled by the equation compiler performs all of its calculations using 80 bit floating point data types with calls to the SANE packages. If the MC68881 coprocessor is available, the SANE packages will take advantage of its presence, otherwise they will do all the work themselves. The code that is assembled by the equation compiler does not take direct advantage of the MC68881 coprocessor, and so there is no advantage in enabling THINK C’s MC68881 option when compiling your project. However, you may have other reasons for setting this option in your project. The code in “EqnCompiler.c” is written so that it will compile correctly with this option, but bear the following comments in mind.

The MC68881 option sets the size of type double to 96 bits and the extended data type remains as 80 bits. The equation code that will be assembled at run time expects 80 bit (extended) arguments and returns an 80 bit (extended) result. This means a 96 bit interface must be defined to call the equation function (see below). Note that the array of coefficients must contain values in 80 bit extended format.

/* 3 */

extended(*eqnFn80)();/* ptr to 80 bit func */
double  eqnFn(double, extended *);

/* 96 bit interface function */
double  eqnFn(double x96, extended *coeff)
{
 extended x80, y80;
 double y96;

 /* convert x from 96 to 80 bits */
 x96tox80(&x96, &x80);
 /* 80 bit func */
 y80=(*eqnFn80)(x80, coeff);
 /* convert result from 80 to 96 bits */
 x80tox96(&y80, &y96);
 /* return 96 bit result */  
 return y96;
}

One other point to remember is that custom unary functions are expected to take an 80 bit (extended) and return an 80 bit (extended) result. Custom functions defined by you in “CustomFn.c” must conform to this requirement. The functions saneasin() and saneacos() manipulate extended variables as if they are doubles. While this is allowed without the MC68881 option, the compiler will report errors if this option is enabled. If you want to use the MC68881 option, you must rewrite or omit these two functions.

The Demo Program

To illustrate the use of EqnCompiler, I have written a small demonstration application. In order to keep it small I’ve programmed the visual interface as a dialog box, in this manner Dialog Manager handles most of the tedious work. The code for this application is listed in the files “Demo.c” and “Demo.h”. To put together the project you need to include the ANSI, MacTraps and SANE libraries.

The completed application allows you to type a string representing an equation into the field labelled “f(x) =”. The typed string, bar comments, is then compiled when the “Compile” button is clicked. The “Plot” button does the obvious. The equation that is plotted is the most recently compiled equation that is evaluated with the current coefficients (left hand fields). The range and domain of the plot are controlled by the bottom fields.

Figure 1: The demonstration program

For those assembly language programmers who’d like to see exactly what the assembled equation code looks like, I suggest modifying “Demo.c” to write the contents of the eqnCode block into a disk file. You can then open this file with a disassembler and see what’s going on. In fact this was how I debugged EqnCompiler. As you can imagine, writing a collection of numbers to the heap and then jumping to it can produce some nasty results.

I hope this equation compiler is of use to some of you number crunchers. In the process of writing this I’ve certainly learned a lot about SANE and assembly language. Enjoy.

Final Note

At the time of submission, a similar article appeared in MacTutor, see “A Practical Parser” Vol. 7, page 8. In this article Bill Murray describes a mathematical parser of his own. However, my parser differs in that it is integrated with a compiler. This allows efficient evaluation of an equation using fast compiled object code.

Listing:  EqnCompiler.h
/* EqnCompiler and Demonstration program. */
/* Written for THINK C, compile with MacHeaders */

/* Header file for EqnCompiler */

/* maximum number of pending operations */
#define MAX_PENDING20
/* maximum length for a numeric constant */
#define MAX_CONST_LEN20
/* maximum length for a custom keyword */
#define MAX_KWORD_LEN10

#define OPERAND_SCAN 1
#define OPERATOR_SCAN2
#define PARENTH  0
#define RANK0xFFFFF000
/* operands */
#define X_OPERAND0x1000
#define COEFF_OPERAND0x2000
#define CONST_OPERAND0x4000
#define PI_OPERAND 0x4000
/* operations - in order of priority */
#define PRIORITY_1 0x1000 /* add, subtract */
#define PRIORITY_2 0x2000 /* mult, divide */
#define UN_MINUS 0x3000 /* unary minus */
#define EXPONENT 0x4000 /* raise power */
#define UN_FUNC  0x5000 /* unary func */
/* function types */
#define FP68K    0x0100
#define ELEMS68K 0x0200
#define CUSTOM   0x0400

/* errors returned by CompileEqn() */
#define nullEqnErr 1
/* only white space chars found in text */
#define memoryErr2
/* problems allocating memory for code */
#define longNumErr 3
/* numeric constant too long */
#define badNumErr4
/* invalid numeric constant */
#define noOperandErr 5
/* expecting operand or function */
#define noOperatorErr6
/* missing operator */
#define badTokenErr7
/* unrecognized characters */
#define noLeftParenErr  8
/* missing left parenthesis in function */
#define unbalParenErr9
/* unbalanced parentheses */
#define tooManyOpErr 10
/* too many pending operations */
#define miscErr  11
/* other error */

/* function prototypes */
intCompileEqn(void *, int, int *, Handle);
intScanNum(extended *);
intScanFn(int *);
intScanOp(int *);
intOperationCode(int, int, int, extended *);
intOperandCode(int, int, extended *);
intUnOpCode(int);
intBinOpCode1(int, int, int);
intBinOpCode2(int);
intReturnCode1(int, int, extended *);
intReturnCode2(void);
intLookUpCF(char *, int *);
Listing:  EqnCompiler.c

/* Contains the equation compiler */

#include<ctype.h>
#include<SANE.h>
#include"EqnCompiler.h"

/* Global Variables */
static int**codeBlock; /* handle to code */
static char *textPtr; /* ptr to eqn text */
static inttextLen; /* length of eqn text */
static char mode; /* text scan mode */
static int*textIndex; /* current scan posn */
static intcodeIndex; /* current write posn */
static intframeIndex; /* stack frame posn */
static intframeSize; /* stack frame size */
extern ProcPtr CFPtr[]; /* custom func ptrs */

intCompileEqn(void *textBuff, int length, 
int *index, Handle codeHand)
{
 int  level=0; /* parenthesis level */
 int  operand=0; /* current operand type */
 int  offset; /* coeff offset from array ptr */
 long operation[MAX_PENDING]; /* stored operns */
 int  opIndex=0; /* next posn in operation[] */
 int  result, error;
 extended num;
 
 /* re-initialize globals */
 codeBlock = (int **) codeHand;
 textPtr = textBuff;
 textIndex = index;
 textLen = length;
 frameIndex = 0;
 frameSize = 0;
 mode = OPERAND_SCAN;
 
 SetHandleSize(codeBlock, 0); /* zero code blk */
 if (MemError()) return memoryErr;
 
 /* get first char, ignore white space chars */
 while (isspace(*(textPtr+*textIndex)) && 
 *textIndex<textLen) (*textIndex)++;
 if (*textIndex>=textLen || *(textPtr+*textIndex)==';')
 return nullEqnErr;
 
 /* code for setting up a stack frame */
 codeIndex = 2;
 SetHandleSize(codeBlock, 4);
 if (MemError()) return memoryErr;
 **codeBlock = 0x4E56; /* link A6,#-__ */
 /* fill in size of frame later */
 
 while (true) {
 if (mode == OPERAND_SCAN) {
 /* scan for operand or unary function */
 if ((*(textPtr+*textIndex)=='.') ||
 isdigit(*(textPtr+*textIndex))) {
 /* scan for numeric constant */
 if (operand) {
 /* copy old operand to frame */
 error = OperandCode(operand, offset, &num);
 if (error) return error;
 }
 error = ScanNum(&num);
 if (error) return error;
 operand = CONST_OPERAND;
 } else {
 /* scan for operand or unary function */
 error = ScanFn(&result);
 if (error) return error;
 switch (result & 0xF000) {
 case PARENTH: /* ( */
 level++; /* inc parenth level */
 break;
 case X_OPERAND: /* x */
 case COEFF_OPERAND: /* a, b, c   */
 case PI_OPERAND: /* Π */
 if (operand) {
 /* copy old operand to frame */
 error = OperandCode(operand, offset, &num);
 if (error) return error;
 }
 operand = result & 0xF000;
 if (operand == PI_OPERAND) num = pi();
 else if (operand == COEFF_OPERAND)
   offset = result & 0x00FF;
 break;
 case UN_MINUS: /* unary minus */
 case UN_FUNC: /* unary function */
 opIndex++;
 if (opIndex > MAX_PENDING)
 return tooManyOpErr;
 /* add operation to queue */
 operation[opIndex-1] = level*0x10000 + result;                
 if ((result & 0xF000) != UN_MINUS)
 level++; /* inc parenth level */
 }
 }
 } else if (mode == OPERATOR_SCAN) {
 /* scan for binary operator */
 error = ScanOp(&result);
 if (error) return error;
 if (result) { /* found operator */
 opIndex++;
 if (opIndex > MAX_PENDING)
 return tooManyOpErr;
 /* add operation to queue */
 operation[opIndex-1] = level*0x10000 + result;
 } else { /* ')' */
 level--; /* decrement parenth level */
 if (level < 0) return unbalParenErr;
 }
 }
 /* compact pending operation array */
 while (opIndex > 1) {
 if ((operation[opIndex-1]&RANK) <=
 (operation[opIndex-2]&RANK)) {
 error = OperationCode(operation[opIndex-2],
 operand, offset, &num); /* add opern to code */
 if (error) return error;
 operand = 0;
 /* remove operation from queue */
 operation[opIndex-2] = operation[opIndex-1];
 opIndex--;
 } else break;
 }
 /* get next token, ignore white space chars */
 while (isspace(*(textPtr+*textIndex)) &&
 *textIndex<textLen) (*textIndex)++;
 if (*textIndex>=textLen || *(textPtr+*textIndex)==';') {
 if (mode==OPERAND_SCAN)
 return noOperandErr;
 else break;
 }
 }
 
 /* add pending operations to code */
 while (opIndex > 0) {
 error = OperationCode(operation[opIndex-1],
 operand, offset, &num);
 if (error) return error;
 operand = 0;
 opIndex--; /* remove opern from queue */
 }
 
 if (level) return unbalParenErr;
 /* set frame size of initial link instrn */
 *(*codeBlock+1) = -10*frameSize;

 if (operand) /* code for return value */
 error = ReturnCode1(operand, offset, &num);
 else error = ReturnCode2();
 if (error) return error;
 codeIndex += 2; /* unlink and rts code */
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 *(*codeBlock+codeIndex-2)=0x4E5E; /* unlk A6 */
 *(*codeBlock+codeIndex-1)=0x4E75; /* rts */
 return 0;
}

/* scan the text for a number */
intScanNum(extended *num)
{
 int    i=0;
 char   valid;
 char   str[MAX_CONST_LEN+1]; /* C str */
 short  index;
 decimaldec;
 
 do { /* find longest valid numeric string */
 if (i>=MAX_CONST_LEN) return longNumErr;
 str[i] = *(textPtr+*textIndex+i);
 str[i+1] = 0; /* NULL terminator */
 index = 0;
 cstr2dec(str, &index, &dec, &valid);
 if (!valid) break;
 i++;
 if (*textIndex+i>=textLen || *(textPtr+*textIndex+i)==';') break;
 } while (valid);
 
 if (!index) return badNumErr;
 *num = dec2num(&dec);
 if (classextended(*num)<3 && classextended(*num)>-3)
 return badNumErr; /* NAN, INF */
 *textIndex += index;
 mode = OPERATOR_SCAN;
 return 0;
}

/* scan text for operand or function */
intScanFn(int *result)
{
 char str[MAX_KWORD_LEN+1]; /* pascal str */
 int  error, i=0;
 
 /* get longest alphabetic substring */
 while (isalpha(*(textPtr+*textIndex+i))) {
 if (i>MAX_KWORD_LEN) return badTokenErr;
 str[i+1] = *(textPtr+*textIndex+i);
 i++;
 if (*textIndex+i>=textLen || *(textPtr+*textIndex+i)==';') break;
 }
 
 str[0] = i; /* length byte */
 switch (i) {
 /* match string against expected strings */
 case 0: /* first char was not alphabetic */
 if (*(textPtr+*textIndex) == '-')
 /* unary minus */
 *result = UN_MINUS+FP68K+(FNEGX&0x00FF);
 else if (*(textPtr+*textIndex) == '(')
 *result = PARENTH; /* '(' */
 else if (*(textPtr+*textIndex) == 'Π' ||
 *(textPtr+*textIndex) == '½') {
 *result = PI_OPERAND; /* pi */
 mode = OPERATOR_SCAN;
 } else return noOperandErr;
 (*textIndex)++;
 return 0;
 case 1: /* single alphabetic character */
 if (tolower(str[1]) >= 'a' && tolower(str[1]) <= 'e') {
 /* coefficient */
 switch (tolower(str[1])) {
 case 'a':
 *result = COEFF_OPERAND;
 break;
 case 'b':
 *result = COEFF_OPERAND + 10;
 break;
 case 'c':
 *result = COEFF_OPERAND + 20;
 break;
 case 'd':
 *result = COEFF_OPERAND + 30;
 break;
 case 'e':
 *result = COEFF_OPERAND + 40;
 }
 } else if (tolower(str[1]) == 'x')
 *result = X_OPERAND; /* x */
 else goto lookup; /* custom func? */
 (*textIndex)++;
 mode = OPERATOR_SCAN;
 return 0;
 case 2: /* two alphabetic characters */
 if (EqualString(str, "\ppi", false, true))
 *result = PI_OPERAND; /* pi */
 else goto lookup; /* custom func? */
 *textIndex += 2;
 mode = OPERATOR_SCAN;
 return 0;
 case 3: /* three alphabetic characters */
 if (EqualString(str, "\psin", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FSINX&0x00FF);
 else if (EqualString(str, "\pcos", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FCOSX&0x00FF);
 else if (EqualString(str, "\ptan", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FTANX&0x00FF);
 else if (EqualString(str, "\plog", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FLNX&0x00FF);
 else if (EqualString(str, "\pexp", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FEXPX&0x00FF);
 else goto lookup; /* custom func? */
 break;
 case 4: /* four alphabetic characters */
 if (EqualString(str, "\psqrt", 0, 1))
 *result = UN_FUNC+FP68K+(FSQRTX&0x00FF);
 else if (EqualString(str, "\patan", 0, 1))
 *result = UN_FUNC+ELEMS68K+(FATANX&0x00FF);
 else goto lookup; /* custom func? */
 break;
 default: /* other string lengths */
 lookup: /* custom func? */
 error = LookUpCF(str, result);
 if (error) return error;
 }

 /* check next char is '(' */
 while (isspace(*(textPtr+*textIndex+i)) &&
 *textIndex+i<textLen) i++;
 if (*textIndex+i>=textLen || *(textPtr+*textIndex+i)==';' ||
 *(textPtr+*textIndex+i)!='(')
 return noLeftParenErr;
 *textIndex += i+1;
 return 0;
}

/* scan text for an operator */
intScanOp(int *result)
{
 switch (*(textPtr+*textIndex)) {
 case '+': /* addition */
 *result = PRIORITY_1+FP68K+(FADDX&0x00FF);
 break;
 case '-': /* subtraction */
 *result = PRIORITY_1+FP68K+(FSUBX&0x00FF);
 break;
 case '*': /* multiplication */
 *result = PRIORITY_2+FP68K+(FMULX&0x00FF);
 break;
 case '/': /* division */
 *result = PRIORITY_2+FP68K+(FDIVX&0x00FF);
 break;
 case '^': /* exponentiation */
 *result = EXPONENT+ELEMS68K+(FXPWRY&0x00FF);
 break;
 case ')': /* end of parenthesis */
 *result = PARENTH;
 break;
 default: /* syntax error */
 return noOperatorErr;
 }

 /* don't change mode if ')' encountered */
 if (*result != PARENTH) mode = OPERAND_SCAN;
 (*textIndex)++;
 return 0;
}

/* classify operation, call handler routines */
intOperationCode(int operation, int operand, 
int offset, extended *numPtr)
{
 int  error;
 
 if ((operation&0xF000) == UN_MINUS ||
 (operation&0xF000) == UN_FUNC) {
 /* unary operation */
 if (operand) {
 error = OperandCode(operand, offset, numPtr);
 if (error) return error;
 }
 error = UnOpCode(operation);
 if (error) return error;
 } else {
 /* binary operation */
 if (operand == X_OPERAND || operand == COEFF_OPERAND) {
 error = BinOpCode1(operation, operand, offset);
 if (error) return error;
 } else {
 if (operand == CONST_OPERAND) {
 error = OperandCode(CONST_OPERAND, 0, numPtr);
 if (error) return error;
 }
 error = BinOpCode2(operation);
 if (error) return error;
 }
 }
 return 0;
}

/* set up code for copying operand into stack frame */
intOperandCode(int operand,  int offset, extended *numPtr)
{
 frameIndex++;
 if (frameIndex>frameSize) frameSize=frameIndex;
 if (operand == X_OPERAND) {
 codeIndex += 7;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* lea.l -10fi(A6),A0; A0 <- frame_adr */
 *(*codeBlock+codeIndex-7) = 0x41EE;
 *(*codeBlock+codeIndex-6) = -10*frameIndex;
 /* lea.l 12(A6),A1; A1 <- x_adr */
 *(*codeBlock+codeIndex-5) = 0x43EE;
 *(*codeBlock+codeIndex-4) = 0x000C;
 /* move.l (A1)+,(A1)+; copy x into frame */
 *(*codeBlock+codeIndex-3) = 0x20D9;
 /* move.l (A1)+,(A1)+ */
 *(*codeBlock+codeIndex-2) = 0x20D9;
 /* move.w (A1)+,(A1)+ */
 *(*codeBlock+codeIndex-1) = 0x30D9;
 } else if (operand == COEFF_OPERAND) {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 22(A6),A0; A0 <- coeff base adr */
 *(*codeBlock+codeIndex-2) = 0x206E;
 *(*codeBlock+codeIndex-1) = 0x0016;
 if (offset) {
 codeIndex += 3;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l off(A0),-10fi(A6); copy coeff */
 *(*codeBlock+codeIndex-3) = 0x2D68;
 *(*codeBlock+codeIndex-2) = offset;
 *(*codeBlock+codeIndex-1) = -10*frameIndex;
 } else {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l (A0),-10fi(A6); copy coeff */
 *(*codeBlock+codeIndex-2) = 0x2D50;
 *(*codeBlock+codeIndex-1) = -10*frameIndex;
 }
 codeIndex += 6;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l off+4(A0),-10fi+4(A6); copy coeff */
 *(*codeBlock+codeIndex-6) = 0x2D68;
 *(*codeBlock+codeIndex-5) = offset+4;
 *(*codeBlock+codeIndex-4) = -10*frameIndex+4;
 /* move.w off+8(A0),-10fi+8(A6); copy coeff */
 *(*codeBlock+codeIndex-3) = 0x3D68;
 *(*codeBlock+codeIndex-2) = offset+8;
 *(*codeBlock+codeIndex-1) = -10*frameIndex+8;
 } else if (operand == CONST_OPERAND) {
 codeIndex += 11;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l #__,-10fi(A6); frame <- const */
 *(*codeBlock+codeIndex-11) = 0x2D7C;
 *(*codeBlock+codeIndex-10) = *((int *) numPtr);
 *(*codeBlock+codeIndex-9) = *((int *) numPtr+1);
 *(*codeBlock+codeIndex-8) = -10*frameIndex;
 /* move.l #__,-10fi+4(A6) */
 *(*codeBlock+codeIndex-7) = 0x2D7C;
 *(*codeBlock+codeIndex-6) = *((int *) numPtr+2);
 *(*codeBlock+codeIndex-5) = *((int *) numPtr+3);
 *(*codeBlock+codeIndex-4) = -10*frameIndex+4;
 /* move.w #__,-10fi+8(A6) */
 *(*codeBlock+codeIndex-3) = 0x3D7C;
 *(*codeBlock+codeIndex-2) = *((int *) numPtr+4);
 *(*codeBlock+codeIndex-1) = -10*frameIndex+8;
 } else return miscErr;
 
return 0;
}

/* write code for unary function, operand in stack frame */
intUnOpCode(int operation)
{
 if (!frameIndex) return miscErr;
 if ((operation&0x0F00)==FP68K ||
 (operation&0x0F00)==ELEMS68K) { /* SANE func */
 codeIndex += 5;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* pea.l -10fi(A6); push operand_adr */
 *(*codeBlock+codeIndex-5) = 0x486E;
 *(*codeBlock+codeIndex-4) = -10*frameIndex;
 /* move.w opword, -(A7); push SANE opword */
 *(*codeBlock+codeIndex-3) = 0x3F3C;
 *(*codeBlock+codeIndex-2) = operation&0x00FF;
 if ((operation&0x0F00) == FP68K)
 *(*codeBlock+codeIndex-1)=0xA9EB;/* _Pack4 */
 else if ((operation&0x0F00) == ELEMS68K)
 *(*codeBlock+codeIndex-1)=0xA9EC;/* _Pack5*/
 else return miscErr;
 } else if ((operation&0x0F00) == CUSTOM) {
 /* custom unary function */
 codeIndex += 14;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l -10fi+6(A6),-(A7); push operand */
 *(*codeBlock+codeIndex-14) = 0x2F2E;
 *(*codeBlock+codeIndex-13) = -10*frameIndex+6;
 /* move.l -10fi+2(A6),-(A7) */
 *(*codeBlock+codeIndex-12) = 0x2F2E;
 *(*codeBlock+codeIndex-11) = -10*frameIndex+2;
 /* move.w -10fi(A6),-(A7) */
 *(*codeBlock+codeIndex-10) = 0x3F2E;
 *(*codeBlock+codeIndex-9) = -10*frameIndex;
 /* pea.l -10fi(A6); push result adr */
 *(*codeBlock+codeIndex-8) = 0x486E;
 *(*codeBlock+codeIndex-7) = -10*frameIndex;
 /* move.l #__,A0; A0 <- func_adr */
 *(*codeBlock+codeIndex-6) = 0x207C;
 *(*codeBlock+codeIndex-5) =
 HiWord((long) CFPtr[operation&0x00FF]);
 *(*codeBlock+codeIndex-4) =
 LoWord((long) CFPtr[operation&0x00FF]);
 /* jsr (A0); jump to func */
 *(*codeBlock+codeIndex-3) = 0x4E90;
 /* lea.l 14(A7),A7; reset stack ptr */
 *(*codeBlock+codeIndex-2) = 0x4FEF;
 *(*codeBlock+codeIndex-1) = 0x000E;
 } else return miscErr;
 return 0;
}

/* set up binary operation code - only destination operand in stack frame 
*/
intBinOpCode1(int operation, int operand, int offset)
{
 if (frameIndex < 1) return miscErr;
 if (operand == X_OPERAND) {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* pea.l 12(A6); push x_adr */
 *(*codeBlock+codeIndex-2) = 0x486E;
 *(*codeBlock+codeIndex-1) = 0x000C;
 } else if (operand == COEFF_OPERAND) {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 22(A6),A0; A0 <- coeff base adr */
 *(*codeBlock+codeIndex-2) = 0x206E;
 *(*codeBlock+codeIndex-1) = 0x0016;
 if (offset) {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* pea.l off(A0); push coeff_adr */
 *(*codeBlock+codeIndex-2) = 0x4868;
 *(*codeBlock+codeIndex-1) = offset;
 } else {
 codeIndex++;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* pea.l (A0); push coeff_adr */
 *(*codeBlock+codeIndex-1) = 0x4850;
 }
 } else return miscErr;
 codeIndex += 5;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /*  pea.l -10fi(A6); push dest_adr  */
 *(*codeBlock+codeIndex-5) = 0x486E;
 *(*codeBlock+codeIndex-4) = -10*frameIndex;
 if ((operation&0xF000) == EXPONENT) {
 /* move.w FXPWRY,-(A7); push FXPWRY */
 *(*codeBlock+codeIndex-3) = 0x3F3C;
 *(*codeBlock+codeIndex-2) = FXPWRY;
 *(*codeBlock+codeIndex-1)=0xA9EC;/* _Pack5 */
 } else if ((operation&0x0F00) == FP68K) {

 /* move.w opword,-(A7); push SANE opword */
 *(*codeBlock+codeIndex-3) = 0x3F3C;
 *(*codeBlock+codeIndex-2) = operation&0x00FF;
 *(*codeBlock+codeIndex-1)=0xA9EB;/* _Pack4 */
 } else return miscErr;
 return 0;
}

/* set up binary operation code - both operands are in stack frame */
intBinOpCode2(int operation)
{
 if (frameIndex < 2) return miscErr;
 codeIndex += 7;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* pea.l -10fi(A6); push src operand */
 *(*codeBlock+codeIndex-7) = 0x486E;
 *(*codeBlock+codeIndex-6) = -10*frameIndex;
 /* pea.l -10fi+10(A6); push dest operand */
 *(*codeBlock+codeIndex-5) = 0x486E;
 *(*codeBlock+codeIndex-4) = -10*frameIndex+10;
 if ((operation&0xF000) == EXPONENT) {
 /* move.w FXPWRY,-(A7); push FXWPRY */
 *(*codeBlock+codeIndex-3) = 0x3F3C;
 *(*codeBlock+codeIndex-2) = FXPWRY;
 *(*codeBlock+codeIndex-1)=0xA9EC;/* _Pack5 */
 } else if ((operation&0x0F00) == FP68K) {
 /* move.w opword,-(A7); push SANE opword */
 *(*codeBlock+codeIndex-3) = 0x3F3C;
 *(*codeBlock+codeIndex-2) = operation&0x00FF;
 *(*codeBlock+codeIndex-1)=0xA9EB;/* _Pack4 */
 } else return miscErr;
 frameIndex--; /* decrement frame position */
 return 0;
}

/* copy operand to address specified by 8(A6) */
intReturnCode1(int operand, int offset, extended *numPtr)
{
 if (operand == CONST_OPERAND) {
 codeIndex += 10;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 8(A6),A0; A0 <- result_adr */
 *(*codeBlock+codeIndex-10) = 0x206E;
 *(*codeBlock+codeIndex-9) = 0x0008;
 /* move.l #__,(A0)+; copy const to result_adr */
 *(*codeBlock+codeIndex-8) = 0x20FC;
 *(*codeBlock+codeIndex-7) = *((int *) numPtr);
 *(*codeBlock+codeIndex-6) = *((int *) numPtr+1);
 /* move.l #__,(A0)+ */
 *(*codeBlock+codeIndex-5) = 0x20FC;
 *(*codeBlock+codeIndex-4) = *((int *) numPtr+2);
 *(*codeBlock+codeIndex-3) = *((int *) numPtr+3);
 /* move.w #__,(A0)+ */
 *(*codeBlock+codeIndex-2) = 0x30FC;
 *(*codeBlock+codeIndex-1) = *((int *) numPtr+4);
 return 0;
 } else if (operand == X_OPERAND) {
 codeIndex += 4;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 8(A6),A0; A0 <- result_adr */
 *(*codeBlock+codeIndex-4) = 0x206E;
 *(*codeBlock+codeIndex-3) = 0x0008;
 /* lea.l 12(A6),A1; A1 <- x_adr */
 *(*codeBlock+codeIndex-2) = 0x43EE;
 *(*codeBlock+codeIndex-1) = 0x000C;
 } else if (operand == COEFF_OPERAND) {
 codeIndex += 4;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 8(A6),A0; A0 <- result_adr */
 *(*codeBlock+codeIndex-4) = 0x206E;
 *(*codeBlock+codeIndex-3) = 0x0008;
 /* move.l 22(A6),A1; A1 <- coeff base Adr */
 *(*codeBlock+codeIndex-2) = 0x226E;
 *(*codeBlock+codeIndex-1) = 0x0016;
 if (offset) {
 codeIndex += 2;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* lea.l off(A1),A1; A1 <- coeff_adr */
 *(*codeBlock+codeIndex-2) = 0x43E9;
 *(*codeBlock+codeIndex-1) = offset;
 }
 } else return miscErr;
 codeIndex += 3;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l (A1)+,(A0)+; copy operand to result */
 *(*codeBlock+codeIndex-3) = 0x20D9;
 /* move.l (A1)+,(A0)+ */
 *(*codeBlock+codeIndex-2) = 0x20D9;
 /* move.w (A1)+,(A0)+ */
 *(*codeBlock+codeIndex-1) = 0x30D9;
 return 0;
}

/* copy result to address in 8(A6) */
intReturnCode2(void)
{
 codeIndex += 7;
 SetHandleSize(codeBlock, 2*codeIndex);
 if (MemError()) return memoryErr;
 /* move.l 8(A6),A0; A0 <- result_adr */
 *(*codeBlock+codeIndex-7) = 0x206E;
 *(*codeBlock+codeIndex-6) = 0x0008;
 /* lea.l -10(A6),A1; A1 <- frame_adr */
 *(*codeBlock+codeIndex-5) = 0x43EE;
 *(*codeBlock+codeIndex-4) = 0xFFF6;
 /* move.l (A1)+,(A0)+; copy frame to result */
 *(*codeBlock+codeIndex-3) = 0x20D9;
 /* move.l (A1)+,(A0)+ */
 *(*codeBlock+codeIndex-2) = 0x20D9;
 /* move.w (A1)+,(A0)+ */
 *(*codeBlock+codeIndex-1) = 0x30D9;
 return 0;
}
Listing:  Demo.c

/* Code for demo program */

#include<SANE.h>
#include"Demo.h"
#include"EqnCompiler.h"

/* Global Variables */
Handle  eqnCode; /* handle to block of code */
char    **eqnText; /* handle to eqn text */
extended(*eqnFn)(); /* func ptr to eqn code */
extendedcoeff[5]; /* array of coefficients */
extendedminX, maxX, minY, maxY; /* plot range */
DialogPtr eqnDialog; /* dialog pointer */
CursHandleiBeam, watch;

main()
{
 Handle userItemH;
 int    i, type, itemHit, error, index;
 Rect   rect;
 
 InitStuff();
 /* get dialog */
 eqnDialog = GetNewDialog(300, NULL, (WindowPtr) -1);
 /* get handle to eqn text */
 GetDItem(eqnDialog, FUNCTION, &type, &eqnText, &rect);
 /* get user item (plot frame) */
 GetDItem(eqnDialog, FRAME, &type, &userItemH, &rect);
 /* install user item */
 SetDItem(eqnDialog, FRAME, type, FramePlot, &rect);
 /* centre window on screen */
 MoveWindow(eqnDialog, (screenBits.bounds.right-
 screenBits.bounds.left - 490)/2, 45, true);
 ShowWindow(eqnDialog);
 SetPort(eqnDialog);
 
 do {
 ModalDialog(DialogFilter, &itemHit);
 if (itemHit == COMPILE) { /* compile eqn */
 /* Code block should be unlocked and */
 /* eqn text buffer should be locked. */
 HUnlock(eqnCode);
 HLock(eqnText);
 index = 0; /* parse text from start */
 error = CompileEqn(*eqnText, GetHandleSize(eqnText),
 &index, eqnCode);
 if (error) {
 /* position of error is in index */
 SelIText (eqnDialog, FUNCTION, index, index);
 /* put up alert to explain error */
 SyntaxErr(error);
 eqnFn = NULL; /* null function ptr */
 } else {
 SelIText (eqnDialog, FUNCTION, 0, 0);
 /* compiled OK, lock code block and */
 /* dereference to get address of func */
 HLock(eqnCode);
 eqnFn = (void *) *eqnCode;
 }
 } else if (itemHit == PLOT) { /* plot eqn */
 /* update coefficients array */
 for (i=0; i<5; i++)
 coeff[i] = GetEditField(COEFF_A + i);
 /* update plot range */
 minX = GetEditField(MIN_X);
 maxX = GetEditField(MAX_X);
 minY = GetEditField(MIN_Y);
 maxY = GetEditField(MAX_Y);
 PlotEqn();
 }
 } while (itemHit != QUIT);
 
 DisposDialog(eqnDialog); /* tidy up & leave */
 HUnlock(eqnCode);
}

/* Plot the equation */
void  PlotEqn(void)
{
 extended x, y, vert;
 int    hPos=0, lastvPos, vPos;
 char   skip=2;
 Rect   plotRect, bigRect;
 
 if (!eqnFn) return; /* invalid function */
 if ((minX >= maxX) || (minY >= maxY))
 return; /* invalid plot range */
 SetRect(&plotRect, 170, 59, 470, 230);
 EraseRect(&plotRect); /* clear plot area */
 ClipRect(&plotRect); /* new clip region */
 SetCursor(*watch);
 
 do {
 /* calculate x */
 x = minX + (maxX - minX)*hPos/300.0;
 y = (*eqnFn)(x, coeff); /* calculate y */
 if (fabs(classextended(y)) < 2)
 skip = 2; /* y is a NAN */
 else { /* get new Y screen coord */
 lastvPos = vPos; /* save last Y coord */
 vert = rint(170*(maxY-y)/(maxY-minY));
 /* pin extreme values to window's edge */
 if (vert+59>eqnDialog->portRect.bottom)
 vPos = eqnDialog->portRect.bottom-59;
 else if (vert+59<eqnDialog->portRect.top)
 vPos = eqnDialog->portRect.top-59;
 else vPos = vert;
 if (y >= minY && y <= maxY) {
 /* y is in range */
 if (skip < 2) /* last y was not NAN */
 LineTo(170+hPos, 59+vPos);
 else MoveTo(170+hPos, 59+vPos);
 skip = 0;
 } else { /* y is out of range */
 if (!skip) /* last point was in range */
 LineTo(170+hPos, 59+vPos);
 else MoveTo(170+hPos, 59+vPos);
 skip = 1;
 }
 }
 } while ((hPos += 2) <= 300);
 
 SetRect(&bigRect, -32767, -32767, 32767, 32767);
 ClipRect(&bigRect); /* reset clip rect */
}
pascal Boolean DialogFilter(DialogPtr theDialog,
 EventRecord *theEvent, int *itemHit)
{
 Point  mousePt;
 int    item, type;
 char   c;
 Rect   rect;
 Handle hand;
 TEHandle dialogTE;
 
 if (theEvent->what == keyDown) {
 /* disable clear, enter keys in all fields */
 c = theEvent->message & charCodeMask;
 if (c == CLEAR_KEY || c == ENTER)
 return true;
 if (((DialogPeek) theDialog)->editField + 1
 == FUNCTION) {
 /* current edit field is function */
 if (c == TAB) {
 /* override default tab behaviour */
 dialogTE = ((DialogPeek) theDialog)->textH;
 TEKey(TAB, dialogTE);
 return true;
 }
 } else if (c == RETURN)
 /* disable return in other fields */
 return true;
 }
 
 /* set cursor as appropriate */
 GetMouse(&mousePt);
 item = 1 + FindDItem(theDialog, mousePt);
 if (item) {
 GetDItem(theDialog, item, &type, &hand, &rect);
 if (type == editText) SetCursor(*iBeam);
 else InitCursor();
 } else InitCursor();

 return false;
}
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Posterino 4.4 - Create posters, collages...
Posterino offers enhanced customization and flexibility including a variety of new, stylish templates featuring grids of identical or odd-sized image boxes. You can customize the size and shape of... Read more
Chromium 119.0.6044.0 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. List of changes available here. Version for Apple... Read more
Spotify 1.2.21.1104 - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
Tor Browser 12.5.5 - Anonymize Web brows...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Malwarebytes 4.21.9.5141 - Adware remova...
Malwarebytes (was AdwareMedic) helps you get your Mac experience back. Malwarebytes scans for and removes code that degrades system performance or attacks your system. Making your Mac once again your... Read more
TinkerTool 9.5 - Expanded preference set...
TinkerTool is an application that gives you access to additional preference settings Apple has built into Mac OS X. This allows to activate hidden features in the operating system and in some of the... Read more
Paragon NTFS 15.11.839 - Provides full r...
Paragon NTFS breaks down the barriers between Windows and macOS. Paragon NTFS effectively solves the communication problems between the Mac system and NTFS. Write, edit, copy, move, delete files on... Read more
Apple Safari 17 - Apple's Web brows...
Apple Safari is Apple's web browser that comes bundled with the most recent macOS. Safari is faster and more energy efficient than other browsers, so sites are more responsive and your notebook... Read more
Firefox 118.0 - Fast, safe Web browser.
Firefox offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals and casual... Read more
ClamXAV 3.6.1 - Virus checker based on C...
ClamXAV is a popular virus checker for OS X. Time to take control ClamXAV keeps threats at bay and puts you firmly in charge of your Mac’s security. Scan a specific file or your entire hard drive.... Read more

Latest Forum Discussions

See All

‘Monster Hunter Now’ October Events Incl...
Niantic and Capcom have just announced this month’s plans for the real world hunting action RPG Monster Hunter Now (Free) for iOS and Android. If you’ve not played it yet, read my launch week review of it here. | Read more »
Listener Emails and the iPhone 15! – The...
In this week’s episode of The TouchArcade Show we finally get to a backlog of emails that have been hanging out in our inbox for, oh, about a month or so. We love getting emails as they always lead to interesting discussion about a variety of topics... | Read more »
TouchArcade Game of the Week: ‘Cypher 00...
This doesn’t happen too often, but occasionally there will be an Apple Arcade game that I adore so much I just have to pick it as the Game of the Week. Well, here we are, and Cypher 007 is one of those games. The big key point here is that Cypher... | Read more »
SwitchArcade Round-Up: ‘EA Sports FC 24’...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 29th, 2023. In today’s article, we’ve got a ton of news to go over. Just a lot going on today, I suppose. After that, there are quite a few new releases to look at... | Read more »
‘Storyteller’ Mobile Review – Perfect fo...
I first played Daniel Benmergui’s Storyteller (Free) through its Nintendo Switch and Steam releases. Read my original review of it here. Since then, a lot of friends who played the game enjoyed it, but thought it was overpriced given the short... | Read more »
An Interview with the Legendary Yu Suzuk...
One of the cool things about my job is that every once in a while, I get to talk to the people behind the games. It’s always a pleasure. Well, today we have a really special one for you, dear friends. Mr. Yu Suzuki of Ys Net, the force behind such... | Read more »
New ‘Marvel Snap’ Update Has Balance Adj...
As we wait for the information on the new season to drop, we shall have to content ourselves with looking at the latest update to Marvel Snap (Free). It’s just a balance update, but it makes some very big changes that combined with the arrival of... | Read more »
‘Honkai Star Rail’ Version 1.4 Update Re...
At Sony’s recently-aired presentation, HoYoverse announced the Honkai Star Rail (Free) PS5 release date. Most people speculated that the next major update would arrive alongside the PS5 release. | Read more »
‘Omniheroes’ Major Update “Tide’s Cadenc...
What secrets do the depths of the sea hold? Omniheroes is revealing the mysteries of the deep with its latest “Tide’s Cadence" update, where you can look forward to scoring a free Valkyrie and limited skin among other login rewards like the 2nd... | Read more »
Recruit yourself some run-and-gun royalt...
It is always nice to see the return of a series that has lost a bit of its global staying power, and thanks to Lilith Games' latest collaboration, Warpath will be playing host the the run-and-gun legend that is Metal Slug 3. [Read more] | Read more »

Price Scanner via MacPrices.net

Clearance M1 Max Mac Studio available today a...
Apple has clearance M1 Max Mac Studios available in their Certified Refurbished store for $270 off original MSRP. Each Mac Studio comes with Apple’s one-year warranty, and shipping is free: – Mac... Read more
Apple continues to offer 24-inch iMacs for up...
Apple has a full range of 24-inch M1 iMacs available today in their Certified Refurbished store. Models are available starting at only $1099 and range up to $260 off original MSRP. Each iMac is in... Read more
Final weekend for Apple’s 2023 Back to School...
This is the final weekend for Apple’s Back to School Promotion 2023. It remains active until Monday, October 2nd. Education customers receive a free $150 Apple Gift Card with the purchase of a new... Read more
Apple drops prices on refurbished 13-inch M2...
Apple has dropped prices on standard-configuration 13″ M2 MacBook Pros, Certified Refurbished, to as low as $1099 and ranging up to $230 off MSRP. These are the cheapest 13″ M2 MacBook Pros for sale... Read more
14-inch M2 Max MacBook Pro on sale for $300 o...
B&H Photo has the Space Gray 14″ 30-Core GPU M2 Max MacBook Pro in stock and on sale today for $2799 including free 1-2 day shipping. Their price is $300 off Apple’s MSRP, and it’s the lowest... Read more
Apple is now selling Certified Refurbished M2...
Apple has added a full line of standard-configuration M2 Max and M2 Ultra Mac Studios available in their Certified Refurbished section starting at only $1699 and ranging up to $600 off MSRP. Each Mac... Read more
New sale: 13-inch M2 MacBook Airs starting at...
B&H Photo has 13″ MacBook Airs with M2 CPUs in stock today and on sale for $200 off Apple’s MSRP with prices available starting at only $899. Free 1-2 day delivery is available to most US... Read more
Apple has all 15-inch M2 MacBook Airs in stoc...
Apple has Certified Refurbished 15″ M2 MacBook Airs in stock today starting at only $1099 and ranging up to $230 off MSRP. These are the cheapest M2-powered 15″ MacBook Airs for sale today at Apple.... Read more
In stock: Clearance M1 Ultra Mac Studios for...
Apple has clearance M1 Ultra Mac Studios available in their Certified Refurbished store for $540 off original MSRP. Each Mac Studio comes with Apple’s one-year warranty, and shipping is free: – Mac... Read more
Back on sale: Apple’s M2 Mac minis for $100 o...
B&H Photo has Apple’s M2-powered Mac minis back in stock and on sale today for $100 off MSRP. Free 1-2 day shipping is available for most US addresses: – Mac mini M2/256GB SSD: $499, save $100 –... Read more

Jobs Board

Licensed Dental Hygienist - *Apple* River -...
Park Dental Apple River in Somerset, WI is seeking a compassionate, professional Dental Hygienist to join our team-oriented practice. COMPETITIVE PAY AND SIGN-ON Read more
Sublease Associate Optometrist- *Apple* Val...
Sublease Associate Optometrist- Apple Valley, CA- Target Optical Date: Sep 30, 2023 Brand: Target Optical Location: Apple Valley, CA, US, 92307 **Requisition Read more
*Apple* / Mac Administrator - JAMF - Amentum...
Amentum is seeking an ** Apple / Mac Administrator - JAMF** to provide support with the Apple Ecosystem to include hardware and software to join our team and Read more
Child Care Teacher - Glenda Drive/ *Apple* V...
Child Care Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.