Articles

Mac OSX - Sub Categories

Cocoa: Mathematical expressions Evaluation Management

Lazy man and the mathematical expressions

It could be needed, and is not so rare, to evaluate a mathematical expression in a program. One thing I could had thought fifteen years ago it is to transform expression in postfix form, then evaluate it. But now I do not like it so much. Another solution could be write lex and yacc definitions and let processor to generate code for that ... but I am lazy, and integrate yacc code in my code could be no so funny ...

Escape: using bc

... Mac OS X include tipical GNU utilities, and bc which is a simple terminal based calculator.

In http://www.cocoadev.com/index.pl?ConvertStringIntoExpression there is an example on using bc for mathematical expression evaluation, but generally there is needed asynchron request, so it must be found a way for evaluating more expression in async way.

First of all, calling object needs to tracks all expression for which it wait an evaluation, and, when evaluation occur, it must have change to use evaluated value and release evaluator.

For this I use a NSMutableArray *evaluators; where to put all evaluator for which it is setted an expression and it is waited a result. The evaluator, in turn, should know a method to call when evaluation is done.

@interface ClasseChiamante :NSObject
{
NSMutableArray *evaluators;
}
-(void)updateExprResult:(id)exprResult;

Class implementation must register itself to notification by evaluator. I used EspressioneOK as name of notification (even if expression could be no well evaluated really)

@implementation ClasseChiamante

-(id)init
{
    if (self = [super init]) {
	evaluators = [[NSMutableArray alloc] initWithCapacity:2];
	[[NSNotificationCenter defaultCenter] addObserver:self
			 selector:@selector(updateExprResult:)
			 name:@"EspressioneOK" 
			   object:nil];
	return self;
     }
     return nil;
}

-(void) valutaEspressione
{
   exprEvalutator *ev = [[[exprEvaluator alloc] init] autorelease];
   // mettere nel autorelease pool
   [ev evaluate:[expressione stringValue] forVoce:elementoSelezionato
           from:self];
   [evaluators addObject:ev];
}

-(void)updateExprResult:(id)not
{
    NSLog(@"CIAO, sono updateExprResult");
    exprEvaluator *exprResult = [not object];
    // controllare che exprResult sia nell'array evalutators !!!
    if ([evaluators contaisObject:exprResult]) {
	//usa il risultato
        if (![exprResult errore]) {
             //.....
        } else {
             // ..in caso di errore
        }
	[evaluator removeObject:exprResult];
    }
}
@end

Now exprEvaluator:

@interface exprEvaluator : NSObject {
	
    NSTask *bcTask;
    NSFileHandle *inputHandle, *outputHandle, *errorHandle;
	
	id senderObj;
	NSString *expression;
	int voce;  //optional
	float risultato;
	BOOL errore;
	BOOL notificaInviata;
}

- (void) evaluate:(NSString*)expr forVoce:(int)v from:(id)sender;

- (void) bcTaskOutput:(id)note;

- (int)voce; //optional
-(float)risultato;
-(BOOL)errore;
@end




Implementation:

#import "exprEvaluator.h"


@implementation exprEvaluator

- (id) init
{
	if (self = [super init]) {
		[[NSNotificationCenter defaultCenter] addObserver:self
			 selector:@selector(bcTaskOutput:)
			 name:NSFileHandleDataAvailableNotification 
			   object:nil];
		return self;
	}
	return nil;
}
- (void) evaluate:(NSString*)expr forVoce:(int)v from:(id)sender
{
	expression = [[NSString alloc] initWithString:expr];
	voce = v;
	senderObj = sender;
	errore = NO;
	notificaInviata = NO;

	NSString *inputString =
		[NSString stringWithFormat:@"scale=12;%@\n", expression];
	NSData *inputData =
		[NSData dataWithBytes:[inputString cString]
			length:[inputString length]];
	if (inputData) {
		if (bcTask) {[bcTask terminate]; [bcTask release]; bcTask = nil;}
		bcTask = [[NSTask alloc] init];
		NSPipe *inputPipe = [NSPipe pipe];
		inputHandle = [inputPipe fileHandleForWriting];
		NSPipe *outputPipe = [NSPipe pipe];
		outputHandle = [outputPipe fileHandleForReading];
		NSPipe *errorPipe = [NSPipe pipe];
		errorHandle = [errorPipe fileHandleForReading];
		[bcTask setStandardOutput:outputPipe];
		[bcTask setStandardInput:inputPipe];
		[bcTask setStandardError:errorPipe];
		[bcTask setLaunchPath:@"/usr/bin/bc"];
		[bcTask launch];
		[inputHandle writeData:inputData];
		[outputHandle waitForDataInBackgroundAndNotify];
		[errorHandle waitForDataInBackgroundAndNotify];
	}
}

- (int)voce
{
	return voce;
}

-(float)risultato;
{return risultato;}

-(BOOL)errore
{return errore;}

- (void) bcTaskOutput:(id)note
{
   id object = [note object];
   /* closing task cause data available in all reading handlers
     (outputHandle e errorHandle), notificaInviata state that
     all is done */
    if (notificaInviata) return;
    if (object == outputHandle) {
		notificaInviata=YES;
		NSData *data = [outputHandle availableData];

		NSString *output = [NSString stringWithCString:[data bytes] length:[data length]];

		risultato = [output floatValue];
        [bcTask terminate]; [bcTask release]; bcTask = nil;
		[[NSNotificationCenter defaultCenter]
			postNotificationName:@"EspressioneOK"
			object:self];
    }
    if (object == errorHandle) {
	errore = YES;
	notificaInviata=YES;
	NSData *data = [errorHandle availableData];

	NSString *output = [NSString stringWithCString:[data bytes] length:[data length]];
        [bcTask terminate]; [bcTask release]; bcTask = nil;
	[[NSNotificationCenter defaultCenter] postNotificationName:@"EspressioneOK"
			object:self];
	}
}

- (void)dealloc {
	NSLog (@"oggetto rimosso");
    [[NSNotificationCenter defaultCenter] removeObserver:self];
	[expression release];
    [super dealloc];
}

@end

The difference with cocoadev version is that are possible situation in which the expression syntax is wrong (and so the errore method). Also Evaluator notify the presence of a result to all Evaluator callers.

I have used a BOOL for stating "notification sent", because closing an NSTask cause errorHandle and outputHandle to signal data available notification (another solution could be to remove evaluator from notification center).