Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Compiler Internals

Nathan Shively-Sanders edited this page Jun 24, 2016 · 7 revisions

This page details the compiler implementation and its philosophy. Because it focuses on implementation, it's necessarily out-of-date and incomplete.

Before reading this page, be sure to read the Architectural Overview first.

General Design

Laziness

To support language services that respond interactively, the compiler is lazy: it does not calculate any information until it is required. This allows it to respond quickly when the language service requests the type of a variable or its members. Unfortunately, laziness also makes the compiler code more complicated.

As an overview, after parsing is complete, the binder does nothing but identify symbols. The checker then waits until a particular symbol is requested to calculate type information, etc.

Immutability

Each phase of the compiler (parser, binder, etc -- see below for details) treats data structures from the previous phases as immutable. In addition, data structures created within each phase are not usually modified after their creation. This requires a look-aside table in some cases. For example, because the binder only looks at one file at a time, the checker needs a merged-symbols table to track merged declarations. It checks whether a symbol has an entry in the merged-symbols table each time before it uses a symbol.

Parser

The parser is a recursive descent parser. It's pretty resilient to small changes, so if you search for function names matching the thing you want to change, you can probably get away with not having to think about the whole parser. There aren't any surprises in the general implementation style here.

Incremental parsing

ECMAScript parsing contexts

ECMAScript automatic semicolon insertion

JSDoc parsing

Binder

Checker

The checker is almost 20,000 lines long, and does almost everything that's not syntactic -- it's the second of two semantic passes, after binding, which is the first semantic pass. Since the semantics of a entire program can change dramatically with a couple of keystrokes (e.g. renaming a class), a new checker gets created every time the language service requests information. Creating a checker is cheap, though, because the compiler as a whole is so lazy. You just have to create some basic types and get the binder to build the global symbol table.

Grammatical checking

Overload resolution

Type argument inference

Type argument fixing

Transformer

The transformer is nearing completion to replace the emitter. The change in name is because the emitter translated TypeScript to JavaScript. The transformer transforms TypeScript or JavaScript (various versions) to JavaScript (various versions) using various module systems. The input and output are basically both trees from the same AST type, just using different features. There is still a small printer that writes any AST back to text.

Rewriting & synthesized nodes

Sourcemap generation

Language service

Clone this wiki locally