blob: 91541a6007b0a4adfacb9f49cb5a5d86da86622e [file] [log] [blame]
function highlightDotty(hljs) {
// identifiers
const capitalizedId = /\b[A-Z][$\w]*\b/
const alphaId = /[a-zA-Z$_][$\w]*/
const op1 = /[^\s\w\d,;"'()[\]{}=:]/
const op2 = /[^\s\w\d,;"'()[\]{}]/
const compound = `[a-zA-Z$][a-zA-Z0-9$]*_${op2.source}` // e.g. value_=
const id = new RegExp(`(${compound}|${alphaId.source}|${op2.source}{2,}|${op1.source}+|\`.+?\`)`)
// numbers
const hexDigit = '[a-fA-F0-9]'
const hexNumber = `0[xX]${hexDigit}((${hexDigit}|_)*${hexDigit}+)?`
const decNumber = `0|([1-9]((\\d|_)*\\d)?)`
const exponent = `[eE][+-]?\\d((\\d|_)*\\d)?`
const floatingPointA = `(${decNumber})?\\.\\d((\\d|_)*\\d)?${exponent}[fFdD]?`
const floatingPointB = `${decNumber}${exponent}[fFdD]?`
const number = new RegExp(`(${hexNumber}|${floatingPointA}|${floatingPointB}|(${decNumber}[lLfFdD]?))`)
// Regular Keywords
// The "soft" keywords (e.g. 'using') are added later where necessary
const alwaysKeywords = {
$pattern: /(\w+|\?=>|\?{1,3}|=>>|=>|<:|>:|_|#|<-|\.nn)/,
keyword:
'abstract case catch class def do else enum export extends final finally for given '+
'if implicit import lazy match new object package private protected override return '+
'sealed then throw trait true try type val var while with yield =>> => ?=> <: >: _ ? <- #',
literal: 'true false null this super',
built_in: '??? asInstanceOf isInstanceOf assert implicitly locally summon valueOf .nn'
}
const modifiers = 'abstract|final|implicit|override|private|protected|sealed'
// End of class, enum, etc. header
const templateDeclEnd = /(\/[/*]|{|:(?= *\n)|\n(?! *(extends|with|derives)))/
// all the keywords + soft keywords, separated by spaces
function withSoftKeywords(kwd) {
return {
$pattern: alwaysKeywords.$pattern,
keyword: kwd + ' ' + alwaysKeywords.keyword,
literal: alwaysKeywords.literal,
built_in: alwaysKeywords.built_in
}
}
// title inside of a complex token made of several parts (e.g. class)
const TITLE = {
className: 'title',
begin: id,
returnEnd: true,
keywords: alwaysKeywords.keyword,
literal: alwaysKeywords.literal,
built_in: alwaysKeywords.built_in
}
// title that goes to the end of a simple token (e.g. val)
const TITLE2 = {
className: 'title',
begin: id,
excludeEnd: true,
endsWithParent: true
}
const TYPED = {
begin: /: (?=[a-zA-Z()?])/,
end: /\/\/|\/\*|\n/,
endsWithParent: true,
returnEnd: true,
contains: [
{
// works better than the usual way of defining keyword,
// in this specific situation
className: 'keyword',
begin: /\?\=>|=>>|[=:][><]|\?/,
},
{
className: 'type',
begin: alphaId
}
]
}
const PROBABLY_TYPE = {
className: 'type',
begin: capitalizedId,
relevance: 0
}
const NUMBER = {
className: 'number',
begin: number,
relevance: 0
}
// type parameters within [square brackets]
const TPARAMS = {
begin: /\[/, end: /\]/,
keywords: {
$pattern: /<:|>:|[+-?_:]/,
keyword: '<: >: : + - ? _'
},
contains: [
hljs.C_BLOCK_COMMENT_MODE,
{
className: 'type',
begin: alphaId
},
],
relevance: 3
}
// Class or method parameters declaration
const PARAMS = {
className: 'params',
begin: /\(/, end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: withSoftKeywords('inline using'),
contains: [
hljs.C_BLOCK_COMMENT_MODE,
hljs.QUOTE_STRING_MODE,
NUMBER,
PROBABLY_TYPE
]
}
// (using T1, T2, T3)
const CTX_PARAMS = {
className: 'params',
begin: /\(using (?!\w+:)/, end: /\)/,
excludeBegin: false,
excludeEnd: true,
relevance: 5,
keywords: withSoftKeywords('using'),
contains: [
PROBABLY_TYPE
]
}
// String interpolation
const SUBST = {
className: 'subst',
variants: [
{begin: /\$[a-zA-Z_]\w*/},
{
begin: /\${/, end: /}/,
contains: [
NUMBER,
hljs.QUOTE_STRING_MODE
]
}
]
}
// "string" or """string""", with or without interpolation
const STRING = {
className: 'string',
variants: [
hljs.QUOTE_STRING_MODE,
{
begin: '"""', end: '"""',
contains: [hljs.BACKSLASH_ESCAPE],
relevance: 10
},
{
begin: alphaId.source + '"', end: '"',
contains: [hljs.BACKSLASH_ESCAPE, SUBST],
illegal: /\n/,
relevance: 5
},
{
begin: alphaId.source + '"""', end: '"""',
contains: [hljs.BACKSLASH_ESCAPE, SUBST],
relevance: 10
}
]
}
// Class or method apply
const APPLY = {
begin: /\(/, end: /\)/,
excludeBegin: true, excludeEnd: true,
keywords: {
$pattern: alwaysKeywords.$pattern,
keyword: 'using ' + alwaysKeywords.keyword,
literal: alwaysKeywords.literal,
built_in: alwaysKeywords.built_in
},
contains: [
STRING,
NUMBER,
hljs.C_BLOCK_COMMENT_MODE,
PROBABLY_TYPE,
]
}
// @annot(...) or @my.package.annot(...)
const ANNOTATION = {
className: 'meta',
begin: `@${id.source}(\\.${id.source})*`,
contains: [
APPLY,
hljs.C_BLOCK_COMMENT_MODE
]
}
// Documentation
const SCALADOC = hljs.COMMENT('/\\*\\*', '\\*/', {
contains: [
{
className: 'doctag',
begin: /@[a-zA-Z]+/
},
// markdown syntax elements:
{
className: 'code',
variants: [
{begin: /```.*\n/, end: /```/},
{begin: /`/, end: /`/}
],
},
{
className: 'bold',
variants: [
{begin: /\*\*/, end: /\*\*/},
{begin: /__/, end: /__/}
],
},
{
className: 'emphasis',
variants: [
{begin: /\*(?!([\*\s/])|([^\*]*\*[\*/]))/, end: /\*/},
{begin: /_/, end: /_/}
],
},
{
className: 'bullet', // list item
begin: /- (?=\S)/, end: /\s/,
},
{
begin: /\[.*?\]\(/, end: /\)/,
contains: [
{
// mark as "link" only the URL
className: 'link',
begin: /.*?/,
endsWithParent: true
}
]
}
]
})
// Methods
const METHOD = {
className: 'function',
begin: `((${modifiers}|transparent|inline|infix) +)*def`, end: / =\s|\n/,
excludeEnd: true,
relevance: 5,
keywords: withSoftKeywords('inline infix transparent'),
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
TPARAMS,
CTX_PARAMS,
PARAMS,
TYPED, // prevents the ":" (declared type) to become a title
PROBABLY_TYPE,
TITLE
]
}
// Variables & Constants
const VAL = {
beginKeywords: 'val var', end: /[=:;\n/]/,
excludeEnd: true,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
TITLE2
]
}
// Type declarations
const TYPEDEF = {
className: 'typedef',
begin: `((${modifiers}|opaque) +)*type`, end: /[=;\n]| ?[<>]:/,
excludeEnd: true,
keywords: withSoftKeywords('opaque'),
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
PROBABLY_TYPE,
TITLE,
]
}
// Given instances
const GIVEN = {
begin: /given/, end: / =|[=;\n]/,
excludeEnd: true,
keywords: 'given using with',
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
PARAMS,
{
begin: 'as',
keywords: 'as'
},
PROBABLY_TYPE,
TITLE
]
}
// Extension methods
const EXTENSION = {
begin: /extension/, end: /(\n|def)/,
returnEnd: true,
keywords: 'extension implicit using',
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
CTX_PARAMS,
PARAMS,
PROBABLY_TYPE
]
}
// 'end' soft keyword
const END = {
begin: `end(?= (if|while|for|match|try|given|extension|this|val|${id.source})\\n)`, end: /\s/,
keywords: 'end'
}
// Classes, traits, enums, etc.
const EXTENDS_PARENT = {
begin: ' extends ', end: /( with | derives |\/[/*])/,
endsWithParent: true,
returnEnd: true,
keywords: 'extends',
contains: [APPLY, PROBABLY_TYPE]
}
const WITH_MIXIN = {
begin: ' with ', end: / derives |\/[/*]/,
endsWithParent: true,
returnEnd: true,
keywords: 'with',
contains: [APPLY, PROBABLY_TYPE],
relevance: 10
}
const DERIVES_TYPECLASS = {
begin: ' derives ', end: /\n|\/[/*]/,
endsWithParent: true,
returnEnd: true,
keywords: 'derives',
contains: [PROBABLY_TYPE],
relevance: 10
}
const CLASS = {
className: 'class',
begin: `((${modifiers}|open|case|transparent) +)*(class|trait|enum|object|package object)`, end: templateDeclEnd,
keywords: withSoftKeywords('open transparent'),
excludeEnd: true,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
TPARAMS,
CTX_PARAMS,
PARAMS,
EXTENDS_PARENT,
WITH_MIXIN,
DERIVES_TYPECLASS,
TITLE,
PROBABLY_TYPE
]
}
// package declaration with a content
const PACKAGE = {
className: 'package',
begin: /package (?=\w+ *[:{\n])/, end: /[:{\n]/,
excludeEnd: true,
keywords: alwaysKeywords,
contains: [
TITLE
]
}
// Case in enum
const ENUM_CASE = {
begin: /case (?!.*=>)/, end: /\n/,
keywords: 'case',
excludeEnd: true,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
PARAMS,
EXTENDS_PARENT,
WITH_MIXIN,
DERIVES_TYPECLASS,
TITLE,
PROBABLY_TYPE
]
}
// Case in pattern matching
const MATCH_CASE = {
begin: /case/, end: /=>|\n/,
keywords: 'case',
excludeEnd: true,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
{
begin: /[@_]/,
keywords: {
$pattern: /[@_]/,
keyword: '@ _'
}
},
NUMBER,
STRING,
PROBABLY_TYPE
]
}
// inline someVar[andMaybeTypeParams] match
const INLINE_MATCH = {
begin: /inline [^\n:]+ match/,
keywords: 'inline match'
}
return {
name: 'Scala3',
aliases: ['scala', 'dotty'],
keywords: alwaysKeywords,
contains: [
NUMBER,
STRING,
SCALADOC,
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
METHOD,
VAL,
TYPEDEF,
PACKAGE,
CLASS,
GIVEN,
EXTENSION,
ANNOTATION,
ENUM_CASE,
MATCH_CASE,
INLINE_MATCH,
END,
APPLY,
PROBABLY_TYPE
]
}
}