16
16
/// `@VARIABLE@` will be substituted with the value of the variable.
17
17
///
18
18
/// The preprocessor will return the preprocessed source code.
19
- func preprocess( source: String , options: PreprocessOptions ) throws -> String {
20
- let tokens = try Preprocessor . tokenize ( source: source)
21
- let parsed = try Preprocessor . parse ( tokens: tokens, source: source, options: options)
22
- return try Preprocessor . preprocess ( parsed: parsed, source: source, options: options)
19
+ func preprocess( source: String , file: String ? , options: PreprocessOptions ) throws -> String {
20
+ let preprocessor = Preprocessor ( source: source, file: file, options: options)
21
+ let tokens = try preprocessor. tokenize ( )
22
+ let parsed = try preprocessor. parse ( tokens: tokens)
23
+ return try preprocessor. preprocess ( parsed: parsed)
23
24
}
24
25
25
26
struct PreprocessOptions {
@@ -42,61 +43,117 @@ private struct Preprocessor {
42
43
let position : String . Index
43
44
}
44
45
45
- struct PreprocessorError : Error {
46
+ struct PreprocessorError : Error , CustomStringConvertible {
47
+ let file : String ?
46
48
let message : String
47
49
let source : String
48
50
let line : Int
49
51
let column : Int
50
52
51
- init ( message: String , source: String , line: Int , column: Int ) {
53
+ init ( file: String ? , message: String , source: String , line: Int , column: Int ) {
54
+ self . file = file
52
55
self . message = message
53
56
self . source = source
54
57
self . line = line
55
58
self . column = column
56
59
}
57
60
58
- init ( message: String , source: String , index: String . Index ) {
59
- func consumeLineColumn( from index: String . Index , in source: String ) -> ( Int , Int ) {
60
- var line = 1
61
- var column = 1
62
- for char in source [ ..< index] {
63
- if char == " \n " {
64
- line += 1
65
- column = 1
66
- } else {
67
- column += 1
68
- }
61
+ init ( file: String ? , message: String , source: String , index: String . Index ) {
62
+ let ( line, column) = Self . computeLineAndColumn ( from: index, in: source)
63
+ self . init ( file: file, message: message, source: source, line: line, column: column)
64
+ }
65
+
66
+ /// Get the 1-indexed line and column
67
+ private static func computeLineAndColumn( from index: String . Index , in source: String ) -> ( line: Int , column: Int ) {
68
+ var line = 1
69
+ var column = 1
70
+ for char in source [ ..< index] {
71
+ if char == " \n " {
72
+ line += 1
73
+ column = 1
74
+ } else {
75
+ column += 1
69
76
}
70
- return ( line, column)
71
77
}
72
- self . message = message
73
- self . source = source
74
- let ( line, column) = consumeLineColumn ( from: index, in: source)
75
- self . line = line
76
- self . column = column
78
+ return ( line, column)
77
79
}
78
80
79
- static func expected(
80
- _ expected: CustomStringConvertible , at index: String . Index , in source: String
81
- ) -> PreprocessorError {
82
- return PreprocessorError (
83
- message: " Expected \( expected) at \( index) " , source: source, index: index)
81
+ var description : String {
82
+ let lines = source. split ( separator: " \n " , omittingEmptySubsequences: false )
83
+ let lineIndex = line - 1
84
+ let lineNumberWidth = " \( line + 1 ) " . count
85
+
86
+ var description = " "
87
+ if let file = file {
88
+ description += " \( file) : "
89
+ }
90
+ description += " \( line) : \( column) : \( message) \n "
91
+
92
+ // Show context lines
93
+ if lineIndex > 0 {
94
+ description += formatLine ( number: line - 1 , content: lines [ lineIndex - 1 ] , width: lineNumberWidth)
95
+ }
96
+ description += formatLine ( number: line, content: lines [ lineIndex] , width: lineNumberWidth)
97
+ description += formatPointer ( column: column, width: lineNumberWidth)
98
+ if lineIndex + 1 < lines. count {
99
+ description += formatLine ( number: line + 1 , content: lines [ lineIndex + 1 ] , width: lineNumberWidth)
100
+ }
101
+
102
+ return description
84
103
}
85
104
86
- static func unexpected( token: Token , at index: String . Index , in source: String )
87
- -> PreprocessorError
88
- {
89
- return PreprocessorError (
90
- message: " Unexpected token \( token) at \( index) " , source: source, index: index)
105
+ private func formatLine( number: Int , content: String . SubSequence , width: Int ) -> String {
106
+ return " \( number) " . padding ( toLength: width, withPad: " " , startingAt: 0 ) + " | \( content) \n "
91
107
}
92
108
93
- static func eof ( at index : String . Index , in source : String ) -> PreprocessorError {
94
- return PreprocessorError (
95
- message : " Unexpected end of input " , source : source , index : index )
109
+ private func formatPointer ( column : Int , width : Int ) -> String {
110
+ let padding = String ( repeating : " " , count : width ) + " | " + String ( repeating : " " , count : column - 1 )
111
+ return padding + " ^ \n "
96
112
}
97
113
}
98
114
99
- static func tokenize( source: String ) throws -> [ TokenInfo ] {
115
+ let source : String
116
+ let file : String ?
117
+ let options : PreprocessOptions
118
+
119
+ init ( source: String , file: String ? , options: PreprocessOptions ) {
120
+ self . source = source
121
+ self . file = file
122
+ self . options = options
123
+ }
124
+
125
+ func unexpectedTokenError( expected: Token ? , token: Token , at index: String . Index ) -> PreprocessorError {
126
+ let message = expected. map { " Expected \( $0) but got \( token) " } ?? " Unexpected token \( token) "
127
+ return PreprocessorError (
128
+ file: file,
129
+ message: message, source: source, index: index)
130
+ }
131
+
132
+ func unexpectedCharacterError( expected: CustomStringConvertible , character: Character , at index: String . Index ) -> PreprocessorError {
133
+ return PreprocessorError (
134
+ file: file,
135
+ message: " Expected \( expected) but got \( character) " , source: source, index: index)
136
+ }
137
+
138
+ func unexpectedDirectiveError( at index: String . Index ) -> PreprocessorError {
139
+ return PreprocessorError (
140
+ file: file,
141
+ message: " Unexpected directive " , source: source, index: index)
142
+ }
143
+
144
+ func eofError( at index: String . Index ) -> PreprocessorError {
145
+ return PreprocessorError (
146
+ file: file,
147
+ message: " Unexpected end of input " , source: source, index: index)
148
+ }
149
+
150
+ func undefinedVariableError( name: String , at index: String . Index ) -> PreprocessorError {
151
+ return PreprocessorError (
152
+ file: file,
153
+ message: " Undefined variable \( name) " , source: source, index: index)
154
+ }
155
+
156
+ func tokenize( ) throws -> [ TokenInfo ] {
100
157
var cursor = source. startIndex
101
158
var tokens : [ TokenInfo ] = [ ]
102
159
@@ -121,7 +178,7 @@ private struct Preprocessor {
121
178
122
179
func expect( _ expected: Character ) throws {
123
180
guard try peek ( ) == expected else {
124
- throw PreprocessorError . expected ( expected, at : cursor , in : source )
181
+ throw unexpectedCharacterError ( expected: expected , character : try peek ( ) , at : cursor )
125
182
}
126
183
consume ( )
127
184
}
@@ -131,24 +188,24 @@ private struct Preprocessor {
131
188
let endIndex = source. index (
132
189
cursor, offsetBy: expected. count, limitedBy: source. endIndex)
133
190
else {
134
- throw PreprocessorError . eof ( at: cursor, in : source )
191
+ throw eofError ( at: cursor)
135
192
}
136
193
guard source [ cursor..< endIndex] == expected else {
137
- throw PreprocessorError . expected ( expected, at : cursor , in : source )
194
+ throw unexpectedCharacterError ( expected: expected , character : try peek ( ) , at : cursor )
138
195
}
139
196
consume ( expected. count)
140
197
}
141
198
142
199
func peek( ) throws -> Character {
143
200
guard cursor < source. endIndex else {
144
- throw PreprocessorError . eof ( at: cursor, in : source )
201
+ throw eofError ( at: cursor)
145
202
}
146
203
return source [ cursor]
147
204
}
148
205
149
206
func peek2( ) throws -> ( Character , Character ) {
150
207
guard cursor < source. endIndex, source. index ( after: cursor) < source. endIndex else {
151
- throw PreprocessorError . eof ( at: cursor, in : source )
208
+ throw eofError ( at: cursor)
152
209
}
153
210
let char1 = source [ cursor]
154
211
let char2 = source [ source. index ( after: cursor) ]
@@ -205,8 +262,7 @@ private struct Preprocessor {
205
262
try expect ( " */ " )
206
263
}
207
264
guard let token = token else {
208
- throw PreprocessorError (
209
- message: " Unexpected directive " , source: source, index: cursor)
265
+ throw unexpectedDirectiveError ( at: directiveStart)
210
266
}
211
267
addToken ( token, at: directiveStart)
212
268
bufferStart = cursor
@@ -221,9 +277,7 @@ private struct Preprocessor {
221
277
condition: String , then: [ ParseResult ] , else: [ ParseResult ] , position: String . Index )
222
278
}
223
279
224
- static func parse( tokens: [ TokenInfo ] , source: String , options: PreprocessOptions ) throws
225
- -> [ ParseResult ]
226
- {
280
+ func parse( tokens: [ TokenInfo ] ) throws -> [ ParseResult ] {
227
281
var cursor = tokens. startIndex
228
282
229
283
func consume( ) {
@@ -252,14 +306,14 @@ private struct Preprocessor {
252
306
}
253
307
}
254
308
guard case . endif = tokens [ cursor] . token else {
255
- throw PreprocessorError . unexpected (
256
- token: tokens [ cursor] . token, at: tokens [ cursor] . position, in : source )
309
+ throw unexpectedTokenError (
310
+ expected : . endif , token: tokens [ cursor] . token, at: tokens [ cursor] . position)
257
311
}
258
312
consume ( )
259
313
return . if( condition: condition, then: then, else: `else`, position: ifPosition)
260
314
case . else, . endif:
261
- throw PreprocessorError . unexpected (
262
- token: tokens [ cursor] . token, at: tokens [ cursor] . position, in : source )
315
+ throw unexpectedTokenError (
316
+ expected : nil , token: tokens [ cursor] . token, at: tokens [ cursor] . position)
263
317
}
264
318
}
265
319
var results : [ ParseResult ] = [ ]
@@ -269,9 +323,7 @@ private struct Preprocessor {
269
323
return results
270
324
}
271
325
272
- static func preprocess( parsed: [ ParseResult ] , source: String , options: PreprocessOptions ) throws
273
- -> String
274
- {
326
+ func preprocess( parsed: [ ParseResult ] ) throws -> String {
275
327
var result = " "
276
328
277
329
func appendBlock( content: String ) {
@@ -290,8 +342,7 @@ private struct Preprocessor {
290
342
appendBlock ( content: content)
291
343
case . if( let condition, let then, let `else`, let position) :
292
344
guard let condition = options. variables [ condition] else {
293
- throw PreprocessorError . unexpected (
294
- token: . if( condition: condition) , at: position, in: source)
345
+ throw undefinedVariableError ( name: condition, at: position)
295
346
}
296
347
let blocks = condition ? then : `else`
297
348
for block in blocks {
0 commit comments