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 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
// MARK: - Parse errors
enum ParseError: Error, Hashable {
// TODO: I wonder if it makes sense to store the string.
// This can make equality weird.
// MARK: Syntactic Errors
case numberOverflow(String)
case expectedNumDigits(String, Int)
case expectedNumber(String, kind: RadixKind)
// Expected the given character or string
case expected(String)
// Expected something, anything really
case unexpectedEndOfInput
// Something happened, fall-back for now
case misc(String)
case tooManyBranchesInConditional(Int)
case unsupportedCondition(String)
case tooManyAbsentExpressionChildren(Int)
case globalMatchingOptionNotAtStart(String)
case expectedASCII(Character)
case expectedNonEmptyContents
case expectedEscape
case invalidEscape(Character)
case confusableCharacter(Character)
case quoteMayNotSpanMultipleLines
case unsetExtendedSyntaxMayNotSpanMultipleLines
case cannotReferToWholePattern
case quantifierRequiresOperand(String)
case backtrackingDirectiveMustHaveName(String)
case unknownGroupKind(String)
case unknownCalloutKind(String)
case unknownTextSegmentMatchingOption(Character)
case invalidMatchingOption(Character)
case cannotRemoveMatchingOptionsAfterCaret
case expectedCustomCharacterClassMembers
case emptyProperty
case unknownProperty(key: String?, value: String)
case unrecognizedScript(String)
case unrecognizedCategory(String)
case unrecognizedBlock(String)
case invalidAge(String)
case invalidNumericValue(String)
case unrecognizedNumericType(String)
case invalidCCC(String)
case expectedGroupSpecifier
case unbalancedEndOfGroup
// Identifier diagnostics.
case expectedIdentifier(IdentifierKind)
case identifierMustBeAlphaNumeric(IdentifierKind)
case identifierCannotStartWithNumber(IdentifierKind)
case cannotRemoveTextSegmentOptions
case cannotRemoveSemanticsOptions
case cannotRemoveExtendedSyntaxInMultilineMode
case cannotResetExtendedSyntaxInMultilineMode
case expectedCalloutArgument
// Excessively nested groups (i.e. recursion)
case nestingTooDeep
// MARK: Semantic Errors
case unsupported(String)
case deprecatedUnicode(String)
case invalidReference(Int)
case invalidNamedReference(String)
case duplicateNamedCapture(String)
case invalidCharacterClassRangeOperand
case unsupportedDotNetSubtraction
case invalidQuantifierRange(Int, Int)
case invalidCharacterRange(from: Character, to: Character)
case notQuantifiable
}
extension IdentifierKind {
fileprivate var diagDescription: String {
switch self {
case .groupName: return "group name"
case .onigurumaCalloutName: return "callout name"
case .onigurumaCalloutTag: return "callout tag"
}
}
}
extension ParseError: CustomStringConvertible {
var description: String {
switch self {
// MARK: Syntactic Errors
case let .numberOverflow(s):
return "number overflow: \(s)"
case let .expectedNumDigits(s, i):
return "expected \(i) digits in '\(s)'"
case let .expectedNumber(s, kind: kind):
let number: String
switch kind {
case .octal:
number = "octal number"
case .decimal:
number = "number"
case .hex:
number = "hexadecimal number"
}
let suffix = s.isEmpty ? "" : " in '\(s)'"
return "expected \(number)\(suffix)"
case let .expected(s):
return "expected '\(s)'"
case .unexpectedEndOfInput:
return "unexpected end of input"
case let .misc(s):
return s
case .expectedNonEmptyContents:
return "expected non-empty contents"
case .expectedEscape:
return "expected escape sequence"
case .invalidEscape(let c):
return "invalid escape sequence '\\\(c)'"
case .confusableCharacter(let c):
return "'\(c)' is confusable for a metacharacter; use '\\u{...}' instead"
case .quoteMayNotSpanMultipleLines:
return "quoted sequence may not span multiple lines in multi-line literal"
case .unsetExtendedSyntaxMayNotSpanMultipleLines:
return "group that unsets extended syntax may not span multiple lines in multi-line literal"
case .cannotReferToWholePattern:
return "cannot refer to whole pattern here"
case .quantifierRequiresOperand(let q):
return "quantifier '\(q)' must appear after expression"
case .backtrackingDirectiveMustHaveName(let b):
return "backtracking directive '\(b)' must include name"
case let .tooManyBranchesInConditional(i):
return "expected 2 branches in conditional, have \(i)"
case let .unsupportedCondition(str):
return "\(str) cannot be used as condition"
case let .tooManyAbsentExpressionChildren(i):
return "expected 2 expressions in absent expression, have \(i)"
case let .globalMatchingOptionNotAtStart(opt):
return "matching option '\(opt)' may only appear at the start of the regex"
case let .unknownGroupKind(str):
return "unknown group kind '(\(str)'"
case let .unknownCalloutKind(str):
return "unknown callout kind '\(str)'"
case let .unknownTextSegmentMatchingOption(m):
return "unknown text segment mode '\(m)'; expected 'w' or 'g'"
case let .invalidMatchingOption(c):
return "invalid matching option '\(c)'"
case .cannotRemoveMatchingOptionsAfterCaret:
return "cannot remove matching options with '^' specifier"
case let .expectedASCII(c):
return "expected ASCII for '\(c)'"
case .expectedCustomCharacterClassMembers:
return "expected custom character class members"
case .invalidCharacterClassRangeOperand:
return "invalid bound for character class range"
case .unsupportedDotNetSubtraction:
return "subtraction with '-' is unsupported; use '--' instead"
case .emptyProperty:
return "expected property name"
case .unknownProperty(let key, let value):
if let key = key {
return "unknown character property '\(key)=\(value)'"
}
return "unknown character property '\(value)'"
case .expectedGroupSpecifier:
return "expected group specifier"
case .unbalancedEndOfGroup:
return "closing ')' does not balance any groups openings"
case .expectedIdentifier(let i):
return "expected \(i.diagDescription)"
case .identifierMustBeAlphaNumeric(let i):
return "\(i.diagDescription) must only contain alphanumeric characters"
case .identifierCannotStartWithNumber(let i):
return "\(i.diagDescription) must not start with number"
case .cannotRemoveTextSegmentOptions:
return "text segment mode cannot be unset, only changed"
case .cannotRemoveSemanticsOptions:
return "semantic level cannot be unset, only changed"
case .cannotRemoveExtendedSyntaxInMultilineMode:
return "extended syntax may not be disabled in multi-line mode"
case .cannotResetExtendedSyntaxInMultilineMode:
return "extended syntax may not be disabled in multi-line mode; use '(?^x)' instead"
case .expectedCalloutArgument:
return "expected argument to callout"
case .unrecognizedScript(let value):
return "unrecognized script '\(value)'"
case .unrecognizedCategory(let value):
return "unrecognized category '\(value)'"
case .unrecognizedBlock(let value):
return "unrecognized block '\(value)'"
case .unrecognizedNumericType(let value):
return "unrecognized numeric type '\(value)'"
case .invalidAge(let value):
return "invalid age format for '\(value)' - use '3.0' or 'V3_0' formats"
case .invalidNumericValue(let value):
return "invalid numeric value '\(value)'"
case .invalidCCC(let value):
return "invalid canonical combining class '\(value)'"
// MARK: Semantic Errors
case let .unsupported(kind):
return "\(kind) is not currently supported"
case let .deprecatedUnicode(kind):
return "\(kind) is a deprecated Unicode property, and is not supported"
case let .invalidReference(i):
return "no capture numbered \(i)"
case let .invalidNamedReference(name):
return "no capture named '\(name)'"
case let .duplicateNamedCapture(str):
return "group named '\(str)' already exists"
case let .invalidQuantifierRange(lhs, rhs):
return "range lower bound '\(lhs)' must be less than or equal to upper bound '\(rhs)'"
case let .invalidCharacterRange(from: lhs, to: rhs):
return "character '\(lhs)' must compare less than or equal to '\(rhs)'"
case .notQuantifiable:
return "expression is not quantifiable"
case .nestingTooDeep:
return "group is too deeply nested"
}
}
}
/// A fatal error that indicates broken logic in the parser.
enum FatalParseError: Hashable, Error {
case unreachable(String)
}
extension FatalParseError: CustomStringConvertible {
var description: String {
switch self {
case .unreachable(let str):
return "UNREACHABLE: \(str)"
}
}
}
// MARK: Diagnostic handling
/// A diagnostic to emit.
public struct Diagnostic: Hashable {
public let behavior: Behavior
public let message: String
public let location: SourceLocation
// TODO: Fixits, notes, etc.
// The underlying ParseError if applicable. This is used for testing.
internal let underlyingParseError: ParseError?
init(_ behavior: Behavior, _ message: String, at loc: SourceLocation,
underlyingParseError: ParseError? = nil) {
self.behavior = behavior
self.message = message
self.location = loc
self.underlyingParseError = underlyingParseError
}
public var isAnyError: Bool { behavior.isAnyError }
}
extension Diagnostic {
public enum Behavior: Hashable {
case fatalError, error, warning
public var isAnyError: Bool {
switch self {
case .fatalError, .error:
return true
case .warning:
return false
}
}
}
}
/// A collection of diagnostics to emit.
public struct Diagnostics: Hashable {
public private(set) var diags = [Diagnostic]()
// In the event of an unrecoverable parse error, set this
// to avoid emitting spurious diagnostics.
internal var suppressFurtherDiagnostics = false
public init() {}
public init(_ diags: [Diagnostic]) {
self.diags = diags
}
/// Add a new diagnostic to emit.
public mutating func append(_ diag: Diagnostic) {
guard !suppressFurtherDiagnostics else {
return
}
diags.append(diag)
}
/// Add all the diagnostics of another diagnostic collection.
public mutating func append(contentsOf other: Diagnostics) {
guard !suppressFurtherDiagnostics else {
return
}
diags.append(contentsOf: other.diags)
}
/// Add all the new fatal error diagnostics of another diagnostic collection.
/// This assumes that `other` was the same as `self`, but may have additional
/// diagnostics added to it.
public mutating func appendNewFatalErrors(from other: Diagnostics) {
guard !suppressFurtherDiagnostics else {
return
}
let newDiags = other.diags.dropFirst(diags.count)
for diag in newDiags where diag.behavior == .fatalError {
append(diag)
}
}
/// Whether any error is present. This includes fatal errors.
public var hasAnyError: Bool {
diags.contains(where: { $0.isAnyError })
}
/// Whether any fatal error is present.
public var hasFatalError: Bool {
diags.contains(where: { $0.behavior == .fatalError })
}
/// If any error diagnostic has been added, throw it as an Error.
func throwAnyError() throws {
for diag in diags where diag.isAnyError {
struct ErrorDiagnostic: Error, CustomStringConvertible {
var diag: Diagnostic
var description: String { diag.message }
}
throw Source.LocatedError(ErrorDiagnostic(diag: diag), diag.location)
}
}
}
// MARK: Diagnostic construction
extension Diagnostic {
init(_ err: ParseError, at loc: SourceLocation) {
self.init(.error, "\(err)", at: loc, underlyingParseError: err)
}
init(_ err: FatalParseError, at loc: SourceLocation) {
self.init(.fatalError, "\(err)", at: loc)
}
}
extension Diagnostics {
mutating func error(_ err: ParseError, at loc: SourceLocation) {
append(Diagnostic(err, at: loc))
}
mutating func fatal(_ err: FatalParseError, at loc: SourceLocation) {
append(Diagnostic(err, at: loc))
}
}
|