mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-05-03 03:00:14 -04:00
Merge branch 'slice' of git://github.com/matehat/coffee-script
This commit is contained in:
@@ -328,8 +328,12 @@ grammar: {
|
||||
|
||||
# The slice literal.
|
||||
Slice: [
|
||||
o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode $2, $5
|
||||
o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode $2, $6, true
|
||||
o "INDEX_START . . Expression INDEX_END", -> new RangeNode null, $4
|
||||
o "INDEX_START Expression . . INDEX_END", -> new RangeNode $2, null
|
||||
o "INDEX_START Expression . . Expression INDEX_END", -> new RangeNode $2, $5
|
||||
o "INDEX_START . . . Expression INDEX_END", -> new RangeNode null, $5, true
|
||||
o "INDEX_START Expression . . . Expression INDEX_END", -> new RangeNode $2, $6, true
|
||||
o "INDEX_START . . . INDEX_END", -> new RangeNode null, null, true
|
||||
]
|
||||
|
||||
# The array literal.
|
||||
|
||||
@@ -464,7 +464,7 @@ KEYWORDS: JS_KEYWORDS.concat COFFEE_KEYWORDS
|
||||
RESERVED: [
|
||||
"case", "default", "do", "function", "var", "void", "with"
|
||||
"const", "let", "debugger", "enum", "export", "import", "native",
|
||||
"__extends", "__hasProp"
|
||||
(u.utilities.key(k) for k of (u: require './utilities').utilities.functions)...
|
||||
]
|
||||
|
||||
# The superset of both JavaScript keywords and reserved words, none of which may
|
||||
|
||||
@@ -188,7 +188,7 @@ exports.Expressions: class Expressions extends BaseNode
|
||||
# declarations of all inner variables pushed up to the top.
|
||||
compile_with_declarations: (o) ->
|
||||
code: @compile_node(o)
|
||||
code: "${@tab}var ${o.scope.compiled_assignments()};\n$code" if o.scope.has_assignments(this)
|
||||
code: "${@tab}var ${o.scope.compiled_assignments(@tab)};\n$code" if o.scope.has_assignments(this)
|
||||
code: "${@tab}var ${o.scope.compiled_declarations()};\n$code" if o.scope.has_declarations(this)
|
||||
code
|
||||
|
||||
@@ -308,6 +308,8 @@ exports.ValueNode: class ValueNode extends BaseNode
|
||||
soaked: false
|
||||
only: del(o, 'only_first')
|
||||
op: del(o, 'operation')
|
||||
splice: del(o, 'splice')
|
||||
replace: del(o, 'replace')
|
||||
props: if only then @properties[0...@properties.length - 1] else @properties
|
||||
baseline: @base.compile o
|
||||
baseline: "($baseline)" if @base instanceof ObjectNode and @has_properties()
|
||||
@@ -322,6 +324,16 @@ exports.ValueNode: class ValueNode extends BaseNode
|
||||
complete: "($temp = $complete)$@SOAK" + (baseline: temp + prop.compile(o))
|
||||
else
|
||||
complete: complete + @SOAK + (baseline: + prop.compile(o))
|
||||
else if prop instanceof SliceNode
|
||||
o.array: complete
|
||||
if splice
|
||||
o.replace: replace
|
||||
part: prop.compile_splice(o)
|
||||
else
|
||||
part: prop.compile_slice(o)
|
||||
baseline = part
|
||||
complete = part
|
||||
@last: part
|
||||
else
|
||||
part: prop.compile(o)
|
||||
baseline: + part
|
||||
@@ -408,8 +420,6 @@ exports.CallNode: class CallNode extends BaseNode
|
||||
exports.CurryNode: class CurryNode extends CallNode
|
||||
type: 'Curry'
|
||||
|
||||
body: 'func.apply(obj, args.concat(Array.prototype.slice.call(arguments, 0)))'
|
||||
|
||||
constructor: (meth, args) ->
|
||||
@children: flatten [@meth: meth, @context: args[0], @args: (args.slice(1) or [])]
|
||||
@compile_splat_arguments: SplatNode.compile_mixed_array <- @, @args
|
||||
@@ -420,10 +430,8 @@ exports.CurryNode: class CurryNode extends CallNode
|
||||
(new ArrayNode(@args)).compile o
|
||||
|
||||
compile_node: (o) ->
|
||||
body: Expressions.wrap([literal @body])
|
||||
curried: new CodeNode([], body)
|
||||
curry: new CodeNode([literal('func'), literal('obj'), literal('args')], Expressions.wrap([curried]))
|
||||
(new ParentheticalNode(new CallNode(curry, [@meth, @context, literal(@arguments(o))]))).compile o
|
||||
ref: new ValueNode literal(o.scope.utility('bind'))
|
||||
(new CallNode(ref, [@meth, @context, literal(@arguments(o))])).compile o
|
||||
|
||||
#### ExtendsNode
|
||||
|
||||
@@ -433,25 +441,13 @@ exports.CurryNode: class CurryNode extends CallNode
|
||||
exports.ExtendsNode: class ExtendsNode extends BaseNode
|
||||
type: 'Extends'
|
||||
|
||||
code: '''
|
||||
function(child, parent) {
|
||||
var ctor = function(){ };
|
||||
ctor.prototype = parent.prototype;
|
||||
child.__superClass__ = parent.prototype;
|
||||
child.prototype = new ctor();
|
||||
child.prototype.constructor = child;
|
||||
}
|
||||
'''
|
||||
|
||||
constructor: (child, parent) ->
|
||||
@children: [@child: child, @parent: parent]
|
||||
|
||||
# Hooks one constructor into another's prototype chain.
|
||||
compile_node: (o) ->
|
||||
o.scope.assign('__extends', @code, true)
|
||||
ref: new ValueNode literal('__extends')
|
||||
call: new CallNode ref, [@child, @parent]
|
||||
call.compile(o)
|
||||
ref: new ValueNode literal(o.scope.utility('extend'))
|
||||
(new CallNode ref, [@child, @parent]).compile o
|
||||
|
||||
#### AccessorNode
|
||||
|
||||
@@ -544,6 +540,27 @@ exports.SliceNode: class SliceNode extends BaseNode
|
||||
plus_part: if @range.exclusive then '' else ' + 1'
|
||||
".slice($from, $to$plus_part)"
|
||||
|
||||
compile_splice: (o) ->
|
||||
array: del o, 'array'
|
||||
replace: del o, 'replace'
|
||||
from: if @range.from? then @range.from else literal('null')
|
||||
to: if @range.to? then @range.to else literal('null')
|
||||
exclusive: if @range.exclusive then 'true' else 'false'
|
||||
v: o.scope.free_variable()
|
||||
rng: new CallNode new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]
|
||||
args: literal "[($v = ${rng.compile(o)})[0], $v[1] - $v[0]].concat(${replace.compile(o)})"
|
||||
call: new CallNode new ValueNode(literal(array), [literal('.splice.apply')]), [literal(array), args]
|
||||
call.compile(o)
|
||||
|
||||
compile_slice: (o) ->
|
||||
array: del o, 'array'
|
||||
from: if @range.from? then @range.from else literal('null')
|
||||
to: if @range.to? then @range.to else literal('null')
|
||||
exclusive: if @range.exclusive then 'true' else 'false'
|
||||
rng: new CallNode new ValueNode(literal(o.scope.utility('range'))), [literal(array), from, to, literal(exclusive)]
|
||||
call: new CallNode new ValueNode(literal(array), [literal('.slice.apply')]), [literal(array), rng]
|
||||
call.compile(o)
|
||||
|
||||
#### ObjectNode
|
||||
|
||||
# An object literal, nothing fancy.
|
||||
@@ -588,7 +605,7 @@ exports.ArrayNode: class ArrayNode extends BaseNode
|
||||
for obj, i in @objects
|
||||
code: obj.compile(o)
|
||||
if obj instanceof SplatNode
|
||||
return @compile_splat_literal(@objects, o)
|
||||
return @compile_splat_literal o
|
||||
else if obj instanceof CommentNode
|
||||
objects.push "\n$code\n$o.indent"
|
||||
else if i is @objects.length - 1
|
||||
@@ -734,14 +751,7 @@ exports.AssignNode: class AssignNode extends BaseNode
|
||||
# Compile the assignment from an array splice literal, using JavaScript's
|
||||
# `Array#splice` method.
|
||||
compile_splice: (o) ->
|
||||
name: @variable.compile(merge(o, {only_first: true}))
|
||||
l: @variable.properties.length
|
||||
range: @variable.properties[l - 1].range
|
||||
plus: if range.exclusive then '' else ' + 1'
|
||||
from: range.from.compile(o)
|
||||
to: range.to.compile(o) + ' - ' + from + plus
|
||||
val: @value.compile(o)
|
||||
"${name}.splice.apply($name, [$from, $to].concat($val))"
|
||||
@variable.compile(merge(o, {only_first: true, splice: true, replace: @value}))
|
||||
|
||||
#### CodeNode
|
||||
|
||||
@@ -791,8 +801,8 @@ exports.CodeNode: class CodeNode extends BaseNode
|
||||
func: "function${ if @bound then '' else name_part }(${ params.join(', ') }) {$code${@idt(if @bound then 1 else 0)}}"
|
||||
func: "($func)" if top and not @bound
|
||||
return func unless @bound
|
||||
inner: "(function$name_part() {\n${@idt(2)}return __func.apply(__this, arguments);\n${@idt(1)}});"
|
||||
"(function(__this) {\n${@idt(1)}var __func = $func;\n${@idt(1)}return $inner\n$@tab})(this)"
|
||||
ref: new ValueNode literal(o.scope.utility('bind'))
|
||||
(new CallNode ref, [literal(func), literal('this')]).compile o
|
||||
|
||||
top_sensitive: ->
|
||||
true
|
||||
@@ -835,13 +845,13 @@ exports.SplatNode: class SplatNode extends BaseNode
|
||||
for trailing in @trailings
|
||||
o.scope.assign(trailing.compile(o), "arguments[arguments.length - $@trailings.length + $i]")
|
||||
i: + 1
|
||||
"$name = Array.prototype.slice.call(arguments, $@index, arguments.length - ${@trailings.length})"
|
||||
"$name = ${o.scope.utility('arraySlice')}.call(arguments, $@index, arguments.length - ${@trailings.length})"
|
||||
|
||||
# A compiling a splat as a destructuring assignment means slicing arguments
|
||||
# from the right-hand-side's corresponding array.
|
||||
compile_value: (o, name, index, trailings) ->
|
||||
if trailings? then "Array.prototype.slice.call($name, $index, ${name}.length - $trailings)" \
|
||||
else "Array.prototype.slice.call($name, $index)"
|
||||
if trailings? then "${o.scope.utility('arraySlice')}.call($name, $index, ${name}.length - $trailings)" \
|
||||
else "${o.scope.utility('arraySlice')}.call($name, $index)"
|
||||
|
||||
# Utility function that converts arbitrary number of elements, mixed with
|
||||
# splats, to a proper array
|
||||
@@ -1160,8 +1170,7 @@ exports.ForNode: class ForNode extends BaseNode
|
||||
if @filter
|
||||
body: Expressions.wrap([new IfNode(@filter, body)])
|
||||
if @object
|
||||
o.scope.assign('__hasProp', 'Object.prototype.hasOwnProperty', true)
|
||||
for_part: "$ivar in $svar) { if (__hasProp.call($svar, $ivar)"
|
||||
for_part: "$ivar in $svar) { if (${o.scope.utility('hasProp')}.call($svar, $ivar)"
|
||||
body: body.compile(merge(o, {indent: body_dent, top: true}))
|
||||
vars: if range then name else "$name, $ivar"
|
||||
close: if @object then '}}\n' else '}\n'
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
# Set up exported variables for both **Node.js** and the browser.
|
||||
this.exports: this unless process?
|
||||
utilities: if process? then require('./utilities').utilities else this.utilities
|
||||
|
||||
exports.Scope: class Scope
|
||||
|
||||
@@ -19,12 +20,22 @@ exports.Scope: class Scope
|
||||
@variables: {}
|
||||
@temp_var: if @parent then @parent.temp_var else '_a'
|
||||
|
||||
# Find the top-most scope object, used for defined global variables
|
||||
topmost: ->
|
||||
if @parent then @parent.topmost() else @
|
||||
|
||||
# Look up a variable name in lexical scope, and declare it if it does not
|
||||
# already exist.
|
||||
find: (name) ->
|
||||
return true if @check name
|
||||
@variables[name]: 'var'
|
||||
false
|
||||
|
||||
# Test variables and return true the first time fn(v, k) returns true
|
||||
any: (fn) ->
|
||||
for v, k of @variables when fn(v, k)
|
||||
return true
|
||||
return false
|
||||
|
||||
# Reserve a variable name as originating from a function parameter for this
|
||||
# scope. No `var` required for internal references.
|
||||
@@ -48,18 +59,35 @@ exports.Scope: class Scope
|
||||
# Ensure that an assignment is made at the top of this scope
|
||||
# (or at the top-level scope, if requested).
|
||||
assign: (name, value, top_level) ->
|
||||
return @parent.assign(name, value, top_level) if top_level and @parent
|
||||
return @topmost().assign(name, value) if top_level
|
||||
@variables[name]: {value: value, assigned: true}
|
||||
|
||||
# Ensure the CoffeeScript utility object is included in the top level
|
||||
# then return a CallNode curried constructor bound to the utility function
|
||||
utility: (name) ->
|
||||
return @topmost().utility(name) if @parent
|
||||
if utilities.functions[name]?
|
||||
@utilities: or {}
|
||||
@utilities[name]: utilities.functions[name]
|
||||
@utility(dep) for dep in (utilities.dependencies[name] or []) when not @utilities[dep]
|
||||
"${utilities.key(name)}"
|
||||
|
||||
# Formats an javascript object containing the utility methods required
|
||||
# in the scope
|
||||
included_utilities: (tab) ->
|
||||
if @utilities?
|
||||
utilities.format(key, tab) for key of @utilities
|
||||
else []
|
||||
|
||||
# Does this scope reference any variables that need to be declared in the
|
||||
# given function body?
|
||||
has_declarations: (body) ->
|
||||
body is @expressions and @declared_variables().length
|
||||
body is @expressions and @any (k, val) -> val is 'var'
|
||||
|
||||
# Does this scope reference any assignments that need to be declared at the
|
||||
# top of the given function body?
|
||||
has_assignments: (body) ->
|
||||
body is @expressions and @assigned_variables().length
|
||||
body is @expressions and (@utilities? or @any (k, val) -> val.assigned)
|
||||
|
||||
# Return the list of variables first declared in this scope.
|
||||
declared_variables: ->
|
||||
@@ -75,5 +103,5 @@ exports.Scope: class Scope
|
||||
@declared_variables().join ', '
|
||||
|
||||
# Compile the JavaScript for all of the variable assignments in this scope.
|
||||
compiled_assignments: ->
|
||||
@assigned_variables().join ', '
|
||||
compiled_assignments: (tab) ->
|
||||
[@assigned_variables()..., @included_utilities(tab)...].join ', '
|
||||
|
||||
45
src/utilities.coffee
Normal file
45
src/utilities.coffee
Normal file
@@ -0,0 +1,45 @@
|
||||
this.exports: this unless process?
|
||||
|
||||
exports.utilities: class utilities
|
||||
@key: (name) ->
|
||||
"__$name"
|
||||
|
||||
@format: (key, tab) ->
|
||||
"${utilities.key(key)} = ${utilities.functions[key].replace(/\n/g, "\n$tab") or 'undefined'}"
|
||||
|
||||
@dependencies: {
|
||||
bind: ['arraySlice']
|
||||
splice: ['range']
|
||||
}
|
||||
|
||||
@functions: {
|
||||
extend: """
|
||||
function(child, parent) {
|
||||
var ctor = function(){ };
|
||||
ctor.prototype = parent.prototype;
|
||||
child.__superClass__ = parent.prototype;
|
||||
child.prototype = new ctor();
|
||||
child.prototype.constructor = child;
|
||||
}
|
||||
"""
|
||||
bind: """
|
||||
function(func, obj, args) {
|
||||
obj = obj || {};
|
||||
return (typeof args !== 'undefined' && args !== null) ? function() {
|
||||
return func.apply(obj, args.concat(${utilities.key('arraySlice')}.call(arguments, 0)));
|
||||
} : function() {
|
||||
return func.apply(obj, arguments);
|
||||
};
|
||||
}
|
||||
"""
|
||||
range: """
|
||||
function(array, from, to, exclusive) {
|
||||
return [
|
||||
(from < 0 ? from + array.length : from || 0),
|
||||
(to < 0 ? to + array.length : to || array.length) + (exclusive ? 0 : 1)
|
||||
];
|
||||
}
|
||||
"""
|
||||
hasProp: 'Object.prototype.hasOwnProperty'
|
||||
arraySlice: 'Array.prototype.slice'
|
||||
}
|
||||
Reference in New Issue
Block a user