coffee-script 0.2.6 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, "=>", "==>", :TRY, :FINALLY, :THEN]
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, :OUTDENT].include?(after[0])
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
- @tokens.insert(idx, [:OUTDENT, Value.new(2, line)])
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
- levels = Hash.new(0)
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
- raise SyntaxError, "unclosed '#{unclosed[0]}'" if unclosed
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
@@ -5,5 +5,5 @@
5
5
  "description": "Unfancy JavaScript",
6
6
  "keywords": ["javascript", "language"],
7
7
  "author": "Jeremy Ashkenas",
8
- "version": "0.2.6"
8
+ "version": "0.3.0"
9
9
  }
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.2.6
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-17 00:00:00 -05:00
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
@@ -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
- # }