aboutsummaryrefslogtreecommitdiff
path: root/Map/Business/WmapParser/Lexer.swift
diff options
context:
space:
mode:
Diffstat (limited to 'Map/Business/WmapParser/Lexer.swift')
-rw-r--r--Map/Business/WmapParser/Lexer.swift93
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()
}
}