mirror of
https://github.com/jashkenas/coffeescript.git
synced 2026-04-11 03:00:13 -04:00
first bit of equals for symbology ... barely started on lexer.coffee
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -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 '@'
|
||||||
|
|||||||
Reference in New Issue
Block a user