1// SPDX-FileCopyrightText: 2025 The Project Pterodactyl Developers
2// SPDX-FileCopyrightText: 2026 The Project Pterodactyl Developers
3//
4// SPDX-License-Identifier: MPL-2.0
5
6import OrderedCollections
7import PterodactylFeedback
8
9enum PrecedenceGroup {
10 case equality
11 case arrow
12
13 enum Associativity {
14 case right
15 case none
16 }
17
18 var associativity: Associativity {
19 switch self {
20 case .equality: return .none
21 case .arrow: return .right
22 }
23 }
24
25 enum Relation {
26 case tighter
27 case looser
28 case same
29 case unrelated
30 }
31
32 private var ordinal: Int {
33 switch self {
34 case .arrow: return 0
35 case .equality: return 1
36 }
37 }
38
39 func relation(to other: PrecedenceGroup) -> Relation {
40 if self == other { return .same }
41 return ordinal > other.ordinal ? .tighter : .looser
42 }
43
44 func canBind(above lowerBound: PrecedenceGroup?) -> Bool {
45 guard let lowerBound else { return true }
46 switch relation(to: lowerBound) {
47 case .tighter: return true
48 case .looser: return false
49 case .same: return lowerBound.associativity == .right
50 case .unrelated: return true
51 }
52 }
53}
54
55
56struct Scanner {
57 let tokens: [Token]
58 var position: Int
59
60 var currentToken: Token? {
61 if tokens.indices.contains(position) {
62 tokens[position]
63 } else {
64 nil
65 }
66 }
67
68 mutating func eat(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
69 guard let currentToken, predicate(currentToken.kind) else { return false }
70 position += 1
71 return true
72 }
73
74 mutating func eat(kind: TokenKind) -> Bool {
75 eat(kindSatisfying: { $0 == kind })
76 }
77
78 mutating func eatTrivia() {
79 while let currentToken, currentToken.kind.isTrivia { position += 1 }
80 }
81
82 public func isAt(kind: TokenKind) -> Bool {
83 currentToken?.kind == kind
84 }
85
86 public func isAt(kindSatisfying predicate: (TokenKind) -> Bool) -> Bool {
87 guard let currentToken else { return false }
88 return predicate(currentToken.kind)
89 }
90}
91
92
93extension Parser {
94 var scanner: Scanner {
95 Scanner(tokens: tokens, position: position)
96 }
97}
98
99
100struct DelimiterInfo {
101 let left: TokenKind
102 let sep: TokenKind
103 let right: TokenKind
104
105 static var block: DelimiterInfo {
106 DelimiterInfo(left: .blockBegin, sep: .blockSep, right: .blockEnd)
107 }
108
109 static var curly: DelimiterInfo {
110 DelimiterInfo(left: .punctuation(.lbrace), sep: .punctuation(.semicolon), right: .punctuation(.rbrace))
111 }
112
113 var leftProduction: Production {
114 Parser.token(kind: left)
115 .then { state in
116 _ = separatorProduction.tryParse(&state)
117 }
118 }
119
120 var separatorProduction: Production {
121 Parser.token(kind: sep).precededByTrivia.many1
122 }
123
124 var rightProduction: Production {
125 Parser.token(kind: right).precededByTrivia
126 }
127}
128
129extension Parser {
130 mutating func keyword(_ keyword: Keyword) {
131 expect(kind: .keyword(keyword), metadata: TokenMetadata(semanticTokenType: .keyword))
132 }
133}
134
135struct Production {
136 var name: String
137 var _precondition: (_ state: Scanner) -> Bool
138 var _commit: (_ state: inout Parser) -> Void
139
140 init(name: String, precondition: @escaping (_: Scanner) -> Bool, commit: @escaping (_: inout Parser) -> Void) {
141 self.name = name
142 self._precondition = precondition
143 self._commit = commit
144 }
145
146 init(
147 name: String,
148 kind: SyntaxTreeKind,
149 metadata: SyntaxTreeMetadata? = nil,
150 precondition: @escaping (_: Scanner) -> Bool,
151 commit: @escaping (_: inout Parser) -> Void
152 ) {
153 self.init(name: name) { state in
154 precondition(state)
155 } commit: { state in
156 state.open()
157 defer { state.close(kind: kind, metadata: metadata) }
158 commit(&state)
159 }
160 }
161
162 func then(_ body: @escaping (_ state: inout Parser) -> Void) -> Production {
163 Production(name: "\(name).then") { scanner in
164 self._precondition(scanner)
165 } commit: { state in
166 self._commit(&state)
167 body(&state)
168 }
169 }
170
171 func precondition(_ state: Scanner) -> Bool {
172 _precondition(state)
173 }
174
175 func commit(_ state: inout Parser) {
176 let position = state.position
177 _commit(&state)
178 assert(state.position > position, "Parser `\(name)` did not consume any tokens.")
179 }
180
181 func tryParse(_ state: inout Parser, error: String? = nil) -> Bool {
182 guard precondition(state.scanner) else {
183 if let error {
184 state.diagnostics.append(Diagnostic(message: error, severity: .error, utf16Range: state.absoluteRangeOfCurrentToken))
185 }
186 return false
187 }
188 commit(&state)
189 return true
190 }
191
192 func parse(_ state: inout Parser, error: String) -> Bool {
193 if !tryParse(&state) {
194 state.recover(until: state.recoveryStack.current, expecting: [], message: error)
195 return false
196 }
197
198 return true
199 }
200
201 var precededByTrivia: Production {
202 Production(name: self.name + ".precededByTrivia") { scanner in
203 var scanner = scanner
204 scanner.eatTrivia()
205 return precondition(scanner)
206 } commit: { state in
207 state.eatTrivia()
208 _commit(&state)
209 }
210 }
211
212 func wrap(name: String, kind: SyntaxTreeKind, metadata: SyntaxTreeMetadata? = nil) -> Production {
213 Production(name: name, kind: kind, metadata: metadata, precondition: _precondition, commit: _commit)
214 }
215
216 static func cascade(_ productions: [Production]) -> Production {
217 Production(name: "cascade[_\(productions.map(\.name))]") { scanner in
218 productions.contains { $0.precondition(scanner) }
219 } commit: { state in
220 for production in productions {
221 if production.tryParse(&state) {
222 return
223 }
224 }
225 }
226 }
227
228 func delimitedList(_ delim: DelimiterInfo) -> Production {
229 delim.leftProduction.then { state in
230 state.recoveryStack.push([.blockSep, .blockEnd])
231 defer { state.recoveryStack.pop() }
232 defer { _ = delim.rightProduction.parse(&state, error: "Missing closing delimiter: \(delim.right)") }
233 while true {
234 _ = precededByTrivia.tryParse(&state)
235 guard delim.separatorProduction.tryParse(&state) else { break }
236 }
237 }
238 }
239
240 var many1: Production {
241 return then { state in
242 while precededByTrivia.tryParse(&state) {}
243 }
244 }
245}
246
247extension Parser {
248 static func token(kind: TokenKind, metadata: TokenMetadata? = nil) -> Production {
249 Production(name: "token[\(kind)]") { scanner in
250 scanner.isAt(kind: kind)
251 } commit: { state in
252 state.expect(kind: kind, metadata: metadata)
253 }
254 }
255
256 private mutating func typedBinder() {
257 recoveryStack.push([.punctuation(.lparen), .punctuation(.colon), .punctuation(.rparen)])
258 defer { recoveryStack.pop() }
259
260 expect(kind: .punctuation(.lparen))
261 let gotIdentifier = Self.token(kind: .identifier).precededByTrivia.tryParse(&self, error: "Binder: expected identifier here")
262 let gotColon = Self.token(kind: .punctuation(.colon)).precededByTrivia.tryParse(&self, error: gotIdentifier ? "Binder: expected type annotation here" : nil)
263 let gotTypeExpr = gotColon ? Self.expression.precededByTrivia.parse(&self, error: "Binder: expected type expression here") : false
264 _ = Self.token(kind: .punctuation(.rparen)).precededByTrivia.tryParse(&self, error: gotTypeExpr ? "Binder: expected closing parenthesis" : nil)
265 }
266
267 static var funTypeBinder: Production {
268 Production(name: "funTypeBinder", kind: .typedBinder) { scanner in
269 var scanner = scanner
270 guard scanner.eat(kind: .punctuation(.lparen)) else { return false }
271 scanner.eatTrivia()
272 guard scanner.eat(kind: .identifier) else { return false }
273 scanner.eatTrivia()
274 guard scanner.eat(kind: .punctuation(.colon)) else { return false }
275 return true
276 } commit: {
277 $0.typedBinder()
278 }
279 }
280
281 static var programParam: Production {
282 Production(name: "programParam", kind: .typedBinder) {
283 $0.isAt(kind: .punctuation(.lparen))
284 } commit: {
285 $0.typedBinder()
286 }
287 }
288
289 static var holeExpression: Production {
290 token(
291 kind: .punctuation(.questionMark),
292 metadata: TokenMetadata(semanticTokenType: .comment, semanticTokenModifiers: [.abstract])
293 )
294 .wrap(name: "holeExpression", kind: .hole)
295 }
296
297 static var universeCodesExpression: Production {
298 token(kind: .keyword(.type), metadata: TokenMetadata(semanticTokenType: .type))
299 .wrap(name: "universeCodesExpression", kind: .universeCodes)
300 }
301
302 static var parenthesisedExpression: Production {
303 token(kind: .punctuation(.lparen))
304 .then { state in
305 _ = expression.precededByTrivia.tryParse(&state, error: "Expected expression")
306 _ = token(kind: .punctuation(.rparen)).precededByTrivia.parse(&state, error: "Expected right parenthesis")
307 }
308 }
309
310 static var reflExpression: Production {
311 token(kind: .keyword(.refl), metadata: TokenMetadata(semanticTokenType: .keyword))
312 .wrap(name: "reflExpression", kind: .refl)
313 }
314
315 static var identifierExpression: Production {
316 token(kind: .identifier)
317 .wrap(name: "identifierExpression", kind: .identifier)
318 }
319
320 static var atomicExpression: Production {
321 .cascade([
322 recordType,
323 holeExpression,
324 universeCodesExpression,
325 identifierExpression,
326 parenthesisedExpression,
327 recordExpression,
328 reflExpression
329 ])
330 }
331
332 static var depFunType: Production {
333 let typedBinder = Self.funTypeBinder
334 return typedBinder.then { state in
335 while typedBinder.precededByTrivia.tryParse(&state) {}
336
337 let gotArrow =
338 token(kind: .punctuation(.rightArrow), metadata: TokenMetadata(semanticTokenType: .keyword))
339 .precededByTrivia
340 .tryParse(&state, error: "Expected function arrow `->`")
341
342 if gotArrow {
343 _ = expression.precededByTrivia.parse(&state, error: "Expected type expression to the right of function arrow")
344 }
345
346 }
347 .wrap(name: "depFunType", kind: .depFunType)
348 }
349
350 static var recordType: Production {
351 token(kind: .keyword(.record), metadata: TokenMetadata(semanticTokenType: .keyword)).then { state in
352 _ = recordItemSpec.delimitedList(.curly).precededByTrivia.parse(&state, error: "Expected record type specification")
353 }
354 .wrap(name: "recordType", kind: .recordType)
355 }
356
357 private static func afterIdentifier(_ kind: TokenKind) -> (Scanner) -> Bool {
358 return { scanner in
359 var scanner = scanner
360 guard scanner.eat(kind: .identifier) else { return false }
361 scanner.eatTrivia()
362 return scanner.eat(kind: kind)
363 }
364 }
365
366 static var recordItemSpec: Production {
367 Production(
368 name: "recordItemSpec",
369 kind: .recordItem(kind: .spec),
370 precondition: afterIdentifier(.punctuation(.colon))
371 ) { state in
372 state.expect(kind: .identifier)
373 _ = token(kind: .punctuation(.colon), metadata: TokenMetadata(semanticTokenType: .keyword))
374 .precededByTrivia
375 .parse(&state, error: "Expected `:`")
376 _ = expression.precededByTrivia.parse(&state, error: "Expected expression")
377 }
378 }
379
380 static var namedRecordItemImpl: Production {
381 Production(
382 name: "recordItemImpl",
383 kind: .recordItem(kind: .impl),
384 precondition: afterIdentifier(.punctuation(.doubleRightArrow))
385 ) { state in
386 state.expect(kind: .identifier)
387 _ = token(kind: .punctuation(.doubleRightArrow), metadata: TokenMetadata(semanticTokenType: .keyword))
388 .precededByTrivia
389 .parse(&state, error: "Expected `=>`")
390 _ = expression.precededByTrivia.parse(&state, error: "Expected expression")
391 }
392 }
393
394 static var unnamedRecordItemImpl: Production {
395 expression.wrap(name: "unnamedRecordItemImpl", kind: .recordItem(kind: .impl))
396 }
397
398 static var recordItemImpl: Production {
399 .cascade([namedRecordItemImpl, unnamedRecordItemImpl])
400 }
401
402 static var recordExpression: Production {
403 recordItemImpl.delimitedList(.curly).wrap(name: "recordExpression", kind: .record)
404 }
405
406 static var appFrame: Production {
407 atomicExpression.wrap(name: "appFrame", kind: .appFrame)
408 }
409
410 static var dotFrame: Production {
411 token(kind: .punctuation(.dot))
412 .then { $0.expect(kind: .identifier) }
413 .wrap(name: "dotFrame", kind: .projFrame)
414 }
415
416 static var spineFrame: Production {
417 .cascade([
418 appFrame,
419 dotFrame
420 ])
421 }
422
423 private static var operatorTable: [(TokenKind, SyntaxTreeKind, PrecedenceGroup)] {
424 [
425 (.punctuation(.equal), .idType, .equality),
426 (.punctuation(.rightArrow), .funType, .arrow)
427 ]
428 }
429
430 private static func peekOperator(_ scanner: Scanner) -> (TokenKind, SyntaxTreeKind, PrecedenceGroup)? {
431 var scanner = scanner
432 scanner.eatTrivia()
433 for (tokenKind, treeKind, group) in operatorTable {
434 if scanner.isAt(kind: tokenKind) {
435 return (tokenKind, treeKind, group)
436 }
437 }
438 return nil
439 }
440
441 static func spineExpression() -> Production {
442 Production(name: "spineExpression") {
443 atomicExpression.precondition($0)
444 } commit: { state in
445 let checkPoint = state.checkpoint
446 _ = atomicExpression.parse(&state, error: "Expected expression")
447
448 if spineFrame.precededByTrivia.precondition(state.scanner) {
449 state.openAtCheckpoint(checkPoint)
450 while spineFrame.precededByTrivia.tryParse(&state) {}
451 state.close(kind: .spine)
452 }
453 }
454 }
455
456 static func operatorExpression(lowerBound: PrecedenceGroup?) -> Production {
457 Production(name: "operatorExpression") {
458 spineExpression().precondition($0)
459 } commit: { state in
460 var checkPoint = state.checkpoint
461 _ = spineExpression().parse(&state, error: "Expected expression")
462
463 while let (tokenKind, treeKind, group) = peekOperator(state.scanner), group.canBind(above: lowerBound) {
464 state.openAtCheckpoint(checkPoint)
465 state.eatTrivia()
466 state.expect(kind: tokenKind, metadata: TokenMetadata(semanticTokenType: .operator))
467 _ = Self.operatorExpression(lowerBound: group)
468 .precededByTrivia
469 .parse(&state, error: "Expected argument on right hand side of `\(tokenKind)` operator")
470 state.close(kind: treeKind)
471 checkPoint = state.checkpoint
472 }
473 }
474 }
475
476 static var operatorExpression: Production {
477 operatorExpression(lowerBound: nil)
478 }
479
480 static var expression: Production {
481 .cascade([depFunType, lambdaExpression, operatorExpression])
482 }
483
484 static var lambdaExpression: Production {
485 token(kind: .punctuation(.backslash)).then { state in
486 _ = token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .variable)).wrap(name: "untypedBinder", kind: .untypedBinder).precededByTrivia.many1.tryParse(&state)
487 _ = token(kind: .punctuation(.doubleRightArrow)).precededByTrivia.parse(&state, error: "Expected `=>`")
488 _ = expression.precededByTrivia.parse(&state, error: "Expected expression")
489 }
490 .wrap(name: "lambdaExpression", kind: .lambda)
491 }
492
493 static var decl: Production {
494 .cascade([theory, programDecl])
495 }
496
497 static var theory: Production {
498 token(kind: .keyword(.theory), metadata: TokenMetadata(semanticTokenType: .keyword))
499 .then { state in
500 _ = theoryName.precededByTrivia.parse(&state, error: "Expected theory name")
501 let gotWhere =
502 token(kind: .keyword(.where), metadata: TokenMetadata(semanticTokenType: .keyword))
503 .precededByTrivia
504 .parse(&state, error: "Expected `where` keyword")
505 if gotWhere {
506 _ = decl.delimitedList(.block).parse(&state, error: "Expected theory block")
507 }
508 }
509 .wrap(name: "theory", kind: .theory, metadata: SyntaxTreeMetadata(delimitedFoldingRangeKind: .region))
510 }
511
512 static var theoryName: Production {
513 token(kind: .identifier, metadata: TokenMetadata(semanticTokenType: .interface))
514 .wrap(name: "theoryName", kind: .theoryName)
515 }
516
517 static var programName: Production {
518 token(kind: .identifier)
519 .wrap(name: "programName", kind: .programName)
520 }
521
522 static var programClauseLhs: Production {
523 expression.wrap(name: "programClauseLhs", kind: .programClauseLhs)
524 }
525
526 static var programReturn: Production {
527 token(kind: .punctuation(.doubleRightArrow), metadata: TokenMetadata(semanticTokenType: .operator))
528 .then { state in
529 _ = expression.precededByTrivia.tryParse(&state, error: "Expected right-hand side to return expression")
530 }
531 .wrap(name: "programReturn", kind: .programReturn)
532 }
533
534 static var programGadgetSplit: Production {
535 token(kind: .keyword(.split))
536 .wrap(name: "programGadgetSplit", kind: .splitGadget)
537 }
538
539 static var programGadgetIntro: Production {
540 token(kind: .keyword(.intro))
541 .wrap(name: "programGadgetIntro", kind: .introGadget)
542 }
543
544 static var programGadget: Production {
545 .cascade([programGadgetSplit, programGadgetIntro])
546 }
547
548 static var programBy: Production {
549 token(kind: .punctuation(.doubleLeftArrow), metadata: TokenMetadata(semanticTokenType: .operator))
550 .then { state in
551 _ = programGadget.precededByTrivia.tryParse(&state, error: "Expected `intro` or `split` gadget after `<=`.")
552 }
553 .wrap(name: "programBy", kind: .programBy)
554 }
555
556 static var programCommand: Production {
557 .cascade([programReturn, programBy])
558 }
559
560 static var programType: Production {
561 token(kind: .punctuation(.colon))
562 .then { state in
563 _ = expression.precededByTrivia.parse(&state, error: "Expected a type expression here")
564 }
565 .wrap(name: "programType", kind: .programType)
566 }
567
568 static var programMatchClause: Production {
569 programClauseLhs
570 .then { state in
571 _ =
572 programCommand
573 .precededByTrivia
574 .wrap(name: "programMatchClauseRhs", kind: .programClauseRhs)
575 .tryParse(&state, error: "Expected right-hand side to program match clause")
576 }
577 .wrap(name: "programMatchClauseRhs", kind: .programMatchClause)
578 }
579
580 static var programDeclWhereBody: Production {
581 token(kind: .keyword(.where), metadata: TokenMetadata(semanticTokenType: .keyword))
582 .then { state in
583 _ =
584 Production.cascade([programCommand, programMatchClause])
585 .delimitedList(.block)
586 .precededByTrivia
587 .tryParse(&state)
588 }
589 }
590
591 static var programDeclBody: Production {
592 .cascade([programReturn, programDeclWhereBody])
593 .wrap(name: "programDeclBody", kind: .programBody)
594 }
595
596 static var programDecl: Production {
597 programName.then { state in
598 let gotParams =
599 programParam
600 .many1
601 .wrap(name: "programParams", kind: .programParams)
602 .precededByTrivia
603 .tryParse(&state)
604
605 let gotType = programType.precededByTrivia.parse(
606 &state,
607 error: gotParams ? "Program declaration: expected type annotation here" : "Program declaration: expected parameters or type annotation here"
608 )
609
610 _ = programDeclBody.precededByTrivia.tryParse(&state, error: gotType ? "Expected `=>` or `where` clause" : nil)
611 }
612 .wrap(name: "programDecl", kind: .programDecl, metadata: SyntaxTreeMetadata(delimitedFoldingRangeKind: .region))
613 }
614
615 static var importName: Production {
616 token(kind: .identifier)
617 .wrap(name: "importName", kind: .importName)
618 }
619
620 static var importDecl: Production {
621 token(kind: .keyword(.import), metadata: TokenMetadata(semanticTokenType: .keyword))
622 .then { state in
623 _ = importName.precededByTrivia.parse(&state, error: "Expected import name") // TODO
624 }
625 .wrap(name: "importDecl", kind: .import)
626 }
627
628 static var documentItem: Production {
629 .cascade([importDecl, decl])
630 }
631
632 static var document: Production {
633 let documentBlockAndEof =
634 documentItem.delimitedList(.block).then { state in
635 _ = token(kind: .eof).precededByTrivia.parse(&state, error: "Expected EOF")
636 }
637
638 return
639 .cascade([token(kind: .eof), documentBlockAndEof])
640 .precededByTrivia
641 .wrap(name: "document", kind: .document)
642 }
643}
644
645extension Parser {
646 public mutating func document() {
647 _ = Self.document.parse(&self, error: "Invalid start of document")
648 }
649}