1616/// `@VARIABLE@` will be substituted with the value of the variable.
1717///
1818/// 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)
2324}
2425
2526struct PreprocessOptions {
@@ -42,61 +43,117 @@ private struct Preprocessor {
4243 let position : String . Index
4344 }
4445
45- struct PreprocessorError : Error {
46+ struct PreprocessorError : Error , CustomStringConvertible {
47+ let file : String ?
4648 let message : String
4749 let source : String
4850 let line : Int
4951 let column : Int
5052
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
5255 self . message = message
5356 self . source = source
5457 self . line = line
5558 self . column = column
5659 }
5760
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
6976 }
70- return ( line, column)
7177 }
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)
7779 }
7880
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
84103 }
85104
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 "
91107 }
92108
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 "
96112 }
97113 }
98114
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 ] {
100157 var cursor = source. startIndex
101158 var tokens : [ TokenInfo ] = [ ]
102159
@@ -121,7 +178,7 @@ private struct Preprocessor {
121178
122179 func expect( _ expected: Character ) throws {
123180 guard try peek ( ) == expected else {
124- throw PreprocessorError . expected ( expected, at : cursor , in : source )
181+ throw unexpectedCharacterError ( expected: expected , character : try peek ( ) , at : cursor )
125182 }
126183 consume ( )
127184 }
@@ -131,24 +188,24 @@ private struct Preprocessor {
131188 let endIndex = source. index (
132189 cursor, offsetBy: expected. count, limitedBy: source. endIndex)
133190 else {
134- throw PreprocessorError . eof ( at: cursor, in : source )
191+ throw eofError ( at: cursor)
135192 }
136193 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 )
138195 }
139196 consume ( expected. count)
140197 }
141198
142199 func peek( ) throws -> Character {
143200 guard cursor < source. endIndex else {
144- throw PreprocessorError . eof ( at: cursor, in : source )
201+ throw eofError ( at: cursor)
145202 }
146203 return source [ cursor]
147204 }
148205
149206 func peek2( ) throws -> ( Character , Character ) {
150207 guard cursor < source. endIndex, source. index ( after: cursor) < source. endIndex else {
151- throw PreprocessorError . eof ( at: cursor, in : source )
208+ throw eofError ( at: cursor)
152209 }
153210 let char1 = source [ cursor]
154211 let char2 = source [ source. index ( after: cursor) ]
@@ -205,8 +262,7 @@ private struct Preprocessor {
205262 try expect ( " */ " )
206263 }
207264 guard let token = token else {
208- throw PreprocessorError (
209- message: " Unexpected directive " , source: source, index: cursor)
265+ throw unexpectedDirectiveError ( at: directiveStart)
210266 }
211267 addToken ( token, at: directiveStart)
212268 bufferStart = cursor
@@ -221,9 +277,7 @@ private struct Preprocessor {
221277 condition: String , then: [ ParseResult ] , else: [ ParseResult ] , position: String . Index )
222278 }
223279
224- static func parse( tokens: [ TokenInfo ] , source: String , options: PreprocessOptions ) throws
225- -> [ ParseResult ]
226- {
280+ func parse( tokens: [ TokenInfo ] ) throws -> [ ParseResult ] {
227281 var cursor = tokens. startIndex
228282
229283 func consume( ) {
@@ -252,14 +306,14 @@ private struct Preprocessor {
252306 }
253307 }
254308 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)
257311 }
258312 consume ( )
259313 return . if( condition: condition, then: then, else: `else`, position: ifPosition)
260314 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)
263317 }
264318 }
265319 var results : [ ParseResult ] = [ ]
@@ -269,9 +323,7 @@ private struct Preprocessor {
269323 return results
270324 }
271325
272- static func preprocess( parsed: [ ParseResult ] , source: String , options: PreprocessOptions ) throws
273- -> String
274- {
326+ func preprocess( parsed: [ ParseResult ] ) throws -> String {
275327 var result = " "
276328
277329 func appendBlock( content: String ) {
@@ -290,8 +342,7 @@ private struct Preprocessor {
290342 appendBlock ( content: content)
291343 case . if( let condition, let then, let `else`, let position) :
292344 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)
295346 }
296347 let blocks = condition ? then : `else`
297348 for block in blocks {
0 commit comments