I've finished a basic TMLanguage grammar for AS3 only (testing it in Visual Studio Code), but I'm getting a weird highlighting with the snippet:
reviver = JSBridge.toJSFunction("");
For some reason, it does not highlight the string literal inside the call. But, if I do
reviver = Aaa.aaaaaaaaaaaaaaaaa("");
It works. Comparison:
It does not happen only with string literal; it happens with function expression and anything else.
Related parts of my grammar:
# repository: directive (merges directives + statements + expressions)
# Parenthesized
- begin: \(
end: \)
name: expression.group
patterns:
- include: '#directive'
# String literal
- begin: \@"""
end: \"""
name: string.quoted.triple
- begin: \@'''
end: \'''
name: string.quoted.triple
- begin: \"""
end: \"""
name: string.quoted.triple
patterns:
- include: '#stringLiteral'
- begin: \'''
end: \'''
name: string.quoted.triple
patterns:
- include: '#stringLiteral'
- begin: \@"
end: \"
name: string.quoted.double
- begin: \@'
end: \'
name: string.quoted.single
- begin: \"
end: \"
name: string.quoted.double
patterns:
- include: '#stringLiteral'
- begin: \'
end: \'
name: string.quoted.single
patterns:
- include: '#stringLiteral'
# Dot handling
- match: (\.)\s*({{id}})
captures:
1: { name: punctuation }
- begin: (\.)\s*(\<)
end: \>
patterns:
- include: '#directive'
- match: \.\.
name: keyword.operator
- match: \.
name: punctuation
Full grammar:
---
$schema: https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json
name: ActionScript 3
scopeName: source.as3
variables:
unicodeEscape: >-
(?:(?x) \\u((\{[A-Fa-f0-9]+\})|[A-Fa-f0-9]{4}))
idStart: >-
(?:(?x) [\p{L}$_\p{Nl}] | {{unicodeEscape}})
idPart: >-
(?:(?x) [\p{L}$_\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}] | {{unicodeEscape}})
id: >-
(?:(?x) {{idStart}} {{idPart}}* )
xmlNameStart: >-
(?:(?x) [\p{L}:_\p{Nl}])
xmlNamePart: >-
(?:(?x) [\p{L}:.\-_\p{Nl}\p{Nd}])
xmlName: >-
(?:(?x) {{xmlNameStart}} {{xmlNamePart}}*)
# Based on https://github.com/microsoft/TypeScript-TmLanguage
decimalNumber: |-
(?<!\$)(?:(?x)(?:
(?:\b[0-9][0-9_]*(\.)[0-9][0-9_]*[eE][+-]?[0-9][0-9_]*([f|F])?\b)| # 1.1E+3
(?:\b[0-9][0-9_]*(\.)[eE][+-]?[0-9][0-9_]*([f|F])?\b)| # 1.E+3
(?:\B(\.)[0-9][0-9_]*[eE][+-]?[0-9][0-9_]*([f|F])?\b)| # .1E+3
(?:\b[0-9][0-9_]*[eE][+-]?[0-9][0-9_]*([f|F])?\b)| # 1E+3
(?:\b[0-9][0-9_]*(\.)[0-9][0-9_]*([f|F])?\b)| # 1.1
(?:\b[0-9][0-9_]*(\.)([f|F])?\B)| # 1.
(?:\B(\.)[0-9][0-9_]*([f|F])?\b)| # .1
(?:\b[0-9][0-9_]*([f|F])?\b(?!\.)) # 1
))(?!\$)
hexLiteral: |-
\b0[Xx][A-Fa-f0-9_]+
binLiteral: |-
\b0[Bb][01_]+
keywordAttribute: >-
(?:(?x) public | private | protected | internal | final | static | dynamic | override | abstract)
definitionKeyword: >-
class|enum|interface|type|namespace|var|const|function
definitionKeywordStop: >-
class|enum|interface|type|namespace|var|const|function|extends|implements|default\s+xml\s+namespace|new|delete|void|typeof|await|yield|instanceof|not\s+in|in|is(\s+not)?|as|if|else|for\s+each|for|return|throw|switch\s+type|switch|case|try|catch|finally
regexOrXMLStartCondition: >-
(?<=^\s*|[:=,;(\[{+\-*/%~&\^|<>]\s*)
patterns:
- include: '#directive'
repository:
directive:
patterns:
- include: '#comment'
# Package definition start
- match: (?x) \b(package) \s* ({{idStart}}{{idPart}}* \s* (\s*\.\s*{{idStart}}{{idPart}}*)*)\b
captures:
1: { name: keyword.other }
2: { name: entity.name.other }
# Package definition start
- match: (?x) \b(package)\b
captures:
1: { name: keyword.other }
# Labeled statement starting as in `labelname:`
- match: >-
(?x) \b({{id}})\s*:(?!:)
captures:
1: { name: entity.name.label }
# Block
- begin: \{
end: \}
beginCaptures:
0: { name: punctuation.definition.block }
endCaptures:
0: { name: punctuation.definition.block }
patterns:
- include: '#directive'
# Annotatable definition attributes
- match: >-
(?x) \b ( (?:\s*(?!{{definitionKeywordStop}}\b){{id}}(?:\s*\.\s*{{id}})*)+ ) \s* (?={{definitionKeyword}})\b
captures:
1: { name: keyword.other }
# Getter or setter
- begin: >-
(?x) \b(function) \s+ (get|set) \s+ ({{id}}) \s* \(
end: \)
captures:
1: { name: keyword.other }
2: { name: keyword.other }
3: { name: entity.name.function }
patterns:
- include: '#parameterList'
# Class
- match: >-
(?x) \b(class) \s+ ({{id}})
captures:
1: { name: keyword.other }
2: { name: entity.name.type.class }
- match: >-
(?x) \b(class)\b
captures:
1: { name: keyword.other }
# Enum
- match: >-
(?x) \b(enum) \s+ ({{id}})
captures:
1: { name: keyword.other }
2: { name: entity.name.type.enum }
# Interface
- match: >-
(?x) \b(interface) \s+ ({{id}})
captures:
1: { name: keyword.other }
2: { name: entity.name.type.interface }
- match: >-
(?x) \b(interface)\b
captures:
1: { name: keyword.other }
# Type
- match: >-
(?x) \b(type) \s+ ({{id}})
captures:
1: { name: keyword.other }
2: { name: entity.name.type }
# Namespace
- match: >-
(?x) \b(namespace) \s+ ({{id}})
captures:
1: { name: keyword.other }
2: { name: entity.name.other }
# `extends` and `implements` keywords
- match: \b(extends|implements)\b
name: keyword.other
# `return` followed by regex
- begin: \b(return)\s*(/)(?![/*])
end: >-
(?x) /([A-Za-z]+\b)?
name: string.regexp
beginCaptures:
1: { name: keyword.control }
patterns:
- include: '#regex'
# `return` followed by XML
- begin: \b(return)\s*(<)({{xmlName}})
end: >-
(?x) (/>) | ( (</) \s* ({{xmlName}}) \s* (>) )
beginCaptures:
1: { name: keyword.control }
2: { name: punctuation.definition.tag.xml }
3: { name: entity.name.tag }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
4: { name: entity.name.tag }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# `return` followed by XML containing interpolated tag names
- begin: \b(return)\s*(<)(\{[^}]*\})
end: >-
(?x) (/>) | ( (</) \s* (\{[^}]*\}) \s* (>) )
beginCaptures:
1: { name: keyword.control }
2: { name: punctuation.definition.tag.xml }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# `break` and `continue`
- match: \b(break|continue)(\s+{{id}})?
captures:
1: { name: keyword.control }
2: { name: entity.name.label }
# Various control keywords
- match: \b(if|else|for\s+each|for|return|throw|switch\s+type|switch|case|try|catch|finally)\b
name: keyword.control
# with
- match: \b(with)\b
name: keyword.other
# default xml namespace =
- match: \b(default\s+xml\s+namespace)\s*(=)
captures:
1: { name: keyword.other }
# import or include
- match: \b(import|include)\b
name: keyword.other
# Variable definition (object destructuring)
- begin: >-
(?x) \b (var|const) \s* \{
end: \}(\s*!)?
captures:
1: { name: keyword.other }
patterns:
- include: '#object'
# Variable definition (array destructuring)
- begin: >-
(?x) \b (var|const) \s* \[
end: \](\s*!)?
captures:
1: { name: keyword.other }
patterns:
- include: '#array'
# Variable definition (non destructuring)
- match: >-
(?x) \b (var|const) \s+ ({{id}})\b
captures:
1: { name: keyword.other }
2: { name: variable }
- match: >-
(?x) \b(var|const)\b
captures:
1: { name: keyword.other }
# Function
- begin: >-
(?x) \b(function) \s+ ({{id}}) \s* \(
end: \)
captures:
1: { name: keyword.other }
2: { name: entity.name.function }
patterns:
- include: '#parameterList'
- begin: >-
(?x) \b(function) \s* \(
end: \)
captures:
1: { name: keyword.other }
patterns:
- include: '#parameterList'
- match: >-
(?x) \b(function)\b
name: keyword.other
# Reserved namespaces
- match: \b(public|private|protected|internal)\b
name: keyword.other
- match: \b(null|false|true)\b
name: constant.language
- match: \b(this|super)\b
name: variable.language
- match: \b(import\s*\.\s*meta)\b
name: variable.language
- begin: \b(new)\s*\<
end: \>
beginCaptures:
1: { name: keyword.other }
patterns:
- include: '#directive'
- match: \b(new|delete|void|typeof|await|yield|instanceof|not\s+in|in|is(\s+not)?|as)\b
name: keyword.other
- match: \b(public|private|protected|internal)\b
name: constant.language
- match: (::)\s*({{id}})
captures:
1: { name: keyword.operator }
- match: |-
::
name: keyword.operator
- match: ({{id}})
- match: |-
{{decimalNumber}}
name: constant.numeric
- match: |-
{{hexLiteral}}
name: constant.numeric
- match: |-
{{binLiteral}}
name: constant.numeric
- begin: \(
end: \)
name: expression.group
patterns:
- include: '#directive'
# String literal
- begin: \@"""
end: \"""
name: string.quoted.triple
- begin: \@'''
end: \'''
name: string.quoted.triple
- begin: \"""
end: \"""
name: string.quoted.triple
patterns:
- include: '#stringLiteral'
- begin: \'''
end: \'''
name: string.quoted.triple
patterns:
- include: '#stringLiteral'
- begin: \@"
end: \"
name: string.quoted.double
- begin: \@'
end: \'
name: string.quoted.single
- begin: \"
end: \"
name: string.quoted.double
patterns:
- include: '#stringLiteral'
- begin: \'
end: \'
name: string.quoted.single
patterns:
- include: '#stringLiteral'
# RegExp
- begin: >-
(?x) {{regexOrXMLStartCondition}} (/)(?![/*])
end: >-
(?x) /([A-Za-z]+\b)?
name: string.regexp
patterns:
- include: '#regex'
# XML tag
- begin: >-
(?x) {{regexOrXMLStartCondition}} (<)({{xmlName}})
end: >-
(?x) (/>) | ( (</) \s* ({{xmlName}}) \s* (>) )
beginCaptures:
1: { name: punctuation.definition.tag.xml }
2: { name: entity.name.tag }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
4: { name: entity.name.tag }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# XML tag containing interpolated tag names
- begin: >-
(?x) {{regexOrXMLStartCondition}} (<)(\{[^}]*\})
end: >-
(?x) (/>) | ( (</) \s* (\{[^}]*\}) \s* (>) )
beginCaptures:
1: { name: punctuation.definition.tag.xml }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# XMLCDATA
- begin: \<\!\[CDATA\[
end: \]\]>
name: string.other
# XMLComment
- begin: \<\!\-\-
end: \-\->
name: comment.block
# XMLPI
- begin: \<\?
end: \?>
name: comment.block
# XMLList
- begin: >-
(?x) (<>)
end: >-
(?x) (</>)
beginCaptures:
1: { name: punctuation.definition.tag.xml }
endCaptures:
1: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlContent'
- match: ;
name: punctuation.terminator.statement
- begin: \[
end: \]
patterns:
- include: '#directive'
- begin: \{
end: \}
patterns:
- include: '#object'
- match: \.\.\.
name: punctuation
- match: >-
(?x) \, | \:
name: punctuation
- match: (\.)\s*({{id}})
captures:
1: { name: punctuation }
- begin: (\.)\s*(\<)
end: \>
patterns:
- include: '#directive'
- match: \.\.
name: keyword.operator
- match: \.
name: punctuation
- match: \@
name: keyword.operator
- match: \.|\?
name: punctuation
- match: (\+\+?|\-\-?|!(==?)?|~|\*\*?|/|%|<=|>=|\<\<?|\>(\>\>?)?|=(==?)?|&&?|\^\^?|\|\|?)
name: keyword.operator
parameterList:
patterns:
- match: >-
(?x) (?<=[,(] \s*) ({{id}})
captures:
1: { name: variable.parameter }
- include: '#directive'
object:
patterns:
- begin: >-
(?x) {{id}} \s* :
end: >-
(?x) [,}]
patterns:
- include: '#directive'
# Shorthand
- match: >-
(?x) ({{id}})
captures:
1: { name: variable }
- include: '#directive'
array:
patterns:
- include: '#directive'
regex:
patterns:
- match: >-
(?x) \\.
xmlTag:
patterns:
- begin: >-
(?x) (?<! </ \s* {{xmlName}} \s*) (>)
beginCaptures:
1: { name: punctuation.definition.tag.xml }
end: (?=\</)
patterns:
- include: '#xmlContent'
- begin: \"
end: \"
name: string.quoted.double
- begin: \'
end: \'
name: string.quoted.single
- match: \{\{xmlName}}
name: entity.name.other.attribute-name
# Interpolation
- begin: \{
end: \}
patterns:
- include: '#directive'
xmlContent:
patterns:
- begin: \<\!\[CDATA\[
end: \]\]>
name: string.other
- begin: \<\!\-\-
end: \-\->
name: comment.block
- begin: \<\?
end: \?>
name: comment.block
- begin: (<)({{xmlName}})
end: >-
(?x) (/>) | ( (</) \s* ({{xmlName}}) \s* (>) )
beginCaptures:
1: { name: punctuation.definition.tag.xml }
2: { name: entity.name.tag }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
4: { name: entity.name.tag }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# XML tag containing interpolated tag names
- begin: (<)(\{[^}]*\})
end: >-
(?x) (/>) | ( (</) \s* (\{[^}]*\}) \s* (>) )
beginCaptures:
1: { name: punctuation.definition.tag.xml }
endCaptures:
1: { name: punctuation.definition.tag.xml }
3: { name: punctuation.definition.tag.xml }
5: { name: punctuation.definition.tag.xml }
patterns:
- include: '#xmlTag'
# Interpolation
- begin: \{
end: \}
patterns:
- include: '#directive'
stringLiteral:
patterns:
- match: \\x[A-Fa-f0-9]{2}
name: constant.character.escape
- match: \\u\{[A-Fa-f0-9]*\}
name: constant.character.escape
- match: \\u[A-Fa-f0-9]{4}
name: constant.character.escape
- match: \\['"\\bfnrtv]
name: constant.character.escape
- match: \\[^0-9]
name: constant.character.escape
- match: \\$
name: constant.character.escape
comment:
patterns:
- match: >-
(?x) (//)(.*)
name: comment.line
captures:
1: { name: punctuation.definition.comment }
- begin: (/\*)
beginCaptures:
1: { name: punctuation.definition.comment }
end: (\*/)
endCaptures:
1: { name: punctuation.definition.comment }
name: comment.block
Your 6th item in directive
causes Catastrophic backtracking
# Annotatable definition attributes
resulting in TextMate giving up on the rest of the line
\b((?:\s*(?!class|enum|interface|type|namespace|var|const|function|extends|implements|default\s+xml\s+namespace|new|delete|void|typeof|await|yield|instanceof|not\s+in|in|is(\s+not)?|as|if|else|for\s+each|for|return|throw|switch\s+type|switch|case|try|catch|finally\b)(?:[\p{L}$_\p{Nl}]|\\u((\{[A-Fa-f0-9]+\})|[A-Fa-f0-9]{4}))(?:[\p{L}$_\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]|\\u((\{[A-Fa-f0-9]+\})|[A-Fa-f0-9]{4}))*(?:\s*\.\s*(?:[\p{L}$_\p{Nl}]|\\u((\{[A-Fa-f0-9]+\})|[A-Fa-f0-9]{4}))(?:[\p{L}$_\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}]|\\u((\{[A-Fa-f0-9]+\})|[A-Fa-f0-9]{4}))*)*)+)\s*(?=class|enum|interface|type|namespace|var|const|function)\b
I would recommend using atomic groups (?>...)
and possessive quantifiers ?+
, *+
& ++
where possible
This will disable backtracking
Just remember it can be a double edge sword if you've set up your regex in a way that requires backtracking for it to work