home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (C) 1993 Robert Davis
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of Version 2, or any later version, of
- * the GNU General Public License as published by the Free Software
- * Foundation.
- */
-
- static char RCSId[]="$Id: FunctionObject.m,v 1.12 1993/05/24 03:59:44 davis Exp $";
-
-
- #import <streams/streams.h>
- #import <sys/stat.h> /* stat() */
- #import <sys/types.h> /* stat() */
- #import <objc/hashtable.h> /* NXCopyStringBufferFromZone() */
- #import <objc/zone.h>
- #import <ctype.h> /* isspace() */
- #import <libc.h> /* index(), rindex() */
-
- #import "FunctionObject.h"
-
-
- const char *styleString[] = {"lines", "points", "linespoints", "dots",
- "impulses", "errorbars", "boxes", "boxerrorbars",
- "steps"};
-
-
- /* Is the string blank (i.e. full of only white space)? */
- static BOOL _isBlank (const char *aString)
- {
- const char *cur;
-
- for (cur = aString ; *cur != '\0' ; cur++)
- if (!isspace(*cur))
- return NO;
-
- return YES;
- }
-
-
- static BOOL _isSpaceNotNL (char aChar)
- {
- return (isspace (aChar)) && (aChar != '\n');
- }
-
-
-
-
- /*
- * aString is assumed to be a one-line string (i.e it contains no
- * newlines). This could be changed easily if need be.
- */
- static void _removeComments (char *aString)
- {
- char *cur = aString;
-
- while (*cur) {
- if ((*cur == '\'') || (*cur == '"')) {
-
- /*
- * Skip the quotation. Note that gnuplot does not
- * support nested quotes.
- */
-
- char c = *(cur++);
- while (*cur && (*cur != c))
- cur++;
-
- } else if (*cur == '#') {
-
- /*
- * If we've encountered a pound sign outside of a
- * quotation, we've found a comment. The rest of the
- * line can be truncated.
- */
- cur = '\0';
-
- }
-
- cur++;
- }
-
- }
-
-
- static void _getWith (char *aString, int *style, int *lineStyle,
- int *pointsStyle)
- {
- char *cur, *with;
-
- /*
- * This attempts to detect a 'with' phrase and to extract the
- * style from it if it exists. There are several ways to trick
- * this.
- */
- if ((with = rindex (aString, 'w')) && (*(with+1) == 'i') &&
- (cur = index (with, ' '))) {
- switch (*(++cur)) {
- case 'p': *style = FUNCTION_POINTS; break;
- case 'd': *style = FUNCTION_DOTS; break;
- case 'i': *style = FUNCTION_IMPULSES; break;
- case 'e': *style = FUNCTION_ERRORBARS; break;
- case 'b':
- if (index (cur, 'r')) *style = FUNCTION_BOXERRORBARS;
- else *style = FUNCTION_BOXES;
- break;
- case 'l':
- if (index (cur, 'p')) *style = FUNCTION_LINESPOINTS;
- else *style = FUNCTION_LINES;
- break;
- case 's': *style = FUNCTION_STEPS; break;
- default: *style = FUNCTION_NOSTYLE; break;
- }
- *(with-1) = '\0'; /* Assume with is not first in aString */
-
- /*
- * If the style type includes lines or points, the user has
- * the option of setting the point/line styles. Check for
- * those...
- */
- /* Line Style */
- if ((*style == FUNCTION_POINTS) || (*style == FUNCTION_LINES) ||
- (*style == FUNCTION_LINESPOINTS)) {
-
- while (cur && *cur && ((*cur > '6') || (*cur < '1')))
- cur++;
- if (cur && *cur)
- *lineStyle = *cur - '0';
-
- /* Point Style */
- if ((*style == FUNCTION_POINTS) ||
- (*style == FUNCTION_LINESPOINTS)) {
-
- while (cur && *cur && ((*cur > '6') || (*cur < '1')))
- cur++;
- if (cur && *cur)
- *pointsStyle = *cur - '0';
- }
-
- }
-
- }
-
- }
-
-
-
-
- static char *_getTitle (char *aString)
- {
- while (aString && *aString) {
-
- /*
- * If this appears to be a "title" phrase, see if a quoted
- * string follows. If one does, chop it off and return it.
- */
-
- if ((*aString == 't') &&
- ((*(aString + 1) == 'i') || _isSpaceNotNL (*(aString + 1)))) {
-
- char *firstQuote, *beginning = aString;
-
- while (!_isSpaceNotNL (*aString))
- aString++;
- while (_isSpaceNotNL (*aString))
- aString++;
- if ((*aString == '\'') || (*aString == '"')) {
-
- /*
- * Okay, we are as sure as can be that this is a
- * "title" phrase.
- */
- firstQuote = aString;
- if (aString = index (++aString, *firstQuote))
- *aString = '\0';
- *(beginning-1) = '\0'; /* Assume title isn't first in aString */
-
- return firstQuote+1;
- }
-
- }
-
- aString++;
- }
-
- return NULL;
- }
-
-
- /* aString assumed to contain no newlines */
- static void _getUsing (char *aString, int *nums, BOOL threeD)
- {
- while (aString && *aString) {
-
-
- /* Look for a using clause */
-
- if ((*aString == 'u') && (*(aString + 1)) &&
- ((*(aString + 1) == 's') || isspace (*(aString + 1)))) {
-
- int i = 0;
-
- *aString = '\0';
- /* skip "using" and space */
- while (*(++aString) && !isspace (*aString))
- ;
- while (*(++aString) && isspace (*aString))
- ;
-
- while (*aString) {
- if (isdigit (*aString))
- nums[i] = (nums[i] * 10) + (*aString - '0');
- else if (nums[i] == '0') {
- /* Stop reading -- problems */
- *(aString+1) = '\0';
- i--;
- } else if (*aString == ':') {
- if (i++ > 4) {
- /* Read all the numbers -- ignore anything else */
- *(aString+1) = '\0';
- i--;
- }
- }
-
- aString++;
- }
-
- return;
-
- } else if (*aString == '/') {
- while (*(++aString) && !isspace(*aString))
- ;
- } else if ((*aString == '"') || (*aString == '\'')) {
- char quote = *aString;
- while (*(++aString) && (*aString != quote))
- ;
- aString++;
- } else
- aString++;
- }
-
- return;
- }
-
-
- static BOOL _isNumberElement (char c)
- {
- return isdigit(c) || (c == '.') || (c == 'E') || (c == '+') || (c == '-');
- }
-
- static int _getNumColumns (const char *path)
- {
- NXStream *s = NXMapFile (path, NX_READONLY);
- int count = 0;
-
- if (s) {
- int c, oldc = '\0';
-
- c = NXGetc(s);
- while (c != EOF) {
- if (c == '#') {
- if (count == 0) {
- while (((c = NXGetc(s)) != EOF) && (c != '\n'))
- ;
- count = 0;
- oldc = c;
- if (c != EOF)
- c = NXGetc(s);
- } else
- c = EOF;
- } else if (_isNumberElement(c) && !_isNumberElement(oldc)) {
- count++;
- oldc = c;
- c = NXGetc(s);
- } else if (c == '\n') {
- if (count > 0)
- c = EOF;
- else
- c = NXGetc(s);
- } else {
- oldc = c;
- c = NXGetc(s);
- }
-
- }
-
- NXCloseMemory (s, NX_FREEBUFFER);
- }
-
- return count;
- }
-
-
-
- @interface FunctionObject (Private)
- - _initColumnDataUsing:(int *)usingData;
- @end
-
-
- @implementation FunctionObject
-
-
- /* Does the string contain something besides white space? */
- + (BOOL) isAcceptableStringValue:(const char *)aString
- {
- return (aString && !_isBlank (aString));
- /* todo, also check for valid functions, if possible */
- }
-
-
- /*
- * Makes sure the file whose full path is specified by aString is a
- * regular, readable file.
- */
- + (BOOL) isAcceptableDataFile:(const char *)aString
- {
- struct stat fileinfo;
- BOOL returnVal;
-
- returnVal = (aString && !stat (aString, &fileinfo) &&
- ((fileinfo.st_mode & S_IFMT) == S_IFREG) &&
- (fileinfo.st_mode & S_IREAD));
- return returnVal;
- }
-
-
-
- /*
- * The attributes of a function are stored by an instance of
- * FunctionObject. The only complicated part is parsing those
- * attributes from a Gnuplot "plot" statement. aString should
- * specify only the part of that statement pertaining to the function
- * that will be stored by this instance and should contain no leading
- * white space. (This method could be nicer by ignoring leading
- * white space.)
- */
- - initFromString:(const char *)aString isThreeD:(BOOL)aCond
- {
- char quote, *cur;
- NXZone *zone;
- int theUsing[5] = {0,0,0,0,0};
-
- [super init];
-
- zone = [self zone];
-
- title = NULL; /* Defaults */
- style = FUNCTION_NOSTYLE;
- pointsStyle = POINTS_NOSTYLE;
- lineStyle = LINE_NOSTYLE;
- isDataFile = NO;
- isThreeD = aCond;
-
- if (aString) {
-
- stringValue = NXCopyStringBufferFromZone (aString, zone);
- _removeComments (stringValue);
- _getWith (stringValue, &style, &lineStyle, &pointsStyle);
- [self setTitle:_getTitle (stringValue)];
-
- /*
- * If this string value is quoted or if it begins with a
- * forward slash, it is a data file. (This forward slash
- * thing is an feature not present in the original gnuplot,
- * but it's very nice in the NeXTSTEP interface.) We remove
- * the quotes and set the isDataFile flag. The string is
- * assumed to have no leading white space, i.e. if it is a
- * data file, the first character is a quote.
- */
-
- quote = *stringValue;
-
- /* Check to see if this is a data file */
- if ((quote == '/') || (quote == '\'') || (quote == '"')) { /* It is */
-
- isDataFile = YES;
- _getUsing (stringValue, theUsing, isThreeD);
-
- if (quote == '/') {
- int len;
- cur = NXCopyStringBufferFromZone (stringValue, zone);
- len = strlen (cur); /* Remove trailing space */
- while (isspace(cur[--len]))
- cur[len] = '\0';
- } else {
- *rindex (stringValue, quote) = '\0';
- cur = NXCopyStringBufferFromZone (stringValue+1, zone);
- }
-
- NXZoneFree (zone, stringValue);
- stringValue = cur;
-
- [self _initColumnDataUsing:theUsing];
-
- } else { /* This is a function, not a data file. */
-
- /* Remove trailing blanks */
- cur = stringValue + strlen (stringValue) - 1 ;
- while (isspace (*cur) && (cur >= stringValue))
- *(cur--) = '\0';
-
- }
-
- } else
- stringValue = NULL;
-
- return self;
- }
-
-
- - free
- {
- NXZoneFree ([self zone], title);
- return [super free];
- }
-
-
-
- - setThreeD:(BOOL)cond
- {
- if (cond != isThreeD) {
- isThreeD = cond;
-
- if (isDataFile) {
- struct coldat temp = columnData;
- columnData = columnDataOther;
- columnDataOther = temp;
- }
- }
-
- return self;
- }
-
-
- - (BOOL)isDataFile
- {
- return isDataFile;
- }
-
-
- - (struct coldat *)columnData
- {
- return isDataFile? &columnData :NULL;
- }
-
-
- - setTitle:(const char *)aString
- {
- NXZone *zone = [self zone];
-
- NXZoneFree (zone, title);
- if (aString) {
- char *c, quote = '\0';
-
- title = NXCopyStringBufferFromZone (aString, zone);
-
- /*
- * Make sure aString doesn't mix both kinds of quotation marks.
- * Gnuplot can handle one or the other but not both in one title.
- * (If there's a mix, use the kind used first.)
- */
-
- for (c = title; c && *c; c++)
- if ((*c == '\'') || (*c == '"')) {
- if (quote && (*c != quote))
- *c = quote;
- else
- quote = *c;
- }
-
- } else
- title = NULL;
-
- return self;
- }
-
-
- - (const char *)title
- {
- return title;
- }
-
-
- - setStyle:(int) anInt
- {
- style = anInt;
- return self;
- }
-
-
- - (int)style
- {
- return style;
- }
-
-
- - (const char *)styleString
- {
- if (style == FUNCTION_NOSTYLE)
- return NULL;
- else
- return styleString[style];
- }
-
-
- - setPointsStyle: (int)anInt
- {
- pointsStyle = anInt;
- return self;
- }
-
-
- - (int)pointsStyle
- {
- return pointsStyle;
- }
-
-
- - setLineStyle: (int)anInt
- {
- lineStyle = anInt;
- return self;
- }
-
-
- - (int)lineStyle
- {
- return lineStyle;
- }
-
-
-
- /*
- * If the function is actually a data file, it is similar to an
- * attachment, or a "file link," so we return YES. If the function
- * is not a data file, we return NO.
- */
- - (BOOL)isAttachment
- {
- return isDataFile;
- }
-
-
- // Shuts up the compiler about unused RCSId
- - (const char *) rcsid
- {
- return RCSId;
- }
-
-
- @end
-
-
- @implementation FunctionObject (Private)
-
- - _initColumnDataUsing:(int *)usingData
- {
- struct coldat *two = isThreeD? &columnDataOther : &columnData;
- struct coldat *three = isThreeD? &columnData : &columnDataOther;
- int numUsing;
- int x;
-
- /*
- * Initialize the column data structures based on the number of
- * columns in the data file, but also consider the usingData.
- */
-
- two->number = three->number = _getNumColumns (stringValue);
-
- for (numUsing = 0; usingData[numUsing]; numUsing++)
- ;
-
- two->isOn = three->isOn = numUsing;
- x = (numUsing)? numUsing : two->number;
-
- if (x >= 1) {
- two->useX = two->useY = three->useZ = YES;
- two->x = two->y = three->useZ = 1;
- if (x >= 2) {
- two->y = 2;
- if (x >= 3) {
- two->useYDelta = three->useX = three->useY = YES;
- two->yDelta = three->z = 3;
- three->y = 2;
- three->x = 1;
- if (x >= 4) {
- two->useYLow = two->useYHigh = YES;
- two->useYDelta = NO;
- two->yLow = 3;
- two->yHigh = 4;
- if (x >= 5)
- two->useBoxWidth = YES;
- two->boxWidth = 5;
- }
- }
- }
- }
-
- if (numUsing) {
- two->x = three->x = (numUsing > 1)? usingData[0] : 1;
- two->y = (numUsing > 1)? usingData[1] : usingData[0];
- three->y = usingData[1];
- two->yDelta = two->yLow = three->z = usingData[2];
- two->yHigh = usingData[3];
- two->boxWidth = usingData[4];
- }
-
- three->useYDelta = NO; /* Can't use these in 3D */
- three->useYLow = NO;
- three->useYHigh = NO;
- three->useZ = YES;
- three->useBoxWidth = NO;
-
- two->useZ = NO; /* Can't use this in 2D */
-
- switch (two->number) { /* Note: this falls through */
- case 0: two->useY = three->useZ = NO;
- case 1: two->useX = NO;
- case 2: two->useYDelta = three->useX = three->useY = NO;
- case 3: two->useYLow = two->useYHigh = NO;
- case 4: two->useBoxWidth = NO;
- }
-
- return self;
- }
-
-
- @end
-