/documentation/docs/lexer.html
http://github.com/jashkenas/coffee-script · HTML · 2024 lines · 1768 code · 256 blank · 0 comment · 0 complexity · 9c4487192ff7778f37ad5ad37144c273 MD5 · raw file
Large files are truncated click here to view the full file
- <!DOCTYPE html>
- <html>
- <head>
- <title>lexer.coffee</title>
- <meta http-equiv="content-type" content="text/html; charset=UTF-8">
- <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
- <link rel="stylesheet" media="all" href="docco.css" />
- </head>
- <body>
- <div id="container">
- <div id="background"></div>
-
- <ul id="jump_to">
- <li>
- <a class="large" href="javascript:void(0);">Jump To …</a>
- <a class="small" href="javascript:void(0);">+</a>
- <div id="jump_wrapper">
- <div id="jump_page_wrapper">
- <div id="jump_page">
-
-
- <a class="source" href="browser.html">
- browser.coffee
- </a>
-
-
- <a class="source" href="cake.html">
- cake.coffee
- </a>
-
-
- <a class="source" href="coffee-script.html">
- coffee-script.coffee
- </a>
-
-
- <a class="source" href="command.html">
- command.coffee
- </a>
-
-
- <a class="source" href="grammar.html">
- grammar.coffee
- </a>
-
-
- <a class="source" href="helpers.html">
- helpers.coffee
- </a>
-
-
- <a class="source" href="index.html">
- index.coffee
- </a>
-
-
- <a class="source" href="lexer.html">
- lexer.coffee
- </a>
-
-
- <a class="source" href="nodes.html">
- nodes.coffee
- </a>
-
-
- <a class="source" href="optparse.html">
- optparse.coffee
- </a>
-
-
- <a class="source" href="register.html">
- register.coffee
- </a>
-
-
- <a class="source" href="repl.html">
- repl.coffee
- </a>
-
-
- <a class="source" href="rewriter.html">
- rewriter.coffee
- </a>
-
-
- <a class="source" href="scope.html">
- scope.litcoffee
- </a>
-
-
- <a class="source" href="sourcemap.html">
- sourcemap.litcoffee
- </a>
-
- </div>
- </div>
- </li>
- </ul>
-
- <ul class="sections">
-
- <li id="title">
- <div class="annotation">
- <h1>lexer.coffee</h1>
- </div>
- </li>
-
-
-
- <li id="section-1">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-1">¶</a>
- </div>
- <p>The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
- matches against the beginning of the source code. When a match is found,
- a token is produced, we consume the match, and start again. Tokens are in the
- form:</p>
- <pre><code>[tag, value, locationData]
- </code></pre><p>where locationData is {first_line, first_column, last_line, last_column}, which is a
- format that can be fed directly into <a href="http://github.com/zaach/jison">Jison</a>. These
- are read by jison in the <code>parser.lexer</code> function defined in coffee-script.coffee.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre>
- {Rewriter, INVERSES} = <span class="hljs-built_in">require</span> <span class="hljs-string">'./rewriter'</span></pre></div></div>
-
- </li>
-
-
- <li id="section-2">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-2">¶</a>
- </div>
- <p>Import the helpers we need.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre>{count, starts, compact, repeat, invertLiterate,
- locationDataToString, throwSyntaxError} = <span class="hljs-built_in">require</span> <span class="hljs-string">'./helpers'</span></pre></div></div>
-
- </li>
-
-
- <li id="section-3">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-3">¶</a>
- </div>
- <h2 id="the-lexer-class">The Lexer Class</h2>
- </div>
-
- </li>
-
-
- <li id="section-4">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-4">¶</a>
- </div>
-
- </div>
-
- </li>
-
-
- <li id="section-5">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-5">¶</a>
- </div>
- <p>The Lexer class reads a stream of CoffeeScript and divvies it up into tagged
- tokens. Some potential ambiguity in the grammar has been avoided by
- pushing some extra smarts into the Lexer.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre><span class="hljs-built_in">exports</span>.Lexer = <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Lexer</span></span></pre></div></div>
-
- </li>
-
-
- <li id="section-6">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-6">¶</a>
- </div>
- <p><strong>tokenize</strong> is the Lexer’s main method. Scan by attempting to match tokens
- one at a time, using a regular expression anchored at the start of the
- remaining code, or a custom recursive token-matching method
- (for interpolations). When the next token has been recorded, we move forward
- within the code past the token, and begin again.</p>
- <p>Each tokenizing method is responsible for returning the number of characters
- it has consumed.</p>
- <p>Before returning the token stream, run it through the <a href="rewriter.html">Rewriter</a>.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">tokenize</span>: <span class="hljs-function"><span class="hljs-params">(code, opts = {})</span> -></span>
- <span class="hljs-property">@literate</span> = opts.literate <span class="hljs-comment"># Are we lexing literate CoffeeScript?</span>
- <span class="hljs-property">@indent</span> = <span class="hljs-number">0</span> <span class="hljs-comment"># The current indentation level.</span>
- <span class="hljs-property">@baseIndent</span> = <span class="hljs-number">0</span> <span class="hljs-comment"># The overall minimum indentation level</span>
- <span class="hljs-property">@indebt</span> = <span class="hljs-number">0</span> <span class="hljs-comment"># The over-indentation at the current level.</span>
- <span class="hljs-property">@outdebt</span> = <span class="hljs-number">0</span> <span class="hljs-comment"># The under-outdentation at the current level.</span>
- <span class="hljs-property">@indents</span> = [] <span class="hljs-comment"># The stack of all current indentation levels.</span>
- <span class="hljs-property">@ends</span> = [] <span class="hljs-comment"># The stack for pairing up tokens.</span>
- <span class="hljs-property">@tokens</span> = [] <span class="hljs-comment"># Stream of parsed tokens in the form `['TYPE', value, location data]`.</span>
- <span class="hljs-property">@seenFor</span> = <span class="hljs-literal">no</span> <span class="hljs-comment"># Used to recognize FORIN and FOROF tokens.</span>
- <span class="hljs-property">@chunkLine</span> =
- opts.line <span class="hljs-keyword">or</span> <span class="hljs-number">0</span> <span class="hljs-comment"># The start line for the current @chunk.</span>
- <span class="hljs-property">@chunkColumn</span> =
- opts.column <span class="hljs-keyword">or</span> <span class="hljs-number">0</span> <span class="hljs-comment"># The start column of the current @chunk.</span>
- code = <span class="hljs-property">@clean</span> code <span class="hljs-comment"># The stripped, cleaned original source code.</span></pre></div></div>
-
- </li>
-
-
- <li id="section-7">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-7">¶</a>
- </div>
- <p>At every position, run through this list of attempted matches,
- short-circuiting if any of them succeed. Their order determines precedence:
- <code>@literalToken</code> is the fallback catch-all.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> i = <span class="hljs-number">0</span>
- <span class="hljs-keyword">while</span> <span class="hljs-property">@chunk</span> = code[i..]
- consumed = \
- <span class="hljs-property">@identifierToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@commentToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@whitespaceToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@lineToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@stringToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@numberToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@regexToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@jsToken</span>() <span class="hljs-keyword">or</span>
- <span class="hljs-property">@literalToken</span>()</pre></div></div>
-
- </li>
-
-
- <li id="section-8">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-8">¶</a>
- </div>
- <p>Update position</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> [<span class="hljs-property">@chunkLine</span>, <span class="hljs-property">@chunkColumn</span>] = <span class="hljs-property">@getLineAndColumnFromChunk</span> consumed
- i += consumed
- <span class="hljs-keyword">return</span> {<span class="hljs-property">@tokens</span>, <span class="hljs-attribute">index</span>: i} <span class="hljs-keyword">if</span> opts.untilBalanced <span class="hljs-keyword">and</span> <span class="hljs-property">@ends</span>.length <span class="hljs-keyword">is</span> <span class="hljs-number">0</span>
- <span class="hljs-property">@closeIndentation</span>()
- <span class="hljs-property">@error</span> <span class="hljs-string">"missing <span class="hljs-subst">#{end.tag}</span>"</span>, end.origin[<span class="hljs-number">2</span>] <span class="hljs-keyword">if</span> end = <span class="hljs-property">@ends</span>.pop()
- <span class="hljs-keyword">return</span> <span class="hljs-property">@tokens</span> <span class="hljs-keyword">if</span> opts.rewrite <span class="hljs-keyword">is</span> <span class="hljs-literal">off</span>
- (<span class="hljs-keyword">new</span> Rewriter).rewrite <span class="hljs-property">@tokens</span></pre></div></div>
-
- </li>
-
-
- <li id="section-9">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-9">¶</a>
- </div>
- <p>Preprocess the code to remove leading and trailing whitespace, carriage
- returns, etc. If we’re lexing literate CoffeeScript, strip external Markdown
- by removing all lines that aren’t indented by at least four spaces or a tab.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">clean</span>: <span class="hljs-function"><span class="hljs-params">(code)</span> -></span>
- code = code.slice(<span class="hljs-number">1</span>) <span class="hljs-keyword">if</span> code.charCodeAt(<span class="hljs-number">0</span>) <span class="hljs-keyword">is</span> BOM
- code = code.replace(<span class="hljs-regexp">/\r/g</span>, <span class="hljs-string">''</span>).replace TRAILING_SPACES, <span class="hljs-string">''</span>
- <span class="hljs-keyword">if</span> WHITESPACE.test code
- code = <span class="hljs-string">"\n<span class="hljs-subst">#{code}</span>"</span>
- <span class="hljs-property">@chunkLine</span>--
- code = invertLiterate code <span class="hljs-keyword">if</span> <span class="hljs-property">@literate</span>
- code</pre></div></div>
-
- </li>
-
-
- <li id="section-10">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-10">¶</a>
- </div>
- <h2 id="tokenizers">Tokenizers</h2>
- </div>
-
- </li>
-
-
- <li id="section-11">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-11">¶</a>
- </div>
-
- </div>
-
- </li>
-
-
- <li id="section-12">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-12">¶</a>
- </div>
- <p>Matches identifying literals: variables, keywords, method names, etc.
- Check to ensure that JavaScript reserved words aren’t being used as
- identifiers. Because CoffeeScript reserves a handful of keywords that are
- allowed in JavaScript, we’re careful not to tag them as keywords when
- referenced as property names here, so you can still do <code>jQuery.is()</code> even
- though <code>is</code> means <code>===</code> otherwise.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">identifierToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> match = IDENTIFIER.exec <span class="hljs-property">@chunk</span>
- [input, id, colon] = match</pre></div></div>
-
- </li>
-
-
- <li id="section-13">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-13">¶</a>
- </div>
- <p>Preserve length of id for location data</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> idLength = id.length
- poppedToken = <span class="hljs-literal">undefined</span>
- <span class="hljs-keyword">if</span> id <span class="hljs-keyword">is</span> <span class="hljs-string">'own'</span> <span class="hljs-keyword">and</span> <span class="hljs-property">@tag</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">'FOR'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'OWN'</span>, id
- <span class="hljs-keyword">return</span> id.length
- <span class="hljs-keyword">if</span> id <span class="hljs-keyword">is</span> <span class="hljs-string">'from'</span> <span class="hljs-keyword">and</span> <span class="hljs-property">@tag</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">'YIELD'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'FROM'</span>, id
- <span class="hljs-keyword">return</span> id.length
- [..., prev] = <span class="hljs-property">@tokens</span>
- forcedIdentifier = colon <span class="hljs-keyword">or</span> prev? <span class="hljs-keyword">and</span>
- (prev[<span class="hljs-number">0</span>] <span class="hljs-keyword">in</span> [<span class="hljs-string">'.'</span>, <span class="hljs-string">'?.'</span>, <span class="hljs-string">'::'</span>, <span class="hljs-string">'?::'</span>] <span class="hljs-keyword">or</span>
- <span class="hljs-keyword">not</span> prev.spaced <span class="hljs-keyword">and</span> prev[<span class="hljs-number">0</span>] <span class="hljs-keyword">is</span> <span class="hljs-string">'@'</span>)
- tag = <span class="hljs-string">'IDENTIFIER'</span>
- <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> forcedIdentifier <span class="hljs-keyword">and</span> (id <span class="hljs-keyword">in</span> JS_KEYWORDS <span class="hljs-keyword">or</span> id <span class="hljs-keyword">in</span> COFFEE_KEYWORDS)
- tag = id.toUpperCase()
- <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">is</span> <span class="hljs-string">'WHEN'</span> <span class="hljs-keyword">and</span> <span class="hljs-property">@tag</span>() <span class="hljs-keyword">in</span> LINE_BREAK
- tag = <span class="hljs-string">'LEADING_WHEN'</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">is</span> <span class="hljs-string">'FOR'</span>
- <span class="hljs-property">@seenFor</span> = <span class="hljs-literal">yes</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">is</span> <span class="hljs-string">'UNLESS'</span>
- tag = <span class="hljs-string">'IF'</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">in</span> UNARY
- tag = <span class="hljs-string">'UNARY'</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">in</span> RELATION
- <span class="hljs-keyword">if</span> tag <span class="hljs-keyword">isnt</span> <span class="hljs-string">'INSTANCEOF'</span> <span class="hljs-keyword">and</span> <span class="hljs-property">@seenFor</span>
- tag = <span class="hljs-string">'FOR'</span> + tag
- <span class="hljs-property">@seenFor</span> = <span class="hljs-literal">no</span>
- <span class="hljs-keyword">else</span>
- tag = <span class="hljs-string">'RELATION'</span>
- <span class="hljs-keyword">if</span> <span class="hljs-property">@value</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">'!'</span>
- poppedToken = <span class="hljs-property">@tokens</span>.pop()
- id = <span class="hljs-string">'!'</span> + id
- <span class="hljs-keyword">if</span> id <span class="hljs-keyword">in</span> JS_FORBIDDEN
- <span class="hljs-keyword">if</span> forcedIdentifier
- tag = <span class="hljs-string">'IDENTIFIER'</span>
- id = <span class="hljs-keyword">new</span> String id
- id.reserved = <span class="hljs-literal">yes</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> id <span class="hljs-keyword">in</span> RESERVED
- <span class="hljs-property">@error</span> <span class="hljs-string">"reserved word '<span class="hljs-subst">#{id}</span>'"</span>, <span class="hljs-attribute">length</span>: id.length
- <span class="hljs-keyword">unless</span> forcedIdentifier
- <span class="hljs-keyword">if</span> id <span class="hljs-keyword">in</span> COFFEE_ALIASES
- alias = id
- id = COFFEE_ALIAS_MAP[id]
- tag = <span class="hljs-keyword">switch</span> id
- <span class="hljs-keyword">when</span> <span class="hljs-string">'!'</span> <span class="hljs-keyword">then</span> <span class="hljs-string">'UNARY'</span>
- <span class="hljs-keyword">when</span> <span class="hljs-string">'=='</span>, <span class="hljs-string">'!='</span> <span class="hljs-keyword">then</span> <span class="hljs-string">'COMPARE'</span>
- <span class="hljs-keyword">when</span> <span class="hljs-string">'&&'</span>, <span class="hljs-string">'||'</span> <span class="hljs-keyword">then</span> <span class="hljs-string">'LOGIC'</span>
- <span class="hljs-keyword">when</span> <span class="hljs-string">'true'</span>, <span class="hljs-string">'false'</span> <span class="hljs-keyword">then</span> <span class="hljs-string">'BOOL'</span>
- <span class="hljs-keyword">when</span> <span class="hljs-string">'break'</span>, <span class="hljs-string">'continue'</span> <span class="hljs-keyword">then</span> <span class="hljs-string">'STATEMENT'</span>
- <span class="hljs-keyword">else</span> tag
- tagToken = <span class="hljs-property">@token</span> tag, id, <span class="hljs-number">0</span>, idLength
- tagToken.origin = [tag, alias, tagToken[<span class="hljs-number">2</span>]] <span class="hljs-keyword">if</span> alias
- tagToken.variable = <span class="hljs-keyword">not</span> forcedIdentifier
- <span class="hljs-keyword">if</span> poppedToken
- [tagToken[<span class="hljs-number">2</span>].first_line, tagToken[<span class="hljs-number">2</span>].first_column] =
- [poppedToken[<span class="hljs-number">2</span>].first_line, poppedToken[<span class="hljs-number">2</span>].first_column]
- <span class="hljs-keyword">if</span> colon
- colonOffset = input.lastIndexOf <span class="hljs-string">':'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">':'</span>, <span class="hljs-string">':'</span>, colonOffset, colon.length
- input.length</pre></div></div>
-
- </li>
-
-
- <li id="section-14">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-14">¶</a>
- </div>
- <p>Matches numbers, including decimals, hex, and exponential notation.
- Be careful not to interfere with ranges-in-progress.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">numberToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> match = NUMBER.exec <span class="hljs-property">@chunk</span>
- number = match[<span class="hljs-number">0</span>]
- lexedLength = number.length
- <span class="hljs-keyword">if</span> <span class="hljs-regexp">/^0[BOX]/</span>.test number
- <span class="hljs-property">@error</span> <span class="hljs-string">"radix prefix in '<span class="hljs-subst">#{number}</span>' must be lowercase"</span>, <span class="hljs-attribute">offset</span>: <span class="hljs-number">1</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> <span class="hljs-regexp">/E/</span>.test(number) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> <span class="hljs-regexp">/^0x/</span>.test number
- <span class="hljs-property">@error</span> <span class="hljs-string">"exponential notation in '<span class="hljs-subst">#{number}</span>' must be indicated with a lowercase 'e'"</span>,
- <span class="hljs-attribute">offset</span>: number.indexOf(<span class="hljs-string">'E'</span>)
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> <span class="hljs-regexp">/^0\d*[89]/</span>.test number
- <span class="hljs-property">@error</span> <span class="hljs-string">"decimal literal '<span class="hljs-subst">#{number}</span>' must not be prefixed with '0'"</span>, <span class="hljs-attribute">length</span>: lexedLength
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> <span class="hljs-regexp">/^0\d+/</span>.test number
- <span class="hljs-property">@error</span> <span class="hljs-string">"octal literal '<span class="hljs-subst">#{number}</span>' must be prefixed with '0o'"</span>, <span class="hljs-attribute">length</span>: lexedLength
- <span class="hljs-keyword">if</span> octalLiteral = <span class="hljs-regexp">/^0o([0-7]+)/</span>.exec number
- number = <span class="hljs-string">'0x'</span> + parseInt(octalLiteral[<span class="hljs-number">1</span>], <span class="hljs-number">8</span>).toString <span class="hljs-number">16</span>
- <span class="hljs-keyword">if</span> binaryLiteral = <span class="hljs-regexp">/^0b([01]+)/</span>.exec number
- number = <span class="hljs-string">'0x'</span> + parseInt(binaryLiteral[<span class="hljs-number">1</span>], <span class="hljs-number">2</span>).toString <span class="hljs-number">16</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'NUMBER'</span>, number, <span class="hljs-number">0</span>, lexedLength
- lexedLength</pre></div></div>
-
- </li>
-
-
- <li id="section-15">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-15">¶</a>
- </div>
- <p>Matches strings, including multi-line strings, as well as heredocs, with or without
- interpolation.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">stringToken</span>:<span class="hljs-function"> -></span>
- [quote] = STRING_START.exec(<span class="hljs-property">@chunk</span>) || []
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> quote
- regex = <span class="hljs-keyword">switch</span> quote
- <span class="hljs-keyword">when</span> <span class="hljs-string">"'"</span> <span class="hljs-keyword">then</span> STRING_SINGLE
- <span class="hljs-keyword">when</span> <span class="hljs-string">'"'</span> <span class="hljs-keyword">then</span> STRING_DOUBLE
- <span class="hljs-keyword">when</span> <span class="hljs-string">"'''"</span> <span class="hljs-keyword">then</span> HEREDOC_SINGLE
- <span class="hljs-keyword">when</span> <span class="hljs-string">'"""'</span> <span class="hljs-keyword">then</span> HEREDOC_DOUBLE
- heredoc = quote.length <span class="hljs-keyword">is</span> <span class="hljs-number">3</span>
- {tokens, <span class="hljs-attribute">index</span>: end} = <span class="hljs-property">@matchWithInterpolations</span> regex, quote
- $ = tokens.length - <span class="hljs-number">1</span>
- delimiter = quote.charAt(<span class="hljs-number">0</span>)
- <span class="hljs-keyword">if</span> heredoc</pre></div></div>
-
- </li>
-
-
- <li id="section-16">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-16">¶</a>
- </div>
- <p>Find the smallest indentation. It will be removed from all lines later.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> indent = <span class="hljs-literal">null</span>
- doc = (token[<span class="hljs-number">1</span>] <span class="hljs-keyword">for</span> token, i <span class="hljs-keyword">in</span> tokens <span class="hljs-keyword">when</span> token[<span class="hljs-number">0</span>] <span class="hljs-keyword">is</span> <span class="hljs-string">'NEOSTRING'</span>).join <span class="hljs-string">'#{}'</span>
- <span class="hljs-keyword">while</span> match = HEREDOC_INDENT.exec doc
- attempt = match[<span class="hljs-number">1</span>]
- indent = attempt <span class="hljs-keyword">if</span> indent <span class="hljs-keyword">is</span> <span class="hljs-literal">null</span> <span class="hljs-keyword">or</span> <span class="hljs-number">0</span> < attempt.length < indent.length
- indentRegex = <span class="hljs-regexp">/// ^<span class="hljs-subst">#{indent}</span> ///</span>gm <span class="hljs-keyword">if</span> indent
- <span class="hljs-property">@mergeInterpolationTokens</span> tokens, {delimiter}, <span class="hljs-function"><span class="hljs-params">(value, i)</span> =></span>
- value = <span class="hljs-property">@formatString</span> value
- value = value.replace LEADING_BLANK_LINE, <span class="hljs-string">''</span> <span class="hljs-keyword">if</span> i <span class="hljs-keyword">is</span> <span class="hljs-number">0</span>
- value = value.replace TRAILING_BLANK_LINE, <span class="hljs-string">''</span> <span class="hljs-keyword">if</span> i <span class="hljs-keyword">is</span> $
- value = value.replace indentRegex, <span class="hljs-string">''</span> <span class="hljs-keyword">if</span> indentRegex
- value
- <span class="hljs-keyword">else</span>
- <span class="hljs-property">@mergeInterpolationTokens</span> tokens, {delimiter}, <span class="hljs-function"><span class="hljs-params">(value, i)</span> =></span>
- value = <span class="hljs-property">@formatString</span> value
- value = value.replace SIMPLE_STRING_OMIT, <span class="hljs-function"><span class="hljs-params">(match, offset)</span> -></span>
- <span class="hljs-keyword">if</span> (i <span class="hljs-keyword">is</span> <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> offset <span class="hljs-keyword">is</span> <span class="hljs-number">0</span>) <span class="hljs-keyword">or</span>
- (i <span class="hljs-keyword">is</span> $ <span class="hljs-keyword">and</span> offset + match.length <span class="hljs-keyword">is</span> value.length)
- <span class="hljs-string">''</span>
- <span class="hljs-keyword">else</span>
- <span class="hljs-string">' '</span>
- value
- end</pre></div></div>
-
- </li>
-
-
- <li id="section-17">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-17">¶</a>
- </div>
- <p>Matches and consumes comments.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">commentToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> match = <span class="hljs-property">@chunk</span>.match COMMENT
- [comment, here] = match
- <span class="hljs-keyword">if</span> here
- <span class="hljs-keyword">if</span> match = HERECOMMENT_ILLEGAL.exec comment
- <span class="hljs-property">@error</span> <span class="hljs-string">"block comments cannot contain <span class="hljs-subst">#{match[<span class="hljs-number">0</span>]}</span>"</span>,
- <span class="hljs-attribute">offset</span>: match.index, <span class="hljs-attribute">length</span>: match[<span class="hljs-number">0</span>].length
- <span class="hljs-keyword">if</span> here.indexOf(<span class="hljs-string">'\n'</span>) >= <span class="hljs-number">0</span>
- here = here.replace <span class="hljs-regexp">/// \n <span class="hljs-subst">#{repeat <span class="hljs-string">' '</span>, <span class="hljs-property">@indent</span>}</span> ///</span>g, <span class="hljs-string">'\n'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'HERECOMMENT'</span>, here, <span class="hljs-number">0</span>, comment.length
- comment.length</pre></div></div>
-
- </li>
-
-
- <li id="section-18">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-18">¶</a>
- </div>
- <p>Matches JavaScript interpolated directly into the source via backticks.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">jsToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> <span class="hljs-property">@chunk</span>.charAt(<span class="hljs-number">0</span>) <span class="hljs-keyword">is</span> <span class="hljs-string">'`'</span> <span class="hljs-keyword">and</span> match = JSTOKEN.exec <span class="hljs-property">@chunk</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'JS'</span>, (script = match[<span class="hljs-number">0</span>])[<span class="hljs-number">1.</span>..-<span class="hljs-number">1</span>], <span class="hljs-number">0</span>, script.length
- script.length</pre></div></div>
-
- </li>
-
-
- <li id="section-19">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-19">¶</a>
- </div>
- <p>Matches regular expression literals, as well as multiline extended ones.
- Lexing regular expressions is difficult to distinguish from division, so we
- borrow some basic heuristics from JavaScript and Ruby.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">regexToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">switch</span>
- <span class="hljs-keyword">when</span> match = REGEX_ILLEGAL.exec <span class="hljs-property">@chunk</span>
- <span class="hljs-property">@error</span> <span class="hljs-string">"regular expressions cannot begin with <span class="hljs-subst">#{match[<span class="hljs-number">2</span>]}</span>"</span>,
- <span class="hljs-attribute">offset</span>: match.index + match[<span class="hljs-number">1</span>].length
- <span class="hljs-keyword">when</span> match = <span class="hljs-property">@matchWithInterpolations</span> HEREGEX, <span class="hljs-string">'///'</span>
- {tokens, index} = match
- <span class="hljs-keyword">when</span> match = REGEX.exec <span class="hljs-property">@chunk</span>
- [regex, body, closed] = match
- <span class="hljs-property">@validateEscapes</span> body, <span class="hljs-attribute">isRegex</span>: <span class="hljs-literal">yes</span>, <span class="hljs-attribute">offsetInChunk</span>: <span class="hljs-number">1</span>
- index = regex.length
- [..., prev] = <span class="hljs-property">@tokens</span>
- <span class="hljs-keyword">if</span> prev
- <span class="hljs-keyword">if</span> prev.spaced <span class="hljs-keyword">and</span> prev[<span class="hljs-number">0</span>] <span class="hljs-keyword">in</span> CALLABLE
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> closed <span class="hljs-keyword">or</span> POSSIBLY_DIVISION.test regex
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> prev[<span class="hljs-number">0</span>] <span class="hljs-keyword">in</span> NOT_REGEX
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
- <span class="hljs-property">@error</span> <span class="hljs-string">'missing / (unclosed regex)'</span> <span class="hljs-keyword">unless</span> closed
- <span class="hljs-keyword">else</span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>
- [flags] = REGEX_FLAGS.exec <span class="hljs-property">@chunk</span>[index..]
- end = index + flags.length
- origin = <span class="hljs-property">@makeToken</span> <span class="hljs-string">'REGEX'</span>, <span class="hljs-literal">null</span>, <span class="hljs-number">0</span>, end
- <span class="hljs-keyword">switch</span>
- <span class="hljs-keyword">when</span> <span class="hljs-keyword">not</span> VALID_FLAGS.test flags
- <span class="hljs-property">@error</span> <span class="hljs-string">"invalid regular expression flags <span class="hljs-subst">#{flags}</span>"</span>, <span class="hljs-attribute">offset</span>: index, <span class="hljs-attribute">length</span>: flags.length
- <span class="hljs-keyword">when</span> regex <span class="hljs-keyword">or</span> tokens.length <span class="hljs-keyword">is</span> <span class="hljs-number">1</span>
- body ?= <span class="hljs-property">@formatHeregex</span> tokens[<span class="hljs-number">0</span>][<span class="hljs-number">1</span>]
- <span class="hljs-property">@token</span> <span class="hljs-string">'REGEX'</span>, <span class="hljs-string">"<span class="hljs-subst">#{<span class="hljs-property">@makeDelimitedLiteral</span> body, delimiter: <span class="hljs-string">'/'</span>}</span><span class="hljs-subst">#{flags}</span>"</span>, <span class="hljs-number">0</span>, end, origin
- <span class="hljs-keyword">else</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'REGEX_START'</span>, <span class="hljs-string">'('</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, origin
- <span class="hljs-property">@token</span> <span class="hljs-string">'IDENTIFIER'</span>, <span class="hljs-string">'RegExp'</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'CALL_START'</span>, <span class="hljs-string">'('</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>
- <span class="hljs-property">@mergeInterpolationTokens</span> tokens, {<span class="hljs-attribute">delimiter</span>: <span class="hljs-string">'"'</span>, <span class="hljs-attribute">double</span>: <span class="hljs-literal">yes</span>}, <span class="hljs-property">@formatHeregex</span>
- <span class="hljs-keyword">if</span> flags
- <span class="hljs-property">@token</span> <span class="hljs-string">','</span>, <span class="hljs-string">','</span>, index, <span class="hljs-number">0</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'STRING'</span>, <span class="hljs-string">'"'</span> + flags + <span class="hljs-string">'"'</span>, index, flags.length
- <span class="hljs-property">@token</span> <span class="hljs-string">')'</span>, <span class="hljs-string">')'</span>, end, <span class="hljs-number">0</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'REGEX_END'</span>, <span class="hljs-string">')'</span>, end, <span class="hljs-number">0</span>
- end</pre></div></div>
-
- </li>
-
-
- <li id="section-20">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-20">¶</a>
- </div>
- <p>Matches newlines, indents, and outdents, and determines which is which.
- If we can detect that the current line is continued onto the next line,
- then the newline is suppressed:</p>
- <pre><code>elements
- .each( ... )
- .map( ... )
- </code></pre><p>Keeps track of the level of indentation, because a single outdent token
- can close multiple indents, so we need to know how far in we happen to be.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">lineToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> match = MULTI_DENT.exec <span class="hljs-property">@chunk</span>
- indent = match[<span class="hljs-number">0</span>]
- <span class="hljs-property">@seenFor</span> = <span class="hljs-literal">no</span>
- size = indent.length - <span class="hljs-number">1</span> - indent.lastIndexOf <span class="hljs-string">'\n'</span>
- noNewlines = <span class="hljs-property">@unfinished</span>()
- <span class="hljs-keyword">if</span> size - <span class="hljs-property">@indebt</span> <span class="hljs-keyword">is</span> <span class="hljs-property">@indent</span>
- <span class="hljs-keyword">if</span> noNewlines <span class="hljs-keyword">then</span> <span class="hljs-property">@suppressNewlines</span>() <span class="hljs-keyword">else</span> <span class="hljs-property">@newlineToken</span> <span class="hljs-number">0</span>
- <span class="hljs-keyword">return</span> indent.length
- <span class="hljs-keyword">if</span> size > <span class="hljs-property">@indent</span>
- <span class="hljs-keyword">if</span> noNewlines
- <span class="hljs-property">@indebt</span> = size - <span class="hljs-property">@indent</span>
- <span class="hljs-property">@suppressNewlines</span>()
- <span class="hljs-keyword">return</span> indent.length
- <span class="hljs-keyword">unless</span> <span class="hljs-property">@tokens</span>.length
- <span class="hljs-property">@baseIndent</span> = <span class="hljs-property">@indent</span> = size
- <span class="hljs-keyword">return</span> indent.length
- diff = size - <span class="hljs-property">@indent</span> + <span class="hljs-property">@outdebt</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'INDENT'</span>, diff, indent.length - size, size
- <span class="hljs-property">@indents</span>.push diff
- <span class="hljs-property">@ends</span>.push {<span class="hljs-attribute">tag</span>: <span class="hljs-string">'OUTDENT'</span>}
- <span class="hljs-property">@outdebt</span> = <span class="hljs-property">@indebt</span> = <span class="hljs-number">0</span>
- <span class="hljs-property">@indent</span> = size
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> size < <span class="hljs-property">@baseIndent</span>
- <span class="hljs-property">@error</span> <span class="hljs-string">'missing indentation'</span>, <span class="hljs-attribute">offset</span>: indent.length
- <span class="hljs-keyword">else</span>
- <span class="hljs-property">@indebt</span> = <span class="hljs-number">0</span>
- <span class="hljs-property">@outdentToken</span> <span class="hljs-property">@indent</span> - size, noNewlines, indent.length
- indent.length</pre></div></div>
-
- </li>
-
-
- <li id="section-21">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-21">¶</a>
- </div>
- <p>Record an outdent token or multiple tokens, if we happen to be moving back
- inwards past several recorded indents. Sets new @indent value.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">outdentToken</span>: <span class="hljs-function"><span class="hljs-params">(moveOut, noNewlines, outdentLength)</span> -></span>
- decreasedIndent = <span class="hljs-property">@indent</span> - moveOut
- <span class="hljs-keyword">while</span> moveOut > <span class="hljs-number">0</span>
- lastIndent = <span class="hljs-property">@indents</span>[<span class="hljs-property">@indents</span>.length - <span class="hljs-number">1</span>]
- <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> lastIndent
- moveOut = <span class="hljs-number">0</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> lastIndent <span class="hljs-keyword">is</span> <span class="hljs-property">@outdebt</span>
- moveOut -= <span class="hljs-property">@outdebt</span>
- <span class="hljs-property">@outdebt</span> = <span class="hljs-number">0</span>
- <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> lastIndent < <span class="hljs-property">@outdebt</span>
- <span class="hljs-property">@outdebt</span> -= lastIndent
- moveOut -= lastIndent
- <span class="hljs-keyword">else</span>
- dent = <span class="hljs-property">@indents</span>.pop() + <span class="hljs-property">@outdebt</span>
- <span class="hljs-keyword">if</span> outdentLength <span class="hljs-keyword">and</span> <span class="hljs-property">@chunk</span>[outdentLength] <span class="hljs-keyword">in</span> INDENTABLE_CLOSERS
- decreasedIndent -= dent - moveOut
- moveOut = dent
- <span class="hljs-property">@outdebt</span> = <span class="hljs-number">0</span></pre></div></div>
-
- </li>
-
-
- <li id="section-22">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-22">¶</a>
- </div>
- <p>pair might call outdentToken, so preserve decreasedIndent</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-property">@pair</span> <span class="hljs-string">'OUTDENT'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'OUTDENT'</span>, moveOut, <span class="hljs-number">0</span>, outdentLength
- moveOut -= dent
- <span class="hljs-property">@outdebt</span> -= moveOut <span class="hljs-keyword">if</span> dent
- <span class="hljs-property">@tokens</span>.pop() <span class="hljs-keyword">while</span> <span class="hljs-property">@value</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">';'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'TERMINATOR'</span>, <span class="hljs-string">'\n'</span>, outdentLength, <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> <span class="hljs-property">@tag</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">'TERMINATOR'</span> <span class="hljs-keyword">or</span> noNewlines
- <span class="hljs-property">@indent</span> = decreasedIndent
- <span class="hljs-keyword">this</span></pre></div></div>
-
- </li>
-
-
- <li id="section-23">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-23">¶</a>
- </div>
- <p>Matches and consumes non-meaningful whitespace. Tag the previous token
- as being “spaced”, because there are some cases where it makes a difference.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">whitespaceToken</span>:<span class="hljs-function"> -></span>
- <span class="hljs-keyword">return</span> <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> (match = WHITESPACE.exec <span class="hljs-property">@chunk</span>) <span class="hljs-keyword">or</span>
- (nline = <span class="hljs-property">@chunk</span>.charAt(<span class="hljs-number">0</span>) <span class="hljs-keyword">is</span> <span class="hljs-string">'\n'</span>)
- [..., prev] = <span class="hljs-property">@tokens</span>
- prev[<span class="hljs-keyword">if</span> match <span class="hljs-keyword">then</span> <span class="hljs-string">'spaced'</span> <span class="hljs-keyword">else</span> <span class="hljs-string">'newLine'</span>] = <span class="hljs-literal">true</span> <span class="hljs-keyword">if</span> prev
- <span class="hljs-keyword">if</span> match <span class="hljs-keyword">then</span> match[<span class="hljs-number">0</span>].length <span class="hljs-keyword">else</span> <span class="hljs-number">0</span></pre></div></div>
-
- </li>
-
-
- <li id="section-24">
- <div class="annotation">
-
- <div class="pilwrap ">
- <a class="pilcrow" href="#section-24">¶</a>
- </div>
- <p>Generate a newline token. Consecutive newlines get merged together.</p>
- </div>
-
- <div class="content"><div class='highlight'><pre> <span class="hljs-attribute">newlineToken</span>: <span class="hljs-function"><span class="hljs-params">(offset)</span> -></span>
- <span class="hljs-property">@tokens</span>.pop() <span class="hljs-keyword">while</span> <span class="hljs-property">@value</span>() <span class="hljs-keyword">is</span> <span class="hljs-string">';'</span>
- <span class="hljs-property">@token</span> <span class="hljs-string">'TERMINATOR'</span>, <span class="hljs-string">'\n'</span>, offset, <span class="hljs-number">0</span> <span class="hljs-keyword">unless</span> <s…