Compiler Theory in JavaScript
Source:https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch1.md
Common Compliation Steps
- Tokenizing/Lexing: breaking up a string of characters into meaningful (to the language) chunks, called tokens. For instance, consider the program:
var a = 2;
. This program would likely be broken up into the following tokens:var
,a
,=
,2
, and;
. Whitespace may or may not be persisted as a token, depending on whether it's meaningful or not.
Note: The difference between tokenizing and lexing is subtle and academic, but it centers on whether or not these tokens are identified in a stateless or stateful way. Put simply, if the tokenizer were to invoke stateful parsing rules to figure out whethera
should be considered a distinct token or just part of another token, that would be lexing.
- Parsing: taking a stream (array) of tokens and turning it into a tree of nested elements, which collectively represent the grammatical structure of the program. This tree is called an "AST" (Abstract Syntax Tree). The tree for
var a = 2;
might start with a top-level node calledVariableDeclaration
, with a child node calledIdentifier
(whose value isa
), and another child calledAssignmentExpression
which itself has a child calledNumericLiteral
(whose value is2
).
- Code-Generation: the process of taking an AST and turning it into executable code. This part varies greatly depending on the language, the platform it's targeting, etc.
Complier Model in JS
- Engine: responsible for start-to-finish compilation and execution of our JavaScript program.Compiler: one of Engine's friends; handles all the dirty work of parsing and code-generation (see previous section).
- Scope: another friend of Engine; collects and maintains a look-up list of all the declared identifiers (variables), and enforces a strict set of rules as to how these are accessible to currently executing code.
LHS & RHS
- An RHS look-up is indistinguishable, for our purposes, from simply a look-up of the value of some variable.(get the value of)
- An LHS look-up is trying to find the variable container itself, so that it can assign.
- In this way, RHS doesn't really mean "right-hand side of an assignment" per se, it just, more accurately, means "not left-hand side".
function foo(a) { console.log( a ); // RHS of console Object, LHS of parameter in log } foo( 2 ); // RHS of foo // inplied a = 2 // LHS of a
function foo(a) { var b = a; // LHS of b, RHS of a return a + b; // RHS of a, RHS of b } var c = foo( 2 ); // LHS of c, RHS of foo // implied a = 2 // LHS of a
Nested Scope
function foo(a) { console.log( a + b ); } var b = 2; foo( 2 ); // 4
The RHS reference for b
cannot be resolved inside the function foo
, but it can be resolved in the Scope surrounding it (in this case, the global).
Error
function foo(a) { console.log( a + b ); // RHS can not find b in the scopes, results in a ReferenceError being thrown by the Engine b = a; } foo( 2 );
If the Engine is performing an LHS look-up, and it arrives at the top floor (global Scope) without finding it, if the program is not running in "Strict Mode" [^note-strictmode], then the global Scope will create a new variable of that name in the global scope, and hand it back to Engine.
Strict mode disallows the automatic/implicit global variable creation. In that case, there would be no global Scope'd variable to hand back from an LHS look-up.
If a variable is found for an RHS look-up, but you try to do something with its value that is impossible, such as trying to execute-as-function a non-function value, or reference a property on a null
or undefined
value, then Engine throws a different kind of error, called a TypeError
.