Repository navigation
Expand file tree
/
Copy pathtwinbasic.tmLanguage.json
More file actions
306 lines (306 loc) · 14.4 KB
/
Copy pathtwinbasic.tmLanguage.json
File metadata and controls
306 lines (306 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "tb",
"displayName": "twinBASIC",
"scopeName": "source.tb",
"fileTypes": ["twin", "bas", "cls", "frm"],
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#comments" },
{ "include": "#preprocessor" },
{ "include": "#dates" },
{ "include": "#attribute-block" },
{ "include": "#numbers" },
{ "include": "#dotted-name" },
{ "include": "#option-decl" },
{ "include": "#exit-keyword" },
{ "include": "#end-keyword" },
{ "include": "#dim-keyword" },
{ "include": "#funcname-keyword" },
{ "include": "#typename-keyword" },
{ "include": "#namespace-keyword" },
{ "include": "#strings" },
{ "include": "#literals" },
{ "include": "#typed-identifier" },
{ "include": "#types-keyword" },
{ "include": "#operator-words" },
{ "include": "#builtins" },
{ "include": "#keywords" },
{ "include": "#punctuation" },
{ "include": "#operators" },
{ "include": "#identifier" },
{ "include": "#invalid" }
],
"repository": {
"line-continuation": {
"match": "_[ \\t]*$",
"name": "punctuation.line-continuation.tb"
},
"comments": {
"patterns": [
{ "match": "(?i)\\bREM\\b.*$", "name": "comment.line.tb" },
{ "match": "'.*$", "name": "comment.line.tb" },
{
"begin": "/\\*",
"end": "\\*/",
"name": "comment.block.tb"
}
]
},
"preprocessor": {
"patterns": [
{
"match": "(?i)#(?:If|ElseIf)\\b[^\\n]*?\\bThen\\b",
"name": "meta.preprocessor.tb"
},
{ "match": "(?i)#Else\\b", "name": "meta.preprocessor.tb" },
{ "match": "(?i)#End[ \\t]+If\\b", "name": "meta.preprocessor.tb" },
{ "match": "(?i)#Const\\b", "name": "meta.preprocessor.tb" },
{ "match": "(?i)#Region\\b[^\\n]*$", "name": "meta.preprocessor.tb" },
{ "match": "(?i)#End[ \\t]+Region\\b", "name": "meta.preprocessor.tb" }
]
},
"dates": {
"match": "#\\d[^#\\n]*#",
"name": "constant.other.date.tb"
},
"numbers": {
"patterns": [
{
"match": "(?i)(?:\\d+\\.\\d*|\\d*\\.\\d+)(?:e[+-]?\\d+)?[!#@]?",
"name": "constant.numeric.float.tb"
},
{
"match": "(?i)\\d+e[+-]?\\d+[!#@]?",
"name": "constant.numeric.float.tb"
},
{
"match": "(?i)&H[0-9a-f]+(?:_[0-9a-f]+)*[%&!#@]?",
"name": "constant.numeric.integer.tb"
},
{
"match": "(?i)&O[0-7]+(?:_[0-7]+)*[%&!#@]?",
"name": "constant.numeric.integer.tb"
},
{
"match": "(?i)&B[01]+(?:_[01]+)*[%&!#@]?",
"name": "constant.numeric.integer.tb"
},
{
"match": "\\d+[%&!#@]?",
"name": "constant.numeric.integer.tb"
}
]
},
"attribute-block": {
"begin": "\\[",
"end": "\\]",
"beginCaptures": { "0": { "name": "punctuation.tb" } },
"endCaptures": { "0": { "name": "punctuation.tb" } },
"patterns": [
{
"begin": "\\(",
"end": "\\)",
"beginCaptures": { "0": { "name": "punctuation.tb" } },
"endCaptures": { "0": { "name": "punctuation.tb" } },
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#strings" },
{ "include": "#numbers" },
{ "include": "#literals" },
{ "include": "#types-keyword" },
{ "include": "#keywords" },
{ "include": "#operators" },
{ "match": ",", "name": "punctuation.tb" },
{ "match": "(?i)[a-z_]\\w*[%&@!#$]?", "name": "entity.name.tb" }
]
},
{ "match": ",", "name": "punctuation.tb" },
{ "match": "(?i)[a-z_]\\w*", "name": "entity.other.attribute-name.tb" }
]
},
"strings": {
"begin": "\"",
"end": "\"(?!\")",
"name": "string.quoted.double.tb",
"patterns": [
{ "match": "\"\"", "name": "string.escape.tb" }
]
},
"literals": {
"patterns": [
{ "match": "(?i)\\b(?:true|false)\\b", "name": "constant.language.boolean.tb" },
{ "match": "(?i)\\bempty\\b", "name": "constant.language.empty.tb" },
{ "match": "(?i)\\bnothing\\b", "name": "constant.language.nothing.tb" },
{ "match": "(?i)\\bnull\\b", "name": "constant.language.null.tb" }
]
},
"types-keyword": {
"match": "(?i)\\b(?:any|boolean|byte|currency|date|decimal|double|integer|long|longlong|longptr|object|single|string|variant)\\b",
"name": "storage.type.tb"
},
"operator-words": {
"match": "(?i)\\b(?:addressof|and|andalso|as|eqv|imp|is|isnot|like|mod|not|or|orelse|typeof|xor)\\b",
"name": "keyword.operator.word.tb"
},
"builtins": {
"match": "(?i)\\b(?:debug|err)\\b",
"name": "support.function.tb"
},
"option-decl": {
"match": "(?i)\\bOption[ \\t]+(?:Strict|Explicit|Compare|Base)[ \\t]+(?:On|Off|Binary|Text|0|1)\\b",
"name": "keyword.declaration.tb"
},
"exit-keyword": {
"comment": "Rouge matches `Exit Function|Sub|...` as one Keyword token, so we keep the whole phrase in a single scope (no captures) to render as one span.",
"match": "(?i)\\bExit[ \\t]+(?:Function|Sub|Property|For|Do|While)\\b",
"name": "keyword.control.tb"
},
"end-keyword": {
"comment": "Rouge tokenises `End` as one Keyword, then re-enters root, then matches the trailing Sub/Function/... as a separate Keyword in :end state -- so the rendered HTML has TWO adjacent <span class=\"k\"> elements. Split via captures so the same shape lands.",
"match": "(?i)\\b(End)\\b(?:([ \\t]+)(Function|Sub|Property|Class|CoClass|Structure|Enum|Module|Namespace|Type|Interface|Select|If|With)\\b)?",
"captures": {
"1": { "name": "keyword.control.tb" },
"3": { "name": "keyword.control.tb" }
}
},
"dim-keyword": {
"comment": "Declaration list after Dim/Const/ReDim. Unlike Rouge's :dim state (which tagged only the FIRST name as a variable and popped), this stays open to the end of the statement so EVERY name in a comma-separated list becomes variable.tb. The As-clause, initializer, and subscript sub-states each consume up to the next TOP-LEVEL comma (or statement end), so their identifiers -- type names, expression names -- are handled there and never reach the variable-name rule; only names in declaration position (right after the keyword, or after a top-level comma) become variable.tb. `WithEvents` (Dim) and `Preserve` (ReDim) precede the first name and stay keywords. Pops at end of line like Rouge, so a `_`-continued list falls back to root handling on the next line.",
"begin": "(?i)\\b(Dim|Const|ReDim)\\b",
"beginCaptures": { "1": { "name": "keyword.control.tb" } },
"end": "(?i)(?=[':])|$",
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#comments" },
{ "match": "(?i)\\b(?:WithEvents|Preserve)\\b", "name": "keyword.control.tb" },
{ "include": "#dim-as-clause" },
{ "include": "#dim-initializer" },
{ "include": "#dim-parens" },
{ "match": ",", "name": "punctuation.tb" },
{ "match": "(?i)[a-z_]\\w*[%&@!#$]?", "name": "variable.tb" }
]
},
"dim-as-clause": {
"comment": "The `As [New] type` part of one declarator. Ends before the next top-level comma, an `=` initializer, a comment, a `:` statement separator, or end of line. Intrinsic types render as storage.type via #types-keyword; a user-defined or dotted type falls through to #identifier / #dotted-name as entity.name.tb, matching root. #dim-parens covers a `New Type(args)` argument list; a fixed-length `String * n` uses #operators + #numbers.",
"begin": "(?i)\\b(As)\\b",
"beginCaptures": { "1": { "name": "keyword.operator.word.tb" } },
"end": "(?i)(?=[,':=])|$",
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#comments" },
{ "match": "(?i)\\b(?:New)\\b", "name": "keyword.control.tb" },
{ "include": "#dim-parens" },
{ "include": "#strings" },
{ "include": "#numbers" },
{ "include": "#dotted-name" },
{ "include": "#types-keyword" },
{ "include": "#operators" },
{ "include": "#identifier" }
]
},
"dim-initializer": {
"comment": "The `= expression` part of one declarator (a twinBASIC inline initialiser). Ends before the next top-level comma, a comment, a `:` statement separator, or end of line. The expression body reuses #dim-expr-inner, so identifiers inside are entity.name.tb -- never variable.tb.",
"begin": "=",
"beginCaptures": { "0": { "name": "keyword.operator.tb" } },
"end": "(?i)(?=[,':])|$",
"patterns": [
{ "include": "#dim-expr-inner" }
]
},
"dim-parens": {
"comment": "A parenthesised group inside a declaration -- array subscripts `(1 To 10, 2 To 5)` or constructor arguments. Balanced across nesting and across newlines (it ends on `)`, not end of line), so an inner comma never leaks out to prematurely end an enclosing As-clause or initializer.",
"begin": "\\(",
"end": "\\)",
"beginCaptures": { "0": { "name": "punctuation.tb" } },
"endCaptures": { "0": { "name": "punctuation.tb" } },
"patterns": [
{ "include": "#dim-expr-inner" }
]
},
"dim-expr-inner": {
"comment": "Expression content inside a subscript group or initialiser -- the same rule tail as the grammar root, minus the declaration-oriented rules (no dim/funcname/typename/variable-name handling). Identifiers here are ordinary names (entity.name.tb).",
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#comments" },
{ "include": "#dates" },
{ "include": "#numbers" },
{ "include": "#dotted-name" },
{ "include": "#strings" },
{ "include": "#literals" },
{ "include": "#typed-identifier" },
{ "include": "#types-keyword" },
{ "include": "#operator-words" },
{ "include": "#builtins" },
{ "include": "#keywords" },
{ "include": "#dim-parens" },
{ "include": "#punctuation" },
{ "include": "#operators" },
{ "include": "#identifier" }
]
},
"funcname-keyword": {
"comment": "Begin/end so the whitespace between Function/Sub/Property and the function name can span lines via a `_` line continuation. Mirrors Rouge's :funcname state. The end pattern includes `$` so the region also terminates at end-of-line when no continuation is seen -- without it the region would span newlines unconditionally and capture the next line's first identifier as a function name (Rouge's :funcname pops at end-of-line, so we must too).",
"begin": "(?i)\\b(Function|Sub|Property)\\b",
"beginCaptures": { "1": { "name": "keyword.control.tb" } },
"end": "(?i)([a-z_]\\w*[%&@!#$]?)|(?=\\S)|$",
"endCaptures": { "1": { "name": "entity.name.function.tb" } },
"patterns": [
{ "include": "#line-continuation" },
{ "include": "#comments" }
]
},
"typename-keyword": {
"comment": "Rouge's :typename state matches a SINGLE identifier and tokens it as Name::Class. The Extends/As continuation (Rouge's :typename_ext) starts a fresh :typename for the next single identifier. A dotted continuation (`stdole.IUnknown`) leaves the typename state after `stdole`; `.IUnknown` then falls through to the dotted-name rule in :root. Stays as a one-line `match` rule because the source convention always puts the typename on the same line as the keyword.",
"match": "(?i)\\b(Alias|Class|CoClass|Structure|Enum|Type|Interface)\\b([ \\t]+)([a-z_]\\w*)(?:([ \\t]+)(Extends|As)([ \\t]+)([a-z_]\\w*))?",
"captures": {
"1": { "name": "keyword.control.tb" },
"3": { "name": "entity.name.type.tb" },
"5": { "name": "keyword.control.tb" },
"7": { "name": "entity.name.type.tb" }
}
},
"namespace-keyword": {
"match": "(?i)\\b(Module|Namespace|Imports)\\b([ \\t]+)([a-z_]\\w*(?:\\.[a-z_]\\w*)*)",
"captures": {
"1": { "name": "keyword.control.tb" },
"3": { "name": "entity.name.namespace.tb" }
}
},
"keywords": {
"match": "(?i)\\b(?:alias|byref|byval|call|case|class|close|coclass|const|continue|declare|default|delegate|dim|do|each|else|elseif|end|endif|enum|erase|error|event|exit|extends|finally|for|friend|function|get|global|gosub|goto|handles|if|implements|imports|inherits|input|interface|let|lib|line|lock|loop|me|mid|module|namespace|new|next|of|on|open|option|optional|overloads|paramarray|preserve|print|private|property|public|put|raiseevent|redim|resume|return|select|set|shared|static|step|stop|structure|sub|then|throw|to|try|type|unlock|until|using|wend|when|while|width|with|withevents|write)\\b",
"name": "keyword.control.tb"
},
"operators": {
"comment": "`:=` is intentionally NOT here -- Rouge tries punctuation first (which matches `:` from the [(){}!#,;:] char class) and then the standalone `=` matches the operator char class. Mirror that by leaving `:=` for the punctuation+operator split.",
"match": "&=|\\*=|/=|\\\\=|\\^=|\\+=|-=|<<=|>>=|<<|>>|<=|>=|<>|[-&*/\\\\^+=<>]",
"name": "keyword.operator.tb"
},
"punctuation": {
"patterns": [
{ "match": "\\.", "name": "punctuation.tb" },
{ "match": "[(){}!#,;:]", "name": "punctuation.tb" }
]
},
"dotted-name": {
"comment": "After `.` the next identifier is always a member name (Rouge's :dotted state, which mixes in :whitespace before matching the identifier so a gap between `.` and the name still counts). Case-insensitive so the leading letter doesn't have to be lowercase.",
"match": "(?i)(\\.)([ \\t]*)([a-z_]\\w*[%&@!#$]?)",
"captures": {
"1": { "name": "punctuation.tb" },
"3": { "name": "entity.name.tb" }
}
},
"typed-identifier": {
"match": "(?i)[a-z_]\\w*[%&@!#$]",
"name": "entity.name.tb"
},
"identifier": {
"match": "(?i)[a-z_]\\w*",
"name": "entity.name.tb"
},
"invalid": {
"comment": "Anything not matched by any other rule (stray backticks, non-ASCII letters like 'À', etc.) becomes Rouge's Error token. Single-char to avoid swallowing whitespace.",
"match": "[^\\s]",
"name": "invalid.illegal.tb"
}
}
}