this repo has no description
www.jonmsterling.com/01HC/
1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
2//
3// SPDX-License-Identifier: MPL-2.0
4
5import Foundation
6
7public struct Lexer {
8 private let input: String
9 private var index: String.Index
10 private var tokens: [PterodactylSyntax.Token]
11
12 public init(input: String) {
13 self.input = input
14 self.index = input.startIndex
15 self.tokens = []
16 }
17
18 private var isAtEnd: Bool { index >= input.endIndex }
19
20 private var peek: Character? {
21 guard index < input.endIndex else { return nil }
22 return input[index]
23 }
24
25 private func lookahead() -> Character? {
26 guard index < input.endIndex else { return nil }
27 let next = input.index(after: index)
28 guard next < input.endIndex else { return nil }
29 return input[next]
30 }
31
32 private mutating func advance() -> Character {
33 let c = input[index]
34 index = input.index(after: index)
35
36 return c
37 }
38
39 private mutating func consume(while predicate: (Character) -> Bool) {
40 while let c = peek, predicate(c) {
41 _ = advance()
42 }
43 }
44
45 func text(from start: String.Index) -> String {
46 let range = start..<index
47 return String(input[range])
48 }
49
50 public mutating func nextToken() -> (kind: TokenKind, text: String)? {
51 guard let c = peek else {
52 return nil
53 }
54
55 let start = index
56
57 if c.isNewline {
58 _ = advance()
59 return (kind: .newline, text: text(from: start))
60 }
61
62 if c.isWhitespace && !c.isNewline {
63 consume { $0.isWhitespace && !$0.isNewline }
64 return (kind: .whitespace, text: text(from: start))
65 }
66
67 if c == "/" && lookahead() == "/" {
68 _ = advance()
69 _ = advance()
70 consume { $0 != "\n" }
71 return (kind: .lineComment, text: text(from: start))
72 }
73
74 if c == "/" && lookahead() == "*" {
75 _ = advance() // consume '/'
76 _ = advance() // consume '*'
77 var terminated = false
78
79 while let ch = peek {
80 if ch == "*" && lookahead() == "/" {
81 _ = advance() // consume '*'
82 _ = advance() // consume '/'
83 terminated = true
84 break
85 }
86 _ = advance()
87 }
88
89 return (kind: .blockComment(terminated: terminated), text: text(from: start))
90 }
91
92 if c == "<" && lookahead() == "=" {
93 _ = advance()
94 _ = advance()
95 return (kind: .punctuation(.doubleLeftArrow), text: text(from: start))
96 }
97
98 if c == "=" && lookahead() == ">" {
99 _ = advance()
100 _ = advance()
101 return (kind: .punctuation(.doubleRightArrow), text: text(from: start))
102 }
103
104 if let punct = Punctuation(rawValue: String(c)) {
105 _ = advance()
106 return (.punctuation(punct), String(c))
107 } else if c.isLetter || c == "_" {
108 _ = advance()
109 consume { $0.isLetter || $0.isNumber || $0 == "_" }
110 let text = text(from: start)
111 if let keyword = Keyword(rawValue: text) {
112 return (kind: .keyword(keyword), text: text)
113 }
114 return (kind: .identifier, text: text)
115 }
116
117 // Invalid single char (don’t drop input)
118 let ch = advance()
119 return (kind: .error, text: String(ch))
120 }
121
122 public mutating func tokenize() -> [Token] {
123 var tokens: [Token] = []
124
125 while !isAtEnd {
126 guard let token = nextToken() else { break }
127 tokens.append(
128 Token(kind: token.kind, text: token.text)
129 )
130 }
131
132 let eofToken = Token(kind: .eof, text: "")
133 tokens.append(eofToken)
134
135 return tokens
136 }
137}