coffee-script 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/coffee-script.gemspec +4 -3
- data/examples/code.coffee +17 -17
- data/examples/poignant.coffee +45 -12
- data/examples/potion.coffee +11 -11
- data/examples/underscore.coffee +124 -115
- data/{lib/coffee_script → extras}/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences +0 -0
- data/{lib/coffee_script → extras}/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage +11 -26
- data/{lib/coffee_script → extras}/CoffeeScript.tmbundle/info.plist +0 -0
- data/extras/EXTRAS +20 -0
- data/extras/coffee.vim +111 -0
- data/lib/coffee-script.rb +1 -1
- data/lib/coffee_script/command_line.rb +6 -4
- data/lib/coffee_script/grammar.y +26 -17
- data/lib/coffee_script/lexer.rb +31 -13
- data/lib/coffee_script/narwhal/coffee-script.coffee +6 -6
- data/lib/coffee_script/narwhal/lib/coffee-script.js +1 -1
- data/lib/coffee_script/narwhal/loader.coffee +3 -3
- data/lib/coffee_script/nodes.rb +48 -32
- data/lib/coffee_script/parse_error.rb +5 -5
- data/lib/coffee_script/parser.rb +1267 -1234
- data/lib/coffee_script/rewriter.rb +80 -10
- data/package.json +1 -1
- metadata +7 -7
- data/examples/documents.coffee +0 -72
- data/examples/syntax_errors.coffee +0 -20
@@ -6,7 +6,8 @@ module CoffeeScript
|
|
6
6
|
class Rewriter
|
7
7
|
|
8
8
|
# Tokens that must be balanced.
|
9
|
-
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT]
|
9
|
+
BALANCED_PAIRS = [['(', ')'], ['[', ']'], ['{', '}'], [:INDENT, :OUTDENT],
|
10
|
+
[:PARAM_START, :PARAM_END], [:CALL_START, :CALL_END], [:INDEX_START, :INDEX_END]]
|
10
11
|
|
11
12
|
# Tokens that signal the start of a balanced pair.
|
12
13
|
EXPRESSION_START = BALANCED_PAIRS.map {|pair| pair.first }
|
@@ -17,6 +18,14 @@ module CoffeeScript
|
|
17
18
|
# Tokens that indicate the close of a clause of an expression.
|
18
19
|
EXPRESSION_CLOSE = [:CATCH, :WHEN, :ELSE, :FINALLY] + EXPRESSION_TAIL
|
19
20
|
|
21
|
+
# Tokens pairs that, in immediate succession, indicate an implicit call.
|
22
|
+
IMPLICIT_FUNC = [:IDENTIFIER, :SUPER, ')', :CALL_END, ']', :INDEX_END]
|
23
|
+
IMPLICIT_END = [:IF, :UNLESS, :FOR, :WHILE, "\n", :OUTDENT]
|
24
|
+
IMPLICIT_CALL = [:IDENTIFIER, :NUMBER, :STRING, :JS, :REGEX, :NEW, :PARAM_START,
|
25
|
+
:TRY, :DELETE, :INSTANCEOF, :TYPEOF, :SWITCH, :ARGUMENTS,
|
26
|
+
:TRUE, :FALSE, :YES, :NO, :ON, :OFF, '!', '!!', :NOT,
|
27
|
+
'->', '=>', '[', '(', '{']
|
28
|
+
|
20
29
|
# The inverse mappings of token pairs we're trying to fix up.
|
21
30
|
INVERSES = BALANCED_PAIRS.inject({}) do |memo, pair|
|
22
31
|
memo[pair.first] = pair.last
|
@@ -26,8 +35,8 @@ module CoffeeScript
|
|
26
35
|
|
27
36
|
# Single-line flavors of block expressions that have unclosed endings.
|
28
37
|
# The grammar can't disambiguate them, so we insert the implicit indentation.
|
29
|
-
SINGLE_LINERS = [:ELSE, "
|
30
|
-
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN]
|
38
|
+
SINGLE_LINERS = [:ELSE, "->", "=>", :TRY, :FINALLY, :THEN]
|
39
|
+
SINGLE_CLOSERS = ["\n", :CATCH, :FINALLY, :ELSE, :OUTDENT, :LEADING_WHEN, :PARAM_START]
|
31
40
|
|
32
41
|
# Rewrite the token stream in multiple passes, one logical filter at
|
33
42
|
# a time. This could certainly be changed into a single pass through the
|
@@ -38,6 +47,8 @@ module CoffeeScript
|
|
38
47
|
remove_leading_newlines
|
39
48
|
remove_mid_expression_newlines
|
40
49
|
move_commas_outside_outdents
|
50
|
+
close_open_calls_and_indexes
|
51
|
+
add_implicit_parentheses
|
41
52
|
add_implicit_indentation
|
42
53
|
ensure_balance(*BALANCED_PAIRS)
|
43
54
|
rewrite_closing_parens
|
@@ -70,7 +81,7 @@ module CoffeeScript
|
|
70
81
|
@tokens.delete_at(i + 2)
|
71
82
|
@tokens.delete_at(i - 2)
|
72
83
|
next 0
|
73
|
-
elsif prev[0] == "\n" && [:INDENT
|
84
|
+
elsif prev[0] == "\n" && [:INDENT].include?(after[0])
|
74
85
|
@tokens.delete_at(i + 2)
|
75
86
|
@tokens[i - 1] = after
|
76
87
|
next 1
|
@@ -111,6 +122,35 @@ module CoffeeScript
|
|
111
122
|
end
|
112
123
|
end
|
113
124
|
|
125
|
+
# We've tagged the opening parenthesis of a method call, and the opening
|
126
|
+
# bracket of an indexing operation. Match them with their close.
|
127
|
+
def close_open_calls_and_indexes
|
128
|
+
parens, brackets = [0], [0]
|
129
|
+
scan_tokens do |prev, token, post, i|
|
130
|
+
case token[0]
|
131
|
+
when :CALL_START then parens.push(0)
|
132
|
+
when :INDEX_START then brackets.push(0)
|
133
|
+
when '(' then parens[-1] += 1
|
134
|
+
when '[' then brackets[-1] += 1
|
135
|
+
when ')'
|
136
|
+
if parens.last == 0
|
137
|
+
parens.pop
|
138
|
+
token[0] = :CALL_END
|
139
|
+
else
|
140
|
+
parens[-1] -= 1
|
141
|
+
end
|
142
|
+
when ']'
|
143
|
+
if brackets.last == 0
|
144
|
+
brackets.pop
|
145
|
+
token[0] = :INDEX_END
|
146
|
+
else
|
147
|
+
brackets[-1] -= 1
|
148
|
+
end
|
149
|
+
end
|
150
|
+
next 1
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
114
154
|
# Because our grammar is LALR(1), it can't handle some single-line
|
115
155
|
# expressions that lack ending delimiters. Use the lexer to add the implicit
|
116
156
|
# blocks, so it doesn't need to.
|
@@ -119,6 +159,7 @@ module CoffeeScript
|
|
119
159
|
scan_tokens do |prev, token, post, i|
|
120
160
|
next 1 unless SINGLE_LINERS.include?(token[0]) && post[0] != :INDENT &&
|
121
161
|
!(token[0] == :ELSE && post[0] == :IF) # Elsifs shouldn't get blocks.
|
162
|
+
starter = token[0]
|
122
163
|
line = token[1].line
|
123
164
|
@tokens.insert(i + 1, [:INDENT, Value.new(2, line)])
|
124
165
|
idx = i + 1
|
@@ -126,9 +167,11 @@ module CoffeeScript
|
|
126
167
|
loop do
|
127
168
|
idx += 1
|
128
169
|
tok = @tokens[idx]
|
129
|
-
if !tok || SINGLE_CLOSERS.include?(tok[0]) ||
|
130
|
-
(tok[0] == ')' && parens == 0)
|
131
|
-
|
170
|
+
if (!tok || SINGLE_CLOSERS.include?(tok[0]) ||
|
171
|
+
(tok[0] == ')' && parens == 0)) &&
|
172
|
+
!(starter == :ELSE && tok[0] == :ELSE)
|
173
|
+
insertion = @tokens[idx - 1][0] == "," ? idx - 1 : idx
|
174
|
+
@tokens.insert(insertion, [:OUTDENT, Value.new(2, line)])
|
132
175
|
break
|
133
176
|
end
|
134
177
|
parens += 1 if tok[0] == '('
|
@@ -140,25 +183,52 @@ module CoffeeScript
|
|
140
183
|
end
|
141
184
|
end
|
142
185
|
|
186
|
+
# Methods may be optionally called without parentheses, for simple cases.
|
187
|
+
# Insert the implicit parentheses here, so that the parser doesn't have to
|
188
|
+
# deal with them.
|
189
|
+
def add_implicit_parentheses
|
190
|
+
stack = [0]
|
191
|
+
scan_tokens do |prev, token, post, i|
|
192
|
+
stack.push(0) if token[0] == :INDENT
|
193
|
+
if token[0] == :OUTDENT
|
194
|
+
last = stack.pop
|
195
|
+
stack[-1] += last
|
196
|
+
end
|
197
|
+
if stack.last > 0 && (IMPLICIT_END.include?(token[0]) || post.nil?)
|
198
|
+
idx = token[0] == :OUTDENT ? i + 1 : i
|
199
|
+
stack.last.times { @tokens.insert(idx, [:CALL_END, Value.new(')', token[1].line)]) }
|
200
|
+
size, stack[-1] = stack[-1] + 1, 0
|
201
|
+
next size
|
202
|
+
end
|
203
|
+
next 1 unless IMPLICIT_FUNC.include?(prev[0]) && IMPLICIT_CALL.include?(token[0])
|
204
|
+
@tokens.insert(i, [:CALL_START, Value.new('(', token[1].line)])
|
205
|
+
stack[-1] += 1
|
206
|
+
next 2
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
143
210
|
# Ensure that all listed pairs of tokens are correctly balanced throughout
|
144
211
|
# the course of the token stream.
|
145
212
|
def ensure_balance(*pairs)
|
146
|
-
|
213
|
+
puts "\nbefore ensure_balance: #{@tokens.inspect}" if ENV['VERBOSE']
|
214
|
+
levels, lines = Hash.new(0), Hash.new
|
147
215
|
scan_tokens do |prev, token, post, i|
|
148
216
|
pairs.each do |pair|
|
149
217
|
open, close = *pair
|
150
218
|
levels[open] += 1 if token[0] == open
|
151
219
|
levels[open] -= 1 if token[0] == close
|
220
|
+
lines[token[0]] = token[1].line
|
152
221
|
raise ParseError.new(token[0], token[1], nil) if levels[open] < 0
|
153
222
|
end
|
154
223
|
next 1
|
155
224
|
end
|
156
225
|
unclosed = levels.detect {|k, v| v > 0 }
|
157
|
-
|
226
|
+
sym = unclosed && unclosed[0]
|
227
|
+
raise ParseError.new(sym, Value.new(sym, lines[sym]), nil, "unclosed '#{sym}'") if unclosed
|
158
228
|
end
|
159
229
|
|
160
230
|
# We'd like to support syntax like this:
|
161
|
-
# el.click(event
|
231
|
+
# el.click((event) ->
|
162
232
|
# el.hide())
|
163
233
|
# In order to accomplish this, move outdents that follow closing parens
|
164
234
|
# inwards, safely. The steps to accomplish this are:
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coffee-script
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Ashkenas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-01-
|
12
|
+
date: 2010-01-26 00:00:00 -05:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -24,15 +24,15 @@ extra_rdoc_files: []
|
|
24
24
|
files:
|
25
25
|
- bin/coffee
|
26
26
|
- examples/code.coffee
|
27
|
-
- examples/documents.coffee
|
28
27
|
- examples/poignant.coffee
|
29
28
|
- examples/potion.coffee
|
30
|
-
- examples/syntax_errors.coffee
|
31
29
|
- examples/underscore.coffee
|
30
|
+
- extras/coffee.vim
|
31
|
+
- extras/CoffeeScript.tmbundle/info.plist
|
32
|
+
- extras/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences
|
33
|
+
- extras/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
|
34
|
+
- extras/EXTRAS
|
32
35
|
- lib/coffee-script.rb
|
33
|
-
- lib/coffee_script/CoffeeScript.tmbundle/info.plist
|
34
|
-
- lib/coffee_script/CoffeeScript.tmbundle/Preferences/CoffeeScript.tmPreferences
|
35
|
-
- lib/coffee_script/CoffeeScript.tmbundle/Syntaxes/CoffeeScript.tmLanguage
|
36
36
|
- lib/coffee_script/command_line.rb
|
37
37
|
- lib/coffee_script/grammar.y
|
38
38
|
- lib/coffee_script/lexer.rb
|
data/examples/documents.coffee
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
# Document Model
|
2
|
-
dc.model.Document: dc.Model.extend({
|
3
|
-
|
4
|
-
constructor: attributes => this.base(attributes)
|
5
|
-
|
6
|
-
# For display, show either the highlighted search results, or the summary,
|
7
|
-
# if no highlights are available.
|
8
|
-
# The import process will take care of this in the future, but the inline
|
9
|
-
# version of the summary has all runs of whitespace squeezed out.
|
10
|
-
displaySummary: =>
|
11
|
-
text: this.get('highlight') or this.get('summary') or ''
|
12
|
-
text and text.replace(/\s+/g, ' ')
|
13
|
-
|
14
|
-
# Return a list of the document's metadata. Think about caching this on the
|
15
|
-
# document by binding to Metadata, instead of on-the-fly.
|
16
|
-
metadata: =>
|
17
|
-
docId: this.id
|
18
|
-
_.select(Metadata.models(), (meta =>
|
19
|
-
_.any(meta.get('instances'), instance =>
|
20
|
-
instance.document_id is docId)))
|
21
|
-
|
22
|
-
bookmark: pageNumber =>
|
23
|
-
bookmark: new dc.model.Bookmark({title: this.get('title'), page_number: pageNumber, document_id: this.id})
|
24
|
-
Bookmarks.create(bookmark)
|
25
|
-
|
26
|
-
# Inspect.
|
27
|
-
toString: => 'Document ' + this.id + ' "' + this.get('title') + '"'
|
28
|
-
|
29
|
-
})
|
30
|
-
|
31
|
-
# Document Set
|
32
|
-
dc.model.DocumentSet: dc.model.RESTfulSet.extend({
|
33
|
-
|
34
|
-
resource: 'documents'
|
35
|
-
|
36
|
-
SELECTION_CHANGED: 'documents:selection_changed'
|
37
|
-
|
38
|
-
constructor: options =>
|
39
|
-
this.base(options)
|
40
|
-
_.bindAll(this, 'downloadSelectedViewers', 'downloadSelectedPDF', 'downloadSelectedFullText')
|
41
|
-
|
42
|
-
selected: => _.select(this.models(), m => m.get('selected'))
|
43
|
-
|
44
|
-
selectedIds: => _.pluck(this.selected(), 'id')
|
45
|
-
|
46
|
-
countSelected: => this.selected().length
|
47
|
-
|
48
|
-
downloadSelectedViewers: =>
|
49
|
-
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_viewer.zip')
|
50
|
-
|
51
|
-
downloadSelectedPDF: =>
|
52
|
-
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('pdf_url'))
|
53
|
-
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_pdfs.zip')
|
54
|
-
|
55
|
-
downloadSelectedFullText: =>
|
56
|
-
if this.countSelected() <= 1 then return window.open(this.selected()[0].get('full_text_url'))
|
57
|
-
dc.app.download('/download/' + this.selectedIds().join('/') + '/document_text.zip')
|
58
|
-
|
59
|
-
# We override "_onModelEvent" to fire selection changed events when documents
|
60
|
-
# change their selected state.
|
61
|
-
_onModelEvent: e, model =>
|
62
|
-
this.base(e, model)
|
63
|
-
fire: e is dc.Model.CHANGED and model.hasChanged('selected')
|
64
|
-
if fire then _.defer(_(this.fire).bind(this, this.SELECTION_CHANGED, this))
|
65
|
-
|
66
|
-
})
|
67
|
-
|
68
|
-
# The main set of Documents, used by the search tab.
|
69
|
-
window.Documents: new dc.model.DocumentSet()
|
70
|
-
|
71
|
-
# The set of documents that is used to look at a particular label.
|
72
|
-
dc.app.LabeledDocuments: new dc.model.DocumentSet()
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# Identifiers run together:
|
2
|
-
# a b c
|
3
|
-
|
4
|
-
# Trailing comma in array:
|
5
|
-
# array: [1, 2, 3, 4, 5,]
|
6
|
-
|
7
|
-
# Unterminated object literal:
|
8
|
-
# obj: { one: 1, two: 2
|
9
|
-
|
10
|
-
# Numbers run together:
|
11
|
-
# 101 202
|
12
|
-
|
13
|
-
# Strings run together:
|
14
|
-
# str: "broken" "words"
|
15
|
-
|
16
|
-
# Forgot to terminate a function:
|
17
|
-
# obj: {
|
18
|
-
# first: a => a[0].
|
19
|
-
# last: a => a[a.length-1]
|
20
|
-
# }
|