     else
       @token 'STRING', @makeString doc, quote, yes
     @line += count heredoc, '\n'
-    @i += heredoc.length
-    true
+    heredoc.length
 
   # Matches and consumes comments.
   commentToken: ->
-    return false unless match = @chunk.match COMMENT
+    return 0 unless match = @chunk.match COMMENT
     [comment, here] = match
     @line += count comment, '\n'
-    @i += comment.length
     if here
       @token 'HERECOMMENT', @sanitizeHeredoc here,
         herecomment: true, indent: Array(@indent + 1).join(' ')
       @token 'TERMINATOR', '\n'
-    true
+    comment.length
 
   # Matches JavaScript interpolated directly into the source via backticks.
   jsToken: ->
-    return false unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
+    return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
     @token 'JS', (script = match[0]).slice 1, -1
-    @i += script.length
-    true
+    script.length
 
   # Matches regular expression literals. Lexing regular expressions is difficult
   # to distinguish from division, so we borrow some basic heuristics from
   # JavaScript and Ruby.
   regexToken: ->
-    return false if @chunk.charAt(0) isnt '/'
+    return 0 if @chunk.charAt(0) isnt '/'
     return @heregexToken match if match = HEREGEX.exec @chunk
-    return false if @tag() in NOT_REGEX
-    return false unless match = REGEX.exec @chunk
+    return 0 if @tag() in NOT_REGEX
+    return 0 unless match = REGEX.exec @chunk
     [regex] = match
     @token 'REGEX', if regex is '//' then '/(?:)/' else regex
-    @i += regex.length
-    true
+    regex.length
 
   # Matches experimental, multiline and extended regular expression literals.
   heregexToken: (match) ->
     [heregex, body, flags] = match
-    @i += heregex.length
     if 0 > body.indexOf '#{'
       re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
       @token 'REGEX', "/#{ re or '(?:)' }/#{flags}"
-      return true
+      return heregex.length
     @token 'IDENTIFIER', 'RegExp'
     @tokens.push ['CALL_START', '(']
     tokens = []

    # At every position, run through this list of attempted matches,
    # short-circuiting if any of them succeed. Their order determines precedence:
    # `@literalToken` is the fallback catch-all.
    while @chunk = code.slice @i
      @identifierToken() or
      @commentToken()    or
      @whitespaceToken() or
      @lineToken()       or
      @heredocToken()    or
      @stringToken()     or
      @numberToken()     or
      @regexToken()      or
      @jsToken()         or
      @literalToken()
    @closeIndentation()
    return @tokens if o.rewrite is off
    (new Rewriter).rewrite @tokens

      @indebt = 0
      @outdentToken @indent - size, noNewlines
    @indent = size
    indent.length

  # Record an outdent token or multiple tokens, if we happen to be moving back
  # inwards past several recorded indents.
