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,
addLocationDataFn, locationDataToString, throwSyntaxError} = require './helpers'
パーサーに必要な関数
exports.extend = extend
exports.addLocationDataFn = addLocationDataFn
カスタマイズを必要としないノードの定数関数。
YES = -> yes
NO = -> no
THIS = -> this
NEGATE = -> @negated = not @negated; this
以下に定義されているさまざまなノードはすべて、CodeFragment オブジェクトのコレクションにコンパイルされます。 CodeFragment は、生成されたコードのブロックと、コードのソースファイル内の位置です。 CodeFragment は、すべての CodeFragment の code
スニペットを順番に連結するだけで、動作するコードに組み立てることができます。
exports.CodeFragment = class CodeFragment
constructor: (parent, code) ->
@code = "#{code}"
@locationData = parent?.locationData
@type = parent?.constructor?.name or 'unknown'
toString: ->
"#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
CodeFragment の配列を文字列に変換します。
fragmentsToText = (fragments) ->
(fragment.code for fragment in fragments).join('')
Base は、構文ツリーのすべてのノードの抽象基底クラスです。各サブクラスは、そのノードのコード生成を実行する compileNode
メソッドを実装します。ノードを JavaScript にコンパイルするには、compile
を呼び出します。これは、生成されたコードをクロージャでラップする必要がある時期を認識するために、compileNode
をいくつかの一般的な追加のスマートさでラップします。オプションハッシュが渡され、ツリーの上位からの環境に関する情報(周囲の関数によって戻り値が要求されているかどうかなど)、現在のスコープに関する情報、インデントレベルなどの情報が複製されます。
exports.Base = class Base
compile: (o, lvl) ->
fragmentsToText @compileToFragments o, lvl
このノードをコンパイルする前にクロージャでラップする必要があるか、直接コンパイルする必要があるかを判断するための共通ロジック。このノードが *ステートメント* であり、*pureStatement* ではなく、ブロックの最上位レベルではなく(不要になります)、結果を返すようにまだ要求されていない場合(ステートメントは結果を返す方法を知っているため)、ラップする必要があります。
compileToFragments: (o, lvl) ->
o = extend {}, o
o.level = lvl if lvl
node = @unfoldSoak(o) or this
node.tab = o.indent
if o.level is LEVEL_TOP or not node.isStatement(o)
node.compileNode o
else
node.compileClosure o
クロージャラッピングによって式に変換されたステートメントは、親クロージャとスコープオブジェクトを共有して、予期されるレキシカルスコープを保持します。
compileClosure: (o) ->
if jumpNode = @jumps()
jumpNode.error 'cannot use a pure statement in an expression'
o.sharedScope = yes
func = new Code [], Block.wrap [this]
args = []
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
if func.isGenerator or func.base?.isGenerator
parts.unshift @makeCode "(yield* "
parts.push @makeCode ")"
parts
コード生成が複雑な式の結果を複数の場所で使用する必要がある場合は、一時変数に割り当てることで、式が一度だけ評価されるようにします。レベルを渡してプリコンパイルします。
level
が渡された場合、[val, ref]
を返します。ここで、val
はコンパイルされた値、ref
はコンパイルされた参照です。 level
が渡されない場合、これは [val, ref]
を返します。ここで、2つの値はコンパイルされていない生のノードです。
cache: (o, level, isComplex) ->
complex = if isComplex? then isComplex this else @isComplex()
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]
cacheToCodeFragments: (cacheValues) ->
[fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
現在のノードの結果を返すノードを構築します。これは、多くのステートメントノード(If、For など)のよりスマートな動作のためにオーバーライドされることに注意してください…
makeReturn: (res) ->
me = @unwrapAll()
if res
new Call new Literal("#{res}.push"), [me]
else
new Return me
このノード、またはその子に、特定の種類のノードが含まれていますか? *children* ノードを再帰的にトラバースし、pred
を検証する最初のノードを返します。それ以外の場合は undefined を返します。 contains
はスコープの境界を越えません。
contains: (pred) ->
node = undefined
@traverseChildren no, (n) ->
if pred n
node = n
return no
node
ノードリストの最後のコメント以外のノードを取り出します。
lastNonComment: (list) ->
i = list.length
return list[i] while i-- when list[i] not instanceof Comment
null
パースツリーを検査するためのノードの toString
表現。これは coffee --nodes
が出力するものです。
toString: (idt = '', name = @constructor.name) ->
tree = '\n' + idt + name
tree += '?' if @soak
@eachChild (node) -> tree += node.toString idt + TAB
tree
各子を関数に渡し、関数が 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
invert: ->
new Op '!', this
unwrapAll: ->
node = this
continue until node is node = node.unwrap()
node
共通のノードプロパティとメソッドのデフォルト実装。ノードは、必要に応じて、カスタムロジックでこれらをオーバーライドします。
children: []
isStatement : NO
jumps : NO
isComplex : YES
isChainable : NO
isAssignable : NO
isNumber : NO
unwrap : THIS
unfoldSoak : NO
このノードは特定の変数を割り当てるために使用されていますか?
assigns: NO
このノードとすべての子孫について、ロケーションデータがまだ設定されていない場合は、ロケーションデータを locationData
に設定します。
updateLocationDataIfMissing: (locationData) ->
return this if @locationData
@locationData = locationData
@eachChild (child) ->
child.updateLocationDataIfMissing locationData
このノードの場所に関連付けられた SyntaxError をスローします。
error: (message) ->
throwSyntaxError message, @locationData
makeCode: (code) ->
new CodeFragment this, code
wrapInBraces: (fragments) ->
[].concat @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
ブロックは、インデントされたコードブロックの本体を形成する式リストです。関数の実装、if
、switch
、または try
の句などです…
exports.Block = class Block extends Base
constructor: (nodes) ->
@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: (res) ->
len = @expressions.length
while len--
expr = @expressions[len]
if expr not instanceof Comment
@expressions[len] = expr.makeReturn res
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
this
Block は、ルートとして使用できる唯一のノードです。
compileToFragments: (o = {}, level) ->
if o.scope then super o, level else @compileRoot o
Block 本体内のすべての式をコンパイルします. 結果を返す必要があり、それが式の場合は、単に返します。ステートメントの場合は、ステートメントにそうするように依頼します。
compileNode: (o) ->
@tab = o.indent
top = o.level is LEVEL_TOP
compiledNodes = []
for node, index in @expressions
node = node.unwrapAll()
node = (node.unfoldSoak(o) or node)
if node instanceof Block
これは入れ子になったブロックです。ここでは、新しいスコープで囲むなど、特別なことは何もしていません。このブロックのステートメントを独自のステートメントと一緒にコンパイルするだけです
compiledNodes.push node.compileNode o
else if top
node.front = true
fragments = node.compileToFragments o
unless node.isStatement o
fragments.unshift @makeCode "#{@tab}"
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 @wrapInBraces answer else answer
たまたま最上位レベルの **Block** である場合は、要求されない限り、すべてを安全クロージャでラップします。そもそも生成しない方がよいでしょうが、今のところ、明らかな二重括弧をクリーンアップしてください.
compileRoot: (o) ->
o.indent = if o.bare then '' else TAB
o.level = LEVEL_TOP
@spaced = yes
o.scope = new Scope null, this, null, o.referencedVars ? []
ルートスコープで指定されたローカル変数をパラメータとしてマークして、このブロックで宣言されないようにします.
o.scope.parameter name for name in o.locals or []
prelude = []
unless o.bare
preludeExps = for exp, i in @expressions
break unless exp.unwrap() instanceof Comment
exp
rest = @expressions[preludeExps.length...]
@expressions = preludeExps
if preludeExps.length
prelude = @compileNode merge(o, indent: '')
prelude.push @makeCode "\n"
@expressions = rest
fragments = @compileWithDeclarations o
return fragments if o.bare
[].concat prelude, @makeCode("(function() {\n"), fragments, @makeCode("\n}).call(this);\n")
すべての内部変数の宣言が先頭にプッシュされた状態で、関数の内容の式本体をコンパイルします.
compileWithDeclarations: (o) ->
fragments = []
post = []
for exp, i in @expressions
exp = exp.unwrap()
break unless exp instanceof Comment or 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
fragments.push @makeCode scope.declaredVariables().join(', ')
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
指定されたノードを **Block** としてラップします。ただし、すでに **Block** である場合は除きます.
@wrap: (nodes) ->
return nodes[0] if nodes.length is 1 and nodes[0] instanceof Block
new Block nodes
Literal
は、文字列、数値、true
、false
、null
など、変換なしで JavaScript に直接渡すことができる静的値の基底クラスです…
exports.Literal = class Literal extends Base
constructor: (@value) ->
isComplex: NO
assigns: (name) ->
name is @value
compileNode: (o) ->
[@makeCode @value]
toString: ->
" #{if @isStatement() then super else @constructor.name}: #{@value}"
exports.NumberLiteral = class NumberLiteral extends Literal
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
compileNode: ->
[@makeCode '2e308']
exports.NaNLiteral = class NaNLiteral extends NumberLiteral
constructor: ->
super 'NaN'
compileNode: (o) ->
code = [@makeCode '0/0']
if o.level >= LEVEL_OP then @wrapInBraces code else code
exports.StringLiteral = class StringLiteral extends Literal
exports.RegexLiteral = class RegexLiteral extends Literal
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: YES
exports.PropertyName = class PropertyName extends Literal
isAssignable: YES
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};"]
exports.ThisLiteral = class ThisLiteral extends Literal
constructor: ->
super 'this'
compileNode: (o) ->
code = if o.scope.method?.bound then o.scope.method.context else @value
[@makeCode code]
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
super 'undefined'
compileNode: (o) ->
[@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
super 'null'
exports.BooleanLiteral = class BooleanLiteral extends Literal
return
は *pureStatement* です。クロージャでラップしても意味がありません。
exports.Return = class Return extends Base
constructor: (@expression) ->
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 回呼び出すと、異なる結果が返される場合があります!
answer.push @makeCode @tab + "return#{if @expression then " " else ""}"
if @expression
answer = answer.concat @expression.compileToFragments o, LEVEL_PAREN
answer.push @makeCode ";"
return answer
yield return
は、関数をジェネレーターに変えることを除いて、return
とまったく同じように機能します.
exports.YieldReturn = class YieldReturn extends Return
compileNode: (o) ->
unless o.scope.parent?
@error 'yield can only occur inside functions'
super
値、変数、リテラル、または括弧で囲まれた、インデックス付きまたはドット付き、またはバニラ。
exports.Value = class Value extends Base
constructor: (base, props, tag) ->
return base if not props and base instanceof Value
@base = base
@properties = props or []
@[tag] = true if tag
return this
children: ['base', 'properties']
プロパティ(または *プロパティ*)Access
をリストに追加します。
add: (props) ->
@properties = @properties.concat props
this
hasProperties: ->
[email protected]
bareLiteral: (type) ->
not @properties.length and @base instanceof type
他のノードの利益のためのいくつかのブール値チェック。
isArray : -> @bareLiteral(Arr)
isRange : -> @bareLiteral(Range)
isComplex : -> @hasProperties() or @base.isComplex()
isAssignable : -> @hasProperties() or @base.isAssignable()
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
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
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)
isSplice: ->
[..., lastProp] = @properties
lastProp instanceof Slice
looksStatic: (className) ->
@base.value is className and @properties.length is 1 and
@properties[0].name?.value isnt 'prototype'
添付されたプロパティがない場合、値は内部ノードとしてアンラップできます。
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.isComplex() and not name?.isComplex()
return [this, this] # `a` `a.b`
base = new Value @base, @properties[...-1]
if base.isComplex() # `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.isComplex() # `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
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 =>
if ifn = @base.unfoldSoak o
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.isComplex()
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
CoffeeScript は、ブロックコメントを JavaScript ブロックコメントとして同じ位置に渡します。
exports.Comment = class Comment extends Base
constructor: (@comment) ->
isStatement: YES
makeReturn: THIS
compileNode: (o, level) ->
comment = @comment.replace /^(\s*)#(?=\s)/gm, "$1 *"
code = "/*#{multident comment, @tab}#{if '\n' in comment then "\n#{@tab}" else ''} */"
code = o.indent + code if (level or o.level) is LEVEL_TOP
[@makeCode("\n"), @makeCode(code)]
関数呼び出しのノード。
exports.Call = class Call extends Base
constructor: (@variable, @args = [], @soak) ->
@isNew = false
if @variable instanceof Value and @variable.isNotCallable()
@variable.error "literal is not a function"
children: ['variable', 'args']
場所を設定する場合、左側に新しく発見された `new` 演算子を考慮して開始場所を更新する必要がある場合があります。これは左側の範囲を拡張しますが、右側の範囲は拡張しません.
updateLocationDataIfMissing: (locationData) ->
if @locationData and @needsUpdatedStartLocation
@locationData.first_line = locationData.first_line
@locationData.first_column = locationData.first_column
base = @variable?.base or @variable
if base.needsUpdatedStartLocation
@variable.locationData.first_line = locationData.first_line
@variable.locationData.first_column = locationData.first_column
base.updateLocationDataIfMissing locationData
delete @needsUpdatedStartLocation
super
この呼び出しに、新しいインスタンスを作成するタグを付けます.
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 this instanceof SuperCall
left = new Literal @superReference o
rite = new Value left
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) ->
@variable?.front = @front
compiledArray = Splat.compileSplattedArray o, @args, true
if compiledArray.length
return @compileSplat o, compiledArray
compiledArgs = []
for arg, argIndex in @args
if argIndex then compiledArgs.push @makeCode ", "
compiledArgs.push (arg.compileToFragments o, LEVEL_LIST)...
fragments = []
if this instanceof SuperCall
preface = @superReference(o) + ".call(#{@superThis(o)}"
if compiledArgs.length then preface += ", "
fragments.push @makeCode preface
else
if @isNew then fragments.push @makeCode 'new '
fragments.push @variable.compileToFragments(o, LEVEL_ACCESS)...
fragments.push @makeCode "("
fragments.push compiledArgs...
fragments.push @makeCode ")"
fragments
splat を使用して関数を呼び出すと、JavaScript の .apply()
呼び出しに変換されて、引数の配列を渡すことができます。コンストラクターの場合は、本当に面倒になります。可変長引数を渡せるように、内部コンストラクターを挿入する必要があります.
splatArgs は、'apply' に入れる CodeFragment の配列です.
compileSplat: (o, splatArgs) ->
if this instanceof SuperCall
return [].concat @makeCode("#{ @superReference o }.apply(#{@superThis(o)}, "),
splatArgs, @makeCode(")")
if @isNew
idt = @tab + TAB
return [].concat @makeCode("""
(function(func, args, ctor) {
#{idt}ctor.prototype = func.prototype;
#{idt}var child = new ctor, result = func.apply(child, args);
#{idt}return Object(result) === result ? result : child;
#{@tab}})("""),
(@variable.compileToFragments o, LEVEL_LIST),
@makeCode(", "), splatArgs, @makeCode(", function(){})")
answer = []
base = new Value @variable
if (name = base.properties.pop()) and base.isComplex()
ref = o.scope.freeVariable 'ref'
answer = answer.concat @makeCode("(#{ref} = "),
(base.compileToFragments o, LEVEL_LIST),
@makeCode(")"),
name.compileToFragments(o)
else
fun = base.compileToFragments o, LEVEL_ACCESS
fun = @wrapInBraces fun if SIMPLENUM.test fragmentsToText fun
if name
ref = fragmentsToText fun
fun.push (name.compileToFragments o)...
else
ref = 'null'
answer = answer.concat fun
answer = answer.concat @makeCode(".apply(#{ref}, "), splatArgs, @makeCode(")")
super()
呼び出しを、同じ名前のプロトタイプの関数に対する呼び出しに変換します.
exports.SuperCall = class SuperCall extends Call
constructor: (args) ->
super null, args ? [new Splat new IdentifierLiteral 'arguments']
括弧と引数のないベア super
呼び出しを認識できるようにします.
@isBare = args?
現在のメソッドのスーパー クラスの実装への参照を取得します.
superReference: (o) ->
method = o.scope.namedMethod()
if method?.klass
{klass, name, variable} = method
if klass.isComplex()
bref = new IdentifierLiteral o.scope.parent.freeVariable 'base'
base = new Value new Parens new Assign bref, klass
variable.base = base
variable.properties.splice 0, klass.properties.length
if name.isComplex() or (name instanceof Index and name.index.isAssignable())
nref = new IdentifierLiteral o.scope.parent.freeVariable 'name'
name = new Index new Assign nref, name.index
variable.properties.pop()
variable.properties.push name
accesses = [new Access new PropertyName '__super__']
accesses.push new Access new PropertyName 'constructor' if method.static
accesses.push if nref? then new Index nref else name
(new Value bref ? klass, accesses).compile o
else if method?.ctor
"#{method.name}.__super__.constructor"
else
@error 'cannot call super outside of an instance method.'
super
呼び出しに適切な this
値.
superThis : (o) ->
method = o.scope.method
(method and not method.klass and method.context) or "this"
補間付きの正規表現は、実際には、内部に StringWithInterpolations
を持つ Call
(正確には RegExp()
呼び出し)のバリエーションにすぎません.
exports.RegexWithInterpolations = class RegexWithInterpolations extends Call
constructor: (args = []) ->
super (new Value new IdentifierLiteral 'RegExp'), args, false
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
constructor: (variable, arg, soak) ->
arg = new StringWithInterpolations Block.wrap([ new Value arg ]) if arg instanceof StringLiteral
super variable, [ arg ], soak
compileNode: (o) ->
StringWithInterpolations
に ES2015 としてコンパイルするかどうかを指示します。 CoffeeScript 2 では削除されます.
o.inTaggedTemplateCall = yes
@variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
オブジェクトのプロトタイプを祖先オブジェクトで拡張するノード。Closure Library の goog.inherits
の後。
exports.Extends = class Extends extends Base
constructor: (@child, @parent) ->
children: ['child', 'parent']
1 つのコンストラクターを別のコンストラクターのプロトタイプ チェーンにフックします.
compileToFragments: (o) ->
new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o
値のプロパティへの .
アクセス、またはオブジェクトのプロトタイプへのアクセスのための ::
ショートハンド。
exports.Access = class Access extends Base
constructor: (@name, tag) ->
@soak = tag is 'soak'
children: ['name']
compileToFragments: (o) ->
name = @name.compileToFragments o
node = @name.unwrap()
if node instanceof PropertyName
if node.value in JS_FORBIDDEN
[@makeCode('["'), name..., @makeCode('"]')]
else
[@makeCode('.'), name...]
else
[@makeCode('['), name..., @makeCode(']')]
isComplex: NO
配列またはオブジェクトへの [ ... ]
インデックス付きアクセス。
exports.Index = class Index extends Base
constructor: (@index) ->
children: ['index']
compileToFragments: (o) ->
[].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
isComplex: ->
@index.isComplex()
範囲リテラル。範囲は、配列の部分(スライス)を抽出したり、内包表記の範囲を指定したり、値として実行時に対応する整数の配列に展開したりするために使用できます。
exports.Range = class Range extends Base
children: ['from', 'to']
constructor: (@from, @to, tag) ->
@exclusive = tag is 'exclusive'
@equals = if @exclusive then '' else '='
範囲のソース変数(開始位置と終了位置)をコンパイルします。ただし、二重評価を避けるためにキャッシュする必要がある場合のみです。
compileVariables: (o) ->
o = merge o, top: true
isComplex = del o, 'isComplex'
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, isComplex
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, isComplex
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, isComplex if step = del o, 'step'
@fromNum = if @from.isNumber() then Number @fromVar else null
@toNum = if @to.isNumber() then Number @toVar else null
@stepNum = if step?.isNumber() then Number @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 = "#{idx} = #{@fromC}"
varPart += ", #{@toC}" if @toC isnt @toVar
varPart += ", #{@step}" if @step isnt @stepVar
[lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
条件を生成します。
condPart = if @stepNum?
if @stepNum > 0 then "#{lt} #{@toVar}" else "#{gt} #{@toVar}"
else if known
[from, to] = [@fromNum, @toNum]
if from <= to then "#{lt} #{to}" else "#{gt} #{to}"
else
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
"#{cond} ? #{lt} #{@toVar} : #{gt} #{@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
result = o.scope.freeVariable 'results'
pre = "\n#{idt}#{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 ? ''})"]
配列スライスリテラル。 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
fromCompiled = from and from.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
TODO: jwalton - これを 'if' に移動しますか?
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 '' })"]
オブジェクトリテラル、何も派手なものはありません。
exports.Obj = class Obj extends Base
constructor: (props, @generated = false) ->
@objects = @properties = props or []
children: ['properties']
compileNode: (o) ->
props = @properties
if @generated
for node in props when node instanceof Value
node.error 'cannot have an implicit value in an implicit object'
break for prop, dynamicIndex in props when (prop.variable or prop).base instanceof Parens
hasDynamic = dynamicIndex < props.length
idt = o.indent += TAB
lastNoncom = @lastNonComment @properties
answer = []
if hasDynamic
oref = o.scope.freeVariable 'obj'
answer.push @makeCode "(\n#{idt}#{oref} = "
answer.push @makeCode "{#{if props.length is 0 or dynamicIndex is 0 then '}' else '\n'}"
for prop, i in props
if i is dynamicIndex
answer.push @makeCode "\n#{idt}}" unless i is 0
answer.push @makeCode ',\n'
join = if i is props.length - 1 or i is dynamicIndex - 1
''
else if prop is lastNoncom or prop instanceof Comment
'\n'
else
',\n'
indent = if prop instanceof Comment then '' else idt
indent += TAB if hasDynamic and i < dynamicIndex
if prop instanceof Assign
if prop.context isnt 'object'
prop.operatorToken.error "unexpected #{prop.operatorToken.value}"
if prop.variable instanceof Value and prop.variable.hasProperties()
prop.variable.error 'invalid object key'
if prop instanceof Value and prop.this
prop = new Assign prop.properties[0].name, prop, 'object'
if prop not instanceof Comment
if i < dynamicIndex
if prop not instanceof Assign
prop = new Assign prop, prop, 'object'
else
if prop instanceof Assign
key = prop.variable
value = prop.value
else
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign (new Value (new IdentifierLiteral oref), [new Access key]), value
if indent then answer.push @makeCode indent
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
if hasDynamic
answer.push @makeCode ",\n#{idt}#{oref}\n#{@tab})"
else
answer.push @makeCode "\n#{@tab}}" unless props.length is 0
if @front and not hasDynamic then @wrapInBraces answer else answer
assigns: (name) ->
for prop in @properties when prop.assigns name then return yes
no
配列リテラル。
exports.Arr = class Arr extends Base
constructor: (objs) ->
@objects = objs or []
children: ['objects']
compileNode: (o) ->
return [@makeCode '[]'] unless @objects.length
o.indent += TAB
answer = Splat.compileSplattedArray o, @objects
return answer if answer.length
answer = []
compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
for fragments, index in compiledObjs
if index
answer.push @makeCode ", "
answer.push fragments...
if fragmentsToText(answer).indexOf('\n') >= 0
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
else
answer.unshift @makeCode "["
answer.push @makeCode "]"
answer
assigns: (name) ->
for obj in @objects when obj.assigns name then return yes
no
CoffeeScript クラス定義。Class を名前、オプションのスーパー クラス、およびプロトタイプ プロパティ割り当てのリストで初期化します.
exports.Class = class Class extends Base
constructor: (@variable, @parent, @body = new Block) ->
@boundFuncs = []
@body.classBody = yes
children: ['variable', 'parent', 'body']
defaultClassVariableName: '_Class'
このクラスのコンストラクター関数の適切な名前を考え出します.
determineName: ->
return @defaultClassVariableName 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 @defaultClassVariableName
name = node.value
unless tail
message = isUnassignable name
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
クラス定義内のすべての `this` 参照とバインドされた関数について、`this` は構築されているクラスです.
setContext: (name) ->
@body.traverseChildren false, (node) ->
return false if node.classBody
if node instanceof ThisLiteral
node.value = name
else if node instanceof Code
node.context = name if node.bound
インスタンスにバインドされたすべての関数がコンストラクターでプロキシされるようにします.
addBoundFunctions: (o) ->
for bvar in @boundFuncs
lhs = (new Value (new ThisLiteral), [new Access bvar]).compile o
@ctor.body.unshift new Literal "#{lhs} = #{utility 'bind', o}(#{lhs}, this)"
return
トップレベルオブジェクトのプロパティをクラスのプロトタイププロパティとしてマージします.
addProperties: (node, name, o) ->
props = node.base.properties[..]
exprs = while assign = props.shift()
if assign instanceof Assign
base = assign.variable.base
delete assign.context
func = assign.value
if base.value is 'constructor'
if @ctor
assign.error 'cannot define more than one constructor in a class'
if func.bound
assign.error 'cannot define a constructor as a bound function'
if func instanceof Code
assign = @ctor = func
else
@externalCtor = o.classScope.freeVariable 'ctor'
assign = new Assign new IdentifierLiteral(@externalCtor), func
else
if assign.variable.this
func.static = yes
else
acc = if base.isComplex() then new Index base else new Access base
assign.variable = new Value(new IdentifierLiteral(name), [(new Access new PropertyName 'prototype'), acc])
if func instanceof Code and func.bound
@boundFuncs.push base
func.bound = no
assign
compact exprs
クラスの本体を調べて、変換されるプロトタイププロパティを探し、静的割り当てにタグを付けます.
walkBody: (name, o) ->
@traverseChildren false, (child) =>
cont = true
return false if child instanceof Class
if child instanceof Block
for node, i in exps = child.expressions
if node instanceof Assign and node.variable.looksStatic name
node.value.static = yes
else if node instanceof Value and node.isObject(true)
cont = false
exps[i] = @addProperties node, name, o
child.expressions = exps = flatten exps
cont and child not instanceof Class
use strict
(その他ディレクティブ) は、関数本体の最初の式文でなければなりません。このメソッドは、プロローグがconstructor
の上に正しく配置されるようにします。
hoistDirectivePrologue: ->
index = 0
{expressions} = @body
++index while (node = expressions[index]) and node instanceof Comment or
node instanceof Value and node.isString()
@directives = expressions.splice 0, index
クラスにコンストラクタが定義され、正しく設定されていることを確認してください。
ensureConstructor: (name) ->
if not @ctor
@ctor = new Code
if @externalCtor
@ctor.body.push new Literal "#{@externalCtor}.apply(this, arguments)"
else if @parent
@ctor.body.push new Literal "#{name}.__super__.constructor.apply(this, arguments)"
@ctor.body.makeReturn()
@body.expressions.unshift @ctor
@ctor.ctor = @ctor.name = name
@ctor.klass = null
@ctor.noReturn = yes
JavaScript文字列を直接生成する代わりに、同等の構文ツリーを構築し、それを部分的にコンパイルします。コンストラクタ、プロパティの割り当て、および継承がどのように構築されるかを以下に示します。
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"
name = @determineName()
lname = new IdentifierLiteral name
func = new Code [], Block.wrap [@body]
args = []
o.classScope = func.makeScope o.scope
@hoistDirectivePrologue()
@setContext name
@walkBody name, o
@ensureConstructor name
@addBoundFunctions o
@body.spaced = yes
@body.expressions.push lname
if @parent
superClass = new IdentifierLiteral o.classScope.freeVariable 'superClass', reserve: no
@body.expressions.unshift new Extends lname, superClass
func.params.push new Param superClass
args.push @parent
@body.expressions.unshift @directives...
klass = new Parens new Call func, args
klass = new Assign @variable, klass, null, { @moduleDeclaration } if @variable
klass.compileToFragments o
exports.ModuleDeclaration = class ModuleDeclaration extends Base
constructor: (@clause, @source) ->
@checkSource()
children: ['clause', 'source']
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) ->
if o.indent.length isnt 0
@error "#{moduleDeclarationType} statements must be at top-level scope"
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
code.push @makeCode ';'
code
exports.ImportClause = class ImportClause extends Base
constructor: (@defaultBinding, @namedImports) ->
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
exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'export'
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)
匿名クラスのエクスポートを防止します。エクスポートされるすべてのメンバーには名前を付ける必要があります。
if @clause instanceof Class and not @clause.variable
@clause.error 'anonymous classes cannot be exported'
ES2015のclass
キーワードがサポートされている場合は、ここにvar
を追加しないでください。
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
code.push @makeCode " from #{@source.value}" if @source?.value?
code.push @makeCode ';'
code
exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
constructor: (@specifiers) ->
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
exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
exports.ModuleSpecifier = class ModuleSpecifier extends Base
constructor: (@original, @alias, @moduleDeclarationType) ->
ローカルスコープに入る変数の名前
@identifier = if @alias? then @alias.value else @original.value
children: ['original', 'alias']
compileNode: (o) ->
o.scope.find @identifier, @moduleDeclarationType
code = []
code.push @makeCode @original.value
code.push @makeCode " as #{@alias.value}" if @alias?
code
exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
constructor: (imported, local) ->
super imported, local, 'import'
compileNode: (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
exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
constructor: (local, exported) ->
super local, exported, 'export'
代入は、ローカル変数に値を代入したり、オブジェクトのプロパティ(オブジェクトリテラル内を含む)を設定したりするために使用されます。
exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) ->
{@param, @subpattern, @operatorToken, @moduleDeclaration} = options
children: ['variable', 'value']
isStatement: (o) ->
o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
checkAssignability: (o, varBase) ->
if Object::hasOwnProperty.call(o.scope.positions, varBase.value) and
o.scope.variables[o.scope.positions[varBase.value]].type 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'
代入をコンパイルし、必要に応じてcompilePatternMatch
またはcompileSplice
に委任します。正しい内部参照のために、代入されたベースオブジェクトの名前を追跡します。現在のスコープ内で変数がまだ確認されていない場合は、宣言します。
compileNode: (o) ->
if isValue = @variable instanceof Value
return @compilePatternMatch o if @variable.isArray() or @variable.isObject()
return @compileSplice o if @variable.isSplice()
return @compileConditional o if @context in ['||=', '&&=', '?=']
return @compileSpecialMath o if @context in ['**=', '//=', '%%=']
if @value instanceof Code
if @value.static
@value.klass = @variable.base
@value.name = @variable.properties[0]
@value.variable = @variable
else if @variable.properties?.length >= 2
[properties..., prototype, name] = @variable.properties
if prototype.name?.value is 'prototype'
@value.klass = new Value @variable.base, properties
@value.name = name
@value.variable = @variable
unless @context
varBase = @variable.unwrapAll()
unless varBase.isAssignable()
@variable.error "'#{@variable.compile o}' can't be assigned"
unless varBase.hasProperties?()
moduleDeclaration
は'import'
または'export'
です。
if @moduleDeclaration
@checkAssignability o, varBase
o.scope.add varBase.value, @moduleDeclaration
else if @param
o.scope.add varBase.value, 'var'
else
@checkAssignability o, varBase
o.scope.find varBase.value
val = @value.compileToFragments o, LEVEL_LIST
@variable.front = true if isValue and @variable.base instanceof Obj
compiledName = @variable.compileToFragments o, LEVEL_LIST
if @context is 'object'
if fragmentsToText(compiledName) in JS_FORBIDDEN
compiledName.unshift @makeCode '"'
compiledName.push @makeCode '"'
return compiledName.concat @makeCode(": "), val
answer = compiledName.concat @makeCode(" #{ @context or '=' } "), val
if o.level <= LEVEL_LIST then answer else @wrapInBraces answer
配列またはオブジェクトリテラルを値に代入する場合の、再帰的なパターンマッチングの簡単な実装。内部名を割り当てるために、それらのプロパティを覗き見ます。
compilePatternMatch: (o) ->
top = o.level is LEVEL_TOP
{value} = this
{objects} = @variable.base
unless olen = objects.length
code = value.compileToFragments o
return if o.level >= LEVEL_OP then @wrapInBraces code else code
[obj] = objects
if olen is 1 and obj instanceof Expansion
obj.error 'Destructuring assignment has no target'
isObject = @variable.isObject()
if top and olen is 1 and obj not instanceof Splat
選択するプロパティが1つだけの場合は、値から直接プロパティを選択します(値を変数にキャッシュする必要はありません)。
defaultValue = null
if obj instanceof Assign and obj.context is 'object'
通常のオブジェクトパターンマッチ。
{variable: {base: idx}, value: obj} = obj
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
else
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
idx = if isObject
省略形の{a, b, @c} = val
パターンマッチ。
if obj.this
obj.properties[0].name
else
new PropertyName obj.unwrap().value
else
通常の配列パターンマッチ。
new NumberLiteral 0
acc = idx.unwrap() instanceof PropertyName
value = new Value value
value.properties.push new (if acc then Access else Index) idx
message = isUnassignable obj.unwrap().value
obj.error message if message
value = new Op '?', value, defaultValue if defaultValue
return new Assign(obj, value, null, param: @param).compileToFragments o, LEVEL_TOP
vvar = value.compileToFragments o, LEVEL_LIST
vvarText = fragmentsToText vvar
assigns = []
expandedIdx = false
vvarが単純な変数でない場合は、単純な変数にします。
if value.unwrap() not instanceof IdentifierLiteral or @variable.assigns(vvarText)
assigns.push [@makeCode("#{ ref = o.scope.freeVariable 'ref' } = "), vvar...]
vvar = [@makeCode ref]
vvarText = ref
for obj, i in objects
idx = i
if not expandedIdx and obj instanceof Splat
name = obj.name.unwrap().value
obj = obj.unwrap()
val = "#{olen} <= #{vvarText}.length ? #{ utility 'slice', o }.call(#{vvarText}, #{i}"
if rest = olen - i - 1
ivar = o.scope.freeVariable 'i', single: true
val += ", #{ivar} = #{vvarText}.length - #{rest}) : (#{ivar} = #{i}, [])"
else
val += ") : []"
val = new Literal val
expandedIdx = "#{ivar}++"
else if not expandedIdx and obj instanceof Expansion
if rest = olen - i - 1
if rest is 1
expandedIdx = "#{vvarText}.length - 1"
else
ivar = o.scope.freeVariable 'i', single: true
val = new Literal "#{ivar} = #{vvarText}.length - #{rest}"
expandedIdx = "#{ivar}++"
assigns.push val.compileToFragments o, LEVEL_LIST
continue
else
if obj instanceof Splat or obj instanceof Expansion
obj.error "multiple splats/expansions are disallowed in an assignment"
defaultValue = null
if obj instanceof Assign and obj.context is 'object'
通常のオブジェクトパターンマッチ。
{variable: {base: idx}, value: obj} = obj
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
else
if obj instanceof Assign
defaultValue = obj.value
obj = obj.variable
idx = if isObject
省略形の{a, b, @c} = val
パターンマッチ。
if obj.this
obj.properties[0].name
else
new PropertyName obj.unwrap().value
else
通常の配列パターンマッチ。
new Literal expandedIdx or idx
name = obj.unwrap().value
acc = idx.unwrap() instanceof PropertyName
val = new Value new Literal(vvarText), [new (if acc then Access else Index) idx]
val = new Op '?', val, defaultValue if defaultValue
if name?
message = isUnassignable name
obj.error message if message
assigns.push new Assign(obj, val, null, param: @param, subpattern: yes).compileToFragments o, LEVEL_LIST
assigns.push vvar unless top or @subpattern
fragments = @joinFragmentArrays assigns, ', '
if o.level < LEVEL_LIST then fragments else @wrapInBraces fragments
条件付き代入をコンパイルする場合は、オペランドを複数回参照する必要がある場合でも、オペランドが1回だけ評価されるように注意してください。
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
@variable.error "the variable \"#{left.base.value}\" can't be assigned with #{@context} because it has not been declared before"
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 @wrapInBraces 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()
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("[].splice.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
if o.level > LEVEL_TOP then @wrapInBraces answer else answer
関数定義。これは、新しいスコープを作成する唯一のノードです。関数本体の内容をウォークスルーする目的で、コードには*子*がありません。子*は内部スコープ内にあります。
exports.Code = class Code extends Base
constructor: (params, body, tag) ->
@params = params or []
@body = body or new Block
@bound = tag is 'boundfunc'
@isGenerator = [email protected] (node) ->
(node instanceof Op and node.isYield()) or node instanceof YieldReturn
children: ['params', 'body']
isStatement: -> !!@ctor
jumps: NO
makeScope: (parentScope) -> new Scope parentScope, @body, this
コンパイルでは、外部スコープとの共有を明示的に要求されない限り、新しいスコープが作成されます。JavaScriptのarguments
オブジェクトを覗き見することで、パラメータリストのスプラットパラメータを処理します。関数が=>
矢印でバインドされている場合、クロージャによって現在のthis
の値を保存するラッパーを生成します。
compileNode: (o) ->
if @bound and o.scope.method?.bound
@context = o.scope.method.context
バインドされた関数を早期に処理します。
if @bound and not @context
@context = '_this'
wrapper = new Code [new Param new IdentifierLiteral @context], new Block [this]
boundfunc = new Call(wrapper, [new ThisLiteral])
boundfunc.updateLocationDataIfMissing @locationData
return boundfunc.compileNode(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
params = []
exprs = []
for param in @params when param not instanceof Expansion
o.scope.parameter param.asReference o
for param in @params when param.splat or param instanceof Expansion
for p in @params when p not instanceof Expansion and p.name.value
o.scope.add p.name.value, 'var', yes
splats = new Assign new Value(new Arr(p.asReference o for p in @params)),
new Value new IdentifierLiteral 'arguments'
break
for param in @params
if param.isComplex()
val = ref = param.asReference o
val = new Op '?', ref, param.value if param.value
exprs.push new Assign new Value(param.name), val, '=', param: yes
else
ref = param
if param.value
lit = new Literal ref.name.value + ' == null'
val = new Assign new Value(param.name), param.value, '='
exprs.push new If lit, val
params.push ref unless splats
wasEmpty = @body.isEmpty()
exprs.unshift splats if splats
@body.expressions.unshift exprs... if exprs.length
for p, i in params
params[i] = p.compileToFragments o
o.scope.parameter fragmentsToText params[i]
uniqs = []
@eachParamName (name, node) ->
node.error "multiple parameters named #{name}" if name in uniqs
uniqs.push name
@body.makeReturn() unless wasEmpty or @noReturn
code = 'function'
code += '*' if @isGenerator
code += ' ' + @name if @ctor
code += '('
answer = [@makeCode(code)]
for p, i in params
if i then answer.push @makeCode ", "
answer.push p...
answer.push @makeCode ') {'
answer = answer.concat(@makeCode("\n"), @body.compileWithDeclarations(o), @makeCode("\n#{@tab}")) unless @body.isEmpty()
answer.push @makeCode '}'
return [@makeCode(@tab), answer...] if @ctor
if @front or (o.level >= LEVEL_ACCESS) then @wrapInBraces answer else answer
eachParamName: (iterator) ->
param.eachName iterator for param in @params
crossScope
がtrue
でない限り、スコープ境界を越えないようにtraverseChildren
メソッドを短絡します。
traverseChildren: (crossScope, func) ->
super(crossScope, func) if crossScope
関数定義のパラメータ。典型的なJavaScriptパラメータに加えて、これらのパラメータは関数のコンテキストに自身をアタッチすることも、スプラットになることもでき、パラメータのグループを配列にまとめます。
exports.Param = class Param extends Base
constructor: (@name, @value, @splat) ->
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
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.isComplex()
node = new IdentifierLiteral o.scope.freeVariable 'arg'
node = new Value node
node = new Splat node if @splat
node.updateLocationDataIfMissing @locationData
@reference = node
isComplex: ->
@name.isComplex()
Param
の名前または名前を反復処理します。ある意味で、分割されたパラメータは複数のJSパラメータを表します。このメソッドを使用すると、それらすべてを反復処理できます。iterator
関数は、iterator(name, node)
として呼び出されます。ここで、name
はパラメータの名前、node
はその名前に対応するASTノードです。
eachName: (iterator, name = @name)->
atParam = (obj) -> iterator "@#{obj.properties[0].name.value}", obj
foo
return iterator name.value, name if name instanceof Literal
@foo
return atParam name if name instanceof Value
for obj in name.objects ? []
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
@eachName iterator, obj.value.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
else iterator obj.base.value, obj.base
else if obj not instanceof Expansion
obj.error "illegal parameter #{obj.compile()}"
return
スプラット。関数の引数、呼び出しの引数、または分割代入の一部として。
exports.Splat = class Splat extends Base
children: ['name']
isAssignable: YES
constructor: (name) ->
@name = if name.compile then name else new Literal name
assigns: (name) ->
@name.assigns name
compileToFragments: (o) ->
@name.compileToFragments o
unwrap: -> @name
スプラットと混合された任意の数の要素を適切な配列に変換するユーティリティ関数。
@compileSplattedArray: (o, list, apply) ->
index = -1
continue while (node = list[++index]) and node not instanceof Splat
return [] if index >= list.length
if list.length is 1
node = list[0]
fragments = node.compileToFragments o, LEVEL_LIST
return fragments if apply
return [].concat node.makeCode("#{ utility 'slice', o }.call("), fragments, node.makeCode(")")
args = list[index..]
for node, i in args
compiledNode = node.compileToFragments o, LEVEL_LIST
args[i] = if node instanceof Splat
then [].concat node.makeCode("#{ utility 'slice', o }.call("), compiledNode, node.makeCode(")")
else [].concat node.makeCode("["), compiledNode, node.makeCode("]")
if index is 0
node = list[0]
concatPart = (node.joinFragmentArrays args[1..], ', ')
return args[0].concat node.makeCode(".concat("), concatPart, node.makeCode(")")
base = (node.compileToFragments o, LEVEL_LIST for node in list[...index])
base = list[0].joinFragmentArrays base, ', '
concatPart = list[index].joinFragmentArrays args, ', '
[..., last] = list
[].concat list[0].makeCode("["), base, list[index].makeCode("].concat("), concatPart, last.makeCode(")")
配列分割(パターンマッチング)またはパラメータリスト内の値をスキップするために使用されます。
exports.Expansion = class Expansion extends Base
isComplex: NO
compileNode: (o) ->
@error 'Expansion must be used inside a destructuring assignment or parameter list'
asReference: (o) ->
this
eachName: (iterator) ->
whileループ。CoffeeScriptによって公開される唯一の種類の低レベルループです。これから、他のすべてのループを作成できます。理解が提供できるよりも柔軟性または速度が必要な場合に役立ちます。
exports.While = class While extends Base
constructor: (condition, options) ->
@condition = if options?.invert then condition.invert() else condition
@guard = options?.guard
children: ['condition', 'guard', 'body']
isStatement: YES
makeReturn: (res) ->
if res
super
else
@returns = not @jumps loop: yes
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 ("), @condition.compileToFragments(o, LEVEL_PAREN),
@makeCode(") {"), body, @makeCode("}")
if @returns
answer.push @makeCode "\n#{@tab}return #{rvar};"
answer
単純な算術演算と論理演算。CoffeeScript演算からJavaScriptの等価物への変換を実行します。
exports.Op = class Op extends Base
constructor: (op, first, second, flip ) ->
return new In first, second if op is 'in'
if op is 'do'
return @generateDo first
if op is 'new'
return first.newInstance() if first instanceof Call and not first.do and not first.isNew
first = new Parens first if first instanceof Code and first.bound or first.do
@operator = CONVERSIONS[op] or op
@first = first
@second = second
@flip = !!flip
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()
isYield: ->
@operator in ['yield', 'yield*']
isUnary: ->
not @second
isComplex: ->
not @isNumber()
Pythonスタイルの比較連鎖は可能ですか?
isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
invert: ->
if @isChainable() and @first.isChainable()
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
compileNode: (o) ->
isChain = @isChainable() and @first.isChainable()
チェーンでは、連鎖された式がラップされているため、裸のオブジェクトリテラルを括弧で囲む必要はありません。
@first.front = @front unless isChain
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
@error 'delete operand may not be argument or var'
if @operator in ['--', '++']
message = isUnassignable @first.unwrapAll().value
@first.error message if message
return @compileYield o if @isYield()
return @compileUnary o if @isUnary()
return @compileChain o if isChain
switch @operator
when '?' then @compileExistence o
when '**' then @compilePower o
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 @wrapInBraces 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)
@wrapInBraces fragments
これが存在代入でない限り、左側の式への参照を保持します。
compileExistence: (o) ->
if @first.isComplex()
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
fst = new Parens new Assign ref, @first
else
fst = @first
ref = fst
new If(new Existence(fst), 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 ['new', 'typeof', 'delete'] or
plusMinus and @first instanceof Op and @first.operator is op
if (plusMinus and @first instanceof Op) or (op is 'new' and @first.isStatement o)
@first = new Parens @first
parts.push @first.compileToFragments o, LEVEL_OP
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compileYield: (o) ->
parts = []
op = @operator
unless o.scope.parent?
@error 'yield can only occur inside functions'
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, ''
compilePower: (o) ->
Math.pow呼び出しを行います。
pow = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'pow']
new Call(pow, [@first, @second]).compileToFragments o
compileFloorDivision: (o) ->
floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
second = if @second.isComplex() 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
exports.In = class In extends Base
constructor: (@object, @array) ->
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
スプラットのない配列リテラルがある場合のみ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 @wrapInBraces 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 @wrapInBraces fragments
toString: (idt) ->
super idt, @constructor.name + if @negated then '!' else ''
典型的な*try/catch/finally*ブロック。
exports.Try = class Try extends Base
constructor: (@attempt, @errorVariable, @recovery, @ensure) ->
children: ['attempt', 'recovery', 'ensure']
isStatement: YES
jumps: (o) -> @attempt.jumps(o) or @recovery?.jumps(o)
makeReturn: (res) ->
@attempt = @attempt .makeReturn res if @attempt
@recovery = @recovery.makeReturn res if @recovery
this
コンパイルは、ほぼ予想どおりです。*finally*句は省略可能ですが、*catch*は省略できません。
compileNode: (o) ->
o.indent += TAB
tryPart = @attempt.compileToFragments o, LEVEL_TOP
catchPart = if @recovery
generatedErrorVariableName = o.scope.freeVariable 'error', reserve: no
placeholder = new IdentifierLiteral generatedErrorVariableName
if @errorVariable
message = isUnassignable @errorVariable.unwrapAll().value
@errorVariable.error message if message
@recovery.unshift new Assign @errorVariable, placeholder
[].concat @makeCode(" catch ("), placeholder.compileToFragments(o), @makeCode(") {\n"),
@recovery.compileToFragments(o, LEVEL_TOP), @makeCode("\n#{@tab}}")
else unless @ensure or @recovery
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
例外をスローする単純なノード。
exports.Throw = class Throw extends Base
constructor: (@expression) ->
children: ['expression']
isStatement: YES
jumps: NO
Throwは、すでに一種のreturnです…
makeReturn: THIS
compileNode: (o) ->
[].concat @makeCode(@tab + "throw "), @expression.compileToFragments(o), @makeCode(";")
変数が存在するかどうかを確認します。*null*ではなく、*undefined*でもありません。これはRubyの.nil?
に似ており、JavaScriptの真理値表を参照する必要がありません。
exports.Existence = class Existence extends Base
constructor: (@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\" #{cnj} #{code} #{cmp} null"
else
厳密な等価性を使用しないでください。既存のコードが壊れます。
code = "#{code} #{if @negated then '==' else '!='} null"
[@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
ソースで明示的に指定された、追加の括弧のセット。かつては冗長な括弧を検出して削除することで結果をクリーンアップしようとしましたが、もはやそうではありません。好きなだけ入れることができます。
括弧は、ステートメントを強制的に式にするための良い方法です。
exports.Parens = class Parens extends Base
constructor: (@body) ->
children: ['body']
unwrap : -> @body
isComplex : -> @body.isComplex()
compileNode: (o) ->
expr = @body.unwrap()
if expr instanceof Value and expr.isAtomic()
expr.front = @front
return expr.compileToFragments o
fragments = expr.compileToFragments o, LEVEL_PAREN
bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call or
(expr instanceof For and expr.returns)) and (o.level < LEVEL_COND or
fragments.length <= 3)
if bare then fragments else @wrapInBraces fragments
補間付き文字列は、実際には内部に文字列連結を持つParens
のバリエーションにすぎません。
exports.StringWithInterpolations = class StringWithInterpolations extends Parens
CoffeeScript 2で次の行のコメントを外して、すべての補間された文字列をES2015構文を使用して出力できるようにします:unwrap: -> this
compileNode: (o) ->
このメソッドは、新しいES2015構文を使用して補間された文字列を生成します。これは、タグ付きテンプレートリテラルを使用することでオプトインされます。このStringWithInterpolationsがタグ付きテンプレートリテラル内にない場合は、CoffeeScript 1.x出力にフォールバックします。(CoffeeScript 2ではこのチェックを削除してください。)
unless o.inTaggedTemplateCall
return super
仮定:exprはValue>StringLiteralまたはOpです。
expr = @body.unwrap()
elements = []
expr.traverseChildren no, (node) ->
if node instanceof StringLiteral
elements.push node
return yes
else if node instanceof Parens
elements.push node
return no
return yes
fragments = []
fragments.push @makeCode '`'
for element in elements
if element instanceof StringLiteral
value = element.value[1...-1]
テンプレートリテラル内のバッククォートと${
はエスケープする必要があります。
value = value.replace /(\\*)(`|\$\{)/g, (match, backslashes, toBeEscaped) ->
if backslashes.length % 2 is 0
"#{backslashes}\\#{toBeEscaped}"
else
match
fragments.push @makeCode value
else
fragments.push @makeCode '${'
fragments.push element.compileToFragments(o, LEVEL_PAREN)...
fragments.push @makeCode '}'
fragments.push @makeCode '`'
fragments
CoffeeScriptの*for*ループの代わりは、配列とオブジェクトの理解です。これはここで*for*ループにコンパイルされます。また、式としても機能し、フィルタリングされた各反復の結果を返すことができます。
Pythonの配列内包表記とは異なり、複数行にすることができ、ループの現在のインデックスを2番目のパラメータとして渡すことができます。Rubyブロックとは異なり、1回のパスでマップおよびフィルタリングできます。
exports.For = class For extends While
constructor: (body, source) ->
{@source, @guard, @step, @name, @index} = source
@body = Block.wrap [body]
@own = !!source.own
@object = !!source.object
@from = !!source.from
@index.error 'cannot use index with for-from' if @from and @index
source.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 instanceof Value and not @index.isAssignable()
@range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length and not @from
@pattern = @name instanceof Value
@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 = false
children: ['body', 'source', 'guard', 'step']
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, isComplexOrAssignable
stepNum = Number 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, isComplex: isComplexOrAssignable}
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) 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}]"
defPartFragments = [].concat @makeCode(defPart), @pluckDirectCall(o, body)
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
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")
[].concat defPartFragments, @makeCode("#{resultPart or ''}#{@tab}for ("),
forPartFragments, @makeCode(") {#{guardPart}#{varPart}"), bodyFragments,
@makeCode("#{@tab}}#{returnResult or ''}")
pluckDirectCall: (o, body) ->
defs = []
for expr, idx in body.expressions
expr = expr.unwrapAll()
continue unless expr instanceof Call
val = expr.variable?.unwrapAll()
continue unless (val instanceof Code) or
(val instanceof Value and
val.base?.unwrapAll() instanceof Code and
val.properties.length is 1 and
val.properties[0].name?.value in ['call', 'apply'])
fn = val.base?.unwrapAll() or val
ref = new IdentifierLiteral o.scope.freeVariable 'fn'
base = new Value ref
if val.base
[val.base, base] = [base, val]
body.expressions[idx] = new Call base, expr.args
defs = defs.concat @makeCode(@tab), (new Assign(ref, fn).compileToFragments(o, LEVEL_TOP)), @makeCode(';\n')
defs
JavaScriptの*switch*ステートメント。オンデマンドで返却可能な式に変換します。
exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) ->
children: ['subject', 'cases', 'otherwise']
isStatement: YES
jumps: (o = {block: yes}) ->
for [conds, block] in @cases
return jumpNode if jumpNode = block.jumps o
@otherwise?.jumps o
makeReturn: (res) ->
pair[1].makeReturn res for pair in @cases
@otherwise or= new Block [new Literal 'void 0'] if res
@otherwise?.makeReturn res
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 = @lastNonComment block.expressions
continue if expr instanceof Return 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
*If/else*ステートメント。要求された戻りを各句の最後の行にプッシュダウンすることにより、式として機能します。
単一式のIfsは、可能であれば条件演算子にコンパイルされます。これは、三項演算子がすでに適切な式であり、変換する必要がないためです。
exports.If = class If extends Base
constructor: (condition, @body, options = {}) ->
@condition = if options.type is 'unless' then condition.invert() else condition
@elseBody = null
@isChain = false
{@soak} = options
children: ['condition', 'body', 'elseBody']
bodyNode: -> @body?.unwrap()
elseBodyNode: -> @elseBody?.unwrap()
デフォルトケースを最後の*else*として追加するために、Ifsのチェーンを書き直します。
addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
else
@isChain = elseBody instanceof If
@elseBody = @ensureBlock elseBody
@elseBody.updateLocationDataIfMissing 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: (res) ->
@elseBody or= new Block [new Literal 'void 0'] if res
@body and= new Block [@body.makeReturn res]
@elseBody and= new Block [@elseBody.makeReturn res]
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(@condition.invert(), @elseBodyNode(), type: 'if').compileToFragments o
indent = o.indent + TAB
cond = @condition.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 = @condition.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 @wrapInBraces fragments else fragments
unfoldSoak: ->
@soak and this
UTILITIES =
super()
呼び出しのスーパー`クラス`への参照、および静的プロパティのコピーを含め、継承のプロトタイプチェーンを正しく設定します。
extend: (o) -> "
function(child, parent) {
for (var key in parent) {
if (#{utility 'hasProp', o}.call(parent, key)) child[key] = parent[key];
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
}
"
現在の「this」の値にバインドされた関数を作成します。
bind: -> '
function(fn, me){
return function(){
return fn.apply(me, arguments);
};
}
'
アイテムが配列内にあるかどうかを検出します。
indexOf: -> "
[].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i;
}
return -1;
}
"
modulo: -> """
function(a, b) { return (+a % (b = +b) + b) % b; }
"""
ネイティブ関数のルックアップ時間を短縮するためのショートカット。
hasProp: -> '{}.hasOwnProperty'
slice : -> '[].slice'
レベルは、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+$/
ユーティリティ関数がトップレベルで割り当てられていることを確認するためのヘルパー。
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) ->
code = code.replace /\n/g, '$&' + tab
code.replace /\s+$/, ''
isLiteralArguments = (node) ->
node instanceof IdentifierLiteral and node.value is 'arguments'
isLiteralThis = (node) ->
node instanceof ThisLiteral or
(node instanceof Code and node.bound) or
node instanceof SuperCall
isComplexOrAssignable = (node) -> node.isComplex() 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