exports.starts = (string, literal, start) ->
literal is string.substr start, literal.length
此檔案包含我們希望在詞法分析器、重寫器和節點之間共用的常見輔助函式。合併物件、壓平陣列、計算字元,這類的事情。
窺視指定字串的開頭,看看是否符合某個順序。
exports.starts = (string, literal, start) ->
literal is string.substr start, literal.length
窺視指定字串的結尾,看看是否符合某個順序。
exports.ends = (string, literal, back) ->
len = literal.length
literal is string.substr string.length - len - (back or 0), len
重複某個字串 n
次。
exports.repeat = repeat = (str, n) ->
使用聰明的演算法,讓字串串接運算的複雜度為 O(log(n))。
res = ''
while n > 0
res += str if n & 1
n >>>= 1
str += str
res
從陣列中移除所有假值。
exports.compact = (array) ->
item for item in array when item
計算字串中某個字串出現的次數。
exports.count = (string, substr) ->
num = pos = 0
return 1/0 unless substr.length
num++ while pos = 1 + string.indexOf substr, pos
num
合併物件,傳回一個包含兩邊屬性的全新副本。每次呼叫 Base#compile
時都會使用,讓選項雜湊中的屬性可以向下傳播到樹狀結構,而不會污染其他分支。
exports.merge = (options, overrides) ->
extend (extend {}, options), overrides
使用另一個物件的屬性來擴充原始物件 (淺層複製)。
extend = exports.extend = (object, properties) ->
for key, val of properties
object[key] = val
object
傳回陣列的壓平版本。對於從節點取得 children
清單很有用。
exports.flatten = flatten = (array) ->
array.flat(Infinity)
從物件中刪除一個金鑰,並回傳其值。當一個節點在一個選項雜湊中尋找一個特定方法時,這會很有用。
exports.del = (obj, key) ->
val = obj[key]
delete obj[key]
val
典型的 Array::some
exports.some = Array::some ? (fn) ->
return true for e in this when fn e
false
一個輔助函式,用於透過移除所有非程式碼區塊來從 Literate CoffeeScript 中萃取程式碼,產生一段可以「正常」編譯的 CoffeeScript 程式碼。
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 = no
for line in code.split('\n')
if blankLine.test(line)
insideComment = no
out.push line
else if insideComment or listItemStart.test(line)
insideComment = yes
out.push "# #{line}"
else if not insideComment and indented.test(line)
out.push line
else
insideComment = yes
out.push "# #{line}"
out.join '\n'
將兩個 jison 風格的位置資料物件合併在一起。如果未提供 last
,這只會回傳 first
。
buildLocationData = (first, last) ->
if not last
first
else
first_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]
]
建立一個清單,列出所有附加到標記的註解。
exports.extractAllCommentTokens = (tokens) ->
allCommentsObj = {}
for token in tokens when token.comments
for comment in token.comments
commentKey = comment.locationData.range[0]
allCommentsObj[commentKey] = comment
sortedKeys = Object.keys(allCommentsObj).sort (a, b) -> a - b
for key in sortedKeys
allCommentsObj[key]
根據標記的位置資料,取得一個標記的查詢雜湊。多個標記可能具有相同的位置雜湊,但使用獨有的位置資料可以區分例如長度為零的產生式標記和實際的原始碼標記。
buildLocationHash = (loc) ->
"#{loc.range[0]}-#{loc.range[1]}"
建立一個額外的標記屬性字典,依標記的位置(用作查詢雜湊)來組織。
exports.buildTokenDataDictionary = buildTokenDataDictionary = (tokens) ->
tokenData = {}
for token in tokens when token.comments
tokenHash = buildLocationHash token[2]
多個標記可能具有相同的位置雜湊,例如在標記串流的開頭或結尾處新增的產生式 JS
標記,用於容納檔案開頭或結尾的註解。
tokenData[tokenHash] ?= {}
if token.comments # `comments` is always an array.
對於「重疊」標記,也就是具有相同位置資料且因此符合 tokenHash
的標記,將來自所有標記的註解合併成一個陣列,即使有重複的註解;它們將在稍後進行排序。
(tokenData[tokenHash].comments ?= []).push token.comments...
tokenData
這會回傳一個函式,這個函式會將一個物件作為參數,如果該物件是一個 AST 節點,則會更新該物件的 locationData。無論如何都會回傳該物件。
exports.addDataToNode = (parserState, firstLocationData, firstValue, lastLocationData, lastValue, forceUpdateLocation = yes) ->
(obj) ->
新增位置資料。
locationData = buildLocationData(firstValue?.locationData ? firstLocationData, lastValue?.locationData ? lastLocationData)
if obj?.updateLocationDataIfMissing? and firstLocationData?
obj.updateLocationDataIfMissing locationData, forceUpdateLocation
else
obj.locationData = locationData
新增註解,如果尚未建立標記資料字典,則建立該字典。
parserState.tokenData ?= buildTokenDataDictionary parserState.parser.tokens
if obj.locationData?
objHash = buildLocationHash obj.locationData
if parserState.tokenData[objHash]?.comments?
attachCommentsToNode parserState.tokenData[objHash].comments, obj
obj
exports.attachCommentsToNode = attachCommentsToNode = (comments, node) ->
return if not comments? or comments.length is 0
node.comments ?= []
node.comments.push comments...
將 jison 位置資料轉換成字串。obj
可以是 token 或 locationData。
exports.locationDataToString = (obj) ->
if ("2" of obj) and ("first_line" of obj[2]) then locationData = obj[2]
else if "first_line" of obj then locationData = obj
if locationData
"#{locationData.first_line + 1}:#{locationData.first_column + 1}-" +
"#{locationData.last_line + 1}:#{locationData.last_column + 1}"
else
"No location data"
產生一個獨特的匿名檔案名稱,以便我們可以區分任意數量的匿名腳本的來源對應快取項目。
exports.anonymousFileName = do ->
n = 0
->
"<anonymous-#{n++}>"
與 basename
相容的 .coffee.md
版本,傳回不含副檔名的檔案。
exports.baseFileName = (file, stripExt = no, useWinPathSep = no) ->
pathSep = if useWinPathSep then /\\|\// else /\//
parts = file.split(pathSep)
file = parts[parts.length - 1]
return file unless stripExt and file.indexOf('.') >= 0
parts = file.split('.')
parts.pop()
parts.pop() if parts[parts.length - 1] is 'coffee' and parts.length > 1
parts.join('.')
判斷檔案名稱是否代表 CoffeeScript 檔案。
exports.isCoffee = (file) -> /\.((lit)?coffee|coffee\.md)$/.test file
判斷檔案名稱是否代表 Literate CoffeeScript 檔案。
exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
從給定的位置擲出 SyntaxError。錯誤的 toString
將會傳回遵循「標準」格式 <filename>:<line>:<col>: <message>
的錯誤訊息,以及包含錯誤的行和標記錯誤位置的標記。
exports.throwSyntaxError = (message, location) ->
error = new SyntaxError message
error.location = location
error.toString = syntaxErrorToString
不要顯示編譯器的堆疊追蹤,而是顯示我們的自訂錯誤訊息(這在例如編譯 CoffeeScript 的 Node.js 應用程式中錯誤浮現時很有用)。
error.stack = error.toString()
throw error
如果編譯器 SyntaxError 尚未包含來源程式碼資訊,則更新該錯誤。
exports.updateSyntaxError = (error, code, filename) ->
避免搞亂其他錯誤的 stack
屬性(例如可能的錯誤)。
if error.toString is syntaxErrorToString
error.code or= code
error.filename or= filename
error.stack = error.toString()
error
syntaxErrorToString = ->
return Error::toString.call @ unless @code and @location
{first_line, first_column, last_line, last_column} = @location
last_line ?= first_line
last_column ?= first_column
if @filename?.startsWith '<anonymous'
filename = '[stdin]'
else
filename = @filename or '[stdin]'
codeLine = @code.split('\n')[first_line]
start = first_column
在多行錯誤中只顯示第一行。
end = if first_line is last_line then last_column + 1 else codeLine.length
marker = codeLine[...start].replace(/[^\s]/g, ' ') + repeat('^', end - start)
檢查我們是否在啟用色彩的 TTY 上執行。
if process?
colorsEnabled = process.stdout?.isTTY and not process.env?.NODE_DISABLE_COLORS
if @colorful ? colorsEnabled
colorize = (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) ->
switch string
when ' ' then 'space'
when '\n' then 'newline'
when '\r' then 'carriage return'
when '\t' then 'tab'
else string
exports.parseNumber = (string) ->
return NaN unless string?
base = switch string.charAt 1
when 'b' then 2
when 'o' then 8
when 'x' then 16
else null
if base?
parseInt string[2..].replace(/_/g, ''), base
else
parseFloat string.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) -> obj is yes or obj is no or Object::toString.call(obj) is '[object Boolean]'
exports.isPlainObject = (obj) -> typeof obj is 'object' and !!obj and not Array.isArray(obj) and not isNumber(obj) and not isString(obj) and not isBoolean(obj)
unicodeCodePointToUnicodeEscapes = (codePoint) ->
toUnicodeEscape = (val) ->
str = val.toString 16
"\\u#{repeat '0', 4 - str.length}#{str}"
return toUnicodeEscape(codePoint) if codePoint < 0x10000
代理對
high = Math.floor((codePoint - 0x10000) / 0x400) + 0xD800
low = (codePoint - 0x10000) % 0x400 + 0xDC00
"#{toUnicodeEscape(high)}#{toUnicodeEscape(low)}"
在沒有 u
旗標的正規表示法中,將 \u{...}
取代為 \uxxxx[\uxxxx]
exports.replaceUnicodeCodePointEscapes = (str, {flags, error, delimiter = ''} = {}) ->
shouldReplace = flags? and 'u' not in flags
str.replace UNICODE_CODE_POINT_ESCAPE, (match, escapedBackslash, codePointHex, offset) ->
return escapedBackslash if escapedBackslash
codePointDecimal = parseInt codePointHex, 16
if codePointDecimal > 0x10ffff
error "unicode code point escapes greater than \\u{10ffff} are not allowed",
offset: offset + delimiter.length
length: codePointHex.length + 4
return match unless shouldReplace
unicodeCodePointToUnicodeEscapes codePointDecimal
UNICODE_CODE_POINT_ESCAPE = ///
( \\\\ ) # Make sure the escape isn’t escaped.
|
\\u\{ ( [\da-fA-F]+ ) \}
///g