{Lexer} = require './lexer'
{parser} = require './parser'
helpers = require './helpers'
SourceMap = require './sourcemap'
CoffeeScriptは、Node.js/V8ベースのコマンドラインコンパイラとしてサーバー上で使用することも、ブラウザで直接CoffeeScriptを実行することもできます。このモジュールには、CoffeeScriptソースをトークン化、解析、およびJavaScriptにコンパイルするための主要なエントリ関数が含まれています。
{Lexer} = require './lexer'
{parser} = require './parser'
helpers = require './helpers'
SourceMap = require './sourcemap'
このファイルはlib/coffeescript
から評価されるため、このファイルの2つ上のレベルにあるpackage.json
をrequireします。
packageJson = require '../../package.json'
現在のCoffeeScriptのバージョン番号。
exports.VERSION = packageJson.version
exports.FILE_EXTENSIONS = FILE_EXTENSIONS = ['.coffee', '.litcoffee', '.coffee.md']
テスト用のヘルパーを公開します。
exports.helpers = helpers
{getSourceMap, registerCompiled} = SourceMap
これは、外部モジュールがソースマップのキャッシュを実装できるようにするために公開されています。これは、キャッシュされたソースマップを持つファイルのスタックトレースを調整するためにpatchStackTrace
が呼び出された場合にのみ使用されます。
exports.registerCompiled = registerCompiled
nodejsとブラウザの両方でbtoaを許可する関数。
base64encode = (src) -> switch
when typeof Buffer is 'function'
Buffer.from(src).toString('base64')
when typeof btoa is 'function'
<script>
ブロックの内容はUTF-16でエンコードされているため、ブロック内で拡張文字が使用されている場合、btoaはUTF-8で最大になるため失敗します。詳細とここで実装されている解決策については、https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding#The_Unicode_Problemを参照してください。
btoa encodeURIComponent(src).replace /%([0-9A-F]{2})/g, (match, p1) ->
String.fromCharCode '0x' + p1
else
throw new Error('Unable to base64 encode inline sourcemap.')
字句解析器/パーサー/コンパイラーによってスローされたSyntaxErrorにソースファイル情報を追加するための関数ラッパー。
withPrettyErrors = (fn) ->
(code, options = {}) ->
try
fn.call @, code, options
catch err
throw err if typeof code isnt 'string' # Support `CoffeeScript.nodes(tokens)`.
throw helpers.updateSyntaxError err, code, options.filename
Coffee/Jisonコンパイラーを使用して、CoffeeScriptコードをJavaScriptにコンパイルします。
options.sourceMap
が指定されている場合は、options.filename
も指定する必要があります。SourceMap#generate
に渡すことができるすべてのオプションもここに渡すことができます。
これは、options.sourceMap
が渡されない限り、javascript文字列を返します。渡された場合は、{js, v3SourceMap, sourceMap}
オブジェクトを返します。ここで、sourceMapは、プログラムによるルックアップに便利なsourcemap.coffee#SourceMapオブジェクトです。
exports.compile = compile = withPrettyErrors (code, options = {}) ->
渡されたoptions
オブジェクトをミューテートしないように、options
を複製します。
options = Object.assign {}, options
generateSourceMap = options.sourceMap or options.inlineMap or not options.filename?
filename = options.filename or helpers.anonymousFileName()
checkShebangLine filename, code
map = new SourceMap if generateSourceMap
tokens = lexer.tokenize code, options
生成された変数が同じ名前にならないように、参照された変数のリストを渡します。
options.referencedVars = (
token[1] for token in tokens when token[0] is 'IDENTIFIER'
)
インポートまたはエクスポートを確認します。見つかった場合は、強制的にベアモードにします。
unless options.bare? and options.bare is yes
for token in tokens
if token[0] in ['IMPORT', 'EXPORT']
options.bare = yes
break
nodes = parser.parse tokens
要求されたのがノードのPOJO表現、例えば抽象構文木(AST)のみである場合は、ここで停止してそれを返すことができます(lexerのclean
関数によって元のソースからずれている可能性がある、ルート/File
»Program
ノードの位置データを修正した後)。
if options.ast
nodes.allCommentTokens = helpers.extractAllCommentTokens tokens
sourceCodeNumberOfLines = (code.match(/\r?\n/g) or '').length + 1
sourceCodeLastLine = /.*$/.exec(code)[0] # `.*` matches all but line break characters.
ast = nodes.ast options
range = [0, code.length]
ast.start = ast.program.start = range[0]
ast.end = ast.program.end = range[1]
ast.range = ast.program.range = range
ast.loc.start = ast.program.loc.start = {line: 1, column: 0}
ast.loc.end.line = ast.program.loc.end.line = sourceCodeNumberOfLines
ast.loc.end.column = ast.program.loc.end.column = sourceCodeLastLine.length
ast.tokens = tokens
return ast
fragments = nodes.compileToFragments options
currentLine = 0
currentLine += 1 if options.header
currentLine += 1 if options.shiftLine
currentColumn = 0
js = ""
for fragment in fragments
各フラグメントからのデータでソースマップを更新します。
if generateSourceMap
空、空白、またはセミコロンのみのフラグメントを含めないでください。
if fragment.locationData and not /^[;\s]*$/.test fragment.code
map.add(
[fragment.locationData.first_line, fragment.locationData.first_column]
[currentLine, currentColumn]
{noReplace: true})
newLines = helpers.count fragment.code, "\n"
currentLine += newLines
if newLines
currentColumn = fragment.code.length - (fragment.code.lastIndexOf("\n") + 1)
else
currentColumn += fragment.code.length
各フラグメントから最終的なJavaScriptにコードをコピーします。
js += fragment.code
if options.header
header = "Generated by CoffeeScript #{@VERSION}"
js = "// #{header}\n#{js}"
if generateSourceMap
v3SourceMap = map.generate options, code
if options.transpile
if typeof options.transpile isnt 'object'
これは、Node APIを介して実行され、transpile
がオブジェクト以外のものに設定されている場合にのみ発生します。
throw new Error 'The transpile option must be given an object with options to pass to Babel'
このコンパイラがCLIまたはNode APIを介して実行されている場合に渡されたBabelへの参照を取得します。
transpiler = options.transpile.transpile
delete options.transpile.transpile
transpilerOptions = Object.assign {}, options.transpile
https://github.com/babel/babel/issues/827#issuecomment-77573107を参照してください。Babelは、inputSourceMap
でv3ソースマップオブジェクトを入力として受け取り、その出力で更新されたv3ソースマップオブジェクトを返します。
if v3SourceMap and not transpilerOptions.inputSourceMap?
transpilerOptions.inputSourceMap = v3SourceMap
transpilerOutput = transpiler js, transpilerOptions
js = transpilerOutput.code
if v3SourceMap and transpilerOutput.map
v3SourceMap = transpilerOutput.map
if options.inlineMap
encoded = base64encode JSON.stringify v3SourceMap
sourceMapDataURI = "//# sourceMappingURL=data:application/json;base64,#{encoded}"
sourceURL = "//# sourceURL=#{filename}"
js = "#{js}\n#{sourceMapDataURI}\n#{sourceURL}"
registerCompiled filename, code, map
if options.sourceMap
{
js
sourceMap: map
v3SourceMap: JSON.stringify v3SourceMap, null, 2
}
else
js
CoffeeScriptコードの文字列をトークン化し、トークンの配列を返します。
exports.tokens = withPrettyErrors (code, options) ->
lexer.tokenize code, options
CoffeeScriptコードの文字列または字句解析されたトークンの配列を解析し、ASTを返します。その後、ルートで.compile()
を呼び出してコンパイルするか、コールバックを使用して.traverseChildren()
を使用することでトラバースできます。
exports.nodes = withPrettyErrors (source, options) ->
source = lexer.tokenize source, options if typeof source is 'string'
parser.parse source
このファイルは以前これらのメソッドをエクスポートしていました。代わりに警告をスローするスタブを残します。これらのメソッドは、Nodeと非Node環境で別々のエントリポイントを提供するためにindex.coffee
に移動されたため、静的解析ツールが非Node環境用にコンパイルするときにNodeパッケージを詰まらせないようにします。
exports.run = exports.eval = exports.register = ->
throw new Error 'require index.coffee, not this file'
ここで使用するためにLexerをインスタンス化します。
lexer = new Lexer
実際のLexerは、トークンの一般的なストリームを生成します。このオブジェクトは、Jison APIと互換性のある、その上に薄いラッパーを提供します。その後、それを「Jisonレクサー」として直接渡すことができます。
parser.lexer =
yylloc:
range: []
options:
ranges: yes
lex: ->
token = parser.tokens[@pos++]
if token
[tag, @yytext, @yylloc] = token
parser.errorToken = token.origin or token
@yylineno = @yylloc.first_line
else
tag = ''
tag
setInput: (tokens) ->
parser.tokens = tokens
@pos = 0
upcomingInput: -> ''
すべてのASTノードをパーサーに表示します。
parser.yy = require './nodes'
Jisonのデフォルトのエラー処理関数をオーバーライドします。
parser.yy.parseError = (message, {token}) ->
Jisonのメッセージを無視します。冗長な行番号情報が含まれています。トークンを無視します。エラーが、その起源を参照する可能性のある生成されたトークンによって引き起こされた場合に備えて、レクサーから直接値を取得します。
{errorToken, tokens} = parser
[errorTag, errorText, errorLoc] = errorToken
errorText = switch
when errorToken is tokens[tokens.length - 1]
'end of input'
when errorTag in ['INDENT', 'OUTDENT']
'indentation'
when errorTag in ['IDENTIFIER', 'NUMBER', 'INFINITY', 'STRING', 'STRING_START', 'REGEX', 'REGEX_START']
errorTag.replace(/_START$/, '').toLowerCase()
else
helpers.nameWhitespaceCharacter errorText
2番目の引数にはloc
プロパティがあり、このトークンの位置データが含まれている必要があります。残念ながら、Jisonは古いloc
(前のトークンからの)を送信しているように見えるため、レクサーから直接位置情報を取得します。
helpers.throwSyntaxError "unexpected #{errorText}", errorLoc
exports.patchStackTrace = ->
http://v8.googlecode.com/svn/branches/bleeding_edge/src/messages.jsに基づいています。sourceMapを処理するように変更されました
formatSourcePosition = (frame, getSourceMapping) ->
filename = undefined
fileLocation = ''
if frame.isNative()
fileLocation = "native"
else
if frame.isEval()
filename = frame.getScriptNameOrSourceURL()
fileLocation = "#{frame.getEvalOrigin()}, " unless filename
else
filename = frame.getFileName()
filename or= "<anonymous>"
line = frame.getLineNumber()
column = frame.getColumnNumber()
sourceMapの位置を確認します
source = getSourceMapping filename, line, column
fileLocation =
if source
"#{filename}:#{source[0]}:#{source[1]}"
else
"#{filename}:#{line}:#{column}"
functionName = frame.getFunctionName()
isConstructor = frame.isConstructor()
isMethodCall = not (frame.isToplevel() or isConstructor)
if isMethodCall
methodName = frame.getMethodName()
typeName = frame.getTypeName()
if functionName
tp = as = ''
if typeName and functionName.indexOf typeName
tp = "#{typeName}."
if methodName and functionName.indexOf(".#{methodName}") isnt functionName.length - methodName.length - 1
as = " [as #{methodName}]"
"#{tp}#{functionName}#{as} (#{fileLocation})"
else
"#{typeName}.#{methodName or '<anonymous>'} (#{fileLocation})"
else if isConstructor
"new #{functionName or '<anonymous>'} (#{fileLocation})"
else if functionName
"#{functionName} (#{fileLocation})"
else
fileLocation
getSourceMapping = (filename, line, column) ->
sourceMap = getSourceMap filename, line, column
answer = sourceMap.sourceLocation [line - 1, column - 1] if sourceMap?
if answer? then [answer[0] + 1, answer[1] + 1] else null
michaelficarra/CoffeeScriptReduxに基づいています。NodeJS / V8は、sourceMapを使用してスタックトレース内の位置を変換することをサポートしていないため、ErrorをモンキーパッチしてCoffeeScriptソースの位置を表示する必要があります。
Error.prepareStackTrace = (err, stack) ->
frames = for frame in stack
CoffeeScript.run
よりも深いスタックフレームを表示しないでください。
break if frame.getFunction() is exports.run
" at #{formatSourcePosition frame, getSourceMapping}"
"#{err.toString()}\n#{frames.join '\n'}\n"
checkShebangLine = (file, input) ->
firstLine = input.split(/$/m, 1)[0]
rest = firstLine?.match(/^#!\s*([^\s]+\s*)(.*)/)
args = rest?[2]?.split(/\s/).filter (s) -> s isnt ''
if args?.length > 1
console.error '''
The script to be run begins with a shebang line with more than one
argument. This script will fail on platforms such as Linux which only
allow a single argument.
'''
console.error "The shebang line was: '#{firstLine}' in file '#{file}'"
console.error "The arguments were: #{JSON.stringify args}"