Line data Source code
1 0 : /*
2 :
3 : OOStringParsing.m
4 :
5 : Oolite
6 : Copyright (C) 2004-2013 Giles C Williams and contributors
7 :
8 : This program is free software; you can redistribute it and/or
9 : modify it under the terms of the GNU General Public License
10 : as published by the Free Software Foundation; either version 2
11 : of the License, or (at your option) any later version.
12 :
13 : This program is distributed in the hope that it will be useful,
14 : but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 : GNU General Public License for more details.
17 :
18 : You should have received a copy of the GNU General Public License
19 : along with this program; if not, write to the Free Software
20 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 : MA 02110-1301, USA.
22 :
23 : */
24 :
25 : #import "OOStringParsing.h"
26 : #import "OOLogging.h"
27 : #import "NSScannerOOExtensions.h"
28 : #import "legacy_random.h"
29 : #import "Universe.h"
30 : #import "PlayerEntity.h"
31 : #import "PlayerEntityLegacyScriptEngine.h"
32 : #import "OOFunctionAttributes.h"
33 : #import "OOCollectionExtractors.h"
34 : #import "ResourceManager.h"
35 : #import "HeadUpDisplay.h"
36 :
37 : #import "OOJavaScriptEngine.h"
38 : #import "OOJSEngineTimeManagement.h"
39 :
40 :
41 0 : static NSString * const kOOLogStringVectorConversion = @"strings.conversion.vector";
42 0 : static NSString * const kOOLogStringQuaternionConversion = @"strings.conversion.quaternion";
43 0 : static NSString * const kOOLogStringRandomSeedConversion = @"strings.conversion.randomSeed";
44 :
45 :
46 0 : NSMutableArray *ScanTokensFromString(NSString *values)
47 : {
48 : NSMutableArray *result = nil;
49 : NSScanner *scanner = nil;
50 : NSString *token = nil;
51 : static NSCharacterSet *space_set = nil;
52 :
53 : // Note: Shark suggests we're getting a lot of early exits, but testing showed a pretty steady 2% early exit rate.
54 : if (EXPECT_NOT(values == nil)) return [NSMutableArray array];
55 : if (EXPECT_NOT(space_set == nil)) space_set = [[NSCharacterSet whitespaceAndNewlineCharacterSet] retain];
56 :
57 : result = [NSMutableArray array];
58 : scanner = [NSScanner scannerWithString:values];
59 :
60 : while (![scanner isAtEnd])
61 : {
62 : [scanner ooliteScanCharactersFromSet:space_set intoString:NULL];
63 : if ([scanner ooliteScanUpToCharactersFromSet:space_set intoString:&token])
64 : {
65 : [result addObject:token];
66 : }
67 : }
68 :
69 : return result;
70 : }
71 :
72 :
73 0 : BOOL ScanVectorFromString(NSString *xyzString, Vector *outVector)
74 : {
75 : GLfloat xyz[] = {0.0, 0.0, 0.0};
76 : int i = 0;
77 : NSString *error = nil;
78 : NSScanner *scanner = nil;
79 :
80 : assert(outVector != NULL);
81 : if (xyzString == nil) return NO;
82 :
83 : if (!error) scanner = [NSScanner scannerWithString:xyzString];
84 : while (![scanner isAtEnd] && i < 3 && !error)
85 : {
86 : if (![scanner scanFloat:&xyz[i++]]) error = @"could not scan a float value.";
87 : }
88 :
89 : if (!error && i < 3) error = @"found less than three float values.";
90 :
91 : if (!error)
92 : {
93 : *outVector = make_vector(xyz[0], xyz[1], xyz[2]);
94 : return YES;
95 : }
96 : else
97 : {
98 : OOLogERR(kOOLogStringVectorConversion, @"cannot make vector from '%@': %@", xyzString, error);
99 : return NO;
100 : }
101 : }
102 :
103 0 : BOOL ScanHPVectorFromString(NSString *xyzString, HPVector *outVector)
104 : {
105 : Vector scanVector;
106 : assert(outVector != NULL);
107 : BOOL result = ScanVectorFromString(xyzString, &scanVector);
108 : if (!result)
109 : {
110 : return NO;
111 : }
112 : *outVector = vectorToHPVector(scanVector);
113 : return YES;
114 : }
115 :
116 0 : BOOL ScanQuaternionFromString(NSString *wxyzString, Quaternion *outQuaternion)
117 : {
118 : GLfloat wxyz[] = {1.0, 0.0, 0.0, 0.0};
119 : int i = 0;
120 : NSString *error = nil;
121 : NSScanner *scanner = nil;
122 :
123 : assert(outQuaternion != NULL);
124 : if (wxyzString == nil) return NO;
125 :
126 : if (!error) scanner = [NSScanner scannerWithString:wxyzString];
127 : while (![scanner isAtEnd] && i < 4 && !error)
128 : {
129 : if (![scanner scanFloat:&wxyz[i++]]) error = @"could not scan a float value.";
130 : }
131 :
132 : if (!error && i < 4) error = @"found less than four float values.";
133 :
134 : if (!error)
135 : {
136 : outQuaternion->w = wxyz[0];
137 : outQuaternion->x = wxyz[1];
138 : outQuaternion->y = wxyz[2];
139 : outQuaternion->z = wxyz[3];
140 : quaternion_normalize(outQuaternion);
141 : return YES;
142 : }
143 : else
144 : {
145 : OOLogERR(kOOLogStringQuaternionConversion, @"cannot make quaternion from '%@': %@", wxyzString, error);
146 : return NO;
147 : }
148 : }
149 :
150 :
151 0 : BOOL ScanVectorAndQuaternionFromString(NSString *xyzwxyzString, Vector *outVector, Quaternion *outQuaternion)
152 : {
153 : GLfloat xyzwxyz[] = { 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0};
154 : int i = 0;
155 : NSString *error = nil;
156 : NSScanner *scanner = nil;
157 :
158 : assert(outVector != NULL && outQuaternion != NULL);
159 : if (xyzwxyzString == nil) return NO;
160 :
161 : if (!error) scanner = [NSScanner scannerWithString:xyzwxyzString];
162 : while (![scanner isAtEnd] && i < 7 && !error)
163 : {
164 : if (![scanner scanFloat:&xyzwxyz[i++]]) error = @"Could not scan a float value.";
165 : }
166 :
167 : if (!error && i < 7) error = @"Found less than seven float values.";
168 :
169 : if (error)
170 : {
171 : OOLogERR(kOOLogStringQuaternionConversion, @"cannot make vector and quaternion from '%@': %@", xyzwxyzString, error);
172 : return NO;
173 : }
174 :
175 : outVector->x = xyzwxyz[0];
176 : outVector->y = xyzwxyz[1];
177 : outVector->z = xyzwxyz[2];
178 : outQuaternion->w = xyzwxyz[3];
179 : outQuaternion->x = xyzwxyz[4];
180 : outQuaternion->y = xyzwxyz[5];
181 : outQuaternion->z = xyzwxyz[6];
182 :
183 : return YES;
184 : }
185 :
186 :
187 0 : Vector VectorFromString(NSString *xyzString, Vector defaultValue)
188 : {
189 : Vector result;
190 : if (!ScanVectorFromString(xyzString, &result)) result = defaultValue;
191 : return result;
192 : }
193 :
194 :
195 0 : Quaternion QuaternionFromString(NSString *wxyzString, Quaternion defaultValue)
196 : {
197 : Quaternion result;
198 : if (!ScanQuaternionFromString(wxyzString, &result)) result = defaultValue;
199 : return result;
200 : }
201 :
202 :
203 0 : NSString *StringFromPoint(NSPoint point)
204 : {
205 : return [NSString stringWithFormat:@"%f %f", point.x, point.y];
206 : }
207 :
208 :
209 0 : NSPoint PointFromString(NSString *xyString)
210 : {
211 : NSArray *tokens = ScanTokensFromString(xyString);
212 : NSPoint result = NSZeroPoint;
213 :
214 : NSUInteger n_tokens = [tokens count];
215 : if (n_tokens == 2)
216 : {
217 : result.x = [[tokens objectAtIndex:0] doubleValue];
218 : result.y = [[tokens objectAtIndex:1] doubleValue];
219 : }
220 : return result;
221 : }
222 :
223 :
224 0 : Random_Seed RandomSeedFromString(NSString *abcdefString)
225 : {
226 : Random_Seed result;
227 : int abcdef[] = { 0, 0, 0, 0, 0, 0};
228 : int i = 0;
229 : NSString *error = nil;
230 : NSScanner *scanner = [NSScanner scannerWithString:abcdefString];
231 :
232 : while (![scanner isAtEnd] && i < 6 && !error)
233 : {
234 : if (![scanner scanInt:&abcdef[i++]]) error = @"could not scan a int value.";
235 : }
236 :
237 : if (!error && i < 6) error = @"found less than six int values.";
238 :
239 : if (!error)
240 : {
241 : result.a = abcdef[0];
242 : result.b = abcdef[1];
243 : result.c = abcdef[2];
244 : result.d = abcdef[3];
245 : result.e = abcdef[4];
246 : result.f = abcdef[5];
247 : }
248 : else
249 : {
250 : OOLogERR(kOOLogStringRandomSeedConversion, @"cannot make Random_Seed from '%@': %@", abcdefString, error);
251 : result = kNilRandomSeed;
252 : }
253 :
254 : return result;
255 : }
256 :
257 :
258 0 : NSString *StringFromRandomSeed(Random_Seed seed)
259 : {
260 : return [NSString stringWithFormat: @"%d %d %d %d %d %d", seed.a, seed.b, seed.c, seed.d, seed.e, seed.f];
261 : }
262 :
263 :
264 0 : NSString *OOPadStringToEms(NSString * string, float padEms)
265 : {
266 : NSString *result = string;
267 : float numEms = padEms - OOStringWidthInEm(result);
268 : if (numEms>0)
269 : {
270 : numEms /= OOStringWidthInEm(@" "); // start with wide space
271 : result=[[@"" stringByPaddingToLength:(NSUInteger)numEms withString: @" " startingAtIndex:0] stringByAppendingString: result];
272 : }
273 : // most of the way there, so switch to narrow space
274 : numEms = padEms - OOStringWidthInEm(result);
275 : if (numEms>0)
276 : {
277 : numEms /= OOStringWidthInEm(@"\037"); // 037 is narrow space
278 : result=[[@"" stringByPaddingToLength:(NSUInteger)numEms withString: @"\037" startingAtIndex:0] stringByAppendingString: result];
279 : }
280 : return result;
281 : }
282 :
283 :
284 0 : NSString *OOStringFromDeciCredits(OOCreditsQuantity tenthsOfCredits, BOOL includeDecimal, BOOL includeSymbol)
285 : {
286 : JSContext *context = OOJSAcquireContext();
287 : JSObject *global = [[OOJavaScriptEngine sharedEngine] globalObject];
288 : JSObject *fakeRoot;
289 : jsval method;
290 : jsval rval;
291 : NSString *result = nil;
292 : jsval exception;
293 : BOOL hadException;
294 :
295 : /* Because the |cr etc. formatting operators call this, and the
296 : implementation may use string expansion, we need to ensure recursion
297 : can't happen.
298 : */
299 : static BOOL reentrancyLock;
300 : if (reentrancyLock) return [NSString stringWithFormat:@"%0.1f", tenthsOfCredits * 0.1];
301 :
302 : reentrancyLock = YES;
303 :
304 : hadException = JS_GetPendingException(context, &exception);
305 : JS_ClearPendingException(context);
306 :
307 : if (JS_GetMethodById(context, global, OOJSID("formatCredits"), &fakeRoot, &method))
308 : {
309 : jsval args[3];
310 : if (JS_NewNumberValue(context, tenthsOfCredits * 0.1, &args[0]))
311 : {
312 : args[1] = OOJSValueFromBOOL(includeDecimal);
313 : args[2] = OOJSValueFromBOOL(includeSymbol);
314 :
315 : OOJSStartTimeLimiter();
316 : JS_CallFunctionValue(context, global, method, 3, args, &rval);
317 : OOJSStopTimeLimiter();
318 :
319 : result = OOStringFromJSValue(context, rval);
320 : }
321 : }
322 :
323 : if (hadException) JS_SetPendingException(context, exception);
324 :
325 : OOJSRelinquishContext(context);
326 :
327 : if (EXPECT_NOT(result == nil)) result = [NSString stringWithFormat:@"%li", (long)(tenthsOfCredits) / 10];
328 :
329 : reentrancyLock = NO;
330 :
331 : return result;
332 : }
333 :
334 :
335 : @implementation NSString (OOUtilities)
336 :
337 : - (BOOL)pathHasExtension:(NSString *)extension
338 : {
339 : return [[self pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame;
340 : }
341 :
342 :
343 : - (BOOL)pathHasExtensionInArray:(NSArray *)extensions
344 : {
345 : NSEnumerator *extEnum = nil;
346 : NSString *extension = nil;
347 :
348 : for (extEnum = [extensions objectEnumerator]; (extension = [extEnum nextObject]); )
349 : {
350 : if ([[self pathExtension] caseInsensitiveCompare:extension] == NSOrderedSame) return YES;
351 : }
352 :
353 : return NO;
354 : }
355 :
356 : @end
357 :
358 :
359 0 : NSArray *ComponentsFromVersionString(NSString *string)
360 : {
361 : NSArray *stringComponents = nil;
362 : NSMutableArray *result = nil;
363 : NSUInteger i, count;
364 : int value;
365 : id component;
366 :
367 : stringComponents = [string componentsSeparatedByString:@" "];
368 : stringComponents = [[stringComponents objectAtIndex:0] componentsSeparatedByString:@"-"];
369 : stringComponents = [[stringComponents objectAtIndex:0] componentsSeparatedByString:@"."];
370 : count = [stringComponents count];
371 : result = [NSMutableArray arrayWithCapacity:count];
372 :
373 : for (i = 0; i != count; ++i)
374 : {
375 : component = [stringComponents objectAtIndex:i];
376 : if ([component respondsToSelector:@selector(intValue)]) value = MAX([component intValue], 0);
377 : else value = 0;
378 :
379 : [result addObject:[NSNumber numberWithUnsignedInt:value]];
380 : }
381 :
382 : return result;
383 : }
384 :
385 :
386 0 : NSComparisonResult CompareVersions(NSArray *version1, NSArray *version2)
387 : {
388 : NSEnumerator *leftEnum = nil,
389 : *rightEnum = nil;
390 : NSNumber *leftComponent = nil,
391 : *rightComponent = nil;
392 : unsigned leftValue,
393 : rightValue;
394 :
395 : leftEnum = [version1 objectEnumerator];
396 : rightEnum = [version2 objectEnumerator];
397 :
398 : for (;;)
399 : {
400 : leftComponent = [leftEnum nextObject];
401 : rightComponent = [rightEnum nextObject];
402 :
403 : if (leftComponent == nil && rightComponent == nil) break; // End of both versions
404 :
405 : // We'll get 0 if the component is nil, which is what we want.
406 : leftValue = [leftComponent unsignedIntValue];
407 : rightValue = [rightComponent unsignedIntValue];
408 :
409 : if (leftValue < rightValue) return NSOrderedAscending;
410 : if (leftValue > rightValue) return NSOrderedDescending;
411 : }
412 :
413 : // If there was a difference, we'd have returned already.
414 : return NSOrderedSame;
415 : }
416 :
417 :
418 0 : NSString *ClockToString(double clock, BOOL adjusting)
419 : {
420 : int days, hrs, mins, secs;
421 : NSString *format = nil;
422 :
423 : days = floor(clock / 86400.0);
424 : secs = floor(clock - days * 86400.0);
425 : hrs = floor(secs / 3600.0);
426 : secs %= 3600;
427 : mins = floor(secs / 60.0);
428 : secs %= 60;
429 :
430 : if (adjusting) format = DESC(@"clock-format-adjusting");
431 : else format = DESC(@"clock-format");
432 :
433 : return [NSString stringWithFormat:format, days, hrs, mins, secs];
434 : }
435 :
436 :
437 : #if DEBUG_GRAPHVIZ
438 :
439 : NSString *EscapedGraphVizString(NSString *string)
440 : {
441 : NSString * const srcStrings[] =
442 : {
443 : //Note: backslash must be first.
444 : @"\\", @"\"", @"\'", @"\r", @"\n", @"\t", nil
445 : };
446 : NSString * const subStrings[] =
447 : {
448 : //Note: must be same order.
449 : @"\\\\", @"\\\"", @"\\\'", @"\\r", @"\\n", @"\\t", nil
450 : };
451 :
452 : NSString * const * src = srcStrings;
453 : NSString * const * sub = subStrings;
454 : NSMutableString *mutable = nil;
455 : NSString *result = nil;
456 :
457 : mutable = [string mutableCopy];
458 : while (*src != nil)
459 : {
460 : [mutable replaceOccurrencesOfString:*src++
461 : withString:*sub++
462 : options:0
463 : range:(NSRange){ 0, [mutable length] }];
464 : }
465 :
466 : if ([mutable length] == [string length])
467 : {
468 : result = string;
469 : }
470 : else
471 : {
472 : result = [[mutable copy] autorelease];
473 : }
474 : [mutable release];
475 : return result;
476 : }
477 :
478 :
479 : static BOOL NameIsTaken(NSString *name, NSSet *uniqueSet);
480 :
481 : NSString *GraphVizTokenString(NSString *string, NSMutableSet *uniqueSet)
482 : {
483 : NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
484 :
485 : BOOL lastWasUnderscore = NO;
486 : NSUInteger i, length = [string length], ri = 0;
487 : unichar result[length];
488 : NSString *token = nil;
489 :
490 : if (length > 0)
491 : {
492 : // Special case for first char - can't be digit.
493 : unichar c = [string characterAtIndex:0];
494 : if (!isalpha(c))
495 : {
496 : c = '_';
497 : lastWasUnderscore = YES;
498 : }
499 : result[ri++] = c;
500 :
501 : for (i = 1; i < length; i++)
502 : {
503 : c = [string characterAtIndex:i];
504 : if (!isalnum(c))
505 : {
506 : if (lastWasUnderscore) continue;
507 : c = '_';
508 : lastWasUnderscore = YES;
509 : }
510 : else
511 : {
512 : lastWasUnderscore = NO;
513 : }
514 :
515 : result[ri++] = c;
516 : }
517 :
518 : token = [NSString stringWithCharacters:result length:ri];
519 : }
520 : else
521 : {
522 : token = @"_";
523 : }
524 :
525 : if (NameIsTaken(token, uniqueSet))
526 : {
527 : if (!lastWasUnderscore) token = [token stringByAppendingString:@"_"];
528 : NSString *uniqueToken = nil;
529 : unsigned uniqueID = 2;
530 :
531 : for (;;)
532 : {
533 : uniqueToken = [NSString stringWithFormat:@"%@%u", token, uniqueID];
534 : if (!NameIsTaken(uniqueToken, uniqueSet)) break;
535 : }
536 : token = uniqueToken;
537 : }
538 : [uniqueSet addObject:token];
539 :
540 : [token retain];
541 : [pool release];
542 : return [token autorelease];
543 : }
544 :
545 :
546 : static BOOL NameIsTaken(NSString *name, NSSet *uniqueSet)
547 : {
548 : if ([uniqueSet containsObject:name]) return YES;
549 :
550 : static NSSet *keywords = nil;
551 : if (keywords == nil) keywords = [[NSSet alloc] initWithObjects:@"node", @"edge", @"graph", @"digraph", @"subgraph", @"strict", nil];
552 :
553 : return [keywords containsObject:[name lowercaseString]];
554 : }
555 :
556 : #endif //DEBUG_GRAPHVIZ
|