Cocoa Gestione Valutazione di espressione

La pigrizia e la definizione delle grammatiche

Si può avere l'esigenza, e la cosa è tutt'altro che rara, di poter valutare una espressione matematica all'interno di un programma. Una cosa che mi sarebbe venuta in mente 15 anni fa sarebbe stata quella di trasformare l'espressione in forma normale postfissa e valutarla. Ma questo oggi non mi piace molto. Un'altra possibilità sarebbe quella di definire dei token e una grammatica per la valutazione delle espressioni usano semplicemente lex e yacc ... ma a dir la verità sono molto pigro e non mi va di fare troppi test per la correttezza ...

Scappatoie: programmi già esistenti

... Visto che Mac OS X include le utility tipiche GNU, include anche bc che è una semplice calcolatrice che funziona da terminale.

http://www.cocoadev.com/index.pl?ConvertStringIntoExpression contiene un'esempio di come fare una valutazione di una espressione, visto che tipicamente questa è una esigenza che si può avere da parte di una applicazione in modo asincrono, bisogna escogitare un modo per valutare più espressioni matematiche in maniera asincrona.

Prima di tutto l'oggetto chiamante dovrà tenere traccia di tutte le espressioni per le quali attende una valutazione, e, a valutazione avvenuta, rimuovere il valutatore dalla memoria.

Per questo uso un semplice NSMutableArray *evaluators; dove mettere tutte i valutatori in attesa, e dal quale togliere i valutatori che hanno terminato il proprio compito. Avrò inoltre un metodo che verrè chiamato dal valutatore una volta ultimata la valutazione.

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

L'implementazione della classe dovrà prevedere l'ascolto di questa per la notifica di espressione valutata (notifica da parte del valutatore dell'espressione). Ho usato EspressioneOK come nome della notifica e l'oggetto exprEvaluator come oggetto associato.

@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

Veniamo ora alla classe 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




Implementazione

#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];
   /* la chiusura del task causa la disponibilita di dati su tutti gli handler in lettura
     (outputHandle e errorHandle), notificaInviata segnala che e' stato fatto tutto
     nei riguardi del chiamante */
    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



La differenza con la versione originale è che sono previste situazioni in cui l'espressione è mal formata e quindi il chiamante dovrà tenerne conto richiedendo al valutatore se si è verificato un errore. In più una importante differenza sta nel fatto che la classe notifica la presenza di un risultato a tutti quelli che sono in ascolto/

Ho dovuto usare un BOOL per segnalare lo stato di notifica inviata, perché succede che alla chiusura del task entrambi gli stream vengono chiusi, facendo si che si ricevano entrambe le notifiche, indifferentemente dal fatto che si sia ho meno verificato un errore.

Reply

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

Captcha
This question is used to make sure you are a human visitor and to prevent spam submissions.
Copy the characters (respecting upper/lower case) from the image.