<commit_before>/*
 * Copyright 2004 ThoughtWorks, Inc
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

// This script contains some HTML utility functions that
// make it possible to handle elements in a way that is
// compatible with both IE-like and Mozilla-like browsers

String.prototype.trim = function() {
  var result = this.replace( /^\s+/g, "" );// strip leading
  return result.replace( /\s+$/g, "" );// strip trailing
};
String.prototype.lcfirst = function() {
   return this.charAt(0).toLowerCase() + this.substr(1);
};
String.prototype.ucfirst = function() {
   return this.charAt(0).toUpperCase() + this.substr(1);
};
String.prototype.startsWith = function(str) {
    return this.indexOf(str) == 0;
};

// Returns the text in this element
function getText(element) {
    var text = "";

    if(browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5")
    {
        var dummyElement = element.cloneNode(true);
        renderWhitespaceInTextContent(dummyElement);
        text = dummyElement.textContent;
    } else if (browserVersion.isOpera) {
    	var dummyElement = element.cloneNode(true);
        renderWhitespaceInTextContent(dummyElement);
        text = dummyElement.innerText;
        text = xmlDecode(text);
    }
    else if(element.textContent)
    {
        text = element.textContent;
    }
    else if(element.innerText)
    {
        text = element.innerText;
    }

    text = normalizeNewlines(text);
    text = normalizeSpaces(text);

    return text.trim();
}

function renderWhitespaceInTextContent(element) {
    // Remove non-visible newlines in text nodes
    if (element.nodeType == Node.TEXT_NODE)
    {
        element.data = element.data.replace(/\n|\r|\t/g, " ");
        return;
    }

    if (element.nodeType == Node.COMMENT_NODE)
    {
        element.data = "";
        return;
    }

    // Don't modify PRE elements
    if (element.tagName == "PRE")
    {
        return;
    }

    // Handle inline element that force newlines
    if (tagIs(element, ["BR", "HR"]))
    {
        // Replace this element with a newline text element
        element.parentNode.replaceChild(element.ownerDocument.createTextNode("\n"), element)
    }

    for (var i = 0; i < element.childNodes.length; i++)
    {
        var child = element.childNodes.item(i)
        renderWhitespaceInTextContent(child);
    }

    // Handle block elements that introduce newlines
// -- From HTML spec:
//<!ENTITY % block
//     "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
//      BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
    if (tagIs(element, ["P", "DIV"]))
    {
        element.appendChild(element.ownerDocument.createTextNode("\n"), element)
    }

}

function tagIs(element, tags)
{
    var tag = element.tagName;
    for (var i = 0; i < tags.length; i++)
    {
        if (tags[i] == tag)
        {
            return true;
        }
    }
    return false;
}

/**
 * Convert all newlines to \m
 */
function normalizeNewlines(text)
{
    return text.replace(/\r\n|\r/g, "\n");
}

/**
 * Replace multiple sequential spaces with a single space, and then convert   to space.
 */
function normalizeSpaces(text)
{
    // IE has already done this conversion, so doing it again will remove multiple nbsp
    if (browserVersion.isIE)
    {
        return text;
    }

    // Replace multiple spaces with a single space
    // TODO - this shouldn't occur inside PRE elements
    text = text.replace(/\ +/g, " ");

    // Replace   with a space
    var pat = String.fromCharCode(160); // Opera doesn't like /\240/g
   	var re = new RegExp(pat, "g");
    return text.replace(re, " ");
}

function xmlDecode(text) {
	text = text.replace(/"/g, '"');
	text = text.replace(/'/g, "'");
	text = text.replace(/</g, "<");
	text = text.replace(/>/g, ">");
	text = text.replace(/&/g, "&");
	return text;
}

// Sets the text in this element
function setText(element, text) {
    if(element.textContent) {
        element.textContent = text;
    } else if(element.innerText) {
        element.innerText = text;
    }
}

// Get the value of an <input> element
function getInputValue(inputElement) {
    if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
        inputElement.type.toUpperCase() == 'RADIO')
    {
        return (inputElement.checked ? 'on' : 'off');
    }
    return inputElement.value;
}

/* Fire an event in a browser-compatible manner */
function triggerEvent(element, eventType, canBubble) {
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
        element.fireEvent('on' + eventType);
    }
    else {
        var evt = document.createEvent('HTMLEvents');
        evt.initEvent(eventType, canBubble, true);
        element.dispatchEvent(evt);
    }
}

function triggerKeyEvent(element, eventType, keycode, canBubble) {
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
    		var w = parent.frames['myiframe']
                if (w==null) {
                	w = window;
                }
		keyEvent = w.document.createEventObject();
		keyEvent.keyCode=keycode;
		element.fireEvent('on' + eventType, keyEvent);
    }
    else {
    	var evt;
    	if( window.KeyEvent ) {
			evt = document.createEvent('KeyEvents');
			evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
		} else {
			evt = document.createEvent('UIEvents');
			evt.initUIEvent( eventType, true, true, window, 1 );
			evt.keyCode = keycode;
		}

        element.dispatchEvent(evt);
    }
}

/* Fire a mouse event in a browser-compatible manner */
function triggerMouseEvent(element, eventType, canBubble, clientX, clientY) {
    clientX = clientX ? clientX : 0;
    clientY = clientY ? clientY : 0;

    // TODO: set these attributes -- they don't seem to be needed by the initial test cases, but that could change...
    var screenX = 0;
    var screenY = 0;

    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
    	if (!screenX && !screenY && !clientX && !clientY) {
        	element.fireEvent('on' + eventType);
        }
        else {
        	var ieEvent = document.createEventObject();
                ieEvent.detail = 0;
                ieEvent.screenX = screenX;
                ieEvent.screenY = screenY;
                ieEvent.clientX = clientX;
                ieEvent.clientY = clientY;
                ieEvent.ctrlKey = false;
                ieEvent.altKey = false;
                ieEvent.shiftKey = false;
                ieEvent.metaKey = false;
                ieEvent.button = 0;
                ieEvent.relatedTarget = null;

                // when we go this route, window.event is never set to contain the event we have just created.
                // ideally we could just slide it in as follows in the try-block below, but this normally
                // doesn't work.  This is why I try to avoid this code path, which is only required if we need to
                // set attributes on the event (e.g., clientX).
                try {
        	        window.event = ieEvent;
                }
                catch(e) {
        		// getting an "Object does not support this action or property" error.  Save the event away
                        // for future reference.
                        // TODO: is there a way to update window.event?

                        // work around for http://jira.openqa.org/browse/SEL-280 -- make the event available somewhere:
                        selenium.browserbot.getCurrentWindow().selenium_event = ieEvent;
                }
                element.fireEvent('on' + eventType, ieEvent);
        }
    }
    else {
        var evt = document.createEvent('MouseEvents');
        if (evt.initMouseEvent)
        {
            evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, false, false, false, false, 0, null)
        }
        else
        {
            // Safari
            // TODO we should be initialising other mouse-event related attributes here
            evt.initEvent(eventType, canBubble, true);
        }
        element.dispatchEvent(evt);
    }
}

function removeLoadListener(element, command) {
    if (window.removeEventListener)
        element.removeEventListener("load", command, true);
    else if (window.detachEvent)
        element.detachEvent("onload", command);
}

function addLoadListener(element, command) {
    if (window.addEventListener && !browserVersion.isOpera)
        element.addEventListener("load",command, true);
    else if (window.attachEvent)
        element.attachEvent("onload",command);
}

function addUnloadListener(element, command) {
    if (window.addEventListener)
        element.addEventListener("unload",command, true);
    else if (window.attachEvent)
        element.attachEvent("onunload",command);
}

/**
 * Override the broken getFunctionName() method from JsUnit
 * This file must be loaded _after_ the jsunitCore.js
 */
function getFunctionName(aFunction) {
  var regexpResult = aFunction.toString().match(/function (\w*)/);
  if (regexpResult && regexpResult[1]) {
      return regexpResult[1];
  }
  return 'anonymous';
}

function getDocumentBase(doc) {
	var bases = document.getElementsByTagName("base");
	if (bases && bases.length && bases[0].href) {
		return bases[0].href;
	}
	return "";
}

function describe(object, delimiter) {
    var props = new Array();
    for (var prop in object) {
        props.push(prop + " -> " + object[prop]);
    }
    return props.join(delimiter || '\n');
}

var PatternMatcher = function(pattern) {
    this.selectStrategy(pattern);
};
PatternMatcher.prototype = {

    selectStrategy: function(pattern) {
        this.pattern = pattern;
        var strategyName = 'glob'; // by default
        if (/^([a-z-]+):(.*)/.test(pattern)) {
            var possibleNewStrategyName = RegExp.$1;
            var possibleNewPattern = RegExp.$2;
            if (PatternMatcher.strategies[possibleNewStrategyName]) {
            	strategyName = possibleNewStrategyName;
                pattern = possibleNewPattern;
            }
        }
        var matchStrategy = PatternMatcher.strategies[strategyName];
        if (!matchStrategy) {
            throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
        }
        this.strategy = matchStrategy;
        this.matcher = new matchStrategy(pattern);
    },

    matches: function(actual) {
        return this.matcher.matches(actual + '');
        // Note: appending an empty string avoids a Konqueror bug
    }

};

/**
 * A "static" convenience method for easy matching
 */
PatternMatcher.matches = function(pattern, actual) {
    return new PatternMatcher(pattern).matches(actual);
};

PatternMatcher.strategies = {

    /**
     * Exact matching, e.g. "exact:***"
     */
    exact: function(expected) {
        this.expected = expected;
        this.matches = function(actual) {
            return actual == this.expected;
        };
    },

    /**
     * Match by regular expression, e.g. "regexp:^[0-9]+$"
     */
    regexp: function(regexpString) {
        this.regexp = new RegExp(regexpString);
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    },

    /**
     * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
     * but don't require a perfect match; instead succeed if actual
     * contains something that matches globString.
     * Making this distinction is motivated by a bug in IE6 which
     * leads to the browser hanging if we implement *TextPresent tests
     * by just matching against a regular expression beginning and
     * ending with ".*".  The globcontains strategy allows us to satisfy
     * the functional needs of the *TextPresent ops more efficiently
     * and so avoid running into this IE6 freeze.
     */
    globContains: function(globString) {
        this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    },

    /**
     * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
     */
    glob: function(globString) {
        this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    }

};

PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
    var re = glob;
    re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
    re = re.replace(/\?/g, "(.|[\r\n])");
    re = re.replace(/\*/g, "(.|[\r\n])*");
    return re;
};

PatternMatcher.regexpFromGlobContains = function(globContains) {
    return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
};

PatternMatcher.regexpFromGlob = function(glob) {
    return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
};

var Assert = {

    fail: function(message) {
        throw new AssertionFailedError(message);
    },

    /*
     * Assert.equals(comment?, expected, actual)
     */
    equals: function() {
        var args = new AssertionArguments(arguments);
        if (args.expected === args.actual) {
            return;
        }
        Assert.fail(args.comment +
                    "Expected '" + args.expected +
                    "' but was '" + args.actual + "'");
    },

    /*
     * Assert.matches(comment?, pattern, actual)
     */
    matches: function() {
        var args = new AssertionArguments(arguments);
        if (PatternMatcher.matches(args.expected, args.actual)) {
            return;
        }
        Assert.fail(args.comment +
                    "Actual value '" + args.actual +
                    "' did not match '" + args.expected + "'");
    },

    /*
     * Assert.notMtches(comment?, pattern, actual)
     */
    notMatches: function() {
        var args = new AssertionArguments(arguments);
        if (!PatternMatcher.matches(args.expected, args.actual)) {
            return;
        }
        Assert.fail(args.comment +
                    "Actual value '" + args.actual +
                    "' did match '" + args.expected + "'");
    }

};

// Preprocess the arguments to allow for an optional comment.
function AssertionArguments(args) {
    if (args.length == 2) {
        this.comment = "";
        this.expected = args[0];
        this.actual = args[1];
    } else {
        this.comment = args[0] + "; ";
        this.expected = args[1];
        this.actual = args[2];
    }
}

function AssertionFailedError(message) {
    this.isAssertionFailedError = true;
    this.isSeleniumError = true;
    this.message = message;
    this.failureMessage = message;
}

function SeleniumError(message) {
    var error = new Error(message);
    error.isSeleniumError = true;
    return error;
};

var Effect = new Object();

Object.extend(Effect, {
    highlight : function(element) {
        var highLightColor = "yellow";
        var originalColor = Element.getStyle(element, "background-color");
        Element.setStyle(element, {"background-color" : highLightColor});
        window.setTimeout(function() {
            Element.setStyle(element, {"background-color" : originalColor});
        }, 300);
    }
});

<commit_msg>add objToString() routine to be able to inspect hashes in vs.2003 debugger, which displays their values as {...} instead of printing out each key/value pair.  There's got to be a better way to do this, but I don't know it...<commit_after>/*
 * Copyright 2004 ThoughtWorks, Inc
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

// This script contains some HTML utility functions that
// make it possible to handle elements in a way that is
// compatible with both IE-like and Mozilla-like browsers

String.prototype.trim = function() {
  var result = this.replace( /^\s+/g, "" );// strip leading
  return result.replace( /\s+$/g, "" );// strip trailing
};
String.prototype.lcfirst = function() {
   return this.charAt(0).toLowerCase() + this.substr(1);
};
String.prototype.ucfirst = function() {
   return this.charAt(0).toUpperCase() + this.substr(1);
};
String.prototype.startsWith = function(str) {
    return this.indexOf(str) == 0;
};

// Returns the text in this element
function getText(element) {
    var text = "";

    if(browserVersion.isFirefox && browserVersion.firefoxVersion >= "1.5")
    {
        var dummyElement = element.cloneNode(true);
        renderWhitespaceInTextContent(dummyElement);
        text = dummyElement.textContent;
    } else if (browserVersion.isOpera) {
    	var dummyElement = element.cloneNode(true);
        renderWhitespaceInTextContent(dummyElement);
        text = dummyElement.innerText;
        text = xmlDecode(text);
    }
    else if(element.textContent)
    {
        text = element.textContent;
    }
    else if(element.innerText)
    {
        text = element.innerText;
    }

    text = normalizeNewlines(text);
    text = normalizeSpaces(text);

    return text.trim();
}

function renderWhitespaceInTextContent(element) {
    // Remove non-visible newlines in text nodes
    if (element.nodeType == Node.TEXT_NODE)
    {
        element.data = element.data.replace(/\n|\r|\t/g, " ");
        return;
    }

    if (element.nodeType == Node.COMMENT_NODE)
    {
        element.data = "";
        return;
    }

    // Don't modify PRE elements
    if (element.tagName == "PRE")
    {
        return;
    }

    // Handle inline element that force newlines
    if (tagIs(element, ["BR", "HR"]))
    {
        // Replace this element with a newline text element
        element.parentNode.replaceChild(element.ownerDocument.createTextNode("\n"), element)
    }

    for (var i = 0; i < element.childNodes.length; i++)
    {
        var child = element.childNodes.item(i)
        renderWhitespaceInTextContent(child);
    }

    // Handle block elements that introduce newlines
// -- From HTML spec:
//<!ENTITY % block
//     "P | %heading; | %list; | %preformatted; | DL | DIV | NOSCRIPT |
//      BLOCKQUOTE | FORM | HR | TABLE | FIELDSET | ADDRESS">
    if (tagIs(element, ["P", "DIV"]))
    {
        element.appendChild(element.ownerDocument.createTextNode("\n"), element)
    }

}

function tagIs(element, tags)
{
    var tag = element.tagName;
    for (var i = 0; i < tags.length; i++)
    {
        if (tags[i] == tag)
        {
            return true;
        }
    }
    return false;
}

/**
 * Convert all newlines to \m
 */
function normalizeNewlines(text)
{
    return text.replace(/\r\n|\r/g, "\n");
}

/**
 * Replace multiple sequential spaces with a single space, and then convert   to space.
 */
function normalizeSpaces(text)
{
    // IE has already done this conversion, so doing it again will remove multiple nbsp
    if (browserVersion.isIE)
    {
        return text;
    }

    // Replace multiple spaces with a single space
    // TODO - this shouldn't occur inside PRE elements
    text = text.replace(/\ +/g, " ");

    // Replace   with a space
    var pat = String.fromCharCode(160); // Opera doesn't like /\240/g
   	var re = new RegExp(pat, "g");
    return text.replace(re, " ");
}

function xmlDecode(text) {
	text = text.replace(/"/g, '"');
	text = text.replace(/'/g, "'");
	text = text.replace(/</g, "<");
	text = text.replace(/>/g, ">");
	text = text.replace(/&/g, "&");
	return text;
}

// Sets the text in this element
function setText(element, text) {
    if(element.textContent) {
        element.textContent = text;
    } else if(element.innerText) {
        element.innerText = text;
    }
}

// Get the value of an <input> element
function getInputValue(inputElement) {
    if (inputElement.type.toUpperCase() == 'CHECKBOX' ||
        inputElement.type.toUpperCase() == 'RADIO')
    {
        return (inputElement.checked ? 'on' : 'off');
    }
    return inputElement.value;
}

/* Fire an event in a browser-compatible manner */
function triggerEvent(element, eventType, canBubble) {
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
        element.fireEvent('on' + eventType);
    }
    else {
        var evt = document.createEvent('HTMLEvents');
        evt.initEvent(eventType, canBubble, true);
        element.dispatchEvent(evt);
    }
}

function triggerKeyEvent(element, eventType, keycode, canBubble) {
    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
    		var w = parent.frames['myiframe']
                if (w==null) {
                	w = window;
                }
		keyEvent = w.document.createEventObject();
		keyEvent.keyCode=keycode;
		element.fireEvent('on' + eventType, keyEvent);
    }
    else {
    	var evt;
    	if( window.KeyEvent ) {
			evt = document.createEvent('KeyEvents');
			evt.initKeyEvent(eventType, true, true, window, false, false, false, false, keycode, keycode);
		} else {
			evt = document.createEvent('UIEvents');
			evt.initUIEvent( eventType, true, true, window, 1 );
			evt.keyCode = keycode;
		}

        element.dispatchEvent(evt);
    }
}

/* Fire a mouse event in a browser-compatible manner */
function triggerMouseEvent(element, eventType, canBubble, clientX, clientY) {
    clientX = clientX ? clientX : 0;
    clientY = clientY ? clientY : 0;

    // TODO: set these attributes -- they don't seem to be needed by the initial test cases, but that could change...
    var screenX = 0;
    var screenY = 0;

    canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
    if (element.fireEvent) {
    	if (!screenX && !screenY && !clientX && !clientY) {
        	element.fireEvent('on' + eventType);
        }
        else {
        	var ieEvent = document.createEventObject();
                ieEvent.detail = 0;
                ieEvent.screenX = screenX;
                ieEvent.screenY = screenY;
                ieEvent.clientX = clientX;
                ieEvent.clientY = clientY;
                ieEvent.ctrlKey = false;
                ieEvent.altKey = false;
                ieEvent.shiftKey = false;
                ieEvent.metaKey = false;
                ieEvent.button = 0;
                ieEvent.relatedTarget = null;

                // when we go this route, window.event is never set to contain the event we have just created.
                // ideally we could just slide it in as follows in the try-block below, but this normally
                // doesn't work.  This is why I try to avoid this code path, which is only required if we need to
                // set attributes on the event (e.g., clientX).
                try {
        	        window.event = ieEvent;
                }
                catch(e) {
        		// getting an "Object does not support this action or property" error.  Save the event away
                        // for future reference.
                        // TODO: is there a way to update window.event?

                        // work around for http://jira.openqa.org/browse/SEL-280 -- make the event available somewhere:
                        selenium.browserbot.getCurrentWindow().selenium_event = ieEvent;
                }
                element.fireEvent('on' + eventType, ieEvent);
        }
    }
    else {
        var evt = document.createEvent('MouseEvents');
        if (evt.initMouseEvent)
        {
            evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, false, false, false, false, 0, null)
        }
        else
        {
            // Safari
            // TODO we should be initialising other mouse-event related attributes here
            evt.initEvent(eventType, canBubble, true);
        }
        element.dispatchEvent(evt);
    }
}

function removeLoadListener(element, command) {
    if (window.removeEventListener)
        element.removeEventListener("load", command, true);
    else if (window.detachEvent)
        element.detachEvent("onload", command);
}

function addLoadListener(element, command) {
    if (window.addEventListener && !browserVersion.isOpera)
        element.addEventListener("load",command, true);
    else if (window.attachEvent)
        element.attachEvent("onload",command);
}

function addUnloadListener(element, command) {
    if (window.addEventListener)
        element.addEventListener("unload",command, true);
    else if (window.attachEvent)
        element.attachEvent("onunload",command);
}

/**
 * Override the broken getFunctionName() method from JsUnit
 * This file must be loaded _after_ the jsunitCore.js
 */
function getFunctionName(aFunction) {
  var regexpResult = aFunction.toString().match(/function (\w*)/);
  if (regexpResult && regexpResult[1]) {
      return regexpResult[1];
  }
  return 'anonymous';
}

function getDocumentBase(doc) {
	var bases = document.getElementsByTagName("base");
	if (bases && bases.length && bases[0].href) {
		return bases[0].href;
	}
	return "";
}

function describe(object, delimiter) {
    var props = new Array();
    for (var prop in object) {
        props.push(prop + " -> " + object[prop]);
    }
    return props.join(delimiter || '\n');
}

var PatternMatcher = function(pattern) {
    this.selectStrategy(pattern);
};
PatternMatcher.prototype = {

    selectStrategy: function(pattern) {
        this.pattern = pattern;
        var strategyName = 'glob'; // by default
        if (/^([a-z-]+):(.*)/.test(pattern)) {
            var possibleNewStrategyName = RegExp.$1;
            var possibleNewPattern = RegExp.$2;
            if (PatternMatcher.strategies[possibleNewStrategyName]) {
            	strategyName = possibleNewStrategyName;
                pattern = possibleNewPattern;
            }
        }
        var matchStrategy = PatternMatcher.strategies[strategyName];
        if (!matchStrategy) {
            throw new SeleniumError("cannot find PatternMatcher.strategies." + strategyName);
        }
        this.strategy = matchStrategy;
        this.matcher = new matchStrategy(pattern);
    },

    matches: function(actual) {
        return this.matcher.matches(actual + '');
        // Note: appending an empty string avoids a Konqueror bug
    }

};

/**
 * A "static" convenience method for easy matching
 */
PatternMatcher.matches = function(pattern, actual) {
    return new PatternMatcher(pattern).matches(actual);
};

PatternMatcher.strategies = {

    /**
     * Exact matching, e.g. "exact:***"
     */
    exact: function(expected) {
        this.expected = expected;
        this.matches = function(actual) {
            return actual == this.expected;
        };
    },

    /**
     * Match by regular expression, e.g. "regexp:^[0-9]+$"
     */
    regexp: function(regexpString) {
        this.regexp = new RegExp(regexpString);
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    },

    /**
     * "globContains" (aka "wildmat") patterns, e.g. "glob:one,two,*",
     * but don't require a perfect match; instead succeed if actual
     * contains something that matches globString.
     * Making this distinction is motivated by a bug in IE6 which
     * leads to the browser hanging if we implement *TextPresent tests
     * by just matching against a regular expression beginning and
     * ending with ".*".  The globcontains strategy allows us to satisfy
     * the functional needs of the *TextPresent ops more efficiently
     * and so avoid running into this IE6 freeze.
     */
    globContains: function(globString) {
        this.regexp = new RegExp(PatternMatcher.regexpFromGlobContains(globString));
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    },

    /**
     * "glob" (aka "wildmat") patterns, e.g. "glob:one,two,*"
     */
    glob: function(globString) {
        this.regexp = new RegExp(PatternMatcher.regexpFromGlob(globString));
        this.matches = function(actual) {
            return this.regexp.test(actual);
        };
    }

};

PatternMatcher.convertGlobMetaCharsToRegexpMetaChars = function(glob) {
    var re = glob;
    re = re.replace(/([.^$+(){}\[\]\\|])/g, "\\$1");
    re = re.replace(/\?/g, "(.|[\r\n])");
    re = re.replace(/\*/g, "(.|[\r\n])*");
    return re;
};

PatternMatcher.regexpFromGlobContains = function(globContains) {
    return PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(globContains);
};

PatternMatcher.regexpFromGlob = function(glob) {
    return "^" + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + "$";
};

var Assert = {

    fail: function(message) {
        throw new AssertionFailedError(message);
    },

    /*
     * Assert.equals(comment?, expected, actual)
     */
    equals: function() {
        var args = new AssertionArguments(arguments);
        if (args.expected === args.actual) {
            return;
        }
        Assert.fail(args.comment +
                    "Expected '" + args.expected +
                    "' but was '" + args.actual + "'");
    },

    /*
     * Assert.matches(comment?, pattern, actual)
     */
    matches: function() {
        var args = new AssertionArguments(arguments);
        if (PatternMatcher.matches(args.expected, args.actual)) {
            return;
        }
        Assert.fail(args.comment +
                    "Actual value '" + args.actual +
                    "' did not match '" + args.expected + "'");
    },

    /*
     * Assert.notMtches(comment?, pattern, actual)
     */
    notMatches: function() {
        var args = new AssertionArguments(arguments);
        if (!PatternMatcher.matches(args.expected, args.actual)) {
            return;
        }
        Assert.fail(args.comment +
                    "Actual value '" + args.actual +
                    "' did match '" + args.expected + "'");
    }

};

// Preprocess the arguments to allow for an optional comment.
function AssertionArguments(args) {
    if (args.length == 2) {
        this.comment = "";
        this.expected = args[0];
        this.actual = args[1];
    } else {
        this.comment = args[0] + "; ";
        this.expected = args[1];
        this.actual = args[2];
    }
}

function AssertionFailedError(message) {
    this.isAssertionFailedError = true;
    this.isSeleniumError = true;
    this.message = message;
    this.failureMessage = message;
}

function SeleniumError(message) {
    var error = new Error(message);
    error.isSeleniumError = true;
    return error;
};

var Effect = new Object();

Object.extend(Effect, {
    highlight : function(element) {
        var highLightColor = "yellow";
        var originalColor = Element.getStyle(element, "background-color");
        Element.setStyle(element, {"background-color" : highLightColor});
        window.setTimeout(function() {
            Element.setStyle(element, {"background-color" : originalColor});
        }, 300);
    }
});

// for use from vs.2003 debugger
function objToString(obj) {
	var s = "";
        for (key in obj) {
    	    s += key + "->" + obj[key] + "\n";
        }
        return s;
}
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
function simpleReporter(results) {
  let passed = 0;
  let failed = 0;
  let lastPassed = true;

  results.on('pass', function (test) {
    passed++;

    clearPassed();
    lastPassed = true;
    process.stdout.write('PASS ' + test.file);
  });

  results.on('fail', function (test) {
    failed++;
    clearPassed();
    lastPassed = false;
    console.log('FAIL ' + test.file);
    console.log('  ' + test.result.message);
    console.log('');
  });

  results.on('end', function () {
    clearPassed();

    console.log('Ran ' + (passed + failed) + ' tests')
    console.log(passed + ' passed')
    console.log(failed + ' failed')
  });

  function clearPassed() {
    if (lastPassed) {
      process.stdout.clearLine();
      process.stdout.cursorTo(0);
    }
  }
}

module.exports = simpleReporter;
<commit_msg>Don't attempt to clear non-TTY output in simple results reporter<commit_after>'use strict';
const path = require('path');
function simpleReporter(results) {
  let passed = 0;
  let failed = 0;
  let lastPassed = true;

  results.on('pass', function (test) {
    passed++;

    clearPassed();
    lastPassed = true;
    process.stdout.write('PASS ' + test.file);
  });

  results.on('fail', function (test) {
    failed++;
    clearPassed();
    lastPassed = false;
    console.log('FAIL ' + test.file);
    console.log('  ' + test.result.message);
    console.log('');
  });

  results.on('end', function () {
    clearPassed();

    console.log('Ran ' + (passed + failed) + ' tests')
    console.log(passed + ' passed')
    console.log(failed + ' failed')
  });

  function clearPassed() {
    if (lastPassed) {
      if (process.stdout.isTTY) {
        process.stdout.clearLine();
        process.stdout.cursorTo(0);
      } else {
        process.stdout.write('\n');
      }
    }
  }
}

module.exports = simpleReporter;
<|endoftext|>


<commit_before>// Load in library dependencies
var assert = require('assert');
var mustache = require('mustache');
var pathval = require('pathval');
var deepClone = require('clone');

// DEV: This is not a perfect backtrack engine, I wonder if this is more/less performant that one.
// DEV: If we were to use regular expressions, we would lose accuracy of re-using variable values (and potentially have contradictions)
// TODO: Can we break each of these recursive items into its own function? I ask because the function is getting large.

// Reverse HTML escapes
// https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L53-L66
var reverseEntityMap = {
  '&': '&',
  '<': '<',
  '>': '>',
  '"': '"',
  ''': '\'',
  '/': '/'
};
var reverseEntityRegExp = /(&|<|>|"|'|/)/g;
function unescapeHtml(string) {
  return String(string).replace(reverseEntityRegExp, function (s) {
    return reverseEntityMap[s];
  });
}

// TODO: Instead of deep clone, consider prototypal lookup for tokens
function Context(content) {
  // Set up content and token internals
  this.originalContent = content;
  this.remainingContent = content || '';
  this.completedContent = '';
  this.tokensByName = {};
}
Context.prototype = {
  // Add a new string to the context
  addStr: function (str) {
    // Assert we are not adding in content that is unwanted
    var expectedStr = this.remainingContent.slice(0, str.length);
    assert.strictEqual(str, expectedStr, '`Context#addStr` received bad match for `remainingContent`. Expected: "' + expectedStr + '", Actual: "' + str + '"');

    // Update remaining and completed content
    this.remainingContent = this.remainingContent.slice(str.length);
    this.completedContent += str;
  },

  // Retrieve a token from our context
  getToken: function (key) {
    var val = pathval.get(this.tokensByName, key);
    return val;
  },

  // Update a token on the context
  setToken: function (key, val) {
    // Assert the token is not yet set
    // DEV: Even if they are the same, we are missing an optimization by reusing that knowledge
    var currentVal = this.getToken(key);
    assert.strictEqual(currentVal, undefined, '`Context#setToken` expected `tokensByName["' + key + '"]` to not be defined but it was.');

    // Set the value (including nested paths, `hello.world`)
    pathval.set(this.tokensByName, key, val);
  },

  // Duplicate context (useful for exploring potential subcases)
  clone: function () {
    var context = new Context(this.originalContent);
    context.remainingContent = this.remainingContent;
    context.completedContent = this.completedContent;
    context.tokensByName = deepClone(this.tokensByName);
    return context;
  },

  // Add results from another context (e.g. subcases)
  addContext: function (context) {
    // Assert the context is not null
    assert(context, '`Context#addContext` received `null`, this is probably due to a bad check of `result` being truthy for inheritance.');

    // Add the content
    this.addStr(context.completedContent);

    // Copy over tokens
    var tokenNames = Object.getOwnPropertyNames(context.tokensByName);
    var i = 0;
    var len = tokenNames.length;
    for (; i < len; i++) {
      var tokenName = tokenNames[i];
      this.setToken(tokenName, context.tokensByName[tokenName]);
    }
  }
};

// TODO: Should this be a new method of a mustache template?
function reverseMustache(params) {
  // Make assertions about parameters
  assert(params, '`reverseMustache` expects `params` to be provided');
  assert(params.template, '`reverseMustache` expects `params.template` to be provided');
  assert(params.content, '`reverseMustache` expects `params.content` to be provided');

  // Interpret and localize parameters
  var ast = mustache.parse(params.template);
  var content = params.content;

  // Define recursive function to perform depth-first traversal
  function dfs(context, tokens) {
    var token = tokens[0];

    // TODO: Figure out how the hell to deal with subpaths (maybe getval, setval)
    // TODO: We are going to need to use sub-tokensByName as with mustache. Maybe re-use their lookup tooling?

    // If there are no tokens left, return context
    // TODO: Is this the right thing to do?
    if (!token) {
      return context;
    }

    // Depending on the type of the token
    var type = token[0];
    var name = token[1];
    switch (type) {
      case '#': // If/loop
        // If we already have the value
        var subtokens = token[4];
        var subcontext = new Context(context.remainingContent);
        var val = context.getToken(name);
        if (val !== undefined) {
          // Explore the tokens within our conditional
          // TODO: This should be done by a method
          // DEV: Both `subcontext` and `proposedContext` already have the value set because they inherit it during cloning
          subcontext.tokensByName = deepClone(context.tokensByName);
          var subresultContext = dfs(subcontext, subtokens);

          // If we had no matches, return `null`
          if (!subresultContext) {
            return null;
          }

          // Otherwise, set up a context that uses our subcontext result
          var proposedContext = context.clone();
          proposedContext.addStr(subresultContext.completedContent);

          // Attempt to use it and result the result
          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }

        // TODO: `parentContext` only makes sense for the looping case
        // TODO: Every other `dfs` traversal should be using the same context as the original (but with a clone to prevent pollution)

        // Match internal content as many times as possible
        var subresultArr = [];
        while (true) {
          // Break the context reference to avoid alteration to stored result
          subcontext = subcontext.clone();

          // Attempt to match the content again
          // DEV: We do not set the variable value here to allow inner-loops to extend as far as they can then cut back when forcing values later on
          // TODO: Don't forget that this runs in its own sub-object context (yey)
          var subresultContext = dfs(subcontext, subtokens);

          // If we could not match, stop looping
          if (!subresultContext) {
            break;
          }

          // Otherwise, save the subresult context
          subresultArr.push(subresultContext);
          subcontext = subresultContext;
        }

        // If the content matched, attempt to save our boolean as true
        // DEV: We make this as an attempt because future content could be invalid
        if (subresultArr.length) {
          // Attempt to use the entire array but progressively backoff
          var i = subresultArr.length;
          for (; i > 0; i -= 1) {
            // If we are on one value, use `true` (lowest common denominator)
            var proposedContext = context.clone();
            var proposedVal = true;
            // Otherwise, go to an array of `true's`
            // TODO: We are currently creating a placeholder array of `true` values. Please stop doing this.
            if (i > 1) {
              proposedVal = subresultArr.slice(0, i).map(function () { return true; });
            }
            proposedContext.setToken(name, proposedVal);
            proposedContext.addStr(subresultArr[i - 1].completedContent);

            // Explore the tokens within our conditional
            var resultContext = dfs(proposedContext, tokens.slice(1));

            // If we matched
            if (resultContext) {
              // Accept proposed/result context
              return resultContext;
            }
          }

          // Otherwise, continue to false
        }

        // Mark the boolean as false
        // DEV: This will fail on future steps if it is not `false` either
        var proposedContext = context.clone();
        proposedContext.setToken(name, false);
        var resultContext = dfs(proposedContext, tokens.slice(1));
        return resultContext;
      case '&': // Unescaped variable
      case 'name': // Escaped variable
        // If the variable is already defined
        var val = context.getToken(name);
        if (val !== undefined) {
          // If the remaining content is longer than what we have, reject it
          if (val.length > context.remainingContent.length) {
            return null;
          }

          // Otherwise, attempt to ues it
          var proposedContext = context.clone();
          proposedContext.addStr(val);
          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }

        // Looping from the entire remaining string to the first character (varying width, fixed at the first character)
        // DEV: This is a greedy regular expression. yey.
        var remainingContent = context.remainingContent;
        var i = remainingContent.length;
        for (; i >= 0; i -= 1) {
          // Attempt to use the substring as a match
          // TODO: There can probably be optimizations for trimming left content (a la quicksort) or even better regexp engines
          var proposedContext = context.clone();
          var proposedStr = remainingContent.slice(0, i);

          // Unescape any escaped variabled
          // https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L506-L509
          var proposedTokenVal = proposedStr;
          if (type === 'name') {
            proposedTokenVal = reverseMustache.unescapeHtml(proposedStr);
          }
          proposedContext.setToken(name, proposedTokenVal);

          // Continue with the match attempt
          proposedContext.addStr(proposedStr);
          var resultContext = dfs(proposedContext, tokens.slice(1));

          // DEV: If we ever do a `matchAll` method, this will make every other action run `n` times (i.e. n^m runtime, n = characters, m = decisions)
          // If we matched successfully, use the result context
          if (resultContext) {
            return resultContext;
          // Otherwise, continue
          } else {
            continue;
          }
        }

        // If there were no matches, return nothing
        return null;
      case 'text': // Text
        // Slice the next snippet of text
        var expectedText = name;
        var actualText = context.remainingContent.slice(0, expectedText.length);

        // If it does not match, reject it
        if (actualText !== expectedText) {
          // TODO: This shouldn't be a return but walk back in `for` loop for previous decisions (e.g. `true` over `false`)
          return null;
        // Otherwise, attempt to use it and pass it through
        } else {
          var proposedContext = context.clone();
          proposedContext.addStr(expectedText);

          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }
        break;
      default:
        throw new Error('`reverseMustache` did not recognize type "' + type + '" for token ' + JSON.stringify(token));
    }
  }

  // Run our depth first traversal
  var context = new Context(content);
  var result = dfs(context, ast);

  // If there was a result and there is remaining content, return negatively
  if (result && result.remainingContent !== '') {
    return null;
  // Otherwise, return the result
  } else {
    // TODO: Move to `tokensByName` in a major release
    return result;
  }
}

// Export `reverseMustache` and `unescapeHtml`
reverseMustache.unescapeHtml = unescapeHtml;
module.exports = reverseMustache;
<commit_msg>Looks like we lack inheritance for loops<commit_after>// Load in library dependencies
var assert = require('assert');
var mustache = require('mustache');
var pathval = require('pathval');
var deepClone = require('clone');

// DEV: This is not a perfect backtrack engine, I wonder if this is more/less performant that one.
// DEV: If we were to use regular expressions, we would lose accuracy of re-using variable values (and potentially have contradictions)
// TODO: Can we break each of these recursive items into its own function? I ask because the function is getting large.

// Reverse HTML escapes
// https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L53-L66
var reverseEntityMap = {
  '&': '&',
  '<': '<',
  '>': '>',
  '"': '"',
  ''': '\'',
  '/': '/'
};
var reverseEntityRegExp = /(&|<|>|"|'|/)/g;
function unescapeHtml(string) {
  return String(string).replace(reverseEntityRegExp, function (s) {
    return reverseEntityMap[s];
  });
}

// TODO: Instead of deep clone, consider prototypal lookup for tokens
function Context(content) {
  // Set up content and token internals
  this.originalContent = content;
  this.remainingContent = content || '';
  this.completedContent = '';
  this.tokensByName = {};
}
Context.prototype = {
  // Add a new string to the context
  addStr: function (str) {
    // Assert we are not adding in content that is unwanted
    var expectedStr = this.remainingContent.slice(0, str.length);
    assert.strictEqual(str, expectedStr, '`Context#addStr` received bad match for `remainingContent`. Expected: "' + expectedStr + '", Actual: "' + str + '"');

    // Update remaining and completed content
    this.remainingContent = this.remainingContent.slice(str.length);
    this.completedContent += str;
  },

  // Retrieve a token from our context
  getToken: function (key) {
    var val = pathval.get(this.tokensByName, key);
    console.log('lookup', this.tokensByName, key, val);
    return val;
  },

  // Update a token on the context
  setToken: function (key, val) {
    // Assert the token is not yet set
    // DEV: Even if they are the same, we are missing an optimization by reusing that knowledge
    var currentVal = this.getToken(key);
    assert.strictEqual(currentVal, undefined, '`Context#setToken` expected `tokensByName["' + key + '"]` to not be defined but it was.');

    // Set the value (including nested paths, `hello.world`)
    console.log('setting', currentVal, key, val);
    pathval.set(this.tokensByName, key, val);
  },

  // Duplicate context (useful for exploring potential subcases)
  clone: function () {
    var context = new Context(this.originalContent);
    context.remainingContent = this.remainingContent;
    context.completedContent = this.completedContent;
    context.tokensByName = deepClone(this.tokensByName);
    return context;
  },

  // Add results from another context (e.g. subcases)
  addContext: function (context) {
    // Assert the context is not null
    assert(context, '`Context#addContext` received `null`, this is probably due to a bad check of `result` being truthy for inheritance.');

    // Add the content
    this.addStr(context.completedContent);

    // Copy over tokens
    var tokenNames = Object.getOwnPropertyNames(context.tokensByName);
    var i = 0;
    var len = tokenNames.length;
    for (; i < len; i++) {
      var tokenName = tokenNames[i];
      this.setToken(tokenName, context.tokensByName[tokenName]);
    }
  }
};

// TODO: Should this be a new method of a mustache template?
function reverseMustache(params) {
  // Make assertions about parameters
  assert(params, '`reverseMustache` expects `params` to be provided');
  assert(params.template, '`reverseMustache` expects `params.template` to be provided');
  assert(params.content, '`reverseMustache` expects `params.content` to be provided');

  // Interpret and localize parameters
  var ast = mustache.parse(params.template);
  var content = params.content;

  // Define recursive function to perform depth-first traversal
  function dfs(context, tokens) {
    var token = tokens[0];

    // TODO: Figure out how the hell to deal with subpaths (maybe getval, setval)
    // TODO: We are going to need to use sub-tokensByName as with mustache. Maybe re-use their lookup tooling?

    // If there are no tokens left, return context
    // TODO: Is this the right thing to do?
    if (!token) {
      return context;
    }

    // Depending on the type of the token
    var type = token[0];
    var name = token[1];
    switch (type) {
      case '#': // If/loop
        // If we already have the value
        var subtokens = token[4];
        var subcontext = new Context(context.remainingContent);
        var val = context.getToken(name);
        if (val !== undefined) {
          // Explore the tokens within our conditional
          // TODO: This should be done by a method
          // DEV: Both `subcontext` and `proposedContext` already have the value set because they inherit it during cloning
          subcontext.tokensByName = deepClone(context.tokensByName);
          var subresultContext = dfs(subcontext, subtokens);

          // If we had no matches, return `null`
          if (!subresultContext) {
            return null;
          }

          // Otherwise, set up a context that uses our subcontext result
          var proposedContext = context.clone();
          proposedContext.addStr(subresultContext.completedContent);

          // Attempt to use it and result the result
          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }

        // TODO: `parentContext` only makes sense for the looping case
        // TODO: Every other `dfs` traversal should be using the same context as the original (but with a clone to prevent pollution)

        // Match internal content as many times as possible
        var subresultArr = [];
        while (true) {
          // Break the context reference to avoid alteration to stored result
          subcontext = subcontext.clone();

          // Attempt to match the content again
          // DEV: We do not set the variable value here to allow inner-loops to extend as far as they can then cut back when forcing values later on
          // TODO: Don't forget that this runs in its own sub-object context (yey)
          var subresultContext = dfs(subcontext, subtokens);

          // If we could not match, stop looping
          if (!subresultContext) {
            break;
          }

          // Otherwise, save the subresult context
          subresultArr.push(subresultContext);
          subcontext = subresultContext;
        }

        // If the content matched, attempt to save our boolean as true
        // DEV: We make this as an attempt because future content could be invalid
        if (subresultArr.length) {
          // Attempt to use the entire array but progressively backoff
          var i = subresultArr.length;
          for (; i > 0; i -= 1) {
            // If we are on one value, use `true` (lowest common denominator)
            var proposedContext = context.clone();
            var proposedVal = true;
            // Otherwise, go to an array of `true's`
            // TODO: We are currently creating a placeholder array of `true` values. Please stop doing this.
            if (i > 1) {
              proposedVal = subresultArr.slice(0, i).map(function () { return true; });
            }
            proposedContext.setToken(name, proposedVal);
            proposedContext.addStr(subresultArr[i - 1].completedContent);

            // Explore the tokens within our conditional
            var resultContext = dfs(proposedContext, tokens.slice(1));

            // If we matched
            if (resultContext) {
              // Accept proposed/result context
              return resultContext;
            }
          }

          // Otherwise, continue to false
        }

        // Mark the boolean as false
        // DEV: This will fail on future steps if it is not `false` either
        var proposedContext = context.clone();
        proposedContext.setToken(name, false);
        var resultContext = dfs(proposedContext, tokens.slice(1));
        return resultContext;
      case '&': // Unescaped variable
      case 'name': // Escaped variable
        // If the variable is already defined
        var val = context.getToken(name);
        if (val !== undefined) {
          // If the remaining content is longer than what we have, reject it
          if (val.length > context.remainingContent.length) {
            return null;
          }

          // Otherwise, attempt to ues it
          var proposedContext = context.clone();
          proposedContext.addStr(val);
          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }

        // Looping from the entire remaining string to the first character (varying width, fixed at the first character)
        // DEV: This is a greedy regular expression. yey.
        var remainingContent = context.remainingContent;
        var i = remainingContent.length;
        for (; i >= 0; i -= 1) {
          // Attempt to use the substring as a match
          // TODO: There can probably be optimizations for trimming left content (a la quicksort) or even better regexp engines
          var proposedContext = context.clone();
          var proposedStr = remainingContent.slice(0, i);

          // Unescape any escaped variabled
          // https://github.com/janl/mustache.js/blob/0.8.1/mustache.js#L506-L509
          var proposedTokenVal = proposedStr;
          if (type === 'name') {
            proposedTokenVal = reverseMustache.unescapeHtml(proposedStr);
          }
          proposedContext.setToken(name, proposedTokenVal);

          // Continue with the match attempt
          proposedContext.addStr(proposedStr);
          var resultContext = dfs(proposedContext, tokens.slice(1));

          // DEV: If we ever do a `matchAll` method, this will make every other action run `n` times (i.e. n^m runtime, n = characters, m = decisions)
          // If we matched successfully, use the result context
          if (resultContext) {
            return resultContext;
          // Otherwise, continue
          } else {
            continue;
          }
        }

        // If there were no matches, return nothing
        return null;
      case 'text': // Text
        // Slice the next snippet of text
        var expectedText = name;
        var actualText = context.remainingContent.slice(0, expectedText.length);

        // If it does not match, reject it
        if (actualText !== expectedText) {
          // TODO: This shouldn't be a return but walk back in `for` loop for previous decisions (e.g. `true` over `false`)
          return null;
        // Otherwise, attempt to use it and pass it through
        } else {
          var proposedContext = context.clone();
          proposedContext.addStr(expectedText);

          var resultContext = dfs(proposedContext, tokens.slice(1));
          return resultContext;
        }
        break;
      default:
        throw new Error('`reverseMustache` did not recognize type "' + type + '" for token ' + JSON.stringify(token));
    }
  }

  // Run our depth first traversal
  var context = new Context(content);
  var result = dfs(context, ast);

  // If there was a result and there is remaining content, return negatively
  if (result && result.remainingContent !== '') {
    return null;
  // Otherwise, return the result
  } else {
    // TODO: Move to `tokensByName` in a major release
    return result;
  }
}

// Export `reverseMustache` and `unescapeHtml`
reverseMustache.unescapeHtml = unescapeHtml;
module.exports = reverseMustache;
<|endoftext|>


<commit_before>/********************************************************
 * Copyright (c) 2013 Azavea, Inc.
 * See LICENSE in the project root for copying permission
 * Module for working with starred locations
 *  add/remove locations and add
 *      listeners for sharing/clearing/toggling favorites
 *********************************************************/

define(['jquery', 'cel-cookie', 'common', 'jquery-cookie'], function($, celcookie, common) {

    'use strict';

    var favs = {

        /*
         * Store a reference here so we don't have to pass it around
         */
        cookie: celcookie,

        /*
         * Get the favorites cookie
         */
        getCookie: function() {
            return $.cookie(favs.cookie.name) || "";
        },

        /*
         * Input: string id
         * Output: true if input in cookie, else false
         */
        isStarred: function(id) {
            if (!id) {
                return false;
            }
            id = parseInt(id, 10);
            var cookie = favs.getCookie(),
                idArray = cookie.split(',').map(function(x) { return parseInt(x, 10); });
            return (idArray.indexOf(id) >= 0);
        },

        /*
         * Given a location id, adds the location id to the correct cookie
         */
        addIdToCookie: function(id) {
            if (!id) {
                return;
            }

            var cookie = favs.cookie,
                cookieString = favs.getCookie(),
                idArray,
                arrayLen,
                idExists;

            if (cookieString) {
                idArray = cookieString.split(',');
                arrayLen = idArray.length;
                idExists = false;

                // only add id if it doesn't exist in the cookie already
                for (var i = 0; i < arrayLen; i++) {
                    if (id == idArray[i]) {
                        idExists = true;
                        break;
                    }
                }
                if (!idExists) {
                    idArray.push(id);
                }

                idArray = $.grep(idArray, function (x) { return x !== ""; });
                cookieString = idArray.join(',');
            } else {
                cookieString = id;
            }

            $.cookie(cookie.name, cookieString, cookie.options);
        },

        /*
         * Given a location id, removes the location id from the correct cookie
         */
        removeIdFromCookie: function(id) {
            if (!id) {
                return;
            }
            var cookie = favs.cookie,
                cookieString = favs.getCookie(),
                idArray,
                arrayLen,
                currentId;

            if (!cookieString) {
                return;
            }

            idArray = cookieString.split(',');
            arrayLen = idArray.length;

            for (var i = 0; i < arrayLen; i++) {
                currentId = idArray[i];
                if (id == currentId) {
                    idArray.splice(i, 1);
                }
            }

            cookieString = idArray.join(',');
            $.cookie(cookie.name, cookieString, cookie.options);
        },

        /*
         * Sync the view with a set cookie on page load
         *
         * Properly sets buttons for all starred locations
         */
        syncUI: function(options) {
            var defaults = {
                    button: '#faves-clear',
                    countspan: '#fav-count',
                    container: '.container'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                cookie = favs.getCookie(),
                starredIds,
                starredIdsLength,
                starredId;

            starredIds = cookie ? cookie.split(',') : [];
            starredIdsLength = starredIds.length;
            starredId = 0;

            // toggle any buttons for starred locations to on state
            for (var i = 0; i < starredIdsLength; i++) {
                starredId = starredIds[i];
                favs.toggle($('#favs-toggle-loc-' + starredId));
            }
            $('.fav-count').html(starredIdsLength);
        },

        /*
         * Toggles the state of whatever element is passed to it,
         *      calling the add/removeIdFromCookie functions to ensure the cookie remains in sync
         *
         * Optional options argument allows for changing some of the default settings
         */
        toggle: function($elt, options) {
            var defaults = {
                    imgpath: '/static/img/icons/',
                    idAttribute: 'data-loc-id',
                    selectedClass: 'favs-button-selected'
                },
                opts = $.extend({}, defaults, options),
                buttonImg = $elt.children('i'),
                buttonId = $elt.attr(opts.idAttribute),
                img = '',
                increment = 0,
                $favbutton;

            // toggle off
            if ($elt.hasClass(opts.selectedClass)) {
                img = 'icon-star-empty';
                favs.removeIdFromCookie(buttonId);
                increment = -1;
                $elt.attr('data-hint', gettext('Click to star location'));
            // toggle on
            } else {
                img = 'icon-star';
                favs.addIdToCookie(buttonId);
                increment = 1;
                $elt.attr('data-hint', gettext('Click to remove star from location'));
            }

            buttonImg.attr('class', img);
            $elt.toggleClass(opts.selectedClass);
            $favbutton = $('.fav-count');
            $favbutton.html(parseInt($favbutton.html(), 10) + increment);
        },

        /*
         * Adds a click listener for toggling a favorite on/off to the button specified
         *      in the options object.
         */
        addToggleListener: function(options) {
            var defaults = {
                    button: '.faves-add'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                button = $(opts.button);

            // toggle the button/cookie state
            button.on('click', function(e) {
                self.toggle($(this));
            });
        },

        /*
         * Adds a click listener to the specified selector for clearing all favorites
         *      from the cookie.
         */
        addClearListener: function(options) {
            var defaults = {
                    button: '#faves-clear',
                    countspan: '.fav-count',
                    container: '.container'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                clearbutton = $(opts.button);

            clearbutton.on('click', function(e) {
                $(opts.countspan).html('0');
                $(opts.container).empty();
                $.removeCookie(self.cookie.name, self.cookie.options);
            });
        },

        /*
         * Initializes a share modal with the stored favorites information
         */
        initShareModal: function() {
            var ids = favs.getCookie(),
                count = ids ? ids.split(',').length : 0;

            $('#share-modal').trigger('init-modal', {
                // the url is passed in to the sharing urls, so it must be absolute
                url: document.location.origin + common.getUrl('starred', { locations: ids }),
                title: 'I just starred ' + count + ' locations'
            });
        },

        /*
         * Adds a click listener to the specified selector for sharing current favorites.
         */
        addShareListener: function(options) {
            var defaults = {
                    button: '#faves-share'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                sharebutton = $(opts.button);

            sharebutton.on('click', favs.initShareModal);
        }
    };
    return favs;
});
<commit_msg>Fix undefined functions in IE8<commit_after>/********************************************************
 * Copyright (c) 2013 Azavea, Inc.
 * See LICENSE in the project root for copying permission
 * Module for working with starred locations
 *  add/remove locations and add
 *      listeners for sharing/clearing/toggling favorites
 *********************************************************/

define(['jquery', 'cel-cookie', 'common', 'jquery-cookie'], function($, celcookie, common) {

    'use strict';

    var favs = {

        /*
         * Store a reference here so we don't have to pass it around
         */
        cookie: celcookie,

        /*
         * Get the favorites cookie
         */
        getCookie: function() {
            return $.cookie(favs.cookie.name) || "";
        },

        /*
         * Input: string id
         * Output: true if input in cookie, else false
         */
        isStarred: function(id) {
            if (!id) {
                return false;
            }
            id = parseInt(id, 10);
            var cookie = favs.getCookie(),
                idArray = $.map(cookie.split(','), function(x) { return parseInt(x, 10); });
            return ($.inArray(id, idArray) >= 0);
        },

        /*
         * Given a location id, adds the location id to the correct cookie
         */
        addIdToCookie: function(id) {
            if (!id) {
                return;
            }

            var cookie = favs.cookie,
                cookieString = favs.getCookie(),
                idArray,
                arrayLen,
                idExists;

            if (cookieString) {
                idArray = cookieString.split(',');
                arrayLen = idArray.length;
                idExists = false;

                // only add id if it doesn't exist in the cookie already
                for (var i = 0; i < arrayLen; i++) {
                    if (id == idArray[i]) {
                        idExists = true;
                        break;
                    }
                }
                if (!idExists) {
                    idArray.push(id);
                }

                idArray = $.grep(idArray, function (x) { return x !== ""; });
                cookieString = idArray.join(',');
            } else {
                cookieString = id;
            }

            $.cookie(cookie.name, cookieString, cookie.options);
        },

        /*
         * Given a location id, removes the location id from the correct cookie
         */
        removeIdFromCookie: function(id) {
            if (!id) {
                return;
            }
            var cookie = favs.cookie,
                cookieString = favs.getCookie(),
                idArray,
                arrayLen,
                currentId;

            if (!cookieString) {
                return;
            }

            idArray = cookieString.split(',');
            arrayLen = idArray.length;

            for (var i = 0; i < arrayLen; i++) {
                currentId = idArray[i];
                if (id == currentId) {
                    idArray.splice(i, 1);
                }
            }

            cookieString = idArray.join(',');
            $.cookie(cookie.name, cookieString, cookie.options);
        },

        /*
         * Sync the view with a set cookie on page load
         *
         * Properly sets buttons for all starred locations
         */
        syncUI: function(options) {
            var defaults = {
                    button: '#faves-clear',
                    countspan: '#fav-count',
                    container: '.container'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                cookie = favs.getCookie(),
                starredIds,
                starredIdsLength,
                starredId;

            starredIds = cookie ? cookie.split(',') : [];
            starredIdsLength = starredIds.length;
            starredId = 0;

            // toggle any buttons for starred locations to on state
            for (var i = 0; i < starredIdsLength; i++) {
                starredId = starredIds[i];
                favs.toggle($('#favs-toggle-loc-' + starredId));
            }
            $('.fav-count').html(starredIdsLength);
        },

        /*
         * Toggles the state of whatever element is passed to it,
         *      calling the add/removeIdFromCookie functions to ensure the cookie remains in sync
         *
         * Optional options argument allows for changing some of the default settings
         */
        toggle: function($elt, options) {
            var defaults = {
                    imgpath: '/static/img/icons/',
                    idAttribute: 'data-loc-id',
                    selectedClass: 'favs-button-selected'
                },
                opts = $.extend({}, defaults, options),
                buttonImg = $elt.children('i'),
                buttonId = $elt.attr(opts.idAttribute),
                img = '',
                increment = 0,
                $favbutton;

            // toggle off
            if ($elt.hasClass(opts.selectedClass)) {
                img = 'icon-star-empty';
                favs.removeIdFromCookie(buttonId);
                increment = -1;
                $elt.attr('data-hint', gettext('Click to star location'));
            // toggle on
            } else {
                img = 'icon-star';
                favs.addIdToCookie(buttonId);
                increment = 1;
                $elt.attr('data-hint', gettext('Click to remove star from location'));
            }

            buttonImg.attr('class', img);
            $elt.toggleClass(opts.selectedClass);
            $favbutton = $('.fav-count');
            $favbutton.html(parseInt($favbutton.html(), 10) + increment);
        },

        /*
         * Adds a click listener for toggling a favorite on/off to the button specified
         *      in the options object.
         */
        addToggleListener: function(options) {
            var defaults = {
                    button: '.faves-add'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                button = $(opts.button);

            // toggle the button/cookie state
            button.on('click', function(e) {
                self.toggle($(this));
            });
        },

        /*
         * Adds a click listener to the specified selector for clearing all favorites
         *      from the cookie.
         */
        addClearListener: function(options) {
            var defaults = {
                    button: '#faves-clear',
                    countspan: '.fav-count',
                    container: '.container'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                clearbutton = $(opts.button);

            clearbutton.on('click', function(e) {
                $(opts.countspan).html('0');
                $(opts.container).empty();
                $.removeCookie(self.cookie.name, self.cookie.options);
            });
        },

        /*
         * Initializes a share modal with the stored favorites information
         */
        initShareModal: function() {
            var ids = favs.getCookie(),
                count = ids ? ids.split(',').length : 0;

            $('#share-modal').trigger('init-modal', {
                // the url is passed in to the sharing urls, so it must be absolute
                url: document.location.origin + common.getUrl('starred', { locations: ids }),
                title: 'I just starred ' + count + ' locations'
            });
        },

        /*
         * Adds a click listener to the specified selector for sharing current favorites.
         */
        addShareListener: function(options) {
            var defaults = {
                    button: '#faves-share'
                },
                opts = $.extend({}, defaults, options),
                self = favs,
                sharebutton = $(opts.button);

            sharebutton.on('click', favs.initShareModal);
        }
    };
    return favs;
});
<|endoftext|>


<commit_before>var vm = require('vm')
var isBuffer = Buffer.isBuffer

var requireLike = require('require-like')

function merge (a, b) {
  if (!a || !b) return a
  // Include all non-enumerable variables, including console (v10+),
  // process (v12+), URL, etc.
  var keys = Object.getOwnPropertyNames(b)
  for (var k, i = 0, n = keys.length; i < n; i++) {
    k = keys[i]
    a[k] = b[k]
  }
  return a
}

// Return the exports/module.exports variable set in the content
// content (String|VmScript): required
module.exports = function (content, filename, scope, includeGlobals) {

  if (typeof filename !== 'string') {
    if (typeof filename === 'object') {
      includeGlobals = scope
      scope = filename
      filename = ''
    } else if (typeof filename === 'boolean') {
      includeGlobals = filename
      scope = {}
      filename = ''
    }
  }

  // Expose standard Node globals
  var sandbox = {}
  var exports = {}
  var _filename = filename || module.parent.filename;

  if (includeGlobals) {
    merge(sandbox, global)
    sandbox.require = requireLike(_filename)
  }

  if (typeof scope === 'object') {
    merge(sandbox, scope)
  }

  sandbox.exports = exports
  sandbox.module = {
    exports: exports,
    filename: _filename,
    id: _filename,
    parent: module.parent,
    require: sandbox.require || requireLike(_filename)
  }
  sandbox.global = sandbox

  var options = {
    filename: filename,
    displayErrors: false
  }

  if (isBuffer(content)) {
    content = content.toString()
  }

  // Evalutate the content with the given scope
  if (typeof content === 'string') {
    var stringScript = content.replace(/^\#\!.*/, '')
    var script = new vm.Script(stringScript, options)
    script.runInNewContext(sandbox, options)
  } else {
    content.runInNewContext(sandbox, options)
  }

  return sandbox.module.exports
}
<commit_msg>fix: only merge non-enumerable global properties that are not in VM scope<commit_after>var vm = require('vm')
var isBuffer = Buffer.isBuffer

var requireLike = require('require-like')

function merge (a, b) {
  if (!a || !b) return a
  var keys = Object.keys(b)
  for (var k, i = 0, n = keys.length; i < n; i++) {
    k = keys[i]
    a[k] = b[k]
  }
  return a
}

// Return the exports/module.exports variable set in the content
// content (String|VmScript): required
module.exports = function (content, filename, scope, includeGlobals) {

  if (typeof filename !== 'string') {
    if (typeof filename === 'object') {
      includeGlobals = scope
      scope = filename
      filename = ''
    } else if (typeof filename === 'boolean') {
      includeGlobals = filename
      scope = {}
      filename = ''
    }
  }

  // Expose standard Node globals
  var sandbox = {}
  var exports = {}
  var _filename = filename || module.parent.filename;

  if (includeGlobals) {
    // Merge enumerable variables on `global`
    merge(sandbox, global)
    // Merge all non-enumerable variables on `global`, including console (v10+),
    // process (v12+), URL, etc. We first filter out anything that's already in
    // the VM scope (i.e. those in the ES standard) so we don't have two copies
    var vmGlobals = Object.getOwnPropertyNames(
      new vm.Script("vmGlobals = globalThis").runInNewContext().vmGlobals
    )
    Object.getOwnPropertyNames(global).forEach((name) => {
      if (!vmGlobals.includes(name)) {
        sandbox[name] = global[name]
      }
    })
    sandbox.require = requireLike(_filename)
  }

  if (typeof scope === 'object') {
    merge(sandbox, scope)
  }

  sandbox.exports = exports
  sandbox.module = {
    exports: exports,
    filename: _filename,
    id: _filename,
    parent: module.parent,
    require: sandbox.require || requireLike(_filename)
  }
  sandbox.global = sandbox

  var options = {
    filename: filename,
    displayErrors: false
  }

  if (isBuffer(content)) {
    content = content.toString()
  }

  // Evalutate the content with the given scope
  if (typeof content === 'string') {
    var stringScript = content.replace(/^\#\!.*/, '')
    var script = new vm.Script(stringScript, options)
    script.runInNewContext(sandbox, options)
  } else {
    content.runInNewContext(sandbox, options)
  }

  return sandbox.module.exports
}
<|endoftext|>


<commit_before>
function quote_desugar(exp) {
    if (pairp(exp)) {
        return list(intern("cons"), quote_desugar(exp.car), quote_desugar(exp.cdr));
    } else if (exp == null) {
        return list(intern("quote"), null);
    }else if(constantp(exp)) {
        return exp;
    } else {
        return list(intern("quote"), exp);
    }
}

function quasiquote_desugar(exp) {
    if (pairp(exp)) {
        return exp.car === intern("unquote") ?
            desugar(exp.cdr.car) :
            list(intern("cons"), quasiquote_desugar(exp.car), quasiquote_desugar(exp.cdr));
    } else {
        return desugar(list(intern("quote"), exp));
    }
}

function desugar(exp) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            if(pairp(exp.cdr.car)) {
                return list(exp.car, exp.cdr.car.car, cons(intern("rel"), cons(exp.cdr.car.cdr, desugar(exp.cdr.cdr))));
            } else {
                return list(exp.car, exp.cdr.car, desugar(exp.cdr.cdr.car));
            }
        case intern("quote"): return quote_desugar(exp.cdr.car);
        case intern("quasiquote"): return quasiquote_desugar(exp.cdr.car);
        case intern("conde"):
            var clauses = map(function(row) { return cons(intern("conj"), row); }, exp.cdr);
            return desugar(cons(intern("disj"), clauses));
        default: return cons(desugar(exp.car), desugar(exp.cdr));
        }
    } else {
        return exp;
    }
}

function lookup(x, xs) {
    while(xs != null) {
        if (x.string === xs.car.car.string) { return xs.car.cdr; }
        else { xs = xs.cdr; }
    } return false;
}

function frees(exp, env, fenv) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            var a = exp.cdr.car;
            toplevel[a.string] = null;
            // TODO: free variables in definitions?
            return list(exp.car, exp.cdr.car, frees(exp.cdr.cdr.car, cons(cons(a, a), null), null));
        case intern("quote"): return exp;
        case intern("cons"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("=="):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("conj"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("disj"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("rel"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            var e1 = foldl(bindings, env, function(e, a) { return cons(cons(a, a), e); });
            return cons(exp.car, cons(bindings, map(function(x) { return frees(x, e1, fenv); }, body)));
        case intern("fresh"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            var e1 = foldl(bindings, env, function(e, a) { return cons(cons(a, a), e); });
            return cons(exp.car, cons(bindings, map(function(x) { return frees(x, e1, fenv); }, body)));
        default:
            return map(function(x) { return frees(x, env, fenv); }, exp);
        }
    } else if(constantp(exp)) {
        return exp;
    } else if(symbolp(exp)) {
        if (lookup(exp, env) || toplevel.hasOwnProperty(exp.string)) { return exp; }
        var v = lookup(exp, fenv.get());
        if (v) {
            return v;
        } else {
            var gen = gensym(exp.string);
            fenv.set(cons(cons(exp, gen), fenv.get()));
            return gen;
        }
    } else {
        throw "unkown exp: " + exp;
    }
}

// returns list(exp, env, s_c)
function lift_frees(exp) {
    var free_env = ref(null);
    var exp1 = frees(exp, null, free_env);
    var e1_c1 = foldl(free_env.get(), cons(null, 0),
                      function(e_c, a) {
                          var var1 = mkvar(e_c.cdr);
                          return cons(cons(cons(a.cdr, function(_) { return var1; }), e_c.car), e_c.cdr+1); });
    return list(exp1, e1_c1.car, cons(null ,e1_c1.cdr));
}

function eval0(exp, env) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            var result = eval0(exp.cdr.cdr.car, env);
            toplevel[exp.cdr.car.string] = result;
            return function(cenv) { return unit; };
        case intern("quote"):
            return function(cenv) { return exp.cdr.car; };
        case intern("cons"):
            var e1 = eval0(exp.cdr.car, env);
            var e2 = eval0(exp.cdr.cdr.car, env);
            return function(cenv) { return cons(e1(cenv), e2(cenv)); };
        case intern("=="):
            var e1 = eval0(exp.cdr.car, env);
            var e2 = eval0(exp.cdr.cdr.car, env);
            return function(cenv) { return eqeq(e1(cenv), e2(cenv)); }
        case intern("conj"):
            if (exp.cdr == null) { throw "error: empty conj"; }
            else if (exp.cdr.cdr == null) {
                var e1 = eval0(exp.cdr.car, env);
                return function(cenv) { return function(s_c) { return function() { return e1(cenv)(s_c); }; }; };
            } else {
                var e1 = eval0(exp.cdr.car, env);
                var e2 = eval0(cons(intern("conj"), exp.cdr.cdr), env);
                return function(cenv) { return conj(function(s_c) { return function() { return e1(cenv)(s_c); }; },
                                                    e2(cenv)); };
            }
        case intern("disj"):
            if (exp.cdr == null) { throw "error: empty conj"; }
            else if (exp.cdr.cdr == null) {
                var e1 = eval0(exp.cdr.car, env);
                return function(cenv) {  return function(s_c) { return function() { return e1(cenv)(s_c); }; }; };
            } else {
                var e1 = eval0(exp.cdr.car, env);
                var e2 = eval0(cons(intern("disj"), exp.cdr.cdr), env);
                return function(cenv) { return disj(function(s_c) { return function() { return e1(cenv)(s_c); }; },
                                                    e2(cenv)); };
            }
        case intern("fresh"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            var body1 = eval0(cons(intern("rel"), exp.cdr), env);

            return function(cenv) {
                return function(s_c) {
                    // make fresh variables as argument list
                    var as_c1 = foldl(bindings, cons(null, s_c.cdr), function(as_c, a) {
                        return cons(cons(mkvar(as_c.cdr), as_c.car), as_c.cdr+1);
                    });

                    return body1(cenv)(as_c1.car)(cons(s_c.car, as_c1.cdr));
                };
            };
        case intern("rel"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            // we get the env by adding (var . offset) pairs to it
            var env1 = foldl(bindings, env,
                             function(e_c, a) {
                                 var offset = e_c.cdr;
                                 var var1 = function(cenv) { return cenv[offset]; }
                                 return cons(cons(cons(a, var1), e_c.car), offset+1); });
            // now we have an (env . cenv_size)
            // var env1 = e1_c1.car;
            var cenv_size = env1.cdr;
            var body1 = eval0(cons(intern("conj"), body), env1);
            // adds fresh variables
            return function(cenv) {
                return function(args) {
                    var fl = cenv.length;
                    // fl .. cenv_size-1 is the fresh variables
                    var m = new Array(cenv_size);
                    var i;
                    // copy parent env...
                    // todo: link environments instead of copy
                    for(i=0; i < fl; i++) {
                        m[i] = cenv[i];
                    }
                    // add arguments calll them with cenv??
                    map(function(a) { m[i++] = a; }, args);
                    return body1(m);
                };
            };
        default: // application
            var evald = map(function(e) { return eval0(e, env); }, exp);
            var fn = evald.car;
            var args = evald.cdr;
            return function(cenv) {
                return fn(cenv)(map(function(a) { return a(cenv); }, args));
            };
        }
    } else if(constantp(exp)) {
        return function(cenv) { return exp; };
    } else if(symbolp(exp)) {
        var v = lookup(exp, env.car);
        if (v) {
            return v;
        } else if(toplevel.hasOwnProperty(exp.string)) {
            var mbox = ref(function() {
                var cache = toplevel[exp.string];
                mbox.set(function() { return cache; })
                return cache;
            });
            return function(cenv) {
                var val = mbox.get()();
                return val(cenv); };
        } else {
            throw ["unbound variable: " + exp.string, env.car];
        }
    } else {
        throw "unkown exp: " + exp;
    }
}

function query(v, s) {
    var v1 = walk_star(v, s);
    return walk_star(v1, reify_s(v1, null));
}

function map_stream(fn, stream) {
    return function() {
        var $ = pull(stream);
        return ($ == null) ? null : cons(fn($.car), map_stream(fn, $.cdr));
    };
}

// take the starting state of the interpreter
// make a stream of each top-level variable queried from the result store
function query_stream(init) {
    var exp = init.car;
    var env = init.cdr.car;
    var s_c = init.cdr.cdr.car;
    var foo = eval0(exp, cons(env, 0))([]);
    var $ = foo(s_c);

    var run_queries = function(s_c) {
        var s = s_c.car;
        var record = new Object(null);
        map(function(x) { record[x.car.string] = query(x.cdr(),s); }, env);
        return record;
    };
    return map_stream(run_queries, $);
}

function stream_generator($) {
    var next = $;
    return function() {
        var cur = next();
        if(cur == null) { return null }
        else { next = cur.cdr;
               return cur.car; }
    };
}

var toplevel = new Object(null);
var vm_state = cons(null, mzero);

function run_expression(p) {
    var desugared = desugar(p);
    var lifted = lift_frees(desugared);
    var q$ = query_stream(lifted);
    return stream_generator(q$);
}

function run_program(p) {
    var stream;
    if(p == null) { throw "no program" }
    while(p != null) {
        stream = run_expression(p.car);
        p = p.cdr;
    } return stream;
}
<commit_msg>new closure scheme<commit_after>
function quote_desugar(exp) {
    if (pairp(exp)) {
        return list(intern("cons"), quote_desugar(exp.car), quote_desugar(exp.cdr));
    } else if (exp == null) {
        return list(intern("quote"), null);
    }else if(constantp(exp)) {
        return exp;
    } else {
        return list(intern("quote"), exp);
    }
}

function quasiquote_desugar(exp) {
    if (pairp(exp)) {
        return exp.car === intern("unquote") ?
            desugar(exp.cdr.car) :
            list(intern("cons"), quasiquote_desugar(exp.car), quasiquote_desugar(exp.cdr));
    } else {
        return desugar(list(intern("quote"), exp));
    }
}

function desugar(exp) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            if(pairp(exp.cdr.car)) {
                return list(exp.car, exp.cdr.car.car, cons(intern("rel"), cons(exp.cdr.car.cdr, desugar(exp.cdr.cdr))));
            } else {
                return list(exp.car, exp.cdr.car, desugar(exp.cdr.cdr.car));
            }
        case intern("quote"): return quote_desugar(exp.cdr.car);
        case intern("quasiquote"): return quasiquote_desugar(exp.cdr.car);
        case intern("conde"):
            var clauses = map(function(row) { return cons(intern("conj"), row); }, exp.cdr);
            return desugar(cons(intern("disj"), clauses));
        default: return cons(desugar(exp.car), desugar(exp.cdr));
        }
    } else {
        return exp;
    }
}

function lookup(x, xs) {
    while(xs != null) {
        if (x.string === xs.car.car.string) { return xs.car.cdr; }
        else { xs = xs.cdr; }
    } return false;
}
function frees(exp, env, fenv) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            var a = exp.cdr.car;
            toplevel[a.string] = null;
            // TODO: free variables in definitions?
            return list(exp.car, exp.cdr.car, frees(exp.cdr.cdr.car, cons(cons(a, a), null), null));
        case intern("quote"): return exp;
        case intern("cons"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("=="):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("conj"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("disj"):
            return cons(exp.car, map(function(x) { return frees(x, env, fenv); }, exp.cdr));
        case intern("rel"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            var e1 = foldl(bindings, env, function(e, a) { return cons(cons(a, a), e); });
            return cons(exp.car, cons(bindings, map(function(x) { return frees(x, e1, fenv); }, body)));
        case intern("fresh"):
            var bindings = exp.cdr.car;
            var body = exp.cdr.cdr;
            var e1 = foldl(bindings, env, function(e, a) { return cons(cons(a, a), e); });
            return cons(exp.car, cons(bindings, map(function(x) { return frees(x, e1, fenv); }, body)));
        default:
            return map(function(x) { return frees(x, env, fenv); }, exp);
        }
    } else if(constantp(exp)) {
        return exp;
    } else if(symbolp(exp)) {
        if (lookup(exp, env) || toplevel.hasOwnProperty(exp.string)) { return exp; }
        var v = lookup(exp, fenv.get());
        if (v) {
            return v;
        } else {
            var gen = gensym(exp.string);
            fenv.set(cons(cons(exp, gen), fenv.get()));
            return gen;
        }
    } else {
        throw "unkown exp: " + exp;
    }
}

// instead of returning a value, it returns a function that fetches a value
// based on the offset of how far it takes to reach the variable
// values in the env are expected to be :: offset -> cenv -> value
function lookup_calc(x, xs) {
    var n = 0;
    while(xs != null) {
        if (x.string === xs.car.car.string) { return xs.car.cdr(n); }
        else { n++; xs = xs.cdr; }
    } return false;
}
function augment_env(env, name) {
    var binding = cons(name, function (offset) { return function(cenv) { return cenv[offset]; } });
    return cons(binding, env);
}

// returns list(exp, env, s_c)
function lift_frees(exp) {
    var free_env = ref(null);
    var exp1 = frees(exp, null, free_env);
    var e1_c1 = foldl(free_env.get(), cons(null, 0),
                      function(e_c, a) {
                          var var1 = mkvar(e_c.cdr);
                          var retrieve = function(_) { return function(_) { return var1; }; };
                          return cons(cons(cons(a.cdr, retrieve), e_c.car), e_c.cdr+1); });
    return list(exp1, e1_c1.car, cons(null ,e1_c1.cdr));
}

function eval0(exp, env) {
    if(pairp(exp)) {
        switch(exp.car) {
        case intern("define"):
            var result = eval0(exp.cdr.cdr.car, env);
            toplevel[exp.cdr.car.string] = result;
            return function(cenv) { return unit; };
        case intern("quote"):
            return function(cenv) { return exp.cdr.car; };
        case intern("cons"):
            var e1 = eval0(exp.cdr.car, env);
            var e2 = eval0(exp.cdr.cdr.car, env);
            return function(cenv) { return cons(e1(cenv), e2(cenv)); };
        case intern("=="):
            var e1 = eval0(exp.cdr.car, env);
            var e2 = eval0(exp.cdr.cdr.car, env);
            return function(cenv) { return eqeq(e1(cenv), e2(cenv)); }
        case intern("conj"):
            if (exp.cdr == null) { throw "error: empty conj"; }
            else if (exp.cdr.cdr == null) {
                var e1 = eval0(exp.cdr.car, env);
                return function(cenv) { return function(s_c) { return function() { return e1(cenv)(s_c); }; }; };
            } else {
                var e1 = eval0(exp.cdr.car, env);
                var e2 = eval0(cons(intern("conj"), exp.cdr.cdr), env);
                return function(cenv) { return conj(function(s_c) { return function() { return e1(cenv)(s_c); }; },
                                                    e2(cenv)); };
            }
        case intern("disj"):
            if (exp.cdr == null) { throw "error: empty conj"; }
            else if (exp.cdr.cdr == null) {
                var e1 = eval0(exp.cdr.car, env);
                return function(cenv) {  return function(s_c) { return function() { return e1(cenv)(s_c); }; }; };
            } else {
                var e1 = eval0(exp.cdr.car, env);
                var e2 = eval0(cons(intern("disj"), exp.cdr.cdr), env);
                return function(cenv) { return disj(function(s_c) { return function() { return e1(cenv)(s_c); }; },
                                                    e2(cenv)); };
            }
        case intern("fresh"):
            var bindings = reverse(exp.cdr.car);
            var body = exp.cdr.cdr;
            var env1 = cons(foldl(bindings, env.car, augment_env), env.cdr);
            var body1 = eval0(cons(intern("conj"), body), env1);
            var arglen = length(bindings);

            return function (cenv) {
                return function(s_c) {
                    return function() {
                        // make fresh variables as argument list
                        var args1 = new Array(arglen);
                        var i = 0;

                        var c1 = foldl(bindings, s_c.cdr, function(c, a) {
                            args1[i++] = mkvar(c);
                            return c+1;
                        });

                        return body1(args1.concat(cenv))(cons(s_c.car, c1));
                    }
                };
            };
        case intern("rel"):
            var bindings = reverse(exp.cdr.car);
            var body = exp.cdr.cdr;

            var env1 = cons(foldl(bindings, env.car, augment_env), env.cdr);
            var body1 = eval0(cons(intern("conj"), body), env1);

            return body1;
        default: // application
            var clos = eval0(exp.car, env);
            var args = map(function(e) { return eval0(e, env); }, exp.cdr);
            var arglen = length(args);
            return function(cenv) {
                var args1 = new Array(arglen);
                var i = 0;
                map(function(a) { args1[i++] = a(cenv); }, args);

                return clos(args1.concat(cenv));
            };
        }
    } else if(constantp(exp)) {
        return function(cenv) { return exp; };
    } else if(symbolp(exp)) {
        var v = lookup_calc(exp, env.car);
        if (v) {
            return v;
        } else if(toplevel.hasOwnProperty(exp.string)) {
            var mbox = ref(function() {
                var cache = toplevel[exp.string];
                mbox.set(function() { return cache; });
                return cache;
            });
            return function(cenv) {
                var val = mbox.get()();
                return val(cenv); };
        } else {
            throw ["unbound variable: " + exp.string, env.car];
        }
    } else {
        throw "unkown exp: " + exp;
    }
}

function query(v, s) {
    var v1 = walk_star(v, s);
    return walk_star(v1, reify_s(v1, null));
}

function map_stream(fn, stream) {
    return function() {
        var $ = pull(stream);
        return ($ == null) ? null : cons(fn($.car), map_stream(fn, $.cdr));
    };
}

// take the starting state of the interpreter
// make a stream of each top-level variable queried from the result store
function query_stream(init) {
    var exp = init.car;
    var env = init.cdr.car;
    var s_c = init.cdr.cdr.car;
    var foo = eval0(exp, cons(env, 0))([]);
    var $ = foo(s_c);

    var run_queries = function(s_c) {
        var s = s_c.car;
        var record = new Object(null);
        map(function(x) { record[x.car.string] = query(x.cdr()(),s); }, env);
        return record;
    };
    return map_stream(run_queries, $);
}

function stream_generator($) {
    var next = $;
    return function() {
        var cur = next();
        if(cur == null) { return null; }
        else { next = cur.cdr;
               return cur.car; }
    };
}

var toplevel = new Object(null);
var vm_state = cons(null, mzero);

function run_expression(p) {
    var desugared = desugar(p);
    var lifted = lift_frees(desugared);
    var q$ = query_stream(lifted);
    return stream_generator(q$);
}

function run_program(p) {
    var stream;
    if(p == null) { throw "no program" }
    while(p != null) {
        stream = run_expression(p.car);
        p = p.cdr;
    } return stream;
}
<|endoftext|>