PageSourceSearch

https://esprima.org/demo/components.js

js esprima.org collected 2026-09-24 15:21:11 UTC 52,342 bytes, 1,422 lines download raw bytes

1Demo = (function () {
2    'use strict';
3
4    function $$(id) {
5        return window.document.getElementById(id);
6    }
7
8    function StatusInfo(configs) {
9        this.configs = configs;
10        this.element = null;
11
12        var self = this;
13
14        this.showInfo = function (message) {
15            var el = $$(self.configs.container);
16            typeof el.innerText === 'string' ? (el.innerText = message) : (el.textContent = message);
17            el.setAttribute('class', 'alert-box success');
18        };
19
20        this.showWarning = function (message) {
21            var el = $$(self.configs.container);
22            typeof el.innerText === 'string' ? (el.innerText = message) : (el.textContent = message);
23            el.setAttribute('class', 'alert-box secondary');
24        };
25
26        this.showError = function (message) {
27            var el = $$(self.configs.container);
28            typeof el.innerText === 'string' ? (el.innerText = message) : (el.textContent = message);
29            el.setAttribute('class', 'alert-box alert');
30        };
31    }
32
33    function CodeEditor(configs, listeners) {
34        this.configs = configs || {};
35        this.listeners = listeners || {};
36
37        this.editor = null;
38        this.debounceTimer = null;
39
40        var self = this;
41
42        function registerCustomLanguage(language) {
43            monaco.languages.register({ id: language });
44            // Mimic Monaco's built-in Monarch for JavaScript
45            // https://github.com/microsoft/monaco-languages
46            monaco.languages.setMonarchTokensProvider(language, {
47                defaultToken: 'invalid',
48                tokenPostfix: '.js',
49
50                keywords: [
51                    'break',
52                    'case',
53                    'catch',
54                    'class',
55                    'continue',
56                    'const',
57                    'constructor',
58                    'debugger',
59                    'default',
60                    'delete',
61                    'do',
62                    'else',
63                    'export',
64                    'extends',
65                    'false',
66                    'finally',
67                    'for',
68                    'from',
69                    'function',
70                    'get',
71                    'if',
72                    'import',
73                    'in',
74                    'instanceof',
75                    'let',
76                    'new',
77                    'null',
78                    'return',
79                    'set',
80                    'super',
81                    'switch',
82                    'symbol',
83                    'this',
84                    'throw',
85                    'true',
86                    'try',
87                    'typeof',
88                    'undefined',
89                    'var',
90                    'void',
91                    'while',
92                    'with',
93                    'yield',
94                    'async',
95                    'await',
96                    'of'
97                ],
98
99                operators: [
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                symbols: /[=><!~?:&|+\-*\/\^%]+/,
145                escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
146                digits: /\d+(_+\d+)*/,
147                octaldigits: /[0-7]+(_+[0-7]+)*/,
148                binarydigits: /[0-1]+(_+[0-1]+)*/,
149                hexdigits: /[[0-9a-fA-F]+(_+[0-9a-fA-F]+)*/,
150
151                regexpctl: /[(){}\[\]\$\^|\-*+?\.]/,
152                regexpesc: /\\(?:[bBdDfnrstvwWn0\\\/]|@regexpctl|c[A-Z]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4})/,
153
154                tokenizer: {
155                    root: [[/[{}]/, 'delimiter.bracket'], { include: 'common' }],
156
157                    common: [
158                        // identifiers and keywords
159                        [
160                            /[a-z_$][\w$]*/,
161                            {
162                                cases: {
163                                    '@keywords': 'keyword',
164                                    '@default': 'identifier'
165                                }
166                            }
167                        ],
168                        [/[A-Z][\w\$]*/, 'type.identifier'], // to show class names nicely
169                        // [/[A-Z][\w\$]*/, 'identifier'],
170
171                        // whitespace
172                        { include: '@whitespace' },
173
174                        // regular expression: ensure it is terminated before beginning (otherwise it is an opeator)
175                        [
176                            /\/(?=([^\\\/]|\\.)+\/([gimsuy]*)(\s*)(\.|;|\/|,|\)|\]|\}|$))/,
177                            { token: 'regexp', bracket: '@open', next: '@regexp' }
178                        ],
179
180                        // delimiters and operators
181                        [/[()\[\]]/, '@brackets'],
182                        [/[<>](?!@symbols)/, '@brackets'],
183                        [
184                            /@symbols/,
185                            {
186                                cases: {
187                                    '@operators': 'delimiter',
188                                    '@default': ''
189                                }
190                            }
191                        ],
192
193                        // numbers
194                        [/(@digits)[eE]([\-+]?(@digits))?/, 'number.float'],
195                        [/(@digits)\.(@digits)([eE][\-+]?(@digits))?/, 'number.float'],
196                        [/0[xX](@hexdigits)/, 'number.hex'],
197                        [/0[oO]?(@octaldigits)/, 'number.octal'],
198                        [/0[bB](@binarydigits)/, 'number.binary'],
199                        [/(@digits)/, 'number'],
200
201                        // delimiter: after number because of .\d floats
202                        [/[;,.]/, 'delimiter'],
203
204                        // strings
205                        [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string
206                        [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-teminated string
207                        [/"/, 'string', '@string_double'],
208                        [/'/, 'string', '@string_single'],
209                        [/`/, 'string', '@string_backtick']
210                    ],
211
212                    whitespace: [
213                        [/[ \t\r\n]+/, ''],
214                        [/\/\*/, 'comment', '@comment'],
215                        [/\/\/.*$/, 'comment']
216                    ],
217
218                    comment: [
219                        [/[^\/*]+/, 'comment'],
220                        [/\*\//, 'comment', '@pop'],
221                        [/[\/*]/, 'comment']
222                    ],
223
224                    // We match regular expression quite precisely
225                    regexp: [
226                        [
227                            /(\{)(\d+(?:,\d*)?)(\})/,
228                            ['regexp.escape.control', 'regexp.escape.control', 'regexp.escape.control']
229                        ],
230                        [
231                            /(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/,
232                            ['regexp.escape.control', { token: 'regexp.escape.control', next: '@regexrange' }]
233                        ],
234                        [/(\()(\?:|\?=|\?!)/, ['regexp.escape.control', 'regexp.escape.control']],
235                        [/[()]/, 'regexp.escape.control'],
236                        [/@regexpctl/, 'regexp.escape.control'],
237                        [/[^\\\/]/, 'regexp'],
238                        [/@regexpesc/, 'regexp.escape'],
239                        [/\\\./, 'regexp.invalid'],
240                        [/(\/)([gimsuy]*)/, [{ token: 'regexp', bracket: '@close', next: '@pop' }, 'keyword.other']]
241                    ],
242
243                    regexrange: [
244                        [/-/, 'regexp.escape.control'],
245                        [/\^/, 'regexp.invalid'],
246                        [/@regexpesc/, 'regexp.escape'],
247                        [/[^\]]/, 'regexp'],
248                        [/\]/, { token: 'regexp.escape.control', next: '@pop', bracket: '@close' }]
249                    ],
250
251                    string_double: [
252                        [/[^\\"]+/, 'string'],
253                        [/@escapes/, 'string.escape'],
254                        [/\\./, 'string.escape.invalid'],
255                        [/"/, 'string', '@pop']
256                    ],
257
258                    string_single: [
259                        [/[^\\']+/, 'string'],
260                        [/@escapes/, 'string.escape'],
261                        [/\\./, 'string.escape.invalid'],
262                        [/'/, 'string', '@pop']
263                    ],
264
265                    string_backtick: [
266                        [/\$\{/, { token: 'delimiter.bracket', next: '@bracketCounting' }],
267                        [/[^\\`$]+/, 'string'],
268                        [/@escapes/, 'string.escape'],
269                        [/\\./, 'string.escape.invalid'],
270                        [/`/, 'string', '@pop']
271                    ],
272
273                    bracketCounting: [
274                        [/\{/, 'delimiter.bracket', '@bracketCounting'],
275                        [/\}/, 'delimiter.bracket', '@pop'],
276                        { include: 'common' }
277                    ]
278                }
279            });
280        }
281
282        require.config({ paths: { vs: '../assets/monaco/min/vs' } });
283        require(['vs/editor/editor.main'], function () {
284            var language = self.configs.language;
285            if (language) {
286                registerCustomLanguage(language);
287            } else {
288                language = 'javascript';
289            }
290
291            monaco.editor.defineTheme('custom', {
292                base: 'vs',
293                inherit: true,
294                rules: [{ token: 'comment', foreground: 'aaaaaa', fontStyle: 'italic' }],
295                colors: {
296                    'editorLineNumber.foreground': '#ccc',
297                    'editor.lineHighlightBackground': '#eaf2fe'
298                }
299            });
300
301            var element = $$(self.configs.container);
302
303            self.editor = monaco.editor.create(element, {
304                language: language,
305                folding: false,
306                lineNumbersMinChars: 3,
307                fontSize: 14,
308                minimap: {
309                    enabled: false
310                },
311                theme: 'custom',
312                wordBasedSuggestions: false,
313                quickSuggestions: false
314            });
315
316            if (self.configs.value) self.editor.getModel().setValue($$(self.configs.value).textContent);
317
318            self.editor.getModel().onDidChangeContent(function () {
319                if (self.editor && self.listeners.contentChanged) {
320                    if (self.debounceTimer) window.clearTimeout(self.debounceTimer);
321                    self.debounceTimer = window.setTimeout(self.listeners.contentChanged, 200);
322                }
323            });
324
325            if (self.listeners.cursorMoved) self.editor.onDidChangeCursorPosition(self.listeners.cursorMoved);
326
327            if (self.listeners.ready) self.listeners.ready();
328            self.editor.focus();
329        });
330
331        this.getValue = function () {
332            return self.editor.getModel().getValue();
333        };
334
335        this.setValue = function (value) {
336            self.editor.getModel().setValue(value);
337        };
338
339        this.getCursorOffset = function () {
340            var selection = self.editor.getSelection();
341            return self.editor.getModel().getOffsetAt(selection.getPosition());
342        };
343
344        this.setCursorOffset = function (offset) {
345            var pos = self.editor.getModel().getPositionAt(offset);
346            self.editor.setSelection(new monaco.Selection(pos.lineNumber, pos.column, pos.lineNumber, pos.column));
347        };
348
349        this.getPositionAt = function (offset) {
350            return self.editor.getModel().getPositionAt(offset);
351        };
352
353        this.markers = [];
354
355        this.clearMarkers = function () {
356            self.markers = [];
357            monaco.editor.setModelMarkers(self.editor.getModel(), 'code-editor', this.markers);
358        };
359
360        this.addErrorMarker = function (message, lineNumber, column) {
361            self.markers.push({
362                severity: monaco.MarkerSeverity.Error,
363                startLineNumber: lineNumber,
364                startColumn: column,
365                endLineNumber: lineNumber,
366                endColumn: column,
367                message: message
368            });
369            monaco.editor.setModelMarkers(self.editor.getModel(), 'code-editor', this.markers);
370        };
371
372        this.addInfoMarker = function (message, lineNumber, column) {
373            self.markers.push({
374                severity: monaco.MarkerSeverity.Info,
375                startLineNumber: lineNumber,
376                startColumn: column,
377                endLineNumber: lineNumber,
378                endColumn: column,
379                message: message
380            });
381            monaco.editor.setModelMarkers(self.editor.getModel(), 'code-editor', this.markers);
382        };
383
384        this.previousDecorations = [];
385
386        this.clearDecorations = function () {
387            self.previousDecorations = self.editor.deltaDecorations(self.previousDecorations, []);
388        };
389
390        this.createDecoration = function (className, loc) {
391            return {
392                range: new monaco.Range(loc.start.line, loc.start.column + 1, loc.end.line, loc.end.column + 1),
393                options: {
394                    inlineClassName: className
395                }
396            };
397        };
398
399        this.applyDecorations = function (highlights) {
400            self.previousDecorations = self.editor.deltaDecorations(self.previousDecorations, highlights);
401        };
402    }
403
404    function TreeViz(configs, listeners) {
405        this.configs = configs || {};
406        this.listeners = listeners || {};
407
408        var self = this;
409
410        $$('show_tree').onclick = function () {
411            $$('tab_tree').className = $$('show_tree').className = 'active';
412            $$('tab_syntax').className = $$('show_syntax').className = '';
413            $$('tab_tokens').className = $$('show_tokens').className = '';
414            $$('tab_tree').style.display = '';
415            $$('tab_syntax').style.display = 'none';
416            $$('tab_tokens').style.display = 'none';
417            if (self.listeners.tabSwitched) self.listeners.tabSwitched();
418        };
419
420        $$('show_syntax').onclick = function () {
421            $$('tab_tree').className = $$('show_tree').className = '';
422            $$('tab_syntax').className = $$('show_syntax').className = 'active';
423            $$('tab_tokens').className = $$('show_tokens').className = '';
424            $$('tab_tree').style.display = 'none';
425            $$('tab_syntax').style.display = '';
426            $$('tab_tokens').style.display = 'none';
427            if (self.listeners.tabSwitched) self.listeners.tabSwitched();
428        };
429
430        $$('show_tokens').onclick = function () {
431            $$('tab_tree').className = $$('show_tree').className = '';
432            $$('tab_syntax').className = $$('show_syntax').className = '';
433            $$('tab_tokens').className = $$('show_tokens').className = 'active';
434            $$('tab_tree').style.display = 'none';
435            $$('tab_syntax').style.display = 'none';
436            $$('tab_tokens').style.display = '';
437            if (self.listeners.tabSwitched) self.listeners.tabSwitched();
438        };
439
440        $('#syntaxtree').tree({
441            autoOpen: true,
442            dragAndDrop: false,
443            openedIcon: '-',
444            closedIcon: '+'
445        });
446
447        // Special handling for regular expression literal since we need to
448        // convert it to a string literal, otherwise it will be decoded
449        // as object "{}" and the regular expression would be lost.
450        function adjustRegexLiteral(key, value) {
451            if (key === 'value' && value instanceof RegExp) {
452                value = value.toString();
453            }
454            return value;
455        }
456
457        this.updateSyntax = function (syntax) {
458            $$('syntax').value = JSON.stringify(syntax, adjustRegexLiteral, 2);
459        };
460
461        this.updateTokens = function (tokens) {
462            $$('tokens').value = JSON.stringify(tokens, adjustRegexLiteral, 4);
463        };
464
465        this.updateTree = function (syntax) {
466            if (typeof syntax === 'undefined') {
467                $('#syntaxtree').tree('loadData', []);
468                return;
469            }
470
471            function isArray(o) {
472                return typeof Array.isArray === 'function'
473                    ? Array.isArray(o)
474                    : Object.prototype.toString.apply(o) === '[object Array]';
475            }
476
477            function convert(node) {
478                var data = [],
479                    value;
480                if (isArray(node)) {
481                    for (var i = 0; i < node.length; ++i) {
482                        value = node[i];
483                        data.push({
484                            name: '#' + (i + 1),
485                            children: convert(value)
486                        });
487                    }
488                } else {
489                    for (var key in node) {
490                        if (Object.prototype.hasOwnProperty.call(node, key)) {
491                            value = node[key];
492                            switch (typeof value) {
493                                case 'string':
494                                case 'number':
495                                case 'boolean':
496                                    data.push({ name: key + ': ' + value.toString() });
497                                    break;
498                                case 'object':
499                                    if (node instanceof RegExp) {
500                                        data.push({ name: key + ': ' + value.toString() });
501                                    } else if (key === 'range' && isArray(value) && value.length === 2) {
502                                        data.push({ name: key + ': [' + value[0] + ', ' + value[1] + ']' });
503                                    } else {
504                                        data.push({ name: key, children: convert(value) });
505                                    }
506                                    break;
507                                default:
508                                    break;
509                            }
510                        }
511                    }
512                }
513                return data;
514            }
515
516            $('#syntaxtree').tree('loadData', convert(syntax));
517        };
518    }
519
520    function deduceSourceType(code) {
521        var type = 'module';
522
523        try {
524            esprima.parse(code, { sourceType: 'script' });
525            type = 'script';
526        } catch (e) {}
527
528        return type;
529    }
530
531    // parse.html
532    function visualizer() {
533        var statusInfo = new StatusInfo({ container: 'info' });
534
535        var codeEditor = new CodeEditor(
536            { container: 'editor', value: 'init' },
537            {
538                ready: initialize,
539                contentChanged: visualize
540            }
541        );
542
543        var treeViz = new TreeViz();
544
545        function initialize() {
546            window.setTimeout(visualize, 0); // first time
547
548            statusInfo.showInfo('Ready.');
549
550            $$('comment').onchange = visualize;
551            $$('range').onchange = visualize;
552            $$('loc').onchange = visualize;
553
554            // Special handling for IE.
555            $$('comment').onclick = visualize;
556            $$('range').onclick = visualize;
557            $$('loc').onclick = visualize;
558
559            if (location.search) {
560                var queries = {};
561                var elements = location.search.substring(1).split(/[;&]/);
562                for (var i = 0; i < elements.length; i += 1) {
563                    var pair = elements[i].split('=');
564                    if (pair.length === 2) {
565                        queries[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
566                    }
567                }
568                var code = queries.code;
569                if (code) {
570                    codeEditor.setValue(code);
571                    visualize();
572                }
573            }
574        }
575
576        function visualize() {
577            var code = codeEditor.getValue();
578
579            var loc = window.location;
580            $$('url').value = loc.protocol + '//' + loc.host + loc.pathname + '?code=' + encodeURIComponent(code);
581
582            var options = {
583                attachComment: $$('comment').checked,
584                range: $$('range').checked,
585                loc: $$('loc').checked,
586                sourceType: deduceSourceType(code)
587            };
588            try {
589                var syntax = esprima.parse(code, options);
590                treeViz.updateTree(syntax);
591                treeViz.updateSyntax(syntax);
592                options.tokens = true;
593                treeViz.updateTokens(esprima.parse(code, options).tokens);
594                statusInfo.showInfo('No error.');
595            } catch (e) {
596                statusInfo.showError(e.toString());
597            }
598        }
599    }
600
601    // validate.html
602    function validator() {
603        var statusInfo = new StatusInfo({ container: 'info' });
604
605        var codeEditor = new CodeEditor(
606            { container: 'editor', value: 'init' },
607            {
608                ready: initialize,
609                contentChanged: validate
610            }
611        );
612
613        function initialize() {
614            statusInfo.showInfo('Ready.');
615            validate(); // first time
616        }
617
618        function validate() {
619            try {
620                codeEditor.clearMarkers();
621                var code = codeEditor.getValue();
622                var syntax = esprima.parse(code, { tolerant: true, loc: true, range: true });
623                if (syntax.errors.length > 0) {
624                    for (var i = 0; i < syntax.errors.length; ++i) {
625                        var e = syntax.errors[i];
626                        codeEditor.addErrorMarker(e.description, e.lineNumber, e.column);
627                    }
628                    statusInfo.showError('Invalid code. Total issues: ' + syntax.errors.length);
629                } else {
630                    if (syntax.body.length === 0) {
631                        statusInfo.showWarning('info', 'Empty code. Nothing to validate.');
632                    }
633                    statusInfo.showInfo('Code is syntactically valid.');
634                }
635            } catch (e) {
636                codeEditor.addErrorMarker(e.toString(), e.lineNumber, e.column);
637                statusInfo.showError(e.toString());
638            }
639        }
640    }
641
642    // collector.html
643    function regexCollector() {
644        var statusInfo = new StatusInfo({ container: 'info' });
645
646        var codeEditor = new CodeEditor(
647            { container: 'editor', value: 'init' },
648            {
649                ready: initialize,
650                contentChanged: collect
651            }
652        );
653
654        function initialize() {
655            statusInfo.showInfo('Ready.');
656            collect();
656 // first time
657        }
658
659        function collect() {
660            function createRegex(pattern, mode) {
661                var literal;
662                try {
663                    literal = new RegExp(pattern, mode);
664                } catch (e) {
665                    // Invalid regular expression.
666                    return;
667                }
668                return literal;
669            }
670
671            codeEditor.clearDecorations();
672            try {
673                var highlights = [];
674                var code = codeEditor.getValue();
675
676                esprima.parse(code, { tolerant: true, loc: true }, function (node) {
677                    if (node.type === 'Literal') {
678                        if (node.value instanceof RegExp)
679                            highlights.push(codeEditor.createDecoration('highlight-decoration', node.loc));
680                    }
681                    if (node.type === 'NewExpression' || node.type === 'CallExpression') {
682                        if (node.callee.type === 'Identifier' && node.callee.name === 'RegExp') {
683                            var arg = node['arguments'];
684                            if (arg.length === 1 && arg[0].type === 'Literal') {
685                                if (typeof arg[0].value === 'string') {
686                                    if (createRegex(arg[0].value))
687                                        highlights.push(codeEditor.createDecoration('highlight-decoration', node.loc));
688                                }
689                            }
690                            if (arg.length === 2 && arg[0].type === 'Literal' && arg[1].type === 'Literal') {
691                                if (typeof arg[0].value === 'string' && typeof arg[1].value === 'string') {
692                                    if (createRegex(arg[0].value, arg[1].value))
693                                        highlights.push(codeEditor.createDecoration('highlight-decoration', node.loc));
694                                }
695                            }
696                        }
697                    }
698                });
699                if (highlights.length > 0) {
700                    codeEditor.applyDecorations(highlights);
701                    statusInfo.showInfo('Total regular expressions: ' + highlights.length);
702                } else {
703                    statusInfo.showWarning('No regex found.');
704                }
705            } catch (e) {
706                statusInfo.showError(e.toString());
707            }
708        }
709    }
710
711    // functiontrace.html
712    function functionTracer() {
713        var statusInfo = new StatusInfo({ container: 'info' });
714
715        var codeEditor = new CodeEditor(
716            { container: 'editor', value: 'init' },
717            {
718                ready: function () {
719                    statusInfo.showWarning('Ready.');
720                    $$('run').onclick = traceRun;
721                },
722                contentChanged: function () {
723                    statusInfo.showWarning('Ready.');
724                }
725            }
726        );
727
728        function traceRun() {
729            try {
730                statusInfo.showWarning('Executing...');
731                createTraceCollector();
732                var code = traceInstrument();
733                var timestamp = +new Date();
734                eval(code);
735                var elapsed = +new Date() - timestamp;
736                showResult();
737                statusInfo.showInfo('Tracing completed in ' + (1 + elapsed) + ' ms');
738            } catch (e) {
739                statusInfo.showError(e.toString());
740            }
741        }
742
743        function createTraceCollector() {
744            window.TRACE = {
745                hits: {},
746                enterFunction: function (info) {
747                    var key = info.name + ':' + info.range[0];
748                    if (this.hits.hasOwnProperty(key)) {
749                        this.hits[key] = this.hits[key] + 1;
750                    } else {
751                        this.hits[key] = 1;
752                    }
753                },
754                getHistogram: function () {
755                    var histogram = [];
756                    for (var entry in this.hits) {
757                        if (this.hits.hasOwnProperty(entry)) {
758                            histogram.push({ name: entry, count: this.hits[entry] });
759                        }
760                    }
761                    histogram.sort(function (a, b) {
762                        return b.count - a.count;
763                    });
764                    return histogram;
765                }
766            };
767        }
768
769        function traceInstrument() {
770            var code = codeEditor.getValue();
771            var tracer = window.esmorph.Tracer.FunctionEntrance(function (fn) {
772                var signature = 'window.TRACE.enterFunction({ ';
773                signature += 'name: "' + fn.name + '", ';
774                signature += 'lineNumber: ' + fn.loc.start.line + ', ';
775                signature += 'range: [' + fn.range[0] + ',' + fn.range[1] + ']';
776                signature += ' });';
777                return signature;
778            });
779
780            code = window.esmorph.modify(code, tracer);
781
782            // Enclose in IIFE.
783            code = '(function() {\n' + code + '\n}())';
784
785            return code;
786        }
787
788        function showResult() {
789            var histogram = window.TRACE.getHistogram();
790            codeEditor.clearMarkers();
791            for (var i = 0; i < histogram.length; i += 1) {
792                var entry = histogram[i];
793                var name = entry.name.split(':')[0];
794                var offset = parseInt(entry.name.split(':')[1], 10);
795                var pos = codeEditor.getPositionAt(offset);
796                var msg = name + ' is called ' + entry.count.toString() + ' time';
797                if (entry.count > 1) msg += 's';
798                codeEditor.addInfoMarker(msg, pos.lineNumber, pos.column);
799            }
800        }
801    }
802
803    // rewrite.html
804    function rewriter() {
805        var statusInfo = new StatusInfo({ container: 'info' });
806
807        var codeEditor = new CodeEditor(
808            { container: 'editor', value: 'init' },
809            {
810                ready: function () {
811                    statusInfo.showWarning('Ready.');
812                    $$('rewrite').onclick = sourceRewrite;
813                },
814                contentChanged: function () {
815                    statusInfo.showWarning('Ready.');
816                }
817            }
818        );
819
820        function sourceRewrite() {
821            var code = codeEditor.getValue();
822
823            var indent = '';
824            if ($$('onetab').checked) {
825                indent = '\t';
826            } else if ($$('twospaces').checked) {
827                indent = '  ';
828            } else if ($$('fourspaces').checked) {
829                indent = '    ';
830            }
831
832            var quotes = 'auto';
833            if ($$('singlequotes').checked) {
834                quotes = 'single';
835            } else if ($$('doublequotes').checked) {
836                quotes = 'double';
837            }
838
839            var option = {
840                comment: true,
841                format: {
842                    indent: {
843                        style: indent
844                    },
845                    quotes: quotes
846                }
847            };
848
849            try {
850                var syntax = window.esprima.parse(code, { raw: true, tokens: true, range: true, comment: true });
851                syntax = window.escodegen.attachComments(syntax, syntax.comments, syntax.tokens);
852                code = window.escodegen.generate(syntax, option);
853                codeEditor.setValue(code);
854                statusInfo.showInfo('Rewriting was successful.');
855            } catch (e) {
856                statusInfo.showError(e.toString());
857            }
858        }
859    }
860
861    // minify.html
862    function minifier() {
863        var statusInfo = new StatusInfo({ container: 'info' });
864
865        var codeEditor = new CodeEditor(
866            { container: 'editor', value: 'init' },
867            {
868                ready: function () {
869                    statusInfo.showWarning('Ready.');
870                    $$('minify').onclick = minify;
871                },
872                contentChanged: function () {
873                    statusInfo.showWarning('Ready.');
874                }
875            }
876        );
877
878        function createPipeline() {
879            var passes, pipeline, inputs, i, el, optimizer;
880
881            passes = {
882                'eliminate-dead-code': 'pass/dead-code-elimination',
883                'fold-constant': 'pass/tree-based-constant-folding',
884                'remove-unreachable-branch': 'pass/remove-unreachable-branch',
885                'remove-unused-vars': 'pass/drop-variable-definition'
886            };
887
888            pipeline = [
889                'pass/hoist-variable-to-arguments',
890                'pass/transform-dynamic-to-static-property-access',
891                'pass/transform-dynamic-to-static-property-definition',
892                'pass/transform-immediate-function-call',
893                'pass/transform-logical-association',
894                'pass/reordering-function-declarations',
895                'pass/remove-unused-label',
896                'pass/remove-empty-statement',
897                'pass/remove-wasted-blocks',
898                'pass/transform-to-compound-assignment',
899                'pass/transform-to-sequence-expression',
900                'pass/transform-branch-to-expression',
901                'pass/transform-typeof-undefined',
902                'pass/reduce-sequence-expression',
903                'pass/reduce-branch-jump',
904                'pass/reduce-multiple-if-statements',
905                'pass/dead-code-elimination',
906                'pass/remove-side-effect-free-expressions',
907                'pass/remove-context-sensitive-expressions',
908                'pass/tree-based-constant-folding',
909                'pass/drop-variable-definition',
910                'pass/remove-unreachable-branch'
911            ];
912
913            inputs = document.getElementsByTagName('input');
914            for (i = 0; i < inputs.length; i += 1) {
915                el = inputs[i];
916                optimizer = passes[el.id];
917                if (optimizer && el.checked === false) {
918                    pipeline.splice(pipeline.indexOf(optimizer), 1);
919                }
920            }
921
922            pipeline = pipeline.map(window.esmangle.require);
923            pipeline = [pipeline];
924            pipeline.push({
925                once: true,
926                pass: [
927                    'post/transform-static-to-dynamic-property-access',
928                    'post/transform-infinity',
929                    'post/rewrite-boolean',
930                    'post/rewrite-conditional-expression'
931                ].map(window.esmangle.require)
932            });
933
934            return pipeline;
935        }
936
937        function obfuscate(syntax) {
938            var result = window.esmangle.optimize(syntax, createPipeline());
939
940            if ($$('mangle').checked) {
941                result = window.esmangle.mangle(result);
942            }
943
944            return result;
945        }
946
947        function minify() {
948            var code = codeEditor.getValue();
949
950            var option = {
951                format: {
952                    indent: {
953                        style: ''
954                    },
955                    quotes: 'auto',
956                    compact: true
957                }
958            };
959
960            try {
961                var before = code.length;
962                var syntax = window.esprima.parse(code, { raw: true, loc: true });
963                syntax = obfuscate(syntax);
964                code = window.escodegen.generate(syntax, option);
965                var after = code.length;
966                if (before > after) {
967                    codeEditor.setValue(code);
968                    statusInfo.showInfo('No error. Minifying ' + before + ' bytes to ' + after + ' bytes.');
969                } else {
970                    statusInfo.showWarning('Can not minify further, code is already optimized.');
971                }
972            } catch (e) {
973                statusInfo.showError(e.toString());
974            }
975        }
976    }
977
978    // highlight.html
979    function highlighter() {
980        var statusInfo = new StatusInfo({ container: 'info' });
981
982        var codeEditor = new CodeEditor(
983            { container: 'editor', value: 'init' },
984            {
985                ready: initialize,
986                contentChanged: reparse,
987                cursorMoved: trackCursor
988            }
989        );
990
991        function initialize() {
992            reparse();
992 // first time
993            codeEditor.setCursorOffset(300);
994        }
995
996        var context;
997
998        function reparse() {
999            var code = codeEditor.getValue();
1000            var options = {
1001                tolerant: true,
1002                range: true,
1003                loc: true,
1004                sourceType: deduceSourceType(code)
1005            };
1006
1007            try {
1008                if (!context) {
1009                    context = new esrefactor.Context();
1010                }
1011                var syntax = esprima.parse(code, options);
1012                context.setCode(syntax);
1013                statusInfo.showInfo('Ready');
1014            } catch (e) {
1015                statusInfo.showError(e.toString());
1016            }
1017        }
1018
1019        function trackCursor() {
1020            var decorations = [];
1021
1022            var offset = codeEditor.getCursorOffset();
1023            var identification = context.identify(offset);
1024            if (identification) {
1025                var identifier = identification.identifier;
1026                var declaration = identification.declaration;
1027                var references = identification.references;
1028                if (declaration) {
1029                    if (declaration.range !== identifier.range)
1030                        decorations.push(codeEditor.createDecoration('declaration-decoration', declaration.loc));
1031                    statusInfo.showInfo(
1032                        "Identifier '" + identifier.name + "' is declared in line " + declaration.loc.start.line + '.'
1033                    );
1034                } else {
1035                    statusInfo.showWarning("Warning: No declaration is found for '" + identifier.name + "'.");
1036                }
1037                for (var i = 0; i < references.length; ++i) {
1038                    decorations.push(codeEditor.createDecoration('reference-decoration', references[i].loc));
1039                }
1040            } else {
1041                statusInfo.showInfo('Ready.');
1042            }
1043
1044            codeEditor.applyDecorations(decorations);
1045        }
1046    }
1047
1048    // rename.html
1049    function renamer() {
1050        var statusInfo = new StatusInfo({ container: 'info' });
1051
1052        var codeEditor = new CodeEditor(
1053            { container: 'editor', value: 'init' },
1054            {
1055                ready: initialize,
1056                contentChanged: parse,
1057                cursorMoved: trackCursor
1058            }
1059        );
1060
1061        // important state
1062        var context, code, cursorOffset, syntax, identification;
1063
1064        function initialize() {
1065            context = new esrefactor.Context();
1066            parse();
1067            codeEditor.setCursorOffset(300);
1068        }
1069
1070        function parse() {
1071            if (!context) {
1072                context = new esrefactor.Context();
1073            }
1074
1075            if (identification && identification.identifier) rename();
1076
1077            code = codeEditor.getValue();
1078            var options = {
1079                tolerant: true,
1080                range: true,
1081                loc: true,
1082                tokens: true,
1083                sourceType: deduceSourceType(code)
1084            };
1085
1086            try {
1087                codeEditor.clearDecorations();
1088                identification = null;
1089                syntax = esprima.parse(code, options);
1090                context.setCode(syntax);
1091                statusInfo.showInfo('Ready.');
1092            } catch (e) {
1093                statusInfo.showError(e.toString());
1094            } finally {
1095                trackCursor();
1096            }
1097        }
1098
1099        function rename() {
1100            if (!identification || !syntax) {
1101                contextify();
1102                return;
1103            }
1104
1105            // Locate the token corresponding to the renamed identifier.
1106            var index = -1,
1107                offset,
1108                charOffset;
1109            for (var i = 0; i < syntax.tokens.length; ++i) {
1110                var token = syntax.tokens[i];
1111                if (token.range[0] === identification.identifier.range[0]) {
1112                    index = i;
1113                    offset = identification.identifier.range[1] - cursorOffset;
1114                    charOffset = cursorOffset - token.range[0];
1115                    break;
1116                }
1117            }
1118
1119            var new_code = codeEditor.getValue();
1120            if (code === new_code) {
1121                return;
1122            }
1123            var options = {
1124                tolerant: true,
1125                range: true,
1126                loc: true,
1127                tokens: true,
1128                sourceType: deduceSourceType(new_code)
1129            };
1130            var new_tokens = esprima.parse(new_code, options).tokens;
1131
1132            // Get the new name for that identifier.
1133            token = new_tokens[index];
1134            if (token && token.value !== identification.identifier.name) {
1135                // Check that other tokens haven't changed.
1136                var old_p = code.substr(0, identification.identifier.range[0]);
1137                var old_q = code.substr(identification.identifier.range[1]);
1138                var new_p = new_code.substr(0, token.range[0]);
1139                var new_q = new_code.substr(token.range[1]);
1140                if (old_p === new_p || old_q === new_q) {
1141                    // Rename the identifier, adjust the source and the cursor.
1142                    context.setCode(code);
1143                    var renamed = context.rename(identification, token.value);
1144                    if (renamed) {
1145                        new_tokens = esprima.parse(renamed, options).tokens;
1146                        var new_cursorOffset = new_tokens[index].range[0] + charOffset;
1147                        codeEditor.setValue(renamed);
1148                        codeEditor.setCursorOffset(new_cursorOffset);
1149                    }
1150                }
1151            }
1152        }
1153
1154        function trackCursor() {
1155            var decorations = [];
1156
1157            cursorOffset = codeEditor.getCursorOffset();
1158            identification = context ? context.identify(cursorOffset) : null;
1159            if (identification) {
1160                var identifier = identification.identifier;
1161                var declaration = identification.declaration;
1162                var references = identification.references;
1163                if (declaration && declaration.loc) {
1164                    if (declaration.range !== identifier.range)
1165                        decorations.push(codeEditor.createDecoration('declaration-decoration', declaration.loc));
1166                    statusInfo.showInfo(
1167                        "Identifier '" + identifier.name + "' is declared in line " + declaration.loc.start.line + '.'
1168                    );
1169                } else {
1170                    statusInfo.showWarning("Warning: No declaration is found for '" + identifier.name + "'.");
1171                }
1172                for (var i = 0; i < references.length; ++i) {
1173                    if (references[i] && references[i].loc)
1174                        decorations.push(codeEditor.createDecoration('reference-decoration', references[i].loc));
1175                }
1176            } else {
1177                statusInfo.showInfo('Ready.');
1178            }
1179
1180            codeEditor.applyDecorations(decorations);
1181        }
1182    }
1183
1184    // autocomplete.html
1185    function autoCompleter() {
1186        var statusInfo = new StatusInfo({ container: 'info' });
1187
1188        var codeEditor = new CodeEditor(
1189            { container: 'editor', value: 'init', language: 'AksaraJawa' },
1190            {
1191                ready: initialize,
1192                cursorMoved: trackCursor
1193            }
1194        );
1195
1196        // important state
1197        var aulx = new Aulx.JS();
1198        var previousLine = -1;
1199
1200        function initialize() {
1201            registerAutocompletion();
1202            trackCursor();
1203        }
1204
1205        function registerAutocompletion() {
1206            monaco.languages.registerCompletionItemProvider('AksaraJawa', {
1207                triggerCharacters: ['.'],
1208                provideCompletionItems: function (model, position) {
1209                    var code = model.getValue();
1210                    var caret = {
1211                        line: position.lineNumber - 1,
1212                        ch: position.column - 1
1213                    };
1214                    var completion = aulx.complete(code, caret);
1215                    var suggestions = [];
1216                    if (completion && completion.candidates && completion.candidates.length) {
1217                        for (var i = 0; i < completion.candidates.length; ++i) {
1218                            var display = completion.candidates[i].display;
1219                            suggestions.push({
1220                                label: display,
1221                                insertText: display
1222                            });
1223                        }
1224                        statusInfo.showInfo('Suggestions: ' + completion.candidates.length);
1225                    } else {
1226                        statusInfo.showWarning('No suggestion');
1227                    }
1228                    return { suggestions: suggestions };
1229                }
1230            });
1231        }
1232
1233        function trackCursor() {
1234            statusInfo.showWarning('Ready.');
1235            var code = codeEditor.getValue();
1236            var offset = codeEditor.getCursorOffset();
1237            var position = codeEditor.getPositionAt(offset);
1238            if (position.lineNumber != previousLine) {
1239                previousLine = position.lineNumber;
1240                var caret = {
1241                    line: position.lineNumber - 1,
1242                    ch: position.column - 1
1243                };
1244                aulx.fireStaticAnalysis(code, caret);
1245            }
1246        }
1247    }
1248
1249    // precedence.html
1250    function precedenceComparator() {
1251        var compareId;
1252        function compare() {
1253            if (compareId) {
1254                window.clearTimeout(compareId);
1255            }
1256
1257            function stringify(node) {
1258                var result;
1259
1260                if (typeof node !== 'object') {
1261                    throw new Error('Node is not valid');
1262                }
1263                if (typeof node.type !== 'string') {
1264                    throw new Error('Node does not have type property');
1265                }
1266
1267                switch (node.type) {
1268                    case 'Program':
1269                        if (node.body.length !== 1) {
1270                            throw new Error('Expression is too complex');
1271                        }
1272                        result = stringify(node.body[0]);
1273                        if (result[0] === '(' && result[result.length - 1] === ')') {
1274                            result = result.substr(1, result.length - 2);
1275                        }
1276                        break;
1277
1278                    case 'ExpressionStatement':
1279                        result = stringify(node.expression);
1280                        break;
1281
1282                    case 'BinaryExpression':
1283                    case 'LogicalExpression':
1284                        result = '(' + stringify(node.left) + ' ' + node.operator + ' ' + stringify(node.right) + ')';
1285                        break;
1286
1287                    case 'UnaryExpression':
1288                        result = '(' + node.operator;
1289                        if (node.operator.length > 2) {
1290                            // delete void typeof
1291                            result += ' ';
1292                        }
1293                        result += stringify(node.argument) + ')';
1294                        break;
1295
1296                    case 'UpdateExpression':
1297                        result = stringify(node.argument);
1298                        if (node.prefix) {
1299                            result = node.operator + result;
1300                        } else {
1301                            result = result + node.operator;
1302                        }
1303                        result = '(' + result + ')';
1304                        break;
1305
1306                    case 'Literal':
1307                        result = node.value.toString();
1308                        if (typeof node.value === 'string') {
1309                            result = '"' + node.value + '"';
1310                        }
1311                        break;
1312
1313                    case 'Identifier':
1314                        result = node.name;
1315                        break;
1316
1317                    default:
1318                        break;
1319                }
1320
1321                if (!result) {
1322                    throw new Error('Unknown node type: ' + node.type);
1323                }
1324
1325                return result;
1326            }
1327
1328            function setText(el, str) {
1329                if (typeof el.innerText === 'string') {
1330                    el.innerText = str;
1331                } else {
1332                    el.textContent = str;
1333                }
1334            }
1335
1336            compareId = window.setTimeout(function () {
1337                var a, b, answer, status, expr, left, right, suggest;
1338
1339                a = $$('a');
1340                b = $$('b');
1341                answer = $$('answer');
1342                status = $$('status');
1343                expr = $$('expr');
1344
1345                a.setAttribute('class', '');
1346                b.setAttribute('class', '');
1347                answer.setAttribute('class', '');
1348
1349                setText(answer, '');
1350                setText(status, '');
1351                setText(expr, '');
1352
1353                try {
1354                    left = esprima.parse(typeof a.innerText === 'string' ? a.innerText : a.textContent);
1355                } catch (e_left) {
1356                    a.setAttribute('class', 'lightred');
1357                }
1358
1359                try {
1360                    right = esprima.parse(typeof b.innerText === 'string' ? b.innerText : b.textContent);
1361                } catch (e_right) {
1362                    b.setAttribute('class', 'lightred');
1363                }
1364
1365                try {
1366                    suggest = stringify(left);
1367                } catch (e_suggest) {
1368                    a.setAttribute('class', 'lightred');
1369                }
1370
1371                if (left && right) {
1372                    if (JSON.stringify(left) === JSON.stringify(right)) {
1373                        setText(answer, 'Yes');
1374                        answer.setAttribute('class', 'yes');
1375                    } else {
1376                        setText(answer, 'No');
1377                        answer.setAttribute('class', 'no');
1378                        setText(status, suggest ? 'It is more like ' : '');
1379                        setText(expr, suggest || '');
1380                    }
1381                } else {
1382                    answer.setAttribute('class', '');
1383                }
1384
1385                compareId = undefined;
1386            }, 57);
1387        }
1388
1389        if (typeof document.body.attachEvent === 'object') {
1390            // Workaround for old Internet Explorer.
1391            // Until there is a reliable way to track the modification to the editable
1392            // inputs, manually track the change periodically.
1393            window.setInterval(compare, 500);
1394        }
1395
1396        // See http://mathiasbynens.be/notes/oninput for details.
1397        $$('a').onkeyup = compare;
1398        $$('a').oninput = function () {
1399            this.onkeyup = null;
1400            compare();
1401        };
1402        $$('b').onkeyup = compare;
1403        $$('b').oninput = function () {
1404            this.onkeyup = null;
1405            compare();
1406        };
1407        compare();
1408    }
1409
1410    return {
1411        visualizer: visualizer,
1412        validator: validator,
1413        regexCollector: regexCollector,
1414        functionTracer: functionTracer,
1415        rewriter: rewriter,
1416        minifier: minifier,
1417        highlighter: highlighter,
1418        renamer: renamer,
1419        autoCompleter: autoCompleter,
1420        precedenceComparator: precedenceComparator
1421    };
1422})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.