@@ -242,9 +242,128 @@ extension ParseError: CustomStringConvertible {
242242 }
243243}
244244
245- // TODO: Fixits, notes, etc.
245+ /// A fatal error that indicates broken logic in the parser.
246+ enum FatalParseError : Hashable , Error {
247+ case unreachable( String )
248+ }
249+
250+ extension FatalParseError : CustomStringConvertible {
251+ var description : String {
252+ switch self {
253+ case . unreachable( let str) :
254+ return " UNREACHABLE: \( str) "
255+ }
256+ }
257+ }
258+
259+ // MARK: Diagnostic handling
260+
261+ /// A diagnostic to emit.
262+ public struct Diagnostic : Hashable {
263+ public let behavior : Behavior
264+ public let message : String
265+ public let location : SourceLocation
266+
267+ // TODO: Fixits, notes, etc.
268+
269+ // The underlying ParseError if applicable. This is used for testing.
270+ internal let underlyingParseError : ParseError ?
271+
272+ init ( _ behavior: Behavior , _ message: String , at loc: SourceLocation ,
273+ underlyingParseError: ParseError ? = nil ) {
274+ self . behavior = behavior
275+ self . message = message
276+ self . location = loc
277+ self . underlyingParseError = underlyingParseError
278+ }
279+
280+ public var isAnyError : Bool { behavior. isAnyError }
281+ }
282+
283+ extension Diagnostic {
284+ public enum Behavior : Hashable {
285+ case fatalError, error, warning
286+
287+ public var isAnyError : Bool {
288+ switch self {
289+ case . fatalError, . error:
290+ return true
291+ case . warning:
292+ return false
293+ }
294+ }
295+ }
296+ }
246297
247- // TODO: Diagnostics engine, recorder, logger, or similar.
298+ /// A collection of diagnostics to emit.
299+ public struct Diagnostics : Hashable {
300+ public private( set) var diags = [ Diagnostic] ( )
248301
302+ public init ( ) { }
303+ public init ( _ diags: [ Diagnostic ] ) {
304+ self . diags = diags
305+ }
249306
307+ /// Add a new diagnostic to emit.
308+ public mutating func append( _ diag: Diagnostic ) {
309+ diags. append ( diag)
310+ }
250311
312+ /// Add all the diagnostics of another diagnostic collection.
313+ public mutating func append( contentsOf other: Diagnostics ) {
314+ diags. append ( contentsOf: other. diags)
315+ }
316+
317+ /// Add all the new fatal error diagnostics of another diagnostic collection.
318+ /// This assumes that `other` was the same as `self`, but may have additional
319+ /// diagnostics added to it.
320+ public mutating func appendNewFatalErrors( from other: Diagnostics ) {
321+ let newDiags = other. diags. dropFirst ( diags. count)
322+ for diag in newDiags where diag. behavior == . fatalError {
323+ append ( diag)
324+ }
325+ }
326+
327+ /// Whether any error is present. This includes fatal errors.
328+ public var hasAnyError : Bool {
329+ diags. contains ( where: { $0. isAnyError } )
330+ }
331+
332+ /// Whether any fatal error is present.
333+ public var hasFatalError : Bool {
334+ diags. contains ( where: { $0. behavior == . fatalError } )
335+ }
336+
337+ /// If any error diagnostic has been added, throw it as an Error.
338+ func throwAnyError( ) throws {
339+ for diag in diags where diag. isAnyError {
340+ struct ErrorDiagnostic : Error , CustomStringConvertible {
341+ var diag : Diagnostic
342+ var description : String { diag. message }
343+ }
344+ throw ErrorDiagnostic ( diag: diag)
345+ }
346+ }
347+ }
348+
349+ // MARK: Diagnostic construction
350+
351+ extension Diagnostic {
352+ init ( _ err: ParseError , at loc: SourceLocation ) {
353+ self . init ( . error, " \( err) " , at: loc, underlyingParseError: err)
354+ }
355+
356+ init ( _ err: FatalParseError , at loc: SourceLocation ) {
357+ self . init ( . fatalError, " \( err) " , at: loc)
358+ }
359+ }
360+
361+ extension Diagnostics {
362+ mutating func error( _ err: ParseError , at loc: SourceLocation ) {
363+ append ( Diagnostic ( err, at: loc) )
364+ }
365+
366+ mutating func fatal( _ err: FatalParseError , at loc: SourceLocation ) {
367+ append ( Diagnostic ( err, at: loc) )
368+ }
369+ }
0 commit comments