//=================================================================================

SWYM.operators = {
	";":  {precedence:1, infix:true, postfix:true,
		customExec:function(parsetree)
		{
			if( parsetree.children[1] )
			{
				SWYM.ExecDefault(parsetree.children[0]);
				return SWYM.ExecDefault(parsetree.children[1]);
			}
			else
			{
				return SWYM.ExecDefault(parsetree.children[0]);
			}
		}
	},

	"yield": {precedence:5, prefix:true,
		customExec:function(parsetree)
		{
			var rval = SWYM.ExecDefault(parsetree.children[1]);
			if( !SWYM.scope.Yielded || !SWYM.scope.Yielded.isSwymArray )
			{
				SWYM.scope.Yielded = SWYM.NewArray();
			}
			
			SWYM.scope.Yielded = SWYM.CommaOp(SWYM.scope.Yielded, rval);
			return rval;
		}},

	":": {precedence:10, infix:true,
		//takeBrackets:true,
		customExec:function(parsetree){ return SWYM.declOp(parsetree); }},

	"else": {precedence:20, infix:true,
		customExec:function(parsetree)
		{
			var lval = SWYM.ExecDefault(parsetree.children[0]);
			if ( !SWYM.isSwymArray(lval) || SWYM.getArrayLength(lval) > 0 )
				return lval;

			//no values on lhs, fallback to the rhs.
			var rhs = parsetree.children[1];

			//else will suppress your first pair of {}s.
			if ( rhs && rhs.op && rhs.op.text === "(lambda{})" )
				return SWYM.ExecDefault(rhs.children[0]);
			else
				return SWYM.ExecDefault(rhs);
		}

	},
	
	"~": {precedence: 25, postfix:function(a,b)
		{
			return {value:a.value, setContents:a.setContents, passByReference:a};
		}},
		
	"=":  {precedence:30, infix:true,
		customExec:function(parsetree)
		{
			var lhs = SWYM.ExecDefault(parsetree.children[0]);
			var rhs = SWYM.ExecDefault(parsetree.children[1]);
			if( !lhs || !rhs )
			{
				SWYM.LogError(0, "Fsckup: = requires two subexpressions");
				return {value:undefined, error:true};
			}
			
			if( SWYM.isSwymArray(lhs) )
				var leftLen = SWYM.getArrayLength(lhs);
			else
				var leftLen = 1;
			
			if( SWYM.isSwymArray(rhs) )
				var rightLen = SWYM.getArrayLength(rhs);
			else
				var rightLen = 1;
			
			// if either side is empty, we have nothing to do
			if( leftLen === 0 || rightLen === 0 )
				return;
				
			if( !SWYM.isSwymArray(rhs) )
			{
				if( leftLen === rightLen )
				{
					// nice, simple, single value assignment
					return SWYM.assignmentOp(lhs, rhs, parsetree.op );
				}
				else
				{
					// multiple vars all get assigned the same one value
					return SWYM.MultiExec(lhs, function(lvar){ return SWYM.assignmentOp(lvar, rhs, parsetree.op); });
				}
			}
			else if( leftLen === rightLen )
			{
				// multiple vars assigned one value each
				var rvalues = SWYM.MultiExec(rhs, function(rval){return {value:rval.value}});
				var multiresult = SWYM.NewArray();

				for (var Idx = 0; Idx < leftLen; Idx++)
				{
					multiresult.push( SWYM.assignmentOp(SWYM.indexOp(lhs,Idx), SWYM.indexOp(rvalues,Idx), parsetree.op) );
				}
				
				return multiresult;
			}
			else
			{
				SWYM.LogError(0, "Multi-value = requires the same number of values on each side");
				return {value:undefined, error:true};
			}
		}
	},//function(a,b,op){return SWYM.assignmentOp(a,b,op);} },
	"+=": {precedence:30, infix:function(a,b,op){return SWYM.assignmentOp(a,{value:a.value + b.value},op);} },
	"-=": {precedence:30, infix:function(a,b,op){return SWYM.assignmentOp(a,{value:a.value - b.value},op);} },
	"*=": {precedence:30, infix:function(a,b,op){return SWYM.assignmentOp(a,{value:a.value * b.value},op);} },
	"/=": {precedence:30, infix:function(a,b,op){return SWYM.assignmentOp(a,{value:a.value / b.value},op);} },
	"%=": {precedence:30, infix:function(a,b,op){return SWYM.assignmentOp(a,{value:a.value % b.value},op);} },
		
	// "/": {precedence:39, prefix} would go here if it wasn't merged with division, later
	
	",":  {precedence:40, infix:true, postfix:true,
		customExec:function(parsetree)
		{
			return SWYM.CommaOp(SWYM.ExecDefault(parsetree.children[0]), SWYM.ExecDefault(parsetree.children[1]));
		}
	},

	"?":  {precedence:41, postfix:true,
		customExec:function(parsetree)
		{
			var inputResults = SWYM.ExecDefault(parsetree.children[0]);

			return SWYM.MultiExec(inputResults, function(x)
				{
					if ( SWYM.isTruthy( x.value ) )
						return x;
					else
						return undefined;
				});
		}
	},
	
	"&&": {precedence:60,infix:true,
		customExec:function(parsetree)
		{
			var a = SWYM.ResolveToBool(SWYM.ExecDefault(parsetree.children[0]));
			if( !a.error && SWYM.isTruthy(a.value) )
			{
				return SWYM.ResolveToBool(SWYM.ExecDefault(parsetree.children[1]));
			}
			else
			{
				return a;
			}
		}},
	"||": {precedence:60,infix:true,
		customExec:function(parsetree)
		{
			var a = SWYM.ResolveToBool(SWYM.ExecDefault(parsetree.children[0]));
			if( a.error || SWYM.isTruthy(a.value) )
			{
				return a;
			}
			else
			{
				return SWYM.ResolveToBool(SWYM.ExecDefault(parsetree.children[1]));
			}
		}},

	"!": {precedence:65,prefix:function(a,b){return {value:!SWYM.isTruthy(b.value)}}},
	
	"is": {precedence:70, infix:function(a,b){ return {value:SWYM.patternDescribes(b.value, a)}; }},
	
	"..":  {precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, true, true, undefined); }},
	"..<": {precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, true, false, 1); }},
	"<..": {precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, false, true, 1); }},
	"<..<":{precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, false, false, 1); }},
	"..>": {precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, true, false, -1); }},
	">..": {precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, false, true, -1); }},
	">..>":{precedence:75, infix:function(a,b){ return SWYM.RangeOp(a,b, false, false, -1); }},
	
	// I cringe at all the new objects being created here, but this will do for the proof-of-concept implementation.
	"==": {precedence:80,infix:function(a,b){return { value:SWYM.isEqual(a,b) }}},
	"===": {precedence:80,infix:function(a,b){return {value:SWYM.cell(a)===SWYM.cell(b)}}},
	"!=": {precedence:80,infix:function(a,b){return {value:a.value!==b.value}}},
	">":  {precedence:81,infix:function(a,b){return SWYM.ComparisonOp(a, function(v){return v.value > b.value})}},
	">=": {precedence:81,infix:function(a,b){return SWYM.ComparisonOp(a, function(v){return v.value >= b.value})}},
	"<":  {precedence:81,infix:function(a,b){return SWYM.ComparisonOp(a, function(v){return v.value < b.value})}},
	"<=": {precedence:81,infix:function(a,b){return SWYM.ComparisonOp(a, function(v){return v.value <= b.value})}},
	
	"|": {precedence:91,
			infix:function(a,b){
				if(a && b && SWYM.isSwymArray(a.value) && SWYM.isSwymArray(b.value))
				{
					var result = SWYM.MultiExec(a.value, function(an){ return an; });
					SWYM.MultiExec(b.value, function(bn){ result.push(bn); });
					return {value:result};
				}
				else
				{
					SWYM.LogError(0,"Can't append "+a.value+" with "+b.value);
					return{value:undefined, error:true};
				}
			},
			prefix:function(a,b,op){return SWYM.assignmentOp(b,{value:b.value+1},op);},
			postfix:function(a,b,op){ var temp = a.value; SWYM.assignmentOp(a,{value:a.value+1},op); return {value:temp}; }
		},
	"--": {precedence:91,
			prefix:function(a,b,op){return SWYM.assignmentOp(b,{value:b.value-1},op);},
			postfix:function(a,b,op){ var temp = a.value; SWYM.assignmentOp(a,{value:a.value-1},op); return {value:temp}; }
		},
	
	"-": {precedence:101,
		infix:function(a,b){return {value:a.value-b.value}},
		prefix:function(a,b){return {value:-b.value}}
	},
	"+": {precedence:102,infix:function(a,b){return {value:a.value+b.value}}},
	"+_": {precedence:102,infix:SWYM.PlusOrMinusExec,prefix:SWYM.OpDebug},
	"±": {precedence:102,infix:SWYM.PlusOrMinusExec,prefix:SWYM.OpDebug},
	"*": {precedence:103,infix:function(a,b){return {value:a.value*b.value}}},
	"/": {precedence:104, prefixprecedence:39, // ugh, special case: /a*b means /(a*b), yet 1/a*b means (1/a)*b
		infix:true,
		prefix:true,
		customExec:function(parsetree)
			{
				if( parsetree.children[0] )
				{
					// division
					var lhsResult = SWYM.ExecDefault(parsetree.children[0]);
					if( lhsResult && lhsResult.error )
						return lhsResult;
					
					var rhsResult = SWYM.ExecDefault(parsetree.children[1]);
					if( rhsResult && rhsResult.error )
						return rhsResult;
					
					return SWYM.MultiExec(lhsResult, function(lv){
						return SWYM.MultiExec(rhsResult, function(rv){
							return {value: lv.value/rv.value};
						});
					});
				}
				else
				{
					// resolve quantifier
					var resolveResult = SWYM.ExecDefault(parsetree.children[1]);
					return SWYM.ResolveToBool(resolveResult);
				}
			}
	},
	"%": {precedence:104,infix:function(a,b){return {value:a.value%b.value}}},

	"$": {precedence:120,prefix:true,
			customExec:function(parsetree)
			{
				var rinput = SWYM.ExecDefault(parsetree.children[1]);
				var result = "";
				SWYM.MultiExec(rinput, function(rv){
					if( !rv || rv.value === undefined )
						SWYM.LogError(0,"Fsckup: null rv during string interp");
					else
						result += rv.value;
				});

				return {value:SWYM.StringWrapper(result)};
			}
		},
	
	"(lambda{})": { customExec:function(parsetree)
		{
			return {value:SWYM.NewLambda(parsetree.children[0])};
		}},

	"(merge[])": { customExec:function(parsetree)
		{
			var result = SWYM.NewArray();

			if( parsetree.children[0] )
			{
				var contents = SWYM.ExecDefault(parsetree.children[0]);
				SWYM.MultiExec(contents, function(v){ result.push( SWYM.PassByValue(v) ); });
			}
			
			return {value:result};
		}},

	"(parentheses())": { customExec:function(parsetree)
		{
			if( parsetree.children[0] )
				return SWYM.ExecDefault(parsetree.children[0]);
			else
				{value:null};
		}},
		
	"(fn)": {precedence:150,
			takeBrackets:true,
			postfix:true,
			standalone:true,
			customExec:SWYM.fnOp
		},
	
	"(str++)": {precedence:200, infix:function(a,b){ return {value:SWYM.StringWrapper(""+a.value+b.value)}; }
/*		customExec:function(parsetree)
		{
			var lhsResult = SWYM.ExecDefault(parsetree.children[0]);
			if( lhsResult && lhsResult.error )
				return lhsResult;
			
			var rhsResult = SWYM.ExecDefault(parsetree.children[1]);
			if( rhsResult && rhsResult.error )
				return rhsResult;
			
			var zresult = "";
			
			SWYM.MultiExec(lhsResult, function(lv){
				if( !lv )
				{
					SWYM.LogError(0,"Fsckup: null lv during string interp");
				}
				else
				{
					// frightening Javascript bug: this was called 'result',
					// and was getting the 'result' from inside MultiExec!!!!
					zresult += lv.value;
				}
			});
			
			SWYM.MultiExec(rhsResult, function(rv){
				if( !rv )
					SWYM.LogError(0,"Fsckup: null rv during string interp");
				else
					zresult += rv.value;
			});

			return {value:SWYM.StringWrapper(zresult)};
		}*/
	}
};

//==================================================================================

SWYM.DefaultGlobalScope = {
	"#etcIndex": {value:undefined},
	"#outOfBoundsFlag": {value:undefined},
	".repeat": {value:function(params){
			if( params.length > 1 )
			{
				//x.repeat{...}
				do
				{
					var result = (params[1].value)([params[0]]);
				}
				while( result && SWYM.isTruthy(result.value) );
			}
			else
			{
				//repeat{...}
				do
				{
					var result = (params[0].value)([SWYM.scope.it]);
				}
				while( result && SWYM.isTruthy(result.value) );
			}
			return {value:null};
		}},
	".sqrt": {value:function(params)
		{
			return {value: Math.sqrt(params[0].value) };
		}},
	".while": {value:function(params)
		{
			var condition = (params[0].value)([]);
			while( condition && SWYM.isTruthy(condition.value) )
			{
				(params[1].value)([]);
				condition = (params[0].value)([]);
			}
			return {value:null};
		}},
	".{}": {value:function(params)
		{
			return SWYM.MultiExec(params[0], function(v){return params[1].value([v])});
		}},
	".at": {value:function(params)
		{ 
			var indexed = SWYM.indexOp(params[0].value, params[1].value);
			return indexed;
		}},
	".getFromEnd": {value:function(params)
		{
			if( SWYM.isSwymArray(params[0].value) )
			{
				var indexed = SWYM.indexOp(params[0].value, params[1].value, true);
				return indexed;
			}
			else
			{
				SWYM.LogError(0, ".getFromEnd expects a list, got "+params[0].value);
			}
		}},
	".random": {value:function(params){
			if( SWYM.isSwymArray(params[0].value) )
			{
				var randMax = SWYM.getArrayLength(params[0].value);
				var randomSel = Math.floor(Math.random() * randMax);
				return SWYM.indexOp(params[0].value, randomSel);
			}
			else
			{
				SWYM.LogError(0, ".random expects a list, got "+params[0].value);
			}
		}},
	".output": {value:function(params){
			if( params.length !== 1 || !params[0] )
			{
				SWYM.LogError(0, ".output expects 1 parameter, got "+params.length);
				return {value:undefined, error:true};
			}
			SWYM.PrintedOutput += params[0].value;
			return {value:null};
		}},
	".id": {value:function(params){ return {value:SWYM.cell(params[0]), type:"id"}; }},
	".each": {value:function(params)
		{
			var sourceList = params[0].value;
	
			if ( SWYM.isSwymArray(sourceList) )
			{
				return SWYM.MultiExec(sourceList, function(arg){ return arg; });
			}
			else
			{
				SWYM.LogError(0, "Can't execute .each on a non-enumerable value: "+sourceList);
			}
		}},
	".entryAt": {value:function(params){
			return {value:params[1].value, entryFrom:params[0].value};
		}},
	".entryFrom": {value:function(params){
			return {value:params[0].entryFrom};
		}},
	".entryValue": {value:function(params){
			if( params[0].entryFrom )
			{
				return {value:SWYM.IndexOp(params[0].entryFrom, params[0].value)};
			}
			else
			{
				SWYM.LogError(0, "Can't get the entryValue of non-entry "+params[0].value);
				return {value:undefined, error:true};
			}
		}},
	".toUpper": {value:function(params){
			return {value:(""+params[0].value).toUpperCase()};
		}},
	".toLower": {value:function(params){
			return {value:(""+params[0].value).toLowerCase()};
		}},
	".floor": {value:function(params){
			return {value:Math.floor(params[0].value)};
		}},
	".pow": {value:function(params){
			return {value:Math.pow(params[0].value, params[1].value)};
		}},
	".length": {value:function(params){
			return {value:SWYM.getArrayLength(params[0].value)};
		}},
/*	".Mutable": {value:function(params){
				var x = params[0];
				if ( x && x.value && x.value.length > 0 )
				{
					var result = SWYM.NewArray();
					result.temporary = false;
					for( var idx = 0; idx < x.value.length; idx++ )
					{
						result.push(SWYM.NewVar(x.value[idx].value));
					}
					return {value:result};
				}
			}},*/
		".attr": {value:function(params){
				return SWYM.NewAttribute(params[0]);
			}},
		".Domain": {value:function(params){
				if( params[0].domain )
				{
					return params[0].domain();
				}
				else
				{
					SWYM.LogError(0, "Value "+params[0].value+" has no domain.");
					return {value:undefined, error:true};
				}
			}},
	"null": {value:null},
	"undefined": {value:undefined},
	"true": {value:true},
	"false": {value:false},
	"Value": {value:SWYM.PatternTestFunc(function(params){ return {value:true}; })},
	"Num": {value:SWYM.PatternTestFunc(function(params){ return {value:typeof params[0].value === 'number'}; })},
	"Int": {value:SWYM.PatternTestFunc(function(params){ return {value:typeof params[0].value === 'number' && Math.round(params[0].value) === params[0].value}; })},
	"String": {value:SWYM.PatternTestFunc(function(params){ return {value:!!SWYM.isSwymArray(params[0].value)}; })},
	"Function": {value:SWYM.PatternTestFunc(function(params){ return {value:typeof params[0].value === 'function'}; })},
	"Pattern": {value:SWYM.PatternTestFunc(function(params){ return {value:params[0].value && !!params[0].value.patternTest}; })},
	"List": {value:SWYM.PatternTestFunc(function(params){ return {value:!!SWYM.isSwymArray(params[0].value)}; })},
	"Cell": {value:SWYM.PatternTestFunc(function(params){ return {value:!!params[0].contents}; })},
	"Class": {value:{
			patternTest:function(cell){ return cell.value.classSignature === SWYM.ClassClass; },
			instantiate:function(params){ return SWYM.NewClass(); },
			classSignature:SWYM.ClassClass,
		}},
	".case": {value:function(params)
		{
			var testee;
			var Idx;
			if( params.length%2 == 0 )
			{
				SWYM.LogError(0, "case function expects an odd number of parameters! (got "+params.length+")");
				return {value:undefined, error:true};
			}
			
			testee = params[0];
			for( var Idx = 2; Idx < params.length; Idx += 2 )
			{
				var pattern = params[Idx-1].value;
				if(pattern && pattern.patternTest)
				{
					if( pattern.patternTest(testee) )
					{
						var bodyFunc = params[Idx].value;
						return bodyFunc([]);
					}
				}
			}
			
			return SWYM.NewArray(); // return zero values, so "else" will catch it
		}},
	".Non": {value:function(params){
			if(params[0].value && params[0].value.patternTest)
				return SWYM.notOp(params[0].value.patternTest);

			SWYM.LogError(0, "Can't perform Not on non-pattern "+params[0].value);
			return {value:undefined};
		}},
	".Where": {value:function(params){
			if(params[0].value && params[0].value.patternTest &&
				params[1].value && params[1].value.patternTest)
			{
				if( SWYM.isSwymArray(params[0].value) )
				{
					return {value:SWYM.arrayFilter(params[0].value, params[1].value.patternTest)};
				}
				else if( SWYM.isSwymArray(params[1].value) )
				{
					return {value:SWYM.arrayFilter(params[1].value, params[0].value.patternTest)};
				}
				else
				{
					return {value:SWYM.patternFilter(params[0].value.patternTest, params[1].value.patternTest)};
				}
			}
			else
			{
				SWYM.LogError(0, "Where expects a pattern and a function, got "+params[0].value+" and "+params[1].value);
				return {value:undefined};
			}
		}},
	".var": {value:function(params){
			if ( params[0].value && params[0].value.patternTest )
			{
				// return a new mutable cell of this type
				// second parameter may be undefined, that's ok
				var contents = params[1]? params[1].value: undefined;
				return SWYM.NewVar(contents, params[0].value.patternTest);
			}
		}},
	".Var": {value:function(params){
			if ( params[0].value && params[0].value.patternTest )
			{
				// should check that the var's content pattern matches ours, but not right now...
				return {value:{patternTest:function(cell){ return cell.setContents !== undefined; }}}
			}
		}},
//	".yield": {value:function(params){
//			if( !SWYM.scope.Yielded || !SWYM.scope.Yielded.value.isSwymArray )
//			{
//				SWYM.scope.Yielded = {value:SWYM.NewArray()};
//			}
//			SWYM.scope.Yielded.value.push(params[0]);
//			return params[0];
//		}},
	".quantifier":{value:function(params){
			var sourceList = params[0].value;
		
			if ( SWYM.isSwymArray(sourceList) )
			{
				var result = SWYM.MultiExec(sourceList, function(arg){ return arg; } );
				result.resolve = params[1].value;
				return result;
			}
			else
			{
				SWYM.LogError(0, "Can't execute .quantifier on a non-enumerable value: "+sourceList);
			}
		}},
	".every": {value:function(params){
		var sourceList = params[0].value;
		
		if ( SWYM.isSwymArray(sourceList) )
		{
			var result = SWYM.MultiExec(sourceList, function(arg){ return arg; } );
			result.resolve = SWYM.ResolveEvery;
			return result;
		}
		else
		{
			SWYM.LogError(0, "Can't execute .every on a non-enumerable value: "+sourceList);
		}
	}},
	".some": {value:function(params){
		var sourceList = params[0].value;
		
		if ( SWYM.isSwymArray(sourceList) )
		{
			var result = SWYM.MultiExec(sourceList, function(arg){ return arg; } );
			result.resolve = SWYM.ResolveSome;
			return result;
		}
		else
		{
			SWYM.LogError(0, "Can't execute .some on a non-enumerable value: "+sourceList);
		}
	}},
	
	".Cell":  {value:function(params){
		var sourceList = params[0].value;
	
		if ( SWYM.isSwymArray(sourceList))
		{
			if( sourceList.ownCellList )
			{
				var cellList = sourceList.ownCellList;
			}
			else
			{
				var cellList = SWYM.NewArray();
				var unCellID = {};
				var length = SWYM.getArrayLength(sourceList);
				for (var Idx = 0; Idx < length; Idx++)
				{
					cellList.push(SWYM.makeCell(sourceList, cellList, Idx, unCellID));
				}
				sourceList.ownCellList = cellList;
			}
			
			if( params[1] )
			{
				// two-argument version of .Cell
				var transformed = params[1].value([{value:cellList}])
				return SWYM.unCell(transformed, unCellID);
			}
			else
			{
				return {value:cellList};
			}
		}
		else
		{
			SWYM.LogError(0, "Can't execute .Cell on a non-enumerable value: "+sourceList);
		}
	}},
	
	".value":  {value:function(params){
		if( params[0].contents )
		{
			return params[0].contents;
		}
		else
		{
			SWYM.LogError(0, "Can't execute .value on a non-cell: "+params[0].value);
		}
	}},

	".index":  {value:function(params){
		return {value:params[0].index};
	}},

	".cellList":  {value:function(params){
		return {value:params[0].cellList};
	}},

	".sourceList":  {value:function(params){
		return {value:params[0].sourceList};
	}},
	
	".Reversed": {value:function(params){
		return {value:SWYM.LazyReverse(params[0].value)};
	}},
	
	".Sorted": {value:function(params){
		if(!params || !params[0] || !params[0].value || !SWYM.isSwymArray(params[0].value) )
		{
			SWYM.LogError(0, "Sorted can only operate on a list");
			return {value:undefined, error:true};
		}
		
		return {value:SWYM.sortBy( params[0].value, function(args){ return args[0]; } )};
	}},
	
	".SortedBy": {value:function(params){
		if(!params || !params[0] || !params[0].value || !SWYM.isSwymArray(params[0].value) ||
					!params[1] || !params[1].value || typeof params[1].value !== "function" )
		{
			SWYM.LogError(0, "SortedBy requires a list and a function");
			return {value:undefined, error:true};
		}
		
		return {value:SWYM.sortBy( params[0].value, params[1].value )};
	}},
	
	".new": {value:function(params){
		if( params[0] && params[0].value && params[0].value.instantiate )
		{
			return params[0].value.instantiate(params.slice(1));
		}
		else
		{
			SWYM.LogError(0, "Tried to call 'new' for non-instantiable value "+params[0].value);
			return {value:undefined, error:true};
		}
	}},

	".Struct": {value:function(params)
	{
		if(!params || !params[0] || !params[0].value || typeof params[0].value !== "function" )
		{
			SWYM.LogError(0, "Struct can only operate on a function");
			return {value:undefined, error:true};
		}
		
		var structPatterns = params[0].value();
		if( !structPatterns )
			return {value:undefined, error:true};
			
		if( !SWYM.isSwymArray(structPatterns) )
		{
			// if it's a single value, make it single-element array
			var temp = SWYM.NewArray();
			temp.push(structPatterns);
			structPatterns = temp;
		}
		
		// determine whether it's finite or infinite - and if finite, how many possible values of this type are there?
		var numElements = SWYM.getArrayLength(structPatterns);
		var isFinite = true;
		var numPossibleValues = 1;
		for (var Idx = 0; Idx < numElements; Idx++)
		{
			var pattern = SWYM.indexOp(structPatterns, Idx);
			if( SWYM.isSwymArray(pattern.value) )
			{
				numPossibleValues *= SWYM.getArrayLength(pattern.value);
			}
			else
			{
				isFinite = false;
				break;
			}
		}
		
		return {value:{
			isSwymArray:isFinite,
			patternTest:function(cell)
			{
				if( !cell || !cell.value || !SWYM.isSwymArray(cell.value) )
					return false;
			
				if( SWYM.getArrayLength(cell.value) != numElements )
					return false;

				for (var Idx = 0; Idx < numElements; Idx++)
				{
					var element = SWYM.indexOp(cell.value, Idx);
					var pattern = SWYM.indexOp(structPatterns, Idx);
				
					if( !SWYM.patternDescribes(pattern.value, element) )
						return false;
				}
			
				return true;
			},
			getElement:	function(n)
			{
				var nLeft = n;
				var selected = [];
				for (var Idx = numElements-1; Idx >= 0; Idx--)
				{
					var pattern = SWYM.indexOp(structPatterns, Idx);
					var lengthOfPattern = SWYM.getArrayLength(pattern.value);
					var indexWithinPattern = nLeft%lengthOfPattern;
					selected.push(SWYM.indexOp(pattern.value, indexWithinPattern));
					nLeft = Math.floor(nLeft/lengthOfPattern);
				}
				
				var result = SWYM.NewArray();
				for(var selectedIdx = selected.length-1; selectedIdx >= 0; selectedIdx--)
				{
					result.push(selected[selectedIdx])
				}
				return {value:result};
			},
//			getElementFromEnd: function(r) { return this.getElement((this.getLength()-r)-1); },
			getLength:	function(){ return numPossibleValues; },
			toString: numPossibleValues>500? function(){ return "Struct/"+numElements }: SWYM.SWYMArrayToString
		}};
	}},
	
	".Permutation": {value:function(params)
	{
		if(!params || !params[0] || !params[0].value || !SWYM.isSwymArray(params[0].value) )
		{
			SWYM.LogError(0, "Permutation can only operate on a list");
			return {value:undefined, error:true};
		}
		
		var baseList = params[0].value;
		var numElements = SWYM.getArrayLength(baseList);
		var numPossibleValues = 1;
		for( var e = 2; e <= numElements; e++ ) // factorial
			numPossibleValues *= e;
		
		return {value:{
			isSwymArray:true,
			patternTest:function(cell)
			{
				if( !cell || !cell.value || !SWYM.isSwymArray(cell.value) || SWYM.getArrayLength(cell.value) !== numElements )
					return false;
					
				var unusedElements = [];
				for( var baseIdx = 0; baseIdx < numElements; baseIdx++ )
				{
					unusedElements.push(SWYM.indexOp(baseList, baseIdx))
				}
			
				for (var Idx = 0; Idx < numElements; Idx++)
				{
					var element = SWYM.indexOp(cell.value, Idx);
				
					var found = false;
					for( var unusedIdx = 0; unusedIdx < unusedElements.length; unusedIdx++ )
					{
						if( SWYM.isEqual(element, unusedElements[unusedIdx]) )
						{
							// once an element is matched, remove it from contention
							unusedElements = unusedElements.slice(0,unusedIdx).concat(unusedElements.slice(unusedIdx+1));
							found = true;
							break;
						}
					}
					
					if( !found )
						return false;
				}
			
				return true;
			},
			getElement:	function(n)
			{
				var originalN = n;
				var result = SWYM.NewArray();
				
				var unusedElements = [];
				for( var baseIdx = 0; baseIdx < numElements; baseIdx++ )
				{
					unusedElements.push(SWYM.indexOp(baseList, baseIdx))
				}
				
				var divisor = numPossibleValues;
				
				while( unusedElements.length > 0 )
				{
					divisor /= unusedElements.length;

					var selectedIdx = Math.floor( n / divisor );
					result.push(unusedElements[selectedIdx]);

					n -= selectedIdx * divisor;

					unusedElements = unusedElements.slice(0,selectedIdx).concat(unusedElements.slice(selectedIdx+1));
				}
				return {value:result};
			},
//			getElementFromEnd: function(r) { return this.getElement((this.getLength()-r)-1); },
			getLength:	function(){ return numPossibleValues; },
			toString: numPossibleValues>500? function(){ return "Permutation/"+numElements }: SWYM.SWYMArrayToString
		}};
	}},
	
	".Filter": {value:function(params)
	{
		if(!params || !params[0] || !params[0].value || !SWYM.isSwymArray(params[0].value) )
		{
			SWYM.LogError(0, "Filter can only operate on a list");
			return {value:undefined, error:true};
		}
		
		var baseList = params[0].value;
		var numElements = SWYM.getArrayLength(baseList);
		var numPossibleValues = 1;
		for( var e = 0; e < numElements; e++ ) // exponential
			numPossibleValues *= 2;
		numPossibleValues--;
		
		return {value:{
			isSwymArray:true,
			patternTest:function(cell)
			{
				if( !cell || !cell.value || !SWYM.isSwymArray(cell.value) || SWYM.getArrayLength(cell.value) > numElements )
					return false;
					
				var drawingFrom = 0;
				var targetNumElements = SWYM.getArrayLength(cell.value);
				var maxDropped = numElements - targetNumElements;
			
				var Idx = 0;
				while(true)
				{
					if( Idx >= targetNumElements )
						return true;

					if (drawingFrom >= numElements || drawingFrom-Idx > numElements-targetNumElements)
						return false;
					
					var targetElement = SWYM.indexOp(cell.value, Idx);
					var baseElement = SWYM.indexOp(baseList, drawingFrom);
					
					if( SWYM.isEqual(targetElement, baseElement) )
						Idx++;
					
					drawingFrom++;
				}
			},
			getElement:	function(n)
			{
				var result = SWYM.NewArray();
				var bit = (numPossibleValues+1)/2;
				
				for( var baseIdx = 0; baseIdx < numElements; baseIdx++ )
				{
					var btest = n & bit;
					if( btest == 0)
						result.push(SWYM.indexOp(baseList, baseIdx));
						
					bit /= 2;
				}
				return {value:result};
			},
//			getElementFromEnd: function(r) { return this.getElement((this.getLength()-r)-1); },
			getLength:	function(){ return numPossibleValues; },
			toString: function(){ return "Filter("+baseList+")"; }
		}};
	}},
	
	".LazyList": {value:function(params)
	{
		if(!params || !params[0] || typeof(params[0].value) !== "number"
					|| !params[1] || typeof(params[1].value) !== "function")
		{
			SWYM.LogError(0, "LazyList expects two arguments: a Number, and a Function");
			return {value:undefined, error:true};
		}
		
		var length = params[0].value;
		var generate = params[1].value;
		var test = params[2]? params[2].value: undefined;
		
		var result =  {value:{
			isSwymArray:true,
			patternTest: test? function(cell)
			{
				return test([cell]).value;
			}: undefined,
			getElement:	function(n)
			{
				return generate([{value:n}]);
			},
			getLength:	function(){ return length; },
			toString: SWYM.SWYMArrayToString
		}};
		
		if( !test )
			result.patternTest = SWYM.ArrayContentTester(result);
			
		return result;
	}},
};

//====================================================================================

SWYM.stdlyb =
"\
Falsy:[false,null];\
Truthy:Non(Falsy);\
Bool:[true,false];\
Value.for(Value.body:Value): {yield .body};\
do(unused:Value)(Value.body:Value): {yield body()};\
List.Each(Value.body:Value): {[.each.body]}\
Value.of:{it};\
if(cond:Bool)(then():): {do(cond?){then() else null}};\
add(a:)(b:): {a+b};\
List.a: .1st;\
List.b: .2nd;\
List.c: .3rd;\
List.d: .4th;\
List.e: .5th;\
List.first: {.1st};\
List.last: {.getFromEnd(0)};\
List.total: {.1st + .2nd + etc};\
List.With(List.property:Value)(P:Pattern): .Where{.property is P};\
List.best(List.prefer:Bool): {\
	result:Value.var = .first;\
	for-each(.Tail){ if(prefer[it,result]){result=it} };\
	result };\
List.Best(List.prefer:): {\
	ideal:Value.var = .best{.prefer};\
	.Where{prefer[ideal,it] == prefer[it,ideal]} };\
List.min: {.best{.1st < .2nd}};\
List.max: {.best{.1st > .2nd}};\
List.Min: {.Best{.1st < .2nd}};\
List.Max: {.Best{.1st > .2nd}};\
List.withBest(Value.prefer:Bool)(Value.property:Value): {\
	best:Value.var = .first;\
	bestProp:Value.var = best.property;\
	for-each(.Tail){\
		cur: it;\
		curProp: cur.property;\
		if(prefer[curProp,bestProp]){ best = cur; bestProp = curProp; }\
	};\
	best };\
List.withMin(Value.property:Value): {.withBest{.1st < .2nd}{.property}};\
List.withMax(Value.property:Value): {.withBest{.1st > .2nd}{.property}};\
List.WithBest(Value.prefer:Bool)(Value.property:Value): {\
	best:Value.var = [.first];\
	bestProp:Value.var = .first.property;\
	for-each(.Tail)\
	{\
		cur: it;\
		curProp: cur.property;\
		if(prefer[curProp,bestProp]){ best = [cur]; bestProp = curProp; }\
		else{ if(!prefer[bestProp,curProp]){ best = [best.each, cur]; } }\
	};\
	best };\
List.WithMin(Value.property:Value): {.WithBest{.1st < .2nd}{.property}};\
List.WithMax(Value.property:Value): {.WithBest{.1st > .2nd}{.property}};\
List.Shuffled: random(.Permutation);\
List.exists: {.length > 0};\
List.Index: [0 ..< .length];\
List.#th: .at(# - 1);\
List.#thLast: .at(.length - #);\
List.Tail:   .Cell{[.first <.. .last]};\
List.Stem:   .Cell{[.first ..< .last]};\
List.Middle: .Cell{[.first <..< .last]};\
List.flatten: [.each.each];\
List.no: .quantifier{!(.1st || .2nd || etc)}; \
Value.print: output(\"$it\\n\");\
Cell.next: .cellList.at(.index+1);\
Cell.prev: .cellList.at(.index-1);\
Cell.#thBefore: .cellList.at(.index - #);\
Cell.#thAfter: .cellList.at(.index + #);\
Cell.Before: [.cellList.at(0 ..< .index)];\
Cell.After: [.cellList.at(.index <..< .cellList.length)];\
Cell.Neighboring2: [.prev, .next];\
Cell.Neighboring3: [.prev, it, .next];\
Cell.Other: [.cellList.first ..< it, it <.. .cellList.last]\
List.With(Value.property:Value)(P:Pattern): .Where{.property is P};\
List.WithIndex(P:Pattern): .Cell{.Where{.index is P}};\
List.CellWhere(P:Pattern): .Cell.With{.value}(P);\
Pattern.describes(v:Value): v is this;\
Pattern.Pair: Struct{it,it};\
";

//List.reverse: {[.#thLast-each[0 <.. .length]]};

SWYM.Eval(SWYM.stdlyb);
result = SWYM.ReportErrors("Stdlyb Error");
if ( result ) alert(result);
SWYM.DefaultGlobalScope = SWYM.scope;
