aboutsummaryrefslogtreecommitdiff
path: root/samples/CoffeeScript
diff options
context:
space:
mode:
Diffstat (limited to 'samples/CoffeeScript')
-rw-r--r--samples/CoffeeScript/browser.coffee55
-rw-r--r--samples/CoffeeScript/classes.coffee21
-rw-r--r--samples/CoffeeScript/coffee-script.coffee130
-rw-r--r--samples/CoffeeScript/empty.coffee0
-rw-r--r--samples/CoffeeScript/hello.coffee1
-rw-r--r--samples/CoffeeScript/intro.coffee28
-rw-r--r--samples/CoffeeScript/lexer.coffee709
-rw-r--r--samples/CoffeeScript/rack_application.coffee256
-rw-r--r--samples/CoffeeScript/xipd.coffee110
9 files changed, 1310 insertions, 0 deletions
diff --git a/samples/CoffeeScript/browser.coffee b/samples/CoffeeScript/browser.coffee
new file mode 100644
index 0000000..5ed0bea
--- /dev/null
+++ b/samples/CoffeeScript/browser.coffee
@@ -0,0 +1,55 @@
+# Override exported methods for non-Node.js engines.
+
+CoffeeScript = require './coffee-script'
+CoffeeScript.require = require
+
+# Use standard JavaScript `eval` to eval code.
+CoffeeScript.eval = (code, options = {}) ->
+ options.bare ?= on
+ eval CoffeeScript.compile code, options
+
+# Running code does not provide access to this scope.
+CoffeeScript.run = (code, options = {}) ->
+ options.bare = on
+ Function(CoffeeScript.compile code, options)()
+
+# If we're not in a browser environment, we're finished with the public API.
+return unless window?
+
+# Load a remote script from the current domain via XHR.
+CoffeeScript.load = (url, callback) ->
+ xhr = new (window.ActiveXObject or XMLHttpRequest)('Microsoft.XMLHTTP')
+ xhr.open 'GET', url, true
+ xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr
+ xhr.onreadystatechange = ->
+ if xhr.readyState is 4
+ if xhr.status in [0, 200]
+ CoffeeScript.run xhr.responseText
+ else
+ throw new Error "Could not load #{url}"
+ callback() if callback
+ xhr.send null
+
+# Activate CoffeeScript in the browser by having it compile and evaluate
+# all script tags with a content-type of `text/coffeescript`.
+# This happens on page load.
+runScripts = ->
+ scripts = document.getElementsByTagName 'script'
+ coffees = (s for s in scripts when s.type is 'text/coffeescript')
+ index = 0
+ length = coffees.length
+ do execute = ->
+ script = coffees[index++]
+ if script?.type is 'text/coffeescript'
+ if script.src
+ CoffeeScript.load script.src, execute
+ else
+ CoffeeScript.run script.innerHTML
+ execute()
+ null
+
+# Listen for window load, both in browsers and in IE.
+if window.addEventListener
+ addEventListener 'DOMContentLoaded', runScripts, no
+else
+ attachEvent 'onload', runScripts
diff --git a/samples/CoffeeScript/classes.coffee b/samples/CoffeeScript/classes.coffee
new file mode 100644
index 0000000..2b2a718
--- /dev/null
+++ b/samples/CoffeeScript/classes.coffee
@@ -0,0 +1,21 @@
+class Animal
+ constructor: (@name) ->
+
+ move: (meters) ->
+ alert @name + " moved " + meters + "m."
+
+class Snake extends Animal
+ move: ->
+ alert "Slithering..."
+ super 5
+
+class Horse extends Animal
+ move: ->
+ alert "Galloping..."
+ super 45
+
+sam = new Snake "Sammy the Python"
+tom = new Horse "Tommy the Palomino"
+
+sam.move()
+tom.move()
diff --git a/samples/CoffeeScript/coffee-script.coffee b/samples/CoffeeScript/coffee-script.coffee
new file mode 100644
index 0000000..2933aeb
--- /dev/null
+++ b/samples/CoffeeScript/coffee-script.coffee
@@ -0,0 +1,130 @@
+# CoffeeScript can be used both on the server, as a command-line compiler based
+# on Node.js/V8, or to run CoffeeScripts directly in the browser. This module
+# contains the main entry functions for tokenizing, parsing, and compiling
+# source CoffeeScript into JavaScript.
+#
+# If included on a webpage, it will automatically sniff out, compile, and
+# execute all scripts present in `text/coffeescript` tags.
+
+fs = require 'fs'
+path = require 'path'
+{Lexer,RESERVED} = require './lexer'
+{parser} = require './parser'
+vm = require 'vm'
+
+# TODO: Remove registerExtension when fully deprecated.
+if require.extensions
+ require.extensions['.coffee'] = (module, filename) ->
+ content = compile fs.readFileSync(filename, 'utf8'), {filename}
+ module._compile content, filename
+else if require.registerExtension
+ require.registerExtension '.coffee', (content) -> compile content
+
+# The current CoffeeScript version number.
+exports.VERSION = '1.3.3'
+
+# Words that cannot be used as identifiers in CoffeeScript code
+exports.RESERVED = RESERVED
+
+# Expose helpers for testing.
+exports.helpers = require './helpers'
+
+# Compile a string of CoffeeScript code to JavaScript, using the Coffee/Jison
+# compiler.
+exports.compile = compile = (code, options = {}) ->
+ {merge} = exports.helpers
+ try
+ js = (parser.parse lexer.tokenize code).compile options
+ return js unless options.header
+ catch err
+ err.message = "In #{options.filename}, #{err.message}" if options.filename
+ throw err
+ header = "Generated by CoffeeScript #{@VERSION}"
+ "// #{header}\n#{js}"
+
+# Tokenize a string of CoffeeScript code, and return the array of tokens.
+exports.tokens = (code, options) ->
+ lexer.tokenize code, options
+
+# Parse a string of CoffeeScript code or an array of lexed tokens, and
+# return the AST. You can then compile it by calling `.compile()` on the root,
+# or traverse it by using `.traverseChildren()` with a callback.
+exports.nodes = (source, options) ->
+ if typeof source is 'string'
+ parser.parse lexer.tokenize source, options
+ else
+ parser.parse source
+
+# Compile and execute a string of CoffeeScript (on the server), correctly
+# setting `__filename`, `__dirname`, and relative `require()`.
+exports.run = (code, options = {}) ->
+ mainModule = require.main
+
+ # Set the filename.
+ mainModule.filename = process.argv[1] =
+ if options.filename then fs.realpathSync(options.filename) else '.'
+
+ # Clear the module cache.
+ mainModule.moduleCache and= {}
+
+ # Assign paths for node_modules loading
+ mainModule.paths = require('module')._nodeModulePaths path.dirname fs.realpathSync options.filename
+
+ # Compile.
+ if path.extname(mainModule.filename) isnt '.coffee' or require.extensions
+ mainModule._compile compile(code, options), mainModule.filename
+ else
+ mainModule._compile code, mainModule.filename
+
+# Compile and evaluate a string of CoffeeScript (in a Node.js-like environment).
+# The CoffeeScript REPL uses this to run the input.
+exports.eval = (code, options = {}) ->
+ return unless code = code.trim()
+ Script = vm.Script
+ if Script
+ if options.sandbox?
+ if options.sandbox instanceof Script.createContext().constructor
+ sandbox = options.sandbox
+ else
+ sandbox = Script.createContext()
+ sandbox[k] = v for own k, v of options.sandbox
+ sandbox.global = sandbox.root = sandbox.GLOBAL = sandbox
+ else
+ sandbox = global
+ sandbox.__filename = options.filename || 'eval'
+ sandbox.__dirname = path.dirname sandbox.__filename
+ # define module/require only if they chose not to specify their own
+ unless sandbox isnt global or sandbox.module or sandbox.require
+ Module = require 'module'
+ sandbox.module = _module = new Module(options.modulename || 'eval')
+ sandbox.require = _require = (path) -> Module._load path, _module, true
+ _module.filename = sandbox.__filename
+ _require[r] = require[r] for r in Object.getOwnPropertyNames require when r isnt 'paths'
+ # use the same hack node currently uses for their own REPL
+ _require.paths = _module.paths = Module._nodeModulePaths process.cwd()
+ _require.resolve = (request) -> Module._resolveFilename request, _module
+ o = {}
+ o[k] = v for own k, v of options
+ o.bare = on # ensure return value
+ js = compile code, o
+ if sandbox is global
+ vm.runInThisContext js
+ else
+ vm.runInContext js, sandbox
+
+# Instantiate a Lexer for our use here.
+lexer = new Lexer
+
+# 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
+# directly as a "Jison lexer".
+parser.lexer =
+ lex: ->
+ [tag, @yytext, @yylineno] = @tokens[@pos++] or ['']
+ tag
+ setInput: (@tokens) ->
+ @pos = 0
+ upcomingInput: ->
+ ""
+
+parser.yy = require './nodes'
diff --git a/samples/CoffeeScript/empty.coffee b/samples/CoffeeScript/empty.coffee
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/samples/CoffeeScript/empty.coffee
diff --git a/samples/CoffeeScript/hello.coffee b/samples/CoffeeScript/hello.coffee
new file mode 100644
index 0000000..4b65a57
--- /dev/null
+++ b/samples/CoffeeScript/hello.coffee
@@ -0,0 +1 @@
+console.log "Hello, World!"
diff --git a/samples/CoffeeScript/intro.coffee b/samples/CoffeeScript/intro.coffee
new file mode 100644
index 0000000..0db65dd
--- /dev/null
+++ b/samples/CoffeeScript/intro.coffee
@@ -0,0 +1,28 @@
+# Assignment:
+number = 42
+opposite = true
+
+# Conditions:
+number = -42 if opposite
+
+# Functions:
+square = (x) -> x * x
+
+# Arrays:
+list = [1, 2, 3, 4, 5]
+
+# Objects:
+math =
+ root: Math.sqrt
+ square: square
+ cube: (x) -> x * square x
+
+# Splats:
+race = (winner, runners...) ->
+ print winner, runners
+
+# Existence:
+alert "I knew it!" if elvis?
+
+# Array comprehensions:
+cubes = (math.cube num for num in list)
diff --git a/samples/CoffeeScript/lexer.coffee b/samples/CoffeeScript/lexer.coffee
new file mode 100644
index 0000000..56e8e32
--- /dev/null
+++ b/samples/CoffeeScript/lexer.coffee
@@ -0,0 +1,709 @@
+# The CoffeeScript Lexer. Uses a series of token-matching regexes to attempt
+# matches against the beginning of the source code. When a match is found,
+# a token is produced, we consume the match, and start again. Tokens are in the
+# form:
+#
+# [tag, value, lineNumber]
+#
+# Which is a format that can be fed directly into [Jison](http://github.com/zaach/jison).
+
+{Rewriter, INVERSES} = require './rewriter'
+
+# Import the helpers we need.
+{count, starts, compact, last} = require './helpers'
+
+# The Lexer Class
+# ---------------
+
+# The Lexer class reads a stream of CoffeeScript and divvies it up into tagged
+# tokens. Some potential ambiguity in the grammar has been avoided by
+# pushing some extra smarts into the Lexer.
+exports.Lexer = class Lexer
+
+ # **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
+ # remaining code, or a custom recursive token-matching method
+ # (for interpolations). When the next token has been recorded, we move forward
+ # within the code past the token, and begin again.
+ #
+ # Each tokenizing method is responsible for returning the number of characters
+ # it has consumed.
+ #
+ # Before returning the token stream, run it through the [Rewriter](rewriter.html)
+ # unless explicitly asked not to.
+ tokenize: (code, opts = {}) ->
+ code = "\n#{code}" if WHITESPACE.test code
+ code = code.replace(/\r/g, '').replace TRAILING_SPACES, ''
+
+ @code = code # The remainder of the source code.
+ @line = opts.line or 0 # The current line.
+ @indent = 0 # The current indentation level.
+ @indebt = 0 # The over-indentation at the current level.
+ @outdebt = 0 # The under-outdentation at the current level.
+ @indents = [] # The stack of all current indentation levels.
+ @ends = [] # The stack for pairing up tokens.
+ @tokens = [] # Stream of parsed tokens in the form `['TYPE', value, line]`.
+
+ # At every position, run through this list of attempted matches,
+ # short-circuiting if any of them succeed. Their order determines precedence:
+ # `@literalToken` is the fallback catch-all.
+ i = 0
+ while @chunk = code[i..]
+ i += @identifierToken() or
+ @commentToken() or
+ @whitespaceToken() or
+ @lineToken() or
+ @heredocToken() or
+ @stringToken() or
+ @numberToken() or
+ @regexToken() or
+ @jsToken() or
+ @literalToken()
+
+ @closeIndentation()
+ @error "missing #{tag}" if tag = @ends.pop()
+ return @tokens if opts.rewrite is off
+ (new Rewriter).rewrite @tokens
+
+ # Tokenizers
+ # ----------
+
+ # Matches identifying literals: variables, keywords, method names, etc.
+ # Check to ensure that JavaScript reserved words aren't being used as
+ # identifiers. Because CoffeeScript reserves a handful of keywords that are
+ # allowed in JavaScript, we're careful not to tag them as keywords when
+ # referenced as property names here, so you can still do `jQuery.is()` even
+ # though `is` means `===` otherwise.
+ identifierToken: ->
+ return 0 unless match = IDENTIFIER.exec @chunk
+ [input, id, colon] = match
+
+ if id is 'own' and @tag() is 'FOR'
+ @token 'OWN', id
+ return id.length
+ forcedIdentifier = colon or
+ (prev = last @tokens) and (prev[0] in ['.', '?.', '::'] or
+ not prev.spaced and prev[0] is '@')
+ tag = 'IDENTIFIER'
+
+ if not forcedIdentifier and (id in JS_KEYWORDS or id in COFFEE_KEYWORDS)
+ tag = id.toUpperCase()
+ if tag is 'WHEN' and @tag() in LINE_BREAK
+ tag = 'LEADING_WHEN'
+ else if tag is 'FOR'
+ @seenFor = yes
+ else if tag is 'UNLESS'
+ tag = 'IF'
+ else if tag in UNARY
+ tag = 'UNARY'
+ else if tag in RELATION
+ if tag isnt 'INSTANCEOF' and @seenFor
+ tag = 'FOR' + tag
+ @seenFor = no
+ else
+ tag = 'RELATION'
+ if @value() is '!'
+ @tokens.pop()
+ id = '!' + id
+
+ if id in JS_FORBIDDEN
+ if forcedIdentifier
+ tag = 'IDENTIFIER'
+ id = new String id
+ id.reserved = yes
+ else if id in RESERVED
+ @error "reserved word \"#{id}\""
+
+ unless forcedIdentifier
+ id = COFFEE_ALIAS_MAP[id] if id in COFFEE_ALIASES
+ tag = switch id
+ when '!' then 'UNARY'
+ when '==', '!=' then 'COMPARE'
+ when '&&', '||' then 'LOGIC'
+ when 'true', 'false' then 'BOOL'
+ when 'break', 'continue' then 'STATEMENT'
+ else tag
+
+ @token tag, id
+ @token ':', ':' if colon
+ input.length
+
+ # Matches numbers, including decimals, hex, and exponential notation.
+ # Be careful not to interfere with ranges-in-progress.
+ numberToken: ->
+ return 0 unless match = NUMBER.exec @chunk
+ number = match[0]
+ if /^0[BOX]/.test number
+ @error "radix prefix '#{number}' must be lowercase"
+ else if /E/.test(number) and not /^0x/.test number
+ @error "exponential notation '#{number}' must be indicated with a lowercase 'e'"
+ else if /^0\d*[89]/.test number
+ @error "decimal literal '#{number}' must not be prefixed with '0'"
+ else if /^0\d+/.test number
+ @error "octal literal '#{number}' must be prefixed with '0o'"
+ lexedLength = number.length
+ if octalLiteral = /^0o([0-7]+)/.exec number
+ number = '0x' + (parseInt octalLiteral[1], 8).toString 16
+ if binaryLiteral = /^0b([01]+)/.exec number
+ number = '0x' + (parseInt binaryLiteral[1], 2).toString 16
+ @token 'NUMBER', number
+ lexedLength
+
+ # Matches strings, including multi-line strings. Ensures that quotation marks
+ # are balanced within the string's contents, and within nested interpolations.
+ stringToken: ->
+ switch @chunk.charAt 0
+ when "'"
+ return 0 unless match = SIMPLESTR.exec @chunk
+ @token 'STRING', (string = match[0]).replace MULTILINER, '\\\n'
+ when '"'
+ return 0 unless string = @balancedString @chunk, '"'
+ if 0 < string.indexOf '#{', 1
+ @interpolateString string[1...-1]
+ else
+ @token 'STRING', @escapeLines string
+ else
+ return 0
+ if octalEsc = /^(?:\\.|[^\\])*\\(?:0[0-7]|[1-7])/.test string
+ @error "octal escape sequences #{string} are not allowed"
+ @line += count string, '\n'
+ string.length
+
+ # Matches heredocs, adjusting indentation to the correct level, as heredocs
+ # preserve whitespace, but ignore indentation to the left.
+ heredocToken: ->
+ return 0 unless match = HEREDOC.exec @chunk
+ heredoc = match[0]
+ quote = heredoc.charAt 0
+ doc = @sanitizeHeredoc match[2], quote: quote, indent: null
+ if quote is '"' and 0 <= doc.indexOf '#{'
+ @interpolateString doc, heredoc: yes
+ else
+ @token 'STRING', @makeString doc, quote, yes
+ @line += count heredoc, '\n'
+ heredoc.length
+
+ # Matches and consumes comments.
+ commentToken: ->
+ return 0 unless match = @chunk.match COMMENT
+ [comment, here] = match
+ if here
+ @token 'HERECOMMENT', @sanitizeHeredoc here,
+ herecomment: true, indent: Array(@indent + 1).join(' ')
+ @line += count comment, '\n'
+ comment.length
+
+ # Matches JavaScript interpolated directly into the source via backticks.
+ jsToken: ->
+ return 0 unless @chunk.charAt(0) is '`' and match = JSTOKEN.exec @chunk
+ @token 'JS', (script = match[0])[1...-1]
+ script.length
+
+ # Matches regular expression literals. Lexing regular expressions is difficult
+ # to distinguish from division, so we borrow some basic heuristics from
+ # JavaScript and Ruby.
+ regexToken: ->
+ return 0 if @chunk.charAt(0) isnt '/'
+ if match = HEREGEX.exec @chunk
+ length = @heregexToken match
+ @line += count match[0], '\n'
+ return length
+
+ prev = last @tokens
+ return 0 if prev and (prev[0] in (if prev.spaced then NOT_REGEX else NOT_SPACED_REGEX))
+ return 0 unless match = REGEX.exec @chunk
+ [match, regex, flags] = match
+ if regex[..1] is '/*' then @error 'regular expressions cannot begin with `*`'
+ if regex is '//' then regex = '/(?:)/'
+ @token 'REGEX', "#{regex}#{flags}"
+ match.length
+
+ # Matches multiline extended regular expressions.
+ heregexToken: (match) ->
+ [heregex, body, flags] = match
+ if 0 > body.indexOf '#{'
+ re = body.replace(HEREGEX_OMIT, '').replace(/\//g, '\\/')
+ if re.match /^\*/ then @error 'regular expressions cannot begin with `*`'
+ @token 'REGEX', "/#{ re or '(?:)' }/#{flags}"
+ return heregex.length
+ @token 'IDENTIFIER', 'RegExp'
+ @tokens.push ['CALL_START', '(']
+ tokens = []
+ for [tag, value] in @interpolateString(body, regex: yes)
+ if tag is 'TOKENS'
+ tokens.push value...
+ else
+ continue unless value = value.replace HEREGEX_OMIT, ''
+ value = value.replace /\\/g, '\\\\'
+ tokens.push ['STRING', @makeString(value, '"', yes)]
+ tokens.push ['+', '+']
+ tokens.pop()
+ @tokens.push ['STRING', '""'], ['+', '+'] unless tokens[0]?[0] is 'STRING'
+ @tokens.push tokens...
+ @tokens.push [',', ','], ['STRING', '"' + flags + '"'] if flags
+ @token ')', ')'
+ heregex.length
+
+ # Matches newlines, indents, and outdents, and determines which is which.
+ # If we can detect that the current line is continued onto the the next line,
+ # then the newline is suppressed:
+ #
+ # elements
+ # .each( ... )
+ # .map( ... )
+ #
+ # Keeps track of the level of indentation, because a single outdent token
+ # can close multiple indents, so we need to know how far in we happen to be.
+ lineToken: ->
+ return 0 unless match = MULTI_DENT.exec @chunk
+ indent = match[0]
+ @line += count indent, '\n'
+ @seenFor = no
+ size = indent.length - 1 - indent.lastIndexOf '\n'
+ noNewlines = @unfinished()
+ if size - @indebt is @indent
+ if noNewlines then @suppressNewlines() else @newlineToken()
+ return indent.length
+ if size > @indent
+ if noNewlines
+ @indebt = size - @indent
+ @suppressNewlines()
+ return indent.length
+ diff = size - @indent + @outdebt
+ @token 'INDENT', diff
+ @indents.push diff
+ @ends.push 'OUTDENT'
+ @outdebt = @indebt = 0
+ else
+ @indebt = 0
+ @outdentToken @indent - size, noNewlines
+ @indent = size
+ indent.length
+
+ # Record an outdent token or multiple tokens, if we happen to be moving back
+ # inwards past several recorded indents.
+ outdentToken: (moveOut, noNewlines) ->
+ while moveOut > 0
+ len = @indents.length - 1
+ if @indents[len] is undefined
+ moveOut = 0
+ else if @indents[len] is @outdebt
+ moveOut -= @outdebt
+ @outdebt = 0
+ else if @indents[len] < @outdebt
+ @outdebt -= @indents[len]
+ moveOut -= @indents[len]
+ else
+ dent = @indents.pop() - @outdebt
+ moveOut -= dent
+ @outdebt = 0
+ @pair 'OUTDENT'
+ @token 'OUTDENT', dent
+ @outdebt -= moveOut if dent
+ @tokens.pop() while @value() is ';'
+ @token 'TERMINATOR', '\n' unless @tag() is 'TERMINATOR' or noNewlines
+ this
+
+ # Matches and consumes non-meaningful whitespace. Tag the previous token
+ # as being "spaced", because there are some cases where it makes a difference.
+ whitespaceToken: ->
+ return 0 unless (match = WHITESPACE.exec @chunk) or
+ (nline = @chunk.charAt(0) is '\n')
+ prev = last @tokens
+ prev[if match then 'spaced' else 'newLine'] = true if prev
+ if match then match[0].length else 0
+
+ # Generate a newline token. Consecutive newlines get merged together.
+ newlineToken: ->
+ @tokens.pop() while @value() is ';'
+ @token 'TERMINATOR', '\n' unless @tag() is 'TERMINATOR'
+ this
+
+ # Use a `\` at a line-ending to suppress the newline.
+ # The slash is removed here once its job is done.
+ suppressNewlines: ->
+ @tokens.pop() if @value() is '\\'
+ this
+
+ # We treat all other single characters as a token. E.g.: `( ) , . !`
+ # Multi-character operators are also literal tokens, so that Jison can assign
+ # the proper order of operations. There are some symbols that we tag specially
+ # here. `;` and newlines are both treated as a `TERMINATOR`, we distinguish
+ # parentheses that indicate a method call from regular parentheses, and so on.
+ literalToken: ->
+ if match = OPERATOR.exec @chunk
+ [value] = match
+ @tagParameters() if CODE.test value
+ else
+ value = @chunk.charAt 0
+ tag = value
+ prev = last @tokens
+ if value is '=' and prev
+ if not prev[1].reserved and prev[1] in JS_FORBIDDEN
+ @error "reserved word \"#{@value()}\" can't be assigned"
+ if prev[1] in ['||', '&&']
+ prev[0] = 'COMPOUND_ASSIGN'
+ prev[1] += '='
+ return value.length
+ if value is ';'
+ @seenFor = no
+ tag = 'TERMINATOR'
+ else if value in MATH then tag = 'MATH'
+ else if value in COMPARE then tag = 'COMPARE'
+ else if value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
+ else if value in UNARY then tag = 'UNARY'
+ else if value in SHIFT then tag = 'SHIFT'
+ else if value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
+ else if prev and not prev.spaced
+ if value is '(' and prev[0] in CALLABLE
+ prev[0] = 'FUNC_EXIST' if prev[0] is '?'
+ tag = 'CALL_START'
+ else if value is '[' and prev[0] in INDEXABLE
+ tag = 'INDEX_START'
+ switch prev[0]
+ when '?' then prev[0] = 'INDEX_SOAK'
+ switch value
+ when '(', '{', '[' then @ends.push INVERSES[value]
+ when ')', '}', ']' then @pair value
+ @token tag, value
+ value.length
+
+ # Token Manipulators
+ # ------------------
+
+ # Sanitize a heredoc or herecomment by
+ # erasing all external indentation on the left-hand side.
+ sanitizeHeredoc: (doc, options) ->
+ {indent, herecomment} = options
+ if herecomment
+ if HEREDOC_ILLEGAL.test doc
+ @error "block comment cannot contain \"*/\", starting"
+ return doc if doc.indexOf('\n') <= 0
+ else
+ while match = HEREDOC_INDENT.exec doc
+ attempt = match[1]
+ indent = attempt if indent is null or 0 < attempt.length < indent.length
+ doc = doc.replace /// \n #{indent} ///g, '\n' if indent
+ doc = doc.replace /^\n/, '' unless herecomment
+ doc
+
+ # A source of ambiguity in our grammar used to be parameter lists in function
+ # definitions versus argument lists in function calls. Walk backwards, tagging
+ # parameters specially in order to make things easier for the parser.
+ tagParameters: ->
+ return this if @tag() isnt ')'
+ stack = []
+ {tokens} = this
+ i = tokens.length
+ tokens[--i][0] = 'PARAM_END'
+ while tok = tokens[--i]
+ switch tok[0]
+ when ')'
+ stack.push tok
+ when '(', 'CALL_START'
+ if stack.length then stack.pop()
+ else if tok[0] is '('
+ tok[0] = 'PARAM_START'
+ return this
+ else return this
+ this
+
+ # Close up all remaining open blocks at the end of the file.
+ closeIndentation: ->
+ @outdentToken @indent
+
+ # Matches a balanced group such as a single or double-quoted string. Pass in
+ # a series of delimiters, all of which must be nested correctly within the
+ # contents of the string. This method allows us to have strings within
+ # interpolations within strings, ad infinitum.
+ balancedString: (str, end) ->
+ continueCount = 0
+ stack = [end]
+ for i in [1...str.length]
+ if continueCount
+ --continueCount
+ continue
+ switch letter = str.charAt i
+ when '\\'
+ ++continueCount
+ continue
+ when end
+ stack.pop()
+ unless stack.length
+ return str[0..i]
+ end = stack[stack.length - 1]
+ continue
+ if end is '}' and letter in ['"', "'"]
+ stack.push end = letter
+ else if end is '}' and letter is '/' and match = (HEREGEX.exec(str[i..]) or REGEX.exec(str[i..]))
+ continueCount += match[0].length - 1
+ else if end is '}' and letter is '{'
+ stack.push end = '}'
+ else if end is '"' and prev is '#' and letter is '{'
+ stack.push end = '}'
+ prev = letter
+ @error "missing #{ stack.pop() }, starting"
+
+ # Expand variables and expressions inside double-quoted strings using
+ # Ruby-like notation for substitution of arbitrary expressions.
+ #
+ # "Hello #{name.capitalize()}."
+ #
+ # If it encounters an interpolation, this method will recursively create a
+ # new Lexer, tokenize the interpolated contents, and merge them into the
+ # token stream.
+ interpolateString: (str, options = {}) ->
+ {heredoc, regex} = options
+ tokens = []
+ pi = 0
+ i = -1
+ while letter = str.charAt i += 1
+ if letter is '\\'
+ i += 1
+ continue
+ unless letter is '#' and str.charAt(i+1) is '{' and
+ (expr = @balancedString str[i + 1..], '}')
+ continue
+ tokens.push ['NEOSTRING', str[pi...i]] if pi < i
+ inner = expr[1...-1]
+ if inner.length
+ nested = new Lexer().tokenize inner, line: @line, rewrite: off
+ nested.pop()
+ nested.shift() if nested[0]?[0] is 'TERMINATOR'
+ if len = nested.length
+ if len > 1
+ nested.unshift ['(', '(', @line]
+ nested.push [')', ')', @line]
+ tokens.push ['TOKENS', nested]
+ i += expr.length
+ pi = i + 1
+ tokens.push ['NEOSTRING', str[pi..]] if i > pi < str.length
+ return tokens if regex
+ return @token 'STRING', '""' unless tokens.length
+ tokens.unshift ['', ''] unless tokens[0][0] is 'NEOSTRING'
+ @token '(', '(' if interpolated = tokens.length > 1
+ for [tag, value], i in tokens
+ @token '+', '+' if i
+ if tag is 'TOKENS'
+ @tokens.push value...
+ else
+ @token 'STRING', @makeString value, '"', heredoc
+ @token ')', ')' if interpolated
+ tokens
+
+ # Pairs up a closing token, ensuring that all listed pairs of tokens are
+ # correctly balanced throughout the course of the token stream.
+ pair: (tag) ->
+ unless tag is wanted = last @ends
+ @error "unmatched #{tag}" unless 'OUTDENT' is wanted
+ # Auto-close INDENT to support syntax like this:
+ #
+ # el.click((event) ->
+ # el.hide())
+ #
+ @indent -= size = last @indents
+ @outdentToken size, true
+ return @pair tag
+ @ends.pop()
+
+ # Helpers
+ # -------
+
+ # Add a token to the results, taking note of the line number.
+ token: (tag, value) ->
+ @tokens.push [tag, value, @line]
+
+ # Peek at a tag in the current token stream.
+ tag: (index, tag) ->
+ (tok = last @tokens, index) and if tag then tok[0] = tag else tok[0]
+
+ # Peek at a value in the current token stream.
+ value: (index, val) ->
+ (tok = last @tokens, index) and if val then tok[1] = val else tok[1]
+
+ # Are we in the midst of an unfinished expression?
+ unfinished: ->
+ LINE_CONTINUER.test(@chunk) or
+ @tag() in ['\\', '.', '?.', 'UNARY', 'MATH', '+', '-', 'SHIFT', 'RELATION'
+ 'COMPARE', 'LOGIC', 'THROW', 'EXTENDS']
+
+ # Converts newlines for string literals.
+ escapeLines: (str, heredoc) ->
+ str.replace MULTILINER, if heredoc then '\\n' else ''
+
+ # Constructs a string token by escaping quotes and newlines.
+ makeString: (body, quote, heredoc) ->
+ return quote + quote unless body
+ body = body.replace /\\([\s\S])/g, (match, contents) ->
+ if contents in ['\n', quote] then contents else match
+ body = body.replace /// #{quote} ///g, '\\$&'
+ quote + @escapeLines(body, heredoc) + quote
+
+ # Throws a syntax error on the current `@line`.
+ error: (message) ->
+ throw SyntaxError "#{message} on line #{ @line + 1}"
+
+# Constants
+# ---------
+
+# Keywords that CoffeeScript shares in common with JavaScript.
+JS_KEYWORDS = [
+ 'true', 'false', 'null', 'this'
+ 'new', 'delete', 'typeof', 'in', 'instanceof'
+ 'return', 'throw', 'break', 'continue', 'debugger'
+ 'if', 'else', 'switch', 'for', 'while', 'do', 'try', 'catch', 'finally'
+ 'class', 'extends', 'super'
+]
+
+# CoffeeScript-only keywords.
+COFFEE_KEYWORDS = ['undefined', 'then', 'unless', 'until', 'loop', 'of', 'by', 'when']
+
+COFFEE_ALIAS_MAP =
+ and : '&&'
+ or : '||'
+ is : '=='
+ isnt : '!='
+ not : '!'
+ yes : 'true'
+ no : 'false'
+ on : 'true'
+ off : 'false'
+
+COFFEE_ALIASES = (key for key of COFFEE_ALIAS_MAP)
+COFFEE_KEYWORDS = COFFEE_KEYWORDS.concat COFFEE_ALIASES
+
+# The list of keywords that are reserved by JavaScript, but not used, or are
+# used by CoffeeScript internally. We throw an error when these are encountered,
+# to avoid having a JavaScript error at runtime.
+RESERVED = [
+ 'case', 'default', 'function', 'var', 'void', 'with'
+ 'const', 'let', 'enum', 'export', 'import', 'native'
+ '__hasProp', '__extends', '__slice', '__bind', '__indexOf'
+ 'implements', 'interface', 'let', 'package',
+ 'private', 'protected', 'public', 'static', 'yield'
+]
+
+STRICT_PROSCRIBED = ['arguments', 'eval']
+
+# The superset of both JavaScript keywords and reserved words, none of which may
+# be used as identifiers or properties.
+JS_FORBIDDEN = JS_KEYWORDS.concat(RESERVED).concat(STRICT_PROSCRIBED)
+
+exports.RESERVED = RESERVED.concat(JS_KEYWORDS).concat(COFFEE_KEYWORDS).concat(STRICT_PROSCRIBED)
+exports.STRICT_PROSCRIBED = STRICT_PROSCRIBED
+
+# Token matching regexes.
+IDENTIFIER = /// ^
+ ( [$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]* )
+ ( [^\n\S]* : (?!:) )? # Is this a property name?
+///
+
+NUMBER = ///
+ ^ 0b[01]+ | # binary
+ ^ 0o[0-7]+ | # octal
+ ^ 0x[\da-f]+ | # hex
+ ^ \d*\.?\d+ (?:e[+-]?\d+)? # decimal
+///i
+
+HEREDOC = /// ^ ("""|''') ([\s\S]*?) (?:\n[^\n\S]*)? \1 ///
+
+OPERATOR = /// ^ (
+ ?: [-=]> # function
+ | [-+*/%<>&|^!?=]= # compound assign / compare
+ | >>>=? # zero-fill right shift
+ | ([-+:])\1 # doubles
+ | ([&|<>])\2=? # logic / shift
+ | \?\. # soak access
+ | \.{2,3} # range or splat
+) ///
+
+WHITESPACE = /^[^\n\S]+/
+
+COMMENT = /^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)?$)|^(?:\s*#(?!##[^#]).*)+/
+
+CODE = /^[-=]>/
+
+MULTI_DENT = /^(?:\n[^\n\S]*)+/
+
+SIMPLESTR = /^'[^\\']*(?:\\.[^\\']*)*'/
+
+JSTOKEN = /^`[^\\`]*(?:\\.[^\\`]*)*`/
+
+# Regex-matching-regexes.
+REGEX = /// ^
+ (/ (?! [\s=] ) # disallow leading whitespace or equals signs
+ [^ [ / \n \\ ]* # every other thing
+ (?:
+ (?: \\[\s\S] # anything escaped
+ | \[ # character class
+ [^ \] \n \\ ]*
+ (?: \\[\s\S] [^ \] \n \\ ]* )*
+ ]
+ ) [^ [ / \n \\ ]*
+ )*
+ /) ([imgy]{0,4}) (?!\w)
+///
+
+HEREGEX = /// ^ /{3} ([\s\S]+?) /{3} ([imgy]{0,4}) (?!\w) ///
+
+HEREGEX_OMIT = /\s+(?:#.*)?/g
+
+# Token cleaning regexes.
+MULTILINER = /\n/g
+
+HEREDOC_INDENT = /\n+([^\n\S]*)/g
+
+HEREDOC_ILLEGAL = /\*\//
+
+LINE_CONTINUER = /// ^ \s* (?: , | \??\.(?![.\d]) | :: ) ///
+
+TRAILING_SPACES = /\s+$/
+
+# Compound assignment tokens.
+COMPOUND_ASSIGN = [
+ '-=', '+=', '/=', '*=', '%=', '||=', '&&=', '?=', '<<=', '>>=', '>>>=', '&=', '^=', '|='
+]
+
+# Unary tokens.
+UNARY = ['!', '~', 'NEW', 'TYPEOF', 'DELETE', 'DO']
+
+# Logical tokens.
+LOGIC = ['&&', '||', '&', '|', '^']
+
+# Bit-shifting tokens.
+SHIFT = ['<<', '>>', '>>>']
+
+# Comparison tokens.
+COMPARE = ['==', '!=', '<', '>', '<=', '>=']
+
+# Mathematical tokens.
+MATH = ['*', '/', '%']
+
+# Relational tokens that are negatable with `not` prefix.
+RELATION = ['IN', 'OF', 'INSTANCEOF']
+
+# Boolean tokens.
+BOOL = ['TRUE', 'FALSE']
+
+# Tokens which a regular expression will never immediately follow, but which
+# a division operator might.
+#
+# See: http://www.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions
+#
+# Our list is shorter, due to sans-parentheses method calls.
+NOT_REGEX = ['NUMBER', 'REGEX', 'BOOL', 'NULL', 'UNDEFINED', '++', '--', ']']
+
+# If the previous token is not spaced, there are more preceding tokens that
+# force a division parse:
+NOT_SPACED_REGEX = NOT_REGEX.concat ')', '}', 'THIS', 'IDENTIFIER', 'STRING'
+
+# Tokens which could legitimately be invoked or indexed. An opening
+# parentheses or bracket following these tokens will be recorded as the start
+# of a function invocation or indexing operation.
+CALLABLE = ['IDENTIFIER', 'STRING', 'REGEX', ')', ']', '}', '?', '::', '@', 'THIS', 'SUPER']
+INDEXABLE = CALLABLE.concat 'NUMBER', 'BOOL', 'NULL', 'UNDEFINED'
+
+# Tokens that, when immediately preceding a `WHEN`, indicate that the `WHEN`
+# occurs at the start of a line. We disambiguate these from trailing whens to
+# avoid an ambiguity in the grammar.
+LINE_BREAK = ['INDENT', 'OUTDENT', 'TERMINATOR']
diff --git a/samples/CoffeeScript/rack_application.coffee b/samples/CoffeeScript/rack_application.coffee
new file mode 100644
index 0000000..674313b
--- /dev/null
+++ b/samples/CoffeeScript/rack_application.coffee
@@ -0,0 +1,256 @@
+# The `RackApplication` class is responsible for managing a
+# [Nack](http://josh.github.com/nack/) pool for a given Rack
+# application. Incoming HTTP requests are dispatched to
+# `RackApplication` instances by an `HttpServer`, where they are
+# subsequently handled by a pool of Nack worker processes. By default,
+# Pow tells Nack to use a maximum of two worker processes per
+# application, but this can be overridden with the configuration's
+# `workers` option.
+#
+# Before creating the Nack pool, Pow executes the `.powrc` and
+# `.powenv` scripts if they're present in the application root,
+# captures their environment variables, and passes them along to the
+# Nack worker processes. This lets you modify your `RUBYOPT` to use
+# different Ruby options, for example.
+#
+# If [rvm](http://rvm.beginrescueend.com/) is installed and an
+# `.rvmrc` file is present in the application's root, Pow will load
+# both before creating the Nack pool. This makes it easy to run an
+# app with a specific version of Ruby.
+#
+# Nack workers remain running until they're killed, restarted (by
+# touching the `tmp/restart.txt` file in the application root), or
+# until the application has not served requests for the length of time
+# specified in the configuration's `timeout` option (15 minutes by
+# default).
+
+async = require "async"
+fs = require "fs"
+nack = require "nack"
+
+{bufferLines, pause, sourceScriptEnv} = require "./util"
+{join, exists, basename, resolve} = require "path"
+
+module.exports = class RackApplication
+ # Create a `RackApplication` for the given configuration and
+ # root path. The application begins life in the uninitialized
+ # state.
+ constructor: (@configuration, @root, @firstHost) ->
+ @logger = @configuration.getLogger join "apps", basename @root
+ @readyCallbacks = []
+ @quitCallbacks = []
+ @statCallbacks = []
+
+ # Queue `callback` to be invoked when the application becomes ready,
+ # then start the initialization process. If the application's state
+ # is ready, the callback is invoked immediately.
+ ready: (callback) ->
+ if @state is "ready"
+ callback()
+ else
+ @readyCallbacks.push callback
+ @initialize()
+
+ # Tell the application to quit and queue `callback` to be invoked
+ # when all workers have exited. If the application has already quit,
+ # the callback is invoked immediately.
+ quit: (callback) ->
+ if @state
+ @quitCallbacks.push callback if callback
+ @terminate()
+ else
+ callback?()
+
+ # Stat `tmp/restart.txt` in the application root and invoke the
+ # given callback with a single argument indicating whether or not
+ # the file has been touched since the last call to
+ # `queryRestartFile`.
+ queryRestartFile: (callback) ->
+ fs.stat join(@root, "tmp/restart.txt"), (err, stats) =>
+ if err
+ @mtime = null
+ callback false
+ else
+ lastMtime = @mtime
+ @mtime = stats.mtime.getTime()
+ callback lastMtime isnt @mtime
+
+ # Check to see if `tmp/always_restart.txt` is present in the
+ # application root, and set the pool's `runOnce` option
+ # accordingly. Invoke `callback` when the existence check has
+ # finished. (Multiple calls to this method are aggregated.)
+ setPoolRunOnceFlag: (callback) ->
+ unless @statCallbacks.length
+ exists join(@root, "tmp/always_restart.txt"), (alwaysRestart) =>
+ @pool.runOnce = alwaysRestart
+ statCallback() for statCallback in @statCallbacks
+ @statCallbacks = []
+
+ @statCallbacks.push callback
+
+ # Collect environment variables from `.powrc` and `.powenv`, in that
+ # order, if present. The idea is that `.powrc` files can be checked
+ # into a source code repository for global configuration, leaving
+ # `.powenv` free for any necessary local overrides.
+ loadScriptEnvironment: (env, callback) ->
+ async.reduce [".powrc", ".envrc", ".powenv"], env, (env, filename, callback) =>
+ exists script = join(@root, filename), (scriptExists) ->
+ if scriptExists
+ sourceScriptEnv script, env, callback
+ else
+ callback null, env
+ , callback
+
+ # If `.rvmrc` and `$HOME/.rvm/scripts/rvm` are present, load rvm,
+ # source `.rvmrc`, and invoke `callback` with the resulting
+ # environment variables. If `.rvmrc` is present but rvm is not
+ # installed, invoke `callback` without sourcing `.rvmrc`.
+ # Before loading rvm, Pow invokes a helper script that shows a
+ # deprecation notice if it has not yet been displayed.
+ loadRvmEnvironment: (env, callback) ->
+ exists script = join(@root, ".rvmrc"), (rvmrcExists) =>
+ if rvmrcExists
+ exists rvm = @configuration.rvmPath, (rvmExists) =>
+ if rvmExists
+ libexecPath = resolve "#{__dirname}/../libexec"
+ before = """
+ '#{libexecPath}/pow_rvm_deprecation_notice' '#{[@firstHost]}'
+ source '#{rvm}' > /dev/null
+ """.trim()
+ sourceScriptEnv script, env, {before}, callback
+ else
+ callback null, env
+ else
+ callback null, env
+
+ # Stat `tmp/restart.txt` to cache its mtime, then load the
+ # application's full environment from `.powrc`, `.powenv`, and
+ # `.rvmrc`.
+ loadEnvironment: (callback) ->
+ @queryRestartFile =>
+ @loadScriptEnvironment @configuration.env, (err, env) =>
+ if err then callback err
+ else @loadRvmEnvironment env, (err, env) =>
+ if err then callback err
+ else callback null, env
+
+ # Begin the initialization process if the application is in the
+ # uninitialized state. (If the application is terminating, queue a
+ # call to `initialize` after all workers have exited.)
+ initialize: ->
+ if @state
+ if @state is "terminating"
+ @quit => @initialize()
+ return
+
+ @state = "initializing"
+
+ # Load the application's environment. If an error is raised or
+ # either of the environment scripts exits with a non-zero status,
+ # reset the application's state and log the error.
+ @loadEnvironment (err, env) =>
+ if err
+ @state = null
+ @logger.error err.message
+ @logger.error "stdout: #{err.stdout}"
+ @logger.error "stderr: #{err.stderr}"
+
+ # Set the application's state to ready. Then create the Nack
+ # pool instance using the `workers` and `timeout` options from
+ # the application's environment or the global configuration.
+ else
+ @state = "ready"
+
+ @pool = nack.createPool join(@root, "config.ru"),
+ env: env
+ size: env?.POW_WORKERS ? @configuration.workers
+ idle: (env?.POW_TIMEOUT ? @configuration.timeout) * 1000
+
+ # Log the workers' stderr and stdout, and log each worker's
+ # PID as it spawns and exits.
+ bufferLines @pool.stdout, (line) => @logger.info line
+ bufferLines @pool.stderr, (line) => @logger.warning line
+
+ @pool.on "worker:spawn", (process) =>
+ @logger.debug "nack worker #{process.child.pid} spawned"
+
+ @pool.on "worker:exit", (process) =>
+ @logger.debug "nack worker exited"
+
+ # Invoke and remove all queued callbacks, passing along the
+ # error, if any.
+ readyCallback err for readyCallback in @readyCallbacks
+ @readyCallbacks = []
+
+ # Begin the termination process. (If the application is initializing,
+ # wait until it is ready before shutting down.)
+ terminate: ->
+ if @state is "initializing"
+ @ready => @terminate()
+
+ else if @state is "ready"
+ @state = "terminating"
+
+ # Instruct all workers to exit. After the processes have
+ # terminated, reset the application's state, then invoke and
+ # remove all queued callbacks.
+ @pool.quit =>
+ @state = null
+ @mtime = null
+ @pool = null
+
+ quitCallback() for quitCallback in @quitCallbacks
+ @quitCallbacks = []
+
+ # Handle an incoming HTTP request. Wait until the application is in
+ # the ready state, restart the workers if necessary, then pass the
+ # request along to the Nack pool. If the Nack worker raises an
+ # exception handling the request, reset the application.
+ handle: (req, res, next, callback) ->
+ resume = pause req
+ @ready (err) =>
+ return next err if err
+ @setPoolRunOnceFlag =>
+ @restartIfNecessary =>
+ req.proxyMetaVariables =
+ SERVER_PORT: @configuration.dstPort.toString()
+ try
+ @pool.proxy req, res, (err) =>
+ @quit() if err
+ next err
+ finally
+ resume()
+ callback?()
+
+ # Terminate the application, re-initialize it, and invoke the given
+ # callback when the application's state becomes ready.
+ restart: (callback) ->
+ @quit =>
+ @ready callback
+
+ # Restart the application if `tmp/restart.txt` has been touched
+ # since the last call to this function.
+ restartIfNecessary: (callback) ->
+ @queryRestartFile (mtimeChanged) =>
+ if mtimeChanged
+ @restart callback
+ else
+ callback()
+
+ # Append RVM autoload boilerplate to the application's `.powrc`
+ # file. This is called by the RVM deprecation notice mini-app.
+ writeRvmBoilerplate: ->
+ powrc = join @root, ".powrc"
+ boilerplate = @constructor.rvmBoilerplate
+
+ fs.readFile powrc, "utf8", (err, contents) ->
+ contents ?= ""
+ if contents.indexOf(boilerplate) is -1
+ fs.writeFile powrc, "#{boilerplate}\n#{contents}"
+
+ @rvmBoilerplate: """
+ if [ -f "$rvm_path/scripts/rvm" ] && [ -f ".rvmrc" ]; then
+ source "$rvm_path/scripts/rvm"
+ source ".rvmrc"
+ fi
+ """
diff --git a/samples/CoffeeScript/xipd.coffee b/samples/CoffeeScript/xipd.coffee
new file mode 100644
index 0000000..8b49f1f
--- /dev/null
+++ b/samples/CoffeeScript/xipd.coffee
@@ -0,0 +1,110 @@
+dnsserver = require "dnsserver"
+
+exports.Server = class Server extends dnsserver.Server
+ NS_T_A = 1
+ NS_T_NS = 2
+ NS_T_CNAME = 5
+ NS_T_SOA = 6
+ NS_C_IN = 1
+ NS_RCODE_NXDOMAIN = 3
+
+ constructor: (domain, @rootAddress) ->
+ super
+ @domain = domain.toLowerCase()
+ @soa = createSOA @domain
+ @on "request", @handleRequest
+
+ handleRequest: (req, res) =>
+ question = req.question
+ subdomain = @extractSubdomain question.name
+
+ if subdomain? and isARequest question
+ res.addRR question.name, NS_T_A, NS_C_IN, 600, subdomain.getAddress()
+ else if subdomain?.isEmpty() and isNSRequest question
+ res.addRR question.name, NS_T_SOA, NS_C_IN, 600, @soa, true
+ else
+ res.header.rcode = NS_RCODE_NXDOMAIN
+
+ res.send()
+
+ extractSubdomain: (name) ->
+ Subdomain.extract name, @domain, @rootAddress
+
+ isARequest = (question) ->
+ question.type is NS_T_A and question.class is NS_C_IN
+
+ isNSRequest = (question) ->
+ question.type is NS_T_NS and question.class is NS_C_IN
+
+ createSOA = (domain) ->
+ mname = "ns-1.#{domain}"
+ rname = "hostmaster.#{domain}"
+ serial = parseInt new Date().getTime() / 1000
+ refresh = 28800
+ retry = 7200
+ expire = 604800
+ minimum = 3600
+ dnsserver.createSOA mname, rname, serial, refresh, retry, expire, minimum
+
+exports.createServer = (domain, address = "127.0.0.1") ->
+ new Server domain, address
+
+exports.Subdomain = class Subdomain
+ @extract: (name, domain, address) ->
+ return unless name
+ name = name.toLowerCase()
+ offset = name.length - domain.length
+
+ if domain is name.slice offset
+ subdomain = if 0 >= offset then null else name.slice 0, offset - 1
+ new constructor subdomain, address if constructor = @for subdomain
+
+ @for: (subdomain = "") ->
+ if IPAddressSubdomain.pattern.test subdomain
+ IPAddressSubdomain
+ else if EncodedSubdomain.pattern.test subdomain
+ EncodedSubdomain
+ else
+ Subdomain
+
+ constructor: (@subdomain, @address) ->
+ @labels = subdomain?.split(".") ? []
+ @length = @labels.length
+
+ isEmpty: ->
+ @length is 0
+
+ getAddress: ->
+ @address
+
+class IPAddressSubdomain extends Subdomain
+ @pattern = /// (^|\.)
+ ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}
+ (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
+ $ ///
+
+ getAddress: ->
+ @labels.slice(-4).join "."
+
+class EncodedSubdomain extends Subdomain
+ @pattern = /(^|\.)[a-z0-9]{1,7}$/
+
+ getAddress: ->
+ decode @labels[@length - 1]
+
+exports.encode = encode = (ip) ->
+ value = 0
+ for byte, index in ip.split "."
+ value += parseInt(byte, 10) << (index * 8)
+ (value >>> 0).toString 36
+
+PATTERN = /^[a-z0-9]{1,7}$/
+
+exports.decode = decode = (string) ->
+ return unless PATTERN.test string
+ value = parseInt string, 36
+ ip = []
+ for i in [1..4]
+ ip.push value & 0xFF
+ value >>= 8
+ ip.join "."