UNPKG

coffee-script

Version:

Unfancy JavaScript

1,440 lines (1,222 loc) 60 kB
# `nodes.coffee` contains all of the node classes for the syntax tree. Most # nodes are created as the result of actions in the [grammar](grammar.html), # but some are created by other nodes as a method of code generation. To convert # the syntax tree into a string of JavaScript code, call `compile()` on the root. {Scope} = require './scope' # Import the helpers we plan to use. {compact, flatten, extend, merge, del, starts, ends, last} = require './helpers' exports.extend = extend # for parser # Constant functions for nodes that don't need customization. YES = -> yes NO = -> no THIS = -> this NEGATE = -> @negated = not @negated; this #### Base # The **Base** is the abstract base class for all nodes in the syntax tree. # Each subclass implements the `compileNode` method, which performs the # code generation for that node. To compile a node to JavaScript, # call `compile` on it, which wraps `compileNode` in some generic extra smarts, # to know when the generated code needs to be wrapped up in a closure. # An options hash is passed and cloned throughout, containing information about # the environment from higher in the tree (such as if a returned value is # being requested by the surrounding function), information about the current # scope, and indentation level. exports.Base = class Base # Common logic for determining whether to wrap this node in a closure before # compiling it, or to compile directly. We need to wrap if this node is a # *statement*, and it's not a *pureStatement*, and we're not at # the top level of a block (which would be unnecessary), and we haven't # already been asked to return the result (because statements know how to # return results). compile: (o, lvl) -> o = extend {}, o o.level = lvl if lvl node = @unfoldSoak(o) or this node.tab = o.indent if o.level is LEVEL_TOP or node.isPureStatement() or not node.isStatement(o) node.compileNode o else node.compileClosure o # Statements converted into expressions via closure-wrapping share a scope # object with their parent closure, to preserve the expected lexical scope. compileClosure: (o) -> if @containsPureStatement() throw SyntaxError 'cannot include a pure statement in an expression.' o.sharedScope = o.scope Closure.wrap(this).compileNode o # If the code generation wishes to use the result of a complex expression # in multiple places, ensure that the expression is only ever evaluated once, # by assigning it to a temporary variable. Pass a level to precompile. cache: (o, level, reused) -> unless @isComplex() ref = if level then @compile o, level else this [ref, ref] else ref = new Literal reused or o.scope.freeVariable 'ref' sub = new Assign ref, this if level then [sub.compile(o, level), ref.value] else [sub, ref] # Compile to a source/variable pair suitable for looping. compileLoopReference: (o, name) -> src = tmp = @compile o, LEVEL_LIST unless -Infinity < +src < Infinity or IDENTIFIER.test(src) and o.scope.check(src, yes) src = "#{ tmp = o.scope.freeVariable name } = #{src}" [src, tmp] # Construct a node that returns the current node's result. # Note that this is overridden for smarter behavior for # many statement nodes (e.g. If, For)... makeReturn: -> new Return this # Does this node, or any of its children, contain a node of a certain kind? # Recursively traverses down the *children* of the nodes, yielding to a block # and returning true when the block finds a match. `contains` does not cross # scope boundaries. contains: (pred) -> contains = no @traverseChildren no, (node) -> if pred node contains = yes return no contains # Is this node of a certain type, or does it contain the type? containsType: (type) -> this instanceof type or @contains (node) -> node instanceof type # Convenience for the most common use of contains. Does the node contain # a pure statement? containsPureStatement: -> @isPureStatement() or @contains (node) -> node.isPureStatement() # `toString` representation of the node, for inspecting the parse tree. # This is what `coffee --nodes` prints out. toString: (idt = '', name = @constructor.name) -> tree = '\n' + idt + name tree += '?' if @soak @eachChild (node) -> tree += node.toString idt + TAB tree # Passes each child to a function, breaking when the function returns `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) -> return false if func(child) is false child.traverseChildren crossScope, func invert: -> new Op '!', this unwrapAll: -> node = this continue until node is node = node.unwrap() node # Default implementations of the common node properties and methods. Nodes # will override these with custom logic, if needed. children: [] isStatement : NO isPureStatement : NO isComplex : YES isChainable : NO isAssignable : NO unwrap : THIS unfoldSoak : NO # Is this node used to assign a certain variable? assigns: NO #### Expressions # The expressions body is the list of expressions that forms the body of an # indented block of code -- the implementation of a function, a clause in an # `if`, `switch`, or `try`, and so on... exports.Expressions = class Expressions extends Base constructor: (nodes) -> @expressions = compact flatten nodes or [] children: ['expressions'] # Tack an expression on to the end of this expression list. push: (node) -> @expressions.push node this # Remove and return the last expression of this expression list. pop: -> @expressions.pop() # Add an expression at the beginning of this expression list. unshift: (node) -> @expressions.unshift node this # If this Expressions consists of just a single node, unwrap it by pulling # it back out. unwrap: -> if @expressions.length is 1 then @expressions[0] else this # Is this an empty block of code? isEmpty: -> not @expressions.length isStatement: (o) -> for exp in @expressions when exp.isPureStatement() or exp.isStatement o return yes no # An Expressions node does not return its entire body, rather it # ensures that the final expression is returned. makeReturn: -> len = @expressions.length while len-- expr = @expressions[len] if expr not instanceof Comment @expressions[len] = expr.makeReturn() break this # An **Expressions** is the only node that can serve as the root. compile: (o = {}, level) -> if o.scope then super o, level else @compileRoot o # Compile all expressions within the **Expressions** body. If we need to # return the result, and it's an expression, simply return it. If it's a # statement, ask the statement to do so. compileNode: (o) -> @tab = o.indent top = o.level is LEVEL_TOP codes = [] for node in @expressions node = node.unwrapAll() node = (node.unfoldSoak(o) or node) if top node.front = true code = node.compile o codes.push if node.isStatement o then code else @tab + code + ';' else codes.push node.compile o, LEVEL_LIST return codes.join '\n' if top code = codes.join(', ') or 'void 0' if codes.length > 1 and o.level >= LEVEL_LIST then "(#{code})" else code # If we happen to be the top-level **Expressions**, wrap everything in # a safety closure, unless requested not to. # It would be better not to generate them in the first place, but for now, # clean up obvious double-parentheses. compileRoot: (o) -> o.indent = @tab = if o.bare then '' else TAB o.scope = new Scope null, this, null o.level = LEVEL_TOP code = @compileWithDeclarations o code = code.replace TRAILING_WHITESPACE, '' if o.bare then code else "(function() {\n#{code}\n}).call(this);\n" # Compile the expressions body for the contents of a function, with # declarations of all inner variables pushed up to the top. compileWithDeclarations: (o) -> code = post = '' for exp, i in @expressions exp = exp.unwrap() break unless exp instanceof Comment or exp instanceof Literal o.level = LEVEL_TOP if i rest = @expressions.splice i, @expressions.length code = @compileNode o @expressions = rest post = @compileNode o {scope} = o if not o.globals and o.scope.hasDeclarations this code += "#{@tab}var #{ scope.compiledDeclarations() };\n" if scope.hasAssignments this code += "#{@tab}var #{ multident scope.compiledAssignments(), @tab };\n" code + post # Wrap up the given nodes as an **Expressions**, unless it already happens # to be one. @wrap: (nodes) -> return nodes[0] if nodes.length is 1 and nodes[0] instanceof Expressions new Expressions nodes #### Literal # Literals are static values that can be passed through directly into # JavaScript without translation, such as: strings, numbers, # `true`, `false`, `null`... exports.Literal = class Literal extends Base constructor: (@value) -> makeReturn: -> if @isPureStatement() then this else new Return this # Break and continue must be treated as pure statements -- they lose their # meaning when wrapped in a closure. isPureStatement: -> @value in ['break', 'continue', 'debugger'] isAssignable: -> IDENTIFIER.test @value isComplex: NO assigns: (name) -> name is @value compile: -> if @value.reserved then "\"#{@value}\"" else @value toString: -> ' "' + @value + '"' #### Return # A `return` is a *pureStatement* -- wrapping it in a closure wouldn't # make sense. exports.Return = class Return extends Base constructor: (@expression) -> children: ['expression'] isStatement: YES isPureStatement: YES makeReturn: THIS compile: (o, level) -> expr = @expression?.makeReturn() if expr and expr not instanceof Return then expr.compile o, level else super o, level compileNode: (o) -> o.level = LEVEL_PAREN @tab + "return#{ if @expression then ' ' + @expression.compile o else '' };" #### Value # A value, variable or literal or parenthesized, indexed or dotted into, # or vanilla. exports.Value = class Value extends Base constructor: (base, props, tag) -> return base if not props and base instanceof Value @base = base @properties = props or [] @[tag] = true if tag return this children: ['base', 'properties'] # Add a property access to the list. push: (prop) -> @properties.push prop this hasProperties: -> !!@properties.length # Some boolean checks for the benefit of other nodes. isArray : -> not @properties.length and @base instanceof Arr isComplex : -> @hasProperties() or @base.isComplex() isAssignable : -> @hasProperties() or @base.isAssignable() isSimpleNumber : -> @base instanceof Literal and SIMPLENUM.test @base.value isAtomic : -> for node in @properties.concat @base return no if node.soak or node instanceof Call yes isStatement : (o) -> not @properties.length and @base.isStatement o assigns : (name) -> not @properties.length and @base.assigns name isObject: (onlyGenerated) -> return no if @properties.length (@base instanceof Obj) and (not onlyGenerated or @base.generated) isSplice: -> last(@properties) instanceof Slice makeReturn: -> if @properties.length then super() else @base.makeReturn() # The value can be unwrapped as its inner node, if there are no attached # properties. unwrap: -> if @properties.length then this else @base # A reference has base part (`this` value) and name part. # We cache them separately for compiling complex expressions. # `a()[b()] ?= c` -> `(_base = a())[_name = b()] ? _base[_name] = c` cacheReference: (o) -> name = last @properties if @properties.length < 2 and not @base.isComplex() and not name?.isComplex() return [this, this] # `a` `a.b` base = new Value @base, @properties.slice 0, -1 if base.isComplex() # `a().b` bref = new Literal o.scope.freeVariable 'base' base = new Value new Parens new Assign bref, base return [base, bref] unless name # `a()` if name.isComplex() # `a[b()]` nref = new Literal o.scope.freeVariable 'name' name = new Index new Assign nref, name.index nref = new Index nref [base.push(name), new Value(bref or base.base, [nref or name])] # We compile a value to JavaScript by compiling and joining each property. # Things get much more interesting if the chain of properties has *soak* # operators `?.` interspersed. Then we have to take care not to accidentally # evaluate anything twice when building the soak chain. compileNode: (o) -> @base.front = @front props = @properties code = @base.compile o, if props.length then LEVEL_ACCESS else null code = "(#{code})" if props[0] instanceof Access and @isSimpleNumber() code += prop.compile o for prop in props code # Unfold a soak into an `If`: `a?.b` -> `a.b if a?` unfoldSoak: (o) -> if ifn = @base.unfoldSoak o Array::push.apply ifn.body.properties, @properties return ifn for prop, i in @properties when prop.soak prop.soak = off fst = new Value @base, @properties.slice 0, i snd = new Value @base, @properties.slice i if fst.isComplex() ref = new Literal o.scope.freeVariable 'ref' fst = new Parens new Assign ref, fst snd.base = ref return new If new Existence(fst), snd, soak: on null #### Comment # CoffeeScript passes through block comments as JavaScript block comments # at the same position. exports.Comment = class Comment extends Base constructor: (@comment) -> isPureStatement: YES isStatement: YES makeReturn: THIS compileNode: (o, level) -> code = '/*' + multident(@comment, @tab) + '*/' code = o.indent + code if (level or o.level) is LEVEL_TOP code #### Call # Node for a function invocation. Takes care of converting `super()` calls into # calls against the prototype's function of the same name. exports.Call = class Call extends Base constructor: (variable, @args = [], @soak) -> @isNew = false @isSuper = variable is 'super' @variable = if @isSuper then null else variable children: ['variable', 'args'] # Tag this invocation as creating a new instance. newInstance: -> @isNew = true this # Grab the reference to the superclass's implementation of the current # method. superReference: (o) -> {method} = o.scope throw SyntaxError 'cannot call super outside of a function.' unless method {name} = method throw SyntaxError 'cannot call super on an anonymous function.' unless name if method.klass "#{method.klass}.__super__.#{name}" else "#{name}.__super__.constructor" # Soaked chained invocations unfold into if/else ternary structures. unfoldSoak: (o) -> if @soak if @variable return ifn if ifn = unfoldSoak o, this, 'variable' [left, rite] = new Value(@variable).cacheReference o else left = new Literal @superReference o rite = new Value left 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 # Compile a vanilla function call. compileNode: (o) -> @variable?.front = @front if code = Splat.compileSplattedArray o, @args, true return @compileSplat o, code args = (arg.compile o, LEVEL_LIST for arg in @args).join ', ' if @isSuper @compileSuper args, o else (if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})" # `super()` is converted into a call against the superclass's implementation # of the current function. compileSuper: (args, o) -> "#{@superReference(o)}.call(this#{ if args.length then ', ' else '' }#{args})" # If you call a function with a splat, it's converted into a JavaScript # `.apply()` call to allow an array of arguments to be passed. # If it's a constructor, then things get real tricky. We have to inject an # inner constructor in order to be able to pass the varargs. compileSplat: (o, splatArgs) -> return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper unless @isNew base = new Value @variable if (name = base.properties.pop()) and base.isComplex() ref = o.scope.freeVariable 'this' fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }" else fun = ref = base.compile o, LEVEL_ACCESS fun += name.compile o if name return "#{fun}.apply(#{ref}, #{splatArgs})" idt = @tab + TAB """ (function(func, args, ctor) { #{idt}ctor.prototype = func.prototype; #{idt}var child = new ctor, result = func.apply(child, args); #{idt}return typeof result === "object" ? result : child; #{@tab}})(#{ @variable.compile o, LEVEL_LIST }, #{splatArgs}, function() {}) """ #### Extends # Node to extend an object's prototype with an ancestor object. # After `goog.inherits` from the # [Closure Library](http://closure-library.googlecode.com/svn/docs/closureGoogBase.js.html). exports.Extends = class Extends extends Base constructor: (@child, @parent) -> children: ['child', 'parent'] # Hooks one constructor into another's prototype chain. compile: (o) -> utility 'hasProp' new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o #### Access # A `.` access into a property of a value, or the `::` shorthand for # an access into the object's prototype. exports.Access = class Access extends Base constructor: (@name, tag) -> @proto = if tag is 'proto' then '.prototype' else '' @soak = tag is 'soak' children: ['name'] compile: (o) -> name = @name.compile o @proto + if IS_STRING.test name then "[#{name}]" else ".#{name}" isComplex: NO #### Index # A `[ ... ]` indexed access into an array or object. exports.Index = class Index extends Base constructor: (@index) -> children: ['index'] compile: (o) -> (if @proto then '.prototype' else '') + "[#{ @index.compile o, LEVEL_PAREN }]" isComplex: -> @index.isComplex() #### Range # A range literal. Ranges can be used to extract portions (slices) of arrays, # to specify a range for comprehensions, or as a value, to be expanded into the # corresponding array of integers at runtime. exports.Range = class Range extends Base children: ['from', 'to'] constructor: (@from, @to, tag) -> @exclusive = tag is 'exclusive' @equals = if @exclusive then '' else '=' # Compiles the range's source variables -- where it starts and where it ends. # But only if they need to be cached to avoid double evaluation. compileVariables: (o) -> o = merge(o, top: true) [@from, @fromVar] = @from.cache o, LEVEL_LIST [@to, @toVar] = @to.cache o, LEVEL_LIST [@fromNum, @toNum] = [@fromVar.match(SIMPLENUM), @toVar.match(SIMPLENUM)] parts = [] parts.push @from if @from isnt @fromVar parts.push @to if @to isnt @toVar # When compiled normally, the range returns the contents of the *for loop* # needed to iterate over the values in the range. Used by comprehensions. compileNode: (o) -> @compileVariables o return @compileArray(o) unless o.index return @compileSimple(o) if @fromNum and @toNum idx = del o, 'index' step = del o, 'step' vars = "#{idx} = #{@from}" + if @to isnt @toVar then ", #{@to}" else '' intro = "(#{@fromVar} <= #{@toVar} ? #{idx}" compare = "#{intro} <#{@equals} #{@toVar} : #{idx} >#{@equals} #{@toVar})" stepPart = if step then step.compile(o) else '1' incr = if step then "#{idx} += #{stepPart}" else "#{intro} += #{stepPart} : #{idx} -= #{stepPart})" "#{vars}; #{compare}; #{incr}" # Compile a simple range comprehension, with integers. compileSimple: (o) -> [from, to] = [+@fromNum, +@toNum] idx = del o, 'index' step = del o, 'step' step and= "#{idx} += #{step.compile(o)}" if from <= to "#{idx} = #{from}; #{idx} <#{@equals} #{to}; #{step or "#{idx}++"}" else "#{idx} = #{from}; #{idx} >#{@equals} #{to}; #{step or "#{idx}--"}" # When used as a value, expand the range into the equivalent array. compileArray: (o) -> if @fromNum and @toNum and Math.abs(@fromNum - @toNum) <= 20 range = [+@fromNum..+@toNum] range.pop() if @exclusive return "[#{ range.join(', ') }]" idt = @tab + TAB i = o.scope.freeVariable 'i' result = o.scope.freeVariable 'results' pre = "\n#{idt}#{result} = [];" if @fromNum and @toNum o.index = i body = @compileSimple o else vars = "#{i} = #{@from}" + if @to isnt @toVar then ", #{@to}" else '' clause = "#{@fromVar} <= #{@toVar} ?" body = "var #{vars}; #{clause} #{i} <#{@equals} #{@toVar} : #{i} >#{@equals} #{@toVar}; #{clause} #{i} += 1 : #{i} -= 1" post = "{ #{result}.push(#{i}); }\n#{idt}return #{result};\n#{o.indent}" "(function() {#{pre}\n#{idt}for (#{body})#{post}}).call(this)" #### Slice # An array slice literal. Unlike JavaScript's `Array#slice`, the second parameter # specifies the index of the end of the slice, just as the first parameter # is the index of the beginning. exports.Slice = class Slice extends Base children: ['range'] constructor: (@range) -> super() compileNode: (o) -> from = if @range.from then @range.from.compile(o) else '0' to = if @range.to then @range.to.compile(o) else '' to += if not to or @range.exclusive then '' else ' + 1' to = ', ' + to if to ".slice(#{from}#{to})" #### Obj # An object literal, nothing fancy. exports.Obj = class Obj extends Base constructor: (props, @generated = false) -> @objects = @properties = props or [] children: ['properties'] compileNode: (o) -> props = @properties return (if @front then '({})' else '{}') unless props.length for prop, i in props if prop instanceof Splat or (prop.variable or prop).base instanceof Parens rest = props.splice i, 1/0 break idt = o.indent += TAB nonComments = (prop for prop in @properties when prop not instanceof Comment) lastNoncom = last nonComments props = for prop, i in props join = if i is props.length - 1 '' else if prop is lastNoncom or prop instanceof Comment '\n' else ',\n' indent = if prop instanceof Comment then '' else idt if prop instanceof Value and prop.this prop = new Assign prop.properties[0].name, prop, 'object' else if prop not instanceof Assign and prop not instanceof Comment prop = new Assign prop, prop, 'object' indent + prop.compile(o, LEVEL_TOP) + join props = props.join '' obj = "{#{ props and '\n' + props + '\n' + @tab }}" return @compileDynamic o, obj, rest if rest if @front then "(#{obj})" else obj compileDynamic: (o, code, props) -> code = "#{ oref = o.scope.freeVariable 'obj' } = #{code}, " for prop, i in props if prop instanceof Comment code += prop.compile(o, LEVEL_LIST) + ' ' continue if prop instanceof Assign acc = prop.variable.base key = acc.compile o, LEVEL_PAREN val = prop.value.compile o, LEVEL_LIST else acc = prop.base [key, val] = acc.cache o, LEVEL_LIST, ref ref = val if key isnt val key = if acc instanceof Literal and IDENTIFIER.test key '.' + key else '[' + key + ']' code += "#{oref}#{key} = #{val}, " code += oref if o.level <= LEVEL_PAREN then code else "(#{code})" assigns: (name) -> for prop in @properties when prop.assigns name then return yes no #### Arr # An array literal. exports.Arr = class Arr extends Base constructor: (objs) -> @objects = objs or [] children: ['objects'] compileNode: (o) -> return '[]' unless @objects.length o.indent += TAB return code if code = Splat.compileSplattedArray o, @objects code = (obj.compile o, LEVEL_LIST for obj in @objects).join ', ' if code.indexOf('\n') >= 0 "[\n#{o.indent}#{code}\n#{@tab}]" else "[#{code}]" assigns: (name) -> for obj in @objects when obj.assigns name then return yes no #### Class # The CoffeeScript class definition. # Initialize a **Class** with its name, an optional superclass, and a # list of prototype property assignments. exports.Class = class Class extends Base constructor: (@variable, @parent, @body = new Expressions) -> @boundFuncs = [] children: ['variable', 'parent', 'body'] # Figure out the appropriate name for the constructor function of this class. determineName: -> return null unless @variable decl = if tail = last @variable.properties tail instanceof Access and tail.name.value else @variable.base.value decl and= IDENTIFIER.test(decl) and decl # For all `this`-references and bound functions in the class definition, # `this` is the Class being constructed. setContext: (name) -> @body.traverseChildren false, (node) -> if node instanceof Literal and node.value is 'this' node.value = name else if node instanceof Code node.klass = name node.context = name if node.bound # Ensure that all functions bound to the instance are proxied in the # constructor. addBoundFunctions: (o) -> if @boundFuncs.length for bvar in @boundFuncs bname = bvar.compile o @ctor.body.unshift new Literal "this.#{bname} = #{utility 'bind'}(this.#{bname}, this);" # Merge the properties from a top-level object as prototypal properties # on the class. addProperties: (node, name) -> props = node.base.properties.slice 0 while assign = props.shift() if assign instanceof Assign base = assign.variable.base delete assign.context func = assign.value if base.value is 'constructor' if @ctor throw new Error 'cannot define more than one constructor in a class' if func.bound throw new Error 'cannot define a constructor as a bound function' if func instanceof Code @ctor = func else @ctor = new Assign(new Value(new Literal name), func) assign = null else unless assign.variable.this assign.variable = new Value(new Literal(name), [new Access(base, 'proto')]) if func instanceof Code and func.bound @boundFuncs.push base func.bound = no assign # Walk the body of the class, looking for prototype properties to be converted. walkBody: (name) -> @traverseChildren false, (child) => if child instanceof Expressions for node, i in exps = child.expressions if node instanceof Value and node.isObject(true) exps[i] = compact @addProperties node, name child.expressions = exps = compact flatten exps # Make sure that a constructor is defined for the class, and properly # configured. ensureConstructor: (name) -> if not @ctor @ctor = new Code @ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] if @parent @ctor.ctor = @ctor.name = name @ctor.klass = null @ctor.noReturn = yes # Instead of generating the JavaScript string directly, we build up the # equivalent syntax tree and compile that, in pieces. You can see the # constructor, property assignments, and inheritance getting built out below. compileNode: (o) -> decl = @determineName() name = decl or @name or '_Class' lname = new Literal name @setContext name @walkBody name @ensureConstructor name @body.expressions.unshift new Extends lname, @parent if @parent @body.expressions.unshift @ctor @body.expressions.push lname @addBoundFunctions o klass = new Parens new Call(new Code [], @body), true klass = new Assign new Value(lname), klass if decl and @variable?.isComplex() klass = new Assign @variable, klass if @variable klass.compile o #### Assign # The **Assign** is used to assign a local variable to value, or to set the # property of an object -- including within object literals. exports.Assign = class Assign extends Base constructor: (@variable, @value, @context) -> # Matchers for detecting class/method names METHOD_DEF: /^(?:(\S+)\.prototype\.|\S+?)?\b([$A-Za-z_][$\w]*)$/ children: ['variable', 'value'] assigns: (name) -> @[if @context is 'object' then 'value' else 'variable'].assigns name unfoldSoak: (o) -> unfoldSoak o, this, 'variable' # Compile an assignment, delegating to `compilePatternMatch` or # `compileSplice` if appropriate. Keep track of the name of the base object # we've been assigned to, for correct internal references. If the variable # has not been seen yet within the current scope, declare it. compileNode: (o) -> if isValue = @variable instanceof Value return @compilePatternMatch o if @variable.isArray() or @variable.isObject() return @compileSplice o if @variable.isSplice() return @compileConditional o if @context in ['||=', '&&=', '?='] name = @variable.compile o, LEVEL_LIST if @value instanceof Code and match = @METHOD_DEF.exec name @value.name = match[2] @value.klass = match[1] if match[1] val = @value.compile o, LEVEL_LIST return "#{name}: #{val}" if @context is 'object' unless @variable.isAssignable() throw SyntaxError "\"#{ @variable.compile o }\" cannot be assigned." o.scope.find name unless @context or isValue and (@variable.namespaced or @variable.hasProperties()) val = name + " #{ @context or '=' } " + val if o.level <= LEVEL_LIST then val else "(#{val})" # Brief implementation of recursive pattern matching, when assigning array or # object literals to a value. Peeks at their properties to assign inner names. # See the [ECMAScript Harmony Wiki](http://wiki.ecmascript.org/doku.php?id=harmony:destructuring) # for details. compilePatternMatch: (o) -> top = o.level is LEVEL_TOP {value} = this {objects} = @variable.base return value.compile o unless olen = objects.length isObject = @variable.isObject() if top and olen is 1 and (obj = objects[0]) not instanceof Splat # Unroll simplest cases: `{v} = x` -> `v = x.v` if obj instanceof Assign {variable: {base: idx}, value: obj} = obj else if obj.base instanceof Parens [obj, idx] = new Value(obj.unwrapAll()).cacheReference o else idx = if isObject if obj.this then obj.properties[0].name else obj else new Literal 0 acc = IDENTIFIER.test idx.unwrap().value or 0 value = new Value value value.properties.push new (if acc then Access else Index) idx return new Assign(obj, value).compile o vvar = value.compile o, LEVEL_LIST assigns = [] splat = false if not IDENTIFIER.test(vvar) or @variable.assigns(vvar) assigns.push "#{ ref = o.scope.freeVariable 'ref' } = #{vvar}" vvar = ref for obj, i in objects # A regular array pattern-match. idx = i if isObject if obj instanceof Assign # A regular object pattern-match. {variable: {base: idx}, value: obj} = obj else # A shorthand `{a, b, @c} = val` pattern-match. if obj.base instanceof Parens [obj, idx] = new Value(obj.unwrapAll()).cacheReference o else idx = if obj.this then obj.properties[0].name else obj if not splat and obj instanceof Splat val = "#{olen} <= #{vvar}.length ? #{ utility 'slice' }.call(#{vvar}, #{i}" if rest = olen - i - 1 ivar = o.scope.freeVariable 'i' val += ", #{ivar} = #{vvar}.length - #{rest}) : (#{ivar} = #{i}, [])" else val += ") : []" val = new Literal val splat = "#{ivar}++" else if obj instanceof Splat obj = obj.name.compile o throw SyntaxError \ "multiple splats are disallowed in an assignment: #{obj} ..." if typeof idx is 'number' idx = new Literal splat or idx acc = no else acc = isObject and IDENTIFIER.test idx.unwrap().value or 0 val = new Value new Literal(vvar), [new (if acc then Access else Index) idx] assigns.push new Assign(obj, val).compile o, LEVEL_TOP assigns.push vvar unless top code = assigns.join ', ' if o.level < LEVEL_LIST then code else "(#{code})" # When compiling a conditional assignment, take care to ensure that the # operands are only evaluated once, even though we have to reference them # more than once. compileConditional: (o) -> [left, rite] = @variable.cacheReference o new Op(@context.slice(0, -1), left, new Assign(rite, @value, '=')).compile o # Compile the assignment from an array splice literal, using JavaScript's # `Array#splice` method. compileSplice: (o) -> {range} = @variable.properties.pop() name = @variable.compile o plus = if range.exclusive then '' else ' + 1' from = if range.from then range.from.compile(o) else '0' to = if range.to then range.to.compile(o) + ' - ' + from + plus else "#{name}.length" ref = o.scope.freeVariable 'ref' val = @value.compile(o) "([].splice.apply(#{name}, [#{from}, #{to}].concat(#{ref} = #{val})), #{ref})" #### Code # A function definition. This is the only node that creates a new Scope. # When for the purposes of walking the contents of a function body, the Code # has no *children* -- they're within the inner scope. exports.Code = class Code extends Base constructor: (params, body, tag) -> @params = params or [] @body = body or new Expressions @bound = tag is 'boundfunc' @context = 'this' if @bound children: ['params', 'body'] isStatement: -> !!@ctor # Compilation creates a new scope unless explicitly asked to share with the # outer scope. Handles splat parameters in the parameter list by peeking at # the JavaScript `arguments` objects. If the function is bound with the `=>` # arrow, generates a wrapper that saves the current value of `this` through # a closure. compileNode: (o) -> sharedScope = del o, 'sharedScope' o.scope = scope = sharedScope or new Scope o.scope, @body, this o.indent += TAB delete o.bare delete o.globals vars = [] exprs = [] for param in @params when param.splat splats = new Assign new Value(new Arr(p.asReference o for p in @params)), new Value new Literal 'arguments' break for param in @params if param.isComplex() val = ref = param.asReference o val = new Op '?', ref, param.value if param.value exprs.push new Assign new Value(param.name), val, '=' else ref = param if param.value lit = new Literal ref.name.value + ' == null' val = new Assign new Value(param.name), param.value, '=' exprs.push new If lit, val vars.push ref unless splats scope.startLevel() wasEmpty = @body.isEmpty() exprs.unshift splats if splats @body.expressions.unshift exprs... if exprs.length scope.parameter vars[i] = v.compile o for v, i in vars unless splats @body.makeReturn() unless wasEmpty or @noReturn idt = o.indent code = 'function' code += ' ' + @name if @ctor code += '(' + vars.join(', ') + ') {' code += "\n#{ @body.compileWithDeclarations o }\n#{@tab}" unless @body.isEmpty() code += '}' return @tab + code if @ctor return utility('bind') + "(#{code}, #{@context})" if @bound if @front then "(#{code})" else code # Short-circuit `traverseChildren` method to prevent it from crossing scope boundaries # unless `crossScope` is `true`. traverseChildren: (crossScope, func) -> super(crossScope, func) if crossScope #### Param # A parameter in a function definition. Beyond a typical Javascript parameter, # these parameters can also attach themselves to the context of the function, # as well as be a splat, gathering up a group of parameters into an array. exports.Param = class Param extends Base constructor: (@name, @value, @splat) -> children: ['name', 'value'] compile: (o) -> @name.compile o, LEVEL_LIST asReference: (o) -> return @reference if @reference node = @name if node.this node = node.properties[0].name node = new Literal '_' + node.value if node.value.reserved else if node.isComplex() node = new Literal o.scope.freeVariable 'arg' node = new Value node node = new Splat node if @splat @reference = node isComplex: -> @name.isComplex() #### Splat # A splat, either as a parameter to a function, an argument to a call, # or as part of a destructuring assignment. exports.Splat = class Splat extends Base children: ['name'] isAssignable: YES constructor: (name) -> @name = if name.compile then name else new Literal name assigns: (name) -> @name.assigns name compile: (o) -> if @index? then @compileParam o else @name.compile o # Utility function that converts arbitrary number of elements, mixed with # splats, to a proper array. @compileSplattedArray: (o, list, apply) -> index = -1 continue while (node = list[++index]) and node not instanceof Splat return '' if index >= list.length if list.length is 1 code = list[0].compile o, LEVEL_LIST return code if apply return "#{ utility 'slice' }.call(#{code})" args = list.slice index for node, i in args code = node.compile o, LEVEL_LIST args[i] = if node instanceof Splat then "#{ utility 'slice' }.call(#{code})" else "[#{code}]" return args[0] + ".concat(#{ args.slice(1).join ', ' })" if index is 0 base = (node.compile o, LEVEL_LIST for node in list.slice 0, index) "[#{ base.join ', ' }].concat(#{ args.join ', ' })" #### While # A while loop, the only sort of low-level loop exposed by CoffeeScript. From # it, all other loops can be manufactured. Useful in cases where you need more # flexibility or more speed than a comprehension can provide. exports.While = class While extends Base constructor: (condition, options) -> @condition = if options?.invert then condition.invert() else condition @guard = options?.guard children: ['condition', 'guard', 'body'] isStatement: YES makeReturn: -> @returns = yes this addBody: (@body) -> this containsPureStatement: -> {expressions} = @body i = expressions.length return true if expressions[--i]?.containsPureStatement() ret = (node) -> node instanceof Return return true while i-- when expressions[i].contains ret false # The main difference from a JavaScript *while* is that the CoffeeScript # *while* can be used as a part of a larger expression -- while loops may # return an array containing the computed result of each iteration. compileNode: (o) -> o.indent += TAB set = '' {body} = this if body.isEmpty() body = '' else if o.level > LEVEL_TOP or @returns rvar = o.scope.freeVariable 'results' set = "#{@tab}#{rvar} = [];\n" body = Push.wrap rvar, body if body body = Expressions.wrap [new If @guard, body] if @guard body = "\n#{ body.compile o, LEVEL_TOP }\n#{@tab}" code = set + @tab + "while (#{ @condition.compile o, LEVEL_PAREN }) {#{body}}" if @returns o.indent = @tab code += '\n' + new Return(new Literal rvar).compile o code #### Op # Simple Arithmetic and logical operations. Performs some conversion from # CoffeeScript operations into their JavaScript equivalents. exports.Op = class Op extends Base constructor: (op, first, second, flip) -> return new In first, second if op is 'in' if op is 'new' return first.newInstance() if first instanceof Call first = new Parens first if first instanceof Code and first.bound @operator = CONVERSIONS[op] or op @first = first @second = second @flip = !!flip return this # The map of conversions from CoffeeScript to JavaScript symbols. CONVERSIONS = '==': '===' '!=': '!==' 'of': 'in' # The map of invertible operators. INVERSIONS = '!==': '===' '===': '!==' '>': '<=' '<=': '>' '<': '>=' '>=': '<' children: ['first', 'second'] isUnary: -> not @second # Am I capable of # [Python-style comparison chaining](http://docs.python.org/reference/expressions.html#notin)? isChainable: -> @operator in ['<', '>', '>=', '<=', '===', '!=='] invert: -> if op = INVERSIONS[@operator] @operator = op 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' compileNode: (o) -> return @compileUnary o if @isUnary() return @compileChain o if @isChainable() and @first.isChainable() return @compileExistence o if @operator is '?' @first.front = @front code = @first.compile(o, LEVEL_OP) + ' ' + @operator + ' ' + @second.compile(o, LEVEL_OP) if o.level <= LEVEL_OP then code else "(#{code})" # Mimic Python's chained comparisons when multiple comparison operators are # used sequentially. For example: # # bin/coffee -e 'console.log 50 < 65 > 10' # true compileChain: (o) -> [@first.second, shared] = @first.second.cache o fst = @first .compile o, LEVEL_OP fst = fst.slice 1, -1 if fst.charAt(0) is '(' code = "#{fst} && #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }" if o.level < LEVEL_OP then code else "(#{code})" compileExistence: (o) -> if @first.isComplex() ref = o.scope.freeVariable 'ref' fst = new Parens new Assign new Literal(ref), @first else fst = @first ref = fst.compile o new Existence(fst).compile(o) + " ? #{ref} : #{ @second.compile o, LEVEL_LIST }" # Compile a unary **Op**. compileUnary: (o) -> parts = [op = @operator] parts.push ' ' if op in ['new', 'typeof', 'delete'] or op in ['+', '-'] and @first instanceof Op and @first.operator is op parts.push @first.compile o, LEVEL_OP parts.reverse() if @flip parts.join '' toString: (idt) -> super idt, @constructor.name + ' ' + @operator #### In exports.In = class In extends Base constructor: (@object, @array) -> children: ['object', 'array'] invert: NEGATE compileNode: (o) -> if @array instanceof Value and @array.isArray() @compileOrTest o else @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 ref else sub) + cmp + item.compile o, LEVEL_OP tests = tests.join cnj if o.level < LEVEL_OP then tests else "(#{tests})" compileLoopTest: (o) -> [sub, ref] = @object.cache o, LEVEL_LIST code = utility('indexOf') + ".call(#{ @array.compile o, LEVEL_LIST }, #{ref}) " + if @negated then '< 0' else '>= 0' return code if sub is ref code = sub + ', ' + code if o.level < LEVEL_LIST then code else "(#{code})" toString: (idt) -> super idt, @constructor.name + if @negated then '!' else '' #### Try # A classic *try/catch/finally* block. exports.Try = class Try extends Base constructor: (@attempt, @error, @recovery, @ensure) -> children: ['attempt', 'recovery', 'ensure'] isStatement: YES makeReturn: -> @attempt = @attempt .makeReturn() if @attempt @recovery = @recovery.makeReturn() if @recovery this # Compilation is more or less as you would expect -- the *finally* clause # is optional, the *catch* is not. compileNode: (o) -> o.indent += TAB errorPart = if @error then " (#{ @error.compile o }) " else ' ' catchPart = if @recovery " catch#{errorPart}{\n#{ @recovery.compile o, LEVEL_TOP }\n#{@tab}}" else unless @ensure or @recovery ' catch (_e) {}' """ #{@tab}try { #{ @attempt.compile o, LEVEL_TOP } #{@tab}}#{ catchPart or '' } """ + if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else '' #### Throw # Simple node to throw an exception. exports.Throw = class Throw extends Base constructor: (@expression) -> children: ['expression'] isStatement: YES # A **Throw** is already a return, of sorts... makeReturn: THIS compileNode: (o) -> @tab + "throw #{ @expression.compile o };" #### Existence # Checks a variable for existence -- not *null* and not *undefined*. This is # similar to `.nil?` in Ruby, and avoids having to consult a JavaScript truth # table. exports.Existence = class Existence extends Base constructor: (@expression) -> children: ['expression'] invert: NEGATE compileNode: (o) -> code = @expression.compile o, LEVEL_OP code = if IDENTIFIER.test(code) and not o.scope.check code if @negated "typeof #{code} == \"undefined\" || #{code} === null" else "typeof #{code} != \"undefined\" && #{code} !== null" else sym = if @negated then '==' else '!=' "#{code} #{sym} null" if o.level <= LEVEL_COND then code else "(#{code})" #### Parens # An extra set of parentheses, specified explicitly in the source. At one time # we tried to clean up the results by detecting and removing redundant # parentheses, but no longer -- you can put in as many as you please. # # Parentheses are a good way to force any statement to become an expression. exports.Parens = class Parens extends Base constructor: (@body) -> children: ['body'] unwrap : -> @body isComplex : -> @body.isComplex() makeReturn: -> @body.makeReturn() compileNode: (o) -> expr = @body.unwrap() if expr instanceof Value and expr.isAtomic() expr.front = @front return expr.compile o bare = o.level < LEVEL_OP and (expr instanceof Op or expr instanceof Call) code = expr.compile o, LEVEL_PAREN if bare then code else "(#{code})" #### For # CoffeeScript's replacement for the *for* loop is our array and object # comprehensions, that compile into *for* loops here. They also act as an # expression, able to return the result of each filtered iteration. # # Unlike Python array comprehensions, they can be multi-line, and you can pass # the current index of the loop as a second parameter. Unlike Ruby blocks, # you can map and filter in a single pass. exports.For = class For extends Base constructor: (body, source, @name, @index) -> {@source, @guard, @step} = source @body = Expressions.wrap [body] @raw = !!source.raw @object = !!source.object [@name, @index] = [@index, @name] if @object throw SyntaxError 'index cannot be a pattern matching expression' if @index instanceof Value @range = @source instanceof Value and @source.base instanceof Range and not @source.properties.length @pattern = @name instanceof Value throw SyntaxError 'cannot pattern match a range loop' if @range and @pattern @returns = false children: ['body', 'source', 'guard', 'step'] isStatement: YES makeReturn: -> @returns = yes this containsPureStatement: While::containsPureStatement # Welcome to the hairiest method in all of CoffeeScript. Handles the inner # loop, filtering, stepping, and result saving for array, object, and range # comprehensions. Some of the generated code can be shared in common, and # some cannot. compileNode: (o) -> body = Expressions.wrap [@body] hasCode = body.contains (node) -> node instanceof Code hasPure = last(body.expressions)?.containsPureStatement() source = if @range then @source.base else @source scope = o.scope name = @name and @name.compile o, LEVEL_LIST index = @index and @index.compile o, LEVEL_LIST unless hasCode scope.find(name, immediate: yes) if name and not @pattern scope.find(index, immediate: yes) if index rvar = scope.freeVariable 'results' if @returns and not hasPure ivar = (if @range then name else index) or scope.freeVariable 'i' varPart = '' guardPart = '' defPart = '' id