forked from erkyrath/Inform7-IDE-Mac
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIFNaturalIntel.m
323 lines (245 loc) · 9.66 KB
/
IFNaturalIntel.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//
// IFNaturalIntel.m
// Inform
//
// Created by Andrew Hunter on 05/02/2005.
// Copyright 2005 Andrew Hunter. All rights reserved.
//
#import "IFNaturalIntel.h"
#import "IFPreferences.h"
static NSArray* headingList = nil;
// English number arrays
static NSArray* units;
static NSArray* tens;
static NSArray* majorUnits;
static BOOL indent = YES;
@implementation IFNaturalIntel
// = Hacky way to enable/disable indentation while undoing =
+ (void) disableIndentation {
indent = NO;
}
+ (void) enableIndentation {
indent = YES;
}
// = Useful parsing functions =
+ (int) parseNumber: (NSString*) number {
// IMPLEMENT ME: parse english numbers (one, two, three, etc)
return [number intValue];
}
+ (int) numberOfHeading: (NSString*) heading {
NSArray* words = [heading componentsSeparatedByString: @" "];
if ([words count] < 2) return 0;
return [IFNaturalIntel parseNumber: [words objectAtIndex: 1]];
}
// = Startup =
+ (void) initialize {
if (!headingList) {
headingList = [[NSArray arrayWithObjects: @"volume", @"book", @"part", @"chapter", @"section", nil] retain];
units = [[NSArray arrayWithObjects: @"zero", @"one", @"two", @"three", @"four", @"five", @"six", @"seven",
@"eight", @"nine", @"ten", @"eleven", @"twelve", @"thirteen", @"fourteen", @"fifteen", @"sixteen",
@"seventeen", @"eighteen", @"nineteen", nil] retain];
tens = [[NSArray arrayWithObjects: @"twenty", @"thirty", @"forty", @"fifty", @"sixty", @"seventy", @"eighty",
@"ninety", @"hundred", nil] retain];
majorUnits = [[NSArray arrayWithObjects: @"hundred", @"thousand", @"million", @"billion", @"trillion", nil] retain];
}
}
// = Notifying of the highlighter currently in use =
- (void) setSyntaxStorage: (IFSyntaxStorage*) storage {
highlighter = storage;
}
// = Gathering information (works like rehint) =
- (void) gatherIntelForLine: (NSString*) line
styles: (IFSyntaxStyle*) styles
initialState: (IFSyntaxState) state
lineNumber: (int) lineNumber
intoData: (IFIntelFile*) data {
// Clear out old data for this line
[data clearSymbolsForLines: NSMakeRange(lineNumber, 1)];
// Heading lines beginning with 'Volume', 'Part', etc are added to the intelligence
if ([line length] < 4) return; // Nothing to do in this case
if (styles[0] == IFSyntaxHeading) {
// Check if this is a heading or not
// MAYBE FIXME: won't deal well with headings starting with whitespace. Bug or not?
NSArray* words = [line componentsSeparatedByString: @" "];
if ([words count] < 1) return;
NSInteger headingType = [headingList indexOfObject: [[words objectAtIndex: 0] lowercaseString]] + 1;
if (headingType == NSNotFound) return; // Not a heading (hmm, shouldn't happen, I guess)
// Got a heading: add to the intel
IFIntelSymbol* newSymbol = [[IFIntelSymbol alloc] init];
[newSymbol setType: IFSectionSymbolType];
[newSymbol setName: line];
[newSymbol setRelation: IFSymbolOnLevel];
[newSymbol setLevelDelta: headingType];
[data addSymbol: newSymbol
atLine: lineNumber];
[newSymbol release];
} else if (lineNumber == 0) {
// The title string
int x = 0;
int start = 0;
while (x < [line length] && styles[x] != IFSyntaxTitle) x++;
start = x;
while (x < [line length] && styles[x] == IFSyntaxTitle) x++;
x--;
// Add this as a level 0 item
if (start <= x) {
IFIntelSymbol* newSymbol = [[IFIntelSymbol alloc] init];
[newSymbol setType: IFSectionSymbolType];
[newSymbol setName: [line substringWithRange: NSMakeRange(start, x-start)]];
[newSymbol setRelation: IFSymbolOnLevel];
[newSymbol setLevelDelta: 0];
[data addSymbol: newSymbol
atLine: lineNumber];
[newSymbol release];
}
}
}
// = Rewriting =
- (NSString*) rewriteInput: (NSString*) input {
// No rewriting if indentation is disabled
if (!indent) return input;
if ([input isEqualToString: @"\n"]) {
// Auto-tab
if (![[IFPreferences sharedPreferences] indentAfterNewline]) return nil;
// 'editingLineNumber' will still be the previous line
int lineNumber = [highlighter editingLineNumber];
int tabs = [highlighter numberOfTabStopsForLine: lineNumber];
// If we're not currently in a string...
IFSyntaxStyle lastStyle = [highlighter styleAtEndOfLine: lineNumber];
if (lastStyle != IFSyntaxGameText && lastStyle != IFSyntaxSubstitution) {
unichar lastChar = [highlighter characterAtEndOfLine: lineNumber];
if (lastChar == ':') {
// Increase tab depth if last character of last line was ':'
tabs++;
} else if (lastChar == '\t' || lastChar == ' ') {
// If line was entirely whitespace then reduce tabs back to 0
NSString* line = [highlighter textForLine: lineNumber];
int len = [line length];
int x;
BOOL whitespace = YES;
for (x=0; x<len-1; x++) {
// Loop to len-1 as last character will always be '\n'
// Exception is the very last line in the file. But we're OK there, as we know the last
// character is whitespace anyway
unichar chr = [line characterAtIndex: x];
if (chr != '\t' && chr != ' ') {
whitespace = NO;
break;
}
}
if (whitespace) {
// Line was entirely whitespace: no tabs now
tabs = 0;
}
}
}
if (tabs > 0) {
// Auto-indent the next line
NSMutableString* res = [NSMutableString stringWithString: @"\n"];
int x;
for (x=0; x<tabs; x++) {
[res appendString: @"\t"];
}
return res;
} else {
// Leave as-is
return nil;
}
} else if ([input isEqualToString: @" "]) {
if (![[IFPreferences sharedPreferences] autoNumberSections]) return nil;
int lineNumber = [highlighter editingLineNumber];
IFSyntaxStyle lastStyle = [highlighter styleAtStartOfLine: lineNumber];
if (lastStyle != IFSyntaxGameText && lastStyle != IFSyntaxSubstitution) {
// If we've got a line 'Volume\n', or (pedantic last line case) 'Volume', then automagically fill
// in the section number using context info
NSString* line = [highlighter textForLine: lineNumber];
NSString* prefix = nil;
// Line must actually have something on it
if ([line length] < 4) return nil; // Too short to be of interest
if ([line length] > 8) return nil; // Too long to be of interest
// See if we're at the last line or somewhere else
if ([line characterAtIndex: [line length]-1] == '\n') {
prefix = [line substringToIndex: [line length]-1];
} else {
prefix = line;
}
// See if this is the start of a heading
NSInteger headingLevel = [headingList indexOfObject: [prefix lowercaseString]];
if (headingLevel == NSNotFound) return nil; // Not a heading
headingLevel++;
// We've got a heading: auto-insert a number
// Find the preceding heading
IFIntelFile* data = [highlighter intelligenceData];
IFIntelSymbol* symbol = [data nearestSymbolToLine: lineNumber];
while (symbol && [symbol level] > headingLevel) symbol = [symbol parent];
// Work out the numeric value of the heading
int lastHeadingNumber = 0;
if (symbol) {
if ([symbol level] != headingLevel) {
lastHeadingNumber = 0; // No preceding items at this level
} else {
lastHeadingNumber = [IFNaturalIntel numberOfHeading: [symbol name]];
if (lastHeadingNumber == 0) {
lastHeadingNumber = -1; // There was a preceding heading, but we don't know the number
}
}
}
// Work out the result
NSMutableString* res = nil;
if (lastHeadingNumber >= 0) {
// Insert a suitable new heading number
res = [NSMutableString stringWithFormat: @" %i - ", lastHeadingNumber+1];
[highlighter callbackForEditing: @selector(renumberSectionsAfterLine:)
withValue: [NSNumber numberWithInt: lineNumber]];
}
return res;
}
}
// No behaviour defined: just fall through
return nil;
}
- (void) renumberSectionsAfterLine: (NSNumber*) lineObject {
// Gather some information about what we're about to do
int lineNumber = [lineObject intValue];
IFIntelFile* data = [highlighter intelligenceData];
IFIntelSymbol* firstSymbol = [data nearestSymbolToLine: lineNumber];
if (firstSymbol == nil) return;
int currentSectionNumber = [IFNaturalIntel numberOfHeading: [firstSymbol name]];
if (currentSectionNumber <= 0) return;
if ([firstSymbol level] == 0) return;
// Renumber all the siblings
IFIntelSymbol* symbol = [firstSymbol sibling];
NSMutableArray* todoList = [NSMutableArray array];
while (symbol != nil) {
currentSectionNumber++;
int symbolSectionNumber = [IFNaturalIntel numberOfHeading: [firstSymbol name]];
if (symbolSectionNumber != currentSectionNumber) {
// Get the data for the line this symbol is on
int symbolLineNumber = [data lineForSymbol: symbol];
NSString* line = [highlighter textForLine: symbolLineNumber];
// Renumber this symbol
NSMutableArray* words = [[line componentsSeparatedByString: @" "] mutableCopy];
if ([words count] > 1) {
[words replaceObjectAtIndex: 1
withObject: [NSString stringWithFormat: @"%i", currentSectionNumber]];
NSString* newString = [words componentsJoinedByString: @" "];
[words release];
// Add to our 'todo' list
[todoList addObject: [NSArray arrayWithObjects: [NSNumber numberWithInt: symbolLineNumber], newString, nil]];
}
}
symbol = [symbol sibling];
}
// Renumber everything in the todo list
// (We put things in a todo list to avoid accidently stuffing up the symbol list while we're working on it)
[highlighter beginEditing];
NSEnumerator* todoEnum = [todoList objectEnumerator];
NSArray* todo;
while (todo = [todoEnum nextObject]) {
[highlighter replaceLine: [[todo objectAtIndex: 0] intValue]
withLine: [todo objectAtIndex: 1]];
}
[highlighter endEditing];
// We're done
}
@end