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
可以是标记或 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++}>"
.coffee.md
的兼容版本 basename
,它返回不带扩展名的文件。
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 编译 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