LCOV - code coverage report
Current view: top level - Core - OOStringExpander.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 63 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.14