Error.stackTraceLimit = Infinity
{Scope} = require './scope'
{isUnassignable, JS_FORBIDDEN} = require './lexer'
nodes.coffee
には、構文ツリーのすべてのノードクラスが含まれています。ほとんどのノードは文法のアクションの結果として作成されますが、コード生成の方法として他のノードによって作成されるものもあります。構文ツリーを 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
以下に定義されているさまざまなノードはすべて、コードフラグメントオブジェクトのコレクションにコンパイルされます。コードフラグメントは生成されたコードのブロックと、コードのソースファイル内の場所です。コードフラグメントは、すべてのコードフラグメントの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 ''}"
コードフラグメントの配列を文字列に変換します。
fragmentsToText = (fragments) ->
(fragment.code for fragment in fragments).join('')
基底は、構文ツリー内のすべてのノードの抽象基底クラスです。各サブクラスは、そのノードのコード生成を実行する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'
このノードをコンパイルする前にクロージャでラップする必要があるか、直接コンパイルする必要があるかを判断するための共通ロジック。このノードが*ステートメント*であり、*純粋なステートメント*ではなく、ブロックの最上位ではなく(不要になります)、結果を返すようにまだ求められていない場合(ステートメントは結果を返す方法を知っているため)、ラップする必要があります。
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
このノード、またはその子に、特定の種類のノードが含まれていますか?*children* ノードを再帰的にトラバースし、`pred` を検証する最初のノードを返します。それ以外の場合は undefined を返します。`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'
JSON としてシリアル化できる、ノードのプレーンな JavaScript オブジェクト表現。これは、Node API の `ast` オプションが返すものです。他のツールとの相互運用性を向上させるために、Babel AST 仕様 にできるだけ厳密に従うようにしています。**警告:子クラスでこのメソッドをオーバーライドしないでください。** 必要に応じて、コンポーネントの `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` からの戻りロジックがその時までに既に実行されている必要があるため、`@makeReturn` は `astProperties` の前に呼び出す必要があります。
@makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
o
astNode: (o) ->
すべての抽象構文ツリーノードオブジェクトには、4つのカテゴリのプロパティがあります
Object.assign {}, {type: @astType(o)}, @astProperties(o), @astLocationData()
デフォルトでは、ノードクラスには特定のプロパティがありません。
astProperties: -> {}
デフォルトでは、ノードクラスの AST `type` はそのクラス名です。
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` には、ステートメントが式の位置に ended up することによるエラーメッセージがいくつかあります。
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
Block ノードは本体全体を返すのではなく、最後の式が返されることを保証します。
makeReturn: (results, mark) ->
len = @expressions.length
[..., lastExp] = @expressions
lastExp = lastExp?.unwrap() or no
また、同じレベルに隣接する JSX タグがある場合、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
Block 本体内のすべての式をコンパイルします。 結果を返す必要があり、それが式の場合は、単にそれを返します。 ステートメントの場合は、ステートメントにそうするように指示します。
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
最初の末尾のコメントは、; // Comment
のようにコード行の最後に続くのですか、それともコード行の後に新しい行を開始するのですか?
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
指定されたノードを Block としてラップします。ただし、すでに Block である場合は除きます。
@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
は *pureStatement* です。クロージャでラップしても意味がありません。
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 にコンパイルします。 プロパティのチェーンに *soak* 演算子 ?.
が散在していると、事態ははるかに面白くなります。 その後、ソークチェーンを構築するときに誤って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)
と同じ順序でコンパイルする必要があります(問題#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つだけの 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()
それ以外の場合は、Base::ast
を呼び出します。これは、以下の astType
メソッドと astProperties
メソッドを呼び出します。
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']
無効な属性をキャッチします:<div {a:”b”, props} {props} “value” />
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
<a><b /></a>
と <a>{<b />}</a>
を区別します。
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)
と同じ順序でコンパイルする必要があります(問題 #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
補間を含む正規表現は、実際には、内部に StringWithInterpolations
を持つ Call
(正確には RegExp()
呼び出し)のバリエーションです。
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
値のプロパティへの .
アクセス、またはオブジェクトのプロトタイプへのアクセスのための ::
shorthand です。
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 番目のパラメータは、最初のパラメータが始まりのインデックスであるのと同様に、スライスの終わりのインデックスを指定します。
exports.Slice = class Slice extends Base
children: ['range']
constructor: (@range) ->
super()
配列の終わりまでスライスしようとすると注意が必要です。すべての実装が undefined
または 1/0
を尊重するわけではないため、9e9
が使用されます。9e9
> 2**32
(最大配列長)であるため、9e9
は安全です。
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()
オブジェクトに splat が含まれているかどうかを確認します。
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 クラス定義。名前、オプションの親クラス、および本体を使用して **クラス** を初期化します。
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 クラス機能(クラスフィールドなど)がステージ 4 に達すると、このメソッドを更新してそれらをサポートする必要があります。さらに、実装されていない ES 機能(たとえば、Object.defineProperty
メソッドではなく get
および set
キーワードを介して定義されたゲッターとセッター)の回避策として、イニシャライザで 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'
クラ lass スコープはまだ利用できないため、後で更新するために代入を返します
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 つ以上の herecomment が添付されている場合、Flow タイピングとの互換性のために、それらを宣言行の一部として出力します(他の herecomment が既にそこにステージングされていない限り)。この代入がクラス用である場合、たとえば 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
「複雑な」objects
はループで処理されます。例:[a, b, {c, r…}, d], [a, …, {b, r…}, c, d]
loopObjects = (objs, vvar, vvarTxt) =>
for obj, i in objs
Elision
はスキップできます。
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
「単純な」objects
は分割して配列にコンパイルできます。[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
objects
内に 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
objects
をスライスまたはスプライスします。
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
objects
内に 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()
関数定義。これは新しいスコープを作成する唯一のノードです。関数本体の内容をウォークスルーする目的では、コードには *子* がありません。それらは内部スコープ内にあります。
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
このパラメータのコンパイルは、スコープ名トラッキングに追加する名前を取得するためだけに行われます。ここでコンパイルされた出力は最終的な出力のために保持されないため、このコンパイルにコメントを含めないでください。そうすることで、このパラメータがコンパイルされる「実際」の時間にコメントが出力されます。
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
名前付き関数を生成することはないため、id
を null
として指定します。これは、匿名関数式/アロー関数の Babel AST と一致します。
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 ノードを新しいノードに置き換えることによって、パラメータの名前を変更します。これは、オブジェクトの分割代入のソースが変更されないようにする必要があります。
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()
チェーンでは、チェーン式がラップされているため、裸のオブジェクトリテラルを括弧で囲む必要はありません。
@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
単項 **演算子** をコンパイルします。
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
と比較する場合は、明示的に緩やかな等価性 (==
) を使用したいと考えています。そのため、存在チェックはほぼ truthiness のチェックに対応します。既存のコードが大量に壊れてしまうため、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
の後にブロックコメントが続くものを囲んでいる場合は、括弧を出力します(言い換えれば、これらの冗長な括弧を最適化して削除しないでください)。これは、コメントベースの型注釈が続く識別子を JavaScript のラベルと区別するために、Flow が特定の状況で括弧を必要とするためです。
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* ループの代替は、配列とオブジェクトの comprehensions(内包表記)であり、ここで *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
値のうち遅い方になります。
最初のノードのロケーションデータを2番目のノードの開始または終了ロケーションデータのみで拡張する場合は、justLeading
または justEnding
オプションを渡します。たとえば、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
値のうち遅い方になります。
最初のノードのロケーションデータを2番目のノードの開始または終了ロケーションデータのみで拡張する場合は、justLeading
または justEnding
オプションを渡します。たとえば、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
]