diff options
Diffstat (limited to 'Map/Business/WmapParser/Lexer.swift')
| -rw-r--r-- | Map/Business/WmapParser/Lexer.swift | 93 |
1 files changed, 74 insertions, 19 deletions
diff --git a/Map/Business/WmapParser/Lexer.swift b/Map/Business/WmapParser/Lexer.swift index dc789bb..c79879f 100644 --- a/Map/Business/WmapParser/Lexer.swift +++ b/Map/Business/WmapParser/Lexer.swift @@ -18,6 +18,7 @@ extension Wmap { private var current: String.Index private var line = 1 private var column = 1 + private var expectingText = false init(_ input: String) { self.input = input @@ -41,17 +42,9 @@ extension Wmap { let tokenStart = current switch char { - case "\n": - advance() - let token = Token( - type: .newline, - value: "\n", - position: pos, - stringRange: tokenStart..<current - ) - line += 1 - column = 1 - return token + case "\n", "\r": + expectingText = false // Reset expectingText on newline + return scanLineEnding() case "(": advance() @@ -128,6 +121,12 @@ extension Wmap { return scanStageOrLabel() default: + // If we're expecting text, scan as text instead of vertex label + if expectingText { + expectingText = false // Reset after scanning text + return scanText() + } + // If we encounter an unexpected character, try to scan it as a vertex label // This provides more graceful degradation than failing immediately return scanVertexLabel() @@ -136,13 +135,17 @@ extension Wmap { func scanText() -> Token { let pos = currentPosition() + + // Skip any leading whitespace + skipWhitespace() + let tokenStart = current - while current < input.endIndex && input[current] != "\n" { + while current < input.endIndex && !isLineEnding(input[current]) { advance() } - let originalValue = String(input[tokenStart..<current]) + let originalValue = String(input[tokenStart..<current]).trimmingCharacters(in: .whitespaces) return Token( type: .text, value: originalValue, @@ -151,19 +154,69 @@ extension Wmap { ) } + private func scanLineEnding() -> Token { + let pos = currentPosition() + let tokenStart = current + + if input[current] == "\r" { + advance() + // Check for CRLF + if current < input.endIndex && input[current] == "\n" { + advance() + let token = Token( + type: .newline, + value: "\r\n", + position: pos, + stringRange: tokenStart..<current + ) + line += 1 + column = 1 + return token + } else { + // Just CR + let token = Token( + type: .newline, + value: "\r", + position: pos, + stringRange: tokenStart..<current + ) + line += 1 + column = 1 + return token + } + } else if input[current] == "\n" { + advance() + let token = Token( + type: .newline, + value: "\n", + position: pos, + stringRange: tokenStart..<current + ) + line += 1 + column = 1 + return token + } else { + fatalError("scanLineEnding called on non-line-ending character") + } + } + + private func isLineEnding(_ char: Character) -> Bool { + return char == "\n" || char == "\r" + } + private func scanBracketedToken() -> Token { let pos = currentPosition() let tokenStart = current advance() // consume '[' let contentStart = current - while current < input.endIndex && input[current] != "]" && input[current] != "\n" { + while current < input.endIndex && input[current] != "]" && !isLineEnding(input[current]) { advance() } - // If we hit a newline or EOF before finding the closing bracket, it's an error + // If we hit a line ending or EOF before finding the closing bracket, it's an error guard current < input.endIndex && input[current] == "]" else { - // Return error token for unclosed bracket (don't consume past newline) + // Return error token for unclosed bracket (don't consume past line ending) return Token( type: .error, value: String(input[tokenStart..<current]), @@ -180,6 +233,7 @@ extension Wmap { switch lowercaseContent { case "note": + expectingText = true // Set expectingText when we return a note keyword return Token( type: .noteKeyword, value: "[Note]", @@ -311,16 +365,17 @@ extension Wmap { private func isVertexLabelDelimiter(_ char: Character) -> Bool { return char == "-" || char == "+" || char == "," || char == "[" || char == "]" || char == "(" - || char == ")" || char == "\n" + || char == ")" || isLineEnding(char) } private func isDelimiter(_ char: Character) -> Bool { - return char.isWhitespace || char == "\n" || char == "(" || char == ")" || char == "[" + return char.isWhitespace || isLineEnding(char) || char == "(" || char == ")" || char == "[" || char == "]" || char == "," } private func skipWhitespace() { - while current < input.endIndex && input[current].isWhitespace && input[current] != "\n" { + while current < input.endIndex && input[current].isWhitespace && !isLineEnding(input[current]) + { advance() } } |