From 1ec7f2c6deb64f5b13b32fae2bf74ae2d8aaaabb Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Wed, 9 Jul 2025 08:34:19 +0200 Subject: Account for CRLF/CR/LF. Lex note text. Use Wmap Parser for renderer. --- Map/Business/WmapParser/Entity.swift | 73 +++++++++++++++++++++++----- Map/Business/WmapParser/Lexer.swift | 93 ++++++++++++++++++++++++++++-------- Map/Business/WmapParser/Parser.swift | 87 +++++++++++++++++---------------- 3 files changed, 181 insertions(+), 72 deletions(-) (limited to 'Map/Business') diff --git a/Map/Business/WmapParser/Entity.swift b/Map/Business/WmapParser/Entity.swift index 40dd7ca..52f6e93 100644 --- a/Map/Business/WmapParser/Entity.swift +++ b/Map/Business/WmapParser/Entity.swift @@ -1,11 +1,51 @@ struct Wmap { - protocol Entity { - var position: SourcePosition { get } - var range: SourceRange { get } - var syntaxElements: [SyntaxElement] { get } + enum Entity: Equatable { + case vertex(Vertex) + case edge(Edge) + case note(Note) + case stage(Stage) + case group(Group) + case inertia(Inertia) + case evolution(Evolution) + + var position: SourcePosition { + switch self { + case .vertex(let vertex): return vertex.position + case .edge(let edge): return edge.position + case .note(let note): return note.position + case .stage(let stage): return stage.position + case .group(let group): return group.position + case .inertia(let inertia): return inertia.position + case .evolution(let evolution): return evolution.position + } + } + + var range: SourceRange { + switch self { + case .vertex(let vertex): return vertex.range + case .edge(let edge): return edge.range + case .note(let note): return note.range + case .stage(let stage): return stage.range + case .group(let group): return group.range + case .inertia(let inertia): return inertia.range + case .evolution(let evolution): return evolution.range + } + } + + var syntaxElements: [SyntaxElement] { + switch self { + case .vertex(let vertex): return vertex.syntaxElements + case .edge(let edge): return edge.syntaxElements + case .note(let note): return note.syntaxElements + case .stage(let stage): return stage.syntaxElements + case .group(let group): return group.syntaxElements + case .inertia(let inertia): return inertia.syntaxElements + case .evolution(let evolution): return evolution.syntaxElements + } + } } - struct Vertex: Entity { + struct Vertex: Equatable { let label: String let position: SourcePosition let coordinates: (Double, Double) @@ -21,6 +61,12 @@ struct Wmap { let rightParenRange: SourceRange let shapeRange: SourceRange? + static func == (lhs: Vertex, rhs: Vertex) -> Bool { + return lhs.label == rhs.label && lhs.position == rhs.position + && lhs.coordinates.0 == rhs.coordinates.0 && lhs.coordinates.1 == rhs.coordinates.1 + && lhs.shape == rhs.shape && lhs.range == rhs.range + } + var syntaxElements: [SyntaxElement] { var elements: [SyntaxElement] = [] @@ -86,7 +132,7 @@ struct Wmap { } } - struct Edge: Entity { + struct Edge: Equatable { let from: String let to: String let isDirected: Bool @@ -129,7 +175,7 @@ struct Wmap { } } - struct Note: Entity { + struct Note: Equatable { let coordinates: (Double, Double) let text: String let position: SourcePosition @@ -144,6 +190,11 @@ struct Wmap { let rightParenRange: SourceRange let textRange: SourceRange + static func == (lhs: Note, rhs: Note) -> Bool { + return lhs.coordinates.0 == rhs.coordinates.0 && lhs.coordinates.1 == rhs.coordinates.1 + && lhs.text == rhs.text && lhs.position == rhs.position && lhs.range == rhs.range + } + var syntaxElements: [SyntaxElement] { var elements: [SyntaxElement] = [] @@ -207,7 +258,7 @@ struct Wmap { } } - struct Stage: Entity { + struct Stage: Equatable { let number: String // i, ii, iii, iv let value: Double let position: SourcePosition @@ -258,7 +309,7 @@ struct Wmap { } } - struct Group: Entity { + struct Group: Equatable { let vertices: [String] let position: SourcePosition let range: SourceRange @@ -303,7 +354,7 @@ struct Wmap { } } - struct Inertia: Entity { + struct Inertia: Equatable { let vertex: String let position: SourcePosition let range: SourceRange @@ -335,7 +386,7 @@ struct Wmap { } } - struct Evolution: Entity { + struct Evolution: Equatable { let vertex: String let isPositive: Bool let value: Double 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.. 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.. 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.. 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.. 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() } } diff --git a/Map/Business/WmapParser/Parser.swift b/Map/Business/WmapParser/Parser.swift index f04998c..9418263 100644 --- a/Map/Business/WmapParser/Parser.swift +++ b/Map/Business/WmapParser/Parser.swift @@ -56,15 +56,15 @@ extension Wmap { case .vertexLabel: return try parseVertexOrEdge() case .noteKeyword: - return try parseNote() + return .note(try parseNote()) case .stageNumber: - return try parseStage() + return .stage(try parseStage()) case .groupKeyword: - return try parseGroup() + return .group(try parseGroup()) case .inertiaKeyword: - return try parseInertia() + return .inertia(try parseInertia()) case .evolutionKeyword: - return try parseEvolution() + return .evolution(try parseEvolution()) case .error: // Error tokens should cause the line to be ignored throw ParseError.invalidLine @@ -97,19 +97,20 @@ extension Wmap { vertexLabelMap[secondLabel.lowercased()] = secondLabel advance() - return Edge( - from: firstLabel, - to: secondLabel, - isDirected: isDirected, - position: startPos, - range: SourceRange( - start: startPos, end: endPos, - stringRange: firstLabelRange.stringRange - .lowerBound..