Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
nodes.coffee
は、構文木のすべてのノードクラスを含んでいます。ほとんどのノードはgrammarのアクションの結果として作成されますが、いくつかはコード生成の方法として他のノードによって作成されます。構文木をJavaScriptコードの文字列に変換するには、ルートでcompile()
を呼び出します。
Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
使用するヘルパーをインポートします。
{compact, flatten, extend, merge, del, starts, ends, some,
addDataToNode, attachCommentsToNode, locationDataToString,
throwSyntaxError, replaceUnicodeCodePointEscapes,
isFunction, isPlainObject, isNumber, parseNumber} = require './helpers'
パーサーで必要な関数。
exports.extend = extend
exports.addDataToNode = addDataToNode
カスタマイズの必要がないノードの定数関数。
YES = -> yes
NO = -> no
THIS = -> this
NEGATE = -> @negated = not @negated; this
以下で定義されているさまざまなノードはすべて、CodeFragmentオブジェクトのコレクションにコンパイルされます。CodeFragmentsは生成されたコードのブロックであり、コードの由来であるソースファイル内の場所です。CodeFragmentsは、すべてのCodeFragmentsのcode
スニペットを順に連結するだけで、動作するコードに組み立てることができます。
exports.CodeFragment = class CodeFragment
constructor: (parent, code) ->
@code = "#{code}"
@type = parent?.constructor?.name or 'unknown'
@locationData = parent?.locationData
@comments = parent?.comments
toString: ->
これはデバッグのみに使用することを意図しています。
"#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
CodeFragmentの配列を文字列に変換します。
fragmentsToText = (fragments) ->
(fragment.code for fragment in fragments).join('')
Baseは、構文木内のすべてのノードの抽象基底クラスです。各サブクラスは、そのノードのコード生成を実行するcompileNode
メソッドを実装します。ノードをJavaScriptにコンパイルするには、そのノードでcompile
を呼び出します。これはcompileNode
をいくつかの一般的な追加のスマート機能でラップし、生成されたコードをクロージャでラップする必要があるかどうかを判断します。オプションのハッシュが渡され、ツリーの上位からの環境に関する情報(周囲の関数によって戻り値が要求されているかどうかなど)、現在のスコープに関する情報、インデントレベルを含む情報が全体に渡って複製されます。
exports.Base = class Base
compile: (o, lvl) ->
fragmentsToText @compileToFragments o, lvl
ノードが複数回コンパイルされる場合があります。たとえば、スコープトラッキングに追加する変数の名前を取得する場合などです。「早期」コンパイルによってコメントが出力されないことがわかっている場合は、それらのコメントを脇に置いておき、コメントが出力されることになる後のcompile
呼び出しのために保存します。
compileWithoutComments: (o, lvl, method = 'compile') ->
if @comments
@ignoreTheseCommentsTemporarily = @comments
delete @comments
unwrapped = @unwrapAll()
if unwrapped.comments
unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments
delete unwrapped.comments
fragments = @[method] o, lvl
if @ignoreTheseCommentsTemporarily
@comments = @ignoreTheseCommentsTemporarily
delete @ignoreTheseCommentsTemporarily
if unwrapped.ignoreTheseCommentsTemporarily
unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily
delete unwrapped.ignoreTheseCommentsTemporarily
fragments
compileNodeWithoutComments: (o, lvl) ->
@compileWithoutComments o, lvl, 'compileNode'
このノードをコンパイルする前にクロージャでラップするかどうか、または直接コンパイルするかどうかを決定するための共通のロジック。このノードがステートメントであり、pureStatementではなく、ブロックの最上位レベルにない場合(不要)、結果の返却を既に要求されていない場合(ステートメントは結果の返却方法を知っているため)にラップする必要があります。
compileToFragments: (o, lvl) ->
o = extend {}, o
o.level = lvl if lvl
node = @unfoldSoak(o) or this
node.tab = o.indent
fragments = if o.level is LEVEL_TOP or not node.isStatement(o)
node.compileNode o
else
node.compileClosure o
@compileCommentFragments o, node, fragments
fragments
compileToFragmentsWithoutComments: (o, lvl) ->
@compileWithoutComments o, lvl, 'compileToFragments'
クロージャラッピングによって式に変換されたステートメントは、期待される字句スコープを維持するために、親クロージャとスコープオブジェクトを共有します。
compileClosure: (o) ->
@checkForPureStatementInExpression()
o.sharedScope = yes
func = new Code [], Block.wrap [this]
args = []
if @contains ((node) -> node instanceof SuperCall)
func.bound = yes
else if (argumentsNode = @contains isLiteralArguments) or @contains isLiteralThis
args = [new ThisLiteral]
if argumentsNode
meth = 'apply'
args.push new IdentifierLiteral 'arguments'
else
meth = 'call'
func = new Value func, [new Access new PropertyName meth]
parts = (new Call func, args).compileNode o
switch
when func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
parts.push @makeCode ")"
when func.isAsync or func.base?.isAsync
parts.unshift @makeCode "(await "
parts.push @makeCode ")"
parts
compileCommentFragments: (o, node, fragments) ->
return fragments unless node.comments
これは、comments
プロパティとしてノードに添付されているコメントがCodeFragment
になる場所です。「インラインブロックコメント」(例:/* */
で区切られた、行のコード内に散在するコメント)は、現在のfragments
ストリームに追加されます。他のすべてのフラグメントは、最も近い先行または後続のフラグメントのプロパティとして添付され、後でcompileComments
で適切に出力されるまで一時的に保管されます。
unshiftCommentFragment = (commentFragment) ->
if commentFragment.unshift
最初の非コメントフラグメントを見つけ、その前にcommentFragment
を挿入します。
unshiftAfterComments fragments, commentFragment
else
if fragments.length isnt 0
precedingFragment = fragments[fragments.length - 1]
if commentFragment.newLine and precedingFragment.code isnt '' and
not /\n\s*$/.test precedingFragment.code
commentFragment.code = "\n#{commentFragment.code}"
fragments.push commentFragment
for comment in node.comments when comment not in @compiledComments
@compiledComments.push comment # Don’t output this comment twice.
###
で示されるブロック/ここコメント(1 + ### comment ### 2
のようなインラインコメント)の場合、フラグメントを作成し、それらをフラグメント配列に挿入します。それ以外の場合は、コメントフラグメントを最も近いフラグメントに一時的に添付しておき、すべての改行が追加された後に後で出力に挿入できるようにします。
if comment.here # Block comment, delimited by `###`.
commentFragment = new HereComment(comment).compileNode o
else # Line comment, delimited by `#`.
commentFragment = new LineComment(comment).compileNode o
if (commentFragment.isHereComment and not commentFragment.newLine) or
node.includeCommentFragments()
1 + /* comment */ 2
のようなインラインブロックコメント、またはcompileToFragments
メソッドにコメントを出力するロジックがあるノード。
unshiftCommentFragment commentFragment
else
fragments.push @makeCode '' if fragments.length is 0
if commentFragment.unshift
fragments[0].precedingComments ?= []
fragments[0].precedingComments.push commentFragment
else
fragments[fragments.length - 1].followingComments ?= []
fragments[fragments.length - 1].followingComments.push commentFragment
fragments
コード生成が複雑な式の結果を複数の場所で使用する場合は、一時変数に代入することで、式が一度だけ評価されるようにします。プリコンパイルするレベルを渡します。
level
が渡された場合、[val, ref]
を返します。ここで、val
はコンパイルされた値であり、ref
はコンパイルされた参照です。level
が渡されない場合、コンパイルされていない生のノードである2つの値を含む[val, ref]
を返します。
cache: (o, level, shouldCache) ->
complex = if shouldCache? then shouldCache this else @shouldCache()
if complex
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
sub = new Assign ref, this
if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
else
ref = if level then @compileToFragments o, level else this
[ref, ref]
式を「ホイスティング」されたかのように動作させることが役立つ場合があります。つまり、式の結果がソース内の場所の前に使用できますが、式の変数スコープはソースの位置に対応しています。これは、クラス内の実行可能なクラスボディを処理するために広く使用されています。
このメソッドを呼び出すと、ノードが変更され、compileNode
メソッドとcompileToFragments
メソッドがプロキシされ、結果が後でtarget
ノード(呼び出しによって返される)を置き換えるために格納されます。
hoist: ->
@hoisted = yes
target = new HoistTarget @
compileNode = @compileNode
compileToFragments = @compileToFragments
@compileNode = (o) ->
target.update compileNode, o
@compileToFragments = (o) ->
target.update compileToFragments, o
target
cacheToCodeFragments: (cacheValues) ->
[fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
現在のノードの結果を返すノードを構築します。これは、多くのステートメントノード(例:If
、For
)に対してよりスマートな動作のためにオーバーライドされることに注意してください。
makeReturn: (results, mark) ->
if mark
このノードを暗黙的に返されるものとしてマークし、ASTで返されるノードメタデータの一部になるようにします。
@canBeReturned = yes
return
node = @unwrapAll()
if results
new Call new Literal("#{results}.push"), [node]
else
new Return node
このノードまたはそのいずれかの子ノードに、特定の種類のノードが含まれていますか?子ノードを再帰的にトラバースし、pred
を検証する最初のノードを返します。それ以外の場合は、未定義を返します。contains
はスコープ境界を超えません。
contains: (pred) ->
node = undefined
@traverseChildren no, (n) ->
if pred n
node = n
return no
node
ノードリストの最後のノードを取り出します。
lastNode: (list) ->
if list.length is 0 then null else list[list.length - 1]
構文木を検査するためのノードのデバッグ表現。これは、coffee --nodes
が出力するものです。
toString: (idt = '', name = @constructor.name) ->
tree = '\n' + idt + name
tree += '?' if @soak
@eachChild (node) -> tree += node.toString idt + TAB
tree
checkForPureStatementInExpression: ->
if jumpNode = @jumps()
jumpNode.error 'cannot use a pure statement in an expression'
ノードのプレーンなJavaScriptオブジェクト表現であり、JSONとしてシリアル化できます。これは、Node APIのast
オプションが返すものです。他のツールとの相互運用性を向上させるために、Babel AST specにできるだけ近づけるように努力しています。警告:子クラスではこのメソッドをオーバーライドしないでください。必要に応じてコンポーネントast*
メソッドのみをオーバーライドしてください。
ast: (o, level) ->
level
をo
にマージし、他の普遍的なチェックを実行します。
o = @astInitialize o, level
このノードのシリアル化可能な表現を作成します。
astNode = @astNode o
(暗黙的に)返す式に対応するASTノードをマークします。親が返されていることをマークするには、まず子ノードを組み立てる必要があるため、astNode
の一部としてこれを行うことはできません。
if @astNode? and @canBeReturned
Object.assign astNode, {returns: yes}
astNode
astInitialize: (o, level) ->
o = Object.assign {}, o
o.level = level if level?
if o.level > LEVEL_TOP
@checkForPureStatementInExpression()
後者は子ノードに対して.ast()
を呼び出す可能性があり、それらのノードには、その時までに既に実行されているmakeReturn
からの戻りロジックが必要となるため、astProperties
の前に@makeReturn
を呼び出す必要があります。
@makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
o
astNode: (o) ->
すべての抽象構文木ノードオブジェクトには、4つのカテゴリのプロパティがあります。
type
フィールドに格納された型であり、NumberLiteral
のような文字列です。loc
、start
、end
、range
フィールドに格納された位置データ。parsedValue
のような、このノードに固有のプロパティ。body
のような、それ自体が子ノードであるプロパティ。これらのフィールドはすべてBabel仕様で混在しています。type
とstart
とparsedValue
はすべてASTノードオブジェクトの最上位レベルのフィールドです。ここでは、各カテゴリを返すための個別のメソッドがあり、それらをマージしています。 Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
デフォルトでは、ノードクラスには特定のプロパティがありません。
astProperties: -> {}
デフォルトでは、ノードクラスのASTtype
はそのクラス名です。
astType: -> @constructor.name
ASTの位置データは、Jisonの位置データの再配置されたバージョンであり、Babel仕様で使用される構造に変換されています。
astLocationData: ->
jisonLocationDataToAstLocationData @locationData
ASTノードにExpressionStatement
ラッパーが必要かどうかを判断します。通常はisStatement()
ロジックと一致しますが、これによりオーバーライドできます。
isStatementAst: (o) ->
@isStatement o
関数を各子に渡し、関数がfalse
を返すときに中断します。
eachChild: (func) ->
return this unless @children
for attr in @children when @[attr]
for child in flatten [@[attr]]
return this if func(child) is false
this
traverseChildren: (crossScope, func) ->
@eachChild (child) ->
recur = func(child)
child.traverseChildren(crossScope, func) unless recur is no
replaceInContext
は、match
がtrueを返すノードを探して子をトラバースします。見つかった場合、一致するノードはreplacement
を呼び出した結果で置き換えられます。
replaceInContext: (match, replacement) ->
return false unless @children
for attr in @children when children = @[attr]
if Array.isArray children
for child, i in children
if match child
children[i..i] = replacement child, @
return true
else
return true if child.replaceInContext match, replacement
else if match children
@[attr] = replacement children, @
return true
else
return true if children.replaceInContext match, replacement
invert: ->
new Op '!', this
unwrapAll: ->
node = this
continue until node is node = node.unwrap()
node
共通のノードプロパティとメソッドのデフォルトの実装。必要に応じて、ノードはこれらをカスタムロジックでオーバーライドします。
children
は、ツリーウォーキング時に再帰するプロパティです。children
リストはASTの構造です。parent
ポインタとchildren
へのポインタは、ツリーをトラバースする方法です。
children: []
isStatement
は「すべてが式である」ことと関係があります。break
など、式にはできないものがいくつかあります。isStatement
がtrue
を返すものは、式として使用できないものです。式の位置にステートメントがあるために、nodes.coffee
から発生するエラーメッセージがいくつかあります。
isStatement: NO
フラグメントにコンパイルされたコメントを追跡して、2回出力されないようにします。
compiledComments: []
includeCommentFragments
により、compileCommentFragments
は、このノードが出力内でコメントを処理する方法に関する特別な認識を持っているかどうかを知ることができます。
includeCommentFragments: NO
jumps
は、式または式の内部部分に、制御の通常のフローからジャンプし、値として使用できないフロー制御構造(break
、continue
、return
など)があるかどうかを示します。(throw
はフロー制御構造とは見なされません。)これは、式の中間のフロー制御が無意味であるため重要です。それを許可することはできません。
jumps: NO
node.shouldCache() is false
の場合、node
を複数回使用しても安全です。それ以外の場合は、node
の値を変数に格納し、代わりにその変数を複数回出力する必要があります。これと似たようなものです。5
はキャッシュする必要がありません。ただし、returnFive()
には、複数回評価した結果、副作用がある可能性があるため、キャッシュする必要があります。パラメータの名前は、冪等である可能性が高いが、簡潔にするためにキャッシュしたい場合など、キャッシュする必要がない場合もあるため、mustCache
ではなくshouldCache
です。
shouldCache: YES
isChainable: NO
isAssignable: NO
isNumber: NO
unwrap: THIS
unfoldSoak: NO
このノードは特定の変数を代入するために使用されていますか?
assigns: NO
このノードとそのすべての後継ノードについて、位置データがまだ設定されていない場合は、位置データをlocationData
に設定します。
updateLocationDataIfMissing: (locationData, force) ->
@forceUpdateLocation = yes if force
return this if @locationData and not @forceUpdateLocation
delete @forceUpdateLocation
@locationData = locationData
@eachChild (child) ->
child.updateLocationDataIfMissing locationData
別のノードから位置データを追加します。
withLocationDataFrom: ({locationData}) ->
@updateLocationDataIfMissing locationData
別のノードから位置データとコメントを追加します。
withLocationDataAndCommentsFrom: (node) ->
@withLocationDataFrom node
{comments} = node
@comments = comments if comments?.length
this
このノードの位置に関連付けられたSyntaxErrorをスローします。
error: (message) ->
throwSyntaxError message, @locationData
makeCode: (code) ->
new CodeFragment this, code
wrapInParentheses: (fragments) ->
[@makeCode('('), fragments..., @makeCode(')')]
wrapInBraces: (fragments) ->
[@makeCode('{'), fragments..., @makeCode('}')]
fragmentsList
は、フラグメントの配列の配列です。fragmentsList
内の各配列は、joinStr
を間に追加して連結され、最終的なフラットなフラグメント配列が生成されます。
joinFragmentArrays: (fragmentsList, joinStr) ->
answer = []
for fragments, i in fragmentsList
if i then answer.push @makeCode joinStr
answer = answer.concat fragments
answer
HoistTargetNodeは、ホイスティングされたノードのノードツリー内の出力位置を表します。Base#hoistを参照してください。
exports.HoistTarget = class HoistTarget extends Base
与えられた配列内のホイスティングされたフラグメントを展開します。
@expand = (fragments) ->
for fragment, i in fragments by -1 when fragment.fragments
fragments[i..i] = @expand fragment.fragments
fragments
constructor: (@source) ->
super()
ソースノードがコンパイルされるときに適用する表示オプションを保持します。
@options = {}
ソースノードのコンパイルで置き換えられるプレースホルダーフラグメント。
@targetFragments = { fragments: [] }
isStatement: (o) ->
@source.isStatement o
ソースコードのコンパイル結果でターゲットフラグメントを更新します。与えられたコンパイル関数にノードとオプション(ターゲットの表示オプションで上書き)を渡して呼び出します。
update: (compile, o) ->
@targetFragments.fragments = compile.call @source, merge o, @options
ターゲットのインデントとレベルをコピーし、プレースホルダーフラグメントを返します。
compileToFragments: (o, level) ->
@options.indent = o.indent
@options.level = level ? o.level
[ @targetFragments ]
compileNode: (o) ->
@compileToFragments o
compileClosure: (o) ->
@compileToFragments o
ノードツリーのルートノード
exports.Root = class Root extends Base
constructor: (@body) ->
super()
@isAsync = (new Code [], @body).isAsync
children: ['body']
要求されない限り、すべてを安全なクロージャでラップします。そもそも生成しない方が良いのですが、現時点では、明らかに重複している二重括弧をクリーンアップします。
compileNode: (o) ->
o.indent = if o.bare then '' else TAB
o.level = LEVEL_TOP
o.compiling = yes
@initializeScope o
fragments = @body.compileRoot o
return fragments if o.bare
functionKeyword = "#{if @isAsync then 'async ' else ''}function"
[].concat @makeCode("(#{functionKeyword}() {\n"), fragments, @makeCode("\n}).call(this);\n")
initializeScope: (o) ->
o.scope = new Scope null, @body, null, o.referencedVars ? []
ルートスコープ内の指定されたローカル変数をパラメータとしてマークし、ルートブロックで宣言されるのを防ぎます。
o.scope.parameter name for name in o.locals or []
commentsAst: ->
@allComments ?=
for commentToken in (@allCommentTokens ? []) when not commentToken.heregex
if commentToken.here
new HereComment commentToken
else
new LineComment commentToken
comment.ast() for comment in @allComments
astNode: (o) ->
o.level = LEVEL_TOP
@initializeScope o
super o
astType: -> 'File'
astProperties: (o) ->
@body.isRootBlock = yes
return
program: Object.assign @body.ast(o), @astLocationData()
comments: @commentsAst()
ブロックは、インデントされたコードブロックの本体を形成する式のリストです。関数のインプリメンテーション、`if`、`switch`、または`try`などの節など…
exports.Block = class Block extends Base
constructor: (nodes) ->
super()
@expressions = compact flatten nodes or []
children: ['expressions']
この式のリストの最後に式を追加します。
push: (node) ->
@expressions.push node
this
この式のリストの最後の式を削除して返します。
pop: ->
@expressions.pop()
この式のリストの先頭に式を追加します。
unshift: (node) ->
@expressions.unshift node
this
このブロックが単一のノードのみで構成されている場合、それを取り出して展開します。
unwrap: ->
if @expressions.length is 1 then @expressions[0] else this
これは空のコードブロックですか?
isEmpty: ->
not @expressions.length
isStatement: (o) ->
for exp in @expressions when exp.isStatement o
return yes
no
jumps: (o) ->
for exp in @expressions
return jumpNode if jumpNode = exp.jumps o
ブロックノードは、その本体全体を返すのではなく、最後の式が返されることを保証します。
makeReturn: (results, mark) ->
len = @expressions.length
[..., lastExp] = @expressions
lastExp = lastExp?.unwrap() or no
また、同じレベルに隣接するJSXタグを返していないか確認する必要があります。JSXではそれが許可されていません。
if lastExp and lastExp instanceof Parens and lastExp.body.expressions.length > 1
{body:{expressions}} = lastExp
[..., penult, last] = expressions
penult = penult.unwrap()
last = last.unwrap()
if penult instanceof JSXElement and last instanceof JSXElement
expressions[expressions.length - 1].error 'Adjacent JSX elements must be wrapped in an enclosing tag'
if mark
@expressions[len - 1]?.makeReturn results, mark
return
while len--
expr = @expressions[len]
@expressions[len] = expr.makeReturn results
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
this
compile: (o, lvl) ->
return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
super o, lvl
**ブロック**本体内のすべての式をコンパイルします。結果を返す必要があり、それが式である場合は、それを返します。ステートメントの場合は、ステートメントにそれを実行するように要求します。
compileNode: (o) ->
@tab = o.indent
top = o.level is LEVEL_TOP
compiledNodes = []
for node, index in @expressions
if node.hoisted
これはホイスティングされた式です。これをコンパイルして、結果は無視します。
node.compileToFragments o
continue
node = (node.unfoldSoak(o) or node)
if node instanceof Block
これはネストされたブロックです。新しいスコープで囲むなどの特別な処理は行いません。このブロック内のステートメントを自分自身のものと一緒にコンパイルするだけです。
compiledNodes.push node.compileNode o
else if top
node.front = yes
fragments = node.compileToFragments o
unless node.isStatement o
fragments = indentInitial fragments, @
[..., lastFragment] = fragments
unless lastFragment.code is '' or lastFragment.isComment
fragments.push @makeCode ';'
compiledNodes.push fragments
else
compiledNodes.push node.compileToFragments o, LEVEL_LIST
if top
if @spaced
return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode('\n')
else
return @joinFragmentArrays(compiledNodes, '\n')
if compiledNodes.length
answer = @joinFragmentArrays(compiledNodes, ', ')
else
answer = [@makeCode 'void 0']
if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
compileRoot: (o) ->
@spaced = yes
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
@compileComments fragments
すべての内部変数の宣言が最上位にプッシュアップされた状態で、関数の内容の式の本体をコンパイルします。
compileWithDeclarations: (o) ->
fragments = []
post = []
for exp, i in @expressions
exp = exp.unwrap()
break unless exp instanceof Literal
o = merge(o, level: LEVEL_TOP)
if i
rest = @expressions.splice i, 9e9
[spaced, @spaced] = [@spaced, no]
[fragments, @spaced] = [@compileNode(o), spaced]
@expressions = rest
post = @compileNode o
{scope} = o
if scope.expressions is this
declars = o.scope.hasDeclarations()
assigns = scope.hasAssignments
if declars or assigns
fragments.push @makeCode '\n' if i
fragments.push @makeCode "#{@tab}var "
if declars
declaredVariables = scope.declaredVariables()
for declaredVariable, declaredVariablesIndex in declaredVariables
fragments.push @makeCode declaredVariable
if Object::hasOwnProperty.call o.scope.comments, declaredVariable
fragments.push o.scope.comments[declaredVariable]...
if declaredVariablesIndex isnt declaredVariables.length - 1
fragments.push @makeCode ', '
if assigns
fragments.push @makeCode ",\n#{@tab + TAB}" if declars
fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
else if fragments.length and post.length
fragments.push @makeCode "\n"
fragments.concat post
compileComments: (fragments) ->
for fragment, fragmentIndex in fragments
次の改行または前の改行に出力にコメントを挿入します。コメントを配置する改行がない場合は、改行を作成します。
if fragment.precedingComments
コメントを挿入する直前のフラグメントのインデントレベルを決定し、挿入するコメントにそのインデントレベルを使用します。この時点で、フラグメントの`code`プロパティは生成された出力JavaScriptであり、CoffeeScriptは常に2スペースでインデントされた出力を生成するため、少なくとも2スペースで始まる`code`プロパティを検索するだけです。
fragmentIndent = ''
for pastFragment in fragments[0...(fragmentIndex + 1)] by -1
indent = /^ {2,}/m.exec pastFragment.code
if indent
fragmentIndent = indent[0]
break
else if '\n' in pastFragment.code
break
code = "\n#{fragmentIndent}" + (
for commentFragment in fragment.precedingComments
if commentFragment.isHereComment and commentFragment.multiline
multident commentFragment.code, fragmentIndent, no
else
commentFragment.code
).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
for pastFragment, pastFragmentIndex in fragments[0...(fragmentIndex + 1)] by -1
newLineIndex = pastFragment.code.lastIndexOf '\n'
if newLineIndex is -1
フラグメントがなくなるか、文字列内で補間されるコードブロック内にあることが判明するまで、前のフラグメントを検索し続けます。
if pastFragmentIndex is 0
pastFragment.code = '\n' + pastFragment.code
newLineIndex = 0
else if pastFragment.isStringWithInterpolations and pastFragment.code is '{'
code = code[1..] + '\n' # Move newline to end.
newLineIndex = 1
else
continue
delete fragment.precedingComments
pastFragment.code = pastFragment.code[0...newLineIndex] +
code + pastFragment.code[newLineIndex..]
break
はい、これは前の`if`ブロックと非常に似ていますが、よく見ると、両方のブロックが共有する関数に抽象化すると混乱を招くような、多くの小さな違いがあります。
if fragment.followingComments
最初の後続コメントは、`;// コメント`のようにコードの行の最後に続くか、コードの行の後に新しい行を開始しますか?
trail = fragment.followingComments[0].trail
fragmentIndent = ''
出力する後続ではないコメントがある場合、次のコード行のインデントを見つけます。まず、これらのコメントは改行の後に出力されるため、次の改行を見つけ、次に次の改行の後の行のインデントを見つける必要があります。
unless trail and fragment.followingComments.length is 1
onNextLine = no
for upcomingFragment in fragments[fragmentIndex...]
unless onNextLine
if '\n' in upcomingFragment.code
onNextLine = yes
else
continue
else
indent = /^ {2,}/m.exec upcomingFragment.code
if indent
fragmentIndent = indent[0]
break
else if '\n' in upcomingFragment.code
break
このコメントは、ベアメードによって挿入されたインデントに従っていますか?その場合は、これ以上インデントする必要はありません。
code = if fragmentIndex is 1 and /^\s+$/.test fragments[0].code
''
else if trail
' '
else
"\n#{fragmentIndent}"
適切にインデントされたコメントを組み立てます。
code += (
for commentFragment in fragment.followingComments
if commentFragment.isHereComment and commentFragment.multiline
multident commentFragment.code, fragmentIndent, no
else
commentFragment.code
).join("\n#{fragmentIndent}").replace /^(\s*)$/gm, ''
for upcomingFragment, upcomingFragmentIndex in fragments[fragmentIndex...]
newLineIndex = upcomingFragment.code.indexOf '\n'
if newLineIndex is -1
フラグメントがなくなるか、文字列内で補間されるコードブロック内にあることが判明するまで、今後のフラグメントを検索し続けます。
if upcomingFragmentIndex is fragments.length - 1
upcomingFragment.code = upcomingFragment.code + '\n'
newLineIndex = upcomingFragment.code.length
else if upcomingFragment.isStringWithInterpolations and upcomingFragment.code is '}'
code = "#{code}\n"
newLineIndex = 0
else
continue
delete fragment.followingComments
余分な空行の挿入を避けます。
code = code.replace /^\n/, '' if upcomingFragment.code is '\n'
upcomingFragment.code = upcomingFragment.code[0...newLineIndex] +
code + upcomingFragment.code[newLineIndex..]
break
fragments
既にブロックである場合を除き、指定されたノードを**ブロック**としてラップします。
@wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
new Block nodes
astNode: (o) ->
if (o.level? and o.level isnt LEVEL_TOP) and @expressions.length
return (new Sequence(@expressions).withLocationDataFrom @).ast o
super o
astType: ->
if @isRootBlock
'Program'
else if @isClassBody
'ClassBody'
else
'BlockStatement'
astProperties: (o) ->
checkForDirectives = del o, 'checkForDirectives'
sniffDirectives @expressions, notFinalExpression: checkForDirectives if @isRootBlock or checkForDirectives
directives = []
body = []
for expression in @expressions
expressionAst = expression.ast o
生成されたPassthroughLiteralは無視します。
if not expressionAst?
continue
else if expression instanceof Directive
directives.push expressionAst
式がステートメントの場合、そのまま本体に追加できます。
else if expression.isStatementAst o
body.push expressionAst
それ以外の場合は、`ExpressionStatement` ASTノードでラップする必要があります。
else
body.push Object.assign
type: 'ExpressionStatement'
expression: expressionAst
,
expression.astLocationData()
return {
現時点では、`Program` ASTノードに`sourceType`を含めていません。その値は`'script'`または`'module'`のいずれかになり、CoffeeScriptが常にどちらであるべきかを知る方法はありません。ソースコードに`import`または`export`ステートメントが存在することは、それが`module`であることを意味しますが、プロジェクトは主にそのようなファイルと、`import`または`export`がないが、それでもプロジェクトにインポートされ、そのため`module`として扱われることが期待される例外的なファイルで構成される場合があります。`sourceType`の値を決定することは、本質的にJavaScriptファイルの解析目標(`module`または`script`)を決定することと同じ課題であり、そのため、Nodeが`.js`ファイルに対してその方法を考案した場合、CoffeeScriptはNodeのアルゴリズムをコピーできます。
sourceType: ‘module’
body, directives
}
astLocationData: ->
return if @isRootBlock and not @locationData?
super()
ディレクティブ(例:'use strict')。現在、AST生成中にのみ使用されます。
exports.Directive = class Directive extends Base
constructor: (@value) ->
super()
astProperties: (o) ->
return
value: Object.assign {},
@value.ast o
type: 'DirectiveLiteral'
`Literal`は、文字列、数値、`true`、`false`、`null`など、翻訳せずに直接JavaScriptに渡すことができる静的値の基本クラスです。
exports.Literal = class Literal extends Base
constructor: (@value) ->
super()
shouldCache: NO
assigns: (name) ->
name is @value
compileNode: (o) ->
[@makeCode @value]
astProperties: ->
return
value: @value
toString: ->
これはデバッグのみに使用することを意図しています。
" #{if @isStatement() then super() else @constructor.name}: #{@value}"
exports.NumberLiteral = class NumberLiteral extends Literal
constructor: (@value, {@parsedValue} = {}) ->
super()
unless @parsedValue?
if isNumber @value
@parsedValue = @value
@value = "#{@value}"
else
@parsedValue = parseNumber @value
isBigInt: ->
/n$/.test @value
astType: ->
if @isBigInt()
'BigIntLiteral'
else
'NumericLiteral'
astProperties: ->
return
value:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
extra:
rawValue:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
raw: @value
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
super()
compileNode: ->
[@makeCode '2e308']
astNode: (o) ->
unless @originalValue is 'Infinity'
return new NumberLiteral(@value).withLocationDataFrom(@).ast o
super o
astType: -> 'Identifier'
astProperties: ->
return
name: 'Infinity'
declaration: no
exports.NaNLiteral = class NaNLiteral extends NumberLiteral
constructor: ->
super 'NaN'
compileNode: (o) ->
code = [@makeCode '0/0']
if o.level >= LEVEL_OP then @wrapInParentheses code else code
astType: -> 'Identifier'
astProperties: ->
return
name: 'NaN'
declaration: no
exports.StringLiteral = class StringLiteral extends Literal
constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
super ''
@quote = null if @quote is '///'
@fromSourceString = @quote?
@quote ?= '"'
heredoc = @isFromHeredoc()
val = @originalValue
if @heregex
val = val.replace HEREGEX_OMIT, '$1$2'
val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
else
val = val.replace STRING_OMIT, '$1'
val =
unless @fromSourceString
val
else if heredoc
indentRegex = /// \n#{@indent} ///g if @indent
val = val.replace indentRegex, '\n' if indentRegex
val = val.replace LEADING_BLANK_LINE, '' if @initialChunk
val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
val
else
val.replace SIMPLE_STRING_OMIT, (match, offset) =>
if (@initialChunk and offset is 0) or
(@finalChunk and offset + match.length is val.length)
''
else
' '
@delimiter = @quote.charAt 0
@value = makeDelimitedLiteral val, {
@delimiter
@double
}
@unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
delimiter: '`'
@double
escapeNewlines: no
includeDelimiters: no
convertTrailingNullEscapes: yes
}
@unquotedValueForJSX = makeDelimitedLiteral val, {
@double
escapeNewlines: no
includeDelimiters: no
escapeDelimiter: no
}
compileNode: (o) ->
return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
return [@makeCode @unquotedValueForJSX] if @jsx
super o
`StringLiteral`は、リテラル文字列全体、またはたとえば補間文字列内のテキスト部分を表すことができます。前者として解析されたが後者として扱う必要がある場合(たとえば、タグ付きテンプレートリテラルの文字列部分)、これは、引用符がその位置データからトリミングされた`StringLiteral`のコピーを返します(補間文字列の一部として解析された場合と同じです)。
withoutQuotesInLocationData: ->
endsWithNewline = @originalValue[-1..] is '\n'
locationData = Object.assign {}, @locationData
locationData.first_column += @quote.length
if endsWithNewline
locationData.last_line -= 1
locationData.last_column =
if locationData.last_line is locationData.first_line
locationData.first_column + @originalValue.length - '\n'.length
else
@originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
else
locationData.last_column -= @quote.length
locationData.last_column_exclusive -= @quote.length
locationData.range = [
locationData.range[0] + @quote.length
locationData.range[1] - @quote.length
]
copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
copy.locationData = locationData
copy
isFromHeredoc: ->
@quote.length is 3
shouldGenerateTemplateLiteral: ->
@isFromHeredoc()
astNode: (o) ->
return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
super o
astProperties: ->
return
value: @originalValue
extra:
raw: "#{@delimiter}#{@originalValue}#{@delimiter}"
exports.RegexLiteral = class RegexLiteral extends Literal
constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
super ''
heregex = @delimiter is '///'
endDelimiterIndex = value.lastIndexOf '/'
@flags = value[endDelimiterIndex + 1..]
val = @originalValue = value[1...endDelimiterIndex]
val = val.replace HEREGEX_OMIT, '$1$2' if heregex
val = replaceUnicodeCodePointEscapes val, {@flags}
@value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"
REGEX_REGEX: /// ^ / (.*) / \w* $ ///
astType: -> 'RegExpLiteral'
astProperties: (o) ->
[, pattern] = @REGEX_REGEX.exec @value
return {
value: undefined
pattern, @flags, @delimiter
originalPattern: @originalValue
extra:
raw: @value
originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
rawValue: undefined
comments:
for heregexCommentToken in @heregexCommentTokens
if heregexCommentToken.here
new HereComment(heregexCommentToken).ast o
else
new LineComment(heregexCommentToken).ast o
}
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
constructor: (@originalValue, {@here, @generated} = {}) ->
super ''
@value = @originalValue.replace /\\+(`|$)/g, (string) ->
`string`は常に`'``'`、`'\`'`、`'\\`'`などの値です。後半分に減らすことで、`'``'`を`''`に、`'\`'`を`'``'`などにします。
string[-Math.ceil(string.length / 2)..]
astNode: (o) ->
return null if @generated
super o
astProperties: ->
return {
value: @originalValue
here: !!@here
}
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: YES
eachName: (iterator) ->
iterator @
astType: ->
if @jsx
'JSXIdentifier'
else
'Identifier'
astProperties: ->
return
name: @value
declaration: !!@isDeclaration
exports.PropertyName = class PropertyName extends Literal
isAssignable: YES
astType: ->
if @jsx
'JSXIdentifier'
else
'Identifier'
astProperties: ->
return
name: @value
declaration: no
exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
compileNode: (o) ->
[@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
astNode: (o) ->
@value.ast o
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
makeReturn: THIS
jumps: (o) ->
return this if @value is 'break' and not (o?.loop or o?.block)
return this if @value is 'continue' and not o?.loop
compileNode: (o) ->
[@makeCode "#{@tab}#{@value};"]
astType: ->
switch @value
when 'continue' then 'ContinueStatement'
when 'break' then 'BreakStatement'
when 'debugger' then 'DebuggerStatement'
exports.ThisLiteral = class ThisLiteral extends Literal
constructor: (value) ->
super 'this'
@shorthand = value is '@'
compileNode: (o) ->
code = if o.scope.method?.bound then o.scope.method.context else @value
[@makeCode code]
astType: -> 'ThisExpression'
astProperties: ->
return
shorthand: @shorthand
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
super 'undefined'
compileNode: (o) ->
[@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
astType: -> 'Identifier'
astProperties: ->
return
name: @value
declaration: no
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
super 'null'
exports.BooleanLiteral = class BooleanLiteral extends Literal
constructor: (value, {@originalValue} = {}) ->
super value
@originalValue ?= @value
astProperties: ->
value: if @value is 'true' then yes else no
name: @originalValue
exports.DefaultLiteral = class DefaultLiteral extends Literal
astType: -> 'Identifier'
astProperties: ->
return
name: 'default'
declaration: no
`return`は*純粋なステートメント*です。クロージャでラップしても意味がありません。
exports.Return = class Return extends Base
constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
super()
children: ['expression']
isStatement: YES
makeReturn: THIS
jumps: THIS
compileToFragments: (o, level) ->
expr = @expression?.makeReturn()
if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
compileNode: (o) ->
answer = []
TODO:ここで`expression.compile()`を2回呼び出すと、異なる結果が返される場合があります!
if @expression
answer = @expression.compileToFragments o, LEVEL_PAREN
unshiftAfterComments answer, @makeCode "#{@tab}return "
`return`が`@tab`でインデントされているため、複数行の先行コメントをインデントする必要があります。
for fragment in answer
if fragment.isHereComment and '\n' in fragment.code
fragment.code = multident fragment.code, @tab
else if fragment.isLineComment
fragment.code = "#{@tab}#{fragment.code}"
else
break
else
answer.push @makeCode "#{@tab}return"
answer.push @makeCode ';'
answer
checkForPureStatementInExpression: ->
`await return`/`yield return`からの`return`を無効としてフラグ付けしないでください。
return if @belongsToFuncDirectiveReturn
super()
astType: -> 'ReturnStatement'
astProperties: (o) ->
argument: @expression?.ast(o, LEVEL_PAREN) ? null
`YieldReturn`/`AwaitReturn`の親クラス。
exports.FuncDirectiveReturn = class FuncDirectiveReturn extends Return
constructor: (expression, {@returnKeyword}) ->
super expression
compileNode: (o) ->
@checkScope o
super o
checkScope: (o) ->
unless o.scope.parent?
@error "#{@keyword} can only occur inside functions"
isStatementAst: NO
astNode: (o) ->
@checkScope o
new Op @keyword,
new Return @expression, belongsToFuncDirectiveReturn: yes
.withLocationDataFrom(
if @expression?
locationData: mergeLocationData @returnKeyword.locationData, @expression.locationData
else
@returnKeyword
)
.withLocationDataFrom @
.ast o
`yield return`は`return`と同じように機能しますが、関数をジェネレータに変換します。
exports.YieldReturn = class YieldReturn extends FuncDirectiveReturn
keyword: 'yield'
exports.AwaitReturn = class AwaitReturn extends FuncDirectiveReturn
keyword: 'await'
値、変数、リテラル、括弧付き、インデックス付き、またはドット付き、またはプレーン。
exports.Value = class Value extends Base
constructor: (base, props, tag, isDefaultValue = no) ->
super()
return base if not props and base instanceof Value
@base = base
@properties = props or []
@tag = tag
@[tag] = yes if tag
@isDefaultValue = isDefaultValue
これが`@foo =`代入の場合、`@`にコメントがある場合は、`foo`に移動します。
if @base?.comments and @base instanceof ThisLiteral and @properties[0]?.name?
moveComments @base, @properties[0].name
children: ['base', 'properties']
プロパティ(または*プロパティ*)`Access`をリストに追加します。
add: (props) ->
@properties = @properties.concat props
@forceUpdateLocation = yes
this
hasProperties: ->
@properties.length isnt 0
bareLiteral: (type) ->
not @properties.length and @base instanceof type
他のノードのためにいくつかのブールチェックを行います。
isArray : -> @bareLiteral(Arr)
isRange : -> @bareLiteral(Range)
shouldCache : -> @hasProperties() or @base.shouldCache()
isAssignable : (opts) -> @hasProperties() or @base.isAssignable opts
isNumber : -> @bareLiteral(NumberLiteral)
isString : -> @bareLiteral(StringLiteral)
isRegex : -> @bareLiteral(RegexLiteral)
isUndefined : -> @bareLiteral(UndefinedLiteral)
isNull : -> @bareLiteral(NullLiteral)
isBoolean : -> @bareLiteral(BooleanLiteral)
isAtomic : ->
for node in @properties.concat @base
return no if node.soak or node instanceof Call or node instanceof Op and node.operator is 'do'
yes
isNotCallable : -> @isNumber() or @isString() or @isRegex() or
@isArray() or @isRange() or @isSplice() or @isObject() or
@isUndefined() or @isNull() or @isBoolean()
isStatement : (o) -> not @properties.length and @base.isStatement o
isJSXTag : -> @base instanceof JSXTag
assigns : (name) -> not @properties.length and @base.assigns name
jumps : (o) -> not @properties.length and @base.jumps o
isObject: (onlyGenerated) ->
return no if @properties.length
(@base instanceof Obj) and (not onlyGenerated or @base.generated)
isElision: ->
return no unless @base instanceof Arr
@base.hasElision()
isSplice: ->
[..., lastProperty] = @properties
lastProperty instanceof Slice
looksStatic: (className) ->
return no unless ((thisLiteral = @base) instanceof ThisLiteral or (name = @base).value is className) and
@properties.length is 1 and @properties[0].name?.value isnt 'prototype'
return
staticClassName: thisLiteral ? name
アタッチされたプロパティがない場合、値は内部ノードとして展開できます。
unwrap: ->
if @properties.length then this else @base
参照には、基本部分(`this`値)と名前部分があります。複雑な式をコンパイルするために、それらを個別にキャッシュします。`a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c`
cacheReference: (o) ->
[..., name] = @properties
if @properties.length < 2 and not @base.shouldCache() and not name?.shouldCache()
return [this, this] # `a` `a.b`
base = new Value @base, @properties[...-1]
if base.shouldCache() # `a().b`
bref = new IdentifierLiteral o.scope.freeVariable 'base'
base = new Value new Parens new Assign bref, base
return [base, bref] unless name # `a()`
if name.shouldCache() # `a[b()]`
nref = new IdentifierLiteral o.scope.freeVariable 'name'
name = new Index new Assign nref, name.index
nref = new Index nref
[base.add(name), new Value(bref or base.base, [nref or name])]
各プロパティをコンパイルして結合することで、値をJavaScriptにコンパイルします。プロパティのチェーンに`?.`という*ソーク*演算子が散在している場合、状況はさらに複雑になります。その場合、ソークチェーンを作成するときに、誤って何かを2回評価しないように注意する必要があります。
compileNode: (o) ->
@base.front = @front
props = @properties
if props.length and @base.cached?
キャッシュされたフラグメントにより、コンパイルの正しい順序と、スコープ内の変数の再利用が可能になります。例:`a(x = 5).b(-> x = 6)`は`a(x = 5); b(-> x = 6)`と同じ順序でコンパイルする必要があります(issue #4437、https://github.com/jashkenas/coffeescript/issues/4437参照)
fragments = @base.cached
else
fragments = @base.compileToFragments o, (if props.length then LEVEL_ACCESS else null)
if props.length and SIMPLENUM.test fragmentsToText fragments
fragments.push @makeCode '.'
for prop in props
fragments.push (prop.compileToFragments o)...
fragments
ソークを`If`に展開します:`a?.b` -> `a.b if a?`
unfoldSoak: (o) ->
@unfoldedSoak ?= do =>
ifn = @base.unfoldSoak o
if ifn
ifn.body.properties.push @properties...
return ifn
for prop, i in @properties when prop.soak
prop.soak = off
fst = new Value @base, @properties[...i]
snd = new Value @base, @properties[i..]
if fst.shouldCache()
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, fst
snd.base = ref
return new If new Existence(fst), snd, soak: on
no
eachName: (iterator, {checkAssignability = yes} = {}) ->
if @hasProperties()
iterator @
else if not checkAssignability or @base.isAssignable()
@base.eachName iterator
else
@error 'tried to assign to unassignable value'
AST生成のために、この`Value`から最後のプロパティ(プロパティがある場合)を除いた`object`が必要です。
object: ->
return @ unless @hasProperties()
最後の1つを除くすべてプロパティを取得します。プロパティが1つしかない`Value`の場合、`initialProperties`は空の配列です。
initialProperties = @properties[0.[email protected] - 1]
分離された最後のプロパティの新しい「ベース」になる`object`を作成します。
object = new Value @base, initialProperties, @tag, @isDefaultValue
新しいノードに位置データを付加し、ソースマップまたはAST位置データへの後続の変換のために正しい位置データを持つようにします。
object.locationData =
if initialProperties.length is 0
この新しい`Value`にはプロパティが1つしかないため、位置データは親`Value`のベースの位置データだけです。
@base.locationData
else
この新しい`Value`には複数のプロパティがあるため、位置データは親`Value`のベースから、この新しいノードに含まれる最後のプロパティ(別名、親の2番目の最後のプロパティ)までをカバーします。
mergeLocationData @base.locationData, initialProperties[initialProperties.length - 1].locationData
object
containsSoak: ->
return no unless @hasProperties()
for property in @properties when property.soak
return yes
return yes if @base instanceof Call and @base.soak
no
astNode: (o) ->
`Value`にプロパティがない場合、ASTノードは、このノードの`base`です。
return @base.ast o unless @hasProperties()
それ以外の場合は、次に`astType`メソッドと`astProperties`メソッドを呼び出す`Base::ast`を呼び出します。
super o
astType: ->
if @isJSXTag()
'JSXMemberExpression'
else if @containsSoak()
'OptionalMemberExpression'
else
'MemberExpression'
この`Value`にプロパティがある場合、*最後の*プロパティ(例:`a.b.c`の`c`)が`property`になり、先行するプロパティ(例:`a.b`)が`object`プロパティに割り当てられた子`Value`ノードになります。
astProperties: (o) ->
[..., property] = @properties
property.name.jsx = yes if @isJSXTag()
computed = property instanceof Index or property.name?.unwrap() not instanceof PropertyName
return {
object: @object().ast o, LEVEL_ACCESS
property: property.ast o, (LEVEL_PAREN if computed)
computed
optional: !!property.soak
shorthand: !!property.shorthand
}
astLocationData: ->
return super() unless @isJSXTag()
JSXタグの先頭`<`を位置データに含めないでください。
mergeAstLocationData(
jisonLocationDataToAstLocationData(@base.tagNameLocationData),
jisonLocationDataToAstLocationData(@properties[@properties.length - 1].locationData)
)
exports.MetaProperty = class MetaProperty extends Base
constructor: (@meta, @property) ->
super()
children: ['meta', 'property']
checkValid: (o) ->
if @meta.value is 'new'
if @property instanceof Access and @property.name.value is 'target'
unless o.scope.parent?
@error "new.target can only occur inside functions"
else
@error "the only valid meta property for new is new.target"
else if @meta.value is 'import'
unless @property instanceof Access and @property.name.value is 'meta'
@error "the only valid meta property for import is import.meta"
compileNode: (o) ->
@checkValid o
fragments = []
fragments.push @meta.compileToFragments(o, LEVEL_ACCESS)...
fragments.push @property.compileToFragments(o)...
fragments
astProperties: (o) ->
@checkValid o
return
meta: @meta.ast o, LEVEL_ACCESS
property: @property.ast o
`###`で区切られたコメント(`/* */`になります)。
exports.HereComment = class HereComment extends Base
constructor: ({ @content, @newLine, @unshift, @locationData }) ->
super()
compileNode: (o) ->
multiline = '\n' in @content
複数行コメントのインデントを解除します。後で再インデントされます。
if multiline
indent = null
for line in @content.split '\n'
leadingWhitespace = /^\s*/.exec(line)[0]
if not indent or leadingWhitespace.length < indent.length
indent = leadingWhitespace
@content = @content.replace /// \n #{indent} ///g, '\n' if indent
hasLeadingMarks = /\n\s*[#|\*]/.test @content
@content = @content.replace /^([ \t]*)#(?=\s)/gm, ' *' if hasLeadingMarks
@content = "/*#{@content}#{if hasLeadingMarks then ' ' else ''}*/"
fragment = @makeCode @content
fragment.newLine = @newLine
fragment.unshift = @unshift
fragment.multiline = multiline
コンパイラがミニファイされたときに壊れる可能性のある`fragment.type`に依存しないでください。
fragment.isComment = fragment.isHereComment = yes
fragment
astType: -> 'CommentBlock'
astProperties: ->
return
value: @content
`#`から行末まで続くコメント(`//`になります)。
exports.LineComment = class LineComment extends Base
constructor: ({ @content, @newLine, @unshift, @locationData, @precededByBlankLine }) ->
super()
compileNode: (o) ->
fragment = @makeCode(if /^\s*$/.test @content then '' else "#{if @precededByBlankLine then "\n#{o.indent}" else ''}//#{@content}")
fragment.newLine = @newLine
fragment.unshift = @unshift
fragment.trail = not @newLine and not @unshift
コンパイラがミニファイされたときに壊れる可能性のある`fragment.type`に依存しないでください。
fragment.isComment = fragment.isLineComment = yes
fragment
astType: -> 'CommentLine'
astProperties: ->
return
value: @content
exports.JSXIdentifier = class JSXIdentifier extends IdentifierLiteral
astType: -> 'JSXIdentifier'
exports.JSXTag = class JSXTag extends JSXIdentifier
constructor: (value, {
@tagNameLocationData
@closingTagOpeningBracketLocationData
@closingTagSlashLocationData
@closingTagNameLocationData
@closingTagClosingBracketLocationData
}) ->
super value
astProperties: ->
return
name: @value
exports.JSXExpressionContainer = class JSXExpressionContainer extends Base
constructor: (@expression, {locationData} = {}) ->
super()
@expression.jsxAttribute = yes
@locationData = locationData ? @expression.locationData
children: ['expression']
compileNode: (o) ->
@expression.compileNode(o)
astProperties: (o) ->
return
expression: astAsBlockIfNeeded @expression, o
exports.JSXEmptyExpression = class JSXEmptyExpression extends Base
exports.JSXText = class JSXText extends Base
constructor: (stringLiteral) ->
super()
@value = stringLiteral.unquotedValueForJSX
@locationData = stringLiteral.locationData
astProperties: ->
return {
@value
extra:
raw: @value
}
exports.JSXAttribute = class JSXAttribute extends Base
constructor: ({@name, value}) ->
super()
@value =
if value?
value = value.base
if value instanceof StringLiteral and not value.shouldGenerateTemplateLiteral()
value
else
new JSXExpressionContainer value
else
null
@value?.comments = value.comments
children: ['name', 'value']
compileNode: (o) ->
compiledName = @name.compileToFragments o, LEVEL_LIST
return compiledName unless @value?
val = @value.compileToFragments o, LEVEL_LIST
compiledName.concat @makeCode('='), val
astProperties: (o) ->
name = @name
if ':' in name.value
name = new JSXNamespacedName name
return
name: name.ast o
value: @value?.ast(o) ? null
exports.JSXAttributes = class JSXAttributes extends Base
constructor: (arr) ->
super()
@attributes = []
for object in arr.objects
@checkValidAttribute object
{base} = object
if base instanceof IdentifierLiteral
値のない属性(例:disabled)
attribute = new JSXAttribute name: new JSXIdentifier(base.value).withLocationDataAndCommentsFrom base
attribute.locationData = base.locationData
@attributes.push attribute
else if not base.generated
オブジェクトスプレッド属性(例:{…props})
attribute = base.properties[0]
attribute.jsx = yes
attribute.locationData = base.locationData
@attributes.push attribute
else
値を持つ属性を含むオブジェクト(例:a=”b” c={d})
for property in base.properties
{variable, value} = property
attribute = new JSXAttribute {
name: new JSXIdentifier(variable.base.value).withLocationDataAndCommentsFrom variable.base
value
}
attribute.locationData = property.locationData
@attributes.push attribute
@locationData = arr.locationData
children: ['attributes']
無効な属性をキャッチします:`
` checkValidAttribute: (object) ->
{base: attribute} = object
properties = attribute?.properties or []
if not (attribute instanceof Obj or attribute instanceof IdentifierLiteral) or (attribute instanceof Obj and not attribute.generated and (properties.length > 1 or not (properties[0] instanceof Splat)))
object.error """
Unexpected token. Allowed JSX attributes are: id="val", src={source}, {props...} or attribute.
"""
compileNode: (o) ->
fragments = []
for attribute in @attributes
fragments.push @makeCode ' '
fragments.push attribute.compileToFragments(o, LEVEL_TOP)...
fragments
astNode: (o) ->
attribute.ast(o) for attribute in @attributes
exports.JSXNamespacedName = class JSXNamespacedName extends Base
constructor: (tag) ->
super()
[namespace, name] = tag.value.split ':'
@namespace = new JSXIdentifier(namespace).withLocationDataFrom locationData: extractSameLineLocationDataFirst(namespace.length) tag.locationData
@name = new JSXIdentifier(name ).withLocationDataFrom locationData: extractSameLineLocationDataLast(name.length ) tag.locationData
@locationData = tag.locationData
children: ['namespace', 'name']
astProperties: (o) ->
return
namespace: @namespace.ast o
name: @name.ast o
JSX要素のノード
exports.JSXElement = class JSXElement extends Base
constructor: ({@tagName, @attributes, @content}) ->
super()
children: ['tagName', 'attributes', 'content']
compileNode: (o) ->
@content?.base.jsx = yes
fragments = [@makeCode('<')]
fragments.push (tag = @tagName.compileToFragments(o, LEVEL_ACCESS))...
fragments.push @attributes.compileToFragments(o)...
if @content
fragments.push @makeCode('>')
fragments.push @content.compileNode(o, LEVEL_LIST)...
fragments.push [@makeCode('</'), tag..., @makeCode('>')]...
else
fragments.push @makeCode(' />')
fragments
isFragment: ->
[email protected]
astNode: (o) ->
開始要素`< … >`にまたがる位置データは、要素の属性を含む生成されたArrによってキャプチャされます。
@openingElementLocationData = jisonLocationDataToAstLocationData @attributes.locationData
tagName = @tagName.base
tagName.locationData = tagName.tagNameLocationData
if @content?
@closingElementLocationData = mergeAstLocationData(
jisonLocationDataToAstLocationData tagName.closingTagOpeningBracketLocationData
jisonLocationDataToAstLocationData tagName.closingTagClosingBracketLocationData
)
super o
astType: ->
if @isFragment()
'JSXFragment'
else
'JSXElement'
elementAstProperties: (o) ->
tagNameAst = =>
tag = @tagName.unwrap()
if tag?.value and ':' in tag.value
tag = new JSXNamespacedName tag
tag.ast o
openingElement = Object.assign {
type: 'JSXOpeningElement'
name: tagNameAst()
selfClosing: not @closingElementLocationData?
attributes: @attributes.ast o
}, @openingElementLocationData
closingElement = null
if @closingElementLocationData?
closingElement = Object.assign {
type: 'JSXClosingElement'
name: Object.assign(
tagNameAst(),
jisonLocationDataToAstLocationData @tagName.base.closingTagNameLocationData
)
}, @closingElementLocationData
if closingElement.name.type in ['JSXMemberExpression', 'JSXNamespacedName']
rangeDiff = closingElement.range[0] - openingElement.range[0] + '/'.length
columnDiff = closingElement.loc.start.column - openingElement.loc.start.column + '/'.length
shiftAstLocationData = (node) =>
node.range = [
node.range[0] + rangeDiff
node.range[1] + rangeDiff
]
node.start += rangeDiff
node.end += rangeDiff
node.loc.start =
line: @closingElementLocationData.loc.start.line
column: node.loc.start.column + columnDiff
node.loc.end =
line: @closingElementLocationData.loc.start.line
column: node.loc.end.column + columnDiff
if closingElement.name.type is 'JSXMemberExpression'
currentExpr = closingElement.name
while currentExpr.type is 'JSXMemberExpression'
shiftAstLocationData currentExpr unless currentExpr is closingElement.name
shiftAstLocationData currentExpr.property
currentExpr = currentExpr.object
shiftAstLocationData currentExpr
else # JSXNamespacedName
shiftAstLocationData closingElement.name.namespace
shiftAstLocationData closingElement.name.name
{openingElement, closingElement}
fragmentAstProperties: (o) ->
openingFragment = Object.assign {
type: 'JSXOpeningFragment'
}, @openingElementLocationData
closingFragment = Object.assign {
type: 'JSXClosingFragment'
}, @closingElementLocationData
{openingFragment, closingFragment}
contentAst: (o) ->
return [] unless @content and not @content.base.isEmpty?()
content = @content.unwrapAll()
children =
if content instanceof StringLiteral
[new JSXText content]
else # StringWithInterpolations
for element in @content.unwrapAll().extractElements o, includeInterpolationWrappers: yes, isJsx: yes
if element instanceof StringLiteral
new JSXText element
else # Interpolation
{expression} = element
unless expression?
emptyExpression = new JSXEmptyExpression()
emptyExpression.locationData = emptyExpressionLocationData {
interpolationNode: element
openingBrace: '{'
closingBrace: '}'
}
new JSXExpressionContainer emptyExpression, locationData: element.locationData
else
unwrapped = expression.unwrapAll()
if unwrapped instanceof JSXElement and
unwrapped.locationData.range[0] is element.locationData.range[0]
unwrapped
else
new JSXExpressionContainer unwrapped, locationData: element.locationData
child.ast(o) for child in children when not (child instanceof JSXText and child.value.length is 0)
astProperties: (o) ->
Object.assign(
if @isFragment()
@fragmentAstProperties o
else
@elementAstProperties o
,
children: @contentAst o
)
astLocationData: ->
if @closingElementLocationData?
mergeAstLocationData @openingElementLocationData, @closingElementLocationData
else
@openingElementLocationData
関数呼び出しのノード。
exports.Call = class Call extends Base
constructor: (@variable, @args = [], @soak, @token) ->
super()
@implicit = @args.implicit
@isNew = no
if @variable instanceof Value and @variable.isNotCallable()
@variable.error "literal is not a function"
if @variable.base instanceof JSXTag
return new JSXElement(
tagName: @variable
attributes: new JSXAttributes @args[0].base
content: @args[1]
)
`@variable`は、`RegexWithInterpolations`の一部として作成されたこのノードの結果として出力されることは決してないため、その場合は、構文を通じて`RegexWithInterpolations`に渡される`args`プロパティにコメントを移動します。
if @variable.base?.value is 'RegExp' and @args.length isnt 0
moveComments @variable, @args[0]
children: ['variable', 'args']
位置を設定する際、左側に新しく発見された`new`演算子を考慮して開始位置を更新する必要がある場合があります。これにより、左側の範囲は拡大しますが、右側の範囲は拡大しません。
updateLocationDataIfMissing: (locationData) ->
if @locationData and @needsUpdatedStartLocation
@locationData = Object.assign {},
@locationData,
first_line: locationData.first_line
first_column: locationData.first_column
range: [
locationData.range[0]
@locationData.range[1]
]
base = @variable?.base or @variable
if base.needsUpdatedStartLocation
@variable.locationData = Object.assign {},
@variable.locationData,
first_line: locationData.first_line
first_column: locationData.first_column
range: [
locationData.range[0]
@variable.locationData.range[1]
]
base.updateLocationDataIfMissing locationData
delete @needsUpdatedStartLocation
super locationData
新しいインスタンスを作成する呼び出しとしてタグ付けします。
newInstance: ->
base = @variable?.base or @variable
if base instanceof Call and not base.isNew
base.newInstance()
else
@isNew = true
@needsUpdatedStartLocation = true
this
ソークされたチェーンされた呼び出しは、if/else三項構造に展開されます。
unfoldSoak: (o) ->
if @soak
if @variable instanceof Super
left = new Literal @variable.compile o
rite = new Value left
@variable.error "Unsupported reference to 'super'" unless @variable.accessor?
else
return ifn if ifn = unfoldSoak o, this, 'variable'
[left, rite] = new Value(@variable).cacheReference o
rite = new Call rite, @args
rite.isNew = @isNew
left = new Literal "typeof #{ left.compile o } === \"function\""
return new If left, new Value(rite), soak: yes
call = this
list = []
loop
if call.variable instanceof Call
list.push call
call = call.variable
continue
break unless call.variable instanceof Value
list.push call
break unless (call = call.variable.base) instanceof Call
for call in list.reverse()
if ifn
if call.variable instanceof Call
call.variable = ifn
else
call.variable.base = ifn
ifn = unfoldSoak o, call, 'variable'
ifn
プレーンな関数呼び出しをコンパイルします。
compileNode: (o) ->
@checkForNewSuper()
@variable?.front = @front
compiledArgs = []
変数が`Accessor`の場合、フラグメントはキャッシュされ、後で`Value::compileNode`で使用され、コンパイルの正しい順序とスコープ内の変数の再利用が保証されます。例:`a(x = 5).b(-> x = 6)`は`a(x = 5); b(-> x = 6)`と同じ順序でコンパイルする必要があります(issue #4437、https://github.com/jashkenas/coffeescript/issues/4437参照)
varAccess = @variable?.properties?[0] instanceof Access
argCode = (arg for arg in (@args || []) when arg instanceof Code)
if argCode.length > 0 and varAccess and not @variable.base.cached
[cache] = @variable.base.cache o, LEVEL_ACCESS, -> no
@variable.base.cached = cache
for arg, argIndex in @args
if argIndex then compiledArgs.push @makeCode ", "
compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
fragments = []
if @isNew
fragments.push @makeCode 'new '
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
fragments.push @makeCode('('), compiledArgs..., @makeCode(')')
fragments
checkForNewSuper: ->
if @isNew
@variable.error "Unsupported reference to 'super'" if @variable instanceof Super
containsSoak: ->
return yes if @soak
return yes if @variable?.containsSoak?()
no
astNode: (o) ->
if @soak and @variable instanceof Super and o.scope.namedMethod()?.ctor
@variable.error "Unsupported reference to 'super'"
@checkForNewSuper()
super o
astType: ->
if @isNew
'NewExpression'
else if @containsSoak()
'OptionalCallExpression'
else
'CallExpression'
astProperties: (o) ->
return
callee: @variable.ast o, LEVEL_ACCESS
arguments: arg.ast(o, LEVEL_LIST) for arg in @args
optional: !!@soak
implicit: !!@implicit
`super()`呼び出しを、同じ名前のプロトタイプの関数に対する呼び出しに変換します。`expressions`が設定されている場合、呼び出しは、`SuperCall`式の戻り値を変更せずに式が評価されるようにコンパイルされます。
exports.SuperCall = class SuperCall extends Call
children: Call::children.concat ['expressions']
isStatement: (o) ->
@expressions?.length and o.level is LEVEL_TOP
compileNode: (o) ->
return super o unless @expressions?.length
superCall = new Literal fragmentsToText super o
replacement = new Block @expressions.slice()
if o.level > LEVEL_TOP
式内にある可能性がある場合、結果をキャッシュして返す必要があります。
[superCall, ref] = superCall.cache o, null, YES
replacement.push ref
replacement.unshift superCall
replacement.compileToFragments o, if o.level is LEVEL_TOP then o.level else LEVEL_LIST
exports.Super = class Super extends Base
constructor: (@accessor, @superLiteral) ->
super()
children: ['accessor']
compileNode: (o) ->
@checkInInstanceMethod o
method = o.scope.namedMethod()
unless method.ctor? or @accessor?
{name, variable} = method
if name.shouldCache() or (name instanceof Index and name.index.isAssignable())
nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
name.index = new Assign nref, name.index
@accessor = if nref? then new Index nref else name
if @accessor?.name?.comments
super()
の呼び出しは、例えば super.method()
にコンパイルされます。これは、method
プロパティ名が最初にここでコンパイルされ、クラスの method:
プロパティがコンパイルされるときに再度コンパイルされることを意味します。このコンパイルは最初に発生するため、method:
に付加されたコメントは、super.method()
の近くに誤って出力されますが、method:
が出力されるときの2回目のパスで出力されるようにしたいです。そのため、このコンパイルパス中にコメントを一旦脇に置き、後でコンパイルできるようにオブジェクトに戻します。
salvagedComments = @accessor.name.comments
delete @accessor.name.comments
fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
.compileToFragments o
attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
fragments
checkInInstanceMethod: (o) ->
method = o.scope.namedMethod()
@error 'cannot use super outside of an instance method' unless method?.isMethod
astNode: (o) ->
@checkInInstanceMethod o
if @accessor?
return (
new Value(
new Super().withLocationDataFrom (@superLiteral ? @)
[@accessor]
).withLocationDataFrom @
).ast o
super o
補間を含む正規表現は、実際には Call
(正確には RegExp()
呼び出し)の変種であり、内部に StringWithInterpolations
を含んでいます。
exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
constructor: (@call, {@heregexCommentTokens = []} = {}) ->
super()
children: ['call']
compileNode: (o) ->
@call.compileNode o
astType: -> 'InterpolatedRegExpLiteral'
astProperties: (o) ->
interpolatedPattern: @call.args[0].ast o
flags: @call.args[1]?.unwrap().originalValue ? ''
comments:
for heregexCommentToken in @heregexCommentTokens
if heregexCommentToken.here
new HereComment(heregexCommentToken).ast o
else
new LineComment(heregexCommentToken).ast o
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
constructor: (variable, arg, soak) ->
arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
super variable, [ arg ], soak
compileNode: (o) ->
@variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
astType: -> 'TaggedTemplateExpression'
astProperties: (o) ->
return
tag: @variable.ast o, LEVEL_ACCESS
quasi: @args[0].ast o, LEVEL_LIST
オブジェクトのプロトタイプに祖先オブジェクトを拡張するためのノードです。Closure Library の goog.inherits
を基にしています。
exports.Extends = class Extends extends Base
constructor: (@child, @parent) ->
super()
children: ['child', 'parent']
あるコンストラクタを別のコンストラクタのプロトタイプチェーンにフックします。
compileToFragments: (o) ->
new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o
値のプロパティへの .
アクセス、またはオブジェクトのプロトタイプへのアクセスのための ::
ショートハンドです。
exports.Access = class Access extends Base
constructor: (@name, {@soak, @shorthand} = {}) ->
super()
children: ['name']
compileToFragments: (o) ->
name = @name.compileToFragments o
node = @name.unwrap()
if node instanceof PropertyName
[@makeCode('.'), name...]
else
[@makeCode('['), name..., @makeCode(']')]
shouldCache: NO
astNode: (o) ->
Babel には Access
の AST ノードがありませんが、代わりにこの Access ノードの子である name
Identifier ノードを MemberExpression
ノードの property
として含めます。
@name.ast o
配列またはオブジェクトへの [ ... ]
インデックスアクセスです。
exports.Index = class Index extends Base
constructor: (@index) ->
super()
children: ['index']
compileToFragments: (o) ->
[].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
shouldCache: ->
@index.shouldCache()
astNode: (o) ->
Babel には Index
の AST ノードがありませんが、代わりにこの Index ノードの子である index
Identifier ノードを MemberExpression
ノードの property
として含めます。MemberExpression
の property
が Index であるということは、MemberExpression
の computed
が true
であることを意味します。
@index.ast o
範囲リテラルです。範囲は、配列の一部(スライス)を抽出したり、内包表記の範囲を指定したり、値として使用して、実行時に対応する整数配列に展開したりするために使用できます。
exports.Range = class Range extends Base
children: ['from', 'to']
constructor: (@from, @to, tag) ->
super()
@exclusive = tag is 'exclusive'
@equals = if @exclusive then '' else '='
範囲のソース変数(開始位置と終了位置)をコンパイルします。ただし、二重評価を避けるためにキャッシュする必要がある場合のみです。
compileVariables: (o) ->
o = merge o, top: true
shouldCache = del o, 'shouldCache'
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
@fromNum = if @from.isNumber() then parseNumber @fromVar else null
@toNum = if @to.isNumber() then parseNumber @toVar else null
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
通常通りコンパイルされると、範囲は範囲内の値を反復処理するために必要な *for ループ* の内容を返します。内包表記で使用されます。
compileNode: (o) ->
@compileVariables o unless @fromVar
return @compileArray(o) unless o.index
エンドポイントを設定します。
known = @fromNum? and @toNum?
idx = del o, 'index'
idxName = del o, 'name'
namedIndex = idxName and idxName isnt idx
varPart =
if known and not namedIndex
"var #{idx} = #{@fromC}"
else
"#{idx} = #{@fromC}"
varPart += ", #{@toC}" if @toC isnt @toVar
varPart += ", #{@step}" if @step isnt @stepVar
[lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
条件を生成します。
[from, to] = [@fromNum, @toNum]
無限ループを回避するために、常に step
がゼロでないかどうかを確認します。
stepNotZero = "#{ @stepNum ? @stepVar } !== 0"
stepCond = "#{ @stepNum ? @stepVar } > 0"
lowerBound = "#{lt} #{ if known then to else @toVar }"
upperBound = "#{gt} #{ if known then to else @toVar }"
condPart =
if @step?
if @stepNum? and @stepNum isnt 0
if @stepNum > 0 then "#{lowerBound}" else "#{upperBound}"
else
"#{stepNotZero} && (#{stepCond} ? #{lowerBound} : #{upperBound})"
else
if known
"#{ if from <= to then lt else gt } #{to}"
else
"(#{@fromVar} <= #{@toVar} ? #{lowerBound} : #{upperBound})"
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
ステップを生成します。
stepPart = if @stepVar
"#{idx} += #{@stepVar}"
else if known
if namedIndex
if from <= to then "++#{idx}" else "--#{idx}"
else
if from <= to then "#{idx}++" else "#{idx}--"
else
if namedIndex
"#{cond} ? ++#{idx} : --#{idx}"
else
"#{cond} ? #{idx}++ : #{idx}--"
varPart = "#{idxName} = #{varPart}" if namedIndex
stepPart = "#{idxName} = #{stepPart}" if namedIndex
最終的なループ本体です。
[@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
値として使用する場合、範囲を等価な配列に展開します。
compileArray: (o) ->
known = @fromNum? and @toNum?
if known and Math.abs(@fromNum - @toNum) <= 20
range = [@fromNum..@toNum]
range.pop() if @exclusive
return [@makeCode "[#{ range.join(', ') }]"]
idt = @tab + TAB
i = o.scope.freeVariable 'i', single: true, reserve: no
result = o.scope.freeVariable 'results', reserve: no
pre = "\n#{idt}var #{result} = [];"
if known
o.index = i
body = fragmentsToText @compileNode o
else
vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
cond = "#{@fromVar} <= #{@toVar}"
body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
hasArgs = (node) -> node?.contains isLiteralArguments
args = ', arguments' if hasArgs(@from) or hasArgs(@to)
[@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
astProperties: (o) ->
return {
from: @from?.ast(o) ? null
to: @to?.ast(o) ? null
@exclusive
}
配列スライスリテラルです。JavaScript の Array#slice
とは異なり、2番目のパラメータはスライスの終了インデックスを指定し、1番目のパラメータは開始インデックスを指定します。
exports.Slice = class Slice extends Base
children: ['range']
constructor: (@range) ->
super()
配列の終端をスライスしようとする際には注意が必要です。9e9
が使用されているのは、すべての実装が undefined
または 1/0
を尊重するわけではないためです。9e9
は 9e9
> 2**32
(最大配列長)であるため安全です。
compileNode: (o) ->
{to, from} = @range
プロパティアクセス内の式を処理します(例:a[!b in c..]
)。
if from?.shouldCache()
from = new Value new Parens from
if to?.shouldCache()
to = new Value new Parens to
fromCompiled = from?.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
if to
compiled = to.compileToFragments o, LEVEL_PAREN
compiledText = fragmentsToText compiled
if not (not @range.exclusive and +compiledText is -1)
toStr = ', ' + if @range.exclusive
compiledText
else if to.isNumber()
"#{+compiledText + 1}"
else
compiled = to.compileToFragments o, LEVEL_ACCESS
"+#{fragmentsToText compiled} + 1 || 9e9"
[@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
astNode: (o) ->
@range.ast o
オブジェクトリテラルで、特別なことはありません。
exports.Obj = class Obj extends Base
constructor: (props, @generated = no) ->
super()
@objects = @properties = props or []
children: ['properties']
isAssignable: (opts) ->
for prop in @properties
予約語を確認します。
message = isUnassignable prop.unwrapAll().value
prop.error message if message
prop = prop.value if prop instanceof Assign and
prop.context is 'object' and
prop.value?.base not instanceof Arr
return no unless prop.isAssignable opts
yes
shouldCache: ->
not @isAssignable()
オブジェクトにスプラットが含まれているかどうかを確認します。
hasSplat: ->
return yes for prop in @properties when prop instanceof Splat
no
rest プロパティをリストの最後に移動します。{a, rest..., b} = obj
-> {a, b, rest...} = obj
foo = ({a, rest..., b}) ->
-> foo = {a, b, rest...}) ->
reorderProperties: ->
props = @properties
splatProps = @getAndCheckSplatProps()
splatProp = props.splice splatProps[0], 1
@objects = @properties = [].concat props, splatProp
compileNode: (o) ->
@reorderProperties() if @hasSplat() and @lhs
props = @properties
if @generated
for node in props when node instanceof Value
node.error 'cannot have an implicit value in an implicit object'
idt = o.indent += TAB
lastNode = @lastNode @properties
このオブジェクトが代入の左辺である場合、すべての子も左辺です。
@propagateLhs()
isCompact = yes
for prop in @properties
if prop instanceof Assign and prop.context is 'object'
isCompact = no
answer = []
answer.push @makeCode if isCompact then '' else '\n'
for prop, i in props
join = if i is props.length - 1
''
else if isCompact
', '
else if prop is lastNode
'\n'
else
',\n'
indent = if isCompact then '' else idt
key = if prop instanceof Assign and prop.context is 'object'
prop.variable
else if prop instanceof Assign
prop.operatorToken.error "unexpected #{prop.operatorToken.value}" unless @lhs
prop.variable
else
prop
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' if prop.context is 'object' or not key.this
key = key.properties[0].name
prop = new Assign key, prop, 'object'
if key is prop
if prop.shouldCache()
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else if key instanceof Value and key.base instanceof ComputedPropertyName
{ [foo()] }
は { [ref = foo()]: ref }
として出力されます。
if prop.base.value.shouldCache()
[key, value] = prop.base.value.cache o
key = new ComputedPropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else
{ [expression] }
は { [expression]: expression }
として出力されます。
prop = new Assign key, prop.base.value, 'object'
else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
prop = new Assign prop, prop, 'object'
if indent then answer.push @makeCode indent
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
answer.push @makeCode if isCompact then '' else "\n#{@tab}"
answer = @wrapInBraces answer
if @front then @wrapInParentheses answer else answer
getAndCheckSplatProps: ->
return unless @hasSplat() and @lhs
props = @properties
splatProps = (i for prop, i in props when prop instanceof Splat)
props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
splatProps
assigns: (name) ->
for prop in @properties when prop.assigns name then return yes
no
eachName: (iterator) ->
for prop in @properties
prop = prop.value if prop instanceof Assign and prop.context is 'object'
prop = prop.unwrapAll()
prop.eachName iterator if prop.eachName?
「ベア」プロパティを ObjectProperty
(または Splat
)に変換します。
expandProperty: (property) ->
{variable, context, operatorToken} = property
key = if property instanceof Assign and context is 'object'
variable
else if property instanceof Assign
operatorToken.error "unexpected #{operatorToken.value}" unless @lhs
variable
else
property
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' unless context isnt 'object' and key.this
if property instanceof Assign
return new ObjectProperty fromAssign: property
else
return new ObjectProperty key: property
return new ObjectProperty(fromAssign: property) unless key is property
return property if property instanceof Splat
new ObjectProperty key: property
expandProperties: ->
@expandProperty(property) for property in @properties
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
for property in @properties
if property instanceof Assign and property.context is 'object'
{value} = property
unwrappedValue = value.unwrapAll()
if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj
unwrappedValue.propagateLhs yes
else if unwrappedValue instanceof Assign
unwrappedValue.nestedLhs = yes
else if property instanceof Assign
デフォルト値を持つショートハンドプロパティ(例:{a = 1} = b
)。
property.nestedLhs = yes
else if property instanceof Splat
property.propagateLhs yes
astNode: (o) ->
@getAndCheckSplatProps()
super o
astType: ->
if @lhs
'ObjectPattern'
else
'ObjectExpression'
astProperties: (o) ->
return
implicit: !!@generated
properties:
property.ast(o) for property in @expandProperties()
exports.ObjectProperty = class ObjectProperty extends Base
constructor: ({key, fromAssign}) ->
super()
if fromAssign
{variable: @key, value, context} = fromAssign
if context is 'object'
すべてのショートハンドではないプロパティ(つまり、:
を含む)。
@value = value
else
デフォルト値を持つ左辺ショートハンド(例:{a = 1} = b
)。
@value = fromAssign
@shorthand = yes
@locationData = fromAssign.locationData
else
デフォルト値のないショートハンド(例:{a}
または {@a}
または {[a]}
)。
@key = key
@shorthand = yes
@locationData = key.locationData
astProperties: (o) ->
isComputedPropertyName = (@key instanceof Value and @key.base instanceof ComputedPropertyName) or @key.unwrap() instanceof StringWithInterpolations
keyAst = @key.ast o, LEVEL_LIST
return
key:
if keyAst?.declaration
Object.assign {}, keyAst, declaration: no
else
keyAst
value: @value?.ast(o, LEVEL_LIST) ? keyAst
shorthand: !!@shorthand
computed: !!isComputedPropertyName
method: no
配列リテラルです。
exports.Arr = class Arr extends Base
constructor: (objs, @lhs = no) ->
super()
@objects = objs or []
@propagateLhs()
children: ['objects']
hasElision: ->
return yes for obj in @objects when obj instanceof Elision
no
isAssignable: (opts) ->
{allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
return allowEmptyArray unless @objects.length
for obj, i in @objects
return no if not allowNontrailingSplat and obj instanceof Splat and i + 1 isnt @objects.length
return no unless (allowExpansion and obj instanceof Expansion) or (obj.isAssignable(opts) and (not obj.isAtomic or obj.isAtomic()))
yes
shouldCache: ->
not @isAssignable()
compileNode: (o) ->
return [@makeCode '[]'] unless @objects.length
o.indent += TAB
fragmentIsElision = ([ fragment ]) ->
fragment.type is 'Elision' and fragment.code.trim() is ','
配列の先頭にある Elision
が処理されたかどうかを検出します(例:[, , , a])。
passedElision = no
answer = []
for obj, objIndex in @objects
unwrappedObj = obj.unwrapAll()
compileCommentFragments
に、この配列をコンパイルするときに作成されたフラグメントにブロックコメントを挿入するように指示します。
if unwrappedObj.comments and
unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
unwrappedObj.includeCommentFragments = YES
compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
olen = compiledObjs.length
compiledObjs
に改行が含まれている場合、これを複数行配列(つまり、[
の後に改行とインデントを付けたもの)として出力します。要素に行コメントが含まれている場合も、定義上、行コメントによって出力に改行が導入されるため、複数行出力がトリガーされます。例外は、最初の要素のみに行コメントがある場合です。その場合は、そうでなければコンパクト形式で出力するようにして、最初の要素の行コメントが出力の配列の前または後に出力されるようにします。
includesLineCommentsOnNonFirstElement = no
for fragments, index in compiledObjs
for fragment in fragments
if fragment.isHereComment
fragment.code = fragment.code.trim()
else if index isnt 0 and includesLineCommentsOnNonFirstElement is no and hasLineComments fragment
includesLineCommentsOnNonFirstElement = yes
配列の先頭にあるすべての Elision
が処理された場合(例:[, , , a])、かつ要素が Elision
ではなく、最後の要素が Elision
でない場合(例:[a,,b,,])、,
を追加します。
if index isnt 0 and passedElision and (not fragmentIsElision(fragments) or index is olen - 1)
answer.push @makeCode ', '
passedElision = passedElision or not fragmentIsElision fragments
answer.push fragments...
if includesLineCommentsOnNonFirstElement or '\n' in fragmentsToText(answer)
for fragment, fragmentIndex in answer
if fragment.isHereComment
fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
else if fragment.code is ', ' and not fragment?.isElision and fragment.type not in ['StringLiteral', 'StringWithInterpolations']
fragment.code = ",\n#{o.indent}"
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
else
for fragment in answer when fragment.isHereComment
fragment.code = "#{fragment.code} "
answer.unshift @makeCode '['
answer.push @makeCode ']'
answer
assigns: (name) ->
for obj in @objects when obj.assigns name then return yes
no
eachName: (iterator) ->
for obj in @objects
obj = obj.unwrapAll()
obj.eachName iterator
この配列が代入の左辺である場合、すべての子も左辺です。
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
for object in @objects
object.lhs = yes if object instanceof Splat or object instanceof Expansion
unwrappedObject = object.unwrapAll()
if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
unwrappedObject.propagateLhs yes
else if unwrappedObject instanceof Assign
unwrappedObject.nestedLhs = yes
astType: ->
if @lhs
'ArrayPattern'
else
'ArrayExpression'
astProperties: (o) ->
return
elements:
object.ast(o, LEVEL_LIST) for object in @objects
CoffeeScript クラス定義です。名前、オプションのスーパークラス、および本体を使用して **Class** を初期化します。
exports.Class = class Class extends Base
children: ['variable', 'parent', 'body']
constructor: (@variable, @parent, @body) ->
super()
unless @body?
@body = new Block
@hasGeneratedBody = yes
compileNode: (o) ->
@name = @determineName()
executableBody = @walkBody o
class expr.A extends A
宣言を許可するための特別な処理
parentName = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
@hasNameClash = @name? and @name is parentName
node = @
if executableBody or @hasNameClash
node = new ExecutableClassBody node, executableBody
else if not @name? and o.level is LEVEL_TOP
匿名クラスは式でのみ有効です。
node = new Parens node
if @boundMethods.length and @parent
@variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
[@variable, @variableRef] = @variable.cache o unless @variableRef?
if @variable
node = new Assign @variable, node, null, { @moduleDeclaration }
@compileNode = @compileClassDeclaration
try
return node.compileToFragments o
finally
delete @compileNode
compileClassDeclaration: (o) ->
@ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
@ctor?.noReturn = true
@proxyBoundMethods() if @boundMethods.length
o.indent += TAB
result = []
result.push @makeCode "class "
result.push @makeCode @name if @name
@compileCommentFragments o, @variable, result if @variable?.comments?
result.push @makeCode ' ' if @name
result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
result.push @makeCode '{'
unless @body.isEmpty()
@body.spaced = true
result.push @makeCode '\n'
result.push @body.compileToFragments(o, LEVEL_TOP)...
result.push @makeCode "\n#{@tab}"
result.push @makeCode '}'
result
このクラスの適切な名前を調べます。
determineName: ->
return null unless @variable
[..., tail] = @variable.properties
node = if tail
tail instanceof Access and tail.name
else
@variable.base
unless node instanceof IdentifierLiteral or node instanceof PropertyName
return null
name = node.value
unless tail
message = isUnassignable name
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
walkBody: (o) ->
@ctor = null
@boundMethods = []
executableBody = null
initializer = []
{ expressions } = @body
i = 0
for expression in expressions.slice()
if expression instanceof Value and expression.isObject true
{ properties } = expression.base
exprs = []
end = 0
start = 0
pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
while assign = properties[end]
if initializerExpression = @addInitializerExpression assign, o
pushSlice()
exprs.push initializerExpression
initializer.push initializerExpression
start = end + 1
end++
pushSlice()
expressions[i..i] = exprs
i += exprs.length
else
if initializerExpression = @addInitializerExpression expression, o
initializer.push initializerExpression
expressions[i] = initializerExpression
i += 1
for method in initializer when method instanceof Code
if method.ctor
method.error 'Cannot define more than one constructor in a class' if @ctor
@ctor = method
else if method.isStatic and method.bound
method.context = @name
else if method.bound
@boundMethods.push method
return unless o.compiling
if initializer.length isnt expressions.length
@body.expressions = (expression.hoist() for expression in initializer)
new Block expressions
クラスイニシャライザに式を追加します。
これは、クラス本体内の式をイニシャライザに出力するか、実行可能本体に出力するかを判断するための重要なメソッドです。与えられた node
がクラス本体で有効な場合、このメソッドはクラスイニシャライザに含めるための(新しい、変更された、または同一の)ノードを返します。そうでない場合、何も返されず、ノードは実行可能本体に出力されます。
執筆時点では、ES クラスイニシャライザではメソッド(インスタンスメソッドと静的メソッド)のみが有効です。クラスフィールドなどの新しい ES クラス機能が Stage 4 に達すると、このメソッドを更新してそれらをサポートする必要があります。さらに、実装されていない ES 機能(例:get
および set
キーワードで定義されたゲッターとセッターではなく、Object.defineProperty
メソッドで定義されたゲッターとセッター)のエスケープハッチとして、イニシャライザで PassthroughLiteral
(バックティックで囲まれた式)を許可しています。
addInitializerExpression: (node, o) ->
if node.unwrapAll() instanceof PassthroughLiteral
node
else if @validInitializerMethod node
@addInitializerMethod node
else if not o.compiling and @validClassProperty node
@addClassProperty node
else if not o.compiling and @validClassPrototypeProperty node
@addClassPrototypeProperty node
else
null
与えられたノードが有効な ES クラスイニシャライザメソッドかどうかを確認します。
validInitializerMethod: (node) ->
return no unless node instanceof Assign and node.value instanceof Code
return yes if node.context is 'object' and not node.variable.hasProperties()
return node.variable.looksStatic(@name) and (@name or not node.value.bound)
設定済みのクラスイニシャライザメソッドを返します。
addInitializerMethod: (assign) ->
{ variable, value: method, operatorToken } = assign
method.isMethod = yes
method.isStatic = variable.looksStatic @name
if method.isStatic
method.name = variable.properties[0]
else
methodName = variable.base
method.name = new (if methodName.shouldCache() then Index else Access) methodName
method.name.updateLocationDataIfMissing methodName.locationData
isConstructor =
if methodName instanceof StringLiteral
methodName.originalValue is 'constructor'
else
methodName.value is 'constructor'
method.ctor = (if @parent then 'derived' else 'base') if isConstructor
method.error 'Cannot define a constructor as a bound (fat arrow) function' if method.bound and method.ctor
method.operatorToken = operatorToken
method
validClassProperty: (node) ->
return no unless node instanceof Assign
return node.variable.looksStatic @name
addClassProperty: (assign) ->
{variable, value, operatorToken} = assign
{staticClassName} = variable.looksStatic @name
new ClassProperty({
name: variable.properties[0]
isStatic: yes
staticClassName
value
operatorToken
}).withLocationDataFrom assign
validClassPrototypeProperty: (node) ->
return no unless node instanceof Assign
node.context is 'object' and not node.variable.hasProperties()
addClassPrototypeProperty: (assign) ->
{variable, value} = assign
new ClassPrototypeProperty({
name: variable.base
value
}).withLocationDataFrom assign
makeDefaultConstructor: ->
ctor = @addInitializerMethod new Assign (new Value new PropertyName 'constructor'), new Code
@body.unshift ctor
if @parent
ctor.body.push new SuperCall new Super, [new Splat new IdentifierLiteral 'arguments']
if @externalCtor
applyCtor = new Value @externalCtor, [ new Access new PropertyName 'apply' ]
applyArgs = [ new ThisLiteral, new IdentifierLiteral 'arguments' ]
ctor.body.push new Call applyCtor, applyArgs
ctor.body.makeReturn()
ctor
proxyBoundMethods: ->
@ctor.thisAssignments = for method in @boundMethods
method.classVariable = @variableRef if @parent
name = new Value(new ThisLiteral, [ method.name ])
new Assign name, new Call(new Value(name, [new Access new PropertyName 'bind']), [new ThisLiteral])
null
declareName: (o) ->
return unless (name = @variable?.unwrap()) instanceof IdentifierLiteral
alreadyDeclared = o.scope.find name.value
name.isDeclaration = not alreadyDeclared
isStatementAst: -> yes
astNode: (o) ->
if jumpNode = @body.jumps()
jumpNode.error 'Class bodies cannot contain pure statements'
if argumentsNode = @body.contains isLiteralArguments
argumentsNode.error "Class bodies shouldn't reference arguments"
@declareName o
@name = @determineName()
@body.isClassBody = yes
@body.locationData = zeroWidthLocationDataFromEndLocation @locationData if @hasGeneratedBody
@walkBody o
sniffDirectives @body.expressions
@ctor?.noReturn = yes
super o
astType: (o) ->
if o.level is LEVEL_TOP
'ClassDeclaration'
else
'ClassExpression'
astProperties: (o) ->
return
id: @variable?.ast(o) ? null
superClass: @parent?.ast(o, LEVEL_PAREN) ? null
body: @body.ast o, LEVEL_TOP
exports.ExecutableClassBody = class ExecutableClassBody extends Base
children: [ 'class', 'body' ]
defaultClassVariableName: '_Class'
constructor: (@class, @body = new Block) ->
super()
compileNode: (o) ->
if jumpNode = @body.jumps()
jumpNode.error 'Class bodies cannot contain pure statements'
if argumentsNode = @body.contains isLiteralArguments
argumentsNode.error "Class bodies shouldn't reference arguments"
params = []
args = [new ThisLiteral]
wrapper = new Code params, @body
klass = new Parens new Call (new Value wrapper, [new Access new PropertyName 'call']), args
@body.spaced = true
o.classScope = wrapper.makeScope o.scope
@name = @class.name ? o.classScope.freeVariable @defaultClassVariableName
ident = new IdentifierLiteral @name
directives = @walkBody()
@setContext()
if @class.hasNameClash
parent = new IdentifierLiteral o.classScope.freeVariable 'superClass'
wrapper.params.push new Param parent
args.push @class.parent
@class.parent = parent
if @externalCtor
externalCtor = new IdentifierLiteral o.classScope.freeVariable 'ctor', reserve: no
@class.externalCtor = externalCtor
@externalCtor.variable.base = externalCtor
if @name isnt @class.name
@body.expressions.unshift new Assign (new IdentifierLiteral @name), @class
else
@body.expressions.unshift @class
@body.expressions.unshift directives...
@body.push ident
klass.compileToFragments o
クラスの子を走査し、
@properties
にホイスティングします。@properties
にホイスティングします。 walkBody: ->
directives = []
index = 0
while expr = @body.expressions[index]
break unless expr instanceof Value and expr.isString()
if expr.hoisted
index++
else
directives.push @body.expressions.splice(index, 1)...
@traverseChildren false, (child) =>
return false if child instanceof Class or child instanceof HoistTarget
cont = true
if child instanceof Block
for node, i in child.expressions
if node instanceof Value and node.isObject(true)
cont = false
child.expressions[i] = @addProperties node.base.properties
else if node instanceof Assign and node.variable.looksStatic @name
node.value.isStatic = yes
child.expressions = flatten child.expressions
cont
directives
setContext: ->
@body.traverseChildren false, (node) =>
if node instanceof ThisLiteral
node.value = @name
else if node instanceof Code and node.bound and (node.isStatic or not node.name)
node.context = @name
無効な ES プロパティに対するクラス/プロトタイプの代入を作成します。
addProperties: (assigns) ->
result = for assign in assigns
variable = assign.variable
base = variable?.base
value = assign.value
delete assign.context
if base.value is 'constructor'
if value instanceof Code
base.error 'constructors must be defined at the top level of a class body'
クラススコープはまだ使用できないため、後で更新するための代入を返します。
assign = @externalCtor = new Assign new Value, value
else if not assign.variable.this
name =
if base instanceof ComputedPropertyName
new Index base.value
else
new (if base.shouldCache() then Index else Access) base
prototype = new Access new PropertyName 'prototype'
variable = new Value new ThisLiteral(), [ prototype, name ]
assign.variable = variable
else if assign.value instanceof Code
assign.value.isStatic = true
assign
compact result
exports.ClassProperty = class ClassProperty extends Base
constructor: ({@name, @isStatic, @staticClassName, @value, @operatorToken}) ->
super()
children: ['name', 'value', 'staticClassName']
isStatement: YES
astProperties: (o) ->
return
key: @name.ast o, LEVEL_LIST
value: @value.ast o, LEVEL_LIST
static: !!@isStatic
computed: @name instanceof Index or @name instanceof ComputedPropertyName
operator: @operatorToken?.value ? '='
staticClassName: @staticClassName?.ast(o) ? null
exports.ClassPrototypeProperty = class ClassPrototypeProperty extends Base
constructor: ({@name, @value}) ->
super()
children: ['name', 'value']
isStatement: YES
astProperties: (o) ->
return
key: @name.ast o, LEVEL_LIST
value: @value.ast o, LEVEL_LIST
computed: @name instanceof ComputedPropertyName or @name instanceof StringWithInterpolations
exports.ModuleDeclaration = class ModuleDeclaration extends Base
constructor: (@clause, @source, @assertions) ->
super()
@checkSource()
children: ['clause', 'source', 'assertions']
isStatement: YES
jumps: THIS
makeReturn: THIS
checkSource: ->
if @source? and @source instanceof StringWithInterpolations
@source.error 'the name of the module to be imported from must be an uninterpolated string'
checkScope: (o, moduleDeclarationType) ->
TODO: AST 生成時(および JS へのコンパイル時)にこのエラーをフラグ付けするのが適切です。しかし、o.indent
は AST 生成時には追跡されず、現在、「プログラムの最上位レベル」にいるかどうかを追跡する代替方法がないようです。
if o.indent.length isnt 0
@error "#{moduleDeclarationType} statements must be at top-level scope"
astAssertions: (o) ->
if @assertions?.properties?
@assertions.properties.map (assertion) =>
{ start, end, loc, left, right } = assertion.ast(o)
{ type: 'ImportAttribute', start, end, loc, key: left, value: right }
else
[]
exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'import'
o.importedSymbols = []
code = []
code.push @makeCode "#{@tab}import "
code.push @clause.compileNode(o)... if @clause?
if @source?.value?
code.push @makeCode ' from ' unless @clause is null
code.push @makeCode @source.value
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
astNode: (o) ->
o.importedSymbols = []
super o
astProperties: (o) ->
ret =
specifiers: @clause?.ast(o) ? []
source: @source.ast o
assertions: @astAssertions(o)
ret.importKind = 'value' if @clause
ret
exports.ImportClause = class ImportClause extends Base
constructor: (@defaultBinding, @namedImports) ->
super()
children: ['defaultBinding', 'namedImports']
compileNode: (o) ->
code = []
if @defaultBinding?
code.push @defaultBinding.compileNode(o)...
code.push @makeCode ', ' if @namedImports?
if @namedImports?
code.push @namedImports.compileNode(o)...
code
astNode: (o) ->
ImportClause
の AST は、ImportDeclaration
AST の specifiers
プロパティになるインポート指定子のネストされていないリストです。
compact flatten [
@defaultBinding?.ast o
@namedImports?.ast o
]
exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'export'
@checkForAnonymousClassExport()
code = []
code.push @makeCode "#{@tab}export "
code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration
if @ not instanceof ExportDefaultDeclaration and
(@clause instanceof Assign or @clause instanceof Class)
code.push @makeCode 'var '
@clause.moduleDeclaration = 'export'
if @clause.body? and @clause.body instanceof Block
code = code.concat @clause.compileToFragments o, LEVEL_TOP
else
code = code.concat @clause.compileNode o
if @source?.value?
code.push @makeCode " from #{@source.value}"
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
匿名クラスのエクスポートを防止します。エクスポートされるメンバーはすべて名前付きである必要があります。
checkForAnonymousClassExport: ->
if @ not instanceof ExportDefaultDeclaration and @clause instanceof Class and not @clause.variable
@clause.error 'anonymous classes cannot be exported'
astNode: (o) ->
@checkForAnonymousClassExport()
super o
exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
astProperties: (o) ->
ret =
source: @source?.ast(o) ? null
assertions: @astAssertions(o)
exportKind: 'value'
clauseAst = @clause.ast o
if @clause instanceof ExportSpecifierList
ret.specifiers = clauseAst
ret.declaration = null
else
ret.specifiers = []
ret.declaration = clauseAst
ret
exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
astProperties: (o) ->
return
declaration: @clause.ast o
assertions: @astAssertions(o)
exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
astProperties: (o) ->
return
source: @source.ast o
assertions: @astAssertions(o)
exportKind: 'value'
exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
constructor: (@specifiers) ->
super()
children: ['specifiers']
compileNode: (o) ->
code = []
o.indent += TAB
compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)
if @specifiers.length isnt 0
code.push @makeCode "{\n#{o.indent}"
for fragments, index in compiledList
code.push @makeCode(",\n#{o.indent}") if index
code.push fragments...
code.push @makeCode "\n}"
else
code.push @makeCode '{}'
code
astNode: (o) ->
specifier.ast(o) for specifier in @specifiers
exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
exports.ModuleSpecifier = class ModuleSpecifier extends Base
constructor: (@original, @alias, @moduleDeclarationType) ->
super()
if @original.comments or @alias?.comments
@comments = []
@comments.push @original.comments... if @original.comments
@comments.push @alias.comments... if @alias?.comments
ローカルスコープに入る変数の名前です。
@identifier = if @alias? then @alias.value else @original.value
children: ['original', 'alias']
compileNode: (o) ->
@addIdentifierToScope o
code = []
code.push @makeCode @original.value
code.push @makeCode " as #{@alias.value}" if @alias?
code
addIdentifierToScope: (o) ->
o.scope.find @identifier, @moduleDeclarationType
astNode: (o) ->
@addIdentifierToScope o
super o
exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
constructor: (imported, local) ->
super imported, local, 'import'
addIdentifierToScope: (o) ->
仕様によると、シンボルを複数回インポートすることはできません(例:import { foo, foo } from 'lib'
は無効です)。
if @identifier in o.importedSymbols or o.scope.check(@identifier)
@error "'#{@identifier}' has already been declared"
else
o.importedSymbols.push @identifier
super o
astProperties: (o) ->
originalAst = @original.ast o
return
imported: originalAst
local: @alias?.ast(o) ? originalAst
importKind: null
exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
astProperties: (o) ->
return
local: @original.ast o
exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
astProperties: (o) ->
return
local: @alias.ast o
exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
constructor: (local, exported) ->
super local, exported, 'export'
astProperties: (o) ->
originalAst = @original.ast o
return
local: originalAst
exported: @alias?.ast(o) ? originalAst
exports.DynamicImport = class DynamicImport extends Base
compileNode: ->
[@makeCode 'import']
astType: -> 'Import'
exports.DynamicImportCall = class DynamicImportCall extends Call
compileNode: (o) ->
@checkArguments()
super o
checkArguments: ->
unless 1 <= @args.length <= 2
@error 'import() accepts either one or two arguments'
astNode: (o) ->
@checkArguments()
super o
**Assign** は、ローカル変数に値を代入したり、オブジェクトのプロパティを設定するために使用されます(オブジェクトリテラル内も含む)。
exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) ->
super()
{@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
@propagateLhs()
children: ['variable', 'value']
isAssignable: YES
isStatement: (o) ->
o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
checkNameAssignability: (o, varBase) ->
if o.scope.type(varBase.value) is 'import'
varBase.error "'#{varBase.value}' is read-only"
assigns: (name) ->
@[if @context is 'object' then 'value' else 'variable'].assigns name
unfoldSoak: (o) ->
unfoldSoak o, this, 'variable'
addScopeVariables: (o, {
AST 生成時には、JS へのコンパイル時に「代入不可能」と見なされるこれらの構成要素への代入を許可する一方で、[null] = b
のようなものはフラグ付けする必要があります。
allowAssignmentToExpansion = no,
allowAssignmentToNontrailingSplat = no,
allowAssignmentToEmptyArray = no,
allowAssignmentToComplexSplat = no
} = {}) ->
return unless not @context or @context is '**='
varBase = @variable.unwrapAll()
if not varBase.isAssignable {
allowExpansion: allowAssignmentToExpansion
allowNontrailingSplat: allowAssignmentToNontrailingSplat
allowEmptyArray: allowAssignmentToEmptyArray
allowComplexSplat: allowAssignmentToComplexSplat
}
@variable.error "'#{@variable.compile o}' can't be assigned"
varBase.eachName (name) =>
return if name.hasProperties?()
message = isUnassignable name.value
name.error message if message
moduleDeclaration
は 'import'
または 'export'
です。
@checkNameAssignability o, name
if @moduleDeclaration
o.scope.add name.value, @moduleDeclaration
name.isDeclaration = yes
else if @param
o.scope.add name.value,
if @param is 'alwaysDeclare'
'var'
else
'param'
else
alreadyDeclared = o.scope.find name.value
name.isDeclaration ?= not alreadyDeclared
この代入識別子に1つ以上の herecomments が付いている場合、Flow 型付けとの互換性のために、宣言行の一部として出力します(他の herecomments が既にそこにステージングされている場合を除く)。この代入がクラス用である場合(例:ClassName = class ClassName {
)、これを行わないでください。Flow はコメントがクラス名と {
の間にくることを要求します。
if name.comments and not o.scope.comments[name.value] and
@value not instanceof Class and
name.comments.every((comment) -> comment.here and not comment.multiline)
commentsNode = new IdentifierLiteral name.value
commentsNode.comments = name.comments
commentFragments = []
@compileCommentFragments o, commentsNode, commentFragments
o.scope.comments[name.value] = commentFragments
適切な場合は compileDestructuring
または compileSplice
に委任して代入をコンパイルします。正しい内部参照のために、代入された基本オブジェクトの名前を追跡します。現在のスコープ内で変数がまだ認識されていない場合は、宣言します。
compileNode: (o) ->
isValue = @variable instanceof Value
if isValue
@variable
が配列またはオブジェクトの場合、デストラクチャリングです。また、isAssignable()
である場合、デストラクチャリング構文は ES でサポートされており、そのまま出力できます。そうでない場合は、@compileDestructuring
を使用して、この ES でサポートされていないデストラクチャリングを許容される出力に変換します。
if @variable.isArray() or @variable.isObject()
unless @variable.isAssignable()
if @variable.isObject() and @variable.base.hasSplat()
return @compileObjectDestruct o
else
return @compileDestructuring o
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @isConditional()
return @compileSpecialMath o if @context in ['//=', '%%=']
@addScopeVariables o
if @value instanceof Code
if @value.isStatic
@value.name = @variable.properties[0]
else if @variable.properties?.length >= 2
[properties..., prototype, name] = @variable.properties
@value.name = name if prototype.name?.value is 'prototype'
val = @value.compileToFragments o, LEVEL_LIST
compiledName = @variable.compileToFragments o, LEVEL_LIST
if @context is 'object'
if @variable.shouldCache()
compiledName.unshift @makeCode '['
compiledName.push @makeCode ']'
return compiledName.concat @makeCode(': '), val
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assignment_without_declaration によると、宣言せずにデストラクチャリングする場合は、デストラクチャリング代入を括弧で囲む必要があります。代入は、'o.level' の優先順位が LEVEL_LIST(3)よりも低い場合(つまり、LEVEL_COND(4)、LEVEL_OP(5)、または LEVEL_ACCESS(6))、またはオブジェクトをデストラクチャリングする場合(例:{a,b} = obj)に括弧で囲まれます。
if o.level > LEVEL_LIST or isValue and @variable.base instanceof Obj and not @nestedLhs and not (@param is yes)
@wrapInParentheses answer
else
answer
オブジェクト rest プロパティは代入できません:{{a}...}
compileObjectDestruct: (o) ->
@variable.base.reorderProperties()
{properties: props} = @variable.base
[..., splat] = props
splatProp = splat.name
assigns = []
refVal = new Value new IdentifierLiteral o.scope.freeVariable 'ref'
props.splice -1, 1, new Splat refVal
assigns.push new Assign(new Value(new Obj props), @value).compileToFragments o, LEVEL_LIST
assigns.push new Assign(new Value(splatProp), refVal).compileToFragments o, LEVEL_LIST
@joinFragmentArrays assigns, ', '
配列またはオブジェクトリテラルを値に代入する場合の再帰的パターンマッチングの簡単な実装です。内部名を代入するために、そのプロパティを調べます。
compileDestructuring: (o) ->
top = o.level is LEVEL_TOP
{value} = this
{objects} = @variable.base
olen = objects.length
{} = a
と [] = a
(空のパターン)の特殊ケースです。単に a
にコンパイルします。
if olen is 0
code = value.compileToFragments o
return if o.level >= LEVEL_OP then @wrapInParentheses code else code
[obj] = objects
@disallowLoneExpansion()
{splats, expans, splatsAndExpans} = @getAndCheckSplatsAndExpansions()
isSplat = splats?.length > 0
isExpans = expans?.length > 0
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
assigns = []
pushAssign = (variable, val) =>
assigns.push new Assign(variable, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
if isSplat
splatVar = objects[splats[0]].name.unwrap()
if splatVar instanceof Arr or splatVar instanceof Obj
splatVarRef = new IdentifierLiteral o.scope.freeVariable 'ref'
objects[splats[0]].name = splatVarRef
splatVarAssign = -> pushAssign new Value(splatVar), splatVarRef
この時点で、デストラクチャリングするものがいくつかあります。そのため、{a, b} = fn()
の fn()
は、例えばキャッシュする必要があります。vvar がまだ単純な変数でない場合は、単純な変数にします。
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
ref = o.scope.freeVariable 'ref'
assigns.push [@makeCode(ref + ' = '), vvar...]
vvar = [@makeCode ref]
vvarText = ref
slicer = (type) -> (vvar, start, end = no) ->
vvar = new IdentifierLiteral vvar unless vvar instanceof Value
args = [vvar, new NumberLiteral(start)]
args.push new NumberLiteral end if end
slice = new Value (new IdentifierLiteral utility type, o), [new Access new PropertyName 'call']
new Value new Call slice, args
[].slice
コードを出力するヘルパーです。
compSlice = slicer "slice"
[].splice
コードを出力するヘルパーです。
compSplice = slicer "splice"
objects
配列に Assign
のインスタンスが含まれているかどうかを確認します(例:{a:1})。
hasObjAssigns = (objs) ->
(i for obj, i in objs when obj instanceof Assign and obj.context is 'object')
objects
配列に代入不可能なオブジェクトが含まれているかどうかを確認します。
objIsUnassignable = (objs) ->
return yes for obj in objs when not obj.isAssignable()
no
オブジェクト代入({a:1})、代入不可能なオブジェクト、または単一ノードがある場合、objects
は複雑です。
complexObjects = (objs) ->
hasObjAssigns(objs).length or objIsUnassignable(objs) or olen is 1
「複雑な」オブジェクト
はループ処理されます。例: [a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
loopObjects = (objs, vvar, vvarTxt) =>
for obj, i in objs
省略
はスキップできます。
continue if obj instanceof Elision
obj
が{a: 1}の場合
if obj instanceof Assign and obj.context is 'object'
{variable: {base: idx}, value: vvar} = obj
{variable: vvar} = vvar if vvar instanceof Assign
idx =
if vvar.this
vvar.properties[0].name
else
new PropertyName vvar.unwrap().value
acc = idx.unwrap() instanceof PropertyName
vval = new Value value, [new (if acc then Access else Index) idx]
else
obj
は[a…], {a…}またはaです
vvar = switch
when obj instanceof Splat then new Value obj.name
else obj
vval = switch
when obj instanceof Splat then compSlice(vvarTxt, i)
else new Value new Literal(vvarTxt), [new Index new NumberLiteral i]
message = isUnassignable vvar.unwrap().value
vvar.error message if message
pushAssign vvar, vval
「単純な」オブジェクト
は分割して配列にコンパイルできます。[a, b, c] = arr, [a, b, c…] = arr
assignObjects = (objs, vvar, vvarTxt) =>
vvar = new Value new Arr(objs, yes)
vval = if vvarTxt instanceof Value then vvarTxt else new Value new Literal(vvarTxt)
pushAssign vvar, vval
processObjects = (objs, vvar, vvarTxt) ->
if complexObjects objs
loopObjects objs, vvar, vvarTxt
else
assignObjects objs, vvar, vvarTxt
オブジェクト
にSplat
またはExpansion
がある場合、配列を2つの単純なサブ配列に分割できます。Splat
[a, b, c…, d, e]は[a, b, c…]と[d, e]に分割できます。Expansion
[a, b, …, c, d]は[a, b]と[c, d]に分割できます。例: a) Splat
CS: [a, b, c…, d, e] = arr JS: [a, b, …c] = arr, [d, e] = splice.call(c, -2) b) Expansion
CS: [a, b, …, d, e] = arr JS: [a, b] = arr, [d, e] = slice.call(arr, -2)
if splatsAndExpans.length
expIdx = splatsAndExpans[0]
leftObjs = objects.slice 0, expIdx + (if isSplat then 1 else 0)
rightObjs = objects.slice expIdx + 1
processObjects leftObjs, vvar, vvarText if leftObjs.length isnt 0
if rightObjs.length isnt 0
オブジェクト
のスライスまたはスプライス。
refExp = switch
when isSplat then compSplice new Value(objects[expIdx].name), rightObjs.length * -1
when isExpans then compSlice vvarText, rightObjs.length * -1
if complexObjects rightObjs
restVar = refExp
refExp = o.scope.freeVariable 'ref'
assigns.push [@makeCode(refExp + ' = '), restVar.compileToFragments(o, LEVEL_LIST)...]
processObjects rightObjs, vvar, refExp
else
オブジェクト
にSplat
またはExpansion
はありません。
processObjects objects, vvar, vvarText
splatVarAssign?()
assigns.push vvar unless top or @subpattern
fragments = @joinFragmentArrays assigns, ', '
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
何らかの理由で[...] = a
を許可しません。([] = a
と同等ですか?)
disallowLoneExpansion: ->
return unless @variable.base instanceof Arr
{objects} = @variable.base
return unless objects?.length is 1
[loneObject] = objects
if loneObject instanceof Expansion
loneObject.error 'Destructuring assignment has no target'
Splat
またはExpansion
が複数ある場合、エラーを表示します。例: [a, b, c…, d, e, f…], [a, b, …, c, d, …], [a, b, …, c, d, e…]
getAndCheckSplatsAndExpansions: ->
return {splats: [], expans: [], splatsAndExpans: []} unless @variable.base instanceof Arr
{objects} = @variable.base
すべてのSplat
をカウントします: [a, b, c…, d, e]
splats = (i for obj, i in objects when obj instanceof Splat)
すべてのExpansion
をカウントします: [a, b, …, c, d]
expans = (i for obj, i in objects when obj instanceof Expansion)
splatとexpansionを組み合わせる。
splatsAndExpans = [splats..., expans...]
if splatsAndExpans.length > 1
最初の許可されていないトークンでエラーを表示できるように、「splatsAndExpans」をソートします。
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
{splats, expans, splatsAndExpans}
条件付き代入をコンパイルする際には、オペランドを複数回参照する必要がある場合でも、オペランドが一度だけ評価されるように注意してください。
compileConditional: (o) ->
[left, right] = @variable.cacheReference o
未定義変数の条件付き代入を許可しません。
if not left.properties.length and left.base instanceof Literal and
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
@throwUnassignableConditionalError left.base.value
if "?" in @context
o.isExistentialEquals = true
new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
else
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments
a //= b
のような特殊な数学的代入演算子を、同等の拡張形式a = a ** b
に変換してからコンパイルします。
compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o
new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
JavaScriptのArray#splice
メソッドを使用して、配列スプライスリテラルから代入をコンパイルします。
compileSplice: (o) ->
{range: {from, to, exclusive}} = @variable.properties.pop()
unwrappedVar = @variable.unwrapAll()
if unwrappedVar.comments
moveComments unwrappedVar, @
delete @variable.comments
name = @variable.compile o
if from
[fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
else
fromDecl = fromRef = '0'
if to
if from?.isNumber() and to.isNumber()
to = to.compile(o) - fromRef
to += 1 unless exclusive
else
to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
to += ' + 1' unless exclusive
else
to = "9e9"
[valDef, valRef] = @value.cache o, LEVEL_LIST
answer = [].concat @makeCode("#{utility 'splice', o}.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
if o.level > LEVEL_TOP then @wrapInParentheses answer else answer
eachName: (iterator) ->
@variable.unwrapAll().eachName iterator
isDefaultAssignment: -> @param or @nestedLhs
propagateLhs: ->
return unless @variable?.isArray?() or @variable?.isObject?()
これは代入の左辺です。Arr
とObj
にそれを知らせ、それらのノードが非構造化変数として代入可能であることを認識させます。
@variable.base.propagateLhs yes
throwUnassignableConditionalError: (name) ->
@variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"
isConditional: ->
@context in ['||=', '&&=', '?=']
isStatementAst: NO
astNode: (o) ->
@disallowLoneExpansion()
@getAndCheckSplatsAndExpansions()
if @isConditional()
variable = @variable.unwrap()
if variable instanceof IdentifierLiteral and not o.scope.check variable.value
@throwUnassignableConditionalError variable.value
@addScopeVariables o, allowAssignmentToExpansion: yes, allowAssignmentToNontrailingSplat: yes, allowAssignmentToEmptyArray: yes, allowAssignmentToComplexSplat: yes
super o
astType: ->
if @isDefaultAssignment()
'AssignmentPattern'
else
'AssignmentExpression'
astProperties: (o) ->
ret =
right: @value.ast o, LEVEL_LIST
left: @variable.ast o, LEVEL_LIST
unless @isDefaultAssignment()
ret.operator = @originalContext ? '='
ret
exports.FuncGlyph = class FuncGlyph extends Base
constructor: (@glyph) ->
super()
関数定義。これは新しいスコープを作成する唯一のノードです。関数の本体の内容を辿る目的では、Codeには子がありません。それらは内部スコープ内にあります。
exports.Code = class Code extends Base
constructor: (params, body, @funcGlyph, @paramStart) ->
super()
@params = params or []
@body = body or new Block
@bound = @funcGlyph?.glyph is '=>'
@isGenerator = no
@isAsync = no
@isMethod = no
@body.traverseChildren no, (node) =>
if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
@isGenerator = yes
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
@isAsync = yes
if node instanceof For and node.isAwait()
@isAsync = yes
@propagateLhs()
children: ['params', 'body']
isStatement: -> @isMethod
jumps: NO
makeScope: (parentScope) -> new Scope parentScope, @body, this
明示的に外部スコープと共有するように指示されない限り、コンパイルは新しいスコープを作成します。ES2015仕様に従って、パラメータリスト内のsplatパラメータを関数の定義の最後のパラメータに設定することで処理します。CoffeeScriptの関数定義にsplatの後にパラメータがあった場合、それらは関数の本体内の式によって宣言されます。
compileNode: (o) ->
@checkForAsyncOrGeneratorConstructor()
if @bound
@context = o.scope.method.context if o.scope.method?.bound
@context = 'this' unless @context
@updateOptions o
params = []
exprs = []
thisAssignments = @thisAssignments?.slice() ? []
paramsAfterSplat = []
haveSplatParam = no
haveBodyParam = no
@checkForDuplicateParams()
@disallowLoneExpansionAndMultipleSplats()
this
の代入を分離します。
@eachParamName (name, node, param, obj) ->
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
target = new IdentifierLiteral o.scope.freeVariable name, reserve: no
Param
はデフォルト値を持つオブジェクトの非構造化です: ({@prop = 1}) -> 変数名が既に予約されている場合、非構造化変数に新しい変数名を割り当てる必要があります: ({prop:prop1 = 1}) ->
replacement =
if param.name instanceof Obj and obj instanceof Assign and
obj.operatorToken.value is '='
new Assign (new IdentifierLiteral name), target, 'object' #, operatorToken: new Literal ':'
else
target
param.renameParam node, replacement
thisAssignments.push new Assign node, target
パラメータを解析し、関数定義に追加するパラメータのリストに追加します。splatやexpansionを処理し、splat/expansionパラメータの後にくるすべてのパラメータ変数を宣言する式を関数本体に追加します。何らかの理由で関数本体で宣言する必要があるパラメータ(例えば、this
で非構造化されている場合)に遭遇した場合、非べき等なパラメータが正しい順序で評価されるように、後続のパラメータも関数本体で宣言して代入します。
for param, i in @params
このパラメータで...
は使用されましたか?Splat/expansionパラメータにはデフォルト値を設定できないため、心配する必要はありません。
if param.splat or param instanceof Expansion
haveSplatParam = yes
if param.splat
if param.name instanceof Arr or param.name instanceof Obj
Splat配列はESによって異常に扱われます。関数本体で従来の方法で処理します。TODO: これは関数パラメータリストで処理する必要がありますか?もしそうなら、どのように?
splatParamName = o.scope.freeVariable 'arg'
params.push ref = new Value new IdentifierLiteral splatParamName
exprs.push new Assign new Value(param.name), ref
else
params.push ref = param.asReference o
splatParamName = fragmentsToText ref.compileNodeWithoutComments o
if param.shouldCache()
exprs.push new Assign new Value(param.name), ref
else # `param` is an Expansion
splatParamName = o.scope.freeVariable 'args'
params.push new Value new IdentifierLiteral splatParamName
o.scope.parameter splatParamName
その他のパラメータを解析します。まだsplatパラメータに遭遇していない場合は、これらのパラメータを関数定義に出力するリストに追加します。
else
if param.shouldCache() or haveBodyParam
param.assignedInBody = yes
haveBodyParam = yes
このパラメータはパラメータリストで宣言または代入できません。そのため、パラメータリストに参照を追加し、それを代入するステートメントを関数本体に追加します。例: (arg) => { var a = arg.a; }
、デフォルト値がある場合はデフォルト値も追加します。
if param.value?
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
else
exprs.push new Assign new Value(param.name), param.asReference(o), null, param: 'alwaysDeclare'
このパラメータがsplatまたはexpansionの前にある場合、関数定義のパラメータリストに追加されます。
unless haveSplatParam
このパラメータにデフォルト値があり、上記のshouldCache()
ブロックによってまだ設定されていない場合、関数本体のステートメントとして定義します。このパラメータはsplatパラメータの後にあるため、パラメータリストにデフォルト値を定義することはできません。
if param.shouldCache()
ref = param.asReference o
else
if param.value? and not param.assignedInBody
ref = new Assign new Value(param.name), param.value, null, param: yes
else
ref = param
このパラメータの参照を関数スコープに追加します。
if param.name instanceof Arr or param.name instanceof Obj
このパラメータは非構造化されています。
param.name.lhs = yes
unless param.shouldCache()
param.name.eachName (prop) ->
o.scope.parameter prop.value
else
このパラメータのコンパイルは、スコープ名トラッキングに追加する名前を取得するためだけです。ここのコンパイル出力は最終的な出力のために保持されないため、コメントを含めないでください。そうすれば、このparamが実際にコンパイルされる際にコメントが出力されます。
paramToAddToScope = if param.value? then param else ref
o.scope.parameter fragmentsToText paramToAddToScope.compileToFragmentsWithoutComments o
params.push ref
else
paramsAfterSplat.push param
このパラメータにデフォルト値があった場合、もはや関数パラメータリストにないため、必要に応じて本体の式としてデフォルト値を代入する必要があります。
if param.value? and not param.shouldCache()
condition = new Op '===', param, new UndefinedLiteral
ifTrue = new Assign new Value(param.name), param.value
exprs.push new If condition, ifTrue
このパラメータは、前にスキップされたためまだ追加されていないため、スコープに追加します。
o.scope.add param.name.value, 'var', yes if param.name?.value?
splatまたはexpansionパラメータの後にパラメータがあった場合、それらのパラメータは関数の本体で代入する必要があります。
if paramsAfterSplat.length isnt 0
非構造化代入を作成します。例: [a, b, c] = [args..., b, c]
exprs.unshift new Assign new Value(
new Arr [new Splat(new IdentifierLiteral(splatParamName)), (param.asReference o for param in paramsAfterSplat)...]
), new Value new IdentifierLiteral splatParamName
関数本体に新しい式を追加します
wasEmpty = @body.isEmpty()
@disallowSuperInParamDefaults()
@checkSuperCallsInConstructorBody()
@body.expressions.unshift thisAssignments... unless @expandCtorSuper thisAssignments
@body.expressions.unshift exprs...
if @isMethod and @bound and not @isStatic and @classVariable
boundMethodCheck = new Value new Literal utility 'boundMethodCheck', o
@body.expressions.unshift new Call(boundMethodCheck, [new Value(new ThisLiteral), @classVariable])
@body.makeReturn() unless wasEmpty or @noReturn
JavaScriptでは、バインドされた(=>
)関数をジェネレータにすることはできません。これは通常Op::compileContinuation
でキャッチされますが、念のため再確認します。
if @bound and @isGenerator
yieldNode = @body.contains (node) -> node instanceof Op and node.operator is 'yield'
(yieldNode or @).error 'yield cannot occur inside bound (fat arrow) functions'
出力の組み立て
modifiers = []
modifiers.push 'static' if @isMethod and @isStatic
modifiers.push 'async' if @isAsync
unless @isMethod or @bound
modifiers.push "function#{if @isGenerator then '*' else ''}"
else if @isGenerator
modifiers.push '*'
signature = [@makeCode '(']
関数名と(
の間のブロックコメントは、function
と(
の間に出力されます。
if @paramStart?.comments?
@compileCommentFragments o, @paramStart, signature
for param, i in params
signature.push @makeCode ', ' if i isnt 0
signature.push @makeCode '...' if haveSplatParam and i is params.length - 1
このパラメータをコンパイルしますが、生成された変数(例:ref
)が作成された場合は、関数パラメータリスト内にvar
行を入れることができないため、それらを親スコープにシフトします。
scopeVariablesCount = o.scope.variables.length
signature.push param.compileToFragments(o, LEVEL_PAREN)...
if scopeVariablesCount isnt o.scope.variables.length
generatedVariables = o.scope.variables.splice scopeVariablesCount
o.scope.parent.variables.push generatedVariables...
signature.push @makeCode ')'
)
と->
/=>
の間のブロックコメントは、)
と{
の間に出力されます。
if @funcGlyph?.comments?
comment.unshift = no for comment in @funcGlyph.comments
@compileCommentFragments o, @funcGlyph, signature
body = @body.compileWithDeclarations o unless @body.isEmpty()
super
参照が処理されるように、メソッド名より前に本体をコンパイルする必要があります。
if @isMethod
[methodScope, o.scope] = [o.scope, o.scope.parent]
name = @name.compileToFragments o
name.shift() if name[0].code is '.'
o.scope = methodScope
answer = @joinFragmentArrays (@makeCode m for m in modifiers), ' '
answer.push @makeCode ' ' if modifiers.length and name
answer.push name... if name
answer.push signature...
answer.push @makeCode ' =>' if @bound and not @isMethod
answer.push @makeCode ' {'
answer.push @makeCode('\n'), body..., @makeCode("\n#{@tab}") if body?.length
answer.push @makeCode '}'
return indentInitial answer, @ if @isMethod
if @front or (o.level >= LEVEL_ACCESS) then @wrapInParentheses answer else answer
updateOptions: (o) ->
o.scope = del(o, 'classScope') or @makeScope o.scope
o.scope.shared = del(o, 'sharedScope')
o.indent += TAB
delete o.bare
delete o.isExistentialEquals
checkForDuplicateParams: ->
paramNames = []
@eachParamName (name, node, param) ->
node.error "multiple parameters named '#{name}'" if name in paramNames
paramNames.push name
eachParamName: (iterator) ->
param.eachName iterator for param in @params
crossScope
がtrue
でない限り、スコープ境界を超えないようにtraverseChildren
メソッドをショートサーキットします。
traverseChildren: (crossScope, func) ->
super(crossScope, func) if crossScope
コンテキスト境界を超えないようにreplaceInContext
メソッドをショートサーキットします。バインドされた関数は同じコンテキストを持ちます。
replaceInContext: (child, replacement) ->
if @bound
super child, replacement
else
false
disallowSuperInParamDefaults: ({forAst} = {}) ->
return false unless @ctor
@eachSuperCall Block.wrap(@params), (superCall) ->
superCall.error "'super' is not allowed in constructor parameter defaults"
, checkForThisBeforeSuper: not forAst
checkSuperCallsInConstructorBody: ->
return false unless @ctor
seenSuper = @eachSuperCall @body, (superCall) =>
superCall.error "'super' is only allowed in derived class constructors" if @ctor is 'base'
seenSuper
flagThisParamInDerivedClassConstructorWithoutCallingSuper: (param) ->
param.error "Can't use @params in derived class constructors without calling super"
checkForAsyncOrGeneratorConstructor: ->
if @ctor
@name.error 'Class constructor may not be async' if @isAsync
@name.error 'Class constructor may not be a generator' if @isGenerator
disallowLoneExpansionAndMultipleSplats: ->
seenSplatParam = no
for param in @params
このパラメータで...
は使用されましたか?(関数ごとにそのようなパラメータは1つだけです。)
if param.splat or param instanceof Expansion
if seenSplatParam
param.error 'only one splat or expansion parameter is allowed per function definition'
else if param instanceof Expansion and @params.length is 1
param.error 'an expansion parameter cannot be the only parameter in a function definition'
seenSplatParam = yes
expandCtorSuper: (thisAssignments) ->
return false unless @ctor
seenSuper = @eachSuperCall @body, (superCall) =>
superCall.expressions = thisAssignments
haveThisParam = thisAssignments.length and thisAssignments.length isnt @thisAssignments?.length
if @ctor is 'derived' and not seenSuper and haveThisParam
param = thisAssignments[0].variable
@flagThisParamInDerivedClassConstructorWithoutCallingSuper param
seenSuper
指定されたコンテキストノード内のすべてのsuper呼び出しを見つけます。iterator
が呼び出された場合はtrue
を返します。
eachSuperCall: (context, iterator, {checkForThisBeforeSuper = yes} = {}) ->
seenSuper = no
context.traverseChildren yes, (child) =>
if child instanceof SuperCall
コンストラクタ内のsuper
(アクセッサのない唯一のsuper
)には、this
への参照を含む引数を渡すことができません。これは、super
を呼び出す前にthis
を参照することになるためです。
unless child.variable.accessor
childArgs = child.args.filter (arg) ->
arg not instanceof Class and (arg not instanceof Code or arg.bound)
Block.wrap(childArgs).traverseChildren yes, (node) =>
node.error "Can't call super with @params in derived class constructors" if node.this
seenSuper = yes
iterator child
else if checkForThisBeforeSuper and child instanceof ThisLiteral and @ctor is 'derived' and not seenSuper
child.error "Can't reference 'this' before calling super in derived class constructors"
super
はバインドされた(アロー)関数でも同じターゲットを持つため、それらもチェックします。
child not instanceof SuperCall and (child not instanceof Code or child.bound)
seenSuper
propagateLhs: ->
for param in @params
{name} = param
if name instanceof Arr or name instanceof Obj
name.propagateLhs yes
else if param instanceof Expansion
param.lhs = yes
astAddParamsToScope: (o) ->
@eachParamName (name) ->
o.scope.add name, 'param'
astNode: (o) ->
@updateOptions o
@checkForAsyncOrGeneratorConstructor()
@checkForDuplicateParams()
@disallowSuperInParamDefaults forAst: yes
@disallowLoneExpansionAndMultipleSplats()
seenSuper = @checkSuperCallsInConstructorBody()
if @ctor is 'derived' and not seenSuper
@eachParamName (name, node) =>
if node.this
@flagThisParamInDerivedClassConstructorWithoutCallingSuper node
@astAddParamsToScope o
@body.makeReturn null, yes unless @body.isEmpty() or @noReturn
super o
astType: ->
if @isMethod
'ClassMethod'
else if @bound
'ArrowFunctionExpression'
else
'FunctionExpression'
paramForAst: (param) ->
return param if param instanceof Expansion
{name, value, splat} = param
if splat
new Splat name, lhs: yes, postfix: splat.postfix
.withLocationDataFrom param
else if value?
new Assign name, value, null, param: yes
.withLocationDataFrom locationData: mergeLocationData name.locationData, value.locationData
else
name
methodAstProperties: (o) ->
getIsComputed = =>
return yes if @name instanceof Index
return yes if @name instanceof ComputedPropertyName
return yes if @name.name instanceof ComputedPropertyName
no
return
static: !!@isStatic
key: @name.ast o
computed: getIsComputed()
kind:
if @ctor
'constructor'
else
'method'
operator: @operatorToken?.value ? '='
staticClassName: @isStatic.staticClassName?.ast(o) ? null
bound: !!@bound
astProperties: (o) ->
return Object.assign
params: @paramForAst(param).ast(o) for param in @params
body: @body.ast (Object.assign {}, o, checkForDirectives: yes), LEVEL_TOP
generator: !!@isGenerator
async: !!@isAsync
名前付き関数を生成することはないため、匿名関数式/アロー関数に対するBabel ASTと一致するid
をnull
として指定します。
id: null
hasIndentedBody: @body.locationData.first_line > @funcGlyph?.locationData.first_line
,
if @isMethod then @methodAstProperties o else {}
astLocationData: ->
functionLocationData = super()
return functionLocationData unless @isMethod
astLocationData = mergeAstLocationData @name.astLocationData(), functionLocationData
if @isStatic.staticClassName?
astLocationData = mergeAstLocationData @isStatic.staticClassName.astLocationData(), astLocationData
astLocationData
関数定義内のパラメータ。典型的なJavaScriptパラメータに加えて、これらのパラメータは関数のコンテキストに自分自身をアタッチすることも、splatになってパラメータのグループを配列に収集することもできます。
exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
super()
message = isUnassignable @name.unwrapAll().value
@name.error message if message
if @name instanceof Obj and @name.generated
token = @name.objects[0].operatorToken
token.error "unexpected #{token.value}"
children: ['name', 'value']
compileToFragments: (o) ->
@name.compileToFragments o, LEVEL_LIST
compileToFragmentsWithoutComments: (o) ->
@name.compileToFragmentsWithoutComments o, LEVEL_LIST
asReference: (o) ->
return @reference if @reference
node = @name
if node.this
name = node.properties[0].name.value
name = "_#{name}" if name in JS_FORBIDDEN
node = new IdentifierLiteral o.scope.freeVariable name
else if node.shouldCache()
node = new IdentifierLiteral o.scope.freeVariable 'arg'
node = new Value node
node.updateLocationDataIfMissing @locationData
@reference = node
shouldCache: ->
@name.shouldCache()
Param
の名前を反復処理します。ある意味、非構造化パラメータは複数のJSパラメータを表します。このメソッドを使用すると、それらをすべて反復処理できます。iterator
関数はiterator(name, node)
として呼び出されます。ここで、name
はパラメータの名前であり、node
はその名前に対応するASTノードです。
eachName: (iterator, name = @name) ->
checkAssignabilityOfLiteral = (literal) ->
message = isUnassignable literal.value
if message
literal.error message
unless literal.isAssignable()
literal.error "'#{literal.value}' can't be assigned"
atParam = (obj, originalObj = null) => iterator "@#{obj.properties[0].name.value}", obj, @, originalObj
if name instanceof Call
name.error "Function invocation can't be assigned"
foo
if name instanceof Literal
checkAssignabilityOfLiteral name
return iterator name.value, name, @
@foo
return atParam name if name instanceof Value
for obj in name.objects ? []
元のobjを保存します。
nObj = obj
if obj instanceof Assign and not obj.context?
obj = obj.variable
{foo:bar}
if obj instanceof Assign
…おそらくデフォルト値付き
if obj.value instanceof Assign
obj = obj.value.variable
else
obj = obj.value
@eachName iterator, obj.unwrap()
[xs...]
else if obj instanceof Splat
node = obj.name.unwrap()
iterator node.value, node, @
else if obj instanceof Value
[{a}]
if obj.isArray() or obj.isObject()
@eachName iterator, obj.base
{@foo}
else if obj.this
atParam obj, nObj
else
checkAssignabilityOfLiteral obj.base
iterator obj.base.value, obj.base, @
else if obj instanceof Elision
obj
else if obj not instanceof Expansion
obj.error "illegal parameter #{obj.compile()}"
return
名前の指定されたASTノードを新しいノードに置き換えることで、paramの名前を変更します。これにより、オブジェクトの非構造化のソースが変更されないようにする必要があります。
renameParam: (node, newNode) ->
isNode = (candidate) -> candidate is node
replacement = (node, parent) =>
if parent instanceof Obj
key = node
key = node.properties[0].name if node.this
変数が予約されていない場合、非構造化変数に新しい変数を割り当てる必要はありません。例: ({@foo}) ->
は({foo}) { this.foo = foo}
にコンパイルする必要があります。foo = 1; ({@foo}) ->
はfoo = 1; ({foo:foo1}) { this.foo = foo1 }
にコンパイルする必要があります。
if node.this and key.value is newNode.value
new Value newNode
else
new Assign new Value(key), newNode, 'object'
else
newNode
@replaceInContext isNode, replacement
関数のパラメータ、呼び出しへの引数、または非構造化代入の一部としてのいずれかのsplat。
exports.Splat = class Splat extends Base
constructor: (name, {@lhs, @postfix = true} = {}) ->
super()
@name = if name.compile then name else new Literal name
children: ['name']
shouldCache: -> no
isAssignable: ({allowComplexSplat = no} = {})->
return allowComplexSplat if @name instanceof Obj or @name instanceof Parens
@name.isAssignable() and (not @name.isAtomic or @name.isAtomic())
assigns: (name) ->
@name.assigns name
compileNode: (o) ->
compiledSplat = [@makeCode('...'), @name.compileToFragments(o, LEVEL_OP)...]
return compiledSplat unless @jsx
return [@makeCode('{'), compiledSplat..., @makeCode('}')]
unwrap: -> @name
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
@name.propagateLhs? yes
astType: ->
if @jsx
'JSXSpreadAttribute'
else if @lhs
'RestElement'
else
'SpreadElement'
astProperties: (o) -> {
argument: @name.ast o, LEVEL_OP
@postfix
}
配列の非構造化(パターンマッチング)またはパラメータリスト内の値をスキップするために使用されます。
exports.Expansion = class Expansion extends Base
shouldCache: NO
compileNode: (o) ->
@throwLhsError()
asReference: (o) ->
this
eachName: (iterator) ->
throwLhsError: ->
@error 'Expansion must be used inside a destructuring assignment or parameter list'
astNode: (o) ->
unless @lhs
@throwLhsError()
super o
astType: -> 'RestElement'
astProperties: ->
return
argument: null
配列の省略要素(例:[,a, , , b, , c, ,])。
exports.Elision = class Elision extends Base
isAssignable: YES
shouldCache: NO
compileToFragments: (o, level) ->
fragment = super o, level
fragment.isElision = yes
fragment
compileNode: (o) ->
[@makeCode ', ']
asReference: (o) ->
this
eachName: (iterator) ->
astNode: ->
null
whileループ。CoffeeScriptで公開されている唯一の低レベルループです。これを使用して、他のすべてのループを作成できます。コンプリヘンションでは柔軟性や速度が不足する場合に役立ちます。
exports.While = class While extends Base
constructor: (@condition, {invert: @inverted, @guard, @isLoop} = {}) ->
super()
children: ['condition', 'guard', 'body']
isStatement: YES
makeReturn: (results, mark) ->
return super(results, mark) if results
@returns = not @jumps()
if mark
@body.makeReturn(results, mark) if @returns
return
this
addBody: (@body) ->
this
jumps: ->
{expressions} = @body
return no unless expressions.length
for node in expressions
return jumpNode if jumpNode = node.jumps loop: yes
no
JavaScriptのwhileとの主な違いは、CoffeeScriptのwhileをより大きな式の内部で使用できることです。whileループは、各反復の計算結果を含む配列を返す場合があります。
compileNode: (o) ->
o.indent += TAB
set = ''
{body} = this
if body.isEmpty()
body = @makeCode ''
else
if @returns
body.makeReturn rvar = o.scope.freeVariable 'results'
set = "#{@tab}#{rvar} = [];\n"
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
body = [].concat @makeCode("\n"), (body.compileToFragments o, LEVEL_TOP), @makeCode("\n#{@tab}")
answer = [].concat @makeCode(set + @tab + "while ("), @processedCondition().compileToFragments(o, LEVEL_PAREN),
@makeCode(") {"), body, @makeCode("}")
if @returns
answer.push @makeCode "\n#{@tab}return #{rvar};"
answer
processedCondition: ->
@processedConditionCache ?= if @inverted then @condition.invert() else @condition
astType: -> 'WhileStatement'
astProperties: (o) ->
return
test: @condition.ast o, LEVEL_PAREN
body: @body.ast o, LEVEL_TOP
guard: @guard?.ast(o) ? null
inverted: !!@inverted
postfix: !!@postfix
loop: !!@isLoop
単純な算術演算と論理演算。CoffeeScriptの演算をJavaScriptの同等の演算に変換します。
exports.Op = class Op extends Base
constructor: (op, first, second, flip, {@invertOperator, @originalOperator = op} = {}) ->
super()
if op is 'new'
if ((firstCall = unwrapped = first.unwrap()) instanceof Call or (firstCall = unwrapped.base) instanceof Call) and not firstCall.do and not firstCall.isNew
return new Value firstCall.newInstance(), if firstCall is unwrapped then [] else unwrapped.properties
first = new Parens first unless first instanceof Parens or first.unwrap() instanceof IdentifierLiteral or first.hasProperties?()
call = new Call first, []
call.locationData = @locationData
call.isNew = yes
return call
@operator = CONVERSIONS[op] or op
@first = first
@second = second
@flip = !!flip
if @operator in ['--', '++']
message = isUnassignable @first.unwrapAll().value
@first.error message if message
return this
CoffeeScript記号からJavaScript記号への変換マップ。
CONVERSIONS =
'==': '==='
'!=': '!=='
'of': 'in'
'yieldfrom': 'yield*'
可逆演算子のマップ。
INVERSIONS =
'!==': '==='
'===': '!=='
children: ['first', 'second']
isNumber: ->
@isUnary() and @operator in ['+', '-'] and
@first instanceof Value and @first.isNumber()
isAwait: ->
@operator is 'await'
isYield: ->
@operator in ['yield', 'yield*']
isUnary: ->
not @second
shouldCache: ->
not @isNumber()
Pythonスタイルの比較チェーンは可能ですか?
isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
isChain: ->
@isChainable() and @first.isChainable()
invert: ->
if @isInOperator()
@invertOperator = '!'
return @
if @isChain()
allInvertable = yes
curr = this
while curr and curr.operator
allInvertable and= (curr.operator of INVERSIONS)
curr = curr.first
return new Parens(this).invert() unless allInvertable
curr = this
while curr and curr.operator
curr.invert = !curr.invert
curr.operator = INVERSIONS[curr.operator]
curr = curr.first
this
else if op = INVERSIONS[@operator]
@operator = op
if @first.unwrap() instanceof Op
@first.invert()
this
else if @second
new Parens(this).invert()
else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
fst.operator in ['!', 'in', 'instanceof']
fst
else
new Op '!', this
unfoldSoak: (o) ->
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
generateDo: (exp) ->
passedParams = []
func = if exp instanceof Assign and (ref = exp.value.unwrap()) instanceof Code
ref
else
exp
for param in func.params or []
if param.value
passedParams.push param.value
delete param.value
else
passedParams.push param
call = new Call exp, passedParams
call.do = yes
call
isInOperator: ->
@originalOperator is 'in'
compileNode: (o) ->
if @isInOperator()
inNode = new In @first, @second
return (if @invertOperator then inNode.invert() else inNode).compileNode o
if @invertOperator
@invertOperator = null
return @invert().compileNode(o)
return Op::generateDo(@first).compileNode o if @operator is 'do'
isChain = @isChain()
チェーンでは、チェーンされた式がラップされているため、生のobjリテラルを括弧でラップする必要はありません。
@first.front = @front unless isChain
@checkDeleteOperand o
return @compileContinuation o if @isYield() or @isAwait()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
switch @operator
when '?' then @compileExistence o, @second.isDefaultValue
when '//' then @compileFloorDivision o
when '%%' then @compileModulo o
else
lhs = @first.compileToFragments o, LEVEL_OP
rhs = @second.compileToFragments o, LEVEL_OP
answer = [].concat lhs, @makeCode(" #{@operator} "), rhs
if o.level <= LEVEL_OP then answer else @wrapInParentheses answer
compileChain: (o) ->
[@first.second, shared] = @first.second.cache o
fst = @first.compileToFragments o, LEVEL_OP
fragments = fst.concat @makeCode(" #{if @invert then '&&' else '||'} "),
(shared.compileToFragments o), @makeCode(" #{@operator} "), (@second.compileToFragments o, LEVEL_OP)
@wrapInParentheses fragments
これが存在的な代入でない限り、左側の式の参照を保持します。
compileExistence: (o, checkOnlyUndefined) ->
if @first.shouldCache()
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, @first
else
fst = @first
ref = fst
new If(new Existence(fst, checkOnlyUndefined), ref, type: 'if').addElse(@second).compileToFragments o
単項Opをコンパイルします。
compileUnary: (o) ->
parts = []
op = @operator
parts.push [@makeCode op]
if op is '!' and @first instanceof Existence
@first.negated = not @first.negated
return @first.compileToFragments o
if o.level >= LEVEL_ACCESS
return (new Parens this).compileToFragments o
plusMinus = op in ['+', '-']
parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
plusMinus and @first instanceof Op and @first.operator is op
if plusMinus and @first instanceof Op
@first = new Parens @first
parts.push @first.compileToFragments o, LEVEL_OP
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compileContinuation: (o) ->
parts = []
op = @operator
@checkContinuation o unless @isAwait()
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
else
parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
parts.push [@makeCode op]
parts.push [@makeCode " "] if @first.base?.value isnt ''
parts.push @first.compileToFragments o, LEVEL_OP
parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
@joinFragmentArrays parts, ''
checkContinuation: (o) ->
unless o.scope.parent?
@error "#{@operator} can only occur inside functions"
if o.scope.method?.bound and o.scope.method.isGenerator
@error 'yield cannot occur inside bound (fat arrow) functions'
compileFloorDivision: (o) ->
floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
second = if @second.shouldCache() then new Parens @second else @second
div = new Op '/', @first, second
new Call(floor, [div]).compileToFragments o
compileModulo: (o) ->
mod = new Value new Literal utility 'modulo', o
new Call(mod, [@first, @second]).compileToFragments o
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
checkDeleteOperand: (o) ->
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
@error 'delete operand may not be argument or var'
astNode: (o) ->
@checkContinuation o if @isYield()
@checkDeleteOperand o
super o
astType: ->
return 'AwaitExpression' if @isAwait()
return 'YieldExpression' if @isYield()
return 'ChainedComparison' if @isChain()
switch @operator
when '||', '&&', '?' then 'LogicalExpression'
when '++', '--' then 'UpdateExpression'
else
if @isUnary() then 'UnaryExpression'
else 'BinaryExpression'
operatorAst: ->
"#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
chainAstProperties: (o) ->
operators = [@operatorAst()]
operands = [@second]
currentOp = @first
loop
operators.unshift currentOp.operatorAst()
operands.unshift currentOp.second
currentOp = currentOp.first
unless currentOp.isChainable()
operands.unshift currentOp
break
return {
operators
operands: (operand.ast(o, LEVEL_OP) for operand in operands)
}
astProperties: (o) ->
return @chainAstProperties(o) if @isChain()
firstAst = @first.ast o, LEVEL_OP
secondAst = @second?.ast o, LEVEL_OP
operatorAst = @operatorAst()
switch
when @isUnary()
argument =
if @isYield() and @first.unwrap().value is ''
null
else
firstAst
return {argument} if @isAwait()
return {
argument
delegate: @operator is 'yield*'
} if @isYield()
return {
argument
operator: operatorAst
prefix: !@flip
}
else
return
left: firstAst
right: secondAst
operator: operatorAst
exports.In = class In extends Base
constructor: (@object, @array) ->
super()
children: ['object', 'array']
invert: NEGATE
compileNode: (o) ->
if @array instanceof Value and @array.isArray() and @array.base.objects.length
for obj in @array.base.objects when obj instanceof Splat
hasSplat = yes
break
splatがない配列リテラルのみcompileOrTest
します。
return @compileOrTest o unless hasSplat
@compileLoopTest o
compileOrTest: (o) ->
[sub, ref] = @object.cache o, LEVEL_OP
[cmp, cnj] = if @negated then [' !== ', ' && '] else [' === ', ' || ']
tests = []
for item, i in @array.base.objects
if i then tests.push @makeCode cnj
tests = tests.concat (if i then ref else sub), @makeCode(cmp), item.compileToFragments(o, LEVEL_ACCESS)
if o.level < LEVEL_OP then tests else @wrapInParentheses tests
compileLoopTest: (o) ->
[sub, ref] = @object.cache o, LEVEL_LIST
fragments = [].concat @makeCode(utility('indexOf', o) + ".call("), @array.compileToFragments(o, LEVEL_LIST),
@makeCode(", "), ref, @makeCode(") " + if @negated then '< 0' else '>= 0')
return fragments if fragmentsToText(sub) is fragmentsToText(ref)
fragments = sub.concat @makeCode(', '), fragments
if o.level < LEVEL_LIST then fragments else @wrapInParentheses fragments
toString: (idt) ->
super idt, @constructor.name + if @negated then '!' else ''
古典的なtry/catch/finallyブロック。
exports.Try = class Try extends Base
constructor: (@attempt, @catch, @ensure, @finallyTag) ->
super()
children: ['attempt', 'catch', 'ensure']
isStatement: YES
jumps: (o) -> @attempt.jumps(o) or @catch?.jumps(o)
makeReturn: (results, mark) ->
if mark
@attempt?.makeReturn results, mark
@catch?.makeReturn results, mark
return
@attempt = @attempt.makeReturn results if @attempt
@catch = @catch .makeReturn results if @catch
this
コンパイルはおおよそ予想どおりです。finally句は省略可能ですが、catch句は省略できません。
compileNode: (o) ->
originalIndent = o.indent
o.indent += TAB
tryPart = @attempt.compileToFragments o, LEVEL_TOP
catchPart = if @catch
@catch.compileToFragments merge(o, indent: originalIndent), LEVEL_TOP
else unless @ensure or @catch
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
[@makeCode(" catch (#{generatedErrorVariableName}) {}")]
else
[]
ensurePart = if @ensure then ([].concat @makeCode(" finally {\n"), @ensure.compileToFragments(o, LEVEL_TOP),
@makeCode("\n#{@tab}}")) else []
[].concat @makeCode("#{@tab}try {\n"),
tryPart,
@makeCode("\n#{@tab}}"), catchPart, ensurePart
astType: -> 'TryStatement'
astProperties: (o) ->
return
block: @attempt.ast o, LEVEL_TOP
handler: @catch?.ast(o) ? null
finalizer:
if @ensure?
Object.assign @ensure.ast(o, LEVEL_TOP),
位置データにfinally
キーワードを含めます。
mergeAstLocationData(
jisonLocationDataToAstLocationData(@finallyTag.locationData),
@ensure.astLocationData()
)
else
null
exports.Catch = class Catch extends Base
constructor: (@recovery, @errorVariable) ->
super()
@errorVariable?.unwrap().propagateLhs? yes
children: ['recovery', 'errorVariable']
isStatement: YES
jumps: (o) -> @recovery.jumps o
makeReturn: (results, mark) ->
ret = @recovery.makeReturn results, mark
return if mark
@recovery = ret
this
compileNode: (o) ->
o.indent += TAB
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
placeholder = new IdentifierLiteral generatedErrorVariableName
@checkUnassignable()
if @errorVariable
@recovery.unshift new Assign @errorVariable, placeholder
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
checkUnassignable: ->
if @errorVariable
message = isUnassignable @errorVariable.unwrapAll().value
@errorVariable.error message if message
astNode: (o) ->
@checkUnassignable()
@errorVariable?.eachName (name) ->
alreadyDeclared = o.scope.find name.value
name.isDeclaration = not alreadyDeclared
super o
astType: -> 'CatchClause'
astProperties: (o) ->
return
param: @errorVariable?.ast(o) ? null
body: @recovery.ast o, LEVEL_TOP
例外をスローするための単純なノード。
exports.Throw = class Throw extends Base
constructor: (@expression) ->
super()
children: ['expression']
isStatement: YES
jumps: NO
Throwはある意味、既にreturnです…
makeReturn: THIS
compileNode: (o) ->
fragments = @expression.compileToFragments o, LEVEL_LIST
unshiftAfterComments fragments, @makeCode 'throw '
fragments.unshift @makeCode @tab
fragments.push @makeCode ';'
fragments
astType: -> 'ThrowStatement'
astProperties: (o) ->
return
argument: @expression.ast o, LEVEL_LIST
変数の存在をチェックします。null
でもundefined
でもありません。これはRubyの.nil?
に似ており、JavaScriptの真理値表を参照する必要がありません。必要に応じて、変数がundefined
でないかどうかだけをチェックします。
exports.Existence = class Existence extends Base
constructor: (@expression, onlyNotUndefined = no) ->
super()
@comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
salvagedComments = []
@expression.traverseChildren yes, (child) ->
if child.comments
for comment in child.comments
salvagedComments.push comment unless comment in salvagedComments
delete child.comments
attachCommentsToNode salvagedComments, @
moveComments @expression, @
children: ['expression']
invert: NEGATE
compileNode: (o) ->
@expression.front = @front
code = @expression.compile o, LEVEL_OP
if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
[cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
else
null
と比較する際には、緩い等号(==
)を明示的に使用したいと考えています。これにより、存在チェックはおおよそ真偽値のチェックに対応します。null
に対して===
に変更しないでください。これは、既存のコードの大部分を破壊します。ただし、undefined
のみと比較する場合、このユースケースはES2015+のデフォルト値との互換性のため、===
を使用します。デフォルト値は、変数がundefined
(ただしnull
ではない)の場合にのみ割り当てられます。
cmp = if @comparisonTarget is 'null'
if @negated then '==' else '!='
else # `undefined`
if @negated then '===' else '!=='
code = "#{code} #{cmp} #{@comparisonTarget}"
[@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
astType: -> 'UnaryExpression'
astProperties: (o) ->
return
argument: @expression.ast o
operator: '?'
prefix: no
ソースで明示的に指定された余分な括弧。かつては冗長な括弧を検出して削除することで結果をきれいにしようとしましたが、もうそうしません。好きなだけ入れることができます。
括弧は、任意のステートメントを式にするための良い方法です。
exports.Parens = class Parens extends Base
constructor: (@body) ->
super()
children: ['body']
unwrap: -> @body
shouldCache: -> @body.shouldCache()
compileNode: (o) ->
expr = @body.unwrap()
これらの括弧がIdentifierLiteral
とブロックコメントをラップしている場合、括弧を出力します(言い換えれば、これらの冗長な括弧を最適化しません)。これは、Flowが特定の状況で括弧を必要とするため、コメントベースの型注釈が付いた識別子とJavaScriptラベルを区別するためです。
shouldWrapComment = expr.comments?.some(
(comment) -> comment.here and not comment.unshift and not comment.newLine)
if expr instanceof Value and expr.isAtomic() and not @jsxAttribute and not shouldWrapComment
expr.front = @front
return expr.compileToFragments o
fragments = expr.compileToFragments o, LEVEL_PAREN
bare = o.level < LEVEL_OP and not shouldWrapComment and (
expr instanceof Op and not expr.isInOperator() or expr.unwrap() instanceof Call or
(expr instanceof For and expr.returns)
) and (o.level < LEVEL_COND or fragments.length <= 3)
return @wrapInBraces fragments if @jsxAttribute
if bare then fragments else @wrapInParentheses fragments
astNode: (o) -> @body.unwrap().ast o, LEVEL_PAREN
exports.StringWithInterpolations = class StringWithInterpolations extends Base
constructor: (@body, {@quote, @startQuote, @jsxAttribute} = {}) ->
super()
@fromStringLiteral: (stringLiteral) ->
updatedString = stringLiteral.withoutQuotesInLocationData()
updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote, jsxAttribute: stringLiteral.jsxAttribute
.withLocationDataFrom stringLiteral
children: ['body']
unwrap
はthis
を返して、祖先ノードが@body
を取得して@body.compileNode
を使用するのを防ぎます。StringWithInterpolations.compileNode
は、埋め込み文字列をコードとして出力するためのカスタムロジックです。
unwrap: -> this
shouldCache: -> @body.shouldCache()
extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->
expr
がBlock
であると仮定します。
expr = @body.unwrap()
elements = []
salvagedComments = []
expr.traverseChildren no, (node) =>
if node instanceof StringLiteral
if node.comments
salvagedComments.push node.comments...
delete node.comments
elements.push node
return yes
else if node instanceof Interpolation
if salvagedComments.length isnt 0
for comment in salvagedComments
comment.unshift = yes
comment.newLine = yes
attachCommentsToNode salvagedComments, node
if (unwrapped = node.expression?.unwrapAll()) instanceof PassthroughLiteral and unwrapped.generated and not (isJsx and o.compiling)
if o.compiling
commentPlaceholder = new StringLiteral('').withLocationDataFrom node
commentPlaceholder.comments = unwrapped.comments
(commentPlaceholder.comments ?= []).push node.comments... if node.comments
elements.push new Value commentPlaceholder
else
empty = new Interpolation().withLocationDataFrom node
empty.comments = node.comments
elements.push empty
else if node.expression or includeInterpolationWrappers
(node.expression?.comments ?= []).push node.comments... if node.comments
elements.push if includeInterpolationWrappers then node else node.expression
return no
else if node.comments
このノードは破棄されますが、コメントをサルベージします。
if elements.length isnt 0 and elements[elements.length - 1] not instanceof StringLiteral
for comment in node.comments
comment.unshift = no
comment.newLine = yes
attachCommentsToNode node.comments, elements[elements.length - 1]
else
salvagedComments.push node.comments...
delete node.comments
return yes
elements
compileNode: (o) ->
@comments ?= @startQuote?.comments
if @jsxAttribute
wrapped = new Parens new StringWithInterpolations @body
wrapped.jsxAttribute = yes
return wrapped.compileNode o
elements = @extractElements o, isJsx: @jsx
fragments = []
fragments.push @makeCode '`' unless @jsx
for element in elements
if element instanceof StringLiteral
unquotedElementValue = if @jsx then element.unquotedValueForJSX else element.unquotedValueForTemplateLiteral
fragments.push @makeCode unquotedElementValue
else
fragments.push @makeCode '$' unless @jsx
code = element.compileToFragments(o, LEVEL_PAREN)
if not @isNestedTag(element) or
code.some((fragment) -> fragment.comments?.some((comment) -> comment.here is no))
code = @wrapInBraces code
この`StringWithInterpolations`ノードによって生成されたものとして`{`と`}`フラグメントをマークし、`compileComments`がそれらを境界として扱うようにします。ただし、囲まれたコメントがすべて`/* */`コメントの場合、括弧は不要です。このコンパイラが縮小されている場合、縮小された変数名を報告する可能性のある`fragment.type`は信頼しないでください。
code[0].isStringWithInterpolations = yes
code[code.length - 1].isStringWithInterpolations = yes
fragments.push code...
fragments.push @makeCode '`' unless @jsx
fragments
isNestedTag: (element) ->
call = element.unwrapAll?()
@jsx and call instanceof JSXElement
astType: -> 'TemplateLiteral'
astProperties: (o) ->
elements = @extractElements o, includeInterpolationWrappers: yes
[..., last] = elements
quasis = []
expressions = []
for element, index in elements
if element instanceof StringLiteral
quasis.push new TemplateElement(
element.originalValue
tail: element is last
).withLocationDataFrom(element).ast o
else # Interpolation
{expression} = element
node =
unless expression?
emptyInterpolation = new EmptyInterpolation()
emptyInterpolation.locationData = emptyExpressionLocationData {
interpolationNode: element
openingBrace: '#{'
closingBrace: '}'
}
emptyInterpolation
else
expression.unwrapAll()
expressions.push astAsBlockIfNeeded node, o
{expressions, quasis, @quote}
exports.TemplateElement = class TemplateElement extends Base
constructor: (@value, {@tail} = {}) ->
super()
astProperties: ->
return
value:
raw: @value
tail: !!@tail
exports.Interpolation = class Interpolation extends Base
constructor: (@expression) ->
super()
children: ['expression']
空の補間(例:`#{}`)の内容を表します。AST生成中のみ使用されます。
exports.EmptyInterpolation = class EmptyInterpolation extends Base
constructor: ->
super()
CoffeeScriptのforループの代替は、配列とオブジェクトの内包表記であり、ここではforループにコンパイルされます。これらは式としても機能し、フィルターされた各反復の結果を返すことができます。
Pythonの配列内包表記とは異なり、複数行にすることができ、ループの現在のインデックスを2番目のパラメーターとして渡すことができます。Rubyのブロックとは異なり、1回のパスでマップとフィルターを実行できます。
exports.For = class For extends While
constructor: (body, source) ->
super()
@addBody body
@addSource source
children: ['body', 'source', 'guard', 'step']
isAwait: -> @await ? no
addBody: (body) ->
@body = Block.wrap [body]
{expressions} = @body
if expressions.length
@body.locationData ?= mergeLocationData expressions[0].locationData, expressions[expressions.length - 1].locationData
this
addSource: (source) ->
{@source = no} = source
attribs = ["name", "index", "guard", "step", "own", "ownTag", "await", "awaitTag", "object", "from"]
@[attr] = source[attr] ? @[attr] for attr in attribs
return this unless @source
@index.error 'cannot use index with for-from' if @from and @index
@ownTag.error "cannot use own with for-#{if @from then 'from' else 'in'}" if @own and not @object
[@name, @index] = [@index, @name] if @object
@index.error 'index cannot be a pattern matching expression' if @index?.isArray?() or @index?.isObject?()
@awaitTag.error 'await must be used with for-from' if @await and not @from
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
@pattern = @name instanceof Value
@name.unwrap().propagateLhs?(yes) if @pattern
@index.error 'indexes do not apply to range loops' if @range and @index
@name.error 'cannot pattern match over range loops' if @range and @pattern
@returns = no
`for`行、つまり`for`を含むコード行にあるコメントを、その行の子ノードから`for`ノード自体まで上に移動して、これらのコメントが出力され、`for`ループの上に配置されるようにします。
for attribute in ['source', 'guard', 'step', 'name', 'index'] when @[attribute]
@[attribute].traverseChildren yes, (node) =>
if node.comments
これらのコメントは非常に深く埋め込まれているため、それらが末尾のコメントである場合、この`for`ループのコンパイルが完了した時点で、それらが続く行は認識できなくなります。そのため、`for`行の上に出力するように単にシフトします。
comment.newLine = comment.unshift = yes for comment in node.comments
moveComments node, @[attribute]
moveComments @[attribute], @
this
CoffeeScript全体で最も厄介なメソッドへようこそ。配列、オブジェクト、範囲の内包表記の内部ループ、フィルター処理、ステップ、結果の保存を処理します。生成されたコードの一部は共通で使用でき、一部は使用できません。
compileNode: (o) ->
body = Block.wrap [@body]
[..., last] = body.expressions
@returns = no if last?.jumps() instanceof Return
source = if @range then @source.base else @source
scope = o.scope
name = @name and (@name.compile o, LEVEL_LIST) if not @pattern
index = @index and (@index.compile o, LEVEL_LIST)
scope.find(name) if name and not @pattern
scope.find(index) if index and @index not instanceof Value
rvar = scope.freeVariable 'results' if @returns
if @from
ivar = scope.freeVariable 'x', single: true if @pattern
else
ivar = (@object and index) or scope.freeVariable 'i', single: true
kvar = ((@range or @from) and name) or index or ivar
kvarAssign = if kvar isnt ivar then "#{kvar} = " else ""
if @step and not @range
[step, stepVar] = @cacheToCodeFragments @step.cache o, LEVEL_LIST, shouldCacheOrIsAssignable
stepNum = parseNumber stepVar if @step.isNumber()
name = ivar if @pattern
varPart = ''
guardPart = ''
defPart = ''
idt1 = @tab + TAB
if @range
forPartFragments = source.compileToFragments merge o,
{index: ivar, name, @step, shouldCache: shouldCacheOrIsAssignable}
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not @from and @source.unwrap() not instanceof IdentifierLiteral
defPart += "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
if name and not @pattern and not @from
namePart = "#{name} = #{svar}[#{kvar}]"
if not @object and not @from
defPart += "#{@tab}#{step};\n" if step isnt stepVar
down = stepNum < 0
lvar = scope.freeVariable 'len' unless @step and stepNum? and down
declare = "#{kvarAssign}#{ivar} = 0, #{lvar} = #{svar}.length"
declareDown = "#{kvarAssign}#{ivar} = #{svar}.length - 1"
compare = "#{ivar} < #{lvar}"
compareDown = "#{ivar} >= 0"
if @step
if stepNum?
if down
compare = compareDown
declare = declareDown
else
compare = "#{stepVar} > 0 ? #{compare} : #{compareDown}"
declare = "(#{stepVar} > 0 ? (#{declare}) : #{declareDown})"
increment = "#{ivar} += #{stepVar}"
else
increment = "#{if kvar isnt ivar then "++#{ivar}" else "#{ivar}++"}"
forPartFragments = [@makeCode("#{declare}; #{compare}; #{kvarAssign}#{increment}")]
if @returns
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = "\n#{@tab}return #{rvar};"
body.makeReturn rvar
if @guard
if body.expressions.length > 1
body.expressions.unshift new If (new Parens @guard).invert(), new StatementLiteral "continue"
else
body = Block.wrap [new If @guard, body] if @guard
if @pattern
body.expressions.unshift new Assign @name, if @from then new IdentifierLiteral kvar else new Literal "#{svar}[#{kvar}]"
varPart = "\n#{idt1}#{namePart};" if namePart
if @object
forPartFragments = [@makeCode("#{kvar} in #{svar}")]
guardPart = "\n#{idt1}if (!#{utility 'hasProp', o}.call(#{svar}, #{kvar})) continue;" if @own
else if @from
if @await
forPartFragments = new Op 'await', new Parens new Literal "#{kvar} of #{svar}"
forPartFragments = forPartFragments.compileToFragments o, LEVEL_TOP
else
forPartFragments = [@makeCode("#{kvar} of #{svar}")]
bodyFragments = body.compileToFragments merge(o, indent: idt1), LEVEL_TOP
if bodyFragments and bodyFragments.length > 0
bodyFragments = [].concat @makeCode('\n'), bodyFragments, @makeCode('\n')
fragments = [@makeCode(defPart)]
fragments.push @makeCode(resultPart) if resultPart
forCode = if @await then 'for ' else 'for ('
forClose = if @await then '' else ')'
fragments = fragments.concat @makeCode(@tab), @makeCode( forCode),
forPartFragments, @makeCode("#{forClose} {#{guardPart}#{varPart}"), bodyFragments,
@makeCode(@tab), @makeCode('}')
fragments.push @makeCode(returnResult) if returnResult
fragments
astNode: (o) ->
addToScope = (name) ->
alreadyDeclared = o.scope.find name.value
name.isDeclaration = not alreadyDeclared
@name?.eachName addToScope, checkAssignability: no
@index?.eachName addToScope, checkAssignability: no
super o
astType: -> 'For'
astProperties: (o) ->
return
source: @source?.ast o
body: @body.ast o, LEVEL_TOP
guard: @guard?.ast(o) ? null
name: @name?.ast(o) ? null
index: @index?.ast(o) ? null
step: @step?.ast(o) ? null
postfix: !!@postfix
own: !!@own
await: !!@await
style: switch
when @from then 'from'
when @object then 'of'
when @name then 'in'
else 'range'
JavaScriptのswitch文。必要に応じて返却可能な式に変換します。
exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) ->
super()
children: ['subject', 'cases', 'otherwise']
isStatement: YES
jumps: (o = {block: yes}) ->
for {block} in @cases
return jumpNode if jumpNode = block.jumps o
@otherwise?.jumps o
makeReturn: (results, mark) ->
block.makeReturn(results, mark) for {block} in @cases
@otherwise or= new Block [new Literal 'void 0'] if results
@otherwise?.makeReturn results, mark
this
compileNode: (o) ->
idt1 = o.indent + TAB
idt2 = o.indent = idt1 + TAB
fragments = [].concat @makeCode(@tab + "switch ("),
(if @subject then @subject.compileToFragments(o, LEVEL_PAREN) else @makeCode "false"),
@makeCode(") {\n")
for {conditions, block}, i in @cases
for cond in flatten [conditions]
cond = cond.invert() unless @subject
fragments = fragments.concat @makeCode(idt1 + "case "), cond.compileToFragments(o, LEVEL_PAREN), @makeCode(":\n")
fragments = fragments.concat body, @makeCode('\n') if (body = block.compileToFragments o, LEVEL_TOP).length > 0
break if i is @cases.length - 1 and not @otherwise
expr = @lastNode block.expressions
continue if expr instanceof Return or expr instanceof Throw or (expr instanceof Literal and expr.jumps() and expr.value isnt 'debugger')
fragments.push cond.makeCode(idt2 + 'break;\n')
if @otherwise and @otherwise.expressions.length
fragments.push @makeCode(idt1 + "default:\n"), (@otherwise.compileToFragments o, LEVEL_TOP)..., @makeCode("\n")
fragments.push @makeCode @tab + '}'
fragments
astType: -> 'SwitchStatement'
casesAst: (o) ->
cases = []
for kase, caseIndex in @cases
{conditions: tests, block: consequent} = kase
tests = flatten [tests]
lastTestIndex = tests.length - 1
for test, testIndex in tests
testConsequent =
if testIndex is lastTestIndex
consequent
else
null
caseLocationData = test.locationData
caseLocationData = mergeLocationData caseLocationData, testConsequent.expressions[testConsequent.expressions.length - 1].locationData if testConsequent?.expressions.length
caseLocationData = mergeLocationData caseLocationData, kase.locationData, justLeading: yes if testIndex is 0
caseLocationData = mergeLocationData caseLocationData, kase.locationData, justEnding: yes if testIndex is lastTestIndex
cases.push new SwitchCase(test, testConsequent, trailing: testIndex is lastTestIndex).withLocationDataFrom locationData: caseLocationData
if @otherwise?.expressions.length
cases.push new SwitchCase(null, @otherwise).withLocationDataFrom @otherwise
kase.ast(o) for kase in cases
astProperties: (o) ->
return
discriminant: @subject?.ast(o, LEVEL_PAREN) ? null
cases: @casesAst o
class SwitchCase extends Base
constructor: (@test, @block, {@trailing} = {}) ->
super()
children: ['test', 'block']
astProperties: (o) ->
return
test: @test?.ast(o, LEVEL_PAREN) ? null
consequent: @block?.ast(o, LEVEL_TOP).body ? []
trailing: !!@trailing
exports.SwitchWhen = class SwitchWhen extends Base
constructor: (@conditions, @block) ->
super()
children: ['conditions', 'block']
If/else文。各節の最後の行に要求された戻り値をプッシュダウンすることにより、式として機能します。
単一式のIfは、可能な場合は条件演算子にコンパイルされます。なぜなら、三項演算子はすでに適切な式であり、変換する必要がないからです。
exports.If = class If extends Base
constructor: (@condition, @body, options = {}) ->
super()
@elseBody = null
@isChain = false
{@soak, @postfix, @type} = options
moveComments @condition, @ if @condition.comments
children: ['condition', 'body', 'elseBody']
bodyNode: -> @body?.unwrap()
elseBodyNode: -> @elseBody?.unwrap()
Ifの連鎖を書き直し、デフォルトのケースを最後のelseとして追加します。
addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
@locationData = mergeLocationData @locationData, @elseBodyNode().locationData
else
@isChain = elseBody instanceof If
@elseBody = @ensureBlock elseBody
@elseBody.updateLocationDataIfMissing elseBody.locationData
@locationData = mergeLocationData @locationData, @elseBody.locationData if @locationData? and @elseBody.locationData?
this
Ifは、その本体のいずれかが文である必要がある場合にのみ、文にコンパイルされます。そうでない場合、条件演算子は安全です。
isStatement: (o) ->
o?.level is LEVEL_TOP or
@bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
jumps: (o) -> @body.jumps(o) or @elseBody?.jumps(o)
compileNode: (o) ->
if @isStatement o then @compileStatement o else @compileExpression o
makeReturn: (results, mark) ->
if mark
@body?.makeReturn results, mark
@elseBody?.makeReturn results, mark
return
@elseBody or= new Block [new Literal 'void 0'] if results
@body and= new Block [@body.makeReturn results]
@elseBody and= new Block [@elseBody.makeReturn results]
this
ensureBlock: (node) ->
if node instanceof Block then node else new Block [node]
If
を通常のif-else文としてコンパイルします。平坦化された連鎖は、内部のelse本体を文形式にします。
compileStatement: (o) ->
child = del o, 'chainChild'
exeq = del o, 'isExistentialEquals'
if exeq
return new If(@processedCondition().invert(), @elseBodyNode(), type: 'if').compileToFragments o
indent = o.indent + TAB
cond = @processedCondition().compileToFragments o, LEVEL_PAREN
body = @ensureBlock(@body).compileToFragments merge o, {indent}
ifPart = [].concat @makeCode("if ("), cond, @makeCode(") {\n"), body, @makeCode("\n#{@tab}}")
ifPart.unshift @makeCode @tab unless child
return ifPart unless @elseBody
answer = ifPart.concat @makeCode(' else ')
if @isChain
o.chainChild = yes
answer = answer.concat @elseBody.unwrap().compileToFragments o, LEVEL_TOP
else
answer = answer.concat @makeCode("{\n"), @elseBody.compileToFragments(merge(o, {indent}), LEVEL_TOP), @makeCode("\n#{@tab}}")
answer
If
を条件演算子としてコンパイルします。
compileExpression: (o) ->
cond = @processedCondition().compileToFragments o, LEVEL_COND
body = @bodyNode().compileToFragments o, LEVEL_LIST
alt = if @elseBodyNode() then @elseBodyNode().compileToFragments(o, LEVEL_LIST) else [@makeCode('void 0')]
fragments = cond.concat @makeCode(" ? "), body, @makeCode(" : "), alt
if o.level >= LEVEL_COND then @wrapInParentheses fragments else fragments
unfoldSoak: ->
@soak and this
processedCondition: ->
@processedConditionCache ?= if @type is 'unless' then @condition.invert() else @condition
isStatementAst: (o) ->
o.level is LEVEL_TOP
astType: (o) ->
if @isStatementAst o
'IfStatement'
else
'ConditionalExpression'
astProperties: (o) ->
isStatement = @isStatementAst o
return
test: @condition.ast o, if isStatement then LEVEL_PAREN else LEVEL_COND
consequent:
if isStatement
@body.ast o, LEVEL_TOP
else
@bodyNode().ast o, LEVEL_TOP
alternate:
if @isChain
@elseBody.unwrap().ast o, if isStatement then LEVEL_TOP else LEVEL_COND
else if not isStatement and @elseBody?.expressions?.length is 1
@elseBody.expressions[0].ast o, LEVEL_TOP
else
@elseBody?.ast(o, LEVEL_TOP) ? null
postfix: !!@postfix
inverted: @type is 'unless'
シーケンス式(例:(a; b)
)。現在、AST生成中のみ使用されます。
exports.Sequence = class Sequence extends Base
children: ['expressions']
constructor: (@expressions) ->
super()
astNode: (o) ->
return @expressions[0].ast(o) if @expressions.length is 1
super o
astType: -> 'SequenceExpression'
astProperties: (o) ->
return
expressions:
expression.ast(o) for expression in @expressions
UTILITIES =
modulo: -> 'function(a, b) { return (+a % (b = +b) + b) % b; }'
boundMethodCheck: -> "
function(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new Error('Bound instance method accessed before binding');
}
}
"
ネイティブ関数のルックアップ時間を高速化するためのショートカット。
hasProp: -> '{}.hasOwnProperty'
indexOf: -> '[].indexOf'
slice : -> '[].slice'
splice : -> '[].splice'
レベルは、AST内のノードの位置を示します。括弧が必要か冗長かを知るのに役立ちます。
LEVEL_TOP = 1 # ...;
LEVEL_PAREN = 2 # (...)
LEVEL_LIST = 3 # [...]
LEVEL_COND = 4 # ... ? x : y
LEVEL_OP = 5 # !...
LEVEL_ACCESS = 6 # ...[0]
タブは、きれいに表示するために2つのスペースです。
TAB = ' '
SIMPLENUM = /^[+-]?\d+(?:_\d+)*$/
SIMPLE_STRING_OMIT = /\s*\n\s*/g
LEADING_BLANK_LINE = /^[^\n\S]*\n/
TRAILING_BLANK_LINE = /\n[^\n\S]*$/
STRING_OMIT = ///
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
| \\[^\S\n]*\n\s* # Remove escaped newlines.
///g
HEREGEX_OMIT = ///
((?:\\\\)+) # Consume (and preserve) an even number of backslashes.
| \\(\s) # Preserve escaped whitespace.
| \s+(?:#.*)? # Remove whitespace and comments.
///g
ユーティリティ関数が最上位レベルに割り当てられるようにするためのヘルパー。
utility = (name, o) ->
{root} = o.scope
if name of root.utilities
root.utilities[name]
else
ref = root.freeVariable name
root.assign ref, UTILITIES[name] o
root.utilities[name] = ref
multident = (code, tab, includingFirstLine = yes) ->
endsWithNewLine = code[code.length - 1] is '\n'
code = (if includingFirstLine then tab else '') + code.replace /\n/g, "$&#{tab}"
code = code.replace /\s+$/, ''
code = code + '\n' if endsWithNewLine
code
CoffeeScript 1でコード行のインデントに`makeCode "#{@tab}"`を挿入した可能性のある場所では、コード行の前にコメントがある可能性を考慮する必要があります。そのようなコメントがある場合は、そのようなコメントの各行をインデントし、その後、続く最初のコード行をインデントします。
indentInitial = (fragments, node) ->
for fragment, fragmentIndex in fragments
if fragment.isHereComment
fragment.code = multident fragment.code, node.tab
else
fragments.splice fragmentIndex, 0, node.makeCode "#{node.tab}"
break
fragments
hasLineComments = (node) ->
return no unless node.comments
for comment in node.comments
return yes if comment.here is no
return no
`comments`プロパティをあるオブジェクトから別のオブジェクトに移動し、最初のオブジェクトから削除します。
moveComments = (from, to) ->
return unless from?.comments
attachCommentsToNode from.comments, to
delete from.comments
ノードをコンパイルするときに、フラグメントの配列の先頭にフラグメントを挿入したい場合があります。しかし、先頭に1つ以上のコメントフラグメントがある場合、それらの後、非コメントの前にこのフラグメントを挿入したいです。
unshiftAfterComments = (fragments, fragmentToInsert) ->
inserted = no
for fragment, fragmentIndex in fragments when not fragment.isComment
fragments.splice fragmentIndex, 0, fragmentToInsert
inserted = yes
break
fragments.push fragmentToInsert unless inserted
fragments
isLiteralArguments = (node) ->
node instanceof IdentifierLiteral and node.value is 'arguments'
isLiteralThis = (node) ->
node instanceof ThisLiteral or (node instanceof Code and node.bound)
shouldCacheOrIsAssignable = (node) -> node.shouldCache() or node.isAssignable?()
ノードの子をsoakで展開し、作成されたIf
の下にノードを配置します。
unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn
特定の文字をエスケープすることによって、文字列または正規表現を構築します。
makeDelimitedLiteral = (body, {delimiter: delimiterOption, escapeNewlines, double, includeDelimiters = yes, escapeDelimiter = yes, convertTrailingNullEscapes} = {}) ->
body = '(?:)' if body is '' and delimiterOption is '/'
escapeTemplateLiteralCurlies = delimiterOption is '`'
regex = ///
(\\\\) # Escaped backslash.
| (\\0(?=\d)) # Null character mistaken as octal escape.
#{
if convertTrailingNullEscapes
/// | (\\0) $ ///.source # Trailing null character that could be mistaken as octal escape.
else
''
}
#{
if escapeDelimiter
/// | \\?(#{delimiterOption}) ///.source # (Possibly escaped) delimiter.
else
''
}
#{
if escapeTemplateLiteralCurlies
/// | \\?(\$\{) ///.source # `${` inside template literals must be escaped.
else
''
}
| \\?(?:
#{if escapeNewlines then '(\n)|' else ''}
(\r)
| (\u2028)
| (\u2029)
) # (Possibly escaped) newlines.
| (\\.) # Other escapes.
///g
body = body.replace regex, (match, backslash, nul, ...args) ->
trailingNullEscape =
args.shift() if convertTrailingNullEscapes
delimiter =
args.shift() if escapeDelimiter
templateLiteralCurly =
args.shift() if escapeTemplateLiteralCurlies
lf =
args.shift() if escapeNewlines
[cr, ls, ps, other] = args
switch
エスケープされたバックスラッシュを無視します。
when backslash then (if double then backslash + backslash else backslash)
when nul then '\\x00'
when trailingNullEscape then "\\x00"
when delimiter then "\\#{delimiter}"
when templateLiteralCurly then "\\${"
when lf then '\\n'
when cr then '\\r'
when ls then '\\u2028'
when ps then '\\u2029'
when other then (if double then "\\#{other}" else other)
printedDelimiter = if includeDelimiters then delimiterOption else ''
"#{printedDelimiter}#{body}#{printedDelimiter}"
sniffDirectives = (expressions, {notFinalExpression} = {}) ->
index = 0
lastIndex = expressions.length - 1
while index <= lastIndex
break if index is lastIndex and notFinalExpression
expression = expressions[index]
if (unwrapped = expression?.unwrap?()) instanceof PassthroughLiteral and unwrapped.generated
index++
continue
break unless expression instanceof Value and expression.isString() and not expression.unwrap().shouldGenerateTemplateLiteral()
expressions[index] =
new Directive expression
.withLocationDataFrom expression
index++
astAsBlockIfNeeded = (node, o) ->
unwrapped = node.unwrap()
if unwrapped instanceof Block and unwrapped.expressions.length > 1
unwrapped.makeReturn null, yes
unwrapped.ast o, LEVEL_TOP
else
node.ast o, LEVEL_PAREN
以下の`mergeLocationData`と`mergeAstLocationData`のヘルパー。
lesser = (a, b) -> if a < b then a else b
greater = (a, b) -> if a > b then a else b
isAstLocGreater = (a, b) ->
return yes if a.line > b.line
return no unless a.line is b.line
a.column > b.column
isLocationDataStartGreater = (a, b) ->
return yes if a.first_line > b.first_line
return no unless a.first_line is b.first_line
a.first_column > b.first_column
isLocationDataEndGreater = (a, b) ->
return yes if a.last_line > b.last_line
return no unless a.last_line is b.last_line
a.last_column > b.last_column
2つのノードの位置データを取得し、両方のノードの位置データを含む新しい`locationData`オブジェクトを返します。そのため、新しい`first_line`値は2つのノードの`first_line`値の早い方の値になり、新しい`last_column`値は2つのノードの`last_column`値の遅い方の値になります。
`justLeading`または`justEnding`オプションを渡すと、最初のノードの位置データを2番目のノードの開始または終了位置データで拡張する場合があります。たとえば、`first`の範囲が[4, 5]で`second`の範囲が[1, 10]の場合、次のようになります。
mergeLocationData(first, second).range # [1, 10]
mergeLocationData(first, second, justLeading: yes).range # [1, 5]
mergeLocationData(first, second, justEnding: yes).range # [4, 10]
exports.mergeLocationData = mergeLocationData = (locationDataA, locationDataB, {justLeading, justEnding} = {}) ->
return Object.assign(
if justEnding
first_line: locationDataA.first_line
first_column: locationDataA.first_column
else
if isLocationDataStartGreater locationDataA, locationDataB
first_line: locationDataB.first_line
first_column: locationDataB.first_column
else
first_line: locationDataA.first_line
first_column: locationDataA.first_column
,
if justLeading
last_line: locationDataA.last_line
last_column: locationDataA.last_column
last_line_exclusive: locationDataA.last_line_exclusive
last_column_exclusive: locationDataA.last_column_exclusive
else
if isLocationDataEndGreater locationDataA, locationDataB
last_line: locationDataA.last_line
last_column: locationDataA.last_column
last_line_exclusive: locationDataA.last_line_exclusive
last_column_exclusive: locationDataA.last_column_exclusive
else
last_line: locationDataB.last_line
last_column: locationDataB.last_column
last_line_exclusive: locationDataB.last_line_exclusive
last_column_exclusive: locationDataB.last_column_exclusive
,
range: [
if justEnding
locationDataA.range[0]
else
lesser locationDataA.range[0], locationDataB.range[0]
,
if justLeading
locationDataA.range[1]
else
greater locationDataA.range[1], locationDataB.range[1]
]
)
2つのASTノード、または2つのASTノードの位置データオブジェクトを取得し、両方のノードの位置データを含む新しい位置データオブジェクトを返します。そのため、新しい`start`値は2つのノードの`start`値の早い方の値になり、新しい`end`値は2つのノードの`end`値の遅い方の値になります。
`justLeading`または`justEnding`オプションを渡すと、最初のノードの位置データを2番目のノードの開始または終了位置データで拡張する場合があります。たとえば、`first`の範囲が[4, 5]で`second`の範囲が[1, 10]の場合、次のようになります。
mergeAstLocationData(first, second).range # [1, 10]
mergeAstLocationData(first, second, justLeading: yes).range # [1, 5]
mergeAstLocationData(first, second, justEnding: yes).range # [4, 10]
exports.mergeAstLocationData = mergeAstLocationData = (nodeA, nodeB, {justLeading, justEnding} = {}) ->
return
loc:
start:
if justEnding
nodeA.loc.start
else
if isAstLocGreater nodeA.loc.start, nodeB.loc.start
nodeB.loc.start
else
nodeA.loc.start
end:
if justLeading
nodeA.loc.end
else
if isAstLocGreater nodeA.loc.end, nodeB.loc.end
nodeA.loc.end
else
nodeB.loc.end
range: [
if justEnding
nodeA.range[0]
else
lesser nodeA.range[0], nodeB.range[0]
,
if justLeading
nodeA.range[1]
else
greater nodeA.range[1], nodeB.range[1]
]
start:
if justEnding
nodeA.start
else
lesser nodeA.start, nodeB.start
end:
if justLeading
nodeA.end
else
greater nodeA.end, nodeB.end
Jisonスタイルのノードクラスの位置データをBabelスタイルの位置データに変換します。
exports.jisonLocationDataToAstLocationData = jisonLocationDataToAstLocationData = ({first_line, first_column, last_line_exclusive, last_column_exclusive, range}) ->
return
loc:
start:
line: first_line + 1
column: first_column
end:
line: last_line_exclusive + 1
column: last_column_exclusive
range: [
range[0]
range[1]
]
start: range[0]
end: range[1]
別のノードの位置の終わりに対応するゼロ幅の位置データを生成します。
zeroWidthLocationDataFromEndLocation = ({range: [, endRange], last_line_exclusive, last_column_exclusive}) -> {
first_line: last_line_exclusive
first_column: last_column_exclusive
last_line: last_line_exclusive
last_column: last_column_exclusive
last_line_exclusive
last_column_exclusive
range: [endRange, endRange]
}
extractSameLineLocationDataFirst = (numChars) -> ({range: [startRange], first_line, first_column}) -> {
first_line
first_column
last_line: first_line
last_column: first_column + numChars - 1
last_line_exclusive: first_line
last_column_exclusive: first_column + numChars
range: [startRange, startRange + numChars]
}
extractSameLineLocationDataLast = (numChars) -> ({range: [, endRange], last_line, last_column, last_line_exclusive, last_column_exclusive}) -> {
first_line: last_line
first_column: last_column - (numChars - 1)
last_line: last_line
last_column: last_column
last_line_exclusive
last_column_exclusive
range: [endRange - numChars, endRange]
}
現在、補間/JSX式の括弧の間の空白に対応するトークンがないため、補間の位置データから括弧を切り捨てることで位置データを組み立てる必要があります。技術的には、最後の括弧の前に改行がある場合、`last_line`/`last_column`の計算は間違っている可能性がありますが、`last_line`/`last_column`はAST生成には使用されません。
emptyExpressionLocationData = ({interpolationNode: element, openingBrace, closingBrace}) ->
first_line: element.locationData.first_line
first_column: element.locationData.first_column + openingBrace.length
last_line: element.locationData.last_line
last_column: element.locationData.last_column - closingBrace.length
last_line_exclusive: element.locationData.last_line
last_column_exclusive: element.locationData.last_column
range: [
element.locationData.range[0] + openingBrace.length
element.locationData.range[1] - closingBrace.length
]