-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathDTMultiExpressionPatch.m
260 lines (215 loc) · 7.75 KB
/
DTMultiExpressionPatch.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#import "DTMultiExpressionPatch.h"
#import "DTMultiExpressionPatchUI.h"
@implementation DTMultiExpressionPatch
+(BOOL)isSafe
{
return YES;
}
+(BOOL)allowsSubpatchesWithIdentifier:(id)identifier
{
return NO;
}
+ (Class)inspectorClassWithIdentifier:(id)fp8
{
return [DTMultiExpressionPatchUI class];
}
+(NSArray*)sourceTypes
{
return [NSArray arrayWithObject:@"expression"];
}
- (id)initWithIdentifier:(id)identifier
{
if(self = [super initWithIdentifier:identifier])
{
[[self userInfo] setObject:@"MultiExpression" forKey:@"name"];
expressions = [NSMutableArray new];
resultVariables = [NSMutableArray new];
[self setSource:@"x=cos(t)\ny=sin(t)" ofType:@"expression"];
[self compileSourceOfType:@"expression"];
}
return self;
}
- (void)dealloc
{
[expressions release];
[resultVariables release];
[super dealloc];
}
- (BOOL)execute:(QCOpenGLContext*)context time:(double)time arguments:(NSDictionary*)arguments
{
// needs to be keyed AND ordered, so standard NS* won't work.
QCStructure *s = [[QCStructure alloc] init];
GFList *results = [s _list];
// set up input conditions
for(QCPort *p in [self parameterPorts])
[results addObject:[p value] forKey:[p key]];
// evaluate expressions sequentially
unsigned int i = 0;
for(QCMathematicalExpression *expr in expressions)
{
// assign known variables
NSArray *variables = [expr variables];
unsigned int n = 0;
for(NSString *i in variables)
{
NSString *v = i;
[expr setVariable:[[results objectForKey:v] doubleValue] atIndex:n];
n++;
}
// evaluate and assign result
[results addObject:[NSNumber numberWithDouble:[expr evaluate]] forKey:[resultVariables objectAtIndex:i]];
i++;
}
for(QCPort *p in [self resultPorts])
[p setValue:[results objectForKey:[p key]]];
[outputStructure setStructureValue:s];
[s release];
return YES;
}
@end
@implementation DTMultiExpressionPatch (QCProgrammablePatch)
+ (NSArray *)sourceTypes
{
return [NSArray arrayWithObject:@"expression"];
}
- (id)compileSourceOfType:(NSString *)sourceType
{
NSString *source = [self sourceOfType:sourceType];
id QCMathematicalExpressionClass = objc_getClass("QCMathematicalExpression");
[expressions removeAllObjects];
[resultVariables removeAllObjects];
GFList *inputParams = [GFList list];
GFList *resultList = [GFList list];
GFList *resultNameList = [GFList list];
NSMutableDictionary *errors = [NSMutableDictionary dictionary];
NSCharacterSet *whitespaceCharacterSet = [NSCharacterSet whitespaceCharacterSet];
QCParameterInfo *parameterInfo = [QCParameterInfo infoWithType:3 size:1];
// for each line...
unsigned int lineNumber = 1;
for(NSString *line in [source componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]])
{
if([line length])
{
// split into result-variable-name and expression-to-evaluate
NSArray *c = [line componentsSeparatedByString:@"="];
if( [c count] != 2 )
{
if([errors count] == 0) // only report first error (but not if there aren't any expressions)
{
// @"error" key is a NSNumber bool -- true means error (red icon), false means warning (yellow icon)
[errors setObject:(id)kCFBooleanFalse forKey:@"error"];
// @"message" key is the stuff that gets printed.
[errors setObject:@"No '=' found in expression." forKey:@"message"];
// @"line" key is the line number to highlight (starts at 1, not zero ... Dijkstra would be pissed...)
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
NSString *resultVariable, *rightSide;
{
resultVariable = [c objectAtIndex:0];
resultVariable = [resultVariable stringByTrimmingCharactersInSet:whitespaceCharacterSet];
rightSide = [c objectAtIndex:1];
rightSide = [rightSide stringByReplacingOccurrencesOfString:@" " withString:@""];
if( ![resultVariable length] )
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanFalse forKey:@"error"];
[errors setObject:@"No variable name for assignment." forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
if( [resultVariable isEqualToString:rightSide] )
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanTrue forKey:@"error"];
[errors setObject:@"Equation's left and right sides cannot match." forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
if( strspn([resultVariable UTF8String],"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") != [resultVariable length] ||
strspn([rightSide UTF8String],"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^!*%/().+_?~|-") != [rightSide length] )
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanTrue forKey:@"error"];
[errors setObject:@"Please use alphanumeric variable names." forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
if([resultList objectForKey:resultVariable])
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanFalse forKey:@"error"];
[errors setObject:[NSString stringWithFormat:@"Variable %@ was already declared.",resultVariable] forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
if( [inputParams objectForKey:resultVariable] )
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanFalse forKey:@"error"];
[errors setObject:@"The same variable name can't be used as both an input and an output." forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
if( [QCProgrammablePatch respondsToSelector:@selector(isKeyValid:)] && ![QCProgrammablePatch isKeyValid:resultVariable] )
{
if([errors count] == 0)
{
[errors setObject:(id)kCFBooleanTrue forKey:@"error"];
[errors setObject:@"Variable name failed [QCProgrammablePatch isKeyValid:]." forKey:@"message"];
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
[resultVariables addObject:resultVariable];
[resultList addObject:parameterInfo forKey:resultVariable];
[resultNameList addObject:line forKey:resultVariable];
}
NSString *exprString = [c objectAtIndex:1];
// parse
NSString *error=nil;
QCMathematicalExpression *expr = [[QCMathematicalExpressionClass alloc] initWithString:exprString error:&error];
if(error)
{
if([errors count] == 0) // only report first error
{
// @"error" key is a NSNumber bool -- true means error (red icon), false means warning (yellow icon)
[errors setObject:(id)kCFBooleanTrue forKey:@"error"];
// @"message" key is the stuff that gets printed.
[errors setObject:[error description] forKey:@"message"];
// @"line" key is the line number to highlight (starts at 1, not zero ... Dijkstra would be pissed...)
[errors setObject:[NSNumber numberWithUnsignedInt:lineNumber] forKey:@"line"];
}
continue;
}
[expressions addObject:expr];
for(NSString *variable in [expr variables])
if( [resultVariables indexOfObjectIdenticalTo:variable]==NSNotFound
&& ![inputParams objectForKey:variable])
{
[inputParams addObject:parameterInfo forKey:variable];
}
[expr release];
}
++lineNumber;
}
if([errors count] > 0)
return errors;
[self setParameterList:inputParams];
[self setResultList:resultList];
for(QCNumberPort *p in [self resultPorts])
[[p userInfo] setObject:[resultNameList objectForKey:[p key]] forKey:@"name"];
return errors;
}
@end