coffee-script 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ })();