// This requires strands.js
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (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.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is the Narcissus JavaScript engine.
  *
  * The Initial Developer of the Original Code is
  * Brendan Eich <brendan@mozilla.org>.
  * Portions created by the Initial Developer are Copyright (C) 2004
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /*
  * Narcissus - JS implemented in JS.
  *
  * Well-known constants and lookup tables.  Many consts are generated from the
  * tokens table via eval to minimize redundancy, so consumers must be compiled
  * separately to take advantage of the simple switch-case constant propagation
  * done by SpiderMonkey.
  */
 
 /*
  * Authenteo edit:
  * Added the # operators
  * strands edit:
  * - Neil: combine jsdefs.js and jsparse.js into a single file as part of an
  *   effort to reduce namespace pollution.
  * - Neil: make opTypeName order explicit for env compatibility.  The original
  *   source relied on a SpiderMonkey specific behavior where object key
  *   iteration occurs in the same order in which the keys were defined in 
  *   the object.
 Ê* - Neil: perf optimizations for OOM+ parse speedup
 Ê* - Neil: make code x-env-compatible
 Ê* - chocolateboy 2006-06-01: add support for $ in identifiers and remove support for ` as the first character as per:
 Ê* Ê http://www.mozilla.org/js/language/es4/formal/lexer-semantics.html#N-InitialIdentifierCharacter and
 Ê* Ê http://www.mozilla.org/js/language/es4/formal/lexer-semantics.html#N-ContinuingIdentifierCharacter
  */

Narcissus = {};

(function() {

	// EDIT: remove references to global to avoid namespace pollution

	 // EDIT: add yielding op
	 var tokens = [
		 // End of source.
		 "END",
	 
		 // Operators and punctuators.  Some pair-wise order matters, e.g. (+, -)
		 // and (UNARY_PLUS, UNARY_MINUS).
		 "\n", ";",
		 ",",
		 "=",
		 "?", ":", "CONDITIONAL",
		 "||",
		 "&&",
		 "|",
		 "^",
		 "&",
		 "->",
		 "==", "!=", "===", "!==",
		 "<", "<=", ">=", ">",
		 "<<", ">>", ">>>",
		 "+", "-",
		 "*", "/", "%",
		 "#","!", "~", "UNARY_PLUS", "UNARY_MINUS",
		 "++", "--",
		 ".",".#",
		 "#[", "[", "]",
		 "{", "}",
		 "(", ")",
	 
		 // Nonterminal tree node type codes.
		 "SCRIPT", "BLOCK", "LABEL", "FOR_IN", "CALL", "NEW_WITH_ARGS", "INDEX", "TRANSIENT_INDEX",
		 "ARRAY_INIT", "OBJECT_INIT", "PROPERTY_INIT", "GETTER", "SETTER",
		 "GROUP", "LIST",
	 
		 // Terminals.
		 "IDENTIFIER", "NUMBER", "STRING", "REGEXP",
	 
		 // Keywords.
		 "break",
		 "case", "catch", "const", "continue",
		 "debugger", "default", "delete", "do",
		 "else", "enum",
		 "false", "finally", "for", "function",
		 "if", "in", "instanceof", "is",
		 "new", "null",
		 "return",
		 "switch",
		 "this", "throw", "true", "try", "typeof",
		 "var", "void",
		 "while", "with" // EDIT: remove trailing comma (breaks IE)
	 ];
	 
	 // Operator and punctuator mapping from token to tree node type name.
	 // NB: superstring tokens (e.g., ++) must come before their substring token
	 // counterparts (+ in the example), so that the opRegExp regular expression
	 // synthesized from this list makes the longest possible match.
	// EDIT: NB comment above indicates reliance on SpiderMonkey-specific
	//       behavior in the ordering of key iteration -- see EDIT below.
	// EDIT: add yeilding op
	 var opTypeNames = {
		 '\n':   "NEWLINE",
		 ';':    "SEMICOLON",
		 ',':    "COMMA",
		 '?':    "HOOK",
		 ':':    "COLON",
		 '||':   "OR",
		 '&&':   "AND",
		 '|':    "BITWISE_OR",
		 '^':    "BITWISE_XOR",
		 '&':    "BITWISE_AND",
		 '->':   "YIELDING",
		 '===':  "STRICT_EQ",
		 '==':   "EQ",
		 '=':    "ASSIGN",
		 '!==':  "STRICT_NE",
		 '!=':   "NE",
		 '<<':   "LSH",
		 '<=':   "LE",
		 '<':    "LT",
		 '>>>':  "URSH",
		 '>>':   "RSH",
		 '>=':   "GE",
		 '>':    "GT",
		 '++':   "INCREMENT",
		 '--':   "DECREMENT",
		 '+':    "PLUS",
		 '-':    "MINUS",
		 '*':    "MUL",
		 '/':    "DIV",
		 '%':    "MOD",
		 '#': "OBJECT_ID_REFERENCE",
		 '!':    "NOT",
		 '~':    "BITWISE_NOT",
		 '.#':    "TRANSIENT_DOT",
		 '.':    "DOT",
		 '#[':    "TRANSIENT_LEFT_BRACKET",
		 '[':    "LEFT_BRACKET",
		 ']':    "RIGHT_BRACKET",
		 '{':    "LEFT_CURLY",
		 '}':    "RIGHT_CURLY",
		 '(':    "LEFT_PAREN",
		 ')':    "RIGHT_PAREN"
	 };
	
	// EDIT: created separate opTypeOrder array to indicate the order in which
	//       to evaluate opTypeNames.  (Apparently, SpiderMonkey must iterate
	//       hash keys in the order in which they are defined, an implementation
	//       detail which the original narcissus code relied on.)
	// EDIT: add yielding op
	 var opTypeOrder = [
		 '\n',
		 ';',
		 ',',
		 '?',
		 ':',
		 '||',
		 '&&',
		 '|',
		 '^',
		 '&',
		 '->',
		 '===',
		 '==',
		 '=',
		 '!==',
		 '!=',
		 '<<',
		 '<=',
		 '<',
		 '>>>',
		 '>>',
		 '>=',
		 '>',
		 '++',
		 '--',
		 '+',
		 '-',
		 '*',
		 '/',
		 '%',
		 '#',
		 '!',
		 '~',
		 '.#',
		 '.',
		 '#[',
		 '[',
		 ']',
		 '{',
		 '}',
		 '(',
		 ')'
	 ];
	 
	 // Hash of keyword identifier to tokens index.  NB: we must null __proto__ to
	 // avoid toString, etc. namespace pollution.
	 var keywords = {__proto__: null};
	 
	 // Define const END, etc., based on the token names.  Also map name to index.
	 // EDIT: use "var " prefix to make definitions local to this function
	 var consts = "var ";
	 for (var i = 0, j = tokens.length; i < j; i++) {
		 if (i > 0)
			 consts += ", ";
		 var t = tokens[i];
		 var name;
		 if (/^[a-z]/.test(t)) {
			 name = t.toUpperCase();
			 keywords[t] = i;
		 } else {
			 name = (/^\W/.test(t) ? opTypeNames[t] : t);
		 }
		 consts += name + " = " + i;
		 this[name] = i;
		 tokens[t] = i;
	 }
	 eval(consts + ";");
	 
	 // Map assignment operators to their indexes in the tokens array.
	 var assignOps = ['|', '^', '&', '<<', '>>', '>>>', '+', '-', '*', '/', '%'];
	 
	 for (i = 0, j = assignOps.length; i < j; i++) {
		 t = assignOps[i];
		 assignOps[t] = tokens[t];
	 }
	 /* vim: set sw=4 ts=8 et tw=80: */
	 /* ***** BEGIN LICENSE BLOCK *****
	  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
	  *
	  * The contents of this file are subject to the Mozilla Public License Version
	  * 1.1 (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.mozilla.org/MPL/
	  *
	  * Software distributed under the License is distributed on an "AS IS" basis,
	  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
	  * for the specific language governing rights and limitations under the
	  * License.
	  *
	  * The Original Code is the Narcissus JavaScript engine.
	  *
	  * The Initial Developer of the Original Code is
	  * Brendan Eich <brendan@mozilla.org>.
	  * Portions created by the Initial Developer are Copyright (C) 2004
	  * the Initial Developer. All Rights Reserved.
	  *
	  * Contributor(s):
	  *
	  * Alternatively, the contents of this file may be used under the terms of
	  * either the GNU General Public License Version 2 or later (the "GPL"), or
	  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
	  * in which case the provisions of the GPL or the LGPL are applicable instead
	  * of those above. If you wish to allow use of your version of this file only
	  * under the terms of either the GPL or the LGPL, and not to allow others to
	  * use your version of this file under the terms of the MPL, indicate your
	  * decision by deleting the provisions above and replace them with the notice
	  * and other provisions required by the GPL or the LGPL. If you do not delete
	  * the provisions above, a recipient may use your version of this file under
	  * the terms of any one of the MPL, the GPL or the LGPL.
	  *
	  * ***** END LICENSE BLOCK ***** */
	 
	 /*
	  * Narcissus - JS implemented in JS.
	  *
	  * Lexical scanner and parser.
	  */
	  
	  
	 // Build a regexp that recognizes operators and punctuators (except newline).
	 var opRegExpSrc = "^(?:";
	 
	 // EDIT: change for loop from iterating through opTypeNames keys to using
	 //       opTypeOrder array so that we're not dependent on SpiderMonkey's
	 //       key order default behavior.
	 // EDIT: change regex structure for OOM perf improvement
	 for (var i = 0; i < opTypeOrder.length; i++) {
		 var op = opTypeOrder[i];
		 if (op == '\n')
			 continue;
		 if (opRegExpSrc != "^(?:")
			 opRegExpSrc += "|";
		 
		 // EDIT: expand out this regexp for environments that don't support $&
		 //opRegExpSrc += op.replace(/[?|^&(){}\[\]+\-*\/\.]/g, "\\$&");
		 op = op.replace(/\?/g, "\\?");
		 op = op.replace(/\|/g, "\\|");
		 op = op.replace(/\^/g, "\\^");
		 op = op.replace(/\&/g, "\\&");
		 op = op.replace(/\(/g, "\\(");
		 op = op.replace(/\)/g, "\\)");
		 op = op.replace(/\{/g, "\\{");
		 op = op.replace(/\}/g, "\\}");
		 op = op.replace(/\[/g, "\\[");
		 op = op.replace(/\]/g, "\\]");
		 op = op.replace(/\+/g, "\\+");
		 op = op.replace(/\-/g, "\\-");
		 op = op.replace(/\*/g, "\\*");
		 op = op.replace(/\//g, "\\/");
		 op = op.replace(/\./g, "\\.");
		 opRegExpSrc += op;
	 }
	 opRegExpSrc += ")";
	 var opRegExp = new RegExp(opRegExpSrc);
	 
	 // A regexp to match floating point literals (but not integer literals).
	 // EDIT: change regex structure for OOM perf improvement
	 var fpRegExp = /^(?:\d+\.\d*(?:[eE][-+]?\d+)?|\d+(?:\.\d*)?[eE][-+]?\d+|\.\d+(?:[eE][-+]?\d+)?)/;
	 
	 function Tokenizer(s, f, l) {
		 this.cursor = 0;
		 this.source = String(s);
		 this.tokens = [];
		 this.tokenIndex = 0;
		 this.lookahead = 0;
		 this.scanNewlines = false;
		 this.scanOperand = true;
		 this.filename = f || "";
		 this.lineno = l ? l : 1;
	 }
	  
	 Tokenizer.prototype = {
	 
	 // EDIT: change "input" from a getter to a regular method for compatibility
	 //       with older JavaScript versions
		 input: function() {
			 return this.source.substring(this.cursor);
		 },
	 
	 // EDIT: change "done" from a getter to a regular method for compatibility
	 //       with older JavaScript versions
		 done: function() {
			 return this.peek() == END;
		 },
	 
	 // EDIT: change "token" from a getter to a regular method for compatibility
	 //       with older JavaScript versions
		 token: function() {
			 return this.tokens[this.tokenIndex];
		 },
	 
		 match: function (tt) {
			 return this.get() == tt || this.unget();
		 },
	 
		 mustMatch: function (tt) {
			 if (!this.match(tt)) {
				 throw this.newSyntaxError("Missing " + tokens[tt].toLowerCase());
			 }
			 return this.token();
		 },
	 
		 peek: function () {
			 var tt;
			 if (this.lookahead) {
				 tt = this.tokens[(this.tokenIndex + this.lookahead) & 3].type;
			 } else {
				 tt = this.get();
				 this.unget();
			 }
			 return tt;
		 },
	 
		 peekOnSameLine: function () {
			 this.scanNewlines = true;
			 var tt = this.peek();
			 this.scanNewlines = false;
			 return tt;
		 },
	 
		 get: function () {
			 var token;
			 while (this.lookahead) {
				 --this.lookahead;
				 this.tokenIndex = (this.tokenIndex + 1) & 3;
				 token = this.tokens[this.tokenIndex];
				 if (token.type != NEWLINE || this.scanNewlines)
					 return token.type;
			 }
	 
			 for (;;) {
				 var input = this.input();
				 var firstChar = input.charCodeAt(0);
				 // EDIT: check first char, then use regex
				 // valid regex whitespace includes char codes: 9 10 11 12 13 32
				 if(firstChar == 32 || (firstChar >= 9 && firstChar <= 13)) {
					 var match = input.match(this.scanNewlines ? /^[ \t]+/ : /^\s+/); // EDIT: use x-browser regex syntax
					 if (match) {
						 var spaces = match[0];
						 this.cursor += spaces.length;
						 var newlines = spaces.match(/\n/g);
						 if (newlines)
							 this.lineno += newlines.length;
						 input = this.input();
					}
				 }
	 
				 // EDIT: improve perf by checking first string char before proceeding to regex,
				 //       use x-browser regex syntax
				 if (input.charCodeAt(0) != 47 || !(match = input.match(/^\/(?:\*(?:.|\n)*?\*\/|\/.*)/)))
					 break;
				 var comment = match[0];
				 this.cursor += comment.length;
				 newlines = comment.match(/\n/g);
				 if (newlines)
					 this.lineno += newlines.length
			 }
	 
			 this.tokenIndex = (this.tokenIndex + 1) & 3;
			 token = this.tokens[this.tokenIndex];
			 if (!token)
				 this.tokens[this.tokenIndex] = token = {};
	 
			 if (!input)
				 return token.type = END;
	
			 var firstChar = input.charCodeAt(0);
			 
			 // EDIT: guard by checking char codes before going to regex
			 if ((firstChar == 46 || (firstChar > 47 && firstChar < 58)) && 
				 (match = input.match(fpRegExp))) { // EDIT: use x-browser regex syntax
				 token.type = NUMBER;
				 token.value = parseFloat(match[0]);
			 } else if ((firstChar > 47 && firstChar < 58) && 
						(match = input.match(/^(?:0[xX][\da-fA-F]+|0[0-7]*|\d+)/))) { // EDIT: change regex structure for OOM perf improvement,
																					  //       use x-browser regex syntax
				 token.type = NUMBER;
				 token.value = parseInt(match[0]);
			 } else if (((firstChar > 47 && firstChar < 58)  ||   // EDIT: add guards to check before using regex
						 (firstChar > 64 && firstChar < 91)  || 
						 (firstChar > 96 && firstChar < 123) ||   // EDIT: exclude `
						 (firstChar == 36 || firstChar == 95)) && // EDIT: allow $ + mv _ here
						(match = input.match(/^[$\w]+/))) {       // EDIT: allow $, use x-browser regex syntax
				 var id = match[0];
				 // EDIT: check the type of the value in the keywords hash, as different envs
				 //       expose implicit Object properties that SpiderMonkey does not.
				 token.type = typeof(keywords[id]) == "number" ? keywords[id] : IDENTIFIER;
				 token.value = id;
			 } else if ((firstChar == 34 || firstChar == 39) && 
						(match = input.match(/^(?:"(?:\\.|[^"])*"|'(?:[^']|\\.)*')/))) { //"){  // EDIT: change regex structure for OOM perf improvement,
																								//       use x-browser regex syntax
				 token.type = STRING;
				 token.value = eval(match[0]);
			 } else if (this.scanOperand && firstChar == 47 && // EDIT: improve perf by guarding with first char check
						(match = input.match(/^\/((?:\\.|[^\/])+)\/([gi]*)/))) { // EDIT: use x-browser regex syntax
				 token.type = REGEXP;
				 token.value = new RegExp(match[1], match[2]);
			 } else if ((match = input.match(opRegExp))) { // EDIT: use x-browser regex syntax
				 var op = match[0];
				 // EDIT: IE doesn't support indexing of strings -- use charAt
				 if (assignOps[op] && input.charAt(op.length) == '=') {
					 token.type = ASSIGN;
					 token.assignOp = eval(opTypeNames[op]);
					 match[0] += '=';
				 } else {
					 token.type = eval(opTypeNames[op]);
					 if (this.scanOperand &&
						 (token.type == PLUS || token.type == MINUS)) {
						 token.type += UNARY_PLUS - PLUS;
					 }
					 token.assignOp = null;
				 }
				 token.value = op;
			 } else {
				 throw this.newSyntaxError("Illegal token");
			 }
	
			 token.start = this.cursor;
			 this.cursor += match[0].length;
			 token.end = this.cursor;
			 token.lineno = this.lineno;
			 return token.type;
		 },
	 
		 unget: function () {
			 if (++this.lookahead == 4) throw "PANIC: too much lookahead!";
			 this.tokenIndex = (this.tokenIndex - 1) & 3;
		 },
	 
		 newSyntaxError: function (m) {
			 var e = new SyntaxError(m, this.filename, this.lineno);
			 e.lineNumber = this.lineno; // EDIT: x-browser exception handling
			 e.source = this.source;
			 e.cursor = this.cursor;
			 return e;
		 }
	 };
	 
	 function CompilerContext(inFunction) {
		 this.inFunction = inFunction;
		 this.stmtStack = [];
		 this.funDecls = [];
		 this.varDecls = [];
	 }
	 
	 var CCp = CompilerContext.prototype;
	 CCp.bracketLevel = CCp.curlyLevel = CCp.parenLevel = CCp.hookLevel = 0;
	 CCp.ecmaStrictMode = CCp.inForLoopInit = false;
	 
	 function Script(t, x) {
		 var n = Statements(t, x);
		 n.type = SCRIPT;
		 n.funDecls = x.funDecls;
		 n.varDecls = x.varDecls;
		 return n;
	 }
	 
	// EDIT: change "top" method to be a regular method, rather than defined
	//       via the SpiderMonkey-specific __defineProperty__
	
	 // Node extends Array, which we extend slightly with a top-of-stack method.
	 Array.prototype.top = function() {
		return this.length && this[this.length-1];
	 }
	 
	 function Node(t, type) {
		 // EDIT: "inherit" from Array in an x-browser way.
		 var _this = [];
		 for (var n in Node.prototype)
		 	_this[n] = Node.prototype[n];

		 _this.constructor = Node;

		 var token = t.token();
		 if (token) {
			 _this.type = type || token.type;
			 _this.value = token.value;
			 _this.lineno = token.lineno;
			 _this.start = token.start;
			 _this.end = token.end;
		 } else {
			 _this.type = type;
			 _this.lineno = t.lineno;
		 }
		 _this.tokenizer = t;
	 
		 for (var i = 2; i < arguments.length; i++) 
			_this.push(arguments[i]);
		
		 return _this;
	 }
	 
	 var Np = Node.prototype; // EDIT: don't inherit from array
	 Np.toSource = Object.prototype.toSource;
	 	
	 // Always use push to add operands to an expression, to update start and end.
	 Np.push = function (kid) {
		 if (kid.start < this.start)
			 this.start = kid.start;
		 if (this.end < kid.end)
			 this.end = kid.end;
	
		 this[this.length] = kid;
	 }
	 
	 Node.indentLevel = 0;
	 
	 Np.toString = function () {
		 var a = [];
		 for (var i in this) {
			 if (this.hasOwnProperty(i) && i != 'type' && i != 'parent' && typeof(this[i]) != 'function') {
				 // EDIT,BUG: add check for 'target' to prevent infinite recursion
				 if(i != 'target')
					 a.push({id: i, value: this[i]});
				 else
					 a.push({id: i, value: "[token: " + this[i].value + "]"});
			 }
					
		 }
		 a.sort(function (a,b) { return (a.id < b.id) ? -1 : 1; });
		 INDENTATION = "    ";
		 var n = ++Node.indentLevel;
		 var t = tokens[this.type];
		 var s = "{\n" + INDENTATION.repeat(n) +
				 "type: " + (/^\W/.test(t) ? opTypeNames[t] : t.toUpperCase());
		 for (i = 0; i < a.length; i++) {
			 s += ",\n" + INDENTATION.repeat(n) + a[i].id + ": " + a[i].value;}
		 n = --Node.indentLevel;
		 s += "\n" + INDENTATION.repeat(n) + "}";
		 return s;
	 }
	 
	 Np.getSource = function () {
		 return this.tokenizer.source.slice(this.start, this.end);
	 };
	 
	// EDIT: change "filename" method to be a regular method, rather than defined
	//       via the SpiderMonkey-specific __defineGetter__
	 Np.filename = function () { return this.tokenizer.filename; };
	 
	 String.prototype.repeat = function (n) {
		 var s = "", t = this + s;
		 while (--n >= 0)
			 s += t;
		 return s;
	 }
	 
	 // Statement stack and nested statement handler.
	 function nest(t, x, node, func, end) {
		 x.stmtStack.push(node);
		 var n = func(t, x);
		 x.stmtStack.pop();
		 end && t.mustMatch(end);
		 return n;
	 }
	 
	 function Statements(t, x) {
		 var n = Node(t, BLOCK);
		 x.stmtStack.push(n);
		 while (!t.done() && t.peek() != RIGHT_CURLY) 
			 n.push(Statement(t, x));
		 x.stmtStack.pop();
		 return n;
	 }
	 
	 function Block(t, x) {
		t.mustMatch(LEFT_CURLY);
		 var n = Statements(t, x);
		 t.mustMatch(RIGHT_CURLY);
		 return n;
	 }
	 
	 var DECLARED_FORM = 0, EXPRESSED_FORM = 1, STATEMENT_FORM = 2;
	 
	 function Statement(t, x) {
		 var i, label, n, n2, ss, tt = t.get();
	 
		 // Cases for statements ending in a right curly return early, avoiding the
		 // common semicolon insertion magic after this switch.
		switch (tt) {
		   case FUNCTION:
			 return FunctionDefinition(t, x, true,DECLARED_FORM);
									   /*(x.stmtStack.length > 1)
									   ? STATEMENT_FORM
									   : DECLARED_FORM);*/
	 
		   case LEFT_CURLY:
			 n = Statements(t, x);
			 t.mustMatch(RIGHT_CURLY);
			 return n;
	 
		   case IF:
			 n = Node(t);
			 n.condition = ParenExpression(t, x);
			 x.stmtStack.push(n);
			 n.thenPart = Statement(t, x);
			 n.elsePart = t.match(ELSE) ? Statement(t, x) : null;
			 x.stmtStack.pop();
			 return n;
	 
		   case SWITCH:
			 n = Node(t);
			 t.mustMatch(LEFT_PAREN);
			 n.discriminant = Expression(t, x);
			 t.mustMatch(RIGHT_PAREN);
			 n.cases = [];
			 n.defaultIndex = -1;
			 x.stmtStack.push(n);
			 t.mustMatch(LEFT_CURLY);
			 while ((tt = t.get()) != RIGHT_CURLY) {
				 switch (tt) {
				   case DEFAULT:
					 if (n.defaultIndex >= 0)
						 throw t.newSyntaxError("More than one switch default");
					 // FALL THROUGH
				   case CASE:
					 n2 = Node(t);
					 if (tt == DEFAULT)
						 n.defaultIndex = n.cases.length;
					 else
						 n2.caseLabel = Expression(t, x, COLON);
					 break;
				   default:
					 throw t.newSyntaxError("Invalid switch case");
				 }
				 t.mustMatch(COLON);
				 n2.statements = Node(t, BLOCK);
				 while ((tt=t.peek()) != CASE && tt != DEFAULT && tt != RIGHT_CURLY)
					 n2.statements.push(Statement(t, x));
				 n.cases.push(n2);
			 }
			 x.stmtStack.pop();
			 return n;
	 
		   case FOR:
			 n = Node(t);
			 n.isLoop = true;
			 t.mustMatch(LEFT_PAREN);
			 if ((tt = t.peek()) != SEMICOLON) {
				 x.inForLoopInit = true;
				 if (tt == VAR || tt == CONST) {
					 t.get();
					 n2 = Variables(t, x);
				 } else {
					 n2 = Expression(t, x);
				 }
				 x.inForLoopInit = false;
			 }
			 if (n2 && t.match(IN)) {
				 n.type = FOR_IN;
				 if (n2.type == VAR) {
					 if (n2.length != 1) {
						 throw new SyntaxError("Invalid for..in left-hand side",
											   t.filename, n2.lineno);
					 }
	 
					 // NB: n2[0].type == IDENTIFIER and n2[0].value == n2[0].name.
					 n.iterator = n2[0];
					 n.varDecl = n2;
				 } else {
					 n.iterator = n2;
					 n.varDecl = null;
				 }
				 n.object = Expression(t, x);
			 } else {
				 n.setup = n2 || null;
				 t.mustMatch(SEMICOLON);
				 n.condition = (t.peek() == SEMICOLON) ? null : Expression(t, x);
				 t.mustMatch(SEMICOLON);
				 n.update = (t.peek() == RIGHT_PAREN) ? null : Expression(t, x);
			 }
			 t.mustMatch(RIGHT_PAREN);
			 n.body = nest(t, x, n, Statement);
			 return n;
	 
		   case WHILE:
			 n = Node(t);
			 n.isLoop = true;
			 n.condition = ParenExpression(t, x);
			 n.body = nest(t, x, n, Statement);
			 return n;
	 
		   case DO:
			 n = Node(t);
			 n.isLoop = true;
			 n.body = nest(t, x, n, Statement, WHILE);
			 n.condition = ParenExpression(t, x);
			 if (!x.ecmaStrictMode) {
				 // <script language="JavaScript"> (without version hints) may need
				 // automatic semicolon insertion without a newline after do-while.
				 // See http://bugzilla.mozilla.org/show_bug.cgi?id=238945.
				 t.match(SEMICOLON);
				 return n;
			 }
			 break;
	 
		   case BREAK:
		   case CONTINUE:
			 n = Node(t);
			 if (t.peekOnSameLine() == IDENTIFIER) {
				 t.get();
				 n.label = t.token().value;
			 }
			 ss = x.stmtStack;
			 i = ss.length;
			 label = n.label;
			 if (label) {
				 do {
					 if (--i < 0)
						 throw t.newSyntaxError("Label not found");
				 } while (ss[i].label != label);
			 } else {
				 do {
					 if (--i < 0) {
						 throw t.newSyntaxError("Invalid " + ((tt == BREAK)
															  ? "break"
															  : "continue"));
					 }
				 } while (!ss[i].isLoop && (tt != BREAK || ss[i].type != SWITCH));
			 }
			 n.target = ss[i];
			 break;
	 
		   case TRY:
			 n = Node(t);
			 n.tryBlock = Block(t, x);
			 n.catchClauses = [];
			 while (t.match(CATCH)) {
				 n2 = Node(t);
				 t.mustMatch(LEFT_PAREN);
				 n2.varName = t.mustMatch(IDENTIFIER).value;
				 if (t.match(IF)) {
					 if (x.ecmaStrictMode)
						 throw t.newSyntaxError("Illegal catch guard");
					 if (n.catchClauses.length && !n.catchClauses.top().guard)
						 throw t.newSyntaxError("Guarded catch after unguarded");
					 n2.guard = Expression(t, x);
				 } else {
					 n2.guard = null;
				 }
				 t.mustMatch(RIGHT_PAREN);
				 n2.block = Block(t, x);
				 n.catchClauses.push(n2);
			 }
			 if (t.match(FINALLY))
				 n.finallyBlock = Block(t, x);
			 if (!n.catchClauses.length && !n.finallyBlock)
				 throw t.newSyntaxError("Invalid try statement");
			 return n;
	 
		   case CATCH:
		   case FINALLY:
			 throw t.newSyntaxError(tokens[tt] + " without preceding try");
	 
		   case THROW:
			 n = Node(t);
			 n.exception = Expression(t, x);
			 break;
	 
		   case RETURN:
			 if (!x.inFunction)
				 throw t.newSyntaxError("Invalid return");
			 n = Node(t);
			 tt = t.peekOnSameLine();
			 // EDIT,BUG?: rather that set n.value (which already has meaning for
			 //            nodes), set n.expression
			 if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY)
				 n.expression = Expression(t, x);
			 break;
	 
		   case WITH:
			 n = Node(t);
			 n.object = ParenExpression(t, x);
			 n.body = nest(t, x, n, Statement);
			 return n;
	 
		   case VAR:
		   case CONST:
			 n = Variables(t, x);
			 break;
	 
		   case DEBUGGER:
			 n = Node(t);
			 break;
	 
		   case NEWLINE:
		   case SEMICOLON:
			 n = Node(t, SEMICOLON);
			 n.expression = null;
			 return n;
	 
		   default:
			 if (tt == IDENTIFIER && t.peek() == COLON) {
				 label = t.token().value;
				 ss = x.stmtStack;
				 for (i = ss.length-1; i >= 0; --i) {
					 if (ss[i].label == label)
						 throw t.newSyntaxError("Duplicate label");
				 }
				 t.get();
				 n = Node(t, LABEL);
				 n.label = label;
				 n.statement = nest(t, x, n, Statement);
				 return n;
			 }
	 
			 n = Node(t, SEMICOLON);
			 t.unget();
			 n.expression = Expression(t, x);
			 n.end = n.expression.end;
			 break;
		 }
	 
		 if (t.lineno == t.token().lineno) {
			 tt = t.peekOnSameLine();
			 if (tt != END && tt != NEWLINE && tt != SEMICOLON && tt != RIGHT_CURLY)
				 throw t.newSyntaxError("Missing ; before statement " + tokens[tt]);
		 }
		 t.match(SEMICOLON);
		 return n;
	 }
	 function TypeDefinition(t,v) {
		if (Narcissus.typeChecking && t.peek() == COLON) { // Edit: Added this for making typed variable
			t.get();
			if (t.peek() == OBJECT_ID_REFERENCE)
				t.match(OBJECT_ID_REFERENCE)
			if (t.peek() == FUNCTION) {				
				t.match(FUNCTION)
				v.varType = {value:"function"};
			}
			else if (t.peek() == MUL) {
				t.match(MUL)
				v.varType = {value:"any"};				
			}
			else {
				t.mustMatch(IDENTIFIER);
				v.varType = Node(t);
			}
		}
	 	
	 }
	 function FunctionDefinition(t, x, requireName, functionForm) {
		 var f = Node(t);
		 if (f.type != FUNCTION)
			 f.type = (f.value == "get") ? GETTER : SETTER;
		 if (t.match(IDENTIFIER))
			 f.name = t.token().value;
		 else if (requireName)
			 throw t.newSyntaxError("Missing function identifier");

	
		 t.mustMatch(LEFT_PAREN);
		 f.params = [];
		 var tt;
		 while ((tt = t.get()) != RIGHT_PAREN) {
			 if (tt != IDENTIFIER)
				 throw t.newSyntaxError("Missing formal parameter");
			var param = Node(t);
			 f.params.push(param);
			 TypeDefinition(t,param);
			 if (t.peek() != RIGHT_PAREN)
				 t.mustMatch(COMMA);
		 }
		 TypeDefinition(t,f);
		 	 	
		 t.mustMatch(LEFT_CURLY);
		 var x2 = new CompilerContext(true);
		 f.body = Script(t, x2);
		 t.mustMatch(RIGHT_CURLY);
		 f.end = t.token().end;
	 
		 f.functionForm = functionForm;
		 if (functionForm == DECLARED_FORM)
			 x.funDecls.push(f);
		 return f;
	 }
	 
	 function Variables(t, x) {
		 var n = Node(t);
		 do {
 		 	 if (!(t.match(IDENTIFIER) || t.match(COLON))) 
			 	throw t.newSyntaxError("Invalid variable initialization");
			 var n2 = Node(t);
			 TypeDefinition(t,n2);
			 n2.name = n2.value;
			 if (t.match(ASSIGN)) {
				 if (t.token().assignOp)
					 throw t.newSyntaxError("Invalid variable initialization");
				 n2.initializer = Expression(t, x, COMMA);
			 }
			 n2.readOnly = (n.type == CONST);
			 n.push(n2);
			 x.varDecls.push(n2);
		 } while (t.match(COMMA));
		 return n;
	 }
	 
	 function ParenExpression(t, x) {
		 t.mustMatch(LEFT_PAREN);
		 var n = Expression(t, x);
		 t.mustMatch(RIGHT_PAREN);
		 return n;
	 }
	 
	 // EDIT: add yielding op precedence
	 var opPrecedence = {
		 SEMICOLON: 0,
		 COMMA: 1,
		 ASSIGN: 2,
		 HOOK: 3, COLON: 3, CONDITIONAL: 3,
		 OR: 4,
		 AND: 5,
		 BITWISE_OR: 6,
		 BITWISE_XOR: 7,
		 BITWISE_AND: 8,
		 EQ: 9, NE: 9, STRICT_EQ: 9, STRICT_NE: 9,
		 LT: 10, LE: 10, GE: 10, GT: 10, IN: 10, INSTANCEOF: 10, IS: 10,
		 LSH: 11, RSH: 11, URSH: 11,
		 PLUS: 12, MINUS: 12,
		 MUL: 13, DIV: 13, MOD: 13,
		 DELETE: 14, VOID: 14, TYPEOF: 14, // PRE_INCREMENT: 14, PRE_DECREMENT: 14,
		 NOT: 14, BITWISE_NOT: 14, UNARY_PLUS: 14, UNARY_MINUS: 14,
		 INCREMENT: 15, DECREMENT: 15,     // postfix
		 NEW: 16,
		 YIELDING: 17,
		 TRANSIENT_DOT: 18,
		 DOT: 18,OBJECT_ID_REFERENCE: 19
	 };
	 
	 // Map operator type code to precedence.
	 // EDIT: slurp opPrecence items into array first, because IE includes
	 //       modified hash items in iterator when modified during iteration
	 var opPrecedenceItems = [];
	 for (i in opPrecedence) 
		opPrecedenceItems.push(i);
	 
	 for (var i = 0; i < opPrecedenceItems.length; i++) {
		var item = opPrecedenceItems[i];
		opPrecedence[eval(item)] = opPrecedence[item];
	 }
	
	 var opArity = {
		 COMMA: -2,
		 ASSIGN: 2,
		 CONDITIONAL: 3,
		 OR: 2,
		 AND: 2,
		 BITWISE_OR: 2,
		 BITWISE_XOR: 2,
		 BITWISE_AND: 2,
		 EQ: 2, NE: 2, STRICT_EQ: 2, STRICT_NE: 2,
		 LT: 2, LE: 2, GE: 2, GT: 2, IN: 2, INSTANCEOF: 2, IS: 2,
		 LSH: 2, RSH: 2, URSH: 2,
		 PLUS: 2, MINUS: 2,
		 MUL: 2, DIV: 2, MOD: 2,
		 DELETE: 1, VOID: 1, TYPEOF: 1,  OBJECT_ID_REFERENCE: 1,// PRE_INCREMENT: 1, PRE_DECREMENT: 1,
		 NOT: 1, BITWISE_NOT: 1, UNARY_PLUS: 1, UNARY_MINUS: 1,
		 INCREMENT: 1, DECREMENT: 1,     // postfix
		 NEW: 1, NEW_WITH_ARGS: 2, DOT: 2, TRANSIENT_DOT: 2, INDEX: 2, TRANSIENT_INDEX: 2, CALL: 2, YIELDING: 3,
		 ARRAY_INIT: 1, OBJECT_INIT: 1, GROUP: 1
	 };
	 
	 // Map operator type code to arity.
	 // EDIT: same as above
	 var opArityItems = [];
	 for (i in opArity)
		opArityItems.push(i);
	 
	 for (var i = 0; i < opArityItems.length; i++) {
		var item = opArityItems[i];
		opArity[eval(item)] = opArity[item];
	 }
	 
	 function Expression(t, x, stop) {
		 var n, id, tt, operators = [], operands = [];
		 var bl = x.bracketLevel, cl = x.curlyLevel, pl = x.parenLevel,
			 hl = x.hookLevel;
	 
		 function reduce() {
			 var n = operators.pop();
			 var op = n.type;
			 var arity = opArity[op];
			 if (arity == -2) {
				 // Flatten left-associative trees.
				 var left = operands.length >= 2 && operands[operands.length-2];
				 if (left.type == op) {
					 var right = operands.pop();
					 left.push(right);
					 return left;
				 }
				 arity = 2;
			 }
	 
			 // Always use push to add operands to n, to update start and end.
			 // EDIT: provide second argument to splice or IE won't work.
			 var index = operands.length - arity;
			 var a = operands.splice(index, operands.length - index);
			 for (var i = 0; i < arity; i++)
				 n.push(a[i]);
	 
			 // Include closing bracket or postfix operator in [start,end).
			 if (n.end < t.token().end)
				 n.end = t.token().end;

			 operands.push(n);
			 return n;
		 }
	 
	 loop:
		 while ((tt = t.get()) != END) {
			 if (tt == stop &&
				 x.bracketLevel == bl && x.curlyLevel == cl && x.parenLevel == pl &&
				 x.hookLevel == hl) {
				 // Stop only if tt matches the optional stop parameter, and that
				 // token is not quoted by some kind of bracket.
				 break;
			 }
			 switch (tt) {
			   case SEMICOLON:
				 // NB: cannot be empty, Statement handled that.
				 break loop;
	 
			   case ASSIGN:
			   case HOOK:
			   case COLON:
				 if (t.scanOperand)
					 break loop;
				 // Use >, not >=, for right-associative ASSIGN and HOOK/COLON.
				 while (opPrecedence[operators.top().type] > opPrecedence[tt])
					 reduce();
				 if (tt == COLON) {
					 n = operators.top();
					 if (n.type != HOOK)
						 throw t.newSyntaxError("Invalid label");
					 n.type = CONDITIONAL;
					 --x.hookLevel;
				 } else {
					 operators.push(Node(t));
					 if (tt == ASSIGN)
						 operands.top().assignOp = t.token().assignOp;
					 else
						 ++x.hookLevel;      // tt == HOOK
				 }
				 t.scanOperand = true;
				 break;
	 
			   case IN:
				 // An in operator should not be parsed if we're parsing the head of
				 // a for (...) loop, unless it is in the then part of a conditional
				 // expression, or parenthesized somehow.
				 if (x.inForLoopInit && !x.hookLevel &&
					 !x.bracketLevel && !x.curlyLevel && !x.parenLevel) {
					 break loop;
				 }
				 // FALL THROUGH
			   case COMMA:
				 // Treat comma as left-associative so reduce can fold left-heavy
				 // COMMA trees into a single array.
				 // FALL THROUGH
			   case OR:
			   case AND:
			   case BITWISE_OR:
			   case BITWISE_XOR:
			   case BITWISE_AND:
			   case EQ: case NE: case STRICT_EQ: case STRICT_NE:
			   case LT: case LE: case GE: case GT:
			   case INSTANCEOF: case IS:
			   case LSH: case RSH: case URSH:
			   case PLUS: case MINUS:
			   case MUL: case DIV: case MOD:
			   case DOT: case TRANSIENT_DOT:
				 if (t.scanOperand)
					 break loop;
				 while (opPrecedence[operators.top().type] >= opPrecedence[tt])
					 reduce();
				 if (tt == DOT) {
					 t.mustMatch(IDENTIFIER);
					 operands.push(Node(t, DOT, operands.pop(), Node(t)));
				 } else if (tt == TRANSIENT_DOT) {
					 t.mustMatch(IDENTIFIER);
					 operands.push(Node(t, TRANSIENT_DOT, operands.pop(), Node(t)));
				 } else {
					 operators.push(Node(t));
					 t.scanOperand = true;
				 }
				 break;
	 
			   case DELETE: case VOID: case TYPEOF:
			   case NOT: case BITWISE_NOT: case UNARY_PLUS: case UNARY_MINUS: case OBJECT_ID_REFERENCE:
			   case NEW:
				 if (!t.scanOperand)
					 break loop;
				 operators.push(Node(t));
				 break;
	 
			   case INCREMENT: case DECREMENT:
				 if (t.scanOperand) {
					 operators.push(Node(t));  // prefix increment or decrement
				 } else {
					 // Use >, not >=, so postfix has higher precedence than prefix.
					 while (opPrecedence[operators.top().type] > opPrecedence[tt])
						 reduce();
					 n = Node(t, tt, operands.pop());
					 n.postfix = true;
					 operands.push(n);
				 }
				 break;
	 
			   case FUNCTION:
				 if (!t.scanOperand)
					 break loop;
				 operands.push(FunctionDefinition(t, x, false, EXPRESSED_FORM));
				 t.scanOperand = false;
				 break;
	 
			   case NULL: case THIS: case TRUE: case FALSE:
			   case IDENTIFIER: case NUMBER: case STRING: case REGEXP:
				 if (!t.scanOperand)
					 break loop;
				 operands.push(Node(t));
				 t.scanOperand = false;
				 break;
	 
			   case LEFT_BRACKET:
				 if (t.scanOperand) {
					 // Array initialiser.  Parse using recursive descent, as the
					 // sub-grammar here is not an operator grammar.
					 n = Node(t, ARRAY_INIT);
					 while ((tt = t.peek()) != RIGHT_BRACKET) {
						 if (tt == COMMA) {
							 t.get();
							 n.push(null);
							 continue;
						 }
						 n.push(Expression(t, x, COMMA));
						 if (!t.match(COMMA))
							 break;
					 }
					 t.mustMatch(RIGHT_BRACKET);
					 operands.push(n);
					 t.scanOperand = false;
				 } else {
					 // Property indexing operator.
					 operators.push(Node(t, INDEX));
					 t.scanOperand = true;
					 ++x.bracketLevel;
				 }
				 break;
	 		   case TRANSIENT_LEFT_BRACKET:
				 // Property indexing operator.
				 operators.push(new Node(t, TRANSIENT_INDEX));
				 t.scanOperand = true;
				 ++x.bracketLevel;
				 break;	 		   
			   case RIGHT_BRACKET:
				 if (t.scanOperand || x.bracketLevel == bl)
					 break loop;
				 do {
				 	var type = reduce().type;
				 }
				 while (type != INDEX && type != TRANSIENT_INDEX)
				 --x.bracketLevel;
				 break;
	 
			   case LEFT_CURLY:
				 if (!t.scanOperand)
					 break loop;
				 // Object initialiser.  As for array initialisers (see above),
				 // parse using recursive descent.
				 ++x.curlyLevel;
				 n = Node(t, OBJECT_INIT);
			   object_init:
				 if (!t.match(RIGHT_CURLY)) {
					 do {
						 tt = t.get();
						 if ((t.token().value == "get" || t.token().value == "set") &&
							 t.peek() == IDENTIFIER) {
							 if (x.ecmaStrictMode)
								 throw t.newSyntaxError("Illegal property accessor");
							 n.push(FunctionDefinition(t, x, true, EXPRESSED_FORM));
						 } else {
							 switch (tt) {
							   case IDENTIFIER:
							   case NUMBER:
							   case STRING:
								 id = Node(t);
								 break;
							   case RIGHT_CURLY:
								 if (x.ecmaStrictMode)
									 throw t.newSyntaxError("Illegal trailing ,");
								 break object_init;
							   default:
								 throw t.newSyntaxError("Invalid property name");
							 }
							 t.mustMatch(COLON);
							 n.push(Node(t, PROPERTY_INIT, id,
											 Expression(t, x, COMMA)));
						 }
					 } while (t.match(COMMA));
					 t.mustMatch(RIGHT_CURLY);
				 }
				 operands.push(n);
				 t.scanOperand = false;
				 --x.curlyLevel;
				 break;
	 
			   case RIGHT_CURLY:
				 if (!t.scanOperand && x.curlyLevel != cl)
					 throw "PANIC: right curly botch";
				 break loop;
	 
			   case YIELDING:
				 while (opPrecedence[operators.top().type] > opPrecedence[YIELDING])
					 reduce();
				 t.mustMatch(LEFT_PAREN);
				 var yielding = true;
				 // FALL THROUGH
				 
			   case LEFT_PAREN:
				 if (t.scanOperand) {
					 operators.push(Node(t, GROUP));
				 } else {
					 while (opPrecedence[operators.top().type] > opPrecedence[NEW])
						 reduce();
	 
					 // Handle () now, to regularize the n-ary case for n > 0.
					 // We must set scanOperand in case there are arguments and
					 // the first one is a regexp or unary+/-.
					 n = operators.top();
					 t.scanOperand = true;
					 if (t.match(RIGHT_PAREN)) {
						 if (n.type == NEW) {
						 	n.type = NEW_WITH_ARGS;
							 --operators.length;
							 n.push(operands.pop());
						 } else {
							 n = Node(t, CALL, operands.pop(),
										  Node(t, LIST));
						 }
						 operands.push(n);
						 t.scanOperand = false;
						 n.yielding = yielding || false;
						 break;
					 }
					 if (n.type == NEW) {
						 n.type = NEW_WITH_ARGS;
					 } else {
						 n = Node(t, CALL);
						 operators.push(n);
					 }
					 n.yielding = yielding || false;
				 }
				 ++x.parenLevel;
				 break;
	 
			   case RIGHT_PAREN:
				 if (t.scanOperand || x.parenLevel == pl)
					 break loop;
				 while ((tt = reduce().type) != GROUP && tt != CALL &&
						tt != NEW_WITH_ARGS) {
					 continue;
				 }
				 if (tt != GROUP) {
					 n = operands.top();
					 if (n[1].type != COMMA)
						 n[1] = Node(t, LIST, n[1]);
					 else
						 n[1].type = LIST;
				 }
				 --x.parenLevel;
				 break;
	 
			   // Automatic semicolon insertion means we may scan across a newline
			   // and into the beginning of another statement.  If so, break out of
			   // the while loop and let the t.scanOperand logic handle errors.
			   default:
				 break loop;
			 }
		 }
	 
		 if (x.hookLevel != hl)
			 throw t.newSyntaxError("Missing : after ?");
		 if (t.scanOperand)
			 throw t.newSyntaxError("Missing operand");
	 
		 // Resume default mode, scanning for operands, not operators.
		 t.scanOperand = true;
		 t.unget();
		 while (operators.length)
			 reduce();
		 return operands.pop();
	 }
	 
	 function parse(s, f, l) {
		 var t = new Tokenizer(s, f, l);
		 var x = new CompilerContext(false);
		 var n = Script(t, x);
		 if (!t.done())
			 throw t.newSyntaxError("Syntax error");
		 return n;
	 }
	 
	 // make stuff visible to StrandsCompiler
	 this.parse      = parse;
	 this.Node       = Node;
	 this.tokens     = tokens;
	 this.consts     = consts;
	 
}).call(Narcissus);
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Narrative JavaScript compiler.
 *
 * The Initial Developer of the Original Code is
 * Neil Mix (neilmix -at- gmail -dot- com).
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

NJS_CODE = Narcissus.REGEXP; // fake node type for strands code segment

// declare it this way so that when it is evaled it gets put in the global scope
StrandsCompiler = function (options) {
	this.nodeSequence = 0;
	this.options = options || {};
	this.parseBooleanOptions("exceptions", true);
	this.parseBooleanOptions("defaultYield", true);
}

var strandscp = StrandsCompiler.prototype;

strandscp.parseBooleanOptions = function (/*...*/) {
	var options = this.options;
	for (var i = 0; i < arguments.length; i += 2) {
		var name = arguments[i];
		var value = options[name];
		if (value == null) {
			options[name] = arguments[i+1];
		} else {
			if (typeof(value) == "string")
				value = value.toLowerCase();
	
			options[name] = value == "yes" || value == "true" || value == "on" || value == "1";
		}
	}
}

strandscp.compile = function (/*string*/ code, /*string*/ scriptName,thisObject,scopeObject) {
	if (!_frm)
		var frame = {_cp:0};
	else
		var frame = _frm(this,arguments,[],[]);
	if (!frame._cp) {
		Narcissus.typeChecking = this.options.typeChecking;
		frame.n = Narcissus.parse(code, scriptName, 1); 
		this.treeify(frame.n);
		var resolver = frame.resolver = new StrandsScopeResolver();
		resolver.push(frame.n, false);
		resolver.globalChecking = !(!thisObject);
		resolver.thisObject = thisObject;
		resolver.scopeObject = scopeObject || {};
		frame._cp = 1;	
	}
	if (_frm) {
		try {
			var errors = strandscp.currentError = {};
			frame.n = this.assemble(frame.n, frame.resolver, this.options);
		} catch (e) {
			if (strands && e == strands.Suspension) // this is a continuation escape, suspend so we can try again later
				return frame._s();
			throw e; // this is a real error
		}
	}
	else {
		var errors = strandscp.currentError = {};
		frame.n = this.assemble(frame.n, frame.resolver, this.options);
	}
	if (errors.next)
		throw errors.next;
	var writer = new StrandsScriptWriter();
	if (writer.debug = this.options.debug)
		writer.sourceLines = code.split('\n');
	writer.add(frame.n);
	return writer.toString();
}

strandscp.treeify = function(n) {
	if (n.type == Narcissus.SWITCH) {
		// hack.  *sigh*  n.cases is an array, not a Node.  So we have
		// to fool our treeification process into thinking this node
		// has Node children.
		for (var i = 0; i < n.cases.length; i++) {
			n[i] = n.cases[i];
		}
		n.length = n.cases.length;
	}
	
	if (n.type == Narcissus.TRY) {
		// another hack.  catchClauses is a regular array.
		for (var i = 0; i < n.catchClauses.length; i++) {
			n["catch" + i] = n.catchClauses[i];
		}
	}
	
	// for-in constructs will use the identifier node within its varDecl
	// as the iterator value, which means it may get operated on twice
	// during the treeification process.
	if( n.treeified )
		return;

	n.treeified = true;
	if (this.options.defaultYield && (n.type==Narcissus.CALL || n.type==Narcissus.NEW || n.type==Narcissus.NEW_WITH_ARGS || n.type==Narcissus.INDEX || n.type==Narcissus.DOT))
		n.yielding = !n.yielding;
	for (var i in n) {
		if (n[i] != null &&
		    typeof(n[i]) == 'object' &&
		    n[i].constructor == Narcissus.Node &&
			i != "target" &&
			i != "parent") 
		{
			var child = n[i];

			// set this before treeification so that our parent is available
			// in the post-treeification below.
			child.parent = n;

			this.treeify(child);
			
			if(child.yielding && child.type != Narcissus.FUNCTION)
				n.yielding = true;
		}
	}

	if (n.type == Narcissus.TRY) {
		n = n.parent;
		while (n != null && n.type != Narcissus.FUNCTION)
			n = n.parent;
		
		if (n != null)
			n.hasTry = true;
	}
}

strandscp.noderize = function(opts) {
	var n = new Array();
	n.nodeID = this.nodeSequence++;
	n.toString = Narcissus.Node.prototype.toString;
	if (opts) {
		for (var i in opts) {
			n[i] = opts[i];
		}
	}
	n.isNode = true;
	return n;
}

strandscp.assemble = function(root, scopeResolver, options) {
	function ObjectType(object,field) {		
		this.object = object;
		this.field = field;
	}
	ObjectType.prototype = {
		getValue : function() {
			if (this.value)
				return this.value;
			else if (this.object && this.field)
				exceptingGet(this.object,this.field);
		},
		getType : function() {
			if (this.object && this.field)
				return exceptingGet(exceptingGet(this.object,"structure"), this.field);
		}
	}
	// make consts names available to execute()
	eval(Narcissus.consts);
	// declare these locally so they can be used within closures
	var noderize = StrandsCompiler.prototype.noderize; 
	var assemble = StrandsCompiler.prototype.assemble;
	
	var codePtrSequence = 0;
	var executed = [];
	
	var stack = [];
	var exPtrStack = [];
		
	var statements = [];
	while (root.length) {
		tailFrame = null;
		statements.push(execute(root.shift()));
	}

	for( var i = 0; i < statements.length; i++ ) {
		if (statements[i])
			root.push(statements[i]);
	}
	var lineno;
	return root;
	
	function execute(node) {
		if (node == null)
			return null;

		stack.push(node);
		lineno = node.lineno;
		switch(node.type) {
		  case SCRIPT:
			throw new Error("what's a script doing in a statement?");

		  case FUNCTION:
		  	if (scopeResolver.isYielding() && node.name) {
		  		node.scoped = true;
		  		node.name = scopeResolver.addSymbol(node.name,FUNCTION_TYPE);				
		  	}
			if (isYielding(node.body)) {
				scopeResolver.push(node.body, true);
				var vars = '';
				var scopedVars = scopeResolver.scopes[scopeResolver.scopes.length-1];
				for (var i in scopedVars) {
					vars += ',"' + i + '"';
				}
				vars = vars.substring(1);
				var params = '';
				for (var i = 0; i < node.params.length; i++) {
					params += ',"' + node.params[i].value + '"';
					scopeResolver.addSymbol(node.params[i].value,node.params[i].varType||ANY_TYPE);
				}
				params = params.substring(1);
				var openNodes = [
					codeNode('var _scope=_frm(this,arguments,[~],[~]);with(_scope){', // we could make it more compact in non debug mode with: codeNode(options.debug?'var _scope=_frm(this,arguments,[~],[~]);with(_scope){':'with(_frm(this,arguments,[~],[~])){',
				             params,vars),
				    codeNode('while(1){'),
				    (!options.exceptions ?
						codeNode("$_noex=1;") :
				        node.hasTry ?
				          codeNode('try{') :
				          codeNode('')),
				    codeNode('switch(_cp){case 0:')
				];

				while (openNodes.length)
					node.body.unshift(openNodes.pop());

				node.body.push(codeNode('return;' + (options.persistent?'case -1:return _s()}':'}')));
				if (options.exceptions && node.hasTry)
					node.body.push(codeNode("}catch(ex){_s(ex)}"));
				node.body.push(codeNode("}}"));

				assemble(node.body, scopeResolver, options);
				node.params = []; // get rid of the parameters so that the JS compression doesn't eliminate them.
				scopeResolver.pop();
			} else {
				for (var i = 0; i < node.params.length; i++)  {
					scopeResolver.addSymbol(node.params[i].value,node.params[i].varType||ANY_TYPE);
					node.params[i] = node.params[i].value;
				}
				
				scopeResolver.push(node.body, false);
				assemble(node.body, scopeResolver, options);
				scopeResolver.pop();
			}
			break;
  		  case ASSIGN: case DELETE:
	  		var propName = node[0].value;
		  	if (options.persistence && node[0].type == DOT && withinYielding(node)) {
		  		if (node.type == DELETE)
		  			node[1] = noderize({
			  					type: LIST,
					  			0: node[0][0],
			  					1: codeNode('"' + propName + '"')});
		  		else {
			  		node[1] = noderize({
			  			type: LIST,
			  			0: node[0][0],
			  			1: codeNode('"' + propName + '"'),
			  			2: node[1]});
		  		}
		  		node.type=CALL;
		  		node[0] = codeNode("_p");
		  	}
		  	else 	if (options.persistence && node[0].type == INDEX && withinYielding(node)) {
		  		var index = node[0][1];
		  		if (node.type == DELETE)
		  			node[1] = noderize({
			  					type: LIST,
					  			0: node[0][0],
			  					1: index});
		  		else {
			  		node[1] = noderize({
			  			type: LIST,
			  			0: node[0][0],
			  			1: index,
			  			2: node[1]});
				  	}
		  		node.type=CALL;
		  		node[0] = codeNode("_p");
		  	}
		  	else {
				for (var i = 0; i < node.length; i++) {
					node[i] = execute(node[i]);
				}
				if (node.type != DELETE)
			  		typeCheck(node[0],node[1]);
				break;
		  	}
		  	// fall through
		  case NEW: case NEW_WITH_ARGS: case CALL:
			// If it is persistent, we can go right to the transients on function calls
			if (node[0].type == TRANSIENT_DOT)
				node[0].type	 = DOT;
			if (node[0].type == TRANSIENT_INDEX) 
				node[0].type = INDEX;
			
			// execute our identifier and args *first*
			node[0] = execute(node[0]);
			if (node[0].value == "spawn" && node[0].type == IDENTIFIER) {
				node[1].yielding = false;
				break;
			}
			var varType;
			if (node.type == NEW)
				varType = node[0].varType;
			if (!isYielding(node) || !withinYielding(node))
				break;
			if (node[1]) {// new (without args) doesn't have a list
				if (node.type == NEW_WITH_ARGS)
					node[1].type = ARRAY_INIT;
				var newArgs = execute(node[1]);
			}
			
			codePtrSequence++;
			
			// update our code pointer
			addCode("_cp=~;",
					codePtrSequence);

			// set up our re-entry point
			newCodeSegment(codePtrSequence);
			if (node.type!=CALL) { // This is the persistent JavaScript extension to do news on objects
				node = noderize({type: CALL,
											yielding: true,
											0 : codeNode("_new"),
											1 : noderize({type: LIST,
																  0:node[0]})
				});
				if (newArgs)
					node[1][1] = newArgs;
			}							 
			// remove the call node from the stack,
			// replace it with rv if necessary
			if (stack.length > 1 && stack[stack.length-2].type == SEMICOLON) {
				// simple semicolon expression.  don't bother
				// with retval -- there aren't any dependencies
				replaceNode(null);
			} else {
				var newNode = codeNode("_r.v~", 
									 codePtrSequence);
				newNode.varType = varType;
				replaceNode(newNode);
			}
			
			var checkRetNode = noderize({
				type: IF,
				condition: noderize({
					type: STRICT_EQ,
					0: noderize({
						type: GROUP,
						0: noderize({
							type: ASSIGN,
							0: codeNode("_r.v~",
										codePtrSequence),
							1: node
						})
					}),
					1: codeNode("_S")
				}),
				thenPart: noderize({
					type: SEMICOLON,
					expression: codeNode("return _s()")
				})
			});
			
			// add our call as a statement at the top level
			statements.push(checkRetNode);
			
			break;

 		  case IF:
 		  	node.condition = execute(node.condition);
			if (isYielding(node.thenPart) ||
				(node.elsePart && isYielding(node.elsePart)))
			{				
				var thenPtr = ++codePtrSequence;
				if (node.elsePart)
					var elsePtr = ++codePtrSequence;
				var endPtr = ++codePtrSequence;
				newConditional(node.condition, thenPtr, elsePtr || endPtr);
				newCodeSegment(thenPtr);
				
				// thenPart
				execBlock(node.thenPart);
				gotoCodeSegment(endPtr);

				// elsePart
				if (node.elsePart) {
					newCodeSegment(elsePtr);
					execBlock(node.elsePart);
					gotoCodeSegment(endPtr);
				}
				
				// end if
				newCodeSegment(endPtr);

				replaceNode(null);
			} else {
				// make sure we catch any breaks or continues
				node.thenPart = execute(node.thenPart);
				if (node.elsePart)
					node.elsePart = execute(node.elsePart);
			}
			break;

  		  case FOR_IN: // varDecl/iterator, object, body
			if (node.varDecl == null) {
				node.iterator = execute(node.iterator);
			} else {
				node.varDecl = execute(node.varDecl);
			}
			node.object = execute(node.object);
			
/*			if (!isYielding(node.body)) { // we will always do it so they we can weed out dont-enums.
				node.body = execute(node.body);
				break;
			}*/
			
			// grab all items from the object and stick them in a local array
			var iterId = codePtrSequence;
			statements.push(noderize({
				type: NJS_CODE, 
				value: subst("_r.iter~=_keys(", iterId),
				lineno: node.object.lineno
			}));
			statements.push(node.object);
			addCode(");");

			// change the FOR_IN into a regular FOR
			node.type = FOR;
			node.setup = codeNode("_r.ctr~=0;", iterId);
			node.condition = codeNode("_r.ctr~<_r.iter~.length",
				                      iterId, iterId);
			node.update = codeNode("_r.ctr~++", iterId);
			var initializer = codeNode("_r.iter~[_r.ctr~]",
				                        iterId, iterId);
			 // make sure our body is a block so we can add a statement to it
			if (node.body.type != BLOCK)
				node.body = noderize({type: BLOCK, yielding: true, 0: node.body});

			if (node.varDecl == null) {
				// iterator -- create an assignment
				var assign = noderize({type: ASSIGN});
				assign.push(node.iterator);
				assign.push(initializer);
				node.body.unshift(noderize({type: SEMICOLON, expression: assign}));
			} else {
				// varDecl -- use the initializer
				node.varDecl[0].initializer = initializer;
				node.body.unshift(node.varDecl);
			}
			node.iterator = null;
			node.varDecl = null;
			// FALL THROUGH
			
		  case FOR:
			node.setup = execute(node.setup);
			if (!isYielding(node.body) && !isYielding(node.update))  {
				node.condition = execute(node.condition);
				node.update    = execute(node.update);
				node.body      = execute(node.body);
				break;
			}
			
			// turn it into a WHILE statement
			node.type = WHILE;
			
			// move the setup before the while
			if(node.setup.type != VAR  && node.setup.type != NJS_CODE)
				node.setup = noderize({type: SEMICOLON, expression: node.setup});

			statements.push(node.setup);
			node.setup = null;

			// make sure our body is a block so we can add a statement to it
			if (node.body.type != BLOCK)
				node.body = noderize({type: BLOCK, 0: node.body});

			node.updatePtr = ++codePtrSequence;
			node.body.push(newCodeSegmentNode(node.updatePtr));
			
			// make sure the proper update happens in the block
			node.body.push(noderize({type: SEMICOLON, expression: node.update}));
			node.update = null; 		  		

			// FALL THROUGH

 		  case WHILE:
 		  	if (isYielding(node)) {
 		  		node.continuePtr = ++codePtrSequence;
 		  		newCodeSegment(node.continuePtr);
 		  		node.condition = execute(node.condition);

 		  		var bodyPtr  = ++codePtrSequence;
 		  		node.breakPtr = ++codePtrSequence;
 		  		newConditional(node.condition, bodyPtr, node.breakPtr);
 		  		newCodeSegment(bodyPtr);
 		  		execBlock(node.body);
 		  		gotoCodeSegment(node.continuePtr);
 		  		newCodeSegment(node.breakPtr);

				replaceNode(null);
 		  	} else {
 		  		node.condition = execute(node.condition);
 		  		node.body = execute(node.body);
 		  	}
 		  	break;
 		  
 		  case DO:
 		  	if (isYielding(node)) {
 		  		node.continuePtr = ++codePtrSequence;
				node.breakPtr = ++codePtrSequence;
 		  		newCodeSegment(node.continuePtr);
 		  		execBlock(node.body);

 		  		newConditional(execute(node.condition), node.continuePtr, node.breakPtr);
 		  		newCodeSegment(node.breakPtr);

 		  		replaceNode(null);
 		  	} else {
 		  		node.condition = execute(node.condition);
 		  		node.body = execute(node.body);
 		  	}
 		    break;
		  
		  case BREAK:
			if (node.target.breakPtr != null) {
				replaceNode(codeNode("_cp=~;break;", 
					                 node.target.breakPtr));
			}
			break;
		  	
		  case CONTINUE:
			if (node.target.continuePtr != null) {
				replaceNode(codeNode("_cp=~;break;", 
					                 node.target.updatePtr || node.target.continuePtr));
			}
			break;
		  
		  case SWITCH:
			if (!isYielding(node))
				break;
			
			node.breakPtr = ++codePtrSequence;
			var conditional = null;
			if (node.defaultIndex >= 0) {
				node[node.defaultIndex].codePtr = ++codePtrSequence;
				conditional = codeNode(node[node.defaultIndex].codePtr);
			} else {
				conditional = codeNode(node.breakPtr);
			}
			
			for (var i = node.length - 1; i >= 0; i--) {
				if (i == node.defaultIndex)
					continue;
				
				// adjust the line numbering of the case label nodes
				removeLineNumbers(node[i].caseLabel);
				
				node[i].codePtr = ++codePtrSequence;
				conditional = noderize({
					type: CONDITIONAL,
					0: noderize({
						type: EQ,
						0: node.discriminant,
						1: node[i].caseLabel
					}),
					1: codeNode(node[i].codePtr),
					2: conditional
				});
			}
			
			statements.push(noderize({
				type: SEMICOLON,
				expression: noderize({
					type: ASSIGN,
					0: codeNode("_cp"),
					1: execute(conditional)
				})
			}));
			statements.push(codeNode("break;"));
			
			for (var i = 0; i < node.length; i++) {
				newCodeSegment(node[i].codePtr);
				execBlock(node[i].statements);
			}
			
			newCodeSegment(node.breakPtr);
			
			replaceNode(null);
			break;
			
		  case WITH:
		    if (isYielding(node)) {
		  		//throw new Error("yielding within " + Narcissus.tokens[node.type].toUpperCase() + " not supported");
		    	var oldStatements = statements;
		    	var innerStatements = statements = [];	    	
		    	node.body.unshift(codeNode('switch(_cp){case ~:',codePtrSequence));
		    	var body = execute(node.body);
		    	for (var i = 0; i < body.length; i++)
		    		if (!body[i]) {
		    			body.splice(i--,1);
		    		}
		    	statements = oldStatements;
		    	
	    		for( var i = 0; i < innerStatements.length; i++ ) {
					if (innerStatements[i]) {
						if (innerStatements[i].codeSegmentId)
							newCodeSegment(innerStatements[i].codeSegmentId);							
						node.body.push(innerStatements[i]);
					}
				}
		    	codePtrSequence++;
		    	node.body.push(codeNode('_cp=~}break;',codePtrSequence));
		    	statements.push(node);
		    	replaceNode(null);
		    	newCodeSegment(codePtrSequence);
		    }
			break;

		  case TRY:
		 	if (!isYielding(node))
		 		break;
		 	
		 	if (!options.exceptions)
		 		throw new Error("yielding within try/catch/finally not allowed when the exceptions are turned off in the compiler");

			//   set codeptr for catches, finally, endptr
			for (var i = 0; i < node.catchClauses.length; i++) {
				node.catchClauses[i].codePtr = ++codePtrSequence;
			}
			
			if (node.finallyBlock)
				node.finallyBlock.codePtr = ++codePtrSequence;

			var endPtr = ++codePtrSequence;
			
			// set exception codePtr
			var exCodePtr = node.catchClauses.length ?
			                node.catchClauses[0].codePtr :
			                node.finallyBlock.codePtr;
			
			addCode("_r.ecp=~;", exCodePtr);
			exPtrStack.push(exCodePtr);
			execBlock(node.tryBlock);
			node.finallyBlock ? gotoCodeSegment(node.finallyBlock.codePtr) :
			                    gotoCodeSegment(endPtr);
			exPtrStack.pop();

			for (var i = 0; i < node.catchClauses.length; i++) {
				var clause = node.catchClauses[i];
				newCodeSegment(clause.codePtr);

				if (i == 0) {
					// first catch block
					// set exception codePtr appropriately
					addCode("_r.ecp=~;", 
						                node.finallyBlock ? 
						                node.finallyBlock.codePtr :
						                (exPtrStack.top() || "null"));
					// reset throwing flag to prevent infinite loopage
					addCode("$_thr=false;");
				}

				// set our exception var.  This will override any masked
				// variables with the same name.  Technically this is
				// incorrect behavior.  I should fix this, but I'm too 
				// lazy right now.
				scopeResolver.addSymbol(clause.varName,ANY_TYPE)
				addCode("~ = $_ex;", scopeResolver.getSymbol(clause.varName));

				if (clause.guard) {
					clause.guard = execute(clause.guard);
					statements.push(noderize({
						type: NJS_CODE,
						value: "if(!(",
						lineno: clause.guard.lineno
					}));
					statements.push(clause.guard);
					addCode(")) {");

					// handle missed guard clause carefully						
					if (i < node.catchClauses.length - 1) {
						gotoCodeSegment(node.catchClauses[i+1].codePtr);
					} else if (node.finallyBlock) {
						gotoCodeSegment(node.finallyBlock.codePtr);
					} else if (exPtrStack.length) {
						gotoCodeSegment(exPtrStack.top());
					} else {
						addCode("throw ~;", scopeResolver.getSymbol(clause.varName));
					}
					
					addCode("}");
				}
			
				if (node.finallyBlock)
					exPtrStack.push(node.finallyBlock.codePtr);
				execBlock(clause.block);
				if (node.finallyBlock)
					exPtrStack.pop();
				
				// handle successful execution of catch clause
				if (node.finallyBlock) {
					gotoCodeSegment(node.finallyBlock.codePtr);
				} else {
					gotoCodeSegment(endPtr);
				}
			}
			
			if (node.finallyBlock) {
				newCodeSegment(node.finallyBlock.codePtr);

				// set the exception code pointer
				addCode("_r.ecp=~;", 
								    exPtrStack.top() || "null");

				execBlock(node.finallyBlock);

				// if we're throwing, rethrow, otherwise goto endPtr
				addCode("if($_thr){");
				if (exPtrStack.length) {
					gotoCodeSegment(exPtrStack.top());
				} else {
					addCode("$_except($_ex);return;");
				}
				addCode("}else{");
				gotoCodeSegment(endPtr);
				addCode("}");
			}

			newCodeSegment(endPtr);
			
			replaceNode(null);
			break;
		  case TRUE: case FALSE: node.varType = "Boolean"; break;
		  case NUMBER: 
		  	node.varType = "Number"; 
		  break;
		  case STRING: 
		  	node.varType = "String";
		  	break;
			
		  case THIS: 
  		  	node.varType = new ObjectType();
  		  	node.varType.value = scopeResolver.thisObject;
  		  	node.varType.type = scopeResolver.thisObject;
		  case DEBUGGER: case LABEL: case NULL:
		  case REGEXP: case NJS_CODE:
			// nothing to do
			break;
		
		  case IDENTIFIER:
			node.value = scopeResolver.getSymbol(node.value);
		    node.initializer = execute(node.initializer);
		    if (node.initializer)
			    typeCheck(node,node.initializer);
		  	break;
			
		  case THROW:
			node.exception = execute(node.exception);
			break;
 
		  case RETURN:
		  case SEMICOLON:
			node.expression = execute(node.expression);
			break;
		  
		  case OR: case AND:
		  	// because of the "guarding" nature of boolean comparisons, we need to
		  	// pull out comparisons with right-side yields into their own
		  	// statements and transform them separately.
		  	var left = node[0];
		  	var right = node[1];
		  	
		  	node[0] = left = execute(left);
		  	if (!isYielding(right)) {
		  		node[1] = execute(right);
		  		break;
		  	}

			var condVar = "_c" + codePtrSequence;
			
			// put the left in it's own assign statement
			statements.push(noderize({
				type: SEMICOLON,
				expression: noderize({
					type: ASSIGN,
					0: codeNode("var ~", condVar),
					1: left
				})
			}));
			
			// create a boolean node that indicates whether or not the left guards
			// against execution of the right
			var cond = codeNode(condVar);
			if (node.type == OR) {
				cond = noderize({
					type: NOT,
					value: "!",
					0: cond
				});
			}
			
			// create an if node that checks the guarded value and executes
			// the right if appropriate
			var guard = noderize({
				type: IF,
				condition: cond,
				thenPart: noderize({
					type: SEMICOLON,
					expression: noderize({
						type: ASSIGN,
						0: codeNode(condVar),
						1: right,
						yielding: true
					}),
					parent: node,
					yielding: true
				}),
				yielding: true
			});
			// execute the if node as if it were top-level
			var tmpStack = stack;
			stack = [];
			statements.push(execute(guard));
			stack = tmpStack;
			
			// finally, hand back the result of the guarding process
			node.type = NJS_CODE;
			node.value = condVar;
		  	break;
		  case VAR:
		  	if(scopeResolver.isYielding())
		  		node.scoped = true;
  			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]);
			}
			break;
		  

		  case EQ: case NE: case STRICT_EQ: case STRICT_NE:
		  case LT: case LE: case GE: case GT:
		  case TYPEOF: case NOT: case INSTANCEOF:
		  	node.varType = "Boolean";
  			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]);
			}
			break;
		  case UNARY_PLUS: case UNARY_MINUS: case INCREMENT: case DECREMENT:
		  case LSH: case RSH: case URSH:
		  case MINUS: case MUL: case DIV: case MOD:
		  	node.varType = "Number";
  			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]); // should do a type check here
			}
			break;
		  case ARRAY_INIT: case OBJECT_INIT:
		  	node.varType = "Object";
  			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]);
			}
			break;
		  
		  case PLUS: // TODO: This is pretty complicate type case
  			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]); // should do a type check here
			}
		  	if (node[0].varType == "Number" && node[1].varType == "Number")
		  		node.varType = "Number";
		  	if (node[0].varType == "String" || node[1].varType == "String")
		  		node.varType = "String";
			break;
		  case CONDITIONAL:
		  	// pull out comparisons with right-side yields into their own
		  	// statements and transform them separately.
		  	var cond = node[0];
		  	var left = node[1];
		  	var right = node[2];
					  	
		  	node[0] = cond = execute(cond);
		  	if (!isYielding(left) && !isYielding(right)) {
		  		node[1] = execute(left);
		  		node[2] = execute(right);
		  		break;
		  	}

			var condVar = "_c" + codePtrSequence;
			
			// put the left in it's own assign statement
			statements.push(noderize({
				type: SEMICOLON,
				expression: codeNode("var ~", condVar)
			}));
			
			// create an if node that checks the guarded value and executes
			// the right if appropriate
			var guard = noderize({
				type: IF,
				condition: cond,
				thenPart: noderize({
					type: SEMICOLON,
					expression: noderize({
						type: ASSIGN,
						0: codeNode(condVar),
						1: left,
						yielding: true
					}),
					parent: node,
					yielding: true
				}),
				elsePart: noderize({
					type: SEMICOLON,
					expression: noderize({
						type: ASSIGN,
						0: codeNode(condVar),
						1: right,
						yielding: true
					}),
					parent: node,
					yielding: true
				}),
				yielding: true
			});
			// execute the if node as if it were top-level
			var tmpStack = stack;
			stack = [];
			statements.push(execute(guard));
			stack = tmpStack;
			
			// finally, hand back the result of the guarding process
			node.type = NJS_CODE;
			node.value = condVar;
		  	break;
		  case BITWISE_OR: case BITWISE_XOR: case BITWISE_AND:
		  case BITWISE_NOT:
		  case VOID: 		  
		  case IN: 
		  case COMMA: 
		  case LIST:
		  case GROUP: case BLOCK:
			for (var i = 0; i < node.length; i++) {
				node[i] = execute(node[i]);
			}
			break;
		  case OBJECT_ID_REFERENCE:
		  	var get = noderize({
						type: CALL,
						parent: node.parent,
						yielding: true,
						0: codeNode("_ref"),
						1: noderize({
							type:LIST,
							0:node[0]})});
		  	get = execute(get);
		  	node.type = get.type;
		  	node.value = get.value;
		  	node.varType = new ObjectType(scopeResolver.scopeObject,node[0].value);
		  	break;
		  case IS:
		  	node.type = CALL;
		  	node[1] = execute(noderize({type: LIST,
		  													0: node[0],
		  													1: node[1]}));
		  	node[0] = codeNode("_is");
		  	break;
		  case INDEX: 
		  	if (options.persistence && withinYielding(node) && node.parent.type != CALL) {
			  	var get = noderize({
							type: CALL,
							parent: node.parent,
							yielding: true,
							0: codeNode("_g"),
							1: noderize({
								type:LIST,
								0:node[0],
								1:node[1]})});
			  	get = execute(get);
			  	node.type = get.type;
			  	node.value = get.value;
			  	break;
			}
			// else fall through
		  case TRANSIENT_INDEX:
				for (var i = 0; i < node.length; i++) {
					node[i] = execute(node[i]);
				}
				node.type = INDEX;
				break;
		  case DOT:
			if (options.persistence && withinYielding(node) && node.parent.type != CALL) {			// don't execute n[1] because it might resolve to a scoped var		
			  	var get = noderize({
							type: CALL,
							parent: node.parent,
							yielding: true,
							0: codeNode("_g"),
							1: noderize({
								type:LIST,
								0:node[0],
								1:codeNode('"' + node[1].value + '"')})});
			  	get = execute(get);
			  	node.type = get.type;
			  	node.value = get.value;
			  	if (typeof node[0].varType == "Object")
			  		node.varType = new ObjectType(node[0].varType.getValue(),node[1].value);
			  	break;
			}
			// else fall through to TRANSIENT_DOT
		  case TRANSIENT_DOT:
//		  		if (!options.persistence)
	//				throw new Error("direct reference syntax (.#) not supported without persevere");
			  	node[0] = execute(node[0]);
				node.type = DOT;
		  		break;

		  case PROPERTY_INIT:
			// don't execute n[0] because it might resolve to a scoped var
		  	node[1] = execute(node[1]);
		  	break;

		  default:
			throw new Error("PANIC: unknown node type " + Narcissus.tokens[node.type]);
		}
		
		return stack.pop();
	}
	
	function subst(str /*, ... */) {
		for(var i = 1; i < arguments.length; i++) {
			str = str.replace("~", arguments[i]);
		}
		return str;
	}

	function replaceNode(node) {
		stack.pop();
		stack.push(node);
	}
	
	function execBlock(set) {
		if (set.type == BLOCK) {

			for (var i = 0; i < set.length; i++) {
				statements.push(execute(set[i]));
			}
			return set;
		} else {
			set = execute(set);
			statements.push(set);
			return set;
		}
	}
	
	function newCodeSegment(id) {
		var newNode = newCodeSegmentNode(id);
		newNode.codeSegmentId = id;
		statements.push(newNode);
	}
	
	function newCodeSegmentNode(id) {
		return codeNode("case ~:", id)
	}
	
	function gotoCodeSegment(id) {
		addCode("_cp=~;break;",id);
	}

	function newConditional(node, thenPtr, elsePtr) {
		// turn the if(cond) into something like:
		//   njf0.cp = (cond) ? 1 : 2; break; case 1:
		statements.push(noderize({
			type: NJS_CODE,
			value: subst("_cp=("),
			lineno: node.lineno
		}));
		statements.push(node);
		addCode(")?~:~;break;", thenPtr, elsePtr);
	}
	
	function addCode(str/*, ...*/) {
		statements.push(codeNode.apply(this, arguments));
	}
	
	function codeNode(str/*, ...*/) {
		return noderize({
			type: NJS_CODE,
			value: subst.apply(this, arguments)
		})
	}

	function isYielding(node) {
		return node != null && node.yielding;
	}
	function withinYielding(node) {
		var parentNode = node;
		while (parentNode) {
			if (parentNode.type == FUNCTION)
				return parentNode.yielding;
			parentNode = parentNode.parent;
		}
		return false;
	}
		
	function removeLineNumbers(node) {
		delete node.lineno;
		for (n in node) {
			if (node[n] != null 
			    && typeof(node[n]) == "object" 
			    && n != "parent"
			    && node[n].isNode) 
			{
				removeLineNumbers(node[n]);
			}
		}
	}
	function exceptingGet(object,field) {
		var value = pjs.get(object,field);
		if (value == strands.Suspension)
			throw value;
		return value;
	}
	function typeCheck(variable, value) {
		if (!options.typeChecking)
			return value;
		var valueType = value.varType;
		if (typeof valueType == "object") // we allow varTypes to be just strings but usually they are objects
			valueType = valueType.type;
		if (!valueType)
			valueType = scopeResolver.getType(value.value);
		if (!valueType)
			valueType = ANY_TYPE;
		if (!variable || variable == ANY_TYPE)
			return value;
		if (typeof variable != "string")
			var variableType =scopeResolver.getType(variable.value);
		if (variableType && variableType != valueType && variableType != ANY_TYPE)
			addError("Can not assign a value of type " + valueType + " when a " + variableType + " is required");
		if (value.varType) {
			variableType = scopeResolver.getSymbolObject(variable.value);
			if (variableType)
				variableType.value = value.varType.value;
		}
		return value;
	}
	function addError(message) {
		strandscp.currentError = strandscp.currentError.next ={message:message, lineNumber:lineno || 0};
	}
}
var FUNCTION_TYPE = {value:"function"};
var ANY_TYPE = {toString:function() {return "any"}};	


function StrandsScopeResolver() {
	this.scopes = [];
	this.yieldingStatus = [];
}

var nsrp = StrandsScopeResolver.prototype;
nsrp.addError = function(message) {
	strandscp.currentError = strandscp.currentError.next ={message:message, lineNumber:lineno || 0};
}
nsrp.push = function(n, isYielding) {
	this.scopes.push({});
	this.yieldingStatus.push(isYielding);
	if(n.varDecls) {
		for(var i = 0; i < n.varDecls.length; i++) {
			this.addSymbol(n.varDecls[i].value,n.varDecls[i].varType||ANY_TYPE);
		}
	}
	if(n.funDecls) {
		for(var i = 0; i < n.funDecls.length; i++) {
			this.addSymbol(n.funDecls[i].name,FUNCTION_TYPE);
		}
	}
}
nsrp.pop = function() {
	this.yieldingStatus.pop();
	return this.scopes.pop();
}

// we need to namespace all symbols so that we don't
// accidentally run across native object members
// (such as "constructor")
nsrp.addSymbol = function(name,type) {
	this.scopes.top()[name] = {type:type.value};
	return this.getSymbol(name);
}
nsrp.getType = function(name) {
	var object = this.getSymbolObject(name);
	if (object && object.type=="any")
		return ANY_TYPE;
	if (object && typeof object.type=="string" && object.type != "String" && object.type != "Number" && object.type != "Boolean"&& object.type != "Object" && object.type != "function") {
		var symbolObject = this.getSymbolObject(object.type);
		if (!symbolObject || !symbolObject.value) 
			this.addError("You must use a known symbol with a known value as a type",lineno);
		object.type = this.getSymbolObject(object.type).value;
	}
	if (object && object.type)
		return object.type;
	return ANY_TYPE;
}
nsrp.getSymbolObject = function(name) {
	for (var i = this.scopes.length; i > 0;) {
		i--;
		if (this.scopes[i][name]) 
			return this.scopes[i][name];
	}
	return;
}

nsrp.getSymbol = function(name) {
	if(!this.scopes.top()[name] && Object.prototype[name])
		throw new Exception("You can not reference a variable in a outer scope (or global scope) with a name from Object.prototype.  Please rename your variable in order to reference it");
	if (!this.globalChecking || window[name] || this.scopeObject[name])
		return name;
	for (var i = this.scopes.length; i > 0;) {
		i--;
		if (this.scopes[i][name]) 
			return name;
	}
	this.addError("The identifier " + name + " was not found");	
	return name;
}
nsrp.getCurrentFrame = function() {
	var id = this.scopes.length - 1;
	if (id < 0) {
		throw new Error("compiler error: empty scope resolver");
	}
	return "njf" + id;
}
nsrp.isYielding = function() {
	if(this.scopes.length == 0)
		return false;
	
	return this.yieldingStatus.top();
}

nsrp.dump = function() {
	for (var i= this.scopes.length - 1; i >= 0; i--) {
		var list = "frame " + i + ": ";
		for (var n in this.scopes[i]) 
			list += n + ", ";

		print(list);
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Narrative JavaScript compiler.
 *
 * The Initial Developer of the Original Code is
 * Neil Mix (neilmix -at- gmail -dot- com).
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

function StrandsScriptWriter() {
	this.lines = [];
	this.infix_lookup = {};
	for( var i = 0; i < this.infix_operators.length; i++ ) {
		this.infix_lookup[this.infix_operators[i]] = true;
	}
	this.prefix_lookup = {};
	for( var i = 0; i < this.prefix_operators.length; i++ ) {
		this.prefix_lookup[this.prefix_operators[i]] = true;
	}
	this.simple_lookup = {};
	for( var i = 0; i < this.simple_tokens.length; i++ ) {
		this.simple_lookup[this.simple_tokens[i]] = true;
	}
}

StrandsScriptWriter.dump = function(n) {
	var o = new StrandsScriptWriter();
	o.add(n);
	print(o);
}

var strandsswp = StrandsScriptWriter.prototype;

strandsswp.infix_operators = [
	 ',',
	 '||',
	 '&&',
	 '|',
	 '^',
	 '&',
	 '===',
	 '==',
	 '!==',
	 '!=',
	 '<<',
	 '<=',
	 '<',
	 '>>>',
	 '>>',
	 '>=',
	 '>',
	 '-',
	 '+',
	 '*',
	 '/',
	 '%',
	 '.',
	 '.#',
	 '='
];

strandsswp.prefix_operators = [
	'!',
	'~',
	'unary_plus',
	'unary_minus'
];

strandsswp.simple_tokens = [
	"identifier",
	"number",
	"regexp",
	"true",
	"false",
	"null",
	"this"
];

strandsswp.add = function(n) {
	if( n == null ) 
	throw new Error("null token");
	if( arguments.length > 1 ) throw new Error("too many args");
	if( Narcissus.tokens[n.type] == null ) throw new Error("not a valid token: " + n);
	var type = Narcissus.tokens[n.type].toLowerCase();
	var method = "write_" + type;
	if( this[method] ) {
		this[method](n);
	} else if( this.infix_lookup[type] ) {
		this.write_infix_operator(n);
	} else if( this.prefix_lookup[type] ) {
		this.write_prefix_operator(n);
	} else if( this.simple_lookup[type] ) {
		this.write(n, n.value);
	} else {
		throw new Error("ScriptWriter Error: unknown type: " + Narcissus.tokens[n.type]);
	}
}

strandsswp.addBlock = function(n) {
	// the compiler can rewrite single statements into multiple statements
	// therefore, we should put brackets around single statements to be safe.
	if(n.type == Narcissus.BLOCK) {
		this.add(n);
	} else {
		this.write(n, "{");
		this.add(n);
		this.write(null, "}");
	}
}

strandsswp.write = function(n, text) {
	if (text == null) 
		throw new Error("null text: " + n);
	var lineno = n && n.lineno >= this.lines.length ? n.lineno : this.lines.length - 1;
	var line = this.lines[lineno] || [];
	line.push(text);
	this.lines[lineno] = line;
}

strandsswp.last = function() {
	return this.lines.top().top();
}

strandsswp.pop = function() {
	return this.lines.top().pop();
}

strandsswp.toString = function() {
	var output = [];
	// Note: line numbers start at 1
	for( var i = 1; i < this.lines.length+1; i++ ) {
		if( this.lines[i] != null ) {
			if (this.debug && this.sourceLines[i-1])
				output.push("/*" + this.sourceLines[i-1].replace(/\*\//g,'* ') + "\t\t\t\t\t\t*/");
			for( var j = 0; j < this.lines[i].length; j++ ) {
				output.push(this.lines[i][j]);
			}
		}
		else {
			if (this.debug && this.sourceLines[i-1])
				output.push("/*" + this.sourceLines[i-1].replace(/\*\//g,'* ') + "\t\t\t\t\t\t*/");
		}
		output.push("\n");
	}
	return output.join("");
}

strandsswp.write_script = function(n,output) {
	for (var i = 0; i < n.length; i++) {
		this.add(n[i]);
	}
}

strandsswp.write_infix_operator = function(n) {
	this.add(n[0]);
	if (n.type == Narcissus.ASSIGN && n[0].assignOp != null)
		this.write(n, Narcissus.tokens[n[0].assignOp]);
	this.write(n, Narcissus.tokens[n.type]); // don't use n.value -- that's incorrect for DOT
	this.add(n[1]);
}

strandsswp.write_prefix_operator = function(n) {
	this.write(n, n.value);
	this.add(n[0]);
}

strandsswp.write_function = function(n) {
	if(n.scoped) {
		this.write(n, n.name);
		this.write(n, " = ");
	}
	this.write(n, "function");
	if(n.name && !n.scoped) {
		this.write(n, " ");
		this.write(n, n.name);
	}
	this.write(n, "(");
	for (var i = 0; i < n.params.length; i++)
		if (n.params[i].value)
			n.params[i] = n.params[i].value;
	this.write(n, n.params);
	this.write(null, "){");
	this.add(n.body);
	this.write(null, "}");
	if(n.scoped) {
		this.write(null, ";");
	}
}

strandsswp.write_var = function(n) {
	if(!n.scoped) this.write(n, "var ");
	for( var i = 0; i < n.length; i++ ) {
		this.write(n[i], n[i].value);
		if( n[i].initializer ) {
			this.write(n[i], "=");
			this.add(n[i].initializer);
		}
		if( i == n.length - 1 ) {
			this.write(null, ";");
		} else {
			this.write(n[i], ",");
		}
	}
}

strandsswp["write_;"] = function(n) {
	if(!n.expression) 
		return;
	this.add(n.expression);
	this.write(null, ";");
}


strandsswp.write_conditional = function(n) {
	this.add(n[0]);
	this.write(null, "?");
	this.add(n[1]);
	this.write(null, ":");
	this.add(n[2]);
}

strandsswp["write_++"] = function(n) {
	if( n.postfix ) {
		this.add(n[0]);
		this.write(n, "++");
	} else {
		this.write(n, "++");
		this.add(n[0]);
	}
}

strandsswp["write_--"] = function(n) {
	if( n.postfix ) {
		this.add(n[0]);
		this.write(n, "--");
	} else {
		this.write(n, "--");
		this.add(n[0]);
	}
}

strandsswp.write_index = function(n) {
	this.add(n[0]);
	this.write(null, '[');
	this.add(n[1]);
	this.write(null, ']');
}

strandsswp.write_array_init = function(n) {
	this.write(n, '[');
	for( var i = 0; i < n.length; i++ ) {
		if (i > 0) {
			this.write(null, ",");
		}
		this.add(n[i]);
	}
	this.write(null, ']');
}

strandsswp.write_object_init = function(n) {
	this.write(n, '{');
	for(var i = 0; i < n.length; i++) {
		this.add(n[i]);
		if( i != n.length - 1 ) {
			this.write(n[i], ',');
		}
	}
	this.write(null, '}');
}

strandsswp.write_property_init = function(n) {
	this.add(n[0]);
	this.write(n[0], ':');
	this.add(n[1]);
}

strandsswp.write_block = function(n) {
	this.write(n, '{');
	for( var i = 0; i < n.length; i++ ) {
		this.add(n[i]);
	}
	this.write(null, "}");
}

strandsswp.write_group = function(n) {
	this.write(n, '(');
	for( var i = 0; i < n.length; i++ ) {
		this.add(n[i]);
	}
	this.write(null, ")");
}

strandsswp.write_list = function(n) {
	this.write(null, '(');
	for( var i = 0; i < n.length; i++ ) {
		this.add(n[i]);
		if( i != n.length - 1 ) {
			this.write(null, ",");
		}
	}
	this.write(n, ')');
}

strandsswp.write_label = function(n) {
	this.write(n, n.label);
	this.write(n, ":");
	this.add(n.statement);
}

strandsswp.write_for = function(n) {
	this.write(n, "for(");
	this.add(n.setup);
	// var statements are never associated with a semicolon, so our
	// write statements automatically insert one.  Therefore, we
	// need to check if a semicolon was already inserted for us.
	if(this.last() != ';') this.write(null, ";");
	this.add(n.condition);
	this.write(null, ";");
	this.add(n.update);
	this.write(null, ")");
	this.add(n.body);
}

strandsswp.write_call = function(n) {
	this.add(n[0]);
	this.add(n[1]);
}

strandsswp.write_new_with_args = function(n) {
	this.write(n, "new ");
	this.add(n[0]);
	if (n[1])
		this.add(n[1]);
}

strandsswp.write_new = function(n) {
	this.write(n, "new ");
	this.add(n[0]);
	this.write(null, "()");
}

strandsswp.write_string = function(n) {
	var value = n.value.replace(/(\\|")/g, "\\$1");
	value = value.replace(/\n/g, "\\n");
	this.write(n, '"');
	this.write(n, value);
	this.write(n, '"');
}

strandsswp.write_switch = function(n) {
	this.write(n, "switch(");
	this.add(n.discriminant);
	this.write(null, "){");
	for( var i = 0; i < n.cases.length; i++ ) {
		this.add(n.cases[i]);
	}
	this.write(null, "}");
}

strandsswp.write_case = function(n) {
	this.write(n, "case ");
	this.add(n.caseLabel);
	this.write(null, ":");
	this.add(n.statements);
}

strandsswp.write_default = function(n) {
	this.write(n, "default:");
	this.add(n.statements);
}

strandsswp.write_delete = function(n) {
	this.write(n, "delete ");
	for( var i = 0; i < n.length; i++ ) {
		this.add(n[i]);
	}
}

strandsswp.write_while = function(n) {
	this.write(n, "while(");
	this.add(n.condition);
	this.write(null, ")");
	this.add(n.body);
}

strandsswp.write_do = function(n) {
	this.write(n, "do");
	this.add(n.body);
	this.write(n.condition, " while(");
	this.add(n.condition);
	this.write(null, ");");
}

strandsswp.write_if = function(n) {
	this.write(n, "if(");
	this.add(n.condition);
	this.write(null, ")");
	this.addBlock(n.thenPart);
	if(n.elsePart != null ) {
		this.write(n.elsePart, " else ");
		this.add(n.elsePart);
	}
}

strandsswp.write_typeof = function(n) {
	this.write(n, "typeof ");
	this.add(n[0]);
}
strandsswp.write_instanceof = function(n) {
	this.add(n[0]);
	this.write(n, " instanceof ");
	this.add(n[1]);
}

strandsswp.write_try = function(n) {
	this.write(n, "try ");
	this.add(n.tryBlock);
	for( var i = 0; i < n.catchClauses.length; i++ ) {
		var clause = n.catchClauses[i];
		this.write(clause, " catch(");
		this.write(null, clause.varName);
		if (clause.guard) {
			this.write(null, " if(");
			this.add(clause.guard);
			this.write(null, ")");
		}
		this.write(null, ")");
		this.add(clause.block);
	}
	if( n.finallyBlock != null ) {
		this.write(n.finallyBlock, " finally ");
		this.add(n.finallyBlock);
	}
}

strandsswp.write_throw = function(n) {
	this.write(n, "throw(");
	this.add(n.exception);
	this.write(n, ");");
}

strandsswp.write_for_in = function(n) {
	this.write(n, "for(");
	if( n.varDecl == null ) {
		this.add(n.iterator);
	} else {
		this.add(n.varDecl);
		// variable writes automatically add a semicolon,
		// we need to remove it.
		this.pop();
	}
	this.write(null, " in ");
	this.add(n.object);
	this.write(null, ")");
	this.add(n.body);
}

strandsswp.write_with = function(n) {
	this.write(n, "with(");
	this.add(n.object);
	this.write(null, ")");
	this.add(n.body);
}

strandsswp.write_void = function(n) {
	this.write(n, "void ");
	this.add(n[0]);
}

strandsswp.write_break = function(n) {
	this.write(n, "break;");
}

strandsswp.write_continue = function(n) {
	this.write(n, "continue;");
}

strandsswp.write_debugger = function(n) {
	this.write(n, "debugger;");
}

strandsswp.write_return = function(n) {
	this.write(n, "return");
	if( n.expression ) { // yes, value has two possible meanings...
		this.write(null, " ");
		this.add(n.expression);
	}
	this.write(null, ";");
}

strands.compiler = new StrandsCompiler({exceptions: true, persistence:false, compress : false});
strands.compiler.loadAndCompile = function(url){
	var frame = _frm(this,arguments,['url'],[]);
	var result = strands.request(frame.url,'GET');		
	if (result == frame._S) return frame._s();
	eval(this.compile(result),url);
}
strands.compiler.Function = function(source,thisObject,scopeObject,runAt) {
	with(_frm(this,arguments,['source','thisObject','scopeObject','runAt'],[])) {
		if (!source)
			source = "function() {\n}";
		 var code = this.compile("temp=" + source, "input",thisObject,scopeObject);
		 if (code == _S) return _s();	 
		var func = eval(code);
		if (runAt == "server") {
			func = function() {
				persevere.serverCall(this,arguments);
			}
			func._psv15 = func.toString();
		}
		else
			func._psv15 = code.substring(5,code.length-3);
		func['function'] = source;
		if (runAt)
			func.runAt = runAt;
		return func;
		}
}

strands.request = function(url, method, postData) {
	var frame = _frm(this,arguments,[],[]);
	if (frame._cp == 0) {
		var getXMLHttpRequest = function () {
			if (parent.XMLHttpRequest)
		        return new parent.XMLHttpRequest();
			else if (window.ActiveXObject)
		        return new ActiveXObject("Microsoft.XMLHTTP");
		}
	 	var xhr = getXMLHttpRequest();
		frame.future = new Future();
		var ajaxDataReader = function () {
			if (xhr.readyState == 4) {
		        // only if "OK"
		        var loaded;
		        try {
		        	var status = xhr.status;
		        	loaded = xhr.responseText.length > 0;//firefox can throw an exception right here
		        } catch(e) {}
		        if (loaded) 
    				frame.future.fulfill(xhr.responseText);				
				else
					frame.future.interrupt();
        		xhr = null; // This is to correct for IE memory leak
			}
		}
		frame._cp = 1;
	    xhr.open(method || "POST", url, true); 
		xhr.onreadystatechange = ajaxDataReader;
	    xhr.send(postData);
	}
	var result = frame.future.result();
	if (result == frame._S) frame._s();
	return result;
}
strands.compiler.tryCatchCompile = function(source,name,persistence,debug) {
	try {
		this.options.persistence = persistence;
		this.options.debug = debug;
		return this.compile(source);
	}
	catch (e) {
		return "alert('ERROR in " + name.replace(/'/g,'') + ": line " + e.lineNumber + ": " + e.message + "');";
	}
}

