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.
@@ -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
- # }