first bit of equals for symbology ... barely started on lexer.coffee

This commit is contained in:
Jeremy Ashkenas
2010-07-24 00:31:04 -07:00
parent e41abe2d83
commit bfc7704ca1
7 changed files with 164 additions and 164 deletions

View File

@@ -7,17 +7,17 @@
# current directory's Cakefile. # current directory's Cakefile.
# External dependencies. # External dependencies.
fs: require 'fs' fs = require 'fs'
path: require 'path' path = require 'path'
helpers: require('./helpers').helpers helpers = require('./helpers').helpers
optparse: require './optparse' optparse = require './optparse'
CoffeeScript: require './coffee-script' CoffeeScript = require './coffee-script'
# Keep track of the list of defined tasks, the accepted options, and so on. # Keep track of the list of defined tasks, the accepted options, and so on.
tasks: {} tasks = {}
options: {} options = {}
switches: [] switches = []
oparse: null oparse = null
# Mixin the top-level Cake functions for Cakefiles to use directly. # Mixin the top-level Cake functions for Cakefiles to use directly.
helpers.extend global, { helpers.extend global, {
@@ -25,8 +25,8 @@ helpers.extend global, {
# Define a Cake task with a short name, an optional sentence description, # Define a Cake task with a short name, an optional sentence description,
# and the function to run as the action itself. # and the function to run as the action itself.
task: (name, description, action) -> task: (name, description, action) ->
[action, description]: [description, action] unless action [action, description] = [description, action] unless action
tasks[name]: {name, description, action} tasks[name] = {name, description, action}
# Define an option that the Cakefile accepts. The parsed options hash, # Define an option that the Cakefile accepts. The parsed options hash,
# containing all of the command-line options passed, will be made available # containing all of the command-line options passed, will be made available
@@ -47,20 +47,20 @@ helpers.extend global, {
exports.run: -> exports.run: ->
path.exists 'Cakefile', (exists) -> path.exists 'Cakefile', (exists) ->
throw new Error("Cakefile not found in ${process.cwd()}") unless exists throw new Error("Cakefile not found in ${process.cwd()}") unless exists
args: process.argv[2...process.argv.length] args = process.argv[2...process.argv.length]
CoffeeScript.run fs.readFileSync('Cakefile').toString(), {source: 'Cakefile'} CoffeeScript.run fs.readFileSync('Cakefile').toString(), {source: 'Cakefile'}
oparse: new optparse.OptionParser switches oparse = new optparse.OptionParser switches
return printTasks() unless args.length return printTasks() unless args.length
options: oparse.parse(args) options = oparse.parse(args)
invoke arg for arg in options.arguments invoke arg for arg in options.arguments
# Display the list of Cake tasks in a format similar to `rake -T` # Display the list of Cake tasks in a format similar to `rake -T`
printTasks: -> printTasks: ->
puts '' puts ''
for all name, task of tasks for all name, task of tasks
spaces: 20 - name.length spaces = 20 - name.length
spaces: if spaces > 0 then (' ' for i in [0..spaces]).join('') else '' spaces = if spaces > 0 then (' ' for i in [0..spaces]).join('') else ''
desc: if task.description then "# $task.description" else '' desc = if task.description then "# $task.description" else ''
puts "cake $name$spaces $desc" puts "cake $name$spaces $desc"
puts oparse.help() if switches.length puts oparse.help() if switches.length

View File

@@ -8,66 +8,66 @@
# Set up dependencies correctly for both the server and the browser. # Set up dependencies correctly for both the server and the browser.
if process? if process?
path: require 'path' path = require 'path'
Lexer: require('./lexer').Lexer Lexer = require('./lexer').Lexer
parser: require('./parser').parser parser = require('./parser').parser
helpers: require('./helpers').helpers helpers = require('./helpers').helpers
helpers.extend global, require './nodes' helpers.extend global, require './nodes'
if require.registerExtension if require.registerExtension
require.registerExtension '.coffee', (content) -> compile content require.registerExtension '.coffee', (content) -> compile content
else else
this.exports: this.CoffeeScript: {} this.exports = this.CoffeeScript = {}
Lexer: this.Lexer Lexer = this.Lexer
parser: this.parser parser = this.parser
helpers: this.helpers helpers = this.helpers
# The current CoffeeScript version number. # The current CoffeeScript version number.
exports.VERSION: '0.7.2' exports.VERSION = '0.7.2'
# Instantiate a Lexer for our use here. # Instantiate a Lexer for our use here.
lexer: new Lexer lexer = new Lexer
# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison # Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
# compiler. # compiler.
exports.compile: compile: (code, options) -> exports.compile = compile = (code, options) ->
options: or {} options = or {}
try try
(parser.parse lexer.tokenize code).compile options (parser.parse lexer.tokenize code).compile options
catch err catch err
err.message: "In $options.source, $err.message" if options.source err.message = "In $options.source, $err.message" if options.source
throw err throw err
# Tokenize a string of CoffeeScript code, and return the array of tokens. # Tokenize a string of CoffeeScript code, and return the array of tokens.
exports.tokens: (code) -> exports.tokens = (code) ->
lexer.tokenize code lexer.tokenize code
# Tokenize and parse a string of CoffeeScript code, and return the AST. You can # Tokenize and parse a string of CoffeeScript code, and return the AST. You can
# then compile it by calling `.compile()` on the root, or traverse it by using # then compile it by calling `.compile()` on the root, or traverse it by using
# `.traverse()` with a callback. # `.traverse()` with a callback.
exports.nodes: (code) -> exports.nodes = (code) ->
parser.parse lexer.tokenize code parser.parse lexer.tokenize code
# Compile and execute a string of CoffeeScript (on the server), correctly # Compile and execute a string of CoffeeScript (on the server), correctly
# setting `__filename`, `__dirname`, and relative `require()`. # setting `__filename`, `__dirname`, and relative `require()`.
exports.run: ((code, options) -> exports.run = ((code, options) ->
module.filename: __filename: options.source module.filename = __filename = options.source
__dirname: path.dirname(__filename) __dirname = path.dirname __filename
eval exports.compile code, options eval exports.compile code, options
) )
# The real Lexer produces a generic stream of tokens. This object provides a # The real Lexer produces a generic stream of tokens. This object provides a
# thin wrapper around it, compatible with the Jison API. We can then pass it # thin wrapper around it, compatible with the Jison API. We can then pass it
# directly as a "Jison lexer". # directly as a "Jison lexer".
parser.lexer: { parser.lexer = {
lex: -> lex: ->
token: @tokens[@pos] or [""] token = @tokens[@pos] or [""]
@pos: + 1 @pos = + 1
this.yylineno: token[2] this.yylineno = token[2]
this.yytext: token[1] this.yytext = token[1]
token[0] token[0]
setInput: (tokens) -> setInput: (tokens) ->
@tokens: tokens @tokens = tokens
@pos: 0 @pos = 0
upcomingInput: -> "" upcomingInput: -> ""
} }
@@ -76,7 +76,7 @@ parser.lexer: {
# on page load. Unfortunately, the text contents of remote scripts cannot be # on page load. Unfortunately, the text contents of remote scripts cannot be
# accessed from the browser, so only inline script tags will work. # accessed from the browser, so only inline script tags will work.
if document? and document.getElementsByTagName if document? and document.getElementsByTagName
processScripts: -> processScripts = ->
for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript' for tag in document.getElementsByTagName('script') when tag.type is 'text/coffeescript'
eval exports.compile tag.innerHTML eval exports.compile tag.innerHTML
if window.addEventListener if window.addEventListener

View File

@@ -5,22 +5,22 @@
# interactive REPL. # interactive REPL.
# External dependencies. # External dependencies.
fs: require 'fs' fs = require 'fs'
path: require 'path' path = require 'path'
optparse: require './optparse' optparse = require './optparse'
CoffeeScript: require './coffee-script' CoffeeScript = require './coffee-script'
{spawn, exec}: require('child_process') {spawn, exec} = require 'child_process'
# The help banner that is printed when `coffee` is called without arguments. # The help banner that is printed when `coffee` is called without arguments.
BANNER: ''' BANNER = '''
coffee compiles CoffeeScript source files into JavaScript. coffee compiles CoffeeScript source files into JavaScript.
Usage: Usage:
coffee path/to/script.coffee coffee path/to/script.coffee
''' '''
# The list of all the valid option flags that `coffee` knows how to handle. # The list of all the valid option flags that `coffee` knows how to handle.
SWITCHES: [ SWITCHES = [
['-c', '--compile', 'compile to JavaScript and save as .js files'] ['-c', '--compile', 'compile to JavaScript and save as .js files']
['-i', '--interactive', 'run an interactive CoffeeScript REPL'] ['-i', '--interactive', 'run an interactive CoffeeScript REPL']
['-o', '--output [DIR]', 'set the directory for compiled JavaScript'] ['-o', '--output [DIR]', 'set the directory for compiled JavaScript']
@@ -37,14 +37,14 @@ SWITCHES: [
] ]
# Top-level objects shared by all the functions. # Top-level objects shared by all the functions.
options: {} options = {}
sources: [] sources = []
optionParser: null optionParser = null
# Run `coffee` by parsing passed options and determining what action to take. # Run `coffee` by parsing passed options and determining what action to take.
# Many flags cause us to divert before compiling anything. Flags passed after # Many flags cause us to divert before compiling anything. Flags passed after
# `--` will be passed verbatim to your script as arguments in `process.argv` # `--` will be passed verbatim to your script as arguments in `process.argv`
exports.run: -> exports.run = ->
parseOptions() parseOptions()
return usage() if options.help return usage() if options.help
return version() if options.version return version() if options.version
@@ -52,24 +52,24 @@ exports.run: ->
return compileStdio() if options.stdio return compileStdio() if options.stdio
return compileScript 'console', sources[0] if options.eval return compileScript 'console', sources[0] if options.eval
return require './repl' unless sources.length return require './repl' unless sources.length
separator: sources.indexOf '--' separator = sources.indexOf '--'
flags: [] flags = []
if separator >= 0 if separator >= 0
flags: sources[(separator + 1)...sources.length] flags = sources[(separator + 1)...sources.length]
sources: sources[0...separator] sources = sources[0...separator]
if options.run if options.run
flags: sources[1..sources.length].concat flags flags = sources[1..sources.length].concat flags
sources: [sources[0]] sources = [sources[0]]
process.ARGV: process.argv: flags process.ARGV = process.argv = flags
compileScripts() compileScripts()
# Asynchronously read in each CoffeeScript in a list of source files and # Asynchronously read in each CoffeeScript in a list of source files and
# compile them. If a directory is passed, recursively compile all # compile them. If a directory is passed, recursively compile all
# '.coffee' extension source files in it and all subdirectories. # '.coffee' extension source files in it and all subdirectories.
compileScripts: -> compileScripts = ->
for source in sources for source in sources
base: source base = source
compile: (source, topLevel) -> compile = (source, topLevel) ->
path.exists source, (exists) -> path.exists source, (exists) ->
throw new Error "File not found: $source" unless exists throw new Error "File not found: $source" unless exists
fs.stat source, (err, stats) -> fs.stat source, (err, stats) ->
@@ -85,15 +85,15 @@ compileScripts: ->
# Compile a single source script, containing the given code, according to the # Compile a single source script, containing the given code, according to the
# requested options. If evaluating the script directly sets `__filename`, # requested options. If evaluating the script directly sets `__filename`,
# `__dirname` and `module.filename` to be correct relative to the script's path. # `__dirname` and `module.filename` to be correct relative to the script's path.
compileScript: (source, code, base) -> compileScript = (source, code, base) ->
o: options o = options
codeOpts: compileOptions source codeOpts = compileOptions source
try try
if o.tokens then printTokens CoffeeScript.tokens code if o.tokens then printTokens CoffeeScript.tokens code
else if o.nodes then puts CoffeeScript.nodes(code).toString() else if o.nodes then puts CoffeeScript.nodes(code).toString()
else if o.run then CoffeeScript.run code, codeOpts else if o.run then CoffeeScript.run code, codeOpts
else else
js: CoffeeScript.compile code, codeOpts js = CoffeeScript.compile code, codeOpts
if o.print then print js if o.print then print js
else if o.compile then writeJs source, js, base else if o.compile then writeJs source, js, base
else if o.lint then lint js else if o.lint then lint js
@@ -103,18 +103,18 @@ compileScript: (source, code, base) ->
# Attach the appropriate listeners to compile scripts incoming over **stdin**, # Attach the appropriate listeners to compile scripts incoming over **stdin**,
# and write them back to **stdout**. # and write them back to **stdout**.
compileStdio: -> compileStdio = ->
code: '' code = ''
stdin: process.openStdin() stdin = process.openStdin()
stdin.on 'data', (buffer) -> stdin.on 'data', (buffer) ->
code: + buffer.toString() if buffer code = + buffer.toString() if buffer
stdin.on 'end', -> stdin.on 'end', ->
compileScript 'stdio', code compileScript 'stdio', code
# Watch a source CoffeeScript file using `fs.watchFile`, recompiling it every # Watch a source CoffeeScript file using `fs.watchFile`, recompiling it every
# time the file is updated. May be used in combination with other options, # time the file is updated. May be used in combination with other options,
# such as `--lint` or `--print`. # such as `--lint` or `--print`.
watch: (source, base) -> watch = (source, base) ->
fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) -> fs.watchFile source, {persistent: true, interval: 500}, (curr, prev) ->
return if curr.mtime.getTime() is prev.mtime.getTime() return if curr.mtime.getTime() is prev.mtime.getTime()
fs.readFile source, (err, code) -> compileScript(source, code.toString(), base) fs.readFile source, (err, code) -> compileScript(source, code.toString(), base)
@@ -122,13 +122,13 @@ watch: (source, base) ->
# Write out a JavaScript source file with the compiled code. By default, files # Write out a JavaScript source file with the compiled code. By default, files
# are written out in `cwd` as `.js` files with the same name, but the output # are written out in `cwd` as `.js` files with the same name, but the output
# directory can be customized with `--output`. # directory can be customized with `--output`.
writeJs: (source, js, base) -> writeJs = (source, js, base) ->
filename: path.basename(source, path.extname(source)) + '.js' filename = path.basename(source, path.extname(source)) + '.js'
srcDir: path.dirname source srcDir = path.dirname source
baseDir: srcDir.substring base.length baseDir = srcDir.substring base.length
dir: if options.output then path.join options.output, baseDir else srcDir dir = if options.output then path.join options.output, baseDir else srcDir
jsPath: path.join dir, filename jsPath = path.join dir, filename
compile: -> compile = ->
fs.writeFile jsPath, js, (err) -> fs.writeFile jsPath, js, (err) ->
puts "Compiled $source" if options.compile and options.watch puts "Compiled $source" if options.compile and options.watch
path.exists dir, (exists) -> path.exists dir, (exists) ->
@@ -136,43 +136,43 @@ writeJs: (source, js, base) ->
# Pipe compiled JS through JSLint (requires a working `jsl` command), printing # Pipe compiled JS through JSLint (requires a working `jsl` command), printing
# any errors or warnings that arise. # any errors or warnings that arise.
lint: (js) -> lint = (js) ->
printIt: (buffer) -> print buffer.toString() printIt = (buffer) -> print buffer.toString()
jsl: spawn 'jsl', ['-nologo', '-stdin'] jsl = spawn 'jsl', ['-nologo', '-stdin']
jsl.stdout.on 'data', printIt jsl.stdout.on 'data', printIt
jsl.stderr.on 'data', printIt jsl.stderr.on 'data', printIt
jsl.stdin.write js jsl.stdin.write js
jsl.stdin.end() jsl.stdin.end()
# Pretty-print a stream of tokens. # Pretty-print a stream of tokens.
printTokens: (tokens) -> printTokens = (tokens) ->
strings: for token in tokens strings = for token in tokens
[tag, value]: [token[0], token[1].toString().replace(/\n/, '\\n')] [tag, value] = [token[0], token[1].toString().replace(/\n/, '\\n')]
"[$tag $value]" "[$tag $value]"
puts strings.join(' ') puts strings.join(' ')
# Use the [OptionParser module](optparse.html) to extract all options from # Use the [OptionParser module](optparse.html) to extract all options from
# `process.argv` that are specified in `SWITCHES`. # `process.argv` that are specified in `SWITCHES`.
parseOptions: -> parseOptions = ->
optionParser: new optparse.OptionParser SWITCHES, BANNER optionParser = new optparse.OptionParser SWITCHES, BANNER
o: options: optionParser.parse(process.argv[2...process.argv.length]) o = options = optionParser.parse(process.argv[2...process.argv.length])
options.compile: or !!o.output options.compile = or !!o.output
options.run: not (o.compile or o.print or o.lint) options.run = not (o.compile or o.print or o.lint)
options.print: !! (o.print or (o.eval or o.stdio and o.compile)) options.print = !! (o.print or (o.eval or o.stdio and o.compile))
sources: options.arguments sources = options.arguments
# The compile-time options to pass to the CoffeeScript compiler. # The compile-time options to pass to the CoffeeScript compiler.
compileOptions: (source) -> compileOptions = (source) ->
o: {source} o = {source}
o.noWrap: options['no-wrap'] o.noWrap = options['no-wrap']
o o
# Print the `--help` usage message and exit. # Print the `--help` usage message and exit.
usage: -> usage = ->
puts optionParser.help() puts optionParser.help()
process.exit 0 process.exit 0
# Print the `--version` message and exit. # Print the `--version` message and exit.
version: -> version = ->
puts "CoffeeScript version $CoffeeScript.VERSION" puts "CoffeeScript version $CoffeeScript.VERSION"
process.exit 0 process.exit 0

View File

@@ -15,7 +15,7 @@
# from our rules and saves it into `lib/parser.js`. # from our rules and saves it into `lib/parser.js`.
# The only dependency is on the **Jison.Parser**. # The only dependency is on the **Jison.Parser**.
Parser: require('jison').Parser Parser = require('jison').Parser
# Jison DSL # Jison DSL
# --------- # ---------
@@ -23,16 +23,16 @@ Parser: require('jison').Parser
# Since we're going to be wrapped in a function by Jison in any case, if our # Since we're going to be wrapped in a function by Jison in any case, if our
# action immediately returns a value, we can optimize by removing the function # action immediately returns a value, we can optimize by removing the function
# wrapper and just returning the value directly. # wrapper and just returning the value directly.
unwrap: /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/ unwrap = /function\s*\(\)\s*\{\s*return\s*([\s\S]*);\s*\}/
# Our handy DSL for Jison grammar generation, thanks to # Our handy DSL for Jison grammar generation, thanks to
# [Tim Caswell](http://github.com/creationix). For every rule in the grammar, # [Tim Caswell](http://github.com/creationix). For every rule in the grammar,
# we pass the pattern-defining string, the action to run, and extra options, # we pass the pattern-defining string, the action to run, and extra options,
# optionally. If no action is specified, we simply pass the value of the # optionally. If no action is specified, we simply pass the value of the
# previous nonterminal. # previous nonterminal.
o: (patternString, action, options) -> o = (patternString, action, options) ->
return [patternString, '$$ = $1;', options] unless action return [patternString, '$$ = $1;', options] unless action
action: if match: (action + '').match(unwrap) then match[1] else "($action())" action = if match = (action + '').match(unwrap) then match[1] else "($action())"
[patternString, "$$ = $action;", options] [patternString, "$$ = $action;", options]
# Grammatical Rules # Grammatical Rules
@@ -48,7 +48,7 @@ o: (patternString, action, options) ->
# `$1` would be the value of the first `Expression`, `$2` would be the token # `$1` would be the value of the first `Expression`, `$2` would be the token
# for the `UNLESS` terminal, and `$3` would be the value of the second # for the `UNLESS` terminal, and `$3` would be the value of the second
# `Expression`. # `Expression`.
grammar: { grammar = {
# The **Root** is the top-level node in the syntax tree. Since we parse bottom-up, # The **Root** is the top-level node in the syntax tree. Since we parse bottom-up,
# all parsing must end here. # all parsing must end here.
@@ -248,8 +248,8 @@ grammar: {
# Indexing into an object or array using bracket notation. # Indexing into an object or array using bracket notation.
Index: [ Index: [
o "INDEX_START Expression INDEX_END", -> new IndexNode $2 o "INDEX_START Expression INDEX_END", -> new IndexNode $2
o "INDEX_SOAK Index", -> $2.soakNode: yes; $2 o "INDEX_SOAK Index", -> $2.soakNode = yes; $2
o "INDEX_PROTO Index", -> $2.proto: yes; $2 o "INDEX_PROTO Index", -> $2.proto = yes; $2
] ]
# In CoffeeScript, an object literal is simply a list of assignments. # In CoffeeScript, an object literal is simply a list of assignments.
@@ -420,14 +420,14 @@ grammar: {
# Comprehensions can either be normal, with a block of expressions to execute, # Comprehensions can either be normal, with a block of expressions to execute,
# or postfix, with a single expression. # or postfix, with a single expression.
For: [ For: [
o "Statement ForStart ForSource", -> $3.raw: $2.raw; new ForNode $1, $3, $2[0], $2[1] o "Statement ForStart ForSource", -> $3.raw = $2.raw; new ForNode $1, $3, $2[0], $2[1]
o "Expression ForStart ForSource", -> $3.raw: $2.raw; new ForNode $1, $3, $2[0], $2[1] o "Expression ForStart ForSource", -> $3.raw = $2.raw; new ForNode $1, $3, $2[0], $2[1]
o "ForStart ForSource Block", -> $2.raw: $1.raw; new ForNode $3, $2, $1[0], $1[1] o "ForStart ForSource Block", -> $2.raw = $1.raw; new ForNode $3, $2, $1[0], $1[1]
] ]
ForStart: [ ForStart: [
o "FOR ForVariables", -> $2 o "FOR ForVariables", -> $2
o "FOR ALL ForVariables", -> $3.raw: true; $3 o "FOR ALL ForVariables", -> $3.raw = true; $3
] ]
# An array of all accepted values for a variable inside the loop. This # An array of all accepted values for a variable inside the loop. This
@@ -604,9 +604,9 @@ operators: [
# our **Jison.Parser**. We do this by processing all of our rules, recording all # our **Jison.Parser**. We do this by processing all of our rules, recording all
# terminals (every symbol which does not appear as the name of a rule above) # terminals (every symbol which does not appear as the name of a rule above)
# as "tokens". # as "tokens".
tokens: [] tokens = []
for name, alternatives of grammar for name, alternatives of grammar
grammar[name]: for alt in alternatives grammar[name] = for alt in alternatives
for token in alt[0].split ' ' for token in alt[0].split ' '
tokens.push token unless grammar[token] tokens.push token unless grammar[token]
alt[1] = "return ${alt[1]}" if name is 'Root' alt[1] = "return ${alt[1]}" if name is 'Root'
@@ -616,7 +616,7 @@ for name, alternatives of grammar
# rules, and the name of the root. Reverse the operators because Jison orders # rules, and the name of the root. Reverse the operators because Jison orders
# precedence from low to high, and we have it high to low # precedence from low to high, and we have it high to low
# (as in [Yacc](http://dinosaur.compilertools.net/yacc/index.html)). # (as in [Yacc](http://dinosaur.compilertools.net/yacc/index.html)).
exports.parser: new Parser { exports.parser = new Parser {
tokens: tokens.join ' ' tokens: tokens.join ' '
bnf: grammar bnf: grammar
operators: operators.reverse() operators: operators.reverse()

View File

@@ -3,11 +3,11 @@
# arrays, count characters, that sort of thing. # arrays, count characters, that sort of thing.
# Set up exported variables for both **Node.js** and the browser. # Set up exported variables for both **Node.js** and the browser.
this.exports: this unless process? this.exports = this unless process?
helpers: exports.helpers: {} helpers = exports.helpers = {}
# Cross-browser indexOf, so that IE can join the party. # Cross-browser indexOf, so that IE can join the party.
helpers.indexOf: indexOf: (array, item, from) -> helpers.indexOf = indexOf = (array, item, from) ->
return array.indexOf item, from if array.indexOf return array.indexOf item, from if array.indexOf
for other, index in array for other, index in array
if other is item and (not from or (from <= index)) if other is item and (not from or (from <= index))
@@ -15,55 +15,55 @@ helpers.indexOf: indexOf: (array, item, from) ->
-1 -1
# Does a list include a value? # Does a list include a value?
helpers.include: include: (list, value) -> helpers.include = include = (list, value) ->
indexOf(list, value) >= 0 indexOf(list, value) >= 0
# Peek at the beginning of a given string to see if it matches a sequence. # Peek at the beginning of a given string to see if it matches a sequence.
helpers.starts: starts: (string, literal, start) -> helpers.starts = starts = (string, literal, start) ->
string.substring(start, (start or 0) + literal.length) is literal string.substring(start, (start or 0) + literal.length) is literal
# Peek at the end of a given string to see if it matches a sequence. # Peek at the end of a given string to see if it matches a sequence.
helpers.ends: ends: (string, literal, back) -> helpers.ends = ends = (string, literal, back) ->
start: string.length - literal.length - (back ? 0) start = string.length - literal.length - (back ? 0)
string.substring(start, start + literal.length) is literal string.substring(start, start + literal.length) is literal
# Trim out all falsy values from an array. # Trim out all falsy values from an array.
helpers.compact: compact: (array) -> item for item in array when item helpers.compact = compact = (array) -> item for item in array when item
# Count the number of occurences of a character in a string. # Count the number of occurences of a character in a string.
helpers.count: count: (string, letter) -> helpers.count = count = (string, letter) ->
num: 0 num = 0
pos: indexOf string, letter pos = indexOf string, letter
while pos isnt -1 while pos isnt -1
num: + 1 num = + 1
pos: indexOf string, letter, pos + 1 pos = indexOf string, letter, pos + 1
num num
# Merge objects, returning a fresh copy with attributes from both sides. # Merge objects, returning a fresh copy with attributes from both sides.
# Used every time `BaseNode#compile` is called, to allow properties in the # Used every time `BaseNode#compile` is called, to allow properties in the
# options hash to propagate down the tree without polluting other branches. # options hash to propagate down the tree without polluting other branches.
helpers.merge: merge: (options, overrides) -> helpers.merge = merge = (options, overrides) ->
fresh: {} fresh = {}
(fresh[key]: val) for all key, val of options (fresh[key] = val) for all key, val of options
(fresh[key]: val) for all key, val of overrides if overrides (fresh[key] = val) for all key, val of overrides if overrides
fresh fresh
# Extend a source object with the properties of another object (shallow copy). # Extend a source object with the properties of another object (shallow copy).
# We use this to simulate Node's deprecated `process.mixin` # We use this to simulate Node's deprecated `process.mixin`
helpers.extend: extend: (object, properties) -> helpers.extend = extend = (object, properties) ->
(object[key]: val) for all key, val of properties (object[key] = val) for all key, val of properties
# Return a completely flattened version of an array. Handy for getting a # Return a completely flattened version of an array. Handy for getting a
# list of `children` from the nodes. # list of `children` from the nodes.
helpers.flatten: flatten: (array) -> helpers.flatten = flatten = (array) ->
memo: [] memo = []
for item in array for item in array
if item instanceof Array then memo: memo.concat(item) else memo.push(item) if item instanceof Array then memo = memo.concat(item) else memo.push(item)
memo memo
# Delete a key from an object, returning the value. Useful when a node is # Delete a key from an object, returning the value. Useful when a node is
# looking for a particular method in an options hash. # looking for a particular method in an options hash.
helpers.del: del: (obj, key) -> helpers.del = del = (obj, key) ->
val: obj[key] val = obj[key]
delete obj[key] delete obj[key]
val val

View File

@@ -1,2 +1,2 @@
# Loader for CoffeeScript as a Node.js library. # Loader for CoffeeScript as a Node.js library.
(exports[key]: val) for key, val of require './coffee-script' (exports[key] = val) for key, val of require './coffee-script'

View File

@@ -9,15 +9,15 @@
# Set up the Lexer for both Node.js and the browser, depending on where we are. # Set up the Lexer for both Node.js and the browser, depending on where we are.
if process? if process?
{Rewriter}: require('./rewriter') {Rewriter} = require('./rewriter')
{helpers}: require('./helpers') {helpers} = require('./helpers')
else else
this.exports: this this.exports = this
Rewriter: this.Rewriter Rewriter = this.Rewriter
helpers: this.helpers helpers = this.helpers
# Import the helpers we need. # Import the helpers we need.
{include, count, starts, compact}: helpers {include, count, starts, compact} = helpers
# The Lexer Class # The Lexer Class
# --------------- # ---------------
@@ -25,7 +25,7 @@ else
# The Lexer class reads a stream of CoffeeScript and divvys it up into tagged # The Lexer class reads a stream of CoffeeScript and divvys it up into tagged
# tokens. Some potential ambiguity in the grammar has been avoided by # tokens. Some potential ambiguity in the grammar has been avoided by
# pushing some extra smarts into the Lexer. # pushing some extra smarts into the Lexer.
exports.Lexer: class Lexer exports.Lexer = class Lexer
# **tokenize** is the Lexer's main method. Scan by attempting to match tokens # **tokenize** is the Lexer's main method. Scan by attempting to match tokens
# one at a time, using a regular expression anchored at the start of the # one at a time, using a regular expression anchored at the start of the
@@ -40,17 +40,17 @@ exports.Lexer: class Lexer
# Before returning the token stream, run it through the [Rewriter](rewriter.html) # Before returning the token stream, run it through the [Rewriter](rewriter.html)
# unless explicitly asked not to. # unless explicitly asked not to.
tokenize: (code, options) -> tokenize: (code, options) ->
code : code.replace /(\r|\s+$)/g, '' code = code.replace /(\r|\s+$)/g, ''
o : options or {} o = options or {}
@code : code # The remainder of the source code. @code = code # The remainder of the source code.
@i : 0 # Current character position we're parsing. @i = 0 # Current character position we're parsing.
@line : o.line or 0 # The current line. @line = o.line or 0 # The current line.
@indent : 0 # The current indentation level. @indent = 0 # The current indentation level.
@outdebt : 0 # The under-outdentation of the last outdent. @outdebt = 0 # The under-outdentation of the last outdent.
@indents : [] # The stack of all current indentation levels. @indents = [] # The stack of all current indentation levels.
@tokens : [] # Stream of parsed tokens in the form ['TYPE', value, line] @tokens = [] # Stream of parsed tokens in the form ['TYPE', value, line]
while @i < @code.length while @i < @code.length
@chunk: @code.slice @i @chunk = @code.slice @i
@extractNextToken() @extractNextToken()
@closeIndentation() @closeIndentation()
return @tokens if o.rewrite is off return @tokens if o.rewrite is off
@@ -81,17 +81,17 @@ exports.Lexer: class Lexer
# referenced as property names here, so you can still do `jQuery.is()` even # referenced as property names here, so you can still do `jQuery.is()` even
# though `is` means `===` otherwise. # though `is` means `===` otherwise.
identifierToken: -> identifierToken: ->
return false unless id: @match IDENTIFIER, 1 return false unless id = @match IDENTIFIER, 1
@i: + id.length @i = + id.length
forcedIdentifier: @tagAccessor() or @match ASSIGNED, 1 forcedIdentifier = @tagAccessor() or @match ASSIGNED, 1
tag: 'IDENTIFIER' tag = 'IDENTIFIER'
tag: id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id)) tag = id.toUpperCase() if include(JS_KEYWORDS, id) or (not forcedIdentifier and include(COFFEE_KEYWORDS, id))
tag: 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag() tag = 'LEADING_WHEN' if tag is 'WHEN' and include LINE_BREAK, @tag()
tag: 'ALL' if id is 'all' and @tag() is 'FOR' tag = 'ALL' if id is 'all' and @tag() is 'FOR'
if include(JS_FORBIDDEN, id) if include(JS_FORBIDDEN, id)
if forcedIdentifier if forcedIdentifier
tag: 'STRING' tag = 'STRING'
id: "'$id'" id = "'$id'"
if forcedIdentifier is 'accessor' if forcedIdentifier is 'accessor'
close_index: true close_index: true
@tokens.pop() if @tag() isnt '@' @tokens.pop() if @tag() isnt '@'