| %{ |
| from gyb_syntax_support import * |
| # -*- mode: Swift -*- |
| # Ignore the following admonition it applies to the resulting .swift file only |
| }% |
| //// Automatically Generated From SyntaxClassifier.swift.gyb. |
| //// Do Not Edit Directly! |
| //===------------ SyntaxClassifier.swift.gyb - Syntax Collection ----------===// |
| // |
| // This source file is part of the Swift.org open source project |
| // |
| // Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors |
| // Licensed under Apache License v2.0 with Runtime Library Exception |
| // |
| // See https://swift.org/LICENSE.txt for license information |
| // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| // |
| //===----------------------------------------------------------------------===// |
| |
| public enum SyntaxClassification { |
| % for classification in SYNTAX_CLASSIFICATIONS: |
| % for line in dedented_lines(classification.description): |
| % if line: |
| /// ${line} |
| % end |
| % end |
| case ${classification.swift_name} |
| % end |
| } |
| |
| fileprivate class _SyntaxClassifier: SyntaxVisitor { |
| |
| /// Don't classify nodes with these IDs or any of their children |
| var skipNodeIds: Set<SyntaxNodeId> = [] |
| |
| /// The top of the `contextStack` determines the classification for all tokens |
| /// encountered that do not have a native classification. If `force` is `true` |
| /// the top of the stack also determines the classification of tokens with a |
| /// native classification |
| private var contextStack: [(classification: SyntaxClassification, force: Bool)] = |
| [(classification: .none, force: false)] |
| |
| /// The classifications that have determined so far are collected in this |
| /// dictionary |
| var classifications: [TokenSyntax: SyntaxClassification] = [:] |
| |
| private func visit( |
| _ node: Syntax, |
| classification: SyntaxClassification, |
| force: Bool = false |
| ) { |
| contextStack.append((classification: classification, force: force)) |
| visit(node) |
| contextStack.removeLast() |
| } |
| |
| private func getContextFreeClassificationForTokenKind(_ tokenKind: TokenKind) |
| -> SyntaxClassification? { |
| switch (tokenKind) { |
| % for token in SYNTAX_TOKENS: |
| case .${token.swift_kind()}: |
| % if token.classification: |
| return SyntaxClassification.${token.classification.swift_name} |
| % else: |
| return nil |
| % end |
| % end |
| case .eof: |
| return SyntaxClassification.none |
| } |
| } |
| |
| override func visit(_ token: TokenSyntax) { |
| // FIXME: We need to come up with some way in which the SyntaxClassifier can |
| // classify trivia (i.e. comments). In particular we need to be able to |
| // look into the comments to find things like URLs or keywords like MARK. |
| var classification = contextStack.last!.classification |
| if !contextStack.last!.force { |
| if let contextFreeClassification = |
| getContextFreeClassificationForTokenKind(token.tokenKind) { |
| classification = contextFreeClassification |
| } |
| if case .unknown = token.tokenKind, token.text.starts(with: "\"") { |
| classification = .stringLiteral |
| } else if case .identifier = token.tokenKind, |
| token.text.starts(with: "<#") && token.text.hasSuffix("#>") { |
| classification = .editorPlaceholder |
| } |
| } |
| assert(classifications[token] == nil, |
| "\(token) has already been classified") |
| classifications[token] = classification |
| } |
| |
| % for node in SYNTAX_NODES: |
| % if is_visitable(node): |
| override func visit(_ node: ${node.name}) { |
| if skipNodeIds.contains(node.raw.id) { |
| return |
| } |
| % if node.is_unknown() or node.is_syntax_collection(): |
| super.visit(node) |
| % else: |
| % for child in node.children: |
| % if child.is_optional: |
| if let ${child.swift_name} = node.${child.swift_name} { |
| % if child.classification: |
| visit(${child.swift_name}, |
| classification: .${child.classification.swift_name}, |
| force: ${"true" if child.force_classification else "false"}) |
| % else: |
| visit(${child.swift_name}) |
| % end |
| } |
| % else: |
| % if child.classification: |
| visit(node.${child.swift_name}, |
| classification: .${child.classification.swift_name}, |
| force: ${"true" if child.force_classification else "false"}) |
| % else: |
| visit(node.${child.swift_name}) |
| % end |
| % end |
| % end |
| % end |
| |
| } |
| % end |
| % end |
| } |
| |
| public enum SyntaxClassifier { |
| /// Classify all tokens in the given syntax tree for syntax highlighting. |
| /// If a `IncrementalTreeTransferSession` is passed, only nodes that have |
| /// changed since the last transfer will be classified. |
| public static func classifyTokensInTree( |
| _ syntaxTree: SourceFileSyntax, |
| skipNodes: Set<SyntaxNodeId> = [] |
| ) -> [TokenSyntax: SyntaxClassification] { |
| let classifier = _SyntaxClassifier() |
| classifier.skipNodeIds = skipNodes |
| classifier.visit(syntaxTree) |
| return classifier.classifications |
| } |
| } |