exports.starts = (string, literal, start) ->
literal is string.substr start, literal.length
このファイルには、Lexer、Rewriter、およびNodes間で共有したい共通のヘルパー関数が含まれています。オブジェクトのマージ、配列の平坦化、文字数のカウントなどを行います。
与えられた文字列の先頭が、あるシーケンスに一致するかどうかを確認します。
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
配列からすべてのfalsyな値を削除します。
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
リテラート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'
2つの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
が一致するトークンについては、たとえ重複するコメントがあったとしても、両方/すべてのトークンのコメントを1つの配列にマージします。これらは後でソートされます。
(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++}>"
拡張子なしでファイルを返す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
ファイル名がリテラートCoffeeScriptファイルを表すかどうかを判断します。
exports.isLiterate = (file) -> /\.(litcoffee|coffee\.md)$/.test file
指定された位置からSyntaxErrorをスローします。エラーのtoString
は、「標準」形式<ファイル名>:<行>:<列>: <メッセージ>
に、エラーのある行とエラーの位置を示すマーカーが続くエラーメッセージを返します。
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