coffee-script 0.3.1 → 0.3.2

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.
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'coffee-script'
3
- s.version = '0.3.1' # Keep version in sync with coffee-script.rb
4
- s.date = '2010-1-27'
3
+ s.version = '0.3.2' # Keep version in sync with coffee-script.rb
4
+ s.date = '2010-2-8'
5
5
 
6
6
  s.homepage = "http://jashkenas.github.com/coffee-script/"
7
7
  s.summary = "The CoffeeScript Compiler"
@@ -0,0 +1,57 @@
1
+ # After wycats' http://yehudakatz.com/2010/02/07/the-building-blocks-of-ruby/
2
+
3
+ # Sinatra.
4
+ get '/hello', ->
5
+ 'Hello World'
6
+
7
+
8
+ # Append.
9
+ append: (location, data) ->
10
+ path: new Pathname location
11
+ throw "Location does not exist" unless path.exists()
12
+
13
+ File.open path, 'a', (file) ->
14
+ file.puts YAML.dump data
15
+
16
+ data
17
+
18
+
19
+ # Rubinius' File.open implementation.
20
+ File.open: (path, mode, block) ->
21
+ io: new File path, mode
22
+
23
+ return io unless block
24
+
25
+ try
26
+ block io
27
+ finally
28
+ try
29
+ io.close() unless io.closed()
30
+ catch error
31
+ # nothing, just swallow them.
32
+
33
+
34
+ # Write.
35
+ write: (location, data) ->
36
+ path = new Pathname location
37
+ raise "Location does not exist" unless path.exists()
38
+
39
+ File.open path, 'w', (file) ->
40
+ return false if Digest.MD5.hexdigest(file.read()) is data.hash()
41
+ file.puts YAML.dump data
42
+ true
43
+
44
+
45
+ # Rails' respond_to.
46
+ index: ->
47
+ people: Person.find 'all'
48
+
49
+ respond_to (format) ->
50
+ format.html()
51
+ format.xml -> render { xml: people.xml() }
52
+
53
+
54
+ # Synchronization.
55
+ synchronize: (block) ->
56
+ lock()
57
+ try block() finally unlock()
@@ -72,6 +72,12 @@
72
72
  <key>name</key>
73
73
  <string>constant.numeric.coffee</string>
74
74
  </dict>
75
+ <dict>
76
+ <key>match</key>
77
+ <string>(@)[a-zA-Z_$]\w*</string>
78
+ <key>name</key>
79
+ <string>variable.other.readwrite.instance.coffee</string>
80
+ </dict>
75
81
  <dict>
76
82
  <key>name</key>
77
83
  <string>string.quoted.heredoc.coffee</string>
data/lib/coffee-script.rb CHANGED
@@ -10,7 +10,7 @@ require "coffee_script/parse_error"
10
10
  # Namespace for all CoffeeScript internal classes.
11
11
  module CoffeeScript
12
12
 
13
- VERSION = '0.3.1' # Keep in sync with the gemspec.
13
+ VERSION = '0.3.2' # Keep in sync with the gemspec.
14
14
 
15
15
  # Compile a script (String or IO) to JavaScript.
16
16
  def self.compile(script, options={})
@@ -0,0 +1,50 @@
1
+ (function(){
2
+ var compiler, path;
3
+ // Executes the `coffee` Ruby program to convert from CoffeeScript to JavaScript.
4
+ path = require('path');
5
+ // The path to the CoffeeScript executable.
6
+ compiler = path.normalize(path.dirname(__filename) + '/../../bin/coffee');
7
+ // Compile a string over stdin, with global variables, for the REPL.
8
+ exports.compile = function compile(code, callback) {
9
+ var coffee, js;
10
+ js = '';
11
+ coffee = process.createChildProcess(compiler, ['--eval', '--no-wrap', '--globals']);
12
+ coffee.addListener('output', function(results) {
13
+ if ((typeof results !== "undefined" && results !== null)) {
14
+ return js += results;
15
+ }
16
+ });
17
+ coffee.addListener('exit', function() {
18
+ return callback(js);
19
+ });
20
+ coffee.write(code);
21
+ return coffee.close();
22
+ };
23
+ // Compile a list of CoffeeScript files on disk.
24
+ exports.compile_files = function compile_files(paths, callback) {
25
+ var coffee, exit_ran, js;
26
+ js = '';
27
+ coffee = process.createChildProcess(compiler, ['--print'].concat(paths));
28
+ coffee.addListener('output', function(results) {
29
+ if ((typeof results !== "undefined" && results !== null)) {
30
+ return js += results;
31
+ }
32
+ });
33
+ // NB: we have to add a mutex to make sure it doesn't get called twice.
34
+ exit_ran = false;
35
+ coffee.addListener('exit', function() {
36
+ if (exit_ran) {
37
+ return null;
38
+ }
39
+ exit_ran = true;
40
+ return callback(js);
41
+ });
42
+ return coffee.addListener('error', function(message) {
43
+ if (!(message)) {
44
+ return null;
45
+ }
46
+ puts(message);
47
+ throw new Error("CoffeeScript compile error");
48
+ });
49
+ };
50
+ })();
@@ -28,8 +28,11 @@ Usage:
28
28
  # Path to the root of the CoffeeScript install.
29
29
  ROOT = File.expand_path(File.dirname(__FILE__) + '/../..')
30
30
 
31
- # Command to execute in Narwhal
32
- LAUNCHER = "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
31
+ # Commands to execute CoffeeScripts.
32
+ RUNNERS = {
33
+ :node => "node #{ROOT}/lib/coffee_script/runner.js",
34
+ :narwhal => "narwhal -p #{ROOT} -e 'require(\"coffee-script\").run(system.args);'"
35
+ }
33
36
 
34
37
  # Run the CommandLine off the contents of ARGV.
35
38
  def initialize
@@ -114,20 +117,20 @@ Usage:
114
117
  puts js
115
118
  end
116
119
 
117
- # Use Narwhal to run an interactive CoffeeScript session.
120
+ # Use Node.js or Narwhal to run an interactive CoffeeScript session.
118
121
  def launch_repl
119
- exec "#{LAUNCHER}"
122
+ exec "#{RUNNERS[@options[:runner]]}"
120
123
  rescue Errno::ENOENT
121
- puts "Error: Narwhal must be installed to use the interactive REPL."
124
+ puts "Error: #{@options[:runner]} must be installed to use the interactive REPL."
122
125
  exit(1)
123
126
  end
124
127
 
125
- # Use Narwhal to compile and execute CoffeeScripts.
128
+ # Use Node.js or Narwhal to compile and execute CoffeeScripts.
126
129
  def run_scripts
127
130
  sources = @sources.join(' ')
128
- exec "#{LAUNCHER} #{sources}"
131
+ exec "#{RUNNERS[@options[:runner]]} #{sources}"
129
132
  rescue Errno::ENOENT
130
- puts "Error: Narwhal must be installed in order to execute CoffeeScripts."
133
+ puts "Error: #{@options[:runner]} must be installed in order to execute scripts."
131
134
  exit(1)
132
135
  end
133
136
 
@@ -166,12 +169,12 @@ Usage:
166
169
 
167
170
  # Use OptionParser for all the options.
168
171
  def parse_options
169
- @options = {}
172
+ @options = {:runner => :node}
170
173
  @option_parser = OptionParser.new do |opts|
171
- opts.on('-i', '--interactive', 'run a CoffeeScript REPL (requires Narwhal)') do |i|
174
+ opts.on('-i', '--interactive', 'run an interactive CoffeeScript REPL') do |i|
172
175
  @options[:interactive] = true
173
176
  end
174
- opts.on('-r', '--run', 'compile and run a script (requires Narwhal)') do |r|
177
+ opts.on('-r', '--run', 'compile and run a CoffeeScript') do |r|
175
178
  @options[:run] = true
176
179
  end
177
180
  opts.on('-o', '--output [DIR]', 'set the directory for compiled JavaScript') do |d|
@@ -202,6 +205,9 @@ Usage:
202
205
  opts.on('-g', '--globals', 'attach all top-level variable as globals') do |n|
203
206
  @options[:globals] = true
204
207
  end
208
+ opts.on_tail('--narwhal', 'use Narwhal instead of Node.js') do |n|
209
+ @options[:runner] = :narwhal
210
+ end
205
211
  opts.on_tail('--install-bundle', 'install the CoffeeScript TextMate bundle') do |i|
206
212
  install_bundle
207
213
  exit
@@ -13,7 +13,7 @@ token FOR IN OF BY WHEN WHILE
13
13
  token SWITCH LEADING_WHEN
14
14
  token DELETE INSTANCEOF TYPEOF
15
15
  token SUPER EXTENDS
16
- token ARGUMENTS
16
+ token ASSIGN RETURN
17
17
  token NEWLINE
18
18
  token COMMENT
19
19
  token JS
@@ -21,24 +21,20 @@ token INDENT OUTDENT
21
21
 
22
22
  # Declare order of operations.
23
23
  prechigh
24
- left '?'
25
24
  nonassoc UMINUS UPLUS NOT '!' '!!' '~' '++' '--'
26
- left '*' '/' '%'
25
+ left '*' '/' '%' '?' '.'
27
26
  left '+' '-'
28
- left '<<' '>>' '>>>'
29
- left '&' '|' '^'
27
+ left '<<' '>>' '>>>' '&' '|' '^'
30
28
  left '<=' '<' '>' '>='
31
29
  right '==' '!=' IS ISNT
32
30
  left '&&' '||' AND OR
33
- right '-=' '+=' '/=' '*=' '%='
31
+ right '-=' '+=' '/=' '*=' '%=' '||=' '&&=' '?='
34
32
  right DELETE INSTANCEOF TYPEOF
35
- left '.'
36
33
  right INDENT
37
34
  left OUTDENT
38
35
  right WHEN LEADING_WHEN IN OF BY
39
36
  right THROW FOR NEW SUPER
40
37
  left EXTENDS
41
- left '||=' '&&=' '?='
42
38
  right ASSIGN RETURN
43
39
  right '->' '=>' UNLESS IF ELSE WHILE
44
40
  preclow
@@ -102,7 +98,6 @@ rule
102
98
  | REGEX { result = LiteralNode.new(val[0]) }
103
99
  | BREAK { result = LiteralNode.new(val[0]) }
104
100
  | CONTINUE { result = LiteralNode.new(val[0]) }
105
- | ARGUMENTS { result = LiteralNode.new(val[0]) }
106
101
  | TRUE { result = LiteralNode.new(Value.new(true)) }
107
102
  | FALSE { result = LiteralNode.new(Value.new(false)) }
108
103
  | YES { result = LiteralNode.new(Value.new(true)) }
@@ -238,6 +233,7 @@ rule
238
233
  | Object { result = ValueNode.new(val[0]) }
239
234
  | Parenthetical { result = ValueNode.new(val[0]) }
240
235
  | Range { result = ValueNode.new(val[0]) }
236
+ | This { result = ValueNode.new(val[0]) }
241
237
  | Value Accessor { result = val[0] << val[1] }
242
238
  | Invocation Accessor { result = ValueNode.new(val[0], [val[1]]) }
243
239
  ;
@@ -300,6 +296,12 @@ rule
300
296
  SUPER CALL_START ArgList CALL_END { result = CallNode.new(Value.new('super'), val[2]) }
301
297
  ;
302
298
 
299
+ # This references, either naked or to a property.
300
+ This:
301
+ '@' { result = ThisNode.new }
302
+ | '@' IDENTIFIER { result = ThisNode.new(val[1]) }
303
+ ;
304
+
303
305
  # The range literal.
304
306
  Range:
305
307
  "[" Expression
@@ -0,0 +1,363 @@
1
+ (function(){
2
+ var ASSIGNMENT, CALLABLE, CODE, COMMENT, COMMENT_CLEANER, HEREDOC, HEREDOC_INDENT, IDENTIFIER, JS, JS_CLEANER, KEYWORDS, LAST_DENT, LAST_DENTS, MULTILINER, MULTI_DENT, NOT_REGEX, NO_NEWLINE, NUMBER, OPERATOR, REGEX, Rewriter, STRING, STRING_NEWLINES, WHITESPACE, lex;
3
+ Rewriter = require('./rewriter').Rewriter;
4
+ // The lexer reads a stream of CoffeeScript and divvys it up into tagged
5
+ // tokens. A minor bit of the ambiguity in the grammar has been avoided by
6
+ // pushing some extra smarts into the Lexer.
7
+ exports.Lexer = (lex = function lex() { });
8
+ // Constants ============================================================
9
+ // The list of keywords passed verbatim to the parser.
10
+ KEYWORDS = ["if", "else", "then", "unless", "true", "false", "yes", "no", "on", "off", "and", "or", "is", "isnt", "not", "new", "return", "arguments", "try", "catch", "finally", "throw", "break", "continue", "for", "in", "of", "by", "where", "while", "delete", "instanceof", "typeof", "switch", "when", "super", "extends"];
11
+ // Token matching regexes.
12
+ IDENTIFIER = /^([a-zA-Z$_](\w|\$)*)/;
13
+ NUMBER = /^(\b((0(x|X)[0-9a-fA-F]+)|([0-9]+(\.[0-9]+)?(e[+\-]?[0-9]+)?)))\b/i;
14
+ STRING = /^(""|''|"([\s\S]*?)([^\\]|\\\\)"|'([\s\S]*?)([^\\]|\\\\)')/;
15
+ HEREDOC = /^("{6}|'{6}|"{3}\n?([\s\S]*?)\n?([ \t]*)"{3}|'{3}\n?([\s\S]*?)\n?([ \t]*)'{3})/;
16
+ JS = /^(``|`([\s\S]*?)([^\\]|\\\\)`)/;
17
+ OPERATOR = /^([+\*&|\/\-%=<>:!?]+)/;
18
+ WHITESPACE = /^([ \t]+)/;
19
+ COMMENT = /^(((\n?[ \t]*)?#.*$)+)/;
20
+ CODE = /^((-|=)>)/;
21
+ REGEX = /^(\/(.*?)([^\\]|\\\\)\/[imgy]{0,4})/;
22
+ MULTI_DENT = /^((\n([ \t]*))+)(\.)?/;
23
+ LAST_DENTS = /\n([ \t]*)/g;
24
+ LAST_DENT = /\n([ \t]*)/;
25
+ ASSIGNMENT = /^(:|=)$/;
26
+ // Token cleaning regexes.
27
+ JS_CLEANER = /(^`|`$)/g;
28
+ MULTILINER = /\n/g;
29
+ STRING_NEWLINES = /\n[ \t]*/g;
30
+ COMMENT_CLEANER = /(^[ \t]*#|\n[ \t]*$)/mg;
31
+ NO_NEWLINE = /^([+\*&|\/\-%=<>:!.\\][<>=&|]*|and|or|is|isnt|not|delete|typeof|instanceof)$/;
32
+ HEREDOC_INDENT = /^[ \t]+/g;
33
+ // Tokens which a regular expression will never immediately follow, but which
34
+ // a division operator might.
35
+ // See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
36
+ NOT_REGEX = ['IDENTIFIER', 'NUMBER', 'REGEX', 'STRING', ')', '++', '--', ']', '}', 'FALSE', 'NULL', 'TRUE'];
37
+ // Tokens which could legitimately be invoked or indexed.
38
+ CALLABLE = ['IDENTIFIER', 'SUPER', ')', ']', '}', 'STRING'];
39
+ // Scan by attempting to match tokens one character at a time. Slow and steady.
40
+ lex.prototype.tokenize = function tokenize(code) {
41
+ this.code = code;
42
+ // Cleanup code by remove extra line breaks, TODO: chomp
43
+ this.i = 0;
44
+ // Current character position we're parsing
45
+ this.line = 1;
46
+ // The current line.
47
+ this.indent = 0;
48
+ // The current indent level.
49
+ this.indents = [];
50
+ // The stack of all indent levels we are currently within.
51
+ this.tokens = [];
52
+ // Collection of all parsed tokens in the form [:TOKEN_TYPE, value]
53
+ this.spaced = null;
54
+ // The last token that has a space following it.
55
+ while (this.i < this.code.length) {
56
+ this.chunk = this.code.slice(this.i);
57
+ this.extract_next_token();
58
+ }
59
+ this.close_indentation();
60
+ return (new Rewriter()).rewrite(this.tokens);
61
+ };
62
+ // At every position, run through this list of attempted matches,
63
+ // short-circuiting if any of them succeed.
64
+ lex.prototype.extract_next_token = function extract_next_token() {
65
+ if (this.identifier_token()) {
66
+ return null;
67
+ }
68
+ if (this.number_token()) {
69
+ return null;
70
+ }
71
+ if (this.heredoc_token()) {
72
+ return null;
73
+ }
74
+ if (this.string_token()) {
75
+ return null;
76
+ }
77
+ if (this.js_token()) {
78
+ return null;
79
+ }
80
+ if (this.regex_token()) {
81
+ return null;
82
+ }
83
+ if (this.indent_token()) {
84
+ return null;
85
+ }
86
+ if (this.comment_token()) {
87
+ return null;
88
+ }
89
+ if (this.whitespace_token()) {
90
+ return null;
91
+ }
92
+ return this.literal_token();
93
+ };
94
+ // Tokenizers ==========================================================
95
+ // Matches identifying literals: variables, keywords, method names, etc.
96
+ lex.prototype.identifier_token = function identifier_token() {
97
+ var id, tag;
98
+ if (!((id = this.match(IDENTIFIER, 1)))) {
99
+ return false;
100
+ }
101
+ // Keywords are special identifiers tagged with their own name,
102
+ // 'if' will result in an ['IF', "if"] token.
103
+ tag = KEYWORDS.indexOf(id) >= 0 ? id.toUpperCase() : 'IDENTIFIER';
104
+ if (tag === 'WHEN' && (this.tag() === 'OUTDENT' || this.tag() === 'INDENT')) {
105
+ tag = 'LEADING_WHEN';
106
+ }
107
+ if (tag === 'IDENTIFIER' && this.value() === '::') {
108
+ this.tag(-1, 'PROTOTYPE_ACCESS');
109
+ }
110
+ if (tag === 'IDENTIFIER' && this.value() === '.' && !(this.value(-2) === '.')) {
111
+ if (this.tag(-2) === '?') {
112
+ this.tag(-1, 'SOAK_ACCESS');
113
+ this.tokens.splice(-2, 1);
114
+ } else {
115
+ this.tag(-1, 'PROPERTY_ACCESS');
116
+ }
117
+ }
118
+ this.token(tag, id);
119
+ this.i += id.length;
120
+ return true;
121
+ };
122
+ // Matches numbers, including decimals, hex, and exponential notation.
123
+ lex.prototype.number_token = function number_token() {
124
+ var number;
125
+ if (!((number = this.match(NUMBER, 1)))) {
126
+ return false;
127
+ }
128
+ this.token('NUMBER', number);
129
+ this.i += number.length;
130
+ return true;
131
+ };
132
+ // Matches strings, including multi-line strings.
133
+ lex.prototype.string_token = function string_token() {
134
+ var escaped, string;
135
+ if (!((string = this.match(STRING, 1)))) {
136
+ return false;
137
+ }
138
+ escaped = string.replace(STRING_NEWLINES, " \\\n");
139
+ this.token('STRING', escaped);
140
+ this.line += this.count(string, "\n");
141
+ this.i += string.length;
142
+ return true;
143
+ };
144
+ // Matches heredocs, adjusting indentation to the correct level.
145
+ lex.prototype.heredoc_token = function heredoc_token() {
146
+ var doc, indent, match;
147
+ if (!((match = this.chunk.match(HEREDOC)))) {
148
+ return false;
149
+ }
150
+ doc = match[2] || match[4];
151
+ indent = doc.match(HEREDOC_INDENT).sort()[0];
152
+ doc = doc.replace(new RegExp("^" + indent, 'g'), '').replace(MULTILINER, "\\n").replace('"', '\\"');
153
+ this.token('STRING', '"' + doc + '"');
154
+ this.line += this.count(match[1], "\n");
155
+ this.i += match[1].length;
156
+ return true;
157
+ };
158
+ // Matches interpolated JavaScript.
159
+ lex.prototype.js_token = function js_token() {
160
+ var script;
161
+ if (!((script = this.match(JS, 1)))) {
162
+ return false;
163
+ }
164
+ this.token('JS', script.replace(JS_CLEANER, ''));
165
+ this.i += script.length;
166
+ return true;
167
+ };
168
+ // Matches regular expression literals.
169
+ lex.prototype.regex_token = function regex_token() {
170
+ var regex;
171
+ if (!((regex = this.match(REGEX, 1)))) {
172
+ return false;
173
+ }
174
+ if (NOT_REGEX.indexOf(this.tag()) >= 0) {
175
+ return false;
176
+ }
177
+ this.token('REGEX', regex);
178
+ this.i += regex.length;
179
+ return true;
180
+ };
181
+ // Matches and conumes comments.
182
+ lex.prototype.comment_token = function comment_token() {
183
+ var comment;
184
+ if (!((comment = this.match(COMMENT, 1)))) {
185
+ return false;
186
+ }
187
+ this.line += comment.match(MULTILINER).length;
188
+ this.token('COMMENT', comment.replace(COMMENT_CLEANER, '').split(MULTILINER));
189
+ this.token('TERMINATOR', "\n");
190
+ this.i += comment.length;
191
+ return true;
192
+ };
193
+ // Record tokens for indentation differing from the previous line.
194
+ lex.prototype.indent_token = function indent_token() {
195
+ var diff, indent, next_character, no_newlines, size;
196
+ if (!((indent = this.match(MULTI_DENT, 1)))) {
197
+ return false;
198
+ }
199
+ this.line += indent.match(MULTILINER).length;
200
+ this.i += indent.length;
201
+ next_character = this.chunk.match(MULTI_DENT)[4];
202
+ no_newlines = next_character === '.' || (this.value().match(NO_NEWLINE) && this.tokens[this.tokens.length - 2][0] !== '.' && !this.value().match(CODE));
203
+ if (no_newlines) {
204
+ return this.suppress_newlines(indent);
205
+ }
206
+ size = indent.match(LAST_DENTS).reverse()[0].match(LAST_DENT)[1].length;
207
+ if (size === this.indent) {
208
+ return this.newline_token(indent);
209
+ }
210
+ if (size > this.indent) {
211
+ diff = size - this.indent;
212
+ this.token('INDENT', diff);
213
+ this.indents.push(diff);
214
+ } else {
215
+ this.outdent_token(this.indent - size);
216
+ }
217
+ this.indent = size;
218
+ return true;
219
+ };
220
+ // Record an oudent token or tokens, if we're moving back inwards past
221
+ // multiple recorded indents.
222
+ lex.prototype.outdent_token = function outdent_token(move_out) {
223
+ var last_indent;
224
+ while (move_out > 0 && this.indents.length) {
225
+ last_indent = this.indents.pop();
226
+ this.token('OUTDENT', last_indent);
227
+ move_out -= last_indent;
228
+ }
229
+ this.token('TERMINATOR', "\n");
230
+ return true;
231
+ };
232
+ // Matches and consumes non-meaningful whitespace.
233
+ lex.prototype.whitespace_token = function whitespace_token() {
234
+ var space;
235
+ if (!((space = this.match(WHITESPACE, 1)))) {
236
+ return false;
237
+ }
238
+ this.spaced = this.value();
239
+ this.i += space.length;
240
+ return true;
241
+ };
242
+ // Multiple newlines get merged together.
243
+ // Use a trailing \ to escape newlines.
244
+ lex.prototype.newline_token = function newline_token(newlines) {
245
+ if (!(this.value() === "\n")) {
246
+ this.token('TERMINATOR', "\n");
247
+ }
248
+ return true;
249
+ };
250
+ // Tokens to explicitly escape newlines are removed once their job is done.
251
+ lex.prototype.suppress_newlines = function suppress_newlines(newlines) {
252
+ if (this.value() === "\\") {
253
+ this.tokens.pop();
254
+ }
255
+ return true;
256
+ };
257
+ // We treat all other single characters as a token. Eg.: ( ) , . !
258
+ // Multi-character operators are also literal tokens, so that Racc can assign
259
+ // the proper order of operations.
260
+ lex.prototype.literal_token = function literal_token() {
261
+ var match, tag, value;
262
+ match = this.chunk.match(OPERATOR);
263
+ value = match && match[1];
264
+ if (value && value.match(CODE)) {
265
+ this.tag_parameters();
266
+ }
267
+ value = value || this.chunk.substr(0, 1);
268
+ tag = value.match(ASSIGNMENT) ? 'ASSIGN' : value;
269
+ if (value === ';') {
270
+ tag = 'TERMINATOR';
271
+ }
272
+ if (this.value() !== this.spaced && CALLABLE.indexOf(this.tag()) >= 0) {
273
+ if (value === '(') {
274
+ tag = 'CALL_START';
275
+ }
276
+ if (value === '[') {
277
+ tag = 'INDEX_START';
278
+ }
279
+ }
280
+ this.token(tag, value);
281
+ this.i += value.length;
282
+ return true;
283
+ };
284
+ // Helpers =============================================================
285
+ // Add a token to the results, taking note of the line number.
286
+ lex.prototype.token = function token(tag, value) {
287
+ return this.tokens.push([tag, value]);
288
+ // this.tokens.push([tag, Value.new(value, @line)])
289
+ };
290
+ // Look at a tag in the current token stream.
291
+ lex.prototype.tag = function tag(index, tag) {
292
+ var tok;
293
+ if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
294
+ return null;
295
+ }
296
+ if ((typeof tag !== "undefined" && tag !== null)) {
297
+ return (tok[0] = tag);
298
+ }
299
+ return tok[0];
300
+ };
301
+ // Look at a value in the current token stream.
302
+ lex.prototype.value = function value(index, val) {
303
+ var tok;
304
+ if (!((tok = this.tokens[this.tokens.length - (index || 1)]))) {
305
+ return null;
306
+ }
307
+ if ((typeof val !== "undefined" && val !== null)) {
308
+ return (tok[1] = val);
309
+ }
310
+ return tok[1];
311
+ };
312
+ // Count the occurences of a character in a string.
313
+ lex.prototype.count = function count(string, letter) {
314
+ var num, pos;
315
+ num = 0;
316
+ pos = string.indexOf(letter);
317
+ while (pos !== -1) {
318
+ count += 1;
319
+ pos = string.indexOf(letter, pos + 1);
320
+ }
321
+ return count;
322
+ };
323
+ // Attempt to match a string against the current chunk, returning the indexed
324
+ // match.
325
+ lex.prototype.match = function match(regex, index) {
326
+ var m;
327
+ if (!((m = this.chunk.match(regex)))) {
328
+ return false;
329
+ }
330
+ return m ? m[index] : false;
331
+ };
332
+ // A source of ambiguity in our grammar was parameter lists in function
333
+ // definitions (as opposed to argument lists in function calls). Tag
334
+ // parameter identifiers in order to avoid this. Also, parameter lists can
335
+ // make use of splats.
336
+ lex.prototype.tag_parameters = function tag_parameters() {
337
+ var i, tok;
338
+ if (this.tag() !== ')') {
339
+ return null;
340
+ }
341
+ i = 0;
342
+ while (true) {
343
+ i += 1;
344
+ tok = this.tokens[this.tokens.length - i];
345
+ if (!tok) {
346
+ return null;
347
+ }
348
+ if (tok[0] === 'IDENTIFIER') {
349
+ tok[0] = 'PARAM';
350
+ } else if (tok[0] === ')') {
351
+ tok[0] = 'PARAM_END';
352
+ } else if (tok[0] === '(') {
353
+ return (tok[0] = 'PARAM_START');
354
+ }
355
+ }
356
+ return true;
357
+ };
358
+ // Close up all remaining open blocks. IF the first token is an indent,
359
+ // axe it.
360
+ lex.prototype.close_indentation = function close_indentation() {
361
+ return this.outdent_token(this.indent);
362
+ };
363
+ })();