/// <summary>
        /// Crunched JS string passed to it, returning crunched string.
        /// The ErrorList property will be set with any errors found during the minification process.
        /// </summary>
        /// <param name="source">source Javascript</param>
        /// <param name="codeSettings">code minification settings</param>
        /// <returns>minified Javascript</returns>
        public string MinifyJavaScript(string source, JsSettings codeSettings)
        {
            // default is an empty string
            var crunched = string.Empty;

            // reset the errors builder
            m_errorList = new List<MinifierError>();

            // create the parser from the source string.
            // pass null for the assumed globals array
            var parser = new JsParser(source);

            // file context is a property on the parser
            parser.FileContext = FileName;

            // hook the engine error event
            parser.CompilerError += OnJavaScriptError;

            try
            {
                var preprocessOnly = codeSettings != null && codeSettings.PreprocessOnly;
                var sb = new StringBuilder();
                using (var stringWriter = new StringWriter(sb, CultureInfo.InvariantCulture))
                {
                    if (preprocessOnly)
                    {
                        parser.EchoWriter = stringWriter;
                    }

                    // parse the input
                    var scriptBlock = parser.Parse(codeSettings);
                    if (scriptBlock != null && !preprocessOnly)
                    {
                        // we'll return the crunched code
                        if (codeSettings != null && codeSettings.Format == JsFormat.JSON)
                        {
                            // we're going to use a different output visitor -- one
                            // that specifically returns valid JSON.
                            if (!JsonOutputVisitor.Apply(stringWriter, scriptBlock))
                            {
                                m_errorList.Add(new MinifierError(
                                    true,
                                    0,
                                    null,
                                    null,
                                    null,
                                    this.FileName,
                                    0,
                                    0,
                                    0,
                                    0,
                                    JScript.InvalidJSONOutput));
                            }
                        }
                        else
                        {
                            // just use the normal output visitor
                            JsOutputVisitor.Apply(stringWriter, scriptBlock, codeSettings);
                        }
                    }
                }

                crunched = sb.ToString();
            }
            catch (Exception e)
            {
                m_errorList.Add(new MinifierError(
                    true,
                    0,
                    null,
                    null,
                    null,
                    this.FileName,
                    0,
                    0,
                    0,
                    0,
                    e.Message));
                throw;
            }

            return crunched;
        }
        private Parsed ParseExpressionFunction()
        {
            var parsed = Parsed.False;
            if (CurrentTokenType == TokenType.Function
                && string.Compare(CurrentTokenText, "expression(", StringComparison.OrdinalIgnoreCase) == 0)
            {
                Append(CurrentTokenText.ToLowerInvariant());
                NextToken();

                // for now, just echo out everything up to the matching closing paren,
                // taking into account that there will probably be other nested paren pairs.
                // The content of the expression is JavaScript, so we'd really
                // need a full-blown JS-parser to crunch it properly. Kinda scary.
                // Start the parenLevel at 0 because the "expression(" token contains the first paren.
                var jsBuilder = new StringBuilder();
                int parenLevel = 0;

                while (!m_scanner.EndOfFile
                  && (CurrentTokenType != TokenType.Character
                    || CurrentTokenText != ")"
                    || parenLevel > 0))
                {
                    if (CurrentTokenType == TokenType.Function)
                    {
                        // the function token INCLUDES the opening parenthesis,
                        // so up the paren level whenever we find a function.
                        // AND this includes the actual expression( token -- so we'll
                        // hit this branch at the beginning. Make sure the parenLevel
                        // is initialized to take that into account
                        ++parenLevel;
                    }
                    else if (CurrentTokenType == TokenType.Character)
                    {
                        switch (CurrentTokenText)
                        {
                            case "(":
                                // start a nested paren
                                ++parenLevel;
                                break;

                            case ")":
                                // end a nested paren
                                // (we know it's nested because if it wasn't, we wouldn't
                                // have entered the loop)
                                --parenLevel;
                                break;
                        }
                    }
                    jsBuilder.Append(CurrentTokenText);
                    NextToken();
                }

                // create a JSParser object with the source we found, crunch it, and send
                // the minified script to the output
                var expressionCode = jsBuilder.ToString();
                if (Settings.MinifyExpressions)
                {
                    // we want to minify the javascript expressions.
                    // create a JSParser object from the code we parsed.
                    JsParser jsParser = new JsParser(expressionCode);

                    // copy the file context
                    jsParser.FileContext = this.FileContext;

                    // hook the error handler and set the "contains errors" flag to false.
                    // the handler will set the value to true if it encounters any errors
                    var containsErrors = false;
                    jsParser.CompilerError += (sender, ea) =>
                    {
                        ReportError(0, CssErrorCode.ExpressionError, ea.Error.Message);
                        containsErrors = true;
                    };

                    // parse the source as an expression using our common JS settings
                    JsBlock block = jsParser.Parse(m_jsSettings);

                    // if we got back a parsed block and there were no errors, output the minified code.
                    // if we didn't get back the block, or if there were any errors at all, just output
                    // the raw expression source.
                    if (block != null && !containsErrors)
                    {
                        Append(block.ToCode());
                    }
                    else
                    {
                        Append(expressionCode);
                    }
                }
                else
                {
                    // we don't want to minify expression code for some reason.
                    // just output the code exactly as we parsed it
                    Append(expressionCode);
                }

                if (CurrentTokenType == TokenType.Character && CurrentTokenText == ")")
                {
                    AppendCurrent();
                    SkipSpace();
                    parsed = Parsed.True;
                }
                else
                {
                    ReportError(0, CssErrorCode.ExpectedClosingParenthesis, CurrentTokenText);
                }
            }

            return parsed;
        }