reslop 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,228 @@
1
+ 'use strict';
2
+
3
+ const { SQUOTE, emit, wordStyle, readUntilNl } = require('./core.js');
4
+
5
+ const langs = ['bash'];
6
+
7
+ const aliases = {
8
+ sh: 'bash',
9
+ shell: 'bash',
10
+ zsh: 'bash',
11
+ };
12
+
13
+ const BASH_QUOTE = `${SQUOTE}\``;
14
+
15
+ const KEYWORDS = [
16
+ 'if',
17
+ 'then',
18
+ 'else',
19
+ 'elif',
20
+ 'fi',
21
+ 'for',
22
+ 'while',
23
+ 'until',
24
+ 'do',
25
+ 'done',
26
+ 'case',
27
+ 'esac',
28
+ 'function',
29
+ 'select',
30
+ 'in',
31
+ 'time',
32
+ 'coproc',
33
+ ];
34
+
35
+ const BUILTINS = [
36
+ 'echo',
37
+ 'printf',
38
+ 'cd',
39
+ 'pwd',
40
+ 'exit',
41
+ 'export',
42
+ 'source',
43
+ 'alias',
44
+ 'unalias',
45
+ 'local',
46
+ 'declare',
47
+ 'typeset',
48
+ 'readonly',
49
+ 'unset',
50
+ 'shift',
51
+ 'read',
52
+ 'test',
53
+ 'true',
54
+ 'false',
55
+ 'exec',
56
+ 'eval',
57
+ 'set',
58
+ 'trap',
59
+ 'wait',
60
+ 'kill',
61
+ 'type',
62
+ 'command',
63
+ 'builtin',
64
+ 'return',
65
+ 'break',
66
+ 'continue',
67
+ 'pushd',
68
+ 'popd',
69
+ 'dirs',
70
+ 'getopts',
71
+ 'mapfile',
72
+ 'readarray',
73
+ 'let',
74
+ 'umask',
75
+ 'ulimit',
76
+ 'fg',
77
+ 'bg',
78
+ 'jobs',
79
+ 'hash',
80
+ 'help',
81
+ 'enable',
82
+ 'caller',
83
+ 'bind',
84
+ 'compgen',
85
+ 'complete',
86
+ ];
87
+
88
+ const WORD_STYLE = {
89
+ ...wordStyle(KEYWORDS, 'control'),
90
+ ...wordStyle(BUILTINS, 'function'),
91
+ };
92
+
93
+ const readBashVar = (source, i) => {
94
+ if (source[i + 1] === '{') {
95
+ let j = i + 2;
96
+ while (j < source.length && source[j] !== '}') j += 1;
97
+ if (j < source.length) j += 1;
98
+ return { text: source.slice(i, j), end: j };
99
+ }
100
+ if (/[0-9#?$!*@-]/.test(source[i + 1] || '')) {
101
+ return { text: source.slice(i, i + 2), end: i + 2 };
102
+ }
103
+ let j = i + 1;
104
+ while (j < source.length && /[A-Za-z0-9_]/.test(source[j])) j += 1;
105
+ if (j === i + 1) return { text: '$', end: i + 1 };
106
+ return { text: source.slice(i, j), end: j };
107
+ };
108
+
109
+ const takeQuoted = (source, i) => {
110
+ const q = source[i];
111
+ let j = i + 1;
112
+ const n = source.length;
113
+ while (j < n && source[j] !== q) j += 1;
114
+ if (j < n) j += 1;
115
+ return { text: source.slice(i, j), end: j };
116
+ };
117
+
118
+ const takeBashDouble = (source, i, out) => {
119
+ let j = i + 1;
120
+ let chunk = '"';
121
+ const n = source.length;
122
+ while (j < n && source[j] !== '"') {
123
+ if (source[j] === '\\' && j + 1 < n) {
124
+ chunk += source.slice(j, j + 2);
125
+ j += 2;
126
+ continue;
127
+ }
128
+ if (source[j] === '$') {
129
+ emit(out, 'string', chunk);
130
+ chunk = '';
131
+ const { text, end } = readBashVar(source, j);
132
+ emit(out, 'variable', text);
133
+ j = end;
134
+ continue;
135
+ }
136
+ chunk += source[j];
137
+ j += 1;
138
+ }
139
+ if (j < n) {
140
+ chunk += '"';
141
+ j += 1;
142
+ }
143
+ if (chunk) emit(out, 'string', chunk);
144
+ return j;
145
+ };
146
+
147
+ const bashWordStyle = (word, nextCh) => {
148
+ const known = WORD_STYLE[word];
149
+ if (known) return known;
150
+ if (nextCh === '=') return 'variable';
151
+ return 'plain';
152
+ };
153
+
154
+ const bashOpLen = (ch, next) => ('|&><'.includes(ch) && next === ch ? 2 : 1);
155
+
156
+ const highlight = (source) => {
157
+ const out = [];
158
+ let i = 0;
159
+ const n = source.length;
160
+ while (i < n) {
161
+ const ch = source[i];
162
+ const next = source[i + 1];
163
+
164
+ if (ch === '#' && (i === 0 || /\s/.test(source[i - 1]))) {
165
+ const { text, end } = readUntilNl(source, i);
166
+ emit(out, 'comment', text);
167
+ i = end;
168
+ continue;
169
+ }
170
+
171
+ if (BASH_QUOTE.includes(ch)) {
172
+ const { text, end } = takeQuoted(source, i);
173
+ emit(out, 'string', text);
174
+ i = end;
175
+ continue;
176
+ }
177
+
178
+ if (ch === '"') {
179
+ i = takeBashDouble(source, i, out);
180
+ continue;
181
+ }
182
+
183
+ if (ch === '$') {
184
+ const { text, end } = readBashVar(source, i);
185
+ emit(out, 'variable', text);
186
+ i = end;
187
+ continue;
188
+ }
189
+
190
+ if (ch === '-' && /[A-Za-z0-9]/.test(next || '')) {
191
+ let j = i + 1;
192
+ while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
193
+ emit(out, 'attr', source.slice(i, j));
194
+ i = j;
195
+ continue;
196
+ }
197
+
198
+ if (/[A-Za-z_]/.test(ch)) {
199
+ let j = i + 1;
200
+ while (j < n && /[A-Za-z0-9_]/.test(source[j])) j += 1;
201
+ const word = source.slice(i, j);
202
+ emit(out, bashWordStyle(word, source[j]), word);
203
+ i = j;
204
+ continue;
205
+ }
206
+
207
+ if (/[0-9]/.test(ch)) {
208
+ let j = i + 1;
209
+ while (j < n && /[0-9]/.test(source[j])) j += 1;
210
+ emit(out, 'number', source.slice(i, j));
211
+ i = j;
212
+ continue;
213
+ }
214
+
215
+ if ('|&;<>(){}'.includes(ch)) {
216
+ const j = i + bashOpLen(ch, next);
217
+ emit(out, 'operator', source.slice(i, j));
218
+ i = j;
219
+ continue;
220
+ }
221
+
222
+ emit(out, 'plain', ch);
223
+ i += 1;
224
+ }
225
+ return out;
226
+ };
227
+
228
+ module.exports = { langs, aliases, highlight };
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ const SQUOTE = '\x27';
4
+ const QUOTES = `${SQUOTE}"`;
5
+ const ANY_QUOTE = `${QUOTES}\``;
6
+
7
+ const OP3 = ['===', '!==', '>>>', '**=', '&&=', '||=', '??='];
8
+
9
+ const OP2 = [
10
+ '==',
11
+ '!=',
12
+ '<=',
13
+ '>=',
14
+ '&&',
15
+ '||',
16
+ '??',
17
+ '=>',
18
+ '++',
19
+ '--',
20
+ '<<',
21
+ '>>',
22
+ '**',
23
+ '+=',
24
+ '-=',
25
+ '*=',
26
+ '/=',
27
+ '%=',
28
+ '&=',
29
+ '|=',
30
+ '^=',
31
+ ];
32
+
33
+ const EXPECT_STYLE = {
34
+ function: 'function',
35
+ class: 'className',
36
+ type: 'className',
37
+ };
38
+
39
+ const emit = (out, style, text) => {
40
+ if (!text) return;
41
+ out.push({ text, style: style || 'plain' });
42
+ };
43
+
44
+ const tokensText = (out) => {
45
+ let s = '';
46
+ for (const t of out) s += t.text;
47
+ return s;
48
+ };
49
+
50
+ const wordStyle = (words, style) => {
51
+ const table = {};
52
+ for (const word of words) table[word] = style;
53
+ return table;
54
+ };
55
+
56
+ const peekIdent = (source, i) => {
57
+ if (!/[A-Za-z_$]/.test(source[i] || '')) return null;
58
+ let j = i + 1;
59
+ while (j < source.length && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
60
+ return source.slice(i, j);
61
+ };
62
+
63
+ const skipWs = (source, i) => {
64
+ while (i < source.length && /\s/.test(source[i])) i += 1;
65
+ return i;
66
+ };
67
+
68
+ const readUntilNl = (source, i) => {
69
+ let j = i;
70
+ const n = source.length;
71
+ while (j < n && source[j] !== '\n') j += 1;
72
+ return { text: source.slice(i, j), end: j };
73
+ };
74
+
75
+ const readBlockComment = (source, i) => {
76
+ let j = i + 2;
77
+ const n = source.length;
78
+ while (j < n && !(source[j] === '*' && source[j + 1] === '/')) {
79
+ j += 1;
80
+ }
81
+ j = Math.min(j + 2, n);
82
+ return { text: source.slice(i, j), end: j };
83
+ };
84
+
85
+ const skipQuoted = (source, j) => {
86
+ const q = source[j];
87
+ j += 1;
88
+ const n = source.length;
89
+ while (j < n && source[j] !== q) {
90
+ if (source[j] === '\\') j += 1;
91
+ j += 1;
92
+ }
93
+ j += 1;
94
+ return j;
95
+ };
96
+
97
+ const readJsNumber = (source, i) => {
98
+ const n = source.length;
99
+ let j = i;
100
+ if (source.startsWith('0x', i) || source.startsWith('0X', i)) {
101
+ j += 2;
102
+ while (j < n && /[0-9a-fA-F_]/.test(source[j])) j += 1;
103
+ } else if (source.startsWith('0b', i) || source.startsWith('0B', i)) {
104
+ j += 2;
105
+ while (j < n && /[01_]/.test(source[j])) j += 1;
106
+ } else if (source.startsWith('0o', i) || source.startsWith('0O', i)) {
107
+ j += 2;
108
+ while (j < n && /[0-7_]/.test(source[j])) j += 1;
109
+ } else {
110
+ while (j < n && /[0-9_n.]/.test(source[j])) j += 1;
111
+ if ('eE'.includes(source[j])) {
112
+ j += 1;
113
+ if ('+-'.includes(source[j])) j += 1;
114
+ while (j < n && /[0-9_]/.test(source[j])) j += 1;
115
+ }
116
+ }
117
+ return { text: source.slice(i, j), end: j };
118
+ };
119
+
120
+ const readJsOperator = (source, i) => {
121
+ const two = source.slice(i, i + 2);
122
+ const three = source.slice(i, i + 3);
123
+ let j = i + 1;
124
+ if (OP3.includes(three)) j = i + 3;
125
+ else if (OP2.includes(two)) j = i + 2;
126
+ return { text: source.slice(i, j), end: j };
127
+ };
128
+
129
+ const readString = (source, i) => {
130
+ const q = source[i];
131
+ let j = i + 1;
132
+ const n = source.length;
133
+ while (j < n) {
134
+ if (source[j] === '\\') {
135
+ j += 2;
136
+ continue;
137
+ }
138
+ if (source[j] === q) {
139
+ j += 1;
140
+ break;
141
+ }
142
+ if (source[j] === '\n') break;
143
+ j += 1;
144
+ }
145
+ return { text: source.slice(i, j), end: j };
146
+ };
147
+
148
+ const readTemplateInterp = (source, j) => {
149
+ let depth = 1;
150
+ const start = j;
151
+ const n = source.length;
152
+ while (j < n && depth > 0) {
153
+ if (ANY_QUOTE.includes(source[j])) {
154
+ j = skipQuoted(source, j);
155
+ continue;
156
+ }
157
+ if (source[j] === '{') depth += 1;
158
+ if (source[j] === '}') {
159
+ depth -= 1;
160
+ if (depth === 0) break;
161
+ }
162
+ j += 1;
163
+ }
164
+ return { expr: source.slice(start, j), end: j };
165
+ };
166
+
167
+ const styleConstant = ({ word }) =>
168
+ /^[A-Z][A-Z0-9_]+$/.test(word) ? 'constant' : null;
169
+
170
+ const stylePascal = ({ word }) => (/^[A-Z]/.test(word) ? 'className' : null);
171
+
172
+ const styleCall = ({ after }) => (after === '(' ? 'function' : null);
173
+
174
+ module.exports = {
175
+ SQUOTE,
176
+ QUOTES,
177
+ ANY_QUOTE,
178
+ EXPECT_STYLE,
179
+ emit,
180
+ tokensText,
181
+ wordStyle,
182
+ peekIdent,
183
+ skipWs,
184
+ readUntilNl,
185
+ readBlockComment,
186
+ skipQuoted,
187
+ readJsNumber,
188
+ readJsOperator,
189
+ readString,
190
+ readTemplateInterp,
191
+ styleConstant,
192
+ stylePascal,
193
+ styleCall,
194
+ };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const { QUOTES, emit, readBlockComment, readString } = require('./core.js');
4
+
5
+ const langs = ['css'];
6
+
7
+ const CSS_NEXT_STYLE = {
8
+ ':': 'property',
9
+ '{': 'tag',
10
+ };
11
+
12
+ const cssWordStyle = (next) => CSS_NEXT_STYLE[next] ?? 'plain';
13
+
14
+ const highlight = (source) => {
15
+ const out = [];
16
+ let i = 0;
17
+ const n = source.length;
18
+ while (i < n) {
19
+ const ch = source[i];
20
+ const next = source[i + 1];
21
+ if (ch === '/' && next === '*') {
22
+ const { text, end } = readBlockComment(source, i);
23
+ emit(out, 'comment', text);
24
+ i = end;
25
+ continue;
26
+ }
27
+ if (QUOTES.includes(ch)) {
28
+ const { text, end } = readString(source, i);
29
+ emit(out, 'string', text);
30
+ i = end;
31
+ continue;
32
+ }
33
+ if (ch === '@') {
34
+ let j = i + 1;
35
+ while (j < n && /[A-Za-z0-9-]/.test(source[j])) j += 1;
36
+ emit(out, 'keyword', source.slice(i, j));
37
+ i = j;
38
+ continue;
39
+ }
40
+ if ('#.'.includes(ch)) {
41
+ let j = i + 1;
42
+ while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
43
+ const kind = ch === '#' ? 'constant' : 'className';
44
+ emit(out, kind, source.slice(i, j));
45
+ i = j;
46
+ continue;
47
+ }
48
+ if (/[0-9]/.test(ch)) {
49
+ let j = i;
50
+ while (j < n && /[0-9.%]/.test(source[j])) j += 1;
51
+ emit(out, 'number', source.slice(i, j));
52
+ i = j;
53
+ continue;
54
+ }
55
+ if (/[A-Za-z_-]/.test(ch)) {
56
+ let j = i + 1;
57
+ while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
58
+ const word = source.slice(i, j);
59
+ let k = j;
60
+ while (k < n && /\s/.test(source[k])) k += 1;
61
+ emit(out, cssWordStyle(source[k]), word);
62
+ i = j;
63
+ continue;
64
+ }
65
+ if (/[{};:,]/.test(ch)) {
66
+ emit(out, 'punct', ch);
67
+ i += 1;
68
+ continue;
69
+ }
70
+ emit(out, 'plain', ch);
71
+ i += 1;
72
+ }
73
+ return out;
74
+ };
75
+
76
+ module.exports = { langs, highlight };
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const { emit } = require('./core.js');
4
+
5
+ const langs = ['csv'];
6
+
7
+ const splitCsv = (line) => {
8
+ const cells = [];
9
+ let cur = '';
10
+ let inQ = false;
11
+ for (let i = 0; i < line.length; i += 1) {
12
+ const ch = line[i];
13
+ if (ch === '"') {
14
+ if (inQ && line[i + 1] === '"') {
15
+ cur += '"';
16
+ i += 1;
17
+ } else {
18
+ inQ = !inQ;
19
+ cur += ch;
20
+ }
21
+ continue;
22
+ }
23
+ if (ch === ',' && !inQ) {
24
+ cells.push(cur);
25
+ cur = '';
26
+ continue;
27
+ }
28
+ cur += ch;
29
+ }
30
+ cells.push(cur);
31
+ return cells;
32
+ };
33
+
34
+ const highlightCsvCell = (cell, row, col) => {
35
+ if (row === 0) return { text: cell, style: 'function' };
36
+ const num = /^-?\d+(\.\d+)?$/.test(cell.trim());
37
+ if (num) return { text: cell, style: 'number' };
38
+ if (col % 2 === 0) return { text: cell, style: 'string' };
39
+ return { text: cell, style: 'variable' };
40
+ };
41
+
42
+ const highlightCsvLine = (line, row) => {
43
+ if (!line) return [];
44
+ const cells = splitCsv(line);
45
+ const out = [];
46
+ for (let col = 0; col < cells.length; col += 1) {
47
+ if (col) emit(out, 'punct', ',');
48
+ const cell = highlightCsvCell(cells[col], row, col);
49
+ emit(out, cell.style, cell.text);
50
+ }
51
+ return out;
52
+ };
53
+
54
+ const highlight = (source) => {
55
+ const lines = source.split(/\r?\n/);
56
+ const out = [];
57
+ for (let row = 0; row < lines.length; row += 1) {
58
+ if (row) emit(out, 'plain', '\n');
59
+ for (const t of highlightCsvLine(lines[row], row === 0 ? 1 : row)) {
60
+ out.push(t);
61
+ }
62
+ }
63
+ return out;
64
+ };
65
+
66
+ module.exports = { langs, highlight };