nodes.coffee | |
---|---|
| {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 | 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: (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: (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 | 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
| 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 scope.expressions is this
if not o.globals and o.scope.hasDeclarations()
code += "#{@tab}var #{ scope.declaredVariables().join(', ') };\n"
if scope.hasAssignments
code += "#{@tab}var #{ multident scope.assignedVariables().join(', '), @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,
| 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 | 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 ( | 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 | 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 | 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 | 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: ->
base = @variable.base or @variable
if base instanceof Call
base.newInstance()
else
@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
@superReference(o) + ".call(this#{ args and ', ' + args })"
else
(if @isNew then 'new ' else '') + @variable.compile(o, LEVEL_ACCESS) + "(#{args})" |
| 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
| compileSplat: (o, splatArgs) ->
return "#{ @superReference o }.apply(this, #{splatArgs})" if @isSuper
if @isNew
idt = @tab + TAB
return """
(function(func, args, ctor) {
#DIVIDER
"""
base = new Value @variable
if (name = base.properties.pop()) and base.isComplex()
ref = o.scope.freeVariable 'ref'
fun = "(#{ref} = #{ base.compile o, LEVEL_LIST })#{ name.compile o }"
else
fun = base.compile o, LEVEL_ACCESS
if name
ref = fun
fun += name.compile o
else
ref = 'null'
"#{fun}.apply(#{ref}, #{splatArgs})" |
{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 | exports.Extends = class Extends extends Base
constructor: (@child, @parent) ->
children: ['child', 'parent'] |
Node to extend an object's prototype with an ancestor object.
After | compile: (o) ->
utility 'hasProp'
new Call(new Value(new Literal utility 'extends'), [@child, @parent]).compile o |
Hooks one constructor into another's prototype chain. | |
Access | 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 |
A | |
Index | 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() |
A | |
Range | exports.Range = class Range extends Base
children: ['from', 'to']
constructor: (@from, @to, tag) ->
@exclusive = tag is 'exclusive'
@equals = if @exclusive then '' else '=' |
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. | 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 |
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. | 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}" |
When compiled normally, the range returns the contents of the for loop needed to iterate over the values in the range. Used by comprehensions. | 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}--"}" |
Compile a simple range comprehension, with integers. | 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)" |
When used as a value, expand the range into the equivalent array. | |
Slice | 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})" |
An array slice literal. Unlike JavaScript's | |
Obj | 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
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 }}"
if @front then "(#{obj})" else obj
assigns: (name) ->
for prop in @properties when prop.assigns name then return yes
no |
An object literal, nothing fancy. | |
Arr | 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 |
An array literal. | |
Class | exports.Class = class Class extends Base
constructor: (@variable, @parent, @body = new Expressions) ->
@boundFuncs = []
children: ['variable', 'parent', 'body'] |
The CoffeeScript class definition. Initialize a Class with its name, an optional superclass, and a list of prototype property assignments. | 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 |
Figure out the appropriate name for the constructor function of this class. | 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 |
For all | 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);" |
Ensure that all functions bound to the instance are proxied in the constructor. | 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
assign = @ctor = func
else
assign = @ctor = new Assign(new Value(new Literal name), func)
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 |
Merge the properties from a top-level object as prototypal properties on the class. | walkBody: (name) ->
@traverseChildren false, (child) =>
return false if child instanceof Class
if child instanceof Expressions
for node, i in exps = child.expressions
if node instanceof Value and node.isObject(true)
exps[i] = @addProperties node, name
child.expressions = exps = flatten exps |
Walk the body of the class, looking for prototype properties to be converted. | ensureConstructor: (name) ->
if not @ctor
@ctor = new Code
@ctor.body.push new Call 'super', [new Splat new Literal 'arguments'] if @parent
@body.expressions.unshift @ctor
@ctor.ctor = @ctor.name = name
@ctor.klass = null
@ctor.noReturn = yes |
Make sure that a constructor is defined for the class, and properly configured. | compileNode: (o) ->
decl = @determineName()
name = decl or @name or '_Class'
lname = new Literal name
@setContext name
@walkBody name
@body.expressions.unshift new Extends lname, @parent if @parent
@ensureConstructor name
@body.expressions.push lname
@addBoundFunctions o
klass = new Parens new Call(new Code [], @body), true
klass = new Assign @variable, klass if @variable
klass.compile o |
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. | |
Assign | exports.Assign = class Assign extends Base
constructor: (@variable, @value, @context, options) ->
@param = options and options.param |
The Assign is used to assign a local variable to value, or to set the property of an object -- including within object literals. | 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' |
Matchers for detecting class/method names | 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."
unless @context or isValue and (@variable.namespaced or @variable.hasProperties())
if @param
o.scope.add name, 'var'
else
o.scope.find name
val = name + " #{ @context or '=' } " + val
if o.level <= LEVEL_LIST then val else "(#{val})" |
Compile an assignment, delegating to | 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 |
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 for details. | 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 |
Unroll simplest cases: | idx = i
if isObject
if obj instanceof Assign |
A regular array pattern-match. | {variable: {base: idx}, value: obj} = obj
else |
A regular object 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, null, param: @param).compile o, LEVEL_TOP
assigns.push vvar unless top
code = assigns.join ', '
if o.level < LEVEL_LIST then code else "(#{code})" |
A shorthand | compileConditional: (o) ->
[left, rite] = @variable.cacheReference o
new Op(@context.slice(0, -1), left, new Assign(rite, @value, '=')).compile o |
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. | compileSplice: (o) ->
{range} = @variable.properties.pop()
name = @variable.compile o
excl = range.exclusive
from = if range.from then range.from.compile(o) else '0'
to = "#{name}.length" unless range.to
unless to
if range.from and range.from.isSimpleNumber() and range.to.isSimpleNumber()
to = +range.to.compile(o) - +from
to += 1 unless excl
else
to = range.to.compile(o) + ' - ' + from
to += ' + 1' unless excl
val = @value.compile(o)
"[].splice.apply(#{name}, [#{from}, #{to}].concat(#{val}))" |
Compile the assignment from an array splice literal, using JavaScript's
| |
Code | 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 |
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. | compileNode: (o) ->
sharedScope = del o, 'sharedScope'
o.scope = sharedScope or new Scope o.scope, @body, this
o.scope.shared = yes if sharedScope
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, '=', param: yes
else
ref = param
if param.value
lit = new Literal ref.name.value + ' == null'
val = new Assign new Value(param.name), param.value, '='
exprs.push new If lit, val
vars.push ref unless splats
wasEmpty = @body.isEmpty()
exprs.unshift splats if splats
@body.expressions.unshift exprs... if exprs.length
o.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 |
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 | traverseChildren: (crossScope, func) ->
super(crossScope, func) if crossScope |
Short-circuit | |
Param | 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() |
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. | |
Splat | 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 |
A splat, either as a parameter to a function, an argument to a call, or as part of a destructuring assignment. | @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 ', ' })" |
Utility function that converts arbitrary number of elements, mixed with splats, to a proper array. | |
While | 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 |
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. | 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 |
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. | |
Op | 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 |
Simple Arithmetic and logical operations. Performs some conversion from CoffeeScript operations into their JavaScript equivalents. | CONVERSIONS =
'==': '==='
'!=': '!=='
'of': 'in' |
The map of conversions from CoffeeScript to JavaScript symbols. | INVERSIONS =
'!==': '==='
'===': '!=='
children: ['first', 'second']
isUnary: ->
not @second |
The map of invertible operators. | isChainable: ->
@operator in ['<', '>', '>=', '<=', '===', '!==']
invert: ->
if @isChainable() and @first.isChainable()
allInvertable = yes
curr = this
while curr and curr.operator
allInvertable and= (curr.operator of INVERSIONS)
curr = curr.first
return new Parens(this).invert() unless allInvertable
curr = this
while curr and curr.operator
curr.invert = !curr.invert
curr.operator = INVERSIONS[curr.operator]
curr = curr.first
this
else if op = INVERSIONS[@operator]
@operator = op
if @first.unwrap() instanceof Op
@first.invert()
this
else if @second
new Parens(this).invert()
else if @operator is '!' and (fst = @first.unwrap()) instanceof Op and
fst.operator in ['!', 'in', 'instanceof']
fst
else
new Op '!', this
unfoldSoak: (o) ->
@operator in ['++', '--', 'delete'] and unfoldSoak o, this, 'first'
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})" |
Am I capable of Python-style comparison chaining? | compileChain: (o) ->
[@first.second, shared] = @first.second.cache o
fst = @first.compile o, LEVEL_OP
code = "#{fst} #{if @invert then '&&' else '||'} #{ shared.compile o } #{@operator} #{ @second.compile o, LEVEL_OP }"
"(#{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 }" |
Mimic Python's chained comparisons when multiple comparison operators are used sequentially. For example: | 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 |
Compile a unary Op. | 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 '' |
In | |
Try | 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 |
A classic try/catch/finally block. | 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) {}'
"""
#DIVIDER
""" + if @ensure then " finally {\n#{ @ensure.compile o, LEVEL_TOP }\n#{@tab}}" else '' |
Compilation is more or less as you would expect -- the finally clause is optional, the catch is not. | |
{@tab}try { { @attempt.compile o, LEVEL_TOP } {@tab}}#{ catchPart or '' } | exports.Throw = class Throw extends Base
constructor: (@expression) ->
children: ['expression']
isStatement: YES |
Throw | makeReturn: THIS
compileNode: (o) ->
@tab + "throw #{ @expression.compile o };" |
Simple node to throw an exception. | |
A Throw is already a return, of sorts... | 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})" |
Existence | |
Checks a variable for existence -- not null and not undefined. This is
similar to | 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})" |
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.For = class For extends Base
constructor: (body, source, @name, @index) ->
{@source, @guard, @step} = source
@body = Expressions.wrap [body]
@own = !!source.own
@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 'indexes do not apply to range loops' if @range and @index
throw SyntaxError 'cannot pattern match over range loops' if @range and @pattern
@returns = false
children: ['body', 'source', 'guard', 'step']
isStatement: YES
makeReturn: ->
@returns = yes
this
containsPureStatement: While::containsPureStatement |
For | 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
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 = ''
idt1 = @tab + TAB
if @range
forPart = source.compile merge(o, {index: ivar, @step})
else
svar = @source.compile o, LEVEL_LIST
if (name or @own) and not IDENTIFIER.test svar
defPart = "#{@tab}#{ref = scope.freeVariable 'ref'} = #{svar};\n"
svar = ref
namePart = if @pattern
new Assign(@name, new Literal "#{svar}[#{ivar}]").compile o, LEVEL_TOP
else if name
"#{name} = #{svar}[#{ivar}]"
unless @object
lvar = scope.freeVariable 'len'
stepPart = if @step then "#{ivar} += #{ @step.compile(o, LEVEL_OP) }" else "#{ivar}++"
forPart = "#{ivar} = 0, #{lvar} = #{svar}.length; #{ivar} < #{lvar}; #{stepPart}"
if @returns and not hasPure
resultPart = "#{@tab}#{rvar} = [];\n"
returnResult = '\n' + (new Return(new Literal(rvar)).compile o, LEVEL_PAREN)
body = Push.wrap rvar, body
if @guard
body = Expressions.wrap [new If @guard, body]
if hasCode
body = Closure.wrap(body, yes, not @returns)
varPart = "\n#{idt1}#{namePart};" if namePart
if @object
forPart = "#{ivar} in #{svar}"
guardPart = "\n#{idt1}if (!#{utility('hasProp')}.call(#{svar}, #{ivar})) continue;" if @own
defPart += @pluckDirectCall o, body, name, index unless @pattern
body = body.compile merge(o, indent: idt1), LEVEL_TOP
body = '\n' + body + '\n' if body
"""
#DIVIDER
"""
pluckDirectCall: (o, body, name, index) ->
defs = ''
for expr, idx in body.expressions
expr = expr.unwrapAll()
continue unless expr instanceof Call
val = expr.variable.unwrapAll()
continue unless (val instanceof Code) or
(val instanceof Value and
val.base?.unwrapAll() instanceof Code and
val.properties.length is 1 and
val.properties[0].name?.value in ['call', 'apply'])
fn = val.base?.unwrapAll() or val
ref = new Literal o.scope.freeVariable 'fn'
base = new Value ref
args = compact [name, index]
args.reverse() if @object
for arg, i in args
fn.params.push new Param args[i] = new Literal arg
if val.base
[val.base, base] = [base, val]
args.unshift new Literal 'this'
body.expressions[idx] = new Call base, args
o.sharedScope = o.scope
defs += @tab + new Assign(ref, fn).compile(o, LEVEL_TOP) + ';\n'
defs |
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. | |
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. | exports.Switch = class Switch extends Base
constructor: (@subject, @cases, @otherwise) ->
children: ['subject', 'cases', 'otherwise']
isStatement: YES
makeReturn: ->
pair[1].makeReturn() for pair in @cases
@otherwise?.makeReturn()
this
compileNode: (o) ->
idt1 = o.indent + TAB
idt2 = o.indent = idt1 + TAB
code = @tab + "switch (#{ @subject?.compile(o, LEVEL_PAREN) or false }) {\n"
for [conditions, block], i in @cases
for cond in flatten [conditions]
cond = cond.invert() unless @subject
code += idt1 + "case #{ cond.compile o, LEVEL_PAREN }:\n"
code += body + '\n' if body = block.compile o, LEVEL_TOP
break if i is @cases.length - 1 and not @otherwise
for expr in block.expressions by -1 when expr not instanceof Comment
code += idt2 + 'break;\n' unless expr instanceof Return
break
code += idt1 + "default:\n#{ @otherwise.compile o, LEVEL_TOP }\n" if @otherwise
code + @tab + '}' |
{defPart}#{resultPart or ''}#{@tab}for (#{forPart}) {#{guardPart}#{varPart}#{body}#{@tab}}#{returnResult or ''} | |
Switch | exports.If = class If extends Base
constructor: (condition, @body, options = {}) ->
@condition = if options.invert then condition.invert() else condition
@elseBody = null
@isChain = false
{@soak} = options
children: ['condition', 'body', 'elseBody']
bodyNode: -> @body?.unwrap()
elseBodyNode: -> @elseBody?.unwrap() |
A JavaScript switch statement. Converts into a returnable expression on-demand. | addElse: (elseBody) ->
if @isChain
@elseBodyNode().addElse elseBody
else
@isChain = elseBody instanceof If
@elseBody = @ensureExpressions elseBody
this |
If | isStatement: (o) ->
o?.level is LEVEL_TOP or
@bodyNode().isStatement(o) or @elseBodyNode()?.isStatement(o)
compileNode: (o) ->
if @isStatement o then @compileStatement o else @compileExpression o
makeReturn: ->
@body and= new Expressions [@body.makeReturn()]
@elseBody and= new Expressions [@elseBody.makeReturn()]
this
ensureExpressions: (node) ->
if node instanceof Expressions then node else new Expressions [node] |
If/else statements. Acts as an expression by pushing down requested returns to the last line of each clause. Single-expression Ifs are compiled into conditional operators if possible, because ternaries are already proper expressions, and don't need conversion. | compileStatement: (o) ->
child = del o, 'chainChild'
cond = @condition.compile o, LEVEL_PAREN
o.indent += TAB
body = @ensureExpressions(@body).compile o
body = "\n#{body}\n#{@tab}" if body
ifPart = "if (#{cond}) {#{body}}"
ifPart = @tab + ifPart unless child
return ifPart unless @elseBody
ifPart + ' else ' + if @isChain
o.indent = @tab
o.chainChild = yes
@elseBody.unwrap().compile o, LEVEL_TOP
else
"{\n#{ @elseBody.compile o, LEVEL_TOP }\n#{@tab}}" |
Rewrite a chain of Ifs to add a default case as the final else. | compileExpression: (o) ->
cond = @condition.compile o, LEVEL_COND
body = @bodyNode().compile o, LEVEL_LIST
alt = if @elseBodyNode() then @elseBodyNode().compile(o, LEVEL_LIST) else 'void 0'
code = "#{cond} ? #{body} : #{alt}"
if o.level >= LEVEL_COND then "(#{code})" else code
unfoldSoak: ->
@soak and this |
The If only compiles into a statement if either of its bodies needs to be a statement. Otherwise a conditional operator is safe. | |
Compile the If as a regular if-else statement. Flattened chains force inner else bodies into statement form. | |
Compile the If as a conditional operator. | Push =
wrap: (name, exps) ->
return exps if exps.isEmpty() or last(exps.expressions).containsPureStatement()
exps.push new Call new Value(new Literal(name), [new Access new Literal 'push']), [exps.pop()] |
Faux-NodesFaux-nodes are never created by the grammar, but are used during code generation to generate other combinations of nodes. | |
Push | Closure = |
The Push creates the tree for | wrap: (expressions, statement, noReturn) ->
return expressions if expressions.containsPureStatement()
func = new Code [], Expressions.wrap [expressions]
args = []
if (mentionsArgs = expressions.contains @literalArgs) or
( expressions.contains @literalThis)
meth = new Literal if mentionsArgs then 'apply' else 'call'
args = [new Literal 'this']
args.push new Literal 'arguments' if mentionsArgs
func = new Value func, [new Access meth]
func.noReturn = noReturn
call = new Call func, args
if statement then Expressions.wrap [call] else call
literalArgs: (node) -> node instanceof Literal and node.value is 'arguments'
literalThis: (node) -> node instanceof Literal and node.value is 'this' or
node instanceof Code and node.bound |
Closure | unfoldSoak = (o, parent, name) ->
return unless ifn = parent[name].unfoldSoak o
parent[name] = ifn.body
ifn.body = new Value parent
ifn |
A faux-node used to wrap an expressions body in a closure. | UTILITIES = |
Wrap the expressions body, unless it contains a pure statement,
in which case, no dice. If the body mentions | extends: '''
function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
}
''' |
Unfold a node's child if soak, then tuck the node under created | bind: '''
function(fn, me){ return function(){ return fn.apply(me, arguments); }; }
''' |
Constants | indexOf: '''
Array.prototype.indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (this[i] === item) return i;
}
return -1;
}
''' |
Correctly set up a prototype chain for inheritance, including a reference
to the superclass for | hasProp: 'Object.prototype.hasOwnProperty'
slice : 'Array.prototype.slice' |
Create a function bound to the current value of "this". | LEVEL_TOP = 1 # ...;
LEVEL_PAREN = 2 # (...)
LEVEL_LIST = 3 # [...]
LEVEL_COND = 4 # ... ? x : y
LEVEL_OP = 5 # !...
LEVEL_ACCESS = 6 # ...[0] |
Discover if an item is in an array. | TAB = ' ' |
Shortcuts to speed up the lookup time for native functions. | TRAILING_WHITESPACE = /[ \t]+$/gm
IDENTIFIER = /^[$A-Za-z_][$\w]*$/
SIMPLENUM = /^[+-]?\d+$/ |
Levels indicates a node's position in the AST. Useful for knowing if parens are necessary or superfluous. | IS_STRING = /^['"]/ |
Tabs are two spaces for pretty printing. | |
Trim out all trailing whitespace, so that the generated code plays nice with Git. | utility = (name) ->
ref = "__#{name}"
Scope.root.assign ref, UTILITIES[name]
ref
multident = (code, tab) ->
code.replace /\n/g, '$&' + tab
|
Is a literal value a string? | undefined |
Utility Functions | undefined |
Helper for ensuring that utility functions are assigned at the top level. | undefined |