/* Language: Swift Description: Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns. Author: Steven Van Impe Contributors: Chris Eidhof , Nate Cook , Alexander Lichter , Richard Gibson Website: https://swift.org Category: common, system */ import * as Swift from './lib/kws_swift.js'; import { concat, either, lookahead } from '../lib/regex.js'; /** @type LanguageFn */ export default function(hljs) { const WHITESPACE = { match: /\s+/, relevance: 0 }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID411 const BLOCK_COMMENT = hljs.COMMENT( '/\\*', '\\*/', { contains: [ 'self' ] } ); const COMMENTS = [ hljs.C_LINE_COMMENT_MODE, BLOCK_COMMENT ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID413 // https://docs.swift.org/swift-book/ReferenceManual/zzSummaryOfTheGrammar.html const DOT_KEYWORD = { match: [ /\./, either(...Swift.dotKeywords, ...Swift.optionalDotKeywords) ], className: { 2: "keyword" } }; const KEYWORD_GUARD = { // Consume .keyword to prevent highlighting properties and methods as keywords. match: concat(/\./, either(...Swift.keywords)), relevance: 0 }; const PLAIN_KEYWORDS = Swift.keywords .filter(kw => typeof kw === 'string') .concat([ "_|0" ]); // seems common, so 0 relevance const REGEX_KEYWORDS = Swift.keywords .filter(kw => typeof kw !== 'string') // find regex .concat(Swift.keywordTypes) .map(Swift.keywordWrapper); const KEYWORD = { variants: [ { className: 'keyword', match: either(...REGEX_KEYWORDS, ...Swift.optionalDotKeywords) } ] }; // find all the regular keywords const KEYWORDS = { $pattern: either( /\b\w+/, // regular keywords /#\w+/ // number keywords ), keyword: PLAIN_KEYWORDS .concat(Swift.numberSignKeywords), literal: Swift.literals }; const KEYWORD_MODES = [ DOT_KEYWORD, KEYWORD_GUARD, KEYWORD ]; // https://github.com/apple/swift/tree/main/stdlib/public/core const BUILT_IN_GUARD = { // Consume .built_in to prevent highlighting properties and methods. match: concat(/\./, either(...Swift.builtIns)), relevance: 0 }; const BUILT_IN = { className: 'built_in', match: concat(/\b/, either(...Swift.builtIns), /(?=\()/) }; const BUILT_INS = [ BUILT_IN_GUARD, BUILT_IN ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID418 const OPERATOR_GUARD = { // Prevent -> from being highlighting as an operator. match: /->/, relevance: 0 }; const OPERATOR = { className: 'operator', relevance: 0, variants: [ { match: Swift.operator }, { // dot-operator: only operators that start with a dot are allowed to use dots as // characters (..., ...<, .*, etc). So there rule here is: a dot followed by one or more // characters that may also include dots. match: `\\.(\\.|${Swift.operatorCharacter})+` } ] }; const OPERATORS = [ OPERATOR_GUARD, OPERATOR ]; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_numeric-literal // TODO: Update for leading `-` after lookbehind is supported everywhere const decimalDigits = '([0-9]_*)+'; const hexDigits = '([0-9a-fA-F]_*)+'; const NUMBER = { className: 'number', relevance: 0, variants: [ // decimal floating-point-literal (subsumes decimal-literal) { match: `\\b(${decimalDigits})(\\.(${decimalDigits}))?` + `([eE][+-]?(${decimalDigits}))?\\b` }, // hexadecimal floating-point-literal (subsumes hexadecimal-literal) { match: `\\b0x(${hexDigits})(\\.(${hexDigits}))?` + `([pP][+-]?(${decimalDigits}))?\\b` }, // octal-literal { match: /\b0o([0-7]_*)+\b/ }, // binary-literal { match: /\b0b([01]_*)+\b/ } ] }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#grammar_string-literal const ESCAPED_CHARACTER = (rawDelimiter = "") => ({ className: 'subst', variants: [ { match: concat(/\\/, rawDelimiter, /[0\\tnr"']/) }, { match: concat(/\\/, rawDelimiter, /u\{[0-9a-fA-F]{1,8}\}/) } ] }); const ESCAPED_NEWLINE = (rawDelimiter = "") => ({ className: 'subst', match: concat(/\\/, rawDelimiter, /[\t ]*(?:[\r\n]|\r\n)/) }); const INTERPOLATION = (rawDelimiter = "") => ({ className: 'subst', label: "interpol", begin: concat(/\\/, rawDelimiter, /\(/), end: /\)/ }); const MULTILINE_STRING = (rawDelimiter = "") => ({ begin: concat(rawDelimiter, /"""/), end: concat(/"""/, rawDelimiter), contains: [ ESCAPED_CHARACTER(rawDelimiter), ESCAPED_NEWLINE(rawDelimiter), INTERPOLATION(rawDelimiter) ] }); const SINGLE_LINE_STRING = (rawDelimiter = "") => ({ begin: concat(rawDelimiter, /"/), end: concat(/"/, rawDelimiter), contains: [ ESCAPED_CHARACTER(rawDelimiter), INTERPOLATION(rawDelimiter) ] }); const STRING = { className: 'string', variants: [ MULTILINE_STRING(), MULTILINE_STRING("#"), MULTILINE_STRING("##"), MULTILINE_STRING("###"), SINGLE_LINE_STRING(), SINGLE_LINE_STRING("#"), SINGLE_LINE_STRING("##"), SINGLE_LINE_STRING("###") ] }; const REGEXP_CONTENTS = [ hljs.BACKSLASH_ESCAPE, { begin: /\[/, end: /\]/, relevance: 0, contains: [ hljs.BACKSLASH_ESCAPE ] } ]; const BARE_REGEXP_LITERAL = { begin: /\/[^\s](?=[^/\n]*\/)/, end: /\//, contains: REGEXP_CONTENTS }; const EXTENDED_REGEXP_LITERAL = (rawDelimiter) => { const begin = concat(rawDelimiter, /\//); const end = concat(/\//, rawDelimiter); return { begin, end, contains: [ ...REGEXP_CONTENTS, { scope: "comment", begin: `#(?!.*${end})`, end: /$/, }, ], }; }; // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Regular-Expression-Literals const REGEXP = { scope: "regexp", variants: [ EXTENDED_REGEXP_LITERAL('###'), EXTENDED_REGEXP_LITERAL('##'), EXTENDED_REGEXP_LITERAL('#'), BARE_REGEXP_LITERAL ] }; // https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412 const QUOTED_IDENTIFIER = { match: concat(/`/, Swift.identifier, /`/) }; const IMPLICIT_PARAMETER = { className: 'variable', match: /\$\d+/ }; const PROPERTY_WRAPPER_PROJECTION = { className: 'variable', match: `\\$${Swift.identifierCharacter}+` }; const IDENTIFIERS = [ QUOTED_IDENTIFIER, IMPLICIT_PARAMETER, PROPERTY_WRAPPER_PROJECTION ]; // https://docs.swift.org/swift-book/ReferenceManual/Attributes.html const AVAILABLE_ATTRIBUTE = { match: /(@|#(un)?)available/, scope: 'keyword', starts: { contains: [ { begin: /\(/, end: /\)/, keywords: Swift.availabilityKeywords, contains: [ ...OPERATORS, NUMBER, STRING ] } ] } }; const KEYWORD_ATTRIBUTE = { scope: 'keyword', match: concat(/@/, either(...Swift.keywordAttributes), lookahead(either(/\(/, /\s+/))), }; const USER_DEFINED_ATTRIBUTE = { scope: 'meta', match: concat(/@/, Swift.identifier) }; const ATTRIBUTES = [ AVAILABLE_ATTRIBUTE, KEYWORD_ATTRIBUTE, USER_DEFINED_ATTRIBUTE ]; // https://docs.swift.org/swift-book/ReferenceManual/Types.html const TYPE = { match: lookahead(/\b[A-Z]/), relevance: 0, contains: [ { // Common Apple frameworks, for relevance boost className: 'type', match: concat(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/, Swift.identifierCharacter, '+') }, { // Type identifier className: 'type', match: Swift.typeIdentifier, relevance: 0 }, { // Optional type match: /[?!]+/, relevance: 0 }, { // Variadic parameter match: /\.\.\./, relevance: 0 }, { // Protocol composition match: concat(/\s+&\s+/, lookahead(Swift.typeIdentifier)), relevance: 0 } ] }; const GENERIC_ARGUMENTS = { begin: //, keywords: KEYWORDS, contains: [ ...COMMENTS, ...KEYWORD_MODES, ...ATTRIBUTES, OPERATOR_GUARD, TYPE ] }; TYPE.contains.push(GENERIC_ARGUMENTS); // https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID552 // Prevents element names from being highlighted as keywords. const TUPLE_ELEMENT_NAME = { match: concat(Swift.identifier, /\s*:/), keywords: "_|0", relevance: 0 }; // Matches tuples as well as the parameter list of a function type. const TUPLE = { begin: /\(/, end: /\)/, relevance: 0, keywords: KEYWORDS, contains: [ 'self', TUPLE_ELEMENT_NAME, ...COMMENTS, REGEXP, ...KEYWORD_MODES, ...BUILT_INS, ...OPERATORS, NUMBER, STRING, ...IDENTIFIERS, ...ATTRIBUTES, TYPE ] }; const GENERIC_PARAMETERS = { begin: //, keywords: 'repeat each', contains: [ ...COMMENTS, TYPE ] }; const FUNCTION_PARAMETER_NAME = { begin: either( lookahead(concat(Swift.identifier, /\s*:/)), lookahead(concat(Swift.identifier, /\s+/, Swift.identifier, /\s*:/)) ), end: /:/, relevance: 0, contains: [ { className: 'keyword', match: /\b_\b/ }, { className: 'params', match: Swift.identifier } ] }; const FUNCTION_PARAMETERS = { begin: /\(/, end: /\)/, keywords: KEYWORDS, contains: [ FUNCTION_PARAMETER_NAME, ...COMMENTS, ...KEYWORD_MODES, ...OPERATORS, NUMBER, STRING, ...ATTRIBUTES, TYPE, TUPLE ], endsParent: true, illegal: /["']/ }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID362 // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/#Macro-Declaration const FUNCTION_OR_MACRO = { match: [ /(func|macro)/, /\s+/, either(QUOTED_IDENTIFIER.match, Swift.identifier, Swift.operator) ], className: { 1: "keyword", 3: "title.function" }, contains: [ GENERIC_PARAMETERS, FUNCTION_PARAMETERS, WHITESPACE ], illegal: [ /\[/, /%/ ] }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID375 // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID379 const INIT_SUBSCRIPT = { match: [ /\b(?:subscript|init[?!]?)/, /\s*(?=[<(])/, ], className: { 1: "keyword" }, contains: [ GENERIC_PARAMETERS, FUNCTION_PARAMETERS, WHITESPACE ], illegal: /\[|%/ }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID380 const OPERATOR_DECLARATION = { match: [ /operator/, /\s+/, Swift.operator ], className: { 1: "keyword", 3: "title" } }; // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID550 const PRECEDENCEGROUP = { begin: [ /precedencegroup/, /\s+/, Swift.typeIdentifier ], className: { 1: "keyword", 3: "title" }, contains: [ TYPE ], keywords: [ ...Swift.precedencegroupKeywords, ...Swift.literals ], end: /}/ }; const TYPE_DECLARATION = { begin: [ /(struct|protocol|class|extension|enum|actor)/, /\s+/, Swift.identifier, /\s*/, ], beginScope: { 1: "keyword", 3: "title.class" }, keywords: KEYWORDS, contains: [ GENERIC_PARAMETERS, ...KEYWORD_MODES, { begin: /:/, end: /\{/, keywords: KEYWORDS, contains: [ { scope: "title.class.inherited", match: Swift.typeIdentifier, }, ...KEYWORD_MODES, ], relevance: 0, }, ] }; // Add supported submodes to string interpolation. for (const variant of STRING.variants) { const interpolation = variant.contains.find(mode => mode.label === "interpol"); // TODO: Interpolation can contain any expression, so there's room for improvement here. interpolation.keywords = KEYWORDS; const submodes = [ ...KEYWORD_MODES, ...BUILT_INS, ...OPERATORS, NUMBER, STRING, ...IDENTIFIERS ]; interpolation.contains = [ ...submodes, { begin: /\(/, end: /\)/, contains: [ 'self', ...submodes ] } ]; } return { name: 'Swift', keywords: KEYWORDS, contains: [ ...COMMENTS, FUNCTION_OR_MACRO, INIT_SUBSCRIPT, TYPE_DECLARATION, OPERATOR_DECLARATION, PRECEDENCEGROUP, { beginKeywords: 'import', end: /$/, contains: [ ...COMMENTS ], relevance: 0 }, REGEXP, ...KEYWORD_MODES, ...BUILT_INS, ...OPERATORS, NUMBER, STRING, ...IDENTIFIERS, ...ATTRIBUTES, TYPE, TUPLE ] }; }