From 6926feead1a7fadb7f5f974996bef8c30b3bfdbe Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Wed, 9 Jul 2025 01:55:50 +0200 Subject: Use AST based text editor --- Map/Business/WmapParser/Lexer.swift | 344 ++++++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) create mode 100644 Map/Business/WmapParser/Lexer.swift (limited to 'Map/Business/WmapParser/Lexer.swift') diff --git a/Map/Business/WmapParser/Lexer.swift b/Map/Business/WmapParser/Lexer.swift new file mode 100644 index 0000000..dc789bb --- /dev/null +++ b/Map/Business/WmapParser/Lexer.swift @@ -0,0 +1,344 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +extension Wmap { + class Lexer { + let input: String + private var current: String.Index + private var line = 1 + private var column = 1 + + init(_ input: String) { + self.input = input + self.current = input.startIndex + } + + func nextToken() -> Token { + skipWhitespace() + + guard current < input.endIndex else { + return Token( + type: .eof, + value: "", + position: currentPosition(), + stringRange: current.." { + advance() + advance() + return Token( + type: .edgeDirected, + value: "->", + position: pos, + stringRange: tokenStart.. Token { + let pos = currentPosition() + let tokenStart = current + + while current < input.endIndex && input[current] != "\n" { + advance() + } + + let originalValue = String(input[tokenStart.. Token { + let pos = currentPosition() + let tokenStart = current + advance() // consume '[' + + let contentStart = current + while current < input.endIndex && input[current] != "]" && input[current] != "\n" { + advance() + } + + // If we hit a newline 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 Token( + type: .error, + value: String(input[tokenStart.. Token { + let pos = currentPosition() + let tokenStart = current + + // Handle leading decimal point + if input[current] == "." { + advance() + } + + while current < input.endIndex && input[current].isNumber { + advance() + } + + // Handle decimal point if we haven't seen one + if current < input.endIndex && input[current] == "." && input[tokenStart] != "." { + advance() + while current < input.endIndex && input[current].isNumber { + advance() + } + } + + let value = String(input[tokenStart.. Token { + let pos = currentPosition() + let tokenStart = current + + // Check if it's a stage number (i, ii, iii, iv) + var tempIndex = current + var iCount = 0 + + while tempIndex < input.endIndex && input[tempIndex].lowercased() == "i" && iCount < 4 { + tempIndex = input.index(after: tempIndex) + iCount += 1 + } + + // If we have 1-4 i's and next char is whitespace/delimiter, it's a stage + if iCount > 0 && iCount <= 4 && (tempIndex >= input.endIndex || isDelimiter(input[tempIndex])) + { + current = tempIndex + return Token( + type: .stageNumber, + value: String(repeating: "i", count: iCount), + position: pos, + stringRange: tokenStart.. Token { + let pos = currentPosition() + let tokenStart = current + + while current < input.endIndex && !isVertexLabelDelimiter(input[current]) { + advance() + } + + let originalValue = String(input[tokenStart.. Bool { + return char == "-" || char == "+" || char == "," || char == "[" || char == "]" || char == "(" + || char == ")" || char == "\n" + } + + private func isDelimiter(_ char: Character) -> Bool { + return char.isWhitespace || char == "\n" || char == "(" || char == ")" || char == "[" + || char == "]" || char == "," + } + + private func skipWhitespace() { + while current < input.endIndex && input[current].isWhitespace && input[current] != "\n" { + advance() + } + } + + private func advance() { + if current < input.endIndex { + current = input.index(after: current) + column += 1 + } + } + + private func peek() -> Character? { + let next = input.index(after: current) + return next < input.endIndex ? input[next] : nil + } + + private func currentPosition() -> SourcePosition { + return SourcePosition(line: line, column: column, stringIndex: current) + } + } +} -- cgit