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
下面定义的各种节点都编译成一个 CodeFragment 对象集合。CodeFragments 是一个生成的代码块,以及代码来源的源文件中的位置。CodeFragments 可以通过按顺序将所有 CodeFragments 的 code
代码片段连接在一起,组合成可工作的代码。
exports.CodeFragment = class CodeFragment
constructor: (parent, code) ->
@code = "#{code}"
@type = parent?.constructor?.name or 'unknown'
@locationData = parent?.locationData
@comments = parent?.comments
toString: ->
这仅用于调试。
"#{@code}#{if @locationData then ": " + locationDataToString(@locationData) else ''}"
将 CodeFragments 数组转换为字符串。
fragmentsToText = (fragments) ->
(fragment.code for fragment in fragments).join('')
Base 是语法树中所有节点的抽象基类。每个子类都实现了 compileNode
方法,该方法执行该节点的代码生成。要将节点编译为 JavaScript,请在其上调用 compile
,它将 compileNode
包含在一些通用的额外智能中,以了解何时需要将生成的代码包装在闭包中。一个选项哈希被传递并克隆到整个过程中,包含来自树中更高位置的环境信息(例如,周围函数是否请求返回值),有关当前作用域的信息,以及缩进级别。
exports.Base = class Base
compile: (o, lvl) ->
fragmentsToText @compileToFragments o, lvl
有时节点会被编译多次,例如为了获取要添加到作用域跟踪的变量的名称。当我们知道“过早”编译不会导致输出注释时,将这些注释放在一边,以便它们在稍后的 compile
调用中被保留,该调用将导致注释被包含在输出中。
compileWithoutComments: (o, lvl, method = 'compile') ->
if @comments
@ignoreTheseCommentsTemporarily = @comments
delete @comments
unwrapped = @unwrapAll()
if unwrapped.comments
unwrapped.ignoreTheseCommentsTemporarily = unwrapped.comments
delete unwrapped.comments
fragments = @[method] o, lvl
if @ignoreTheseCommentsTemporarily
@comments = @ignoreTheseCommentsTemporarily
delete @ignoreTheseCommentsTemporarily
if unwrapped.ignoreTheseCommentsTemporarily
unwrapped.comments = unwrapped.ignoreTheseCommentsTemporarily
delete unwrapped.ignoreTheseCommentsTemporarily
fragments
compileNodeWithoutComments: (o, lvl) ->
@compileWithoutComments o, lvl, 'compileNode'
确定是否在编译此节点之前将其包装在闭包中,或者直接编译的通用逻辑。如果此节点是语句,并且它不是纯语句,并且我们不在块的顶层(这将是不必要的),并且我们还没有被要求返回结果(因为语句知道如何返回结果),我们需要包装。
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
,则返回 [val, ref]
,其中这两个值是尚未编译的原始节点。
cache: (o, level, shouldCache) ->
complex = if shouldCache? then shouldCache this else @shouldCache()
if complex
ref = new IdentifierLiteral o.scope.freeVariable 'ref'
sub = new Assign ref, this
if level then [sub.compileToFragments(o, level), [@makeCode(ref.value)]] else [sub, ref]
else
ref = if level then @compileToFragments o, level else this
[ref, ref]
有时让表达式表现得好像它被“提升”了可能很有用,这样表达式的结果在它在源代码中的位置之前就可用,但表达式的变量作用域对应于源代码位置。这被广泛用于处理类中的可执行类体。
调用此方法会改变节点,代理 compileNode
和 compileToFragments
方法以存储它们的结果,以便稍后替换 target
节点,该节点由调用返回。
hoist: ->
@hoisted = yes
target = new HoistTarget @
compileNode = @compileNode
compileToFragments = @compileToFragments
@compileNode = (o) ->
target.update compileNode, o
@compileToFragments = (o) ->
target.update compileToFragments, o
target
cacheToCodeFragments: (cacheValues) ->
[fragmentsToText(cacheValues[0]), fragmentsToText(cacheValues[1])]
构造一个返回当前节点结果的节点。请注意,这被覆盖以实现许多语句节点(例如 If
、For
)的更智能的行为。
makeReturn: (results, mark) ->
if mark
将此节点标记为隐式返回,以便它可以成为 AST 中返回的节点元数据的一部分。
@canBeReturned = yes
return
node = @unwrapAll()
if results
new Call new Literal("#{results}.push"), [node]
else
new Return node
此节点或其任何子节点是否包含某种类型的节点?递归地遍历子节点,并返回第一个验证 pred
的节点。否则返回 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'
节点的纯 JavaScript 对象表示,可以序列化为 JSON。这就是 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()
@makeReturn
必须在 astProperties
之前调用,因为后者可能会为子节点调用 .ast()
,而这些节点需要在 makeReturn
中执行的返回逻辑。
@makeReturn null, yes if @isStatement(o) and o.level isnt LEVEL_TOP and o.scope?
o
astNode: (o) ->
每个抽象语法树节点对象都有四类属性
type
字段中,是一个字符串,例如 NumberLiteral
。loc
、start
、end
和 range
字段中。parsedValue
。body
。这些字段在 Babel 规范中都是混合的;type
和 start
以及 parsedValue
都是 AST 节点对象中的顶级字段。我们有单独的方法来返回每个类别,我们将它们在这里合并在一起。 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
,因为语句最终出现在表达式位置。
isStatement: NO
跟踪已编译到片段中的注释,以避免输出两次。
compiledComments: []
includeCommentFragments
让 compileCommentFragments
知道此节点是否对如何处理其输出中的注释有特殊了解。
includeCommentFragments: NO
jumps
告诉你表达式或表达式内部的一部分是否具有一个流控制结构(如 break
、continue
或 return
),该结构跳出正常的流控制,不能用作值。(请注意,throw
不被视为流控制结构。)这很重要,因为表达式中间的流控制毫无意义;我们必须禁止它。
jumps: NO
如果 node.shouldCache() is false
,则可以使用 node
多次。否则,你需要将 node
的值存储在一个变量中,并多次输出该变量。有点像这样:5
不需要缓存。但是,returnFive()
可能由于多次评估而产生副作用,因此我们需要缓存它。参数名为 shouldCache
而不是 mustCache
,因为还有一些情况,我们可能不需要缓存,但我们希望缓存,例如一个可能很幂等的较长表达式,但我们希望为了简洁而缓存它。
shouldCache: YES
isChainable: NO
isAssignable: NO
isNumber: NO
unwrap: THIS
unfoldSoak: NO
此节点是否用于分配某个变量?
assigns: NO
对于此节点及其所有后代,如果位置数据尚未设置,则将位置数据设置为 locationData
。
updateLocationDataIfMissing: (locationData, force) ->
@forceUpdateLocation = yes if force
return this if @locationData and not @forceUpdateLocation
delete @forceUpdateLocation
@locationData = locationData
@eachChild (child) ->
child.updateLocationDataIfMissing locationData
添加来自另一个节点的位置数据
withLocationDataFrom: ({locationData}) ->
@updateLocationDataIfMissing locationData
添加来自另一个节点的位置数据和注释
withLocationDataAndCommentsFrom: (node) ->
@withLocationDataFrom node
{comments} = node
@comments = comments if comments?.length
this
抛出一个与此节点位置相关的 SyntaxError。
error: (message) ->
throwSyntaxError message, @locationData
makeCode: (code) ->
new CodeFragment this, code
wrapInParentheses: (fragments) ->
[@makeCode('('), fragments..., @makeCode(')')]
wrapInBraces: (fragments) ->
[@makeCode('{'), fragments..., @makeCode('}')]
fragmentsList
是一个片段数组的数组。fragmentsList
中的每个数组将被连接在一起,并在每个数组之间添加 joinStr
,以生成一个最终的扁平片段数组。
joinFragmentArrays: (fragmentsList, joinStr) ->
answer = []
for fragments, i in fragmentsList
if i then answer.push @makeCode joinStr
answer = answer.concat fragments
answer
HoistTargetNode 表示节点树中提升节点的输出位置。请参阅 Base#hoist。
exports.HoistTarget = class HoistTarget extends Base
扩展给定数组中的提升片段
@expand = (fragments) ->
for fragment, i in fragments by -1 when fragment.fragments
fragments[i..i] = @expand fragment.fragments
fragments
constructor: (@source) ->
super()
保存源节点编译时要应用的表示选项。
@options = {}
要由源节点的编译替换的占位符片段。
@targetFragments = { fragments: [] }
isStatement: (o) ->
@source.isStatement o
使用编译源节点的结果更新目标片段。使用节点和选项(用目标表示选项覆盖)调用给定的编译函数。
update: (compile, o) ->
@targetFragments.fragments = compile.call @source, merge o, @options
复制目标缩进和级别,并返回占位符片段
compileToFragments: (o, level) ->
@options.indent = o.indent
@options.level = level ? o.level
[ @targetFragments ]
compileNode: (o) ->
@compileToFragments o
compileClosure: (o) ->
@compileToFragments o
节点树的根节点
exports.Root = class Root extends Base
constructor: (@body) ->
super()
@isAsync = (new Code [], @body).isAsync
children: ['body']
将所有内容包装在一个安全闭包中,除非请求不要这样做。最好不要在第一时间生成它们,但就目前而言,清理明显的双括号。
compileNode: (o) ->
o.indent = if o.bare then '' else TAB
o.level = LEVEL_TOP
o.compiling = yes
@initializeScope o
fragments = @body.compileRoot o
return fragments if o.bare
functionKeyword = "#{if @isAsync then 'async ' else ''}function"
[].concat @makeCode("(#{functionKeyword}() {\n"), fragments, @makeCode("\n}).call(this);\n")
initializeScope: (o) ->
o.scope = new Scope null, @body, null, o.referencedVars ? []
将给定的局部变量标记为根作用域中的参数,这样它们就不会最终在根块上声明。
o.scope.parameter name for name in o.locals or []
commentsAst: ->
@allComments ?=
for commentToken in (@allCommentTokens ? []) when not commentToken.heregex
if commentToken.here
new HereComment commentToken
else
new LineComment commentToken
comment.ast() for comment in @allComments
astNode: (o) ->
o.level = LEVEL_TOP
@initializeScope o
super o
astType: -> 'File'
astProperties: (o) ->
@body.isRootBlock = yes
return
program: Object.assign @body.ast(o), @astLocationData()
comments: @commentsAst()
块是构成缩进代码块主体的一系列表达式列表,例如函数的实现、if
、switch
或 try
中的子句等等。
exports.Block = class Block extends Base
constructor: (nodes) ->
super()
@expressions = compact flatten nodes or []
children: ['expressions']
将一个表达式添加到此表达式列表的末尾。
push: (node) ->
@expressions.push node
this
删除并返回此表达式列表的最后一个表达式。
pop: ->
@expressions.pop()
在该表达式列表的开头添加一个表达式。
unshift: (node) ->
@expressions.unshift node
this
如果此块仅包含一个节点,则通过将其拉回到外部来将其展开。
unwrap: ->
if @expressions.length is 1 then @expressions[0] else this
这是一个空代码块吗?
isEmpty: ->
not @expressions.length
isStatement: (o) ->
for exp in @expressions when exp.isStatement o
return yes
no
jumps: (o) ->
for exp in @expressions
return jumpNode if jumpNode = exp.jumps o
块节点不会返回其整个主体,而是确保返回最后一个表达式。
makeReturn: (results, mark) ->
len = @expressions.length
[..., lastExp] = @expressions
lastExp = lastExp?.unwrap() or no
我们还需要检查,如果在同一级别存在相邻的 JSX 标签,我们不会返回 JSX 标签;JSX 不允许这样做。
if lastExp and lastExp instanceof Parens and lastExp.body.expressions.length > 1
{body:{expressions}} = lastExp
[..., penult, last] = expressions
penult = penult.unwrap()
last = last.unwrap()
if penult instanceof JSXElement and last instanceof JSXElement
expressions[expressions.length - 1].error 'Adjacent JSX elements must be wrapped in an enclosing tag'
if mark
@expressions[len - 1]?.makeReturn results, mark
return
while len--
expr = @expressions[len]
@expressions[len] = expr.makeReturn results
@expressions.splice(len, 1) if expr instanceof Return and not expr.expression
break
this
compile: (o, lvl) ->
return new Root(this).withLocationDataFrom(this).compile o, lvl unless o.scope
super o, lvl
编译**块**主体内的所有表达式。如果我们需要返回结果,并且它是一个表达式,则直接返回它。如果它是一个语句,则要求语句执行此操作。
compileNode: (o) ->
@tab = o.indent
top = o.level is LEVEL_TOP
compiledNodes = []
for node, index in @expressions
if node.hoisted
这是一个提升的表达式。我们希望编译它并忽略结果。
node.compileToFragments o
continue
node = (node.unfoldSoak(o) or node)
if node instanceof Block
这是一个嵌套块。我们不会在这里执行任何特殊操作,例如将其包含在新作用域中;我们只是与我们自己的语句一起编译此块中的语句。
compiledNodes.push node.compileNode o
else if top
node.front = yes
fragments = node.compileToFragments o
unless node.isStatement o
fragments = indentInitial fragments, @
[..., lastFragment] = fragments
unless lastFragment.code is '' or lastFragment.isComment
fragments.push @makeCode ';'
compiledNodes.push fragments
else
compiledNodes.push node.compileToFragments o, LEVEL_LIST
if top
if @spaced
return [].concat @joinFragmentArrays(compiledNodes, '\n\n'), @makeCode('\n')
else
return @joinFragmentArrays(compiledNodes, '\n')
if compiledNodes.length
answer = @joinFragmentArrays(compiledNodes, ', ')
else
answer = [@makeCode 'void 0']
if compiledNodes.length > 1 and o.level >= LEVEL_LIST then @wrapInParentheses answer else answer
compileRoot: (o) ->
@spaced = yes
fragments = @compileWithDeclarations o
HoistTarget.expand fragments
@compileComments fragments
编译函数内容的表达式主体,并将所有内部变量的声明向上推到顶部。
compileWithDeclarations: (o) ->
fragments = []
post = []
for exp, i in @expressions
exp = exp.unwrap()
break unless exp instanceof Literal
o = merge(o, level: LEVEL_TOP)
if i
rest = @expressions.splice i, 9e9
[spaced, @spaced] = [@spaced, no]
[fragments, @spaced] = [@compileNode(o), spaced]
@expressions = rest
post = @compileNode o
{scope} = o
if scope.expressions is this
declars = o.scope.hasDeclarations()
assigns = scope.hasAssignments
if declars or assigns
fragments.push @makeCode '\n' if i
fragments.push @makeCode "#{@tab}var "
if declars
declaredVariables = scope.declaredVariables()
for declaredVariable, declaredVariablesIndex in declaredVariables
fragments.push @makeCode declaredVariable
if Object::hasOwnProperty.call o.scope.comments, declaredVariable
fragments.push o.scope.comments[declaredVariable]...
if declaredVariablesIndex isnt declaredVariables.length - 1
fragments.push @makeCode ', '
if assigns
fragments.push @makeCode ",\n#{@tab + TAB}" if declars
fragments.push @makeCode scope.assignedVariables().join(",\n#{@tab + TAB}")
fragments.push @makeCode ";\n#{if @spaced then '\n' else ''}"
else if fragments.length and post.length
fragments.push @makeCode "\n"
fragments.concat post
compileComments: (fragments) ->
for fragment, fragmentIndex in fragments
在下一个或上一个换行符处将注释插入输出。如果不存在可放置注释的换行符,则创建它们。
if fragment.precedingComments
确定我们即将在其中插入注释的片段的缩进级别,并使用该缩进级别来插入注释。此时,片段的code
属性是生成的输出 JavaScript,而 CoffeeScript 始终生成缩进两个空格的输出;因此,我们只需要搜索以至少两个空格开头的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
将给定的节点包装成一个**块**,除非它碰巧已经是块。
@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
是静态值的基类,可以不经翻译直接传递到 JavaScript 中,例如:字符串、数字、true
、false
、null
…
exports.Literal = class Literal extends Base
constructor: (@value) ->
super()
shouldCache: NO
assigns: (name) ->
name is @value
compileNode: (o) ->
[@makeCode @value]
astProperties: ->
return
value: @value
toString: ->
这仅用于调试。
" #{if @isStatement() then super() else @constructor.name}: #{@value}"
exports.NumberLiteral = class NumberLiteral extends Literal
constructor: (@value, {@parsedValue} = {}) ->
super()
unless @parsedValue?
if isNumber @value
@parsedValue = @value
@value = "#{@value}"
else
@parsedValue = parseNumber @value
isBigInt: ->
/n$/.test @value
astType: ->
if @isBigInt()
'BigIntLiteral'
else
'NumericLiteral'
astProperties: ->
return
value:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
extra:
rawValue:
if @isBigInt()
@parsedValue.toString()
else
@parsedValue
raw: @value
exports.InfinityLiteral = class InfinityLiteral extends NumberLiteral
constructor: (@value, {@originalValue = 'Infinity'} = {}) ->
super()
compileNode: ->
[@makeCode '2e308']
astNode: (o) ->
unless @originalValue is 'Infinity'
return new NumberLiteral(@value).withLocationDataFrom(@).ast o
super o
astType: -> 'Identifier'
astProperties: ->
return
name: 'Infinity'
declaration: no
exports.NaNLiteral = class NaNLiteral extends NumberLiteral
constructor: ->
super 'NaN'
compileNode: (o) ->
code = [@makeCode '0/0']
if o.level >= LEVEL_OP then @wrapInParentheses code else code
astType: -> 'Identifier'
astProperties: ->
return
name: 'NaN'
declaration: no
exports.StringLiteral = class StringLiteral extends Literal
constructor: (@originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex} = {}) ->
super ''
@quote = null if @quote is '///'
@fromSourceString = @quote?
@quote ?= '"'
heredoc = @isFromHeredoc()
val = @originalValue
if @heregex
val = val.replace HEREGEX_OMIT, '$1$2'
val = replaceUnicodeCodePointEscapes val, flags: @heregex.flags
else
val = val.replace STRING_OMIT, '$1'
val =
unless @fromSourceString
val
else if heredoc
indentRegex = /// \n#{@indent} ///g if @indent
val = val.replace indentRegex, '\n' if indentRegex
val = val.replace LEADING_BLANK_LINE, '' if @initialChunk
val = val.replace TRAILING_BLANK_LINE, '' if @finalChunk
val
else
val.replace SIMPLE_STRING_OMIT, (match, offset) =>
if (@initialChunk and offset is 0) or
(@finalChunk and offset + match.length is val.length)
''
else
' '
@delimiter = @quote.charAt 0
@value = makeDelimitedLiteral val, {
@delimiter
@double
}
@unquotedValueForTemplateLiteral = makeDelimitedLiteral val, {
delimiter: '`'
@double
escapeNewlines: no
includeDelimiters: no
convertTrailingNullEscapes: yes
}
@unquotedValueForJSX = makeDelimitedLiteral val, {
@double
escapeNewlines: no
includeDelimiters: no
escapeDelimiter: no
}
compileNode: (o) ->
return StringWithInterpolations.fromStringLiteral(@).compileNode o if @shouldGenerateTemplateLiteral()
return [@makeCode @unquotedValueForJSX] if @jsx
super o
StringLiteral
可以表示整个字面量字符串或例如插值字符串中的文本片段。当解析为前者但需要将其视为后者(例如,标记模板字面量中的字符串部分)时,这将返回一个StringLiteral
的副本,其引号已从其位置数据中修剪(就像它在解析为插值字符串的一部分时一样)。
withoutQuotesInLocationData: ->
endsWithNewline = @originalValue[-1..] is '\n'
locationData = Object.assign {}, @locationData
locationData.first_column += @quote.length
if endsWithNewline
locationData.last_line -= 1
locationData.last_column =
if locationData.last_line is locationData.first_line
locationData.first_column + @originalValue.length - '\n'.length
else
@originalValue[...-1].length - '\n'.length - @originalValue[...-1].lastIndexOf('\n')
else
locationData.last_column -= @quote.length
locationData.last_column_exclusive -= @quote.length
locationData.range = [
locationData.range[0] + @quote.length
locationData.range[1] - @quote.length
]
copy = new StringLiteral @originalValue, {@quote, @initialChunk, @finalChunk, @indent, @double, @heregex}
copy.locationData = locationData
copy
isFromHeredoc: ->
@quote.length is 3
shouldGenerateTemplateLiteral: ->
@isFromHeredoc()
astNode: (o) ->
return StringWithInterpolations.fromStringLiteral(@).ast o if @shouldGenerateTemplateLiteral()
super o
astProperties: ->
return
value: @originalValue
extra:
raw: "#{@delimiter}#{@originalValue}#{@delimiter}"
exports.RegexLiteral = class RegexLiteral extends Literal
constructor: (value, {@delimiter = '/', @heregexCommentTokens = []} = {}) ->
super ''
heregex = @delimiter is '///'
endDelimiterIndex = value.lastIndexOf '/'
@flags = value[endDelimiterIndex + 1..]
val = @originalValue = value[1...endDelimiterIndex]
val = val.replace HEREGEX_OMIT, '$1$2' if heregex
val = replaceUnicodeCodePointEscapes val, {@flags}
@value = "#{makeDelimitedLiteral val, delimiter: '/'}#{@flags}"
REGEX_REGEX: /// ^ / (.*) / \w* $ ///
astType: -> 'RegExpLiteral'
astProperties: (o) ->
[, pattern] = @REGEX_REGEX.exec @value
return {
value: undefined
pattern, @flags, @delimiter
originalPattern: @originalValue
extra:
raw: @value
originalRaw: "#{@delimiter}#{@originalValue}#{@delimiter}#{@flags}"
rawValue: undefined
comments:
for heregexCommentToken in @heregexCommentTokens
if heregexCommentToken.here
new HereComment(heregexCommentToken).ast o
else
new LineComment(heregexCommentToken).ast o
}
exports.PassthroughLiteral = class PassthroughLiteral extends Literal
constructor: (@originalValue, {@here, @generated} = {}) ->
super ''
@value = @originalValue.replace /\\+(`|$)/g, (string) ->
string
始终是一个像‘`‘、‘\`‘、‘\\`‘ 等等的值。通过将其简化为后半部分,我们将‘`‘ 转换为 ‘','\\\
‘ 转换为 ‘`‘ 等等。
string[-Math.ceil(string.length / 2)..]
astNode: (o) ->
return null if @generated
super o
astProperties: ->
return {
value: @originalValue
here: !!@here
}
exports.IdentifierLiteral = class IdentifierLiteral extends Literal
isAssignable: YES
eachName: (iterator) ->
iterator @
astType: ->
if @jsx
'JSXIdentifier'
else
'Identifier'
astProperties: ->
return
name: @value
declaration: !!@isDeclaration
exports.PropertyName = class PropertyName extends Literal
isAssignable: YES
astType: ->
if @jsx
'JSXIdentifier'
else
'Identifier'
astProperties: ->
return
name: @value
declaration: no
exports.ComputedPropertyName = class ComputedPropertyName extends PropertyName
compileNode: (o) ->
[@makeCode('['), @value.compileToFragments(o, LEVEL_LIST)..., @makeCode(']')]
astNode: (o) ->
@value.ast o
exports.StatementLiteral = class StatementLiteral extends Literal
isStatement: YES
makeReturn: THIS
jumps: (o) ->
return this if @value is 'break' and not (o?.loop or o?.block)
return this if @value is 'continue' and not o?.loop
compileNode: (o) ->
[@makeCode "#{@tab}#{@value};"]
astType: ->
switch @value
when 'continue' then 'ContinueStatement'
when 'break' then 'BreakStatement'
when 'debugger' then 'DebuggerStatement'
exports.ThisLiteral = class ThisLiteral extends Literal
constructor: (value) ->
super 'this'
@shorthand = value is '@'
compileNode: (o) ->
code = if o.scope.method?.bound then o.scope.method.context else @value
[@makeCode code]
astType: -> 'ThisExpression'
astProperties: ->
return
shorthand: @shorthand
exports.UndefinedLiteral = class UndefinedLiteral extends Literal
constructor: ->
super 'undefined'
compileNode: (o) ->
[@makeCode if o.level >= LEVEL_ACCESS then '(void 0)' else 'void 0']
astType: -> 'Identifier'
astProperties: ->
return
name: @value
declaration: no
exports.NullLiteral = class NullLiteral extends Literal
constructor: ->
super 'null'
exports.BooleanLiteral = class BooleanLiteral extends Literal
constructor: (value, {@originalValue} = {}) ->
super value
@originalValue ?= @value
astProperties: ->
value: if @value is 'true' then yes else no
name: @originalValue
exports.DefaultLiteral = class DefaultLiteral extends Literal
astType: -> 'Identifier'
astProperties: ->
return
name: 'default'
declaration: no
return
是一个纯语句——将其包装在闭包中将没有意义。
exports.Return = class Return extends Base
constructor: (@expression, {@belongsToFuncDirectiveReturn} = {}) ->
super()
children: ['expression']
isStatement: YES
makeReturn: THIS
jumps: THIS
compileToFragments: (o, level) ->
expr = @expression?.makeReturn()
if expr and expr not instanceof Return then expr.compileToFragments o, level else super o, level
compileNode: (o) ->
answer = []
TODO:如果我们在这里两次调用expression.compile()
,有时我们会得到不同的结果!
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。如果属性链中穿插着浸泡 运算符?.
,情况会变得更加有趣。然后,我们必须注意在构建浸泡链时不要意外地评估任何内容两次。
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 生成,我们需要一个object
,它就是这个Value
减去它的最后一个属性(如果有属性)。
object: ->
return @ unless @hasProperties()
获取除最后一个属性之外的所有属性;对于只有一个属性的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
只有一个属性,因此位置数据只是父Value
的基的位置数据。
@base.locationData
else
这个新的Value
具有多个属性,因此位置数据从父Value
的基一直延伸到包含在这个新节点中的最后一个属性(也称为父节点的倒数第二个属性)。
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
)将成为一个子Value
节点,并分配给object
属性。
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
包含带值的属性的 Obj,例如 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 捕获,该 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:
被输出时在第二次传递中被输出。因此,在这次编译过程中将它们放在一边,并将它们放回对象上,以便它们在以后的编译中可用。
salvagedComments = @accessor.name.comments
delete @accessor.name.comments
fragments = (new Value (new Literal 'super'), if @accessor then [ @accessor ] else [])
.compileToFragments o
attachCommentsToNode salvagedComments, @accessor.name if salvagedComments
fragments
checkInInstanceMethod: (o) ->
method = o.scope.namedMethod()
@error 'cannot use super outside of an instance method' unless method?.isMethod
astNode: (o) ->
@checkInInstanceMethod o
if @accessor?
return (
new Value(
new Super().withLocationDataFrom (@superLiteral ? @)
[@accessor]
).withLocationDataFrom @
).ast o
super o
带插值的正则表达式实际上只是 Call
(准确地说是 RegExp()
调用)的变体,其中包含 StringWithInterpolations
。
exports.RegexWithInterpolations = class RegexWithInterpolations extends Base
constructor: (@call, {@heregexCommentTokens = []} = {}) ->
super()
children: ['call']
compileNode: (o) ->
@call.compileNode o
astType: -> 'InterpolatedRegExpLiteral'
astProperties: (o) ->
interpolatedPattern: @call.args[0].ast o
flags: @call.args[1]?.unwrap().originalValue ? ''
comments:
for heregexCommentToken in @heregexCommentTokens
if heregexCommentToken.here
new HereComment(heregexCommentToken).ast o
else
new LineComment(heregexCommentToken).ast o
exports.TaggedTemplateCall = class TaggedTemplateCall extends Call
constructor: (variable, arg, soak) ->
arg = StringWithInterpolations.fromStringLiteral arg if arg instanceof StringLiteral
super variable, [ arg ], soak
compileNode: (o) ->
@variable.compileToFragments(o, LEVEL_ACCESS).concat @args[0].compileToFragments(o, LEVEL_LIST)
astType: -> 'TaggedTemplateExpression'
astProperties: (o) ->
return
tag: @variable.ast o, LEVEL_ACCESS
quasi: @args[0].ast o, LEVEL_LIST
exports.Extends = class Extends extends Base
constructor: (@child, @parent) ->
super()
children: ['child', 'parent']
将一个构造函数挂钩到另一个构造函数的原型链中。
compileToFragments: (o) ->
new Call(new Value(new Literal utility 'extend', o), [@child, @parent]).compileToFragments o
对值的属性进行 .
访问,或者对对象的原型进行 ::
简写访问。
exports.Access = class Access extends Base
constructor: (@name, {@soak, @shorthand} = {}) ->
super()
children: ['name']
compileToFragments: (o) ->
name = @name.compileToFragments o
node = @name.unwrap()
if node instanceof PropertyName
[@makeCode('.'), name...]
else
[@makeCode('['), name..., @makeCode(']')]
shouldCache: NO
astNode: (o) ->
Babel 没有用于 Access
的 AST 节点,而是将此 Access 节点的子节点 name
Identifier 节点作为 MemberExpression
节点的 property
包含。
@name.ast o
对数组或对象的 [ ... ]
索引访问。
exports.Index = class Index extends Base
constructor: (@index) ->
super()
children: ['index']
compileToFragments: (o) ->
[].concat @makeCode("["), @index.compileToFragments(o, LEVEL_PAREN), @makeCode("]")
shouldCache: ->
@index.shouldCache()
astNode: (o) ->
Babel 没有用于 Index
的 AST 节点,而是将此 Index 节点的子节点 index
Identifier 节点作为 MemberExpression
节点的 property
包含。MemberExpression
的 property
是 Index 的事实意味着 MemberExpression
的 computed
为 true
。
@index.ast o
范围字面量。范围可用于提取数组的部分(切片),指定理解的范围,或作为值,在运行时扩展为相应的整数数组。
exports.Range = class Range extends Base
children: ['from', 'to']
constructor: (@from, @to, tag) ->
super()
@exclusive = tag is 'exclusive'
@equals = if @exclusive then '' else '='
编译范围的源变量 - 范围从哪里开始以及从哪里结束。但只有在需要缓存以避免双重评估时才这样做。
compileVariables: (o) ->
o = merge o, top: true
shouldCache = del o, 'shouldCache'
[@fromC, @fromVar] = @cacheToCodeFragments @from.cache o, LEVEL_LIST, shouldCache
[@toC, @toVar] = @cacheToCodeFragments @to.cache o, LEVEL_LIST, shouldCache
[@step, @stepVar] = @cacheToCodeFragments step.cache o, LEVEL_LIST, shouldCache if step = del o, 'step'
@fromNum = if @from.isNumber() then parseNumber @fromVar else null
@toNum = if @to.isNumber() then parseNumber @toVar else null
@stepNum = if step?.isNumber() then parseNumber @stepVar else null
当正常编译时,范围返回用于迭代范围中值的 for 循环 的内容。由理解使用。
compileNode: (o) ->
@compileVariables o unless @fromVar
return @compileArray(o) unless o.index
设置端点。
known = @fromNum? and @toNum?
idx = del o, 'index'
idxName = del o, 'name'
namedIndex = idxName and idxName isnt idx
varPart =
if known and not namedIndex
"var #{idx} = #{@fromC}"
else
"#{idx} = #{@fromC}"
varPart += ", #{@toC}" if @toC isnt @toVar
varPart += ", #{@step}" if @step isnt @stepVar
[lt, gt] = ["#{idx} <#{@equals}", "#{idx} >#{@equals}"]
生成条件。
[from, to] = [@fromNum, @toNum]
始终检查 step
是否不为零,以避免无限循环。
stepNotZero = "#{ @stepNum ? @stepVar } !== 0"
stepCond = "#{ @stepNum ? @stepVar } > 0"
lowerBound = "#{lt} #{ if known then to else @toVar }"
upperBound = "#{gt} #{ if known then to else @toVar }"
condPart =
if @step?
if @stepNum? and @stepNum isnt 0
if @stepNum > 0 then "#{lowerBound}" else "#{upperBound}"
else
"#{stepNotZero} && (#{stepCond} ? #{lowerBound} : #{upperBound})"
else
if known
"#{ if from <= to then lt else gt } #{to}"
else
"(#{@fromVar} <= #{@toVar} ? #{lowerBound} : #{upperBound})"
cond = if @stepVar then "#{@stepVar} > 0" else "#{@fromVar} <= #{@toVar}"
生成步长。
stepPart = if @stepVar
"#{idx} += #{@stepVar}"
else if known
if namedIndex
if from <= to then "++#{idx}" else "--#{idx}"
else
if from <= to then "#{idx}++" else "#{idx}--"
else
if namedIndex
"#{cond} ? ++#{idx} : --#{idx}"
else
"#{cond} ? #{idx}++ : #{idx}--"
varPart = "#{idxName} = #{varPart}" if namedIndex
stepPart = "#{idxName} = #{stepPart}" if namedIndex
最终的循环体。
[@makeCode "#{varPart}; #{condPart}; #{stepPart}"]
当用作值时,将范围扩展为等效的数组。
compileArray: (o) ->
known = @fromNum? and @toNum?
if known and Math.abs(@fromNum - @toNum) <= 20
range = [@fromNum..@toNum]
range.pop() if @exclusive
return [@makeCode "[#{ range.join(', ') }]"]
idt = @tab + TAB
i = o.scope.freeVariable 'i', single: true, reserve: no
result = o.scope.freeVariable 'results', reserve: no
pre = "\n#{idt}var #{result} = [];"
if known
o.index = i
body = fragmentsToText @compileNode o
else
vars = "#{i} = #{@fromC}" + if @toC isnt @toVar then ", #{@toC}" else ''
cond = "#{@fromVar} <= #{@toVar}"
body = "var #{vars}; #{cond} ? #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{cond} ? #{i}++ : #{i}--"
post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}"
hasArgs = (node) -> node?.contains isLiteralArguments
args = ', arguments' if hasArgs(@from) or hasArgs(@to)
[@makeCode "(function() {#{pre}\n#{idt}for (#{body})#{post}}).apply(this#{args ? ''})"]
astProperties: (o) ->
return {
from: @from?.ast(o) ? null
to: @to?.ast(o) ? null
@exclusive
}
数组切片字面量。与 JavaScript 的 Array#slice
不同,第二个参数指定切片结束的索引,就像第一个参数是开始的索引一样。
exports.Slice = class Slice extends Base
children: ['range']
constructor: (@range) ->
super()
在尝试切片数组末尾时,我们必须小心,使用 9e9
是因为并非所有实现都尊重 undefined
或 1/0
。9e9
应该是安全的,因为 9e9
> 2**32
,最大数组长度。
compileNode: (o) ->
{to, from} = @range
处理属性访问中的表达式,例如 a[!b in c..]
。
if from?.shouldCache()
from = new Value new Parens from
if to?.shouldCache()
to = new Value new Parens to
fromCompiled = from?.compileToFragments(o, LEVEL_PAREN) or [@makeCode '0']
if to
compiled = to.compileToFragments o, LEVEL_PAREN
compiledText = fragmentsToText compiled
if not (not @range.exclusive and +compiledText is -1)
toStr = ', ' + if @range.exclusive
compiledText
else if to.isNumber()
"#{+compiledText + 1}"
else
compiled = to.compileToFragments o, LEVEL_ACCESS
"+#{fragmentsToText compiled} + 1 || 9e9"
[@makeCode ".slice(#{ fragmentsToText fromCompiled }#{ toStr or '' })"]
astNode: (o) ->
@range.ast o
对象字面量,没什么特别的。
exports.Obj = class Obj extends Base
constructor: (props, @generated = no) ->
super()
@objects = @properties = props or []
children: ['properties']
isAssignable: (opts) ->
for prop in @properties
检查保留字。
message = isUnassignable prop.unwrapAll().value
prop.error message if message
prop = prop.value if prop instanceof Assign and
prop.context is 'object' and
prop.value?.base not instanceof Arr
return no unless prop.isAssignable opts
yes
shouldCache: ->
not @isAssignable()
检查对象是否包含 splat。
hasSplat: ->
return yes for prop in @properties when prop instanceof Splat
no
将剩余属性移动到列表的末尾。{a, rest..., b} = obj
-> {a, b, rest...} = obj
foo = ({a, rest..., b}) ->
-> foo = {a, b, rest...}) ->
reorderProperties: ->
props = @properties
splatProps = @getAndCheckSplatProps()
splatProp = props.splice splatProps[0], 1
@objects = @properties = [].concat props, splatProp
compileNode: (o) ->
@reorderProperties() if @hasSplat() and @lhs
props = @properties
if @generated
for node in props when node instanceof Value
node.error 'cannot have an implicit value in an implicit object'
idt = o.indent += TAB
lastNode = @lastNode @properties
如果此对象是赋值的左侧,则其所有子节点也是如此。
@propagateLhs()
isCompact = yes
for prop in @properties
if prop instanceof Assign and prop.context is 'object'
isCompact = no
answer = []
answer.push @makeCode if isCompact then '' else '\n'
for prop, i in props
join = if i is props.length - 1
''
else if isCompact
', '
else if prop is lastNode
'\n'
else
',\n'
indent = if isCompact then '' else idt
key = if prop instanceof Assign and prop.context is 'object'
prop.variable
else if prop instanceof Assign
prop.operatorToken.error "unexpected #{prop.operatorToken.value}" unless @lhs
prop.variable
else
prop
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' if prop.context is 'object' or not key.this
key = key.properties[0].name
prop = new Assign key, prop, 'object'
if key is prop
if prop.shouldCache()
[key, value] = prop.base.cache o
key = new PropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else if key instanceof Value and key.base instanceof ComputedPropertyName
{ [foo()] }
输出为 { [ref = foo()]: ref }
。
if prop.base.value.shouldCache()
[key, value] = prop.base.value.cache o
key = new ComputedPropertyName key.value if key instanceof IdentifierLiteral
prop = new Assign key, value, 'object'
else
{ [expression] }
输出为 { [expression]: expression }
。
prop = new Assign key, prop.base.value, 'object'
else if not prop.bareLiteral?(IdentifierLiteral) and prop not instanceof Splat
prop = new Assign prop, prop, 'object'
if indent then answer.push @makeCode indent
answer.push prop.compileToFragments(o, LEVEL_TOP)...
if join then answer.push @makeCode join
answer.push @makeCode if isCompact then '' else "\n#{@tab}"
answer = @wrapInBraces answer
if @front then @wrapInParentheses answer else answer
getAndCheckSplatProps: ->
return unless @hasSplat() and @lhs
props = @properties
splatProps = (i for prop, i in props when prop instanceof Splat)
props[splatProps[1]].error "multiple spread elements are disallowed" if splatProps?.length > 1
splatProps
assigns: (name) ->
for prop in @properties when prop.assigns name then return yes
no
eachName: (iterator) ->
for prop in @properties
prop = prop.value if prop instanceof Assign and prop.context is 'object'
prop = prop.unwrapAll()
prop.eachName iterator if prop.eachName?
将“裸”属性转换为 ObjectProperty
(或 Splat
)。
expandProperty: (property) ->
{variable, context, operatorToken} = property
key = if property instanceof Assign and context is 'object'
variable
else if property instanceof Assign
operatorToken.error "unexpected #{operatorToken.value}" unless @lhs
variable
else
property
if key instanceof Value and key.hasProperties()
key.error 'invalid object key' unless context isnt 'object' and key.this
if property instanceof Assign
return new ObjectProperty fromAssign: property
else
return new ObjectProperty key: property
return new ObjectProperty(fromAssign: property) unless key is property
return property if property instanceof Splat
new ObjectProperty key: property
expandProperties: ->
@expandProperty(property) for property in @properties
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
for property in @properties
if property instanceof Assign and property.context is 'object'
{value} = property
unwrappedValue = value.unwrapAll()
if unwrappedValue instanceof Arr or unwrappedValue instanceof Obj
unwrappedValue.propagateLhs yes
else if unwrappedValue instanceof Assign
unwrappedValue.nestedLhs = yes
else if property instanceof Assign
带默认值的简写属性,例如 {a = 1} = b
。
property.nestedLhs = yes
else if property instanceof Splat
property.propagateLhs yes
astNode: (o) ->
@getAndCheckSplatProps()
super o
astType: ->
if @lhs
'ObjectPattern'
else
'ObjectExpression'
astProperties: (o) ->
return
implicit: !!@generated
properties:
property.ast(o) for property in @expandProperties()
exports.ObjectProperty = class ObjectProperty extends Base
constructor: ({key, fromAssign}) ->
super()
if fromAssign
{variable: @key, value, context} = fromAssign
if context is 'object'
所有非简写属性(即包含 :
)。
@value = value
else
带默认值的左侧简写,例如 {a = 1} = b
。
@value = fromAssign
@shorthand = yes
@locationData = fromAssign.locationData
else
没有默认值的简写,例如 {a}
或 {@a}
或 {[a]}
。
@key = key
@shorthand = yes
@locationData = key.locationData
astProperties: (o) ->
isComputedPropertyName = (@key instanceof Value and @key.base instanceof ComputedPropertyName) or @key.unwrap() instanceof StringWithInterpolations
keyAst = @key.ast o, LEVEL_LIST
return
key:
if keyAst?.declaration
Object.assign {}, keyAst, declaration: no
else
keyAst
value: @value?.ast(o, LEVEL_LIST) ? keyAst
shorthand: !!@shorthand
computed: !!isComputedPropertyName
method: no
数组字面量。
exports.Arr = class Arr extends Base
constructor: (objs, @lhs = no) ->
super()
@objects = objs or []
@propagateLhs()
children: ['objects']
hasElision: ->
return yes for obj in @objects when obj instanceof Elision
no
isAssignable: (opts) ->
{allowExpansion, allowNontrailingSplat, allowEmptyArray = no} = opts ? {}
return allowEmptyArray unless @objects.length
for obj, i in @objects
return no if not allowNontrailingSplat and obj instanceof Splat and i + 1 isnt @objects.length
return no unless (allowExpansion and obj instanceof Expansion) or (obj.isAssignable(opts) and (not obj.isAtomic or obj.isAtomic()))
yes
shouldCache: ->
not @isAssignable()
compileNode: (o) ->
return [@makeCode '[]'] unless @objects.length
o.indent += TAB
fragmentIsElision = ([ fragment ]) ->
fragment.type is 'Elision' and fragment.code.trim() is ','
检测数组开头的 Elision
是否已处理(例如 [, , , a])。
passedElision = no
answer = []
for obj, objIndex in @objects
unwrappedObj = obj.unwrapAll()
让 compileCommentFragments
知道将块注释插入编译此数组时创建的片段中。
if unwrappedObj.comments and
unwrappedObj.comments.filter((comment) -> not comment.here).length is 0
unwrappedObj.includeCommentFragments = YES
compiledObjs = (obj.compileToFragments o, LEVEL_LIST for obj in @objects)
olen = compiledObjs.length
如果 compiledObjs
包含换行符,我们将将其输出为多行数组(即在 [
之后带有换行符和缩进)。如果元素包含行注释,这也应该触发多行输出,因为根据定义,行注释会将换行符引入我们的输出。例外情况是,只有第一个元素包含行注释;在这种情况下,如果我们本来会输出为紧凑形式,则输出为紧凑形式,以便第一个元素的行注释在数组之前或之后输出。
includesLineCommentsOnNonFirstElement = no
for fragments, index in compiledObjs
for fragment in fragments
if fragment.isHereComment
fragment.code = fragment.code.trim()
else if index isnt 0 and includesLineCommentsOnNonFirstElement is no and hasLineComments fragment
includesLineCommentsOnNonFirstElement = yes
如果数组开头的所有 Elision
都已处理(例如 [, , , a]),并且元素不是 Elision
或最后一个元素是 Elision
(例如 [a,,b,,]),则添加 ‘, ‘。
if index isnt 0 and passedElision and (not fragmentIsElision(fragments) or index is olen - 1)
answer.push @makeCode ', '
passedElision = passedElision or not fragmentIsElision fragments
answer.push fragments...
if includesLineCommentsOnNonFirstElement or '\n' in fragmentsToText(answer)
for fragment, fragmentIndex in answer
if fragment.isHereComment
fragment.code = "#{multident(fragment.code, o.indent, no)}\n#{o.indent}"
else if fragment.code is ', ' and not fragment?.isElision and fragment.type not in ['StringLiteral', 'StringWithInterpolations']
fragment.code = ",\n#{o.indent}"
answer.unshift @makeCode "[\n#{o.indent}"
answer.push @makeCode "\n#{@tab}]"
else
for fragment in answer when fragment.isHereComment
fragment.code = "#{fragment.code} "
answer.unshift @makeCode '['
answer.push @makeCode ']'
answer
assigns: (name) ->
for obj in @objects when obj.assigns name then return yes
no
eachName: (iterator) ->
for obj in @objects
obj = obj.unwrapAll()
obj.eachName iterator
如果此数组是赋值的左侧,则其所有子节点也是如此。
propagateLhs: (setLhs) ->
@lhs = yes if setLhs
return unless @lhs
for object in @objects
object.lhs = yes if object instanceof Splat or object instanceof Expansion
unwrappedObject = object.unwrapAll()
if unwrappedObject instanceof Arr or unwrappedObject instanceof Obj
unwrappedObject.propagateLhs yes
else if unwrappedObject instanceof Assign
unwrappedObject.nestedLhs = yes
astType: ->
if @lhs
'ArrayPattern'
else
'ArrayExpression'
astProperties: (o) ->
return
elements:
object.ast(o, LEVEL_LIST) for object in @objects
CoffeeScript 类定义。用其名称、可选的超类和主体初始化一个 Class。
exports.Class = class Class extends Base
children: ['variable', 'parent', 'body']
constructor: (@variable, @parent, @body) ->
super()
unless @body?
@body = new Block
@hasGeneratedBody = yes
compileNode: (o) ->
@name = @determineName()
executableBody = @walkBody o
特殊处理以允许 class expr.A extends A
声明
parentName = @parent.base.value if @parent instanceof Value and not @parent.hasProperties()
@hasNameClash = @name? and @name is parentName
node = @
if executableBody or @hasNameClash
node = new ExecutableClassBody node, executableBody
else if not @name? and o.level is LEVEL_TOP
匿名类仅在表达式中有效
node = new Parens node
if @boundMethods.length and @parent
@variable ?= new IdentifierLiteral o.scope.freeVariable '_class'
[@variable, @variableRef] = @variable.cache o unless @variableRef?
if @variable
node = new Assign @variable, node, null, { @moduleDeclaration }
@compileNode = @compileClassDeclaration
try
return node.compileToFragments o
finally
delete @compileNode
compileClassDeclaration: (o) ->
@ctor ?= @makeDefaultConstructor() if @externalCtor or @boundMethods.length
@ctor?.noReturn = true
@proxyBoundMethods() if @boundMethods.length
o.indent += TAB
result = []
result.push @makeCode "class "
result.push @makeCode @name if @name
@compileCommentFragments o, @variable, result if @variable?.comments?
result.push @makeCode ' ' if @name
result.push @makeCode('extends '), @parent.compileToFragments(o)..., @makeCode ' ' if @parent
result.push @makeCode '{'
unless @body.isEmpty()
@body.spaced = true
result.push @makeCode '\n'
result.push @body.compileToFragments(o, LEVEL_TOP)...
result.push @makeCode "\n#{@tab}"
result.push @makeCode '}'
result
找出此类的适当名称
determineName: ->
return null unless @variable
[..., tail] = @variable.properties
node = if tail
tail instanceof Access and tail.name
else
@variable.base
unless node instanceof IdentifierLiteral or node instanceof PropertyName
return null
name = node.value
unless tail
message = isUnassignable name
@variable.error message if message
if name in JS_FORBIDDEN then "_#{name}" else name
walkBody: (o) ->
@ctor = null
@boundMethods = []
executableBody = null
initializer = []
{ expressions } = @body
i = 0
for expression in expressions.slice()
if expression instanceof Value and expression.isObject true
{ properties } = expression.base
exprs = []
end = 0
start = 0
pushSlice = -> exprs.push new Value new Obj properties[start...end], true if end > start
while assign = properties[end]
if initializerExpression = @addInitializerExpression assign, o
pushSlice()
exprs.push initializerExpression
initializer.push initializerExpression
start = end + 1
end++
pushSlice()
expressions[i..i] = exprs
i += exprs.length
else
if initializerExpression = @addInitializerExpression expression, o
initializer.push initializerExpression
expressions[i] = initializerExpression
i += 1
for method in initializer when method instanceof Code
if method.ctor
method.error 'Cannot define more than one constructor in a class' if @ctor
@ctor = method
else if method.isStatic and method.bound
method.context = @name
else if method.bound
@boundMethods.push method
return unless o.compiling
if initializer.length isnt expressions.length
@body.expressions = (expression.hoist() for expression in initializer)
new Block expressions
向类初始化程序添加表达式
这是确定类主体中的表达式应该出现在初始化程序中还是可执行主体中的关键方法。如果给定的 node
在类主体中有效,则该方法将返回一个(新的、修改的或相同的)节点以包含在类初始化程序中,否则将不返回任何内容,并且该节点将出现在可执行主体中。
在撰写本文时,只有方法(实例和静态)在 ES 类初始化程序中有效。随着新的 ES 类功能(如类字段)达到第 4 阶段,此方法将需要更新以支持它们。我们还允许 PassthroughLiteral
(反引号表达式)在初始化程序中作为 ES 功能的逃生舱口,这些功能尚未实现(例如,通过 get
和 set
关键字定义的 getter 和 setter,而不是 Object.defineProperty
方法)。
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
walkBody: ->
directives = []
index = 0
while expr = @body.expressions[index]
break unless expr instanceof Value and expr.isString()
if expr.hoisted
index++
else
directives.push @body.expressions.splice(index, 1)...
@traverseChildren false, (child) =>
return false if child instanceof Class or child instanceof HoistTarget
cont = true
if child instanceof Block
for node, i in child.expressions
if node instanceof Value and node.isObject(true)
cont = false
child.expressions[i] = @addProperties node.base.properties
else if node instanceof Assign and node.variable.looksStatic @name
node.value.isStatic = yes
child.expressions = flatten child.expressions
cont
directives
setContext: ->
@body.traverseChildren false, (node) =>
if node instanceof ThisLiteral
node.value = @name
else if node instanceof Code and node.bound and (node.isStatic or not node.name)
node.context = @name
为无效的 ES 属性创建类/原型赋值
addProperties: (assigns) ->
result = for assign in assigns
variable = assign.variable
base = variable?.base
value = assign.value
delete assign.context
if base.value is 'constructor'
if value instanceof Code
base.error 'constructors must be defined at the top level of a class body'
类范围尚不可用,因此返回赋值以供以后更新
assign = @externalCtor = new Assign new Value, value
else if not assign.variable.this
name =
if base instanceof ComputedPropertyName
new Index base.value
else
new (if base.shouldCache() then Index else Access) base
prototype = new Access new PropertyName 'prototype'
variable = new Value new ThisLiteral(), [ prototype, name ]
assign.variable = variable
else if assign.value instanceof Code
assign.value.isStatic = true
assign
compact result
exports.ClassProperty = class ClassProperty extends Base
constructor: ({@name, @isStatic, @staticClassName, @value, @operatorToken}) ->
super()
children: ['name', 'value', 'staticClassName']
isStatement: YES
astProperties: (o) ->
return
key: @name.ast o, LEVEL_LIST
value: @value.ast o, LEVEL_LIST
static: !!@isStatic
computed: @name instanceof Index or @name instanceof ComputedPropertyName
operator: @operatorToken?.value ? '='
staticClassName: @staticClassName?.ast(o) ? null
exports.ClassPrototypeProperty = class ClassPrototypeProperty extends Base
constructor: ({@name, @value}) ->
super()
children: ['name', 'value']
isStatement: YES
astProperties: (o) ->
return
key: @name.ast o, LEVEL_LIST
value: @value.ast o, LEVEL_LIST
computed: @name instanceof ComputedPropertyName or @name instanceof StringWithInterpolations
exports.ModuleDeclaration = class ModuleDeclaration extends Base
constructor: (@clause, @source, @assertions) ->
super()
@checkSource()
children: ['clause', 'source', 'assertions']
isStatement: YES
jumps: THIS
makeReturn: THIS
checkSource: ->
if @source? and @source instanceof StringWithInterpolations
@source.error 'the name of the module to be imported from must be an uninterpolated string'
checkScope: (o, moduleDeclarationType) ->
TODO:在 AST 生成期间(以及编译为 JS 时)标记此错误将是合适的。但是 o.indent
在 AST 生成期间没有被跟踪,并且似乎没有当前的替代方法来跟踪我们是否处于“程序顶层”。
if o.indent.length isnt 0
@error "#{moduleDeclarationType} statements must be at top-level scope"
astAssertions: (o) ->
if @assertions?.properties?
@assertions.properties.map (assertion) =>
{ start, end, loc, left, right } = assertion.ast(o)
{ type: 'ImportAttribute', start, end, loc, key: left, value: right }
else
[]
exports.ImportDeclaration = class ImportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'import'
o.importedSymbols = []
code = []
code.push @makeCode "#{@tab}import "
code.push @clause.compileNode(o)... if @clause?
if @source?.value?
code.push @makeCode ' from ' unless @clause is null
code.push @makeCode @source.value
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
astNode: (o) ->
o.importedSymbols = []
super o
astProperties: (o) ->
ret =
specifiers: @clause?.ast(o) ? []
source: @source.ast o
assertions: @astAssertions(o)
ret.importKind = 'value' if @clause
ret
exports.ImportClause = class ImportClause extends Base
constructor: (@defaultBinding, @namedImports) ->
super()
children: ['defaultBinding', 'namedImports']
compileNode: (o) ->
code = []
if @defaultBinding?
code.push @defaultBinding.compileNode(o)...
code.push @makeCode ', ' if @namedImports?
if @namedImports?
code.push @namedImports.compileNode(o)...
code
astNode: (o) ->
ImportClause
的 AST 是将成为 ImportDeclaration
AST 的 specifiers
属性的非嵌套导入规范符列表
compact flatten [
@defaultBinding?.ast o
@namedImports?.ast o
]
exports.ExportDeclaration = class ExportDeclaration extends ModuleDeclaration
compileNode: (o) ->
@checkScope o, 'export'
@checkForAnonymousClassExport()
code = []
code.push @makeCode "#{@tab}export "
code.push @makeCode 'default ' if @ instanceof ExportDefaultDeclaration
if @ not instanceof ExportDefaultDeclaration and
(@clause instanceof Assign or @clause instanceof Class)
code.push @makeCode 'var '
@clause.moduleDeclaration = 'export'
if @clause.body? and @clause.body instanceof Block
code = code.concat @clause.compileToFragments o, LEVEL_TOP
else
code = code.concat @clause.compileNode o
if @source?.value?
code.push @makeCode " from #{@source.value}"
if @assertions?
code.push @makeCode ' assert '
code.push @assertions.compileToFragments(o)...
code.push @makeCode ';'
code
防止导出匿名类;所有导出的成员都必须命名
checkForAnonymousClassExport: ->
if @ not instanceof ExportDefaultDeclaration and @clause instanceof Class and not @clause.variable
@clause.error 'anonymous classes cannot be exported'
astNode: (o) ->
@checkForAnonymousClassExport()
super o
exports.ExportNamedDeclaration = class ExportNamedDeclaration extends ExportDeclaration
astProperties: (o) ->
ret =
source: @source?.ast(o) ? null
assertions: @astAssertions(o)
exportKind: 'value'
clauseAst = @clause.ast o
if @clause instanceof ExportSpecifierList
ret.specifiers = clauseAst
ret.declaration = null
else
ret.specifiers = []
ret.declaration = clauseAst
ret
exports.ExportDefaultDeclaration = class ExportDefaultDeclaration extends ExportDeclaration
astProperties: (o) ->
return
declaration: @clause.ast o
assertions: @astAssertions(o)
exports.ExportAllDeclaration = class ExportAllDeclaration extends ExportDeclaration
astProperties: (o) ->
return
source: @source.ast o
assertions: @astAssertions(o)
exportKind: 'value'
exports.ModuleSpecifierList = class ModuleSpecifierList extends Base
constructor: (@specifiers) ->
super()
children: ['specifiers']
compileNode: (o) ->
code = []
o.indent += TAB
compiledList = (specifier.compileToFragments o, LEVEL_LIST for specifier in @specifiers)
if @specifiers.length isnt 0
code.push @makeCode "{\n#{o.indent}"
for fragments, index in compiledList
code.push @makeCode(",\n#{o.indent}") if index
code.push fragments...
code.push @makeCode "\n}"
else
code.push @makeCode '{}'
code
astNode: (o) ->
specifier.ast(o) for specifier in @specifiers
exports.ImportSpecifierList = class ImportSpecifierList extends ModuleSpecifierList
exports.ExportSpecifierList = class ExportSpecifierList extends ModuleSpecifierList
exports.ModuleSpecifier = class ModuleSpecifier extends Base
constructor: (@original, @alias, @moduleDeclarationType) ->
super()
if @original.comments or @alias?.comments
@comments = []
@comments.push @original.comments... if @original.comments
@comments.push @alias.comments... if @alias?.comments
进入局部范围的变量的名称
@identifier = if @alias? then @alias.value else @original.value
children: ['original', 'alias']
compileNode: (o) ->
@addIdentifierToScope o
code = []
code.push @makeCode @original.value
code.push @makeCode " as #{@alias.value}" if @alias?
code
addIdentifierToScope: (o) ->
o.scope.find @identifier, @moduleDeclarationType
astNode: (o) ->
@addIdentifierToScope o
super o
exports.ImportSpecifier = class ImportSpecifier extends ModuleSpecifier
constructor: (imported, local) ->
super imported, local, 'import'
addIdentifierToScope: (o) ->
根据规范,符号不能多次导入(例如,import { foo, foo } from 'lib'
无效)
if @identifier in o.importedSymbols or o.scope.check(@identifier)
@error "'#{@identifier}' has already been declared"
else
o.importedSymbols.push @identifier
super o
astProperties: (o) ->
originalAst = @original.ast o
return
imported: originalAst
local: @alias?.ast(o) ? originalAst
importKind: null
exports.ImportDefaultSpecifier = class ImportDefaultSpecifier extends ImportSpecifier
astProperties: (o) ->
return
local: @original.ast o
exports.ImportNamespaceSpecifier = class ImportNamespaceSpecifier extends ImportSpecifier
astProperties: (o) ->
return
local: @alias.ast o
exports.ExportSpecifier = class ExportSpecifier extends ModuleSpecifier
constructor: (local, exported) ->
super local, exported, 'export'
astProperties: (o) ->
originalAst = @original.ast o
return
local: originalAst
exported: @alias?.ast(o) ? originalAst
exports.DynamicImport = class DynamicImport extends Base
compileNode: ->
[@makeCode 'import']
astType: -> 'Import'
exports.DynamicImportCall = class DynamicImportCall extends Call
compileNode: (o) ->
@checkArguments()
super o
checkArguments: ->
unless 1 <= @args.length <= 2
@error 'import() accepts either one or two arguments'
astNode: (o) ->
@checkArguments()
super o
Assign 用于将局部变量赋值给值,或设置对象的属性 - 包括在对象字面量中。
exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options = {}) ->
super()
{@param, @subpattern, @operatorToken, @moduleDeclaration, @originalContext = @context} = options
@propagateLhs()
children: ['variable', 'value']
isAssignable: YES
isStatement: (o) ->
o?.level is LEVEL_TOP and @context? and (@moduleDeclaration or "?" in @context)
checkNameAssignability: (o, varBase) ->
if o.scope.type(varBase.value) is 'import'
varBase.error "'#{varBase.value}' is read-only"
assigns: (name) ->
@[if @context is 'object' then 'value' else 'variable'].assigns name
unfoldSoak: (o) ->
unfoldSoak o, this, 'variable'
addScopeVariables: (o, {
在 AST 生成期间,我们需要允许将这些结构赋值给在编译为 JS 期间被认为是“不可赋值”的结构,同时仍然标记诸如 [null] = b
之类的东西。
allowAssignmentToExpansion = no,
allowAssignmentToNontrailingSplat = no,
allowAssignmentToEmptyArray = no,
allowAssignmentToComplexSplat = no
} = {}) ->
return unless not @context or @context is '**='
varBase = @variable.unwrapAll()
if not varBase.isAssignable {
allowExpansion: allowAssignmentToExpansion
allowNontrailingSplat: allowAssignmentToNontrailingSplat
allowEmptyArray: allowAssignmentToEmptyArray
allowComplexSplat: allowAssignmentToComplexSplat
}
@variable.error "'#{@variable.compile o}' can't be assigned"
varBase.eachName (name) =>
return if name.hasProperties?()
message = isUnassignable name.value
name.error message if message
moduleDeclaration
可以是 'import'
或 'export'
。
@checkNameAssignability o, name
if @moduleDeclaration
o.scope.add name.value, @moduleDeclaration
name.isDeclaration = yes
else if @param
o.scope.add name.value,
if @param is 'alwaysDeclare'
'var'
else
'param'
else
alreadyDeclared = o.scope.find name.value
name.isDeclaration ?= not alreadyDeclared
如果此赋值标识符附加了一个或多个 herecomments,则将它们输出为声明行的一部分(除非其他 herecomments 已经在此处暂存),以与 Flow 类型兼容。如果此赋值是针对类,例如 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
对象剩余属性不可赋值:{{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
,我们可以将数组拆分成两个简单的子数组。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
统计所有 Splats
:[a, b, c…, d, e]
splats = (i for obj, i in objects when obj instanceof Splat)
统计所有 Expansions
:[a, b, …, c, d]
expans = (i for obj, i in objects when obj instanceof Expansion)
合并 splats 和 expansions。
splatsAndExpans = [splats..., expans...]
if splatsAndExpans.length > 1
对 ‘splatsAndExpans’ 进行排序,以便我们可以在第一个不允许的标记处显示错误。
objects[splatsAndExpans.sort()[1]].error "multiple splats/expansions are disallowed in an assignment"
{splats, expans, splatsAndExpans}
在编译条件赋值时,注意确保操作数只被评估一次,即使我们必须多次引用它们。
compileConditional: (o) ->
[left, right] = @variable.cacheReference o
不允许对未定义的变量进行条件赋值。
if not left.properties.length and left.base instanceof Literal and
left.base not instanceof ThisLiteral and not o.scope.check left.base.value
@throwUnassignableConditionalError left.base.value
if "?" in @context
o.isExistentialEquals = true
new If(new Existence(left), right, type: 'if').addElse(new Assign(right, @value, '=')).compileToFragments o
else
fragments = new Op(@context[...-1], left, new Assign(right, @value, '=')).compileToFragments o
if o.level <= LEVEL_LIST then fragments else @wrapInParentheses fragments
将像 a //= b
这样的特殊数学赋值运算符转换为等效的扩展形式 a = a ** b
,然后进行编译。
compileSpecialMath: (o) ->
[left, right] = @variable.cacheReference o
new Assign(left, new Op(@context[...-1], right, @value)).compileToFragments o
使用 JavaScript 的 Array#splice
方法编译来自数组拼接字面量的赋值。
compileSplice: (o) ->
{range: {from, to, exclusive}} = @variable.properties.pop()
unwrappedVar = @variable.unwrapAll()
if unwrappedVar.comments
moveComments unwrappedVar, @
delete @variable.comments
name = @variable.compile o
if from
[fromDecl, fromRef] = @cacheToCodeFragments from.cache o, LEVEL_OP
else
fromDecl = fromRef = '0'
if to
if from?.isNumber() and to.isNumber()
to = to.compile(o) - fromRef
to += 1 unless exclusive
else
to = to.compile(o, LEVEL_ACCESS) + ' - ' + fromRef
to += ' + 1' unless exclusive
else
to = "9e9"
[valDef, valRef] = @value.cache o, LEVEL_LIST
answer = [].concat @makeCode("#{utility 'splice', o}.apply(#{name}, [#{fromDecl}, #{to}].concat("), valDef, @makeCode(")), "), valRef
if o.level > LEVEL_TOP then @wrapInParentheses answer else answer
eachName: (iterator) ->
@variable.unwrapAll().eachName iterator
isDefaultAssignment: -> @param or @nestedLhs
propagateLhs: ->
return unless @variable?.isArray?() or @variable?.isObject?()
这是赋值的左侧;让 Arr
和 Obj
知道这一点,以便这些节点知道它们可以作为解构变量进行赋值。
@variable.base.propagateLhs yes
throwUnassignableConditionalError: (name) ->
@variable.error "the variable \"#{name}\" can't be assigned with #{@context} because it has not been declared before"
isConditional: ->
@context in ['||=', '&&=', '?=']
isStatementAst: NO
astNode: (o) ->
@disallowLoneExpansion()
@getAndCheckSplatsAndExpansions()
if @isConditional()
variable = @variable.unwrap()
if variable instanceof IdentifierLiteral and not o.scope.check variable.value
@throwUnassignableConditionalError variable.value
@addScopeVariables o, allowAssignmentToExpansion: yes, allowAssignmentToNontrailingSplat: yes, allowAssignmentToEmptyArray: yes, allowAssignmentToComplexSplat: yes
super o
astType: ->
if @isDefaultAssignment()
'AssignmentPattern'
else
'AssignmentExpression'
astProperties: (o) ->
ret =
right: @value.ast o, LEVEL_LIST
left: @variable.ast o, LEVEL_LIST
unless @isDefaultAssignment()
ret.operator = @originalContext ? '='
ret
exports.FuncGlyph = class FuncGlyph extends Base
constructor: (@glyph) ->
super()
函数定义。这是唯一创建新作用域的节点。在遍历函数体内容时,Code 没有子节点——它们在内部作用域中。
exports.Code = class Code extends Base
constructor: (params, body, @funcGlyph, @paramStart) ->
super()
@params = params or []
@body = body or new Block
@bound = @funcGlyph?.glyph is '=>'
@isGenerator = no
@isAsync = no
@isMethod = no
@body.traverseChildren no, (node) =>
if (node instanceof Op and node.isYield()) or node instanceof YieldReturn
@isGenerator = yes
if (node instanceof Op and node.isAwait()) or node instanceof AwaitReturn
@isAsync = yes
if node instanceof For and node.isAwait()
@isAsync = yes
@propagateLhs()
children: ['params', 'body']
isStatement: -> @isMethod
jumps: NO
makeScope: (parentScope) -> new Scope parentScope, @body, this
编译会创建一个新的作用域,除非明确要求与外部作用域共享。通过将这些参数设置为函数定义中的最后一个参数来处理参数列表中的 splat 参数,如 ES2015 规范要求。如果 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
解析参数,将它们添加到要放入函数定义中的参数列表中;并处理 splats 或 expansions,包括向函数体添加表达式以声明所有本来应该在 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
短路 traverseChildren
方法,以防止它跨越作用域边界,除非 crossScope
为 true
。
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
此参数是否使用了 ...
?(每个函数只允许一个这样的参数。)
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 ? []
保存原始对象。
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
编译一元 Op。
compileUnary: (o) ->
parts = []
op = @operator
parts.push [@makeCode op]
if op is '!' and @first instanceof Existence
@first.negated = not @first.negated
return @first.compileToFragments o
if o.level >= LEVEL_ACCESS
return (new Parens this).compileToFragments o
plusMinus = op in ['+', '-']
parts.push [@makeCode(' ')] if op in ['typeof', 'delete'] or
plusMinus and @first instanceof Op and @first.operator is op
if plusMinus and @first instanceof Op
@first = new Parens @first
parts.push @first.compileToFragments o, LEVEL_OP
parts.reverse() if @flip
@joinFragmentArrays parts, ''
compileContinuation: (o) ->
parts = []
op = @operator
@checkContinuation o unless @isAwait()
if 'expression' in Object.keys(@first) and not (@first instanceof Throw)
parts.push @first.expression.compileToFragments o, LEVEL_OP if @first.expression?
else
parts.push [@makeCode "("] if o.level >= LEVEL_PAREN
parts.push [@makeCode op]
parts.push [@makeCode " "] if @first.base?.value isnt ''
parts.push @first.compileToFragments o, LEVEL_OP
parts.push [@makeCode ")"] if o.level >= LEVEL_PAREN
@joinFragmentArrays parts, ''
checkContinuation: (o) ->
unless o.scope.parent?
@error "#{@operator} can only occur inside functions"
if o.scope.method?.bound and o.scope.method.isGenerator
@error 'yield cannot occur inside bound (fat arrow) functions'
compileFloorDivision: (o) ->
floor = new Value new IdentifierLiteral('Math'), [new Access new PropertyName 'floor']
second = if @second.shouldCache() then new Parens @second else @second
div = new Op '/', @first, second
new Call(floor, [div]).compileToFragments o
compileModulo: (o) ->
mod = new Value new Literal utility 'modulo', o
new Call(mod, [@first, @second]).compileToFragments o
toString: (idt) ->
super idt, @constructor.name + ' ' + @operator
checkDeleteOperand: (o) ->
if @operator is 'delete' and o.scope.check(@first.unwrapAll().value)
@error 'delete operand may not be argument or var'
astNode: (o) ->
@checkContinuation o if @isYield()
@checkDeleteOperand o
super o
astType: ->
return 'AwaitExpression' if @isAwait()
return 'YieldExpression' if @isYield()
return 'ChainedComparison' if @isChain()
switch @operator
when '||', '&&', '?' then 'LogicalExpression'
when '++', '--' then 'UpdateExpression'
else
if @isUnary() then 'UnaryExpression'
else 'BinaryExpression'
operatorAst: ->
"#{if @invertOperator then "#{@invertOperator} " else ''}#{@originalOperator}"
chainAstProperties: (o) ->
operators = [@operatorAst()]
operands = [@second]
currentOp = @first
loop
operators.unshift currentOp.operatorAst()
operands.unshift currentOp.second
currentOp = currentOp.first
unless currentOp.isChainable()
operands.unshift currentOp
break
return {
operators
operands: (operand.ast(o, LEVEL_OP) for operand in operands)
}
astProperties: (o) ->
return @chainAstProperties(o) if @isChain()
firstAst = @first.ast o, LEVEL_OP
secondAst = @second?.ast o, LEVEL_OP
operatorAst = @operatorAst()
switch
when @isUnary()
argument =
if @isYield() and @first.unwrap().value is ''
null
else
firstAst
return {argument} if @isAwait()
return {
argument
delegate: @operator is 'yield*'
} if @isYield()
return {
argument
operator: operatorAst
prefix: !@flip
}
else
return
left: firstAst
right: secondAst
operator: operatorAst
exports.In = class In extends Base
constructor: (@object, @array) ->
super()
children: ['object', 'array']
invert: NEGATE
compileNode: (o) ->
if @array instanceof Value and @array.isArray() and @array.base.objects.length
for obj in @array.base.objects when obj instanceof Splat
hasSplat = yes
break
compileOrTest
仅当我们有一个没有 splats 的数组字面量时。
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 本身就是一种返回,可以这么说……
makeReturn: THIS
compileNode: (o) ->
fragments = @expression.compileToFragments o, LEVEL_LIST
unshiftAfterComments fragments, @makeCode 'throw '
fragments.unshift @makeCode @tab
fragments.push @makeCode ';'
fragments
astType: -> 'ThrowStatement'
astProperties: (o) ->
return
argument: @expression.ast o, LEVEL_LIST
检查变量是否存在——不为 null
也不为 undefined
。这类似于 Ruby 中的 .nil?
,并且避免了必须查阅 JavaScript 真值表。可以选择只检查变量是否不为 undefined
。
exports.Existence = class Existence extends Base
constructor: (@expression, onlyNotUndefined = no) ->
super()
@comparisonTarget = if onlyNotUndefined then 'undefined' else 'null'
salvagedComments = []
@expression.traverseChildren yes, (child) ->
if child.comments
for comment in child.comments
salvagedComments.push comment unless comment in salvagedComments
delete child.comments
attachCommentsToNode salvagedComments, @
moveComments @expression, @
children: ['expression']
invert: NEGATE
compileNode: (o) ->
@expression.front = @front
code = @expression.compile o, LEVEL_OP
if @expression.unwrap() instanceof IdentifierLiteral and not o.scope.check code
[cmp, cnj] = if @negated then ['===', '||'] else ['!==', '&&']
code = "typeof #{code} #{cmp} \"undefined\"" + if @comparisonTarget isnt 'undefined' then " #{cnj} #{code} #{cmp} #{@comparisonTarget}" else ''
else
我们明确地希望在与 null
进行比较时使用松散相等 (==
),以便存在性检查大致对应于对真值的检查。不要将此更改为 ===
用于 null
,因为这将破坏大量现有代码。但是,当仅与 undefined
进行比较时,我们希望使用 ===
,因为这种用例是为了与 ES2015+ 默认值保持一致,这些默认值仅在变量为 undefined
(但不是 null
)时才会被赋值。
cmp = if @comparisonTarget is 'null'
if @negated then '==' else '!='
else # `undefined`
if @negated then '===' else '!=='
code = "#{code} #{cmp} #{@comparisonTarget}"
[@makeCode(if o.level <= LEVEL_COND then code else "(#{code})")]
astType: -> 'UnaryExpression'
astProperties: (o) ->
return
argument: @expression.ast o
operator: '?'
prefix: no
exports.Parens = class Parens extends Base
constructor: (@body) ->
super()
children: ['body']
unwrap: -> @body
shouldCache: -> @body.shouldCache()
compileNode: (o) ->
expr = @body.unwrap()
如果这些括号包含一个 IdentifierLiteral
后跟一个块注释,则输出括号(或者换句话说,不要优化掉这些冗余括号)。这是因为 Flow 在某些情况下需要括号来区分标识符后跟基于注释的类型注释和 JavaScript 标签。
shouldWrapComment = expr.comments?.some(
(comment) -> comment.here and not comment.unshift and not comment.newLine)
if expr instanceof Value and expr.isAtomic() and not @jsxAttribute and not shouldWrapComment
expr.front = @front
return expr.compileToFragments o
fragments = expr.compileToFragments o, LEVEL_PAREN
bare = o.level < LEVEL_OP and not shouldWrapComment and (
expr instanceof Op and not expr.isInOperator() or expr.unwrap() instanceof Call or
(expr instanceof For and expr.returns)
) and (o.level < LEVEL_COND or fragments.length <= 3)
return @wrapInBraces fragments if @jsxAttribute
if bare then fragments else @wrapInParentheses fragments
astNode: (o) -> @body.unwrap().ast o, LEVEL_PAREN
exports.StringWithInterpolations = class StringWithInterpolations extends Base
constructor: (@body, {@quote, @startQuote, @jsxAttribute} = {}) ->
super()
@fromStringLiteral: (stringLiteral) ->
updatedString = stringLiteral.withoutQuotesInLocationData()
updatedStringValue = new Value(updatedString).withLocationDataFrom updatedString
new StringWithInterpolations Block.wrap([updatedStringValue]), quote: stringLiteral.quote, jsxAttribute: stringLiteral.jsxAttribute
.withLocationDataFrom stringLiteral
children: ['body']
unwrap
返回 this
以阻止祖先节点深入获取 @body,并使用 @body.compileNode。StringWithInterpolations.compileNode
是输出插值字符串作为代码的自定义逻辑。
unwrap: -> this
shouldCache: -> @body.shouldCache()
extractElements: (o, {includeInterpolationWrappers, isJsx} = {}) ->
假设 expr
是 Block
expr = @body.unwrap()
elements = []
salvagedComments = []
expr.traverseChildren no, (node) =>
if node instanceof StringLiteral
if node.comments
salvagedComments.push node.comments...
delete node.comments
elements.push node
return yes
else if node instanceof Interpolation
if salvagedComments.length isnt 0
for comment in salvagedComments
comment.unshift = yes
comment.newLine = yes
attachCommentsToNode salvagedComments, node
if (unwrapped = node.expression?.unwrapAll()) instanceof PassthroughLiteral and unwrapped.generated and not (isJsx and o.compiling)
if o.compiling
commentPlaceholder = new StringLiteral('').withLocationDataFrom node
commentPlaceholder.comments = unwrapped.comments
(commentPlaceholder.comments ?= []).push node.comments... if node.comments
elements.push new Value commentPlaceholder
else
empty = new Interpolation().withLocationDataFrom node
empty.comments = node.comments
elements.push empty
else if node.expression or includeInterpolationWrappers
(node.expression?.comments ?= []).push node.comments... if node.comments
elements.push if includeInterpolationWrappers then node else node.expression
return no
else if node.comments
此节点将被丢弃,但要保留其注释。
if elements.length isnt 0 and elements[elements.length - 1] not instanceof StringLiteral
for comment in node.comments
comment.unshift = no
comment.newLine = yes
attachCommentsToNode node.comments, elements[elements.length - 1]
else
salvagedComments.push node.comments...
delete node.comments
return yes
elements
compileNode: (o) ->
@comments ?= @startQuote?.comments
if @jsxAttribute
wrapped = new Parens new StringWithInterpolations @body
wrapped.jsxAttribute = yes
return wrapped.compileNode o
elements = @extractElements o, isJsx: @jsx
fragments = []
fragments.push @makeCode '`' unless @jsx
for element in elements
if element instanceof StringLiteral
unquotedElementValue = if @jsx then element.unquotedValueForJSX else element.unquotedValueForTemplateLiteral
fragments.push @makeCode unquotedElementValue
else
fragments.push @makeCode '$' unless @jsx
code = element.compileToFragments(o, LEVEL_PAREN)
if not @isNestedTag(element) or
code.some((fragment) -> fragment.comments?.some((comment) -> comment.here is no))
code = @wrapInBraces code
将{
和 }
片段标记为由此 StringWithInterpolations
节点生成,以便 compileComments
知道将它们视为边界。但是,如果所有包含的注释都是 /* */
注释,则大括号是不必要的。不要相信 fragment.type
,当此编译器被缩小时,它可能会报告缩小的变量名。
code[0].isStringWithInterpolations = yes
code[code.length - 1].isStringWithInterpolations = yes
fragments.push code...
fragments.push @makeCode '`' unless @jsx
fragments
isNestedTag: (element) ->
call = element.unwrapAll?()
@jsx and call instanceof JSXElement
astType: -> 'TemplateLiteral'
astProperties: (o) ->
elements = @extractElements o, includeInterpolationWrappers: yes
[..., last] = elements
quasis = []
expressions = []
for element, index in elements
if element instanceof StringLiteral
quasis.push new TemplateElement(
element.originalValue
tail: element is last
).withLocationDataFrom(element).ast o
else # Interpolation
{expression} = element
node =
unless expression?
emptyInterpolation = new EmptyInterpolation()
emptyInterpolation.locationData = emptyExpressionLocationData {
interpolationNode: element
openingBrace: '#{'
closingBrace: '}'
}
emptyInterpolation
else
expression.unwrapAll()
expressions.push astAsBlockIfNeeded node, o
{expressions, quasis, @quote}
exports.TemplateElement = class TemplateElement extends Base
constructor: (@value, {@tail} = {}) ->
super()
astProperties: ->
return
value:
raw: @value
tail: !!@tail
exports.Interpolation = class Interpolation extends Base
constructor: (@expression) ->
super()
children: ['expression']
表示空插值的内容(例如 #{}
)。仅在 AST 生成期间使用。
exports.EmptyInterpolation = class EmptyInterpolation extends Base
constructor: ->
super()
CoffeeScript 对 for 循环的替代是我们的数组和对象推导,它们在这里编译成 for 循环。它们也充当表达式,能够返回每次过滤迭代的结果。
与 Python 数组推导不同,它们可以是多行的,并且可以将循环的当前索引作为第二个参数传递。与 Ruby 块不同,您可以在一次传递中进行映射和过滤。
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']
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]
制表符是两个空格,用于美化打印。
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
有时在编译节点时,我们希望在片段数组的开头插入一个片段;但是,如果开头有一个或多个注释片段,我们希望在这些片段之后但在任何非注释片段之前插入此片段。
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?()
如果浸泡,则展开节点的子节点,然后将节点塞入创建的 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
获取两个节点的位置数据,并返回一个新的 locationData
对象,该对象包含两个节点的位置数据。因此,新的 first_line
值将是两个节点的 first_line
值中较早的值,新的 last_column
值将是两个节点的 last_column
值中较晚的值,等等。
如果您只想使用第二个节点的开始或结束位置数据来扩展第一个节点的位置数据,请传递 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]
]
)
获取两个 AST 节点或两个 AST 节点的位置数据对象,并返回一个新的位置数据对象,该对象包含两个节点的位置数据。因此,新的 start
值将是两个节点的 start
值中较早的值,新的 end
值将是两个节点的 end
值中较晚的值,等等。
如果您只想使用第二个节点的开始或结束位置数据来扩展第一个节点的位置数据,请传递 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 表达式大括号之间的空格相对应的标记,因此通过从插值的 locationData 中修剪大括号来拼凑 locationData。从技术上讲,如果结束大括号前面是换行符,则此处的 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
]