
SWYM.cell = function(x) { if (x.cell) return x.cell; else return x; }

SWYM.isSwymArray = function(a)
{
	return a && a.isSwymArray;
}

SWYM.getArrayLength = function(array)
{
	if ( typeof array === "string" )
		return array.length;

	return array.getLength();
}

SWYM.indexOp = function(array, index, fromEnd)
{
		if( !array || !SWYM.isSwymArray(array) )
		{
			SWYM.LogError(0, "Fsckup: Non-array value in indexOp");
			return {value:undefined, error:true};
		}

		var foundCell;

		if( fromEnd )
			foundCell = array.getElementFromEnd(index);
		else
			foundCell = array.getElement(index);

		if( foundCell )
			return foundCell;

		// out of bounds
		if ( SWYM.scope["#outOfBoundsFlag"].value === false )
			SWYM.scope["#outOfBoundsFlag"].value = true;

		return {value:null};
//	}
}

SWYM.MultiExec = function(multival, exp, isInternalCall)
{
	var meresult;

	if( multival && SWYM.isSwymArray(multival) )
	{
		// step through the values
		meresult = SWYM.NewArray(multival.resolve);
		var length = SWYM.getArrayLength(multival);
		
		for (var Idx = 0; Idx < length; Idx++)
		{
			var temp = SWYM.MultiExec(SWYM.indexOp(multival,Idx), exp, true);
			if( temp )
				meresult.push( temp );
		}
	}
	else
	{
		meresult = exp(multival);
	}

	if( isInternalCall || meresult )
	{
		return meresult;
	}
	else
	{
		// normally, if there's nothing else to return, return an empty multivalue
		return SWYM.NewArray(multival? multival.resolve: undefined); 
	}
}

SWYM.MultiExecUntilFirst = function(multival, exp, resultIfNone)
{
	var meresult;

	if( multival && SWYM.isSwymArray(multival) )
	{
		// step through the values
		meresult = SWYM.NewArray(multival.resolve);
		var length = SWYM.getArrayLength(multival);
		
		for (var Idx = 0; Idx < length; Idx++)
		{
			var temp = SWYM.MultiExecUntilFirst(SWYM.indexOp(multival,Idx), exp, undefined);
			if( temp )
				return temp;
		}
	}
	else
	{
		var temp = exp(multival);
		if( temp )
			return temp;
	}

	return resultIfNone;
}

SWYM.MultiExecLazy = function(baseArray, exp, resolveFn, is1to1)
{
	var cached = [];
	var nextUncached = 0;
	var baseLength = SWYM.getArrayLength(baseArray);
//	var lazyDataFromEnd = []; // in reverse order - last element of the array is at [0]

	var Cache;
	var CacheFromEnd;
	if(is1to1)
	{
		Cache = function(index)
		{
			if( !cached[index] && index >= 0 && index < baseLength )
			{
				var baseElement = SWYM.indexOp(baseArray, index);
				if( baseElement )
				{
					var result = exp(baseElement);
					cached[index] = result;
				}
			}
		}
		
		CacheFromEnd = function(rindex)
		{
			Cache((baseLength-rindex)-1);
		}
	}
	else
	{
		var cachedFromEnd = []; // in reverse order: cachedFromEnd[0] is the last element in the list
		var nextUncachedFromEnd = baseLength-1; // index into the base list. NB normal ordering, not reversed.

		function MergeCaches()
		{
			if( nextUncached <= nextUncachedFromEnd )
				return;
			
			for(var Idx = cachedFromEnd.length-1; Idx >= 0; Idx++ )
			{
				cached.push(cachedFromEnd[Idx]);
			}
			cachedFromEnd = undefined;

			Cache = function(index){};
			CacheFromEnd = function(rindex){};
		}

		Cache = function(index)
		{
			while( cached.length <= index && nextUncached <= nextUncachedFromEnd )
			{
				var baseElement = SWYM.indexOp(baseArray, nextUncached);
				var ourElement = exp(baseElement);
				if( ourElement )
					cached.push(ourElement);
				nextUncached++;
			}
			
			MergeCaches();
		}

		CacheFromEnd = function(rindex)
		{
			while( cachedFromEnd.length <= rindex && nextUncachedFromEnd >= nextUncached )
			{
				var baseElement = SWYM.indexOp(baseArray, nextUncachedFromEnd);
				var ourElement = exp(baseElement);
				if( ourElement )
					cachedFromEnd.push(ourElement);
				nextUncachedFromEnd--;
			}

			MergeCaches();
		}
	}

	var result = {
		"(cached)": cached, // for debug only
		isSwymArray:true,
		resolve: resolveFn? resolveFn: SWYM.ResolveEach,
		getElement: function(index)
		{
			Cache(index);
			return cached[index];
		},
		getElementFromEnd: function(rindex)
		{
			CacheFromEnd(rindex);
			if( cachedFromEnd && cachedFromEnd.length > rindex )
				return cachedFromEnd[rindex];
			else
				return cached[(cached.length-rindex)-1];
		},
		getLength:	is1to1? function(){ return baseLength; }: function()
		{
			Cache(baseLength);
			return cached.length;
		},
		toString: SWYM.SWYMArrayToString
	};
	
	result.patternTest = SWYM.ArrayContentTester(result);
	return result;
}

SWYM.LazyEtcSequence = function(parsetree)
{
	var stringVersion = parsetree.toString();

	return {
		getElement: function(index)
		{
			return cached[index];
		},
		toString: function(){ return stringVersion; }
	};
}

SWYM.LazyReverse = function(baseArray)
{
	var baseLength = SWYM.getArrayLength(baseArray);
	
	var ToString = function()
	{
		var str = "";
		var e
		SWYM.MultiExec(this, function(arg)
		{
			if(str)
				str += ",";
			str += arg.value;
		});
		return "["+str+"]";
	}

	var result = {
		isSwymArray:true,
		resolve: SWYM.ResolveEach,
		getElement: function(index){ return SWYM.indexOp(baseArray, (baseLength-index)-1); },
		getLength: function(){ return baseLength; },
		toString: SWYM.SWYMArrayToString
	};
	
	result.patternTest = SWYM.ArrayContentTester(result);
	return result;
}

SWYM.NewArray = function(resolveFn)
{
	var theArray = [];
	theArray.toString = SWYM.ArrayToString;

	var result = {
		isSwymArray:true,
		temporary:true,
		resolve: resolveFn? resolveFn: SWYM.ResolveEach,
		push:		function(v){ return theArray.push(v); },
		getElement:	function(n){ return theArray[n]; },
		getElementFromEnd: function(r) { return this.getElement((this.getLength()-r)-1); },
		getLength:	function() { return theArray.length; },
		toString:	function() { return theArray.toString(); }
	};

    result.patternTest = SWYM.ArrayContentTester(result);
	result["(theArray)"] = theArray; // for debug only
	
	return result;
}

SWYM.StringWrapper = function(str)
{
	return {
		isSwymArray:true,
		patternTest: function(v)
		{
			if( v && v.value )
			{
				var swymChar = v.value.swymChar;
				if( swymChar )
				{
					for( var Idx = 0; Idx < str.length; Idx++ )
					{
						if( str[Idx] === swymChar )
							return true;
					}
				}
			}

			return false;
		},
		getElement:	function(n)
		{
			if( str[n] === undefined )
				return undefined;
			else
				return {value:SWYM.CharWrapper(str[n])};
		},
		getElementFromEnd: function(r) { return this.getElement((this.getLength()-r)-1); },
		getLength:	function() { return str.length; },
		toString:	function() { return str; }
	};
}

SWYM.CharWrapper = function(c)
{
	return {swymChar:c, toString:function(){return "'"+this.swymChar+"'"}};
}

SWYM.ArrayContains = function(array, cell)
{
	return SWYM.MultiExecUntilFirst(array, function(element)
	{
		if( element.value === cell.value) 
			return true;
	}, false);
}

SWYM.ArrayContentTester = function(array)
{
    return function(cell){ return SWYM.ArrayContains(array,cell); };
}


//===============================================================
//===============================================================

//=============================================================

SWYM.Compile = function(parsetree)
{  
    // handle the 'etc' keyword
    parsetree = SWYM.FindAndProcessEtc(parsetree, 0);
	//otherwise, no compiling in this mode
	return parsetree;
}

SWYM.GetOutput = function()
{
	return SWYM.PrintedOutput;
}

// The primary Exec function, called only by the interpreter.
SWYM.Exec = function(parsetree)
{
  SWYM.scope = object(SWYM.DefaultGlobalScope);
	SWYM.PrintedOutput = "";
    var result = SWYM.ExecDefault(parsetree);
	
	if ( SWYM.PrintedOutput === "" && result )
    {
		SWYM.MultiExec(result, function(cell)
		{
			if( !cell )
				SWYM.LogError(0, "Fsckup: Undefined result cell");
			else
			{
				var out = ""+cell.value;
				
				if( SWYM.PrintedOutput && out )
					SWYM.PrintedOutput+= "\n";
				
				SWYM.PrintedOutput += out;
			}
		});
    }
}

SWYM.ExecNode = function(parsetree)
{
	if( parsetree.op.behaviour.customExec )
	{
		return parsetree.op.behaviour.customExec(parsetree);
	}
	else
	{
		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 SWYM.ExecOp(lv,parsetree.op,rv);
			});
		});
	}
}

SWYM.ExecDefault = function(parsetree)
{
	if ( !parsetree )
	{
		return;
	}
	
    switch ( parsetree.type )
	{
	case "name":
		{
			var lookedUp = SWYM.scope[parsetree.text];
			if ( !lookedUp )
			{
				SWYM.LogError(parsetree.pos, "Undeclared identifier '"+parsetree.text+"'");
				return {value:undefined, error:true};
			}
			else
				return lookedUp;
		}
		break;
	case "literal":
		{
			if ( parsetree.etcSequence )
			{
				return {value:SWYM.ResolveEtcSequence(parsetree.etcSequence, SWYM.scope["#etcIndex"+parsetree.etcId].value)};
			}
			else if ( parsetree.value === undefined )
			{
				SWYM.LogError(parsetree.pos, "Undefined literal(!?!) '"+parsetree.value+"'");
				return {value:undefined};
			}
			else
			{
				if (typeof parsetree.value === "string")
				{
					if( parsetree.text === "'"+parsetree.value+"'" )
					{
						parsetree.value = SWYM.CharWrapper(parsetree.value);
					}
					else
					{
						parsetree.value = SWYM.StringWrapper(parsetree.value);
					}
				}
				
				return {value:parsetree.value};
			}
		}
		break;
	case "node":
		{
			var etcIndex = ( parsetree.etcExpandAround === undefined )?
					undefined:
					SWYM.scope["#etcIndex"+parsetree.etcId].value;

			if( etcIndex <= 0 )
			{
				return SWYM.ExecDefault(parsetree.children[parsetree.etcExpandAround]);
			}

			if( parsetree.op.behaviour.customExec )
			{
				return parsetree.op.behaviour.customExec(parsetree);
			}

			if ( parsetree.etcExpandAround !== undefined )
			{
				if ( parsetree.etcExpandAround == 0 )
				{
					SWYM.scope["#etcIndex"+parsetree.etcId].value--;
					var lhsResult = SWYM.ExecDefault(parsetree);
					SWYM.scope["#etcIndex"+parsetree.etcId].value = etcIndex;
					if( lhsResult && lhsResult.error )
						return lhsResult;
					
					var rhsResult = SWYM.ExecDefault(parsetree.children[1]);
				}
				else
				{
					var lhsResult = SWYM.ExecDefault(parsetree.children[0]);
					if( lhsResult && lhsResult.error )
						return lhsResult;
					
					SWYM.scope["#etcIndex"+parsetree.etcId].value--;
					var rhsResult = SWYM.ExecDefault(parsetree);
					SWYM.scope["#etcIndex"+parsetree.etcId].value = etcIndex;
				}
			}
			else
			{
				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 SWYM.ExecOp(lv,parsetree.op,rv);
				});
			});
		}
		break;
	case "etc":
		{
			//if ( parsetree.op.text === "," && !parsetree.haltBefore && !parsetree.haltAfter )
			//{
			//	return SWYM.LazyEtcSequence(parsetree);
			//}
			
            var etcIdx = 0;
            var etcIdxCell = SWYM.scope["#etcIndex"+parsetree.etcId];
            
            if( !etcIdxCell )
            {
				etcIdxCell = {value:undefined};
				SWYM.DefaultGlobalScope["#etcIndex"+parsetree.etcId] = etcIdxCell;
            }

            var oldEtcIdx = etcIdxCell.value;
            var oldOOBF = SWYM.scope["#outOfBoundsFlag"].value;
            SWYM.scope["#outOfBoundsFlag"].value = false;

            var accumulatedResults = undefined;
            
            var haltCell = undefined;
            if( parsetree.haltExpression )
            {
				haltCell = SWYM.ExecDefault(parsetree.haltExpression);
				
				if( haltCell.error )
				{
					return haltCell;
				}
				else if ( !haltCell )
				{
					SWYM.LogError(parsetree.pos, "Undefined literal(!?!) '"+parsetree.value+"'");
					return {value:undefined, error:true};
				}
			}
            
            while(etcIdx < 1000)
            {
                etcIdxCell.value = etcIdx;
                var resultsThisStep = SWYM.ExecDefault(parsetree.body);
                
                var shouldHalt = undefined;
                if( parsetree.haltCondition )
				{
					shouldHalt = parsetree.haltCondition(resultsThisStep, haltCell);
				}
				
				if( shouldHalt && parsetree.haltBefore && shouldHalt.value === true )
				{
					break;
				}
				else if ( resultsThisStep.error || SWYM.scope["#outOfBoundsFlag"].value )
				{
					break;
				}
                else if ( !accumulatedResults )
                {
                    accumulatedResults = resultsThisStep;
                }
                else if ( parsetree.op.text === "," )
				{
					accumulatedResults = SWYM.CommaOp(accumulatedResults, resultsThisStep);
				}
				else
                {
                    accumulatedResults = SWYM.MultiExec(accumulatedResults, function(accV)
					{
						return SWYM.MultiExec(resultsThisStep, function(resV)
						{
							return SWYM.ExecOp(accV, parsetree.op, resV);
						});
					});
                }
                
				if( shouldHalt && !parsetree.haltBefore && shouldHalt.value === true )
				{
					break;
				}
                etcIdx++;
            }
            
            if( !accumulatedResults )
            {
                accumulatedResults = SWYM.NewArray();
                
                // no terms! fill in a default if appropriate
                if( parsetree.op.text === "+" )
                    accumulatedResults.push({value:0});
                else if ( parsetree.op.text === "*" )
                    accumulatedResults.push({value:1});
                else if ( parsetree.op.text === "||" )
                    accumulatedResults.push({value:true});
                else if ( parsetree.op.text === "&&" )
                    accumulatedResults.push({value:false});

				if( oldOOBF != undefined && SWYM.scope["#outOfBoundsFlag"].value )
				{
					// we fell out of bounds in the very first iteration! Looks like the parent is out of bounds.
					oldOOBF = true;
				}
            }
            
            etcIdxCell.value = oldEtcIdx;
            SWYM.scope["#outOfBoundsFlag"].value = oldOOBF;
			
			return accumulatedResults;
        }
		break;
	default:
		{
			SWYM.LogError(parsetree.pos, "Invalid parse tree node: "+parsetree);
			return {value:undefined, error:true};
		}
	}
}

SWYM.ExecOp = function(lval, optoken, rval)
{
	if ( optoken )
	{
		var mode;
		if ( lval !== undefined && rval !== undefined )
			mode = "infix";
		else if ( lval !== undefined )
			mode = "postfix";
		else if ( rval !== undefined )
			mode = "prefix";
		else
			mode = "standalone";

		if ( !optoken.behaviour[mode] )
		{
			if ( (mode == "postfix" && optoken.behaviour.infix) || (mode == "standalone" && optoken.behaviour.prefix) )
			{
				SWYM.LogError(optoken.pos, "Missing right-hand side for operator '"+optoken.text+"'");
			}
			else
			{
				SWYM.LogError(optoken.pos, "Operator "+optoken.text+" cannot be used "+mode);
			}
			return null;
		}
		else
		{
			optoken.activeBehaviour = optoken.behaviour[mode];
			var result = optoken.activeBehaviour(lval, rval, optoken);
            if ( !result )
            {
                SWYM.LogError(0, "Fsckup: Node <"+lval+">"+optoken+"<"+rval+"> didn't return a value");
                return {value:undefined, error:true};
            }
            else
            {
                return result;
            }
		}
	}
	else if ( lval != undefined )
	{
		return lval;
	}
	else if ( rval != undefined )
	{
		return rval;
	}
	else
	{
		return undefined;
	}
}

//=========================================================
//=========================================================

// a temporary list may contain lvalues. When we store the list, we strip those lvalues out.
SWYM.MakeStorable = function(value)
{
	if ( !SWYM.isSwymArray(value) || !value.temporary )
		return value;

	var result = SWYM.MultiExec(value, function(arri)
	{
		if ( arri && arri.value && arri.value.temporary )
		{
			return {value:SWYM.MakeStorable(arri.value)};
		}
		else
		{
			return SWYM.cell(arri);
		}
	});
	result.temporary = false;
	return result;
}

/*
SWYM.ArrayToString = function()
{
	var result = "";
	var inBrackets = false;
	
	if( this.length === 0 )
		return "[]";
	
	for(var Idx = 0; Idx < this.length; Idx++)
	{
		if ( !this[Idx] )
		{
			result += ",?"+this[Idx]+"?";
		}
		else if ( this[Idx].value && this[Idx].value.swymChar )
		{
			if( inBrackets )
				result += "]";
			
			inBrackets = false;			
			result += this[Idx].value;
		}
		else
		{
			if( inBrackets )
				result += ",";
			else
				result += "[";

			inBrackets = true;
			result += this[Idx].value;
		}
	}
	
	if( inBrackets )
		result += "]";

	return result;
}
*/

SWYM.ArrayToStringGeneric = function(iterator)
{
	var result = "";
	var firstEntry = true;
	var stringResult = "";
	
	iterator(function(arg)
	{
		if( stringResult !== undefined && arg && arg.value && arg.value.swymChar )
			stringResult += arg.value.swymChar;
		else
			stringResult = undefined;
		
		if( !firstEntry )
			result += ",";

		if( arg )
			result += arg.value;
		else
			result += "(undefined)";
		firstEntry = false;
	});
	
	if( stringResult !== undefined )
		return stringResult;
	else
		return "["+result+"]";
}

SWYM.ArrayToString = function()
{
	var array = this;
	return SWYM.ArrayToStringGeneric( function(step)
	{
		for( var Idx = 0; Idx < array.length; Idx++ )
		{
			step(array[Idx]);
		}
	});
}

SWYM.SWYMArrayToString = function()
{
	var array = this;
	return SWYM.ArrayToStringGeneric( function(step)
	{
		SWYM.MultiExec(array, step)
	});
}

SWYM.OpDebug = function(a,b,c)
{
  alert("running("+a+"{"+c+"}"+b+")");
}

//=============================================================

SWYM.parseFunctionArguments = function(arguments, argTypes, argNames, hasHash)
{	
	function addArgument(arg)
	{
		if( arg.type === "node" && arg.op.text === "(parentheses())" )
			arg = arg.children[0];
		
		if( arg.type === "node" && arg.op.text === "," )
		{
			addArgument(arg.children[0]);
			addArgument(arg.children[1]);
		}
		else if( arg.type === "node" && arg.op.text === ":" )
		{
			if ( arg.children[0] && arg.children[0].type === "name" )
			{
				argNames.push(arg.children[0].text);

				var typeCell = SWYM.ExecDefault(arg.children[1]);
				if( typeCell )
					argTypes.push(typeCell.value);
				else
					argTypes.push(SWYM.DefaultGlobalScope.Value.value);
			}
			else if ( arg.children[0] && arg.children[0].op && arg.children[0].op.type === "(fn)" )
			{
				argNames.push(arg.children[0].op.text);
				argTypes.push(SWYM.DefaultGlobalScope.Function.value);
			}
			else
			{
				SWYM.LogError(arg.op.pos, "Invalid parameter name "+arg.children[0]+"'");
			}
		}
		else
		{
			argNames.push(null);
			var argResult = SWYM.ExecDefault(arg);
			if( argResult && argResult.value )
			{
				argTypes.push(argResult.value);
			}
		}
	};
	
	
	addArgument(arguments[0]);
	
	if( hasHash ) // if a function has a # in the name, it gets a free second parameter, called #.
	{
		argTypes.push(SWYM.DefaultGlobalScope.Int.value);
		argNames.push("#");
	}
	
	for( var Idx = 1; Idx < arguments.length; Idx++ )
	{
		addArgument(arguments[Idx]);
	}
	
	if( argNames[0] === null )
		argNames[0] = "it";
}

SWYM.PatternTestFunc = function(fn)
{
	fn.patternTest = function(toTest)
	{
		return fn([toTest]).value;
	}
	
	return fn;
}

SWYM.NewLambda = function(parsetree)
{
    var declScope = SWYM.scope;
    
	return SWYM.PatternTestFunc(function(params)
	{
        if( !params || params.length < 1 )
        {
            
        }
        else if( params.length > 1 )
        {
            SWYM.LogError(parsetree.pos, "Too many params in function call!");
        }

		var oldScope = SWYM.scope;
		SWYM.scope = object(declScope);
		if ( params && params.length > 0 && params[0] )
		{
			SWYM.scope.it = params[0]; // n.b. not just .value.

			var isOutmostBlock = false;
			
			if ( !SWYM.scope["this"] )
			{
				isOutmostBlock = true;
				SWYM.scope.Yielded = SWYM.NewArray();
				SWYM.scope["this"] = {value:params[0].value};
			}
		}

		var result = SWYM.ExecDefault(parsetree);

		// an explicit Yield overrides the result value
		if ( isOutmostBlock && SWYM.scope.Yielded.getLength() > 0 )
        {
			result = SWYM.scope.Yielded;
        }
//        else
//        {
//			result = SWYM.ResolveToBool(result);
//        }

		SWYM.scope = oldScope;
		
		return result;
	});
}

SWYM.selectOverload = function(declName, params, overloadList)
{
	var tooShortForAll = true;
	var tooLongForAll = true;
	var allSameLength = true;
	
	// hacky optimization
	if( overloadList.length === 1 )
		return overloadList[0]

	for( var Idx = 0; Idx < overloadList.length; Idx++ )
	{
		var curOverload = overloadList[Idx];
		
		if( curOverload.paramTypes.length === params.length )
		{
			tooLongForAll = false;
			tooShortForAll = false;
			
			var matched = true;
			for( var pIdx = 0; pIdx < params.length; pIdx++ )
			{
				if( !SWYM.patternDescribes( curOverload.paramTypes[pIdx], params[pIdx] ) )
				{
					matched = false;
					break;
				}
			}
			
			if( matched )
			{
				return curOverload;
			}
		}
		else
		{
			allSameLength = false;
			if( curOverload.paramTypes.length < params.length )
			{
				tooLongForAll = false;
			}
			else if( curOverload.paramTypes.length < params.length )
			{
				tooShortForAll = false;
			}
		}
	}

	// failed, report error
	if ( tooLongForAll )
	{
		SWYM.LogError(0, "Too many parameters in call to "+declName+" (got "+params.length+")");
		return undefined;
	}
	else if ( tooShortForAll )
	{
		SWYM.LogError(0, "Not enough parameters in call to "+declName+" (got "+params.length+")");
		return undefined;
	}
	else if ( overloadList.length === 1 )
	{
		SWYM.LogError(0, "Invalid arguments in call to "+declName);
		return undefined;
	}
	else
	{
		SWYM.LogError(0, "None of the "+overloadList.length+" overloads matched when calling "+declName);
		return undefined;
	}
}

SWYM.NewMultiFunc = function(declName)
{
    var declScope = SWYM.scope;
    
    var newfn = function(params)
    {
		var overload = SWYM.selectOverload(declName, params, arguments.callee.overloads);
        var bodyscope = object(declScope);
        
        if( !overload )
        {
			return {value:undefined, error:true};
        }

        for( var Idx = 0; Idx < params.length; Idx++ )
        {
            bodyscope[overload.paramNames[Idx]] = params[Idx];
        }

        var callerScope = SWYM.scope;

        SWYM.scope = bodyscope;
        
		if ( !SWYM.scope["this"] )
		{
			SWYM.scope["this"] = SWYM.scope["it"];
		}
		SWYM.scope.Yielded = SWYM.NewArray();
		
		var result = SWYM.ExecDefault(overload.body);

		if ( SWYM.scope.Yielded.getLength() > 0 )
        {
			// an explicit Yield overrides the result value
			result = SWYM.scope.Yielded;
        }
        else
        {
			// if there's no yield, we auto-resolve the result
			result = SWYM.ResolveToBool(result);
        }
        SWYM.scope = callerScope;

		//return SWYM.ResolveToBool(result);
		return result;
    };
    
    newfn.patternTest = function(toTest)
    {
		return newfn([toTest]).value;
    }
    
    newfn.overloads = [];
    return newfn;
}

SWYM.patternDescribes = function(pattern,cell)
{
    if ( !pattern || !cell )
    {
        SWYM.LogError(0, "Can't pattern-test a null value!");
        return {value:undefined, error:true};
    }
    
    if ( pattern.patternTest )
    {
        return pattern.patternTest(cell);
    }
    else
    {
        SWYM.LogError(0, "Value "+pattern+" is not a pattern!");
    }
}

SWYM.NewVar = function(contents, patternTest, patternName)
{
    var cell = {
        value:contents,
        setContents:function(newcell){
            if(!patternTest || patternTest(newcell))
            {
                cell.value = newcell.value;
                return cell;
            }
            else
            {
                SWYM.LogError(0, "Can't store "+newcell+" here: expecting "+patternName);
                return {value:undefined, error:true};
            }
        },
    };
    return cell;
}

SWYM.NewAttribute = function(defaultcell)
{
	var theHash={};
	var accessor = function(params)
	{
		var found = theHash[params[0].value];
		if (found)
			return found;
		else
			return {value:defaultcell.value,
				setContents:function(newcell){
                    theHash[params[0].value] = newcell;
                    return newcell;
                },
			};
	}
	
	return {
		value:accessor,
		domain:function()
		{
			var result=SWYM.NewArray();
			for (var key in theHash) {
				result.push({value:key});
			}
			return {value:result};
		}
	};
}

SWYM.isTruthy = function(v) { return v !== undefined && v !== null && v !== false; }

SWYM.pushAll = function(resultSoFar, newResults)
{
    for( var Idx = 0; Idx < newResults.length; Idx++ )
    {
        resultSoFar.push(newResults[Idx]);
    }
}


SWYM.PlusOrMinusExec = function(a,b)
{
    var result = SWYM.NewArray();
	result.push({value:a.value + b.value});
	result.push({value:a.value - b.value});
	return result;
};

SWYM.ResolveToBool = function(multival)
{
	if( SWYM.isSwymArray(multival) )
	{
		var fnInputList = null;
		var length = SWYM.getArrayLength(multival)

		for (var Idx = 0; Idx < length; Idx++ )
		{
			var element = SWYM.indexOp(multival, Idx);
			if( SWYM.isSwymArray(element) )
			{
				if( !fnInputList )
				{
					fnInputList = SWYM.NewArray();
					for( var FillIdx = 0; FillIdx < Idx; FillIdx++ )
					{
						fnInputList.push(SWYM.indexOp(multival, FillIdx));
					}
				}

				fnInputList.push(SWYM.ResolveToBool(element));
			}
			else if ( fnInputList )
			{
				fnInputList.push(element);
			}
		}

		if( !fnInputList )
			fnInputList = multival;

		var resolve = multival.resolve;
		return resolve([{value:fnInputList}]);
	}
	else
	{
		return multival;
	}
}

SWYM.ResolveEach = function(params)
{
	// no effect, just unbox it
	return params[0].value;
}
/*	if( params.length !== 1 || !SWYM.isSwymArray(params[0].value) )
	{
		SWYM.LogError(0, "Invalid parameters when resolving Each: "+params);
		return {value:undefined, error:true};
	}
	
	return {value:SWYM.MultiExec(params[0].value, function(element)
	{
		return element;
	})};
}*/

SWYM.ResolveEvery = function(params)
{
	if( params.length !== 1 || !SWYM.isSwymArray(params[0].value) )
	{
		SWYM.LogError(0, "Invalid parameters when resolving Each: "+params);
		return {value:undefined, error:true};
	}
	
	return SWYM.MultiExecUntilFirst(params[0].value, function(element)
	{
		if( !element || !SWYM.isTruthy(element.value) )
			return {value:false};
	}, {value:true});
}

SWYM.ResolveSome = function(params)
{
	if( params.length !== 1 || !SWYM.isSwymArray(params[0].value) )
	{
		SWYM.LogError(0, "Invalid parameters when resolving Some: "+params);
		return {value:undefined, error:true};
	}
	
	return SWYM.MultiExecUntilFirst(params[0].value, function(element)
	{
		if( element && SWYM.isTruthy(element.value) )
			return {value:true};
	}, {value:false});
}

SWYM.isEqual = function(a,b)
{
	if( a === b )
		return true;

	return SWYM.isEqualValue(a.value, b.value);
}

SWYM.isEqualValue = function(a,b)
{
	if( a === b )
		return true;
	
	if( !a || !b ) // any false values that were equal should have been caught above
		return false;
		
	if( SWYM.isSwymArray(a) && SWYM.isSwymArray(b) )
	{
		var alen = SWYM.getArrayLength(a);
		var blen = SWYM.getArrayLength(b);
		
		if( alen != blen )
			return false;

		for( var Idx = 0; Idx < alen; Idx++ )
		{
			var av = SWYM.indexOp(a, Idx);
			var bv = SWYM.indexOp(b, Idx);
			if( !SWYM.isEqual( av, bv ) )
				return false;
		}
		
		return true;
	}
	
	if( a.swymChar && a.swymChar === b.swymChar )
		return true;
	
	return false;
}

SWYM.makeCell = function(sourceList, cellList, index, unCellID)
{
	var contents = SWYM.indexOp(sourceList, index);
	return {index:index, sourceList:sourceList, cellList:cellList, unCellID:unCellID, contents:contents,
//		value:index};
		value:contents.value};
}

SWYM.unCell = function(x, unCellID)
{
	if( !x || x.error)
		return x;

	if( x.unCellID == unCellID )
		return x.contents;
	
	if( SWYM.isSwymArray(x) )
	{
		var newList = undefined;
		var length = SWYM.getArrayLength(x);
		for( var Idx = 0; Idx < length; Idx++ )
		{
			var oldElement = SWYM.indexOp(x, Idx);
			var newElement = SWYM.unCell(oldElement, unCellID);
			if( newList || newElement !== oldElement )
			{
				if( !newList )
				{
					newList = SWYM.NewArray();
					for( var Idx2 = 0; Idx2 < Idx; Idx2++ )
					{
						newList.push( SWYM.indexOp(x, Idx2) );
					}
				}
				
				newList.push( newElement );
			}
		}
		
		if( newList )
			return newList;
	}
	else
	{
		var newV = SWYM.unCell(x.value, unCellID);
		if( newV !== x.value )
			return {value:newV};
	}
	
	return x;
}

SWYM.assignmentOp = function(a,b, op)
{
	if (a.setContents)
	{
		return a.setContents(b);
	}
	else
	{
		SWYM.LogError(op.pos, "Can't assign "+b+" to non-mutable value "+a);
		return {value:undefined, error:true};
	}
}

SWYM.declOp = function(parsetree)
{
	if ( !parsetree.children[0] )
	{
		SWYM.LogError(parsetree.op.pos, "Missing left side of declaration'");
		return {value:undefined, error:true};
	}

	var declName;
	var valueToSet; // the cell we're going to put into the scope

	if ( parsetree.children[0].type === "name" )
	{
		declName = parsetree.children[0].text;

		if ( SWYM.scope[declName] )
		{
			SWYM.LogError(parsetree.op.pos, "Can't declare "+parsetree.op.text+" because it already exists!");
			return {value:undefined, error:true};
		}

		if ( !parsetree.children[1] )
		{
			SWYM.LogError(parsetree.op.pos, "Left side of declaration is neither a name nor a function: '"+parsetree.children[0]+"'");
			return {value:undefined, error:true};
		}

		valueToSet = SWYM.ExecDefault(parsetree.children[1]);
	}
	else if (parsetree.children[0].type === "node" )
	{
		var argTypes = [];
		var argNames = [];
		if ( parsetree.children[0].op.type === "(fn)" )
		{		
			declName = parsetree.children[0].op.text;
			SWYM.parseFunctionArguments(parsetree.children[0].children, argTypes, argNames, parsetree.children[0].op.startsWithHash);
		}
		else if ( parsetree.children[0].op.text === "is" && parsetree.children[0].children[1].type === "name" )
		{
			declName = parsetree.children[0].children[1].text;
			declName = parsetree.children[0].children[1].text;
			argTypes.push(SWYM.ExecDefault(parsetree.children[0].children[0]).value);
			argNames.push("it");
		}

		if( parsetree.children[1] && parsetree.children[1].type === "node" &&
			parsetree.children[1].op.text === "(lambda{})" )
		{
			body = parsetree.children[1].children[0];
		}
		else
		{
			body = parsetree.children[1];
		}

		var mfunc;
		if( SWYM.scope[declName] )
		{
			mfunc = SWYM.scope[declName].value;
			mfunc.isSwymArray = false; // TODO: allow overloaded functions to be enumerated
		}
		else
		{
			mfunc = SWYM.NewMultiFunc(declName);
			SWYM.scope[declName] = {value:mfunc};

			// if the argtype is finite, allow this function to be treated as a list
			if( argTypes.length == 1 && argTypes[0].isSwymArray )
			{
				mfunc.isSwymArray = true;
				var array = SWYM.arrayFilter(argTypes[0], mfunc.patternTest);
				mfunc.getElement = array.getElement;
				mfunc.getLength = array.getLength;
				mfunc.patternTest = SWYM.ArrayContentTester(mfunc);
			}
		}
		
		mfunc.overloads.push({
			paramTypes:argTypes,
			paramNames:argNames,
			body:body
		});
	}
	else
	{
		SWYM.LogError(parsetree.op.pos, "Left side of declaration should be either 'bar', 'Foo is bar', or 'Foo.bar' - got: '"+parsetree.children[0]+"'");
		return {value:undefined, error:true};
	}
		
	var result;
	if ( valueToSet )
	{
		if ( SWYM.isSwymArray(valueToSet) && SWYM.getArrayLength(valueToSet) === 0 )
		{
			SWYM.LogError(parsetree.op.pos, "Can't assign zero values to "+declName);
			return {value:undefined, error:true};
		}
		else if ( SWYM.isSwymArray(valueToSet) )
		{
			if ( SWYM.getArrayLength(valueToSet) > 1 )
			{
				SWYM.LogError(parsetree.op.pos, "Can't assign multiple values to "+declName);
			}
			
			result = SWYM.cell(SWYM.indexOp(valueToSet, 0));
		}
		else
		{
			result = SWYM.cell(valueToSet);
		}

		if ( result && result.value && result.value.temporary )
		{
			result.value = SWYM.MakeStorable(result.value);
		}
	
		SWYM.scope[declName] = result;
	}
	return result;
}

SWYM.patternFilter = function(oldPatternTest, newFilter)
{
	return {patternTest:function(cell){
		return oldPatternTest(cell) && newFilter(cell);
	}};
}

/*
SWYM.arrayFilter = function(array, filter)
{
	return SWYM.MultiExec(array, function(element)
	{
		if ( SWYM.isTruthy(filter(element)) )
			return element;
	});
}

*/
// lazy version
SWYM.arrayFilter = function(array, filter)
{
	var result = SWYM.MultiExecLazy(array, function(element)
	{
		if ( SWYM.isTruthy(filter(element)) )
			return element;
	});
	
	result.patternTest = function(v){ return filter.patternTest(v) && array.patternTest(v); }
	return result;
}

SWYM.PassByValue = function(cell)
{
	if( cell && cell.passByReference )
		return cell.passByReference

	if( cell && cell.setContents )
		return {value:cell.value};

	return cell;
}

// SwymArray properties:
//	result.patternTest = SWYM.ArrayContentTester(result);
//	result.isSwymArray = true;
//	result.toString = function(){ return theArray.toString() };
//	result.getLength = function(){ return theArray.length; }
//	result.getElement = function(idx){ return theArray[idx]; }
//	result.push = function(v){ return theArray.push(v); }
//	result.resolve = resolveFn;

SWYM.fnOp = function(parsetree)
{
	var op = parsetree.op;
	var scopeEntry = SWYM.scope[op.text];
	var thefn;
	if ( scopeEntry && scopeEntry.value )
	{
		thefn = scopeEntry.value;
	}

	if (!thefn)
	{
		SWYM.LogError(op.pos, "Unknown function "+op.text);
		return {value:undefined, error:true};
	}

	if ( parsetree.children.length === 0 || (parsetree.children.length === 1 && parsetree.children[0] === undefined) )
	{
		return thefn([]);
	}

	var paramsResult = [];
	for(var Idx = 0; Idx < parsetree.children.length; Idx++ )
	{
		var newResult;
		var child = parsetree.children[Idx];
		
		if ( parsetree.etcExpandAround === Idx )
		{
			var etcIndexCell = SWYM.scope["#etcIndex"+parsetree.etcId];
			var oldEtcIndex = etcIndexCell.value;

			etcIndexCell.value--;
			newResult = SWYM.ExecDefault(parsetree);
			etcIndexCell.value = oldEtcIndex;

			if( newResult && newResult.error )
				return newResult;

			paramsResult.push( SWYM.PassByValue(newResult) );
		}
		else if( child.op && child.op.text === "(parentheses())" && child.children[0] &&
			child.children[0].op && child.children[0].op.text === "," )
		{
			var processComma = function(comma)
			{
				if( comma && comma.op && comma.op.text === "," )
				{
					return processComma(comma.children[0]) && processComma(comma.children[1]);
				}
				else
				{
					newResult = SWYM.ExecDefault(comma);
					if( newResult && newResult.error )
						return false;
			
					if( newResult && newResult.setContents && !newResult.passByReference )
						newResult = {value:newResult.value};

					paramsResult.push( SWYM.PassByValue(newResult) );
					return true;
				}
			};
			if( !processComma(child.children[0]) )
				return {value:undefined, error:true};
		}
		else
		{
			newResult = SWYM.ExecDefault(child);
			if( newResult && newResult.error )
				return newResult;

			if( newResult && newResult.setContents && !newResult.passByReference )
				newResult = {value:newResult.value};

			paramsResult.push( SWYM.PassByValue(newResult) );
		}
	}

	// check whether this is just a single-path execution
	var allParamsSingular = true;
	for(var Idx = 0; Idx < paramsResult.length; Idx++ )
	{
		if( SWYM.isSwymArray(paramsResult[Idx]) )
		{
			allParamsSingular = false;
			break;
		}
	}
	// ok, single path! Just pass the params right in.
	if( allParamsSingular )
		return thefn(paramsResult);
		
	// it's multi-path - see if we can do an easy one-arg or two-args case.
	if( paramsResult.length === 1 )
	{
		return SWYM.MultiExec(paramsResult[0], function(arg) { return thefn([arg]); });
	}
	else if ( paramsResult.length === 2 )
	{
		return SWYM.MultiExec(paramsResult[0], function(arg0){
			return SWYM.MultiExec(paramsResult[1], function(arg1){
				return thefn([arg0, arg1]);
			});
		});
	}
	
	//fine. Just enumerate all possible combinations of parameters
	var nextParam = 0;
	var curParams = [];
	var paramAccumulate = function(nextP)
	{
		if( nextParam >= paramsResult.length )
		{
			// finally call the function!
			return thefn(curParams);
		}

		var oldParams = curParams;

		nextParam++;
		curParams = oldParams.concat([nextP]);

			var result = SWYM.MultiExec(paramsResult[nextParam], paramAccumulate);

		nextParam--;
		curParams = oldParams;
		
		return result;
	};
	
	return SWYM.MultiExec(paramsResult[0], paramAccumulate);
}

SWYM.notOp = function(patternTest)
{
	return {value:{patternTest:function(cell){ return !patternTest(cell); }}};
}

SWYM.CommaOp = function(lv, rv)
{
	var results = SWYM.NewArray();
	
	if( lv )
	{
		if( results.resolve == lv.resolve )
			results = lv;
		else
			results.push(lv);
	}
	
	if( rv )
	{
		if( results.resolve == rv.resolve )
			SWYM.MultiExec(rv, function(rvn){ results.push(rvn); });
		else
			results.push(rv);
	}
	
	return results;
}

SWYM.RangeOp = function(lval, rval, includeStart, includeEnd, step)
{
	var result = SWYM.NewArray();
	
	if( lval.cellList || rval.cellList )
	{
		if( !lval.cellList )
		{
			SWYM.LogError(0, "Can't do cell interpolation, left hand side is not a cell");
			return {value:undefined, error:true};
		}
		else if( !rval.cellList )
		{
			SWYM.LogError(0, "Can't do cell interpolation, right hand side is not a cell");
			return {value:undefined, error:true};
		}
		else if ( lval.cellList !== rval.cellList )
		{
			SWYM.LogError(0, "Can't do cell interpolation, cells are from different lists");
			return {value:undefined, error:true};
		}
		else if ( lval.unCellID !== rval.unCellID )
		{
			SWYM.LogError(0, "Can't do cell interpolation, cells are from different .Cell{} expressions");
			return {value:undefined, error:true};
		}
		var ltarget = lval.index;
		var rtarget = rval.index;
	}
	else
	{
		var ltarget = Math.round(Number(lval.value));
		var rtarget = Math.round(Number(rval.value));
	}

	if( step === undefined )
		step = lval.value < rval.value? 1: -1;
								
	if( !includeStart )
		ltarget += step;
	
	if( !includeEnd )
		rtarget -= step;
													
	var Idx = ltarget-step;
													
	if( lval.cellList )
	{
		for( Idx = ltarget; Idx*step <= rtarget*step; Idx+=step )
		{
			result.push(SWYM.makeCell(lval.sourceList, lval.cellList, Idx, lval.unCellID));
		}
	}
	else
	{
		for( Idx = ltarget; Idx*step <= rtarget*step; Idx+=step )
		{
			result.push({value: Idx});
		}
	}

	return result;
}

SWYM.ComparisonOp = function(v, compare)
{
	if( SWYM.isSwymArray(v.value) )
	{
		return {value:SWYM.arrayFilter(v.value, compare)};
	}
	else if( v.value.patternTest )
	{
		return {value:SWYM.patternFilter(v.value.patternTest, compare)};
	}
	else
	{
		return {value:compare(v)};
	}
}

SWYM.sortBy = function(baseList, getv)
{
	var numElements = SWYM.getArrayLength(baseList);
		
	var gotvs = [];
	for( var baseIdx = 0; baseIdx < numElements; baseIdx++ )
	{
		gotvs.push(getv([SWYM.indexOp(baseList, baseIdx)]).value);
	}
	
	var result = SWYM.NewArray();
	while( SWYM.getArrayLength(result) < numElements )
	{
		var best = undefined;
		var bestv = undefined;
		var bestIdx = undefined;
		for( var baseIdx = 0; baseIdx < numElements; baseIdx++ )
		{
			var testv = gotvs[baseIdx];
			if( testv !== undefined )
			{
				if( bestIdx === undefined || testv < bestv )
				{
					bestv = testv;
					bestIdx = baseIdx;
				}
			}
		}

		result.push(SWYM.indexOp(baseList, bestIdx));
		gotvs[bestIdx] = undefined;
	}
	
	return result;
}

SWYM.ClassClass = {};

SWYM.NewClass = function()
{
  var classSignature = {};
  return {value:{
	patternTest:function(cell){ return cell.value.classSignature === classSignature; },
    instantiate:function(params){ return {value:{classSignature:classSignature}}; },
	classSignature:SWYM.ClassClass,
  }};
}