This is FunctionObject.m in view mode; [Download] [Up]
/*
* 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
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.