Back to Coffeescript

helpers.coffee

docs/v2/annotated-source/helpers.html

2.7.012.4 KB
Original Source

browser.coffeecake.coffeecoffeescript.coffeecommand.coffeegrammar.coffeehelpers.coffeeindex.coffeelexer.coffeenodes.coffeeoptparse.coffeeregister.coffeerepl.coffeerewriter.coffeescope.litcoffeesourcemap.litcoffee

helpers.coffee

§

This file contains the common helper functions that we’d like to share among the Lexer , Rewriter , and the Nodes. Merge objects, flatten arrays, count characters, that sort of thing.

§

Peek at the beginning of a given string to see if it matches a sequence.

exports.starts =(string, literal, start) -\>literalisstring.substr start, literal.length

§

Peek at the end of a given string to see if it matches a sequence.

exports.ends =(string, literal, back) -\>len = literal.length
  literalisstring.substr string.length - len - (backor0), len

§

Repeat a string n times.

exports.repeat = repeat =(str, n) -\>

§

Use clever algorithm to have O(log(n)) string concatenation operations.

res =''whilen >0res += strifn &1n >>>=1str += str
  res

§

Trim out all falsy values from an array.

exports.compact =(array) -\>itemforiteminarraywhenitem

§

Count the number of occurrences of a string in a string.

exports.count =(string, substr) -\>num = pos =0return1/0unlesssubstr.length
  num++whilepos =1+ string.indexOf substr, pos
  num

§

Merge objects, returning a fresh copy with attributes from both sides. Used every time Base#compile is called, to allow properties in the options hash to propagate down the tree without polluting other branches.

exports.merge =(options, overrides) -\>extend (extend {}, options), overrides

§

Extend a source object with the properties of another object (shallow copy).

extend =exports.extend =(object, properties) -\>forkey, valofproperties
    object[key] = val
  object

§

Return a flattened version of an array. Handy for getting a list of children from the nodes.

exports.flatten = flatten =(array) -\>array.flat(Infinity)

§

Delete a key from an object, returning the value. Useful when a node is looking for a particular method in an options hash.

exports.del =(obj, key) -\>val = obj[key]deleteobj[key]
  val

§

Typical Array::some

exports.some = Array::some ? (fn) ->returntrueforeinthiswhenfn efalse

§

Helper function for extracting code from Literate CoffeeScript by stripping out all non-code blocks, producing a string of CoffeeScript code that can be compiled “normally.”

exports.invertLiterate =(code) -\>out = []
  blankLine =/^\s\*$/indented =/^[\t]/listItemStart =/// ^ (?:\t?|\ {0,3}) # Up to one tab, or up to three spaces, or neither; (?: [\*\-\+] | # followed by `*`, `-` or `+`; [0-9]{1,9}\. # or by an integer up to 9 digits long, followed by a period; ) [\ \t] # followed by a space or a tab. ///insideComment =noforlineincode.split('\n')ifblankLine.test(line)
      insideComment =noout.push lineelseifinsideCommentorlistItemStart.test(line)
      insideComment =yesout.push"# #{line}"elseifnotinsideCommentandindented.test(line)
      out.push lineelseinsideComment =yesout.push"# #{line}"out.join'\n'

§

Merge two jison-style location data objects together. If last is not provided, this will simply return first.

buildLocationData = (first, last) -\>ifnotlast
    firstelsefirst_line: first.first_line
    first_column: first.first_column
    last_line: last.last_line
    last_column: last.last_column
    last_line_exclusive: last.last_line_exclusive
    last_column_exclusive: last.last_column_exclusive
    range: [
      first.range[0]
      last.range[1]
    ]

§

Build a list of all comments attached to tokens.

exports.extractAllCommentTokens =(tokens) -\>allCommentsObj = {}fortokenintokenswhentoken.commentsforcommentintoken.comments
      commentKey = comment.locationData.range[0]
      allCommentsObj[commentKey] = comment
  sortedKeys =Object.keys(allCommentsObj).sort (a, b) -> a - bforkeyinsortedKeys
    allCommentsObj[key]

§

Get a lookup hash for a token based on its location data. Multiple tokens might have the same location hash, but using exclusive location data distinguishes e.g. zero-length generated tokens from actual source tokens.

buildLocationHash = (loc) -\>"#{loc.range[0]}-#{loc.range[1]}"

§

Build a dictionary of extra token properties organized by tokens’ locations used as lookup hashes.

exports.buildTokenDataDictionary = buildTokenDataDictionary =(tokens) -\>tokenData = {}fortokenintokenswhentoken.comments
    tokenHash = buildLocationHash token[2]

§

Multiple tokens might have the same location hash, such as the generated JS tokens added at the start or end of the token stream to hold comments that start or end a file.

tokenData[tokenHash] ?= {}iftoken.comments# `comments` is always an array.

§

For “overlapping” tokens, that is tokens with the same location data and therefore matching tokenHashes, merge the comments from both/all tokens together into one array, even if there are duplicate comments; they will get sorted out later.

(tokenData[tokenHash].comments ?= []).push token.comments...
  tokenData

§

This returns a function which takes an object as a parameter, and if that object is an AST node, updates that object’s locationData. The object is returned either way.

exports.addDataToNode =(parserState, firstLocationData, firstValue, lastLocationData, lastValue, forceUpdateLocation = yes) -\>(obj) ->

§

Add location data.

locationData = buildLocationData(firstValue?.locationData ? firstLocationData, lastValue?.locationData ? lastLocationData)ifobj?.updateLocationDataIfMissing?andfirstLocationData?
      obj.updateLocationDataIfMissing locationData, forceUpdateLocationelseobj.locationData = locationData

§

Add comments, building the dictionary of token data if it hasn’t been built yet.

parserState.tokenData ?= buildTokenDataDictionary parserState.parser.tokensifobj.locationData?
      objHash = buildLocationHash obj.locationDataifparserState.tokenData[objHash]?.comments?
        attachCommentsToNode parserState.tokenData[objHash].comments, obj
    objexports.attachCommentsToNode = attachCommentsToNode =(comments, node) -\>returnifnotcomments?orcomments.lengthis0node.comments ?= []
  node.comments.push comments...

§

Convert jison location data to a string. obj can be a token, or a locationData.

exports.locationDataToString =(obj) -\>if("2"ofobj)and("first\_line"ofobj[2])thenlocationData = obj[2]elseif"first\_line"ofobjthenlocationData = objiflocationData"#{locationData.first\_line + 1}:#{locationData.first\_column + 1}-"+"#{locationData.last\_line + 1}:#{locationData.last\_column + 1}"else"No location data"

§

Generate a unique anonymous file name so we can distinguish source map cache entries for any number of anonymous scripts.

exports.anonymousFileName =do->
  n =0->"\<anonymous-#{n++}\>"

§

A .coffee.md compatible version of basename, that returns the file sans-extension.

exports.baseFileName =(file, stripExt = no, useWinPathSep = no) -\>pathSep =ifuseWinPathSepthen/\\|\//else/\//parts = file.split(pathSep)
  file = parts[parts.length -1]returnfileunlessstripExtandfile.indexOf('.') >=0parts = file.split('.')
  parts.pop()
  parts.pop()ifparts[parts.length -1]is'coffee'andparts.length >1parts.join('.')

§

Determine if a filename represents a CoffeeScript file.

exports.isCoffee =(file) -\>/\.((lit)?coffee|coffee\.md)$/.test file

§

Determine if a filename represents a Literate CoffeeScript file.

exports.isLiterate =(file) -\>/\.(litcoffee|coffee\.md)$/.test file

§

Throws a SyntaxError from a given location. The error’s toString will return an error message following the “standard” format <filename>:<line>:<col>: <message> plus the line with the error and a marker showing where the error is.

exports.throwSyntaxError =(message, location) -\>error =newSyntaxErrormessage
  error.location = location
  error.toString = syntaxErrorToString

§

Instead of showing the compiler’s stacktrace, show our custom error message (this is useful when the error bubbles up in Node.js applications that compile CoffeeScript for example).

error.stack = error.toString()throwerror

§

Update a compiler SyntaxError with source code information if it didn’t have it already.

exports.updateSyntaxError =(error, code, filename) -\>

§

Avoid screwing up the stack property of other errors (i.e. possible bugs).

iferror.toStringissyntaxErrorToString
    error.codeor= code
    error.filenameor= filename
    error.stack = error.toString()
  error syntaxErrorToString = -\>returnError::toString.call @unless@codeand@location

  {first_line, first_column, last_line, last_column} = @location
  last_line ?= first_line
  last_column ?= first_columnif@filename?.startsWith'\<anonymous'filename ='[stdin]'elsefilename = @filenameor'[stdin]'codeLine = @code.split('\n')[first_line]
  start = first_column

§

Show only the first line on multi-line errors.

end =iffirst_lineislast_linethenlast_column +1elsecodeLine.length
  marker = codeLine[...start].replace(/[^\s]/g,' ') + repeat('^', end - start)

§

Check to see if we’re running on a color-enabled TTY.

ifprocess?
    colorsEnabled = process.stdout?.isTTYandnotprocess.env?.NODE_DISABLE_COLORSif@colorful ? colorsEnabledcolorize = (str) -\>"\x1B[1;31m#{str}\x1B[0m"codeLine = codeLine[...start] + colorize(codeLine[start...end]) + codeLine[end..]
    marker = colorize marker""" #{filename}:#{first\_line + 1}:#{first\_column + 1}: error: #{@message} #{codeLine} #{marker} """exports.nameWhitespaceCharacter =(string) -\>switchstringwhen' 'then'space'when'\n'then'newline'when'\r'then'carriage return'when'\t'then'tab'elsestringexports.parseNumber =(string) -\>returnNaNunlessstring?

  base =switchstring.charAt1when'b'then2when'o'then8when'x'then16elsenullifbase?parseIntstring[2..].replace(/\_/g,''), baseelseparseFloatstring.replace(/\_/g,'')exports.isFunction =(obj) -\>Object::toString.call(obj)is'[object Function]'exports.isNumber = isNumber =(obj) -\>Object::toString.call(obj)is'[object Number]'exports.isString = isString =(obj) -\>Object::toString.call(obj)is'[object String]'exports.isBoolean = isBoolean =(obj) -\>objisyesorobjisnoorObject::toString.call(obj)is'[object Boolean]'exports.isPlainObject =(obj) -\>typeofobjis'object'and!!objandnotArray.isArray(obj)andnotisNumber(obj)andnotisString(obj)andnotisBoolean(obj) unicodeCodePointToUnicodeEscapes = (codePoint) -\>toUnicodeEscape = (val) -\>str = val.toString16"\\u#{repeat '0', 4 - str.length}#{str}"returntoUnicodeEscape(codePoint)ifcodePoint <0x10000

§

surrogate pair

high =Math.floor((codePoint -0x10000) /0x400) +0xD800low = (codePoint -0x10000) %0x400+0xDC00"#{toUnicodeEscape(high)}#{toUnicodeEscape(low)}"

§

Replace \u{...} with \uxxxx[\uxxxx] in regexes without u flag

exports.replaceUnicodeCodePointEscapes =(str, {flags, error, delimiter = ''} = {}) -\>shouldReplace = flags?and'u'notinflags
  str.replace UNICODE_CODE_POINT_ESCAPE,(match, escapedBackslash, codePointHex, offset) -\>returnescapedBackslashifescapedBackslash

    codePointDecimal =parseIntcodePointHex,16ifcodePointDecimal >0x10fffferror"unicode code point escapes greater than \\u{10ffff} are not allowed",
        offset: offset + delimiter.length
        length: codePointHex.length +4returnmatchunlessshouldReplace

    unicodeCodePointToUnicodeEscapes codePointDecimal

UNICODE_CODE_POINT_ESCAPE =/// ( \\\\ ) # Make sure the escape isn’t escaped. | \\u\{ ( [\da-fA-F]+ ) \} ///g