Line data Source code
1 0 : /*
2 :
3 : OOStringExpander.m
4 :
5 :
6 : Oolite
7 : Copyright (C) 2004-2013 Giles C Williams and contributors
8 :
9 : This program is free software; you can redistribute it and/or
10 : modify it under the terms of the GNU General Public License
11 : as published by the Free Software Foundation; either version 2
12 : of the License, or (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the impllied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program; if not, write to the Free Software
21 : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 : MA 02110-1301, USA.
23 :
24 : */
25 :
26 : #import "OOCocoa.h"
27 : #import "OOStringExpander.h"
28 : #import "Universe.h"
29 : #import "OOJavaScriptEngine.h"
30 : #import "OOCollectionExtractors.h"
31 : #import "OOStringParsing.h"
32 : #import "ResourceManager.h"
33 : #import "PlayerEntityScriptMethods.h"
34 : #import "PlayerEntity.h"
35 :
36 : // Don't bother with syntax warnings in Deployment builds.
37 0 : #define WARNINGS OOLITE_DEBUG
38 :
39 0 : #define OO_EXPANDER_RANDOM (context->useGoodRNG ? (Ranrot()&0xFF) : gen_rnd_number())
40 :
41 0 : enum
42 : {
43 : /*
44 : Total stack limit for strings being parsed (in UTF-16 code elements,
45 : i.e. units of 2 bytes), used recursively - for instance, if the root
46 : string takes 10,000 characters, any string it recurses into gets
47 : kStackAllocationLimit - 10,000. If the limit would be exceeded,
48 : the unexpanded string is returned instead.
49 :
50 : The limit is expected to be much higher than necessary for any practical
51 : string, and exists only to catch pathological behaviour without crashing.
52 : */
53 : kStackAllocationLimit = UINT16_MAX,
54 :
55 : /*
56 : Recursion limit, for much the same purpose. Without it, we crash about
57 : 22,000 stack frames deep when trying to expand a = "[a]" on a Mac.
58 : */
59 : kRecursionLimit = 100
60 : };
61 :
62 :
63 : /* OOStringExpansionContext
64 :
65 : Struct used to store context and caches for the entire string expansion
66 : operation, including recursive calls (so it can't contain anything pertaining
67 : to the specific string being expanded).
68 : */
69 0 : typedef struct
70 : {
71 0 : Random_Seed seed;
72 0 : NSString *systemName;
73 0 : NSDictionary *overrides;
74 0 : NSDictionary *legacyLocals;
75 0 : bool isJavaScript;
76 0 : bool convertBackslashN;
77 0 : bool hasPercentR; // Set to indicate we need an ExpandPercentR() pass.
78 0 : bool useGoodRNG;
79 0 : bool disallowPercentI;
80 :
81 0 : NSString *systemNameWithIan; // Cache for %I
82 0 : NSString *randomNameN; // Cache for %N
83 0 : NSString *randomNameR; // Cache for %R
84 0 : NSArray *systemDescriptions;// Cache for system_description, used for numbered keys.
85 0 : NSUInteger sysDescCount; // Count of systemDescriptions, valid after GetSystemDescriptions() called.
86 : } OOStringExpansionContext;
87 :
88 :
89 : /* Accessors for lazily-instantiated caches in context.
90 : */
91 : static NSString *GetSystemName(OOStringExpansionContext *context); // %H
92 : static NSString *GetSystemNameIan(OOStringExpansionContext *context); // %I
93 : static NSString *GetRandomNameN(OOStringExpansionContext *context); // %N
94 : static NSString *GetRandomNameR(OOStringExpansionContext *context); // %R
95 : static NSArray *GetSystemDescriptions(OOStringExpansionContext *context);
96 :
97 : static void AppendCharacters(NSMutableString **result, const unichar *characters, NSUInteger start, NSUInteger end);
98 :
99 : static NSString *NewRandomDigrams(OOStringExpansionContext *context);
100 : static NSString *OldRandomDigrams(void);
101 :
102 :
103 : // Various bits of expansion logic, each with a comment of its very own at the implementation.
104 : static NSString *Expand(OOStringExpansionContext *context, NSString *string, NSUInteger sizeLimit, NSUInteger recursionLimit);
105 :
106 : static NSString *ExpandKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength, NSUInteger sizeLimit, NSUInteger recursionLimit);
107 : static NSString *ExpandDigitKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger keyStart, NSUInteger keyLength, NSUInteger sizeLimit, NSUInteger recursionLimit);
108 : static NSString *ExpandStringKey(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit);
109 : static NSString *ExpandStringKeyOverride(OOStringExpansionContext *context, NSString *key);
110 : static NSString *ExpandStringKeySpecial(OOStringExpansionContext *context, NSString *key);
111 : static NSString *ExpandStringKeyKeyboardBinding(OOStringExpansionContext *context, NSString *key);
112 : static NSMapTable *SpecialSubstitutionSelectors(void);
113 : static NSString *ExpandStringKeyFromDescriptions(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit);
114 : static NSString *ExpandStringKeyMissionVariable(OOStringExpansionContext *context, NSString *key);
115 : static NSString *ExpandStringKeyLegacyLocalVariable(OOStringExpansionContext *context, NSString *key);
116 : static NSString *ExpandLegacyScriptSelectorKey(OOStringExpansionContext *context, NSString *key);
117 : static SEL LookUpLegacySelector(NSString *key);
118 :
119 : static NSString *ExpandPercentEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength);
120 : static NSString *ExpandSystemNameForGalaxyEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength);
121 : static NSString *ExpandSystemNameEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength);
122 : static NSString *ExpandPercentR(OOStringExpansionContext *context, NSString *input);
123 : #if WARNINGS
124 : static void ReportWarningForUnknownKey(OOStringExpansionContext *context, NSString *key);
125 : #endif
126 :
127 : static NSString *ApplyOperators(NSString *string, NSString *operatorsString);
128 : static NSString *ApplyOneOperator(NSString *string, NSString *op, NSString *param);
129 :
130 :
131 : /* SyntaxWarning(context, logMessageClass, format, ...)
132 : SyntaxError(context, logMessageClass, format, ...)
133 :
134 : Report warning or error for expansion syntax, including unknown keys.
135 :
136 : Warnings are reported as JS warnings or log messages (depending on the
137 : context->isJavaScript flag) if the relevant log message class is enabled.
138 : Warnings are completely disabled in Deployment builds.
139 :
140 : Errors are reported as JS warnings (not exceptions) or log messages (again
141 : depending on context->isJavaScript) in all configurations. Exceptions are
142 : not used to avoid breaking code that worked with the old expander, even if
143 : it was questionable.
144 :
145 : Errors that are not syntax or invalid keys are reported with OOLogERR().
146 : */
147 0 : static void SyntaxIssue(OOStringExpansionContext *context, const char *function, const char *fileName, NSUInteger line, NSString *logMessageClass, NSString *prefix, NSString *format, ...) OO_TAKES_FORMAT_STRING(7, 8);
148 0 : #define SyntaxError(CONTEXT, CLASS, FORMAT, ...) SyntaxIssue(CONTEXT, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, CLASS, OOLOG_WARNING_PREFIX, FORMAT, ## __VA_ARGS__)
149 :
150 : #if WARNINGS
151 0 : #define SyntaxWarning(CONTEXT, CLASS, FORMAT, ...) SyntaxIssue(CONTEXT, OOLOG_FUNCTION_NAME, OOLOG_FILE_NAME, __LINE__, CLASS, OOLOG_WARNING_PREFIX, FORMAT, ## __VA_ARGS__)
152 : #else
153 : #define SyntaxWarning(...) do {} while (0)
154 : #endif
155 :
156 :
157 : // MARK: -
158 : // MARK: Public functions
159 :
160 0 : NSString *OOExpandDescriptionString(Random_Seed seed, NSString *string, NSDictionary *overrides, NSDictionary *legacyLocals, NSString *systemName, OOExpandOptions options)
161 : {
162 : if (string == nil) return nil;
163 :
164 : OOStringExpansionContext context =
165 : {
166 : .seed = seed,
167 : .systemName = [systemName retain],
168 : .overrides = [overrides retain],
169 : .legacyLocals = [legacyLocals retain],
170 : .isJavaScript = options & kOOExpandForJavaScript,
171 : .convertBackslashN = options & kOOExpandBackslashN,
172 : .useGoodRNG = options & kOOExpandGoodRNG
173 : };
174 :
175 : // Avoid recursive %I expansion by pre-seeding cache with literal %I.
176 : if (options & kOOExpandDisallowPercentI) {
177 : context.systemNameWithIan = @"%I";
178 : }
179 :
180 : OORandomState savedRandomState;
181 : if (options & kOOExpandReseedRNG)
182 : {
183 : savedRandomState = OOSaveRandomState();
184 : OOSetReallyRandomRANROTAndRndSeeds();
185 : }
186 :
187 : NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
188 : NSString *result = nil, *intermediate = nil;
189 : @try
190 : {
191 : // TODO: profile caching the results. Would need to keep track of whether we've done something nondeterministic (array selection, %R etc).
192 : if (options & kOOExpandKey)
193 : {
194 : intermediate = ExpandStringKey(&context, string, kStackAllocationLimit, kRecursionLimit);
195 : }
196 : else
197 : {
198 : intermediate = Expand(&context, string, kStackAllocationLimit, kRecursionLimit);
199 : }
200 : if (!context.hasPercentR)
201 : {
202 : result = intermediate;
203 : }
204 : else
205 : {
206 : result = ExpandPercentR(&context, intermediate);
207 : }
208 : }
209 : @finally
210 : {
211 : [context.systemName release];
212 : [context.overrides release];
213 : [context.legacyLocals release];
214 : [context.systemNameWithIan release];
215 : [context.randomNameN release];
216 : [context.randomNameR release];
217 : [context.systemDescriptions release];
218 : }
219 :
220 : if (options & kOOExpandReseedRNG)
221 : {
222 : OORestoreRandomState(savedRandomState);
223 : }
224 :
225 : result = [result copy];
226 : [pool release];
227 : return [result autorelease];
228 : }
229 :
230 :
231 0 : NSString *OOGenerateSystemDescription(Random_Seed seed, NSString *name)
232 : {
233 : seed_RNG_only_for_planet_description(seed);
234 : return OOExpandDescriptionString(seed, @"system-description-string", nil, nil, name, kOOExpandKey);
235 : }
236 :
237 :
238 0 : Random_Seed OOStringExpanderDefaultRandomSeed(void)
239 : {
240 : return [[UNIVERSE systemManager] getRandomSeedForCurrentSystem];
241 : }
242 :
243 :
244 : // MARK: -
245 : // MARK: Guts
246 :
247 :
248 : /* Expand(context, string, sizeLimit, recursionLimit)
249 :
250 : Top-level expander. Expands all types of substitution in a string.
251 :
252 : <sizeLimit> is the remaining budget for stack allocation of read buffers.
253 : (Expand() is the only function that creates such buffers.) <recursionLimit>
254 : limits the number of recursive calls of Expand() that are permitted. If one
255 : of the limits would be exceeded, Expand() returns the input string unmodified.
256 : */
257 0 : static NSString *Expand(OOStringExpansionContext *context, NSString *string, NSUInteger sizeLimit, NSUInteger recursionLimit)
258 : {
259 : NSCParameterAssert(string != nil && context != NULL && sizeLimit <= kStackAllocationLimit);
260 :
261 : const NSUInteger size = [string length];
262 :
263 : // Avoid stack overflow.
264 : if (EXPECT_NOT(size > sizeLimit || recursionLimit == 0)) return string;
265 : sizeLimit -= size;
266 : recursionLimit--;
267 :
268 : // Nothing to expand in an empty string, and the size-1 thing below would be trouble.
269 : if (size == 0) return string;
270 :
271 : unichar characters[size];
272 : [string getCharacters:characters range:(NSRange){ 0, size }];
273 :
274 : /* Beginning of current range of non-special characters. If we encounter
275 : a substitution, we'll be copying from here forward.
276 : */
277 : NSUInteger copyRangeStart = 0;
278 :
279 : // Mutable string for result if we perform any substitutions.
280 : NSMutableString *result = nil;
281 :
282 : /* The iteration limit is size - 1 because every valid substitution is at
283 : least 2 characters long. This way, characters[idx + 1] is always valid.
284 : */
285 : for (NSUInteger idx = 0; idx < size - 1; idx++)
286 : {
287 : /* Main parsing loop. If, at the end of the loop, replacement != nil,
288 : we copy the characters from copyRangeStart to idx into the result,
289 : the insert replacement, and skip replaceLength characters forward
290 : (minus one, because idx is incremented by the loop.)
291 : */
292 : NSString *replacement = nil;
293 : NSUInteger replaceLength = 0;
294 : unichar thisChar = characters[idx];
295 :
296 : if (thisChar == '[')
297 : {
298 : replacement = ExpandKey(context, characters, size, idx, &replaceLength, sizeLimit, recursionLimit);
299 : }
300 : else if (thisChar == '%')
301 : {
302 : replacement = ExpandPercentEscape(context, characters, size, idx, &replaceLength);
303 : }
304 : else if (thisChar == ']')
305 : {
306 : SyntaxWarning(context, @"strings.expand.warning.unbalancedClosingBracket", @"%@", @"Unbalanced ] in string.");
307 : }
308 : else if (thisChar == '\\' && context->convertBackslashN)
309 : {
310 : if (characters[idx + 1] == 'n')
311 : {
312 : replaceLength = 2;
313 : replacement = @"\n";
314 : }
315 : }
316 : else
317 : {
318 : // No token start character, so we definitely have no replacement.
319 : continue;
320 : }
321 :
322 : if (replacement != nil)
323 : {
324 : /* If replacement string is "\x7F", eat the following character.
325 : This is used in system_description for the one empty string
326 : in [22].
327 : */
328 : if ([replacement isEqualToString:@"\x7F"] && replaceLength < size)
329 : {
330 : replaceLength++;
331 : replacement = @"";
332 : }
333 :
334 : // Avoid copying if we're replacing the entire input string.
335 : if (copyRangeStart == 0 && replaceLength == size)
336 : {
337 : return replacement;
338 : }
339 :
340 : // Write the pending literal segment to result. This also allocates result if needed.
341 : AppendCharacters(&result, characters, copyRangeStart, idx);
342 :
343 : [result appendString:replacement];
344 :
345 : // Skip over replaced part and start a new literal segment.
346 : idx += replaceLength - 1;
347 : copyRangeStart = idx + 1;
348 : }
349 : }
350 :
351 : if (result != nil)
352 : {
353 : // Append any trailing literal segment.
354 : AppendCharacters(&result, characters, copyRangeStart, size);
355 :
356 : // Don't turn result immutable; doing it once at top level is sufficient.
357 : return result;
358 : }
359 : else
360 : {
361 : // No substitutions, return original string.
362 : return string;
363 : }
364 : }
365 :
366 :
367 : /* ExpandKey(context, characters, size, idx, replaceLength, sizeLimit, recursionLimit)
368 :
369 : Expand a substitution key, i.e. a section surrounded by square brackets.
370 : On entry, <idx> is the offset to an opening bracket. ExpandKey() searches
371 : for the balancing closing bracket, and if it is found dispatches to either
372 : ExpandDigitKey() (for a key consisting only of digits) or ExpandStringKey()
373 : (for anything else).
374 :
375 : The key may be terminated by a vertical bar |, followed by an operator. An
376 : operator is an identifier, optionally followed by a colon and additional
377 : text, and may be terminated with another bar and operator.
378 : */
379 0 : static NSString *ExpandKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength, NSUInteger sizeLimit, NSUInteger recursionLimit)
380 : {
381 : NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL);
382 : NSCParameterAssert(characters[idx] == '[');
383 :
384 : // Find the balancing close bracket.
385 : NSUInteger end, balanceCount = 1, firstBar = 0;
386 : bool allDigits = true;
387 :
388 : for (end = idx + 1; end < size && balanceCount > 0; end++)
389 : {
390 : if (characters[end] == ']') balanceCount--;
391 : else
392 : {
393 : if (characters[end] == '[') balanceCount++;
394 : else if (characters[end] == '|' && firstBar == 0) firstBar = end;
395 : if (!isdigit(characters[end]) && firstBar == 0) allDigits = false;
396 : }
397 : }
398 :
399 : // Fail if no balancing bracket.
400 : if (EXPECT_NOT(balanceCount != 0))
401 : {
402 : SyntaxWarning(context, @"strings.expand.warning.unbalancedOpeningBracket", @"%@", @"Unbalanced [ in string.");
403 : return nil;
404 : }
405 :
406 : NSUInteger totalLength = end - idx;
407 : *replaceLength = totalLength;
408 : NSUInteger keyStart = idx + 1, keyLength = totalLength - 2;
409 : if (firstBar != 0) keyLength = firstBar - idx - 1;
410 :
411 : if (EXPECT_NOT(keyLength == 0))
412 : {
413 : SyntaxWarning(context, @"strings.expand.warning.emptyKey", @"%@", @"Invalid expansion code [] string. (To avoid this message, use %%[%%].)");
414 : return nil;
415 : }
416 :
417 : NSString *expanded = nil;
418 : if (allDigits)
419 : {
420 : expanded = ExpandDigitKey(context, characters, keyStart, keyLength, sizeLimit, recursionLimit);
421 : }
422 : else
423 : {
424 : NSString *key = [NSString stringWithCharacters:characters + keyStart length:keyLength];
425 : expanded = ExpandStringKey(context, key, sizeLimit, recursionLimit);
426 : }
427 :
428 : if (firstBar != 0)
429 : {
430 : NSString *operators = [NSString stringWithCharacters:characters + firstBar + 1 length:end - firstBar - 2];
431 : expanded = ApplyOperators(expanded, operators);
432 : }
433 :
434 : return expanded;
435 : }
436 :
437 :
438 : /* ApplyOperators(string, operatorsString)
439 :
440 : Given a string and a series of formatting operators separated by vertical
441 : bars (or a single formatting operator), apply the operators, in sequence,
442 : to the string.
443 : */
444 0 : static NSString *ApplyOperators(NSString *string, NSString *operatorsString)
445 : {
446 : NSArray *operators = [operatorsString componentsSeparatedByString:@"|"];
447 : NSString *op = nil;
448 :
449 : foreach(op, operators)
450 : {
451 : NSString *param = nil;
452 : NSRange colon = [op rangeOfString:@":"];
453 : if (colon.location != NSNotFound)
454 : {
455 : param = [op substringFromIndex:colon.location + colon.length];
456 : op = [op substringToIndex:colon.location];
457 : }
458 : string = ApplyOneOperator(string, op, param);
459 : }
460 :
461 : return string;
462 : }
463 :
464 :
465 0 : static NSString *Operator_cr(NSString *string, NSString *param)
466 : {
467 : return OOCredits([string doubleValue] * 10);
468 : }
469 :
470 :
471 0 : static NSString *Operator_dcr(NSString *string, NSString *param)
472 : {
473 : return OOCredits([string longLongValue]);
474 : }
475 :
476 :
477 0 : static NSString *Operator_icr(NSString *string, NSString *param)
478 : {
479 : return OOIntCredits([string longLongValue]);
480 : }
481 :
482 :
483 0 : static NSString *Operator_idcr(NSString *string, NSString *param)
484 : {
485 : return OOIntCredits(round([string doubleValue] / 10.0));
486 : }
487 :
488 :
489 0 : static NSString *Operator_precision(NSString *string, NSString *param)
490 : {
491 : return [NSString stringWithFormat:@"%.*f", [param intValue], [string doubleValue]];
492 : }
493 :
494 :
495 0 : static NSString *Operator_multiply(NSString *string, NSString *param)
496 : {
497 : return [NSString stringWithFormat:@"%g", [string doubleValue] * [param doubleValue]];
498 : }
499 :
500 :
501 0 : static NSString *Operator_add(NSString *string, NSString *param)
502 : {
503 : return [NSString stringWithFormat:@"%g", [string doubleValue] + [param doubleValue]];
504 : }
505 :
506 :
507 : /* ApplyOneOperator(string, op, param)
508 :
509 : Apply a single formatting operator to a string.
510 :
511 : For example, the expansion expression "[distance|precision:1]" will be
512 : expanded by a call to ApplyOneOperator(@"distance", @"precision", @"1").
513 :
514 : <param> may be nil, indicating an operator with no parameter (no colon).
515 : */
516 0 : static NSString *ApplyOneOperator(NSString *string, NSString *op, NSString *param)
517 : {
518 : static NSDictionary *operators = nil;
519 :
520 : if (operators == nil)
521 : {
522 0 : #define OPERATOR(name) [NSValue valueWithPointer:Operator_##name], @#name
523 : operators = [[NSDictionary alloc] initWithObjectsAndKeys:
524 : OPERATOR(dcr),
525 : OPERATOR(cr),
526 : OPERATOR(icr),
527 : OPERATOR(idcr),
528 : OPERATOR(precision),
529 : OPERATOR(multiply),
530 : OPERATOR(add),
531 : nil];
532 : }
533 :
534 : NSString *(*operator)(NSString *string, NSString *param) = [[operators objectForKey:op] pointerValue];
535 : if (operator != NULL)
536 : {
537 : return operator(string, param);
538 : }
539 :
540 : OOLogERR(@"strings.expand.invalidOperator", @"Unknown string expansion operator %@", op);
541 : return string;
542 : }
543 :
544 :
545 : /* ExpandDigitKey(context, characters, keyStart, keyLength, sizeLimit, recursionLimit)
546 :
547 : Expand a key (as per ExpandKey()) consisting entirely of digits. <keyStart>
548 : and <keyLength> specify the range of characters containing the key.
549 :
550 : Digit-only keys are looked up in the system_description array in
551 : descriptions.plist, which is expected to contain only arrays of strings (no
552 : loose strings). When an array is retrieved, a string is selected from it
553 : at random and the result is expanded recursively by calling Expand().
554 : */
555 0 : static NSString *ExpandDigitKey(OOStringExpansionContext *context, const unichar *characters, NSUInteger keyStart, NSUInteger keyLength, NSUInteger sizeLimit, NSUInteger recursionLimit)
556 : {
557 : NSCParameterAssert(context != NULL && characters != NULL);
558 :
559 : NSUInteger keyValue = 0, idx;
560 : for (idx = keyStart; idx < (keyStart + keyLength); idx++)
561 : {
562 : NSCAssert2(isdigit(characters[idx]), @"%s called with non-numeric key [%@].", __FUNCTION__, [NSString stringWithCharacters:characters + keyStart length:keyLength]);
563 :
564 : keyValue = keyValue * 10 + characters[idx] - '0';
565 : }
566 :
567 : // Retrieve selected system_description entry.
568 : NSArray *sysDescs = GetSystemDescriptions(context);
569 : NSArray *entry = [sysDescs oo_arrayAtIndex:keyValue];
570 :
571 : if (EXPECT_NOT(entry == nil))
572 : {
573 : if (keyValue >= context->sysDescCount)
574 : {
575 : SyntaxWarning(context, @"strings.expand.warning.outOfRangeKey", @"Out-of-range system description expansion key [%@] in string.", [NSString stringWithCharacters:characters + keyStart length:keyLength]);
576 : }
577 : else
578 : {
579 : // This is out of the scope of whatever triggered it, so shouldn't be a JS warning.
580 : OOLogERR(@"strings.expand.invalidData", @"%@", @"descriptions.plist entry system_description must be an array of arrays of strings.");
581 : }
582 : return nil;
583 : }
584 :
585 : // Select a random sub-entry.
586 : NSUInteger selection, count = [entry count];
587 : NSUInteger rnd = OO_EXPANDER_RANDOM;
588 : if (count == 5 && !context->useGoodRNG)
589 : {
590 : // Time-honoured Elite-compatible way for five items.
591 : if (rnd >= 0xCC) selection = 4;
592 : else if (rnd >= 0x99) selection = 3;
593 : else if (rnd >= 0x66) selection = 2;
594 : else if (rnd >= 0x33) selection = 1;
595 : else selection = 0;
596 : }
597 : else
598 : {
599 : // General way.
600 : selection = (rnd * count) / 256;
601 : }
602 :
603 : // Look up and recursively expand string.
604 : NSString *string = [entry oo_stringAtIndex:selection];
605 : return Expand(context, string, sizeLimit, recursionLimit);
606 : }
607 :
608 :
609 : /* ExpandStringKey(context, key, sizeLimit, recursionLimit)
610 :
611 : Expand a key (as per ExpandKey()) which doesn't consist entirely of digits.
612 : Looks for the key in a number of different places in prioritized order.
613 : */
614 0 : static NSString *ExpandStringKey(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit)
615 : {
616 : NSCParameterAssert(context != NULL && key != nil);
617 :
618 : // Overrides have top priority.
619 : NSString *result = ExpandStringKeyOverride(context, key);
620 :
621 : // Specials override descriptions.plist.
622 : if (result == nil) result = ExpandStringKeySpecial(context, key);
623 :
624 : // Now try descriptions.plist.
625 : if (result == nil) result = ExpandStringKeyFromDescriptions(context, key, sizeLimit, recursionLimit);
626 :
627 : // For efficiency, descriptions.plist overrides keybindings.
628 : // OXPers should therefore avoid oolite_key_ description keys
629 : if (result == nil) result = ExpandStringKeyKeyboardBinding(context, key);
630 :
631 : // Try mission variables.
632 : if (result == nil) result = ExpandStringKeyMissionVariable(context, key);
633 :
634 : // Try legacy local variables.
635 : if (result == nil) result = ExpandStringKeyLegacyLocalVariable(context, key);
636 :
637 : // Try legacy script methods.
638 : if (result == nil) ExpandLegacyScriptSelectorKey(context, key);
639 :
640 : #if WARNINGS
641 : // None of that worked, so moan a bit.
642 : if (result == nil) ReportWarningForUnknownKey(context, key);
643 : #endif
644 :
645 : return result;
646 : }
647 :
648 :
649 : /* ExpandStringKeyOverride(context, key)
650 :
651 : Attempt to expand a key by retriving it from the overrides dictionary of
652 : the context (ultimately from OOExpandDescriptionString()). Overrides are
653 : used to provide context-specific expansions, such as "[self:name]" in
654 : comms messages, and can also be used from JavaScript.
655 :
656 : The main difference between overrides and legacy locals is priority.
657 : */
658 0 : static NSString *ExpandStringKeyOverride(OOStringExpansionContext *context, NSString *key)
659 : {
660 : NSCParameterAssert(context != NULL && key != nil);
661 :
662 : id value = [context->overrides objectForKey:key];
663 : if (value != nil)
664 : {
665 : #if WARNINGS
666 : if (![value isKindOfClass:[NSString class]] && ![value isKindOfClass:[NSNumber class]])
667 : {
668 : SyntaxWarning(context, @"strings.expand.warning.invalidOverride", @"String expansion override value %@ for [%@] is not a string or number.", [value shortDescription], key);
669 : }
670 : #endif
671 : return [value description];
672 : }
673 :
674 : return nil;
675 : }
676 :
677 :
678 : /* ExpandStringKeySpecial(context, key)
679 :
680 : Attempt to expand a key by matching a set of special expansion codes that
681 : call PlayerEntity methods but aren't legacy script methods. Also unlike
682 : legacy script methods, all these methods return strings.
683 : */
684 0 : static NSString *ExpandStringKeySpecial(OOStringExpansionContext *context, NSString *key)
685 : {
686 : NSCParameterAssert(context != NULL && key != nil);
687 :
688 : NSMapTable *specials = SpecialSubstitutionSelectors();
689 : SEL selector = NSMapGet(specials, key);
690 : if (selector != NULL)
691 : {
692 : NSCAssert2([PLAYER respondsToSelector:selector], @"Special string expansion selector %@ for [%@] is not implemented.", NSStringFromSelector(selector), key);
693 :
694 : NSString *result = [PLAYER performSelector:selector];
695 : if (result != nil)
696 : {
697 : NSCAssert2([result isKindOfClass:[NSString class]], @"Special string expansion [%@] expanded to %@, but expected a string.", key, [result shortDescription]);
698 : return result;
699 : }
700 : }
701 :
702 : return nil;
703 : }
704 :
705 :
706 : /* ExpandStringKeyKeyboardBinding(context, key)
707 :
708 : Attempt to expand a key by matching it against the keybindings
709 : */
710 0 : static NSString *ExpandStringKeyKeyboardBinding(OOStringExpansionContext *context, NSString *key)
711 : {
712 : NSCParameterAssert(context != NULL && key != nil);
713 : if ([key hasPrefix:@"oolite_key_"])
714 : {
715 : NSString *binding = [key substringFromIndex:7];
716 : return [PLAYER keyBindingDescription2:binding];
717 : }
718 : return nil;
719 : }
720 :
721 :
722 : /* SpecialSubstitutionSelectors()
723 :
724 : Retrieve the mapping of special keys for ExpandStringKeySpecial() to
725 : selectors.
726 : */
727 0 : static NSMapTable *SpecialSubstitutionSelectors(void)
728 : {
729 : static NSMapTable *specials = NULL;
730 : if (specials != NULL) return specials;
731 :
732 : struct { NSString *key; SEL selector; } selectors[] =
733 : {
734 : { @"commander_name", @selector(commanderName_string) },
735 : { @"commander_shipname", @selector(commanderShip_string) },
736 : { @"commander_shipdisplayname", @selector(commanderShipDisplayName_string) },
737 : { @"commander_rank", @selector(commanderRank_string) },
738 : { @"commander_kills", @selector(commanderKillsAsString) },
739 : { @"commander_legal_status", @selector(commanderLegalStatus_string) },
740 : { @"commander_bounty", @selector(commanderBountyAsString) },
741 : { @"credits_number", @selector(creditsFormattedForSubstitution) },
742 : { @"_oo_legacy_credits_number", @selector(creditsFormattedForLegacySubstitution) }
743 : };
744 : unsigned i, count = sizeof selectors / sizeof *selectors;
745 :
746 : specials = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, count);
747 : for (i = 0; i < count; i++)
748 : {
749 : NSMapInsertKnownAbsent(specials, selectors[i].key, selectors[i].selector);
750 : }
751 :
752 : return specials;
753 : }
754 :
755 :
756 : /* ExpandStringKeyFromDescriptions(context, key, sizeLimit, recursionLimit)
757 :
758 : Attempt to expand a key by looking it up in descriptions.plist. Matches
759 : may be single strings or arrays of strings. For arrays, one of the strings
760 : is selected at random.
761 :
762 : Matched strings are expanded recursively by calling Expand().
763 : */
764 0 : static NSString *ExpandStringKeyFromDescriptions(OOStringExpansionContext *context, NSString *key, NSUInteger sizeLimit, NSUInteger recursionLimit)
765 : {
766 : id value = [[UNIVERSE descriptions] objectForKey:key];
767 : if (value != nil)
768 : {
769 : if ([value isKindOfClass:[NSArray class]] && [value count] > 0)
770 : {
771 : NSUInteger rnd = OO_EXPANDER_RANDOM % [value count];
772 : value = [value oo_objectAtIndex:rnd];
773 : }
774 :
775 : if (![value isKindOfClass:[NSString class]])
776 : {
777 : // This is out of the scope of whatever triggered it, so shouldn't be a JS warning.
778 : OOLogERR(@"strings.expand.invalidData", @"String expansion value %@ for [%@] from descriptions.plist is not a string or number.", [value shortDescription], key);
779 : return nil;
780 : }
781 :
782 : // Expand recursively.
783 : return Expand(context, value, sizeLimit, recursionLimit);
784 : }
785 :
786 : return nil;
787 : }
788 :
789 :
790 : /* ExpandStringKeyMissionVariable(context, key)
791 :
792 : Attempt to expand a key by matching it to a mission variable.
793 : */
794 0 : static NSString *ExpandStringKeyMissionVariable(OOStringExpansionContext *context, NSString *key)
795 : {
796 : if ([key hasPrefix:@"mission_"])
797 : {
798 : return [PLAYER missionVariableForKey:key];
799 : }
800 :
801 : return nil;
802 : }
803 :
804 :
805 : /* ExpandStringKeyMissionVariable(context, key)
806 :
807 : Attempt to expand a key by matching it to a legacy local variable.
808 :
809 : The main difference between overrides and legacy locals is priority.
810 : */
811 0 : static NSString *ExpandStringKeyLegacyLocalVariable(OOStringExpansionContext *context, NSString *key)
812 : {
813 : return [[context->legacyLocals objectForKey:key] description];
814 : }
815 :
816 :
817 : /* ExpandLegacyScriptSelectorKey(context, key)
818 :
819 : Attempt to expand a key by treating it as a legacy script query method and
820 : invoking it. Only whitelisted methods are permitted, and aliases are
821 : respected.
822 : */
823 0 : static NSString *ExpandLegacyScriptSelectorKey(OOStringExpansionContext *context, NSString *key)
824 : {
825 : NSCParameterAssert(context != NULL && key != nil);
826 :
827 : SEL selector = LookUpLegacySelector(key);
828 :
829 : if (selector != NULL)
830 : {
831 : return [[PLAYER performSelector:selector] description];
832 : }
833 : else
834 : {
835 : return nil;
836 : }
837 : }
838 :
839 :
840 : /* LookUpLegacySelector(key)
841 :
842 : If <key> is a whitelisted legacy script query method, or aliases to one,
843 : return the corresponding selector.
844 : */
845 0 : static SEL LookUpLegacySelector(NSString *key)
846 : {
847 : SEL selector = NULL;
848 : static NSMapTable *selectorCache = NULL;
849 :
850 : // Try cache lookup.
851 : if (selectorCache != NULL)
852 : {
853 : selector = NSMapGet(selectorCache, key);
854 : }
855 :
856 : if (selector == NULL)
857 : {
858 : static NSDictionary *aliases = nil;
859 : static NSSet *whitelist = nil;
860 : if (whitelist == nil)
861 : {
862 : NSDictionary *whitelistDict = [ResourceManager whitelistDictionary];
863 : whitelist = [[NSSet alloc] initWithArray:[whitelistDict oo_arrayForKey:@"query_methods"]];
864 : aliases = [[whitelistDict oo_dictionaryForKey:@"query_method_aliases"] copy];
865 : }
866 :
867 : NSString *selectorName = [aliases oo_stringForKey:key];
868 : if (selectorName == nil) selectorName = key;
869 :
870 : if ([whitelist containsObject:selectorName])
871 : {
872 : selector = NSSelectorFromString(selectorName);
873 :
874 : /* This is an assertion, not a warning, because whitelist.plist is
875 : part of the game and cannot be overriden by OXPs. If there is an
876 : invalid selector in the whitelist, it's a game bug.
877 : */
878 : NSCAssert1([PLAYER respondsToSelector:selector], @"Player does not respond to whitelisted query selector %@.", key);
879 : }
880 :
881 : if (selector != NULL)
882 : {
883 : // Add it to cache.
884 : if (selectorCache == NULL)
885 : {
886 : selectorCache = NSCreateMapTable(NSObjectMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, [whitelist count]);
887 : }
888 : NSMapInsertKnownAbsent(selectorCache, key, selector);
889 : }
890 : }
891 :
892 : return selector;
893 : }
894 :
895 :
896 : #if WARNINGS
897 : /* ReportWarningForUnknownKey(context, key)
898 :
899 : Called when we fall through all the various ways of expanding string keys
900 : above. If the key looks like a legacy script query method, assume it is
901 : and report a bad selector. Otherwise, report it as an unknown key.
902 : */
903 0 : static void ReportWarningForUnknownKey(OOStringExpansionContext *context, NSString *key)
904 : {
905 : if ([key hasSuffix:@"_string"] || [key hasSuffix:@"_number"] || [key hasSuffix:@"_bool"])
906 : {
907 : SyntaxError(context, @"strings.expand.invalidSelector", @"Unpermitted legacy script method [%@] in string.", key);
908 : }
909 : else
910 : {
911 : SyntaxWarning(context, @"strings.expand.warning.unknownExpansion", @"Unknown expansion key [%@] in string.", key);
912 : }
913 : }
914 : #endif
915 :
916 :
917 : /* ExpandKey(context, characters, size, idx, replaceLength)
918 :
919 : Expand an escape code. <idx> is the index of the % sign introducing the
920 : escape code. Supported escape codes are:
921 : %H
922 : %I
923 : %N
924 : %R
925 : %J###, where ### are three digits
926 : %G######, where ### are six digits
927 : %%
928 : %[
929 : %]
930 :
931 : In addition, the codes %@, %d and %. are ignored, because they're used
932 : with -[NSString stringWithFormat:] on strings that have already been
933 : expanded.
934 :
935 : Any other code results in a warning.
936 : */
937 0 : static NSString *ExpandPercentEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength)
938 : {
939 : NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL);
940 : NSCParameterAssert(characters[idx] == '%');
941 :
942 : // All %-escapes except %J and %G are 2 characters.
943 : *replaceLength = 2;
944 : unichar selector = characters[idx + 1];
945 :
946 : switch (selector)
947 : {
948 : case 'H':
949 : return GetSystemName(context);
950 :
951 : case 'I':
952 : return GetSystemNameIan(context);
953 :
954 : case 'N':
955 : return GetRandomNameN(context);
956 :
957 : case 'R':
958 : // to keep planet description generation consistent with earlier versions
959 : // this must be done after all other substitutions in a second pass.
960 : context->hasPercentR = true;
961 : return @"%R";
962 :
963 : case 'G':
964 : return ExpandSystemNameForGalaxyEscape(context, characters, size, idx, replaceLength);
965 :
966 : case 'J':
967 : return ExpandSystemNameEscape(context, characters, size, idx, replaceLength);
968 :
969 : case '%':
970 : return @"%";
971 :
972 : case '[':
973 : return @"[";
974 :
975 : case ']':
976 : return @"]";
977 :
978 : /* These are NSString formatting specifiers that occur in
979 : descriptions.plist. The '.' is for floating-point (g and f)
980 : specifiers that have field widths specified. No unadorned
981 : %f or %g is found in vanilla Oolite descriptions.plist.
982 :
983 : Ideally, these would be replaced with the caller formatting
984 : the value and passing it as an override - it would be safer
985 : and make descriptions.plist clearer - but it would be a big
986 : job and uglify the callers without newfangled Objective-C
987 : dictionary literals.
988 : -- Ahruman 2012-10-05
989 : */
990 : case '@':
991 : case 'd':
992 : case '.':
993 : return nil;
994 :
995 : default:
996 : // Yay, percent signs!
997 : SyntaxWarning(context, @"strings.expand.warning.unknownPercentEscape", @"Unknown escape code in string: %%%lc. (To encode a %% sign without this warning, use %%%% - but prefer \"percent\" in prose writing.)", selector);
998 :
999 : return nil;
1000 : }
1001 : }
1002 :
1003 :
1004 : /* ExpandPercentR(context, string)
1005 : Replaces all %R in string with its expansion.
1006 : Separate to allow this to be delayed to the end of the string expansion
1007 : for compatibility with 1.76 expansion of %R in planet descriptions
1008 : */
1009 0 : static NSString *ExpandPercentR(OOStringExpansionContext *context, NSString *input)
1010 : {
1011 : NSRange containsR = [input rangeOfString:@"%R"];
1012 : if (containsR.location == NSNotFound)
1013 : {
1014 : return input; // no %Rs to replace
1015 : }
1016 : NSString *percentR = GetRandomNameR(context);
1017 : NSMutableString *output = [NSMutableString stringWithString:input];
1018 :
1019 : /* This loop should be completely unnecessary, but for some reason
1020 : * replaceOccurrencesOfString sometimes only replaces the first
1021 : * instance of %R if percentR contains the non-ASCII
1022 : * digrams-apostrophe character. (I guess
1023 : * http://lists.gnu.org/archive/html/gnustep-dev/2011-10/msg00048.html
1024 : * this bug in GNUstep's implementation here, which is in 1.22) So
1025 : * to cover that case, if there are still %R in the string after
1026 : * replacement, try again. Affects things like thargoid curses, and
1027 : * particularly %Rful expansions of [nom]. Probably this can be
1028 : * tidied up once GNUstep 1.22 is ancient history, but that'll be a
1029 : * few years yet. - CIM 15/1/2013 */
1030 :
1031 : do {
1032 : [output replaceOccurrencesOfString:@"%R" withString:percentR options:NSLiteralSearch range:NSMakeRange(0, [output length])];
1033 : } while([output rangeOfString:@"%R"].location != NSNotFound);
1034 :
1035 : return [NSString stringWithString:output];
1036 : }
1037 :
1038 :
1039 : /* ExpandSystemNameForGalaxyEscape(context, characters, size, idx, replaceLength)
1040 :
1041 : Expand a %G###### code by looking up the corresponding system name in any
1042 : cgalaxy.
1043 : */
1044 0 : static NSString *ExpandSystemNameForGalaxyEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength)
1045 : {
1046 : NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL);
1047 : NSCParameterAssert(characters[idx + 1] == 'G');
1048 :
1049 : // A valid %G escape is always eight characters including the six digits.
1050 : *replaceLength = 8;
1051 :
1052 0 : #define kInvalidGEscapeMessage @"String escape code %G must be followed by six integers."
1053 : if (EXPECT_NOT(size - idx < 8))
1054 : {
1055 : // Too close to end of string to actually have six characters, let alone six digits.
1056 : SyntaxError(context, @"strings.expand.invalidJEscape", @"%@", kInvalidGEscapeMessage);
1057 : return nil;
1058 : }
1059 :
1060 : char hundreds = characters[idx + 2];
1061 : char tens = characters[idx + 3];
1062 : char units = characters[idx + 4];
1063 : char galHundreds = characters[idx + 5];
1064 : char galTens = characters[idx + 6];
1065 : char galUnits = characters[idx + 7];
1066 :
1067 : if (!(isdigit(hundreds) && isdigit(tens) && isdigit(units) && isdigit(galHundreds) && isdigit(galTens) && isdigit(galUnits)))
1068 : {
1069 : SyntaxError(context, @"strings.expand.invalidJEscape", @"%@", kInvalidGEscapeMessage);
1070 : return nil;
1071 : }
1072 :
1073 : OOSystemID sysID = (hundreds - '0') * 100 + (tens - '0') * 10 + (units - '0');
1074 : if (sysID > kOOMaximumSystemID)
1075 : {
1076 : SyntaxError(context, @"strings.expand.invalidJEscape.range", @"String escape code %%G%3u for system is out of range (must be less than %u).", sysID, kOOMaximumSystemID + 1);
1077 : return nil;
1078 : }
1079 :
1080 : OOGalaxyID galID = (galHundreds - '0') * 100 + (galTens - '0') * 10 + (galUnits - '0');
1081 : if (galID > kOOMaximumGalaxyID)
1082 : {
1083 : SyntaxError(context, @"strings.expand.invalidJEscape.range", @"String escape code %%G%3u for galaxy is out of range (must be less than %u).", galID, kOOMaximumGalaxyID + 1);
1084 : return nil;
1085 : }
1086 :
1087 : return [UNIVERSE getSystemName:sysID forGalaxy:galID];
1088 : }
1089 :
1090 :
1091 : /* ExpandSystemNameEscape(context, characters, size, idx, replaceLength)
1092 :
1093 : Expand a %J### code by looking up the corresponding system name in the
1094 : current galaxy.
1095 : */
1096 0 : static NSString *ExpandSystemNameEscape(OOStringExpansionContext *context, const unichar *characters, NSUInteger size, NSUInteger idx, NSUInteger *replaceLength)
1097 : {
1098 : NSCParameterAssert(context != NULL && characters != NULL && replaceLength != NULL);
1099 : NSCParameterAssert(characters[idx + 1] == 'J');
1100 :
1101 : // A valid %J escape is always five characters including the three digits.
1102 : *replaceLength = 5;
1103 :
1104 0 : #define kInvalidJEscapeMessage @"String escape code %J must be followed by three integers."
1105 : if (EXPECT_NOT(size - idx < 5))
1106 : {
1107 : // Too close to end of string to actually have three characters, let alone three digits.
1108 : SyntaxError(context, @"strings.expand.invalidJEscape", @"%@", kInvalidJEscapeMessage);
1109 : return nil;
1110 : }
1111 :
1112 : char hundreds = characters[idx + 2];
1113 : char tens = characters[idx + 3];
1114 : char units = characters[idx + 4];
1115 :
1116 : if (!(isdigit(hundreds) && isdigit(tens) && isdigit(units)))
1117 : {
1118 : SyntaxError(context, @"strings.expand.invalidJEscape", @"%@", kInvalidJEscapeMessage);
1119 : return nil;
1120 : }
1121 :
1122 : OOSystemID sysID = (hundreds - '0') * 100 + (tens - '0') * 10 + (units - '0');
1123 : if (sysID > kOOMaximumSystemID)
1124 : {
1125 : SyntaxError(context, @"strings.expand.invalidJEscape.range", @"String escape code %%J%3u is out of range (must be less than %u).", sysID, kOOMaximumSystemID + 1);
1126 : return nil;
1127 : }
1128 :
1129 : return [UNIVERSE getSystemName:sysID];
1130 : }
1131 :
1132 :
1133 0 : static void AppendCharacters(NSMutableString **result, const unichar *characters, NSUInteger start, NSUInteger end)
1134 : {
1135 : NSCParameterAssert(result != NULL && characters != NULL && start <= end);
1136 :
1137 : if (*result == nil)
1138 : {
1139 : // Ensure there is a string. We want this even if the range is empty.
1140 : *result = [NSMutableString string];
1141 : }
1142 :
1143 : if (start == end) return;
1144 :
1145 : /* What we want here is a method like -[NSMutableString
1146 : appendCharacters:(unichar)characters length:(NSUInteger)length], which
1147 : unfortunately doesn't exist. On Mac OS X, CoreFoundation provides an
1148 : equivalent. For GNUstep, we have to use a temporary string.
1149 :
1150 : TODO: build the output string in a fixed-size stack buffer instead.
1151 : */
1152 : #if OOLITE_MAC_OS_X
1153 : CFStringAppendCharacters((CFMutableStringRef)*result, characters + start, end - start);
1154 : #else
1155 : NSString *temp = [[NSString alloc] initWithCharacters:characters + start length:end - start];
1156 : [*result appendString:temp];
1157 : [temp release];
1158 : #endif
1159 : }
1160 :
1161 :
1162 0 : static NSString *GetSystemName(OOStringExpansionContext *context)
1163 : {
1164 : NSCParameterAssert(context != NULL);
1165 : if (context->systemName == nil) {
1166 : context->systemName = [[UNIVERSE getSystemName:[PLAYER systemID]] retain];
1167 : }
1168 :
1169 : return context->systemName;
1170 : }
1171 :
1172 :
1173 0 : static NSString *GetSystemNameIan(OOStringExpansionContext *context)
1174 : {
1175 : NSCParameterAssert(context != NULL);
1176 :
1177 : if (context->systemNameWithIan == nil)
1178 : {
1179 : context->systemNameWithIan = [OOExpandWithOptions(context->seed, kOOExpandDisallowPercentI | kOOExpandGoodRNG | kOOExpandKey, @"planetname-possessive") retain];
1180 : }
1181 :
1182 : return context->systemNameWithIan;
1183 : }
1184 :
1185 :
1186 0 : static NSString *GetRandomNameN(OOStringExpansionContext *context)
1187 : {
1188 : NSCParameterAssert(context != NULL);
1189 :
1190 : if (context->randomNameN == nil)
1191 : {
1192 : context->randomNameN = [NewRandomDigrams(context) retain];
1193 : }
1194 :
1195 : return context->randomNameN;
1196 : }
1197 :
1198 :
1199 0 : static NSString *GetRandomNameR(OOStringExpansionContext *context)
1200 : {
1201 : NSCParameterAssert(context != NULL);
1202 :
1203 : if (context->randomNameR == nil)
1204 : {
1205 : context->randomNameR = [OldRandomDigrams() retain];
1206 : }
1207 :
1208 : return context->randomNameR;
1209 : }
1210 :
1211 :
1212 0 : static NSArray *GetSystemDescriptions(OOStringExpansionContext *context)
1213 : {
1214 : NSCParameterAssert(context != NULL);
1215 :
1216 : if (context->systemDescriptions == nil)
1217 : {
1218 : context->systemDescriptions = [[[UNIVERSE descriptions] oo_arrayForKey:@"system_description"] retain];
1219 : context->sysDescCount = [context->systemDescriptions count];
1220 : }
1221 :
1222 : return context->systemDescriptions;
1223 : }
1224 :
1225 :
1226 : /* Generates pseudo-random digram string using gen_rnd_number()
1227 : (world-generation consistent PRNG), but misses some possibilities. Used
1228 : for "%R" description string for backwards compatibility.
1229 : */
1230 0 : static NSString *OldRandomDigrams(void)
1231 : {
1232 : /* The only point of using %R is for world generation, so there's
1233 : * no point in checking the context */
1234 : unsigned len = gen_rnd_number() & 3;
1235 : NSString *digrams = [[UNIVERSE descriptions] objectForKey:@"digrams"];
1236 : NSMutableString *name = [NSMutableString stringWithCapacity:256];
1237 :
1238 : for (unsigned i = 0; i <=len; i++)
1239 : {
1240 : unsigned x = gen_rnd_number() & 0x3e;
1241 : [name appendString:[digrams substringWithRange:NSMakeRange(x, 2)]];
1242 : }
1243 :
1244 : return [name capitalizedString];
1245 : }
1246 :
1247 :
1248 : /* Generates pseudo-random digram string. Used for "%N" description string.
1249 : */
1250 0 : static NSString *NewRandomDigrams(OOStringExpansionContext *context)
1251 : {
1252 : unsigned length = (OO_EXPANDER_RANDOM % 4) + 1;
1253 : if ((OO_EXPANDER_RANDOM % 5) < ((length == 1) ? 3 : 1)) ++length; // Make two-letter names rarer and 10-letter names happen sometimes
1254 : NSString *digrams = [[UNIVERSE descriptions] objectForKey:@"digrams"];
1255 : NSUInteger count = [digrams length] / 2;
1256 : NSMutableString *name = [NSMutableString stringWithCapacity:length * 2];
1257 :
1258 : for (unsigned i = 0; i != length; ++i)
1259 : {
1260 : [name appendString:[digrams substringWithRange:NSMakeRange((OO_EXPANDER_RANDOM % count) * 2, 2)]];
1261 : }
1262 :
1263 : return [name capitalizedString];
1264 : }
1265 :
1266 :
1267 : static void SyntaxIssue(OOStringExpansionContext *context, const char *function, const char *fileName, NSUInteger line, NSString *logMessageClass, NSString *prefix, NSString *format, ...)
1268 : {
1269 : NSCParameterAssert(context != NULL);
1270 :
1271 : va_list args;
1272 : va_start(args, format);
1273 :
1274 : if (OOLogWillDisplayMessagesInClass(logMessageClass))
1275 : {
1276 : if (context->isJavaScript)
1277 : {
1278 : /* NOTE: syntax errors are reported as warnings when called from JS
1279 : because we don't want to start throwing exceptions when the old
1280 : expander didn't.
1281 : */
1282 : JSContext *jsc = OOJSAcquireContext();
1283 : OOJSReportWarningWithArguments(jsc, format, args);
1284 : OOJSRelinquishContext(jsc);
1285 : }
1286 : else
1287 : {
1288 : format = [prefix stringByAppendingString:format];
1289 : OOLogWithFunctionFileAndLineAndArguments(logMessageClass, function, fileName, line, format, args);
1290 : }
1291 : }
1292 :
1293 : va_end(args);
1294 : }
|