Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
3291 lines (2929 sloc) 91.6 KB
SWYM.Compile = function(parsetree)
{
var executable = [];
SWYM.MainCScope = object(SWYM.DefaultGlobalCScope);
var resultType = SWYM.CompileNode(parsetree, SWYM.MainCScope, executable);
if( !resultType || resultType.nativeType !== "Void" )
{
if (resultType && resultType.multivalueOf !== undefined)
{
executable.push("#Native", 1, function (value)
{
if (SWYM.doFinalOutput)
{
if(value.length > 0)
SWYM.DisplayValue(value.run(0));
for (var Idx = 1; Idx < value.length; Idx++)
{
SWYM.DisplayOutput("\n");
SWYM.DisplayValue(value.run(Idx));
}
}
});
}
else
{
executable.push("#Native", 1, function (value)
{
if (SWYM.doFinalOutput)
{
SWYM.DisplayValue(value);
}
});
}
}
return executable;
}
// The "base" node compiler, which gets called by CompileNode (below).
SWYM.CompileLValue = function(parsetree, cscope, executable)
{
if( parsetree === undefined )
{
return SWYM.VoidType;
}
var identityValue = undefined;
if( parsetree.etcExpansionId !== undefined )
{
var childExecutable = [];
cscope = object(cscope);
cscope["<etcExpansionCurrent>"] = SWYM.DontCareType;
if( parsetree.op !== undefined )
{
if( parsetree.op.text === "," )
{
cscope["<etcExpansionCurrent>"] = SWYM.ToMultivalueType(SWYM.DontCareType); //FIXME
}
var identityValue = parsetree.op.behaviour.identity;
SWYM.pushEach(["#Native", 0, identityValue], executable);
}
else if( parsetree.type === "fnnode" )
{
var identityValue = SWYM.GetFunctionIdentity(parsetree, cscope);
SWYM.pushEach(["#Literal", identityValue], executable);
}
SWYM.pushEach([
"#Load", "__n",
"#EtcExpansion", childExecutable
], executable);
executable = childExecutable;
// and now we proceed to compile the node into childExecutable...
}
if ( parsetree.type === "etc" )
{
return SWYM.CompileEtc(parsetree, cscope, executable);
}
else if( parsetree.op && parsetree.op.behaviour.customCompile )
{
return parsetree.op.behaviour.customCompile(parsetree, cscope, executable);
}
else if ( parsetree.type === "literal" )
{
if( typeof parsetree.value === "number" ) // the javascript type, that is
{
executable.push("#Literal");
executable.push(parsetree.value);
return SWYM.BakedValue(parsetree.value);
}
else if( typeof parsetree.value === "string" )
{
executable.push("#Literal");
var str = SWYM.StringWrapper(parsetree.value);
executable.push(str);
return SWYM.BakedValue(str);
}
else if ( parsetree.etcSequence )
{
executable.push("#EtcSequence");
executable.push(SWYM.EtcCreateGenerator(parsetree.etcSequence, parsetree));
return parsetree.etcSequence.type;
}
else if ( parsetree.etcExpandingSequence )
{
executable.push("#Load");
executable.push("__n");
executable.push("#Load");
executable.push("<etcExpansion>");
executable.push("#Native");
executable.push(2);
executable.push(SWYM.EtcCreateExpandingGenerator(parsetree.etcExpandingSequence, parsetree));
return parsetree.etcExpandingSequence.type;
}
else
{
SWYM.LogError(parsetree, "Unexpected literal <"+parsetree.text+">.");
}
}
else if ( parsetree.type === "name" )
{
var loadName = parsetree.text;
var loadName = SWYM.CScopeRedirect(cscope, loadName);
var scopeEntry = cscope[loadName];
if( scopeEntry === undefined )
{
if( parsetree.text === "__default" )
{
SWYM.LogError(parsetree, "There is no default value in this context.");
}
else
{
SWYM.LogError(parsetree, "Unknown identifier \""+parsetree.text+"\".");
}
}
else
{
executable.push("#Load");
executable.push(loadName);
}
return scopeEntry;
}
else if ( parsetree.type === "fnnode" )
{
// a function expression
if( parsetree.isDecl )
{
return SWYM.CompileFunctionDeclaration("fn#"+parsetree.name, parsetree.argNames, parsetree.children, parsetree.body, parsetree.returnTypeNode, cscope, executable);
}
else
{
return SWYM.CompileFunctionCall(parsetree, cscope, executable);
}
}
else if ( parsetree.op )
{
var opFunction;
if( !parsetree.children[0] && !parsetree.children[1] )
opFunction = parsetree.op.behaviour.standalone;
else if( !parsetree.children[1] )
opFunction = parsetree.op.behaviour.postfix;
else if( !parsetree.children[0] )
opFunction = parsetree.op.behaviour.prefix;
else
opFunction = parsetree.op.behaviour.infix;
var argTypes = parsetree.op.behaviour.argTypes;
if( !argTypes ) argTypes = [];
var numArgs = 0;
var tempexecutable0 = [];
if( parsetree.children[0] )
{
var type0 = SWYM.CompileNode( parsetree.children[0], cscope, tempexecutable0 );
type0 = SWYM.TypeCoerce(argTypes[0], type0, parsetree.children[0], "operator '"+parsetree.op.text+"'");
++numArgs;
}
var tempexecutable1 = [];
if( parsetree.children[1] )
{
var type1 = SWYM.CompileNode( parsetree.children[1], cscope, tempexecutable1 );
type1 = SWYM.TypeCoerce(argTypes[1], type1, parsetree.children[1], "operator '"+parsetree.op.text+"'");
++numArgs;
}
SWYM.pushEach(tempexecutable0, executable);
SWYM.pushEach(tempexecutable1, executable);
if( type0 && type0.multivalueOf !== undefined )
{
SWYM.pushEach(["#DoMultiple", 0, numArgs, type0.quantifier === undefined? 1: type0.quantifier.length], executable);
oldExecutable = executable;
executable = [];
oldExecutable.push(executable);
}
if( type1 && type1.multivalueOf !== undefined )
{
SWYM.pushEach(["#DoMultiple", numArgs-1, numArgs, type1.quantifier === undefined? 1: type1.quantifier.length], executable);
oldExecutable = executable;
executable = [];
oldExecutable.push(executable);
}
/* if( (type0 && type0.multivalueOf !== undefined) || (type1 && type1.multivalueOf !== undefined) )
{
var isLazy = false;
SWYM.pushEach(tempexecutable0, executable);
if( parsetree.children[0] && (!type0 || type0.multivalueOf === undefined) )
{
executable.push("#SingletonArray");
}
else if( type0 && type0.isLazy )
{
isLazy = true;
}
SWYM.pushEach(tempexecutable1, executable);
if( parsetree.children[1] && (!type1 || type1.multivalueOf === undefined) )
{
executable.push("#SingletonArray");
}
else if( type1 && type1.isLazy )
{
isLazy = true;
}
executable.push(isLazy? "#LazyNative": "#MultiNative");
executable.push(numArgs);
executable.push(opFunction);
if( parsetree.op.behaviour.returnType )
{
return SWYM.ToMultivalueType(parsetree.op.behaviour.returnType);
}
else if( parsetree.op.behaviour.getReturnType )
{
return SWYM.ToMultivalueType(parsetree.op.behaviour.getReturnType(type0, type1));
}
else
{
SWYM.LogError(0, "Fsckup: Undefined return type for operator "+parsetree.op.text);
return SWYM.ToMultivalueType(SWYM.DontCareType);
}
}
else
{
SWYM.pushEach(tempexecutable0, executable);
SWYM.pushEach(tempexecutable1, executable);
*/ if( typeof(opFunction) !== 'function' )
{
SWYM.LogError(parsetree.op, "Illegal use of operator "+parsetree.op.text);
}
executable.push("#Native");
executable.push(numArgs);
executable.push(opFunction);
if( parsetree.op.behaviour.returnType )
{
returnType = parsetree.op.behaviour.returnType;
}
else if( parsetree.op.behaviour.getReturnType )
{
returnType = parsetree.op.behaviour.getReturnType(SWYM.ToSinglevalueType(type0), SWYM.ToSinglevalueType(type1));
}
else
{
SWYM.LogError(parsetree, "Fsckup: Undefined return type for operator "+parsetree.op.text);
returnType = {type:"value"};
}
// }
if( type1 && type1.multivalueOf !== undefined )
{
returnType = SWYM.ToMultivalueType(returnType, type1.quantifier);
}
if( type0 && type0.multivalueOf !== undefined )
{
returnType = SWYM.ToMultivalueType(returnType, type0.quantifier);
}
return returnType;
}
else
{
SWYM.LogError(parsetree, "Symbol \""+parsetree.text+"\" is illegal here.");
}
return SWYM.DontCareType; // if an error occurred, don't care about type
}
SWYM.CompileNode = function(node, cscope, executable)
{
if( SWYM.errors )
{
return SWYM.DontCareType;
}
var resultType = SWYM.CompileLValue(node, cscope, executable);
while(true)
{
if( !resultType )
{
break;
}
else if(resultType.multivalueOf !== undefined && resultType.quantifier !== undefined && resultType.quantifier.length >= 2 &&
resultType.quantifier[0] === "EACH" && resultType.quantifier[1] === "EACH")
{
executable.push("#Flatten");
resultType = {type:resultType.type, multivalueOf:resultType.multivalueOf, quantifier:resultType.quantifier.slice(1)};
}
/* // autoresolving quantifiers that produce boolean values (bad idea)
else if( resultType.multivalueOf !== undefined && resultType.quantifier !== undefined && resultType.quantifier[0] !== "EACH" && resultType.multivalueOf === SWYM.BoolType )
{
// Insert the instruction(s) to resolve this quantifier into a single value
var qexecutable = [];
for( var Idx = resultType.quantifier.length-1; Idx >= 0; --Idx )
{
var q = resultType.quantifier[Idx];
if( q === "OR" )
{
qexecutable.push("#ORQuantifier");
}
else if( q === "AND" )
{
qexecutable.push("#ANDQuantifier");
}
else if( q === "NOR" )
{
qexecutable.push("#NORQuantifier");
}
else if( q === "NAND" )
{
qexecutable.push("#NANDQuantifier");
}
if( Idx > 0 )
{
qexecutable = ["#DoMultiple", 0, 1, 1, qexecutable];
}
}
SWYM.pushEach( qexecutable, executable );
resultType = SWYM.BoolType;
}
*/ else
{
break;
}
}
var testType = resultType;
if( resultType && resultType.multivalueOf !== undefined )
testType = resultType.multivalueOf;
// We want to condense an array of StringChars into a String.
// This slightly dodgy test checks for an array of StringChars that's not already a String.
if( testType && testType.nativeType !== "String" && !testType.isMutable &&
testType.outType && testType.outType.isStringChar &&
testType.memberTypes && testType.memberTypes.length && !testType.isLazy )
{
if( resultType.multivalueOf !== undefined )
{
executable.push("#MultiNative");
executable.push(1);
executable.push(function(v){ return SWYM.StringWrapper(SWYM.ToTerseString(v)); });
resultType = SWYM.ToMultivalueType(SWYM.StringType);
}
else
{
executable.push("#ToTerseString");
if( testType.baked !== undefined )
{
resultType = SWYM.BakedValue(SWYM.StringWrapper(SWYM.ToTerseString(testType.baked)));
}
else if( testType.memberTypes.length.baked !== undefined )
{
resultType = SWYM.FixedLengthStringType(testType.memberTypes.length.baked);
}
else
{
resultType = SWYM.StringType;
}
}
}
return resultType;
}
SWYM.GetPositionalName = function(theFunction, expectedArgName)
{
if( expectedArgName === "#" )
{
return undefined;
}
else if(expectedArgName === "this" && !theFunction.hasOnlyThis ) // 'this' can only be used as a positional argument if it's the _only_ argument.
{
return undefined;
}
var targetIdx = theFunction.expectedArgs[expectedArgName].index+1;
if( theFunction.expectedArgs["this"] && theFunction.expectedArgs["this"].index < theFunction.expectedArgs[expectedArgName].index )
{
targetIdx -= 1;
}
if( theFunction.expectedArgs["#"] && theFunction.expectedArgs["#"].index < theFunction.expectedArgs[expectedArgName].index )
{
targetIdx -= 1;
}
return "__"+targetIdx;
}
SWYM.CompileFunctionCall = function(fnNode, cscope, executable, OUT)
{
var fnName = fnNode.name;
var numAnonymous = 0;
var args = {};
var argNames = [];
for( var Idx = 0; Idx < fnNode.argNames.length; ++Idx )
{
if( fnNode.argNames[Idx] === "__" )
{
var argName = "__"+numAnonymous;
++numAnonymous;
}
else
{
var argName = fnNode.argNames[Idx];
}
args[argName] = fnNode.children[Idx];
argNames.push(argName);
}
var isMulti = false;
var inputArgTypes = {};
var inputArgExecutables = {};
for( Idx = 0; Idx < argNames.length; ++Idx )
{
var inputExecutable = [];
var inputArgName = argNames[Idx];
var inputType = SWYM.CompileNode( args[inputArgName], cscope, inputExecutable );
if( inputType && inputType.multivalueOf !== undefined )
{
isMulti = true;
}
inputArgTypes[inputArgName] = inputType;
inputArgExecutables[inputArgName] = inputExecutable;
}
var fnScopeName = "fn#"+fnName;
var overloads = cscope[fnScopeName];
if( !overloads )
{
SWYM.LogError(fnNode, "Unknown function "+fnName);
return SWYM.DontCareType;
}
var validOverloads = [];
var invalidOverloads = [];
for( var Idx = 0; Idx < overloads.length; ++Idx )
{
var overloadResult = SWYM.TestFunctionOverload(fnName, args, cscope, overloads[Idx], isMulti, inputArgTypes, inputArgExecutables, fnNode, false);
if( overloadResult.error === undefined )
{
validOverloads.push(overloadResult);
}
else
{
invalidOverloads.push(overloads[Idx]);
}
if( Idx === overloads.length-1 && overloads.more )
{
// skip any 0-length entries
do
{
Idx = -1;
overloads = overloads.more;
}
while( Idx === overloads.length-1 && overloads.more );
continue;
}
}
if( validOverloads.length === 0 )
{
var bestError = undefined;
var bestErrorQuality = -1;
for( var Idx = 0; Idx < invalidOverloads.length; ++Idx )
{
var overloadResult = SWYM.TestFunctionOverload(fnName, args, cscope, invalidOverloads[Idx], isMulti, inputArgTypes, inputArgExecutables, fnNode, true);
if( bestErrorQuality < overloadResult.quality )
{
bestError = overloadResult.error;
bestErrorQuality = overloadResult.quality;
}
}
SWYM.LogError(fnNode, bestError);
return SWYM.DontCareType;
}
var chosenOverload = undefined;
if( validOverloads.length === 1 )
{
chosenOverload = validOverloads[0];
}
else
{
// Try to choose the strictest overload
var bestIdx = 0;
var bestTypeChecks = validOverloads[0].typeChecks;
for( var Idx = 1; Idx < validOverloads.length; ++Idx )
{
var curTypeChecks = validOverloads[Idx].typeChecks;
if( SWYM.TypeChecksStricter(curTypeChecks, bestTypeChecks) )
{
bestIdx = Idx;
bestTypeChecks = curTypeChecks;
}
}
// Sanity check: is this one stricter than all the others?
var isStricter = true;
for( var Idx = 0; Idx < validOverloads.length; ++Idx )
{
if( bestIdx != Idx && !SWYM.TypeChecksStricter(bestTypeChecks, validOverloads[Idx].typeChecks) )
{
isStricter = false;
break;
}
}
if( isStricter )
{
chosenOverload = validOverloads[bestIdx];
}
else
{
SWYM.LogError(fnNode, "Too many valid overloads for "+fnName+"! (we don't do dynamic dispatch yet.)");
return;
}
}
if(OUT !== undefined)
{
OUT.theFunction = chosenOverload.theFunction;
}
if (fnNode.rhsOmittedStart)
{
var identityArg = chosenOverload.theFunction.expectedArgs["__identity"];
if( identityArg === undefined || identityArg.defaultValueNode === undefined )
{
SWYM.LogError(parsetree, "Function "+fnName+" can't be used in etc expressions because it has no default argument named __identity.");
}
var identityType = SWYM.CompileNode(identityArg.defaultValueNode, cscope, []);
if (fnNode.children[1].etcSequence === undefined || identityType.baked === undefined)
{
SWYM.LogError(parsetree, "Fsckup: Unable to restore omitted identity value to etcSequence");
}
else
{
// 'omitted start' means we wanted to insert the identity value into this sequence,
/// but didn't know what it was yet. Now we do!
fnNode.children[1].etcSequence.unshift(identityType.baked);
var recompiledSequenceExecutable = [];
SWYM.CompileNode(fnNode.children[1], cscope, recompiledSequenceExecutable);
chosenOverload.inputArgExecutables[chosenOverload.inputArgNameList[1]] = recompiledSequenceExecutable;
}
}
return SWYM.CompileFunctionOverload( fnName, chosenOverload, cscope, executable );
}
SWYM.TypeChecksStricter = function(curTypeChecks, bestTypeChecks)
{
var stricter = false;
var noneWeaker = true;
for( var inputArgName in curTypeChecks )
{
if( !stricter && !SWYM.TypeMatches( curTypeChecks[inputArgName], bestTypeChecks[inputArgName] ))
{
stricter = true;
}
if( !SWYM.TypeMatches( bestTypeChecks[inputArgName], curTypeChecks[inputArgName] ) )
{
// nope, it's stricter.
return false;
}
}
return stricter;
}
SWYM.RecordErrorOfQuality = function(overloadResult, quality)
{
// Returns true if this is the worst error we've seen
if( overloadResult.quality == 0 || overloadResult.quality > quality )
{
overloadResult.quality = quality;
return true;
}
return false;
}
// returns {error:<an error>, executable:<an executable>, returnType:<a return type>}
SWYM.TestFunctionOverload = function(fnName, args, cscope, theFunction, isMulti, inputArgTypes, inputArgExecutables, fnNode, isErrorReport)
{
var overloadResult = { theFunction: theFunction, error: undefined, executable: [], typeChecks: {}, returnType: undefined, quality: 0 };
// if this needs to have a __identity argument, ignore overloads without one
if ((fnNode.etcId !== undefined || fnNode.rhsOmittedStart) && theFunction.expectedArgs.__identity === undefined)
{
if (SWYM.RecordErrorOfQuality(overloadResult, 80)) // ok match, this is an esoteric issue
{
overloadResult.error = "Function '" + fnName + "' can't form an etc sequence because it has no '__identity' argument.";
}
}
// surely we should precompute this information?
var expectedArgNamesByIndex = theFunction.expectedArgNamesByIndex;
var nextPositionalArg = 0;
var inputArgNameList = [];
var qualityBonus = 0;
var checkExtraneousArgs = true;
// make sure all the expected args are present and of the correct types
for( var expectedArgIndex = 0; expectedArgIndex < expectedArgNamesByIndex.length; ++expectedArgIndex )
{
var expectedArgName = expectedArgNamesByIndex[expectedArgIndex];
if( expectedArgName && args[expectedArgName] === undefined )
{
var positionalArgName = theFunction.expectedArgs[expectedArgName].indexName;
if( positionalArgName === undefined )
{
positionalArgName = "__"+nextPositionalArg;
}
// Some parameters don't accept anonymous positional arguments.
if( args[positionalArgName] !== undefined && !theFunction.expectedArgs[expectedArgName].explicitNameRequired )
{
inputArgNameList[expectedArgIndex] = positionalArgName;
++nextPositionalArg;
}
else if( theFunction.expectedArgs[expectedArgName].defaultValueNode === undefined )
{
// We found a missing parameter (and it's not allowed to be missing).
if( !isErrorReport )
{
overloadResult.error = "i dunno some error";
return overloadResult;
}
var typeSig = "";
if( theFunction.expectedArgs[expectedArgName].typeCheck !== undefined )
{
typeSig = "("+SWYM.TypeToString( theFunction.expectedArgs[expectedArgName].typeCheck ) + ") ";
}
if( expectedArgName === "this" )
{
if( SWYM.RecordErrorOfQuality(overloadResult, 10) ) // very bad match
{
overloadResult.error = "Function '"+fnName+"' requires a 'this' parameter. something."+fnName+"";
}
}
else if( expectedArgName === "else" )
{
if( SWYM.RecordErrorOfQuality(overloadResult, 10) ) // very bad match
{
overloadResult.error = "Function '"+fnName+"' requires an 'else' parameter. fnName() else {...}";
}
}
else if( args[positionalArgName] !== undefined && theFunction.expectedArgs[expectedArgName].explicitNameRequired )
{
if( SWYM.RecordErrorOfQuality(overloadResult, 50) ) // reasonable match, you just forgot to state the name
{
overloadResult.error = "Function '"+fnName+"'s parameter '"+expectedArgName+"' must be given explicitly: "+fnName+"("+expectedArgName+"=something)";
}
checkExtraneousArgs = false; // suppress errors about the extraneous positional argument
}
else
{
if( SWYM.RecordErrorOfQuality(overloadResult, 10) ) // very bad match
{
overloadResult.error = "Function '"+fnName+"' requires an additional argument "+typeSig+"'"+expectedArgName+"'";
}
}
continue;
}
}
else
{
inputArgNameList[expectedArgIndex] = expectedArgName;
}
var inputArgName = inputArgNameList[expectedArgIndex];
if( inputArgName !== undefined ) // undefined = default parameter
{
if( inputArgTypes[inputArgName] )
{
var expectedArgTypeCheck = theFunction.expectedArgs[expectedArgName].typeCheck;
if( !expectedArgTypeCheck )
{
expectedArgTypeCheck = SWYM.AnyType;
}
overloadResult.typeChecks[inputArgName] = expectedArgTypeCheck;
if( SWYM.TypeMatches(expectedArgTypeCheck, inputArgTypes[inputArgName]) )
{
// Each valid argument increases the likelihood that this was the intended overload
qualityBonus += 10;
}
else
{
// Found an argument of the wrong type
if( !isErrorReport )
{
overloadResult.error = "eek an error, run!";
return overloadResult;
}
if( SWYM.RecordErrorOfQuality(overloadResult, 100) ) // pretty good match - just the wrong type
{
var expectedString;
if( inputArgTypes[inputArgName].baked !== undefined )
{
expectedString = " ("+SWYM.ToDebugString(inputArgTypes[inputArgName].baked)+" is not of type "+SWYM.TypeToString(expectedArgTypeCheck)+")";
}
else
{
expectedString = " (Expected "+SWYM.TypeToString(expectedArgTypeCheck)+", got "+SWYM.TypeToString(inputArgTypes[inputArgName])+")";
}
if( inputArgName !== expectedArgName )
{
overloadResult.error = "Positional argument "+inputArgName+" was the wrong type for parameter \""+expectedArgName+"\" during call to function '"+fnName+"'."+expectedString;
}
else
{
overloadResult.error = "Argument '"+expectedArgName+"' was the wrong type during call to function '"+fnName+"'."+expectedString;
}
}
continue;
}
}
}
}
// make sure there are no extraneous args
if( checkExtraneousArgs )
{
for ( var remainingArgName in args )
{
var matched = false;
for( var expectedArgIndex = 0; expectedArgIndex < expectedArgNamesByIndex.length; ++expectedArgIndex )
{
if( remainingArgName === inputArgNameList[expectedArgIndex] )
{
matched = true;
break;
}
}
if( !matched )
{
if( !isErrorReport )
{
overloadResult.error = "errors, lol";
return overloadResult;
}
// terrible match: extraneous arguments are unlikely to be an accident
//(it's not just a mistyped name - that would have been caught as a missing argument.)
if( SWYM.RecordErrorOfQuality(overloadResult, 5) )
{
if(remainingArgName === "__mutator")
overloadResult.error = "function '"+fnName+"' does not support = modification.";
else
overloadResult.error = "Unexpected argument '"+remainingArgName+"' during call to function '"+fnName+"'.";
}
}
}
}
var finalArgTypes = [];
for( var Idx = 0; Idx < inputArgNameList.length; Idx++ )
{
var inputArgName = inputArgNameList[Idx];
finalArgTypes[Idx] = inputArgTypes[inputArgName];
}
if( theFunction.customTypeCheck )
{
var typeCheckResult = theFunction.customTypeCheck(finalArgTypes);
if( typeCheckResult !== true )
{
if( !isErrorReport )
{
overloadResult.error = "error, whatevs";
return overloadResult;
}
// pretty good match, just failed at the last hurdle
if( SWYM.RecordErrorOfQuality(overloadResult, 120) )
{
overloadResult.error = typeCheckResult;
}
return overloadResult;
}
}
if( isErrorReport )
{
if( overloadResult.error )
{
overloadResult.quality += qualityBonus;
return overloadResult;
}
// Why are we still here!?
SWYM.LogError(0, "Fsckup: Expected an error while compiling this overload");
}
// in order to support function overloads, this function has been split into two halves.
// the logic continues in CompileFunctionOverload (below), using the following data.
overloadResult.inputArgNameList = inputArgNameList;
overloadResult.inputArgNodes = args; // we've finished processing these; they're just here for error reporting
overloadResult.expectedArgNamesByIndex = expectedArgNamesByIndex;
overloadResult.inputArgTypes = inputArgTypes;
overloadResult.finalArgTypes = finalArgTypes;
overloadResult.inputArgExecutables = inputArgExecutables;
overloadResult.theFunction = theFunction;
overloadResult.isMulti = isMulti;
overloadResult.errorNode = fnNode;
return overloadResult;
}
//(fnName, args, cscope, theFunction, isMulti, inputArgTypes, inputArgExecutables)
SWYM.CompileFunctionOverload = function(fnName, data, cscope, executable)
{
var argIndices = [];
var customArgExecutables = [];
var numArgsPassed = 0;
var argNamesPassed = [];
var argTypesPassed = [];
var isMulti = data.isMulti;
var isLazy = false;
var isQuantifier = false;
var theFunction = data.theFunction;
var finalArgTypes = data.finalArgTypes;
var numArgsPushed = 0;
for (var Idx = 0; Idx < data.expectedArgNamesByIndex.length; Idx++)
{
var inputArgName = data.inputArgNameList[Idx];
var expectedArgName = data.expectedArgNamesByIndex[Idx];
if (inputArgName === undefined)
{
// default parameter
var defaultValueNode = theFunction.expectedArgs[expectedArgName].defaultValueNode;
data.inputArgNameList[Idx] = expectedArgName;
var defaultValueExecutable = [];
data.inputArgExecutables[inputArgName] = defaultValueExecutable;
// TODO: in allowing default arguments to access the caller's scope,
// this breaks the old behaviour, where default arguments could access other arguments
data.inputArgTypes[inputArgName] = SWYM.CompileNode(defaultValueNode, cscope, defaultValueExecutable);
}
var tempType = data.inputArgTypes[inputArgName];
argIndices[Idx] = Idx;
argTypesPassed[numArgsPassed] = SWYM.ToSinglevalueType(tempType);
argNamesPassed[numArgsPassed] = theFunction.expectedArgs[expectedArgName].finalName;
++numArgsPassed;
if( tempType && tempType.multivalueOf !== undefined )
{
if( isMulti && tempType.isLazy )
{
isLazy = true;
}
if( tempType.quantifier !== undefined )
{
isQuantifier = true;
}
}
if( theFunction.customCompileWithoutArgs || (theFunction.bakeOutArgs && theFunction.bakeOutArgs[Idx]) )
{
customArgExecutables[Idx] = data.inputArgExecutables[inputArgName];
}
else
{
++numArgsPushed;
var argExecutable = data.inputArgExecutables[inputArgName];
if( argExecutable )
{
SWYM.pushEach(argExecutable, executable);
}
else
{
SWYM.LogError(-1, "Fsckup: Missing argExecutable for function "+fnName+", arg '"+inputArgName+"'");
}
SWYM.TypeCoerce(theFunction.expectedArgs[expectedArgName].typeCheck, tempType, data.inputArgNodes[expectedArgName], "calling '"+fnName+"', argument "+expectedArgName);
// if( isMulti && (!tempType || tempType.multivalueOf === undefined))
// {
// executable.push("#SingletonArray");
// }
}
}
if( isMulti && !theFunction.customCompileWithoutArgs )
{
var argPositionOnStack = 0;
for( var Idx = 0; Idx < data.inputArgNameList.length; Idx++ )
{
var inputArgName = data.inputArgNameList[Idx];
if( inputArgName !== undefined )
{
var tempType = data.inputArgTypes[inputArgName];
if( tempType && tempType.multivalueOf !== undefined )
{
finalArgTypes[Idx] = SWYM.ToSinglevalueType(tempType);
if( !theFunction.bakeOutArgs || !theFunction.bakeOutArgs[Idx] )
{
SWYM.pushEach(["#DoMultiple", argPositionOnStack, numArgsPushed, tempType.quantifier? tempType.quantifier.length: 1], executable);
oldExecutable = executable;
executable = [];
oldExecutable.push(executable);
}
}
if( !theFunction.bakeOutArgs || !theFunction.bakeOutArgs[Idx] )
{
++argPositionOnStack;
}
}
}
}
var resultType;
var didMultiCustomCompile = false;
if( theFunction.customCompileWithoutArgs )
{
if( !isMulti )
{
resultType = theFunction.customCompile(finalArgTypes, cscope, executable, data.errorNode, customArgExecutables);
}
else
{
//NB: multiCustomCompile is a special compile mode that takes raw multivalues (and/or normal values) as input.
resultType = theFunction.multiCustomCompile(finalArgTypes, cscope, executable, data.errorNode, customArgExecutables);
didMultiCustomCompile = true;
}
}
else if ( theFunction.customCompile || theFunction.nativeCode )
{
if( theFunction.customCompile )
{
resultType = theFunction.customCompile(finalArgTypes, cscope, executable, data.errorNode, customArgExecutables);
}
/* else if( isMulti && theFunction.multiCustomCompile )
{
//NB: multiCustomCompile is a special compile mode that takes raw multivalues (and/or normal values) as input,
// and generates multivalues as output. But for consistency with other compile modes,
// its result_type_ is still expected to be a single value type.
resultType = theFunction.multiCustomCompile(finalArgTypes, cscope, executable, customArgExecutables);
}
else if( isMulti && theFunction.customCompile && !theFunction.multiCustomCompile )
{
//var singularTypes = SWYM.Each(finalArgTypes, SWYM.ToSinglevalueType);
resultType = theFunction.customCompile(finalArgTypes, cscope, executable, customArgExecutables);
}
*/
else if( theFunction.nativeCode )
{
/* if( isMulti )
{
executable.push(isLazy? "#LazyNative": "#MultiNative");
executable.push(numArgsPushed);
executable.push(theFunction.nativeCode);
}
else
{
*/ executable.push("#Native");
executable.push(numArgsPushed);
executable.push(theFunction.nativeCode);
// }
}
if( theFunction.getReturnType )
{
if( isMulti )
{
var singularTypes = SWYM.Each(finalArgTypes, SWYM.ToSinglevalueType);
resultType = theFunction.getReturnType(singularTypes, cscope);
}
else
{
resultType = theFunction.getReturnType(finalArgTypes, cscope);
}
}
}
else
{
var precompiled = SWYM.GetPrecompiled(theFunction, argTypesPassed);
if( precompiled.executable === undefined || (precompiled.isCompiling && !precompiled.isRecursive) )
{
var targetExecutable = [];
if( precompiled.isCompiling )
{
// if we're compiling recursively, don't actually keep the target executable.
// (only the outermost call will write the executable.)
precompiled.isRecursive = true;
}
else
{
precompiled.executable = targetExecutable;
}
if( theFunction.compiling !== undefined && theFunction.compiling.length > 30 &&
theFunction.compiling.indexOf(precompiled) === -1 )
{
// we appear to be compiling a recursive function whose argument types don't remain consistent
// as it recurses. Illegal!
SWYM.LogError(-1, "Inconsistent argument types during recursive function call to "+fnName+"!");
return SWYM.DontCareType;
}
if( theFunction.returnType !== undefined && theFunction.returnType.type !== "incomplete" )
precompiled.returnType = theFunction.returnType;
else
precompiled.returnType = SWYM.DontCareType; // treat recursive calls as though they yield our least offensive type.
var bodyCScope = object(SWYM.MainCScope);
for( var Idx = 0; Idx < argNamesPassed.length; Idx++ )
{
bodyCScope[argNamesPassed[Idx]] = argTypesPassed[Idx];
if(argNamesPassed[Idx] === "this")
bodyCScope["__default"] = { redirect: "this" };
}
var oldIsCompiling = precompiled.isCompiling;
if( theFunction.compiling === undefined )
{
theFunction.compiling = [];
}
theFunction.compiling.push(precompiled);
precompiled.isCompiling = true;
precompiled.returnType = SWYM.CompileFunctionBody(fnName, theFunction, bodyCScope, targetExecutable);
precompiled.isCompiling = oldIsCompiling;
theFunction.compiling.pop();
/*if( precompiled.isRecursive )
{
// if it's a recursive function, recompile now that we have proper type information.
precompiled.executable.clear();
precompiled.isCompiling = true;
precompiled.returnType = SWYM.CompileFunctionBody(theFunction, bodyCScope, precompiled.executable);
precompiled.isCompiling = false;
}*/
if( SWYM.errors )
{
for( var Idx = 0; Idx < theFunction.precompiled.length; ++Idx )
{
if( theFunction.precompiled[Idx] === precompiled )
{
theFunction.precompiled.splice(Idx, 1);
break;
}
}
}
}
resultType = precompiled.returnType;
/* if( isMulti )
{
executable.push("#MultiFnCall");
executable.push(fnName);
executable.push(argNamesPassed);
executable.push(precompiled.executable);
}
else
{
*/ executable.push("#FnCall");
executable.push(fnName);
executable.push(argNamesPassed);
executable.push(precompiled.executable);
// }
}
if( resultType === undefined )
{
if( theFunction.returnType !== undefined )
{
resultType = theFunction.returnType;
}
else
{
resultType = SWYM.GetFunctionReturnType(finalArgTypes, argIndices, data.theFunction);
}
}
if( !didMultiCustomCompile && (isMulti || isQuantifier) )
{
for( var Idx = data.inputArgNameList.length-1; Idx >= 0; --Idx )
{
var inputArgName = data.inputArgNameList[Idx];
if( inputArgName !== undefined )
{
var tempType = data.inputArgTypes[inputArgName];
if( tempType && tempType.multivalueOf !== undefined )
{
resultType = SWYM.ToMultivalueType(resultType, tempType.quantifier);
}
}
}
}
return resultType;
}
SWYM.GetPrecompiled = function(theFunction, argTypes)
{
if( !theFunction.precompiled )
{
theFunction.precompiled = [];
}
for( var Idx = 0; Idx < theFunction.precompiled.length; ++Idx )
{
var cur = theFunction.precompiled[Idx];
var matched = !!cur.types && cur.types.length === argTypes.length;
if( matched )
{
for( var AIdx = 0; AIdx < cur.types.length; ++AIdx )
{
if( argTypes[AIdx] )
{
if( !SWYM.TypeMatches(cur.types[AIdx], argTypes[AIdx], true) ||
!SWYM.TypeMatches(argTypes[AIdx], cur.types[AIdx], true) )
{
matched = false;
break;
}
}
}
}
if( matched )
{
return cur;
}
}
var result = { types:argTypes };
theFunction.precompiled.push(result);
return result;
}
SWYM.CompileFunctionDeclaration = function(fnName, argNames, argTypes, body, returnTypeNode, cscope, executable, errorNode)
{
// var bodyCScope = object(cscope);
var bodyScope = object(cscope);
var expectedArgs = {};
var numPositionalArgs = 0;
for( var argIdx = 0; argIdx < argNames.length; ++argIdx )
{
// var typeCheck = {type:"value", canConstrain:true};
var finalName = argNames[argIdx];
var argNode = argTypes[argIdx];
var defaultValueNode = undefined;
if( finalName === "this" || finalName === "else" || finalName === "#" || finalName === "__mutator" )
{
// special arguments must always be passed explicitly by name
// (we make an exception for "this" later in the function)
argNode.explicitNameRequired = true;
}
else if( finalName === "__" )
{
// turn anonymous arguments (e.g: deconstructors with named parts,
// but the argument as a whole is anonymous) into positional arguments
finalName += argIdx;
}
if( !argNode.explicitNameRequired )
{
numPositionalArgs++;
}
var argData = {finalName:finalName, index:argIdx, explicitNameRequired:argNode.explicitNameRequired};
if( argNode.op && argNode.op.text === "(" && !argNode.children[0] )
{
argNode = argNode.children[1];
}
if( argNode !== undefined && argNode.op && argNode.op.text === "=" )
{
argData.defaultValueNode = argNode.children[1];
argNode = argNode.children[0];
}
if( argNode !== undefined && argNode.type === "decl" )
{
if( argNode.children !== undefined )
{
argNode = argNode.children[0];
}
else
{
argNode = undefined;
}
}
if( argNode !== undefined )
{
if ( argNode.type === "node" && argNode.op.text === "[" )
{
// deconstructing arg
argData.deconstructNode = argNode;
}
// a type check
var typeCheckScope = object(cscope);
for(var expected in expectedArgs)
{
typeCheckScope[expected] = SWYM.DontCareType;
}
var typeCheckType = SWYM.GetTypeFromPatternNode(argNode, typeCheckScope);
if( typeCheckType === undefined && argNode !== undefined )
{
SWYM.LogError(argNode, "Invalid type expression");
}
argData.typeCheck = typeCheckType;
}
expectedArgs[finalName] = argData;
}
// special exception for the "this" parameter: it can be passed
// anonymously if there's no other anonymous argument.
if( numPositionalArgs === 0 && expectedArgs["this"] !== undefined )
{
expectedArgs["this"].explicitNameRequired = false;
}
var returnType = {type:"incomplete", debugName:"incomplete"};
if( returnTypeNode !== undefined )
{
var returnTypeBaked = SWYM.CompileNode(returnTypeNode, cscope, executable);
if( returnTypeBaked === undefined || returnTypeBaked.baked === undefined || returnTypeBaked.baked.type !== "type" )
{
SWYM.LogError(returnTypeNode, "This return type is not a type!");
}
returnType = returnTypeBaked.baked;
}
if( fnName === "fn#$$" )
{
var structType = expectedArgs["this"].typeCheck;
if( structType === undefined || structType.nativeType !== "Struct" )
{
SWYM.LogError(errorNode, "The $$ operator can only be declared on a struct type.");
return SWYM.VoidType;
}
var debugName = structType.debugName;
// Ugh... this is really hacky, and doesn't even work if stdlyb wants to define $$ overrides.
//Surely we can do something better?
var bodyExecutable = [];
var bodyCScope = object(SWYM.MainCScope);
bodyCScope["this"] = structType;
bodyCScope["__default"] = {redirect:"this"};
var returnType = SWYM.CompileNode(body, bodyCScope, bodyExecutable);
SWYM.TypeCoerce(returnType, SWYM.StringType);
structType.toDebugString = function(value)
{
var rscope = object(SWYM.MainRScope);
rscope["this"] = value;
return SWYM.ExecWithScope(debugName, bodyExecutable, rscope, []);
}
return SWYM.VoidType;
}
var cscopeFunction = {
bodyNode:body,
expectedArgs:expectedArgs,
executable:[],
// bodyCScope:bodyCScope, // used by implicit member definitions, like 'Yielded'.
toString: function(){ return "'"+fnName+"'"; },
returnType:returnType
};
SWYM.AddFunctionDeclaration(fnName, cscope, cscopeFunction);
var negateExecutable = ["#Native",1,function(v){return !v}];
if( fnName === "fn#<" )
{
expectedArgs["this"].explicitNameRequired = false;
SWYM.AddModifiedFunctionDeclaration("fn#>=", fnName, cscope, cscopeFunction, negateExecutable);
SWYM.AddSwappedFunctionDeclaration("fn#>", fnName, cscope, cscopeFunction, undefined);
SWYM.AddSwappedFunctionDeclaration("fn#<=", fnName, cscope, cscopeFunction, negateExecutable);
}
else if( fnName === "fn#>" )
{
expectedArgs["this"].explicitNameRequired = false;
SWYM.AddModifiedFunctionDeclaration("fn#<=", fnName, cscope, cscopeFunction, negateExecutable);
SWYM.AddSwappedFunctionDeclaration("fn#<", fnName, cscope, cscopeFunction, undefined);
SWYM.AddSwappedFunctionDeclaration("fn#>=", fnName, cscope, cscopeFunction, negateExecutable);
}
else if( fnName === "fn#<=" )
{
expectedArgs["this"].explicitNameRequired = false;
SWYM.AddModifiedFunctionDeclaration("fn#>", fnName, cscope, cscopeFunction, negateExecutable);
SWYM.AddSwappedFunctionDeclaration("fn#>=", fnName, cscope, cscopeFunction, undefined);
SWYM.AddSwappedFunctionDeclaration("fn#<", fnName, cscope, cscopeFunction, negateExecutable);
}
else if( fnName === "fn#>=" )
{
expectedArgs["this"].explicitNameRequired = false;
SWYM.AddModifiedFunctionDeclaration("fn#<", fnName, cscope, cscopeFunction, negateExecutable);
SWYM.AddSwappedFunctionDeclaration("fn#<=", fnName, cscope, cscopeFunction, undefined);
SWYM.AddSwappedFunctionDeclaration("fn#>", fnName, cscope, cscopeFunction, negateExecutable);
}
else if( fnName === "fn#==" )
{
SWYM.AddModifiedFunctionDeclaration("fn#!=", fnName, cscope, cscopeFunction, negateExecutable);
}
else if( fnName === "fn#!=" )
{
SWYM.AddModifiedFunctionDeclaration("fn#==", fnName, cscope, cscopeFunction, negateExecutable);
}
return SWYM.VoidType;
}
SWYM.PrecomputeFunctionProperties = function(theFunction)
{
// so that we don't have to calculate this stuff for each function call
var expectedArgNamesByIndex = [];
var hasOnlyThis = theFunction.expectedArgs["this"] !== undefined;
for( var expectedArgName in theFunction.expectedArgs )
{
expectedArgNamesByIndex[ theFunction.expectedArgs[expectedArgName].index ] = expectedArgName;
if( expectedArgName !== "this" && expectedArgName !== "#" )
{
hasOnlyThis = false;
}
}
theFunction.hasOnlyThis = hasOnlyThis;
theFunction.expectedArgNamesByIndex = expectedArgNamesByIndex;
}
SWYM.AddFunctionDeclaration = function(fnName, cscope, cscopeFunction)
{
if( !cscope[fnName] )
{
cscope[fnName] = [];
}
else if( !cscope.hasOwnProperty(fnName) )
{
// FIXME: this is technically wrong, we ought to go through the __proto__ chain adding a placeholder to every level of the scope.
// there must be a better way to handle overloads...
var temp = cscope[fnName];
cscope[fnName] = [];
cscope[fnName].more = temp;
}
SWYM.PrecomputeFunctionProperties(cscopeFunction);
cscope[fnName].push( cscopeFunction );
}
SWYM.AddModifiedFunctionDeclaration = function(fnName, baseName, cscope, baseFunction, postExec)
{
var negatedFunction = {
bodyNode:baseFunction.bodyNode,
expectedArgs:baseFunction.expectedArgs,
executable:[],
postExec:postExec,
toString: function(){ return "'"+fnName+"'"; },
returnType:object(baseFunction.returnType)
};
SWYM.AddFunctionDeclaration(fnName, cscope, negatedFunction);
}
SWYM.AddSwappedFunctionDeclaration = function(fnName, baseName, cscope, baseFunction, postExec)
{
var swappedArgs = object(baseFunction.expectedArgs);
var arg0;
var arg1;
for( var argName in baseFunction.expectedArgs )
{
if( baseFunction.expectedArgs[argName].index === 0 )
{
arg0 = argName;
}
else if( baseFunction.expectedArgs[argName].index === 1 )
{
arg1 = argName;
}
}
swappedArgs[arg0] = baseFunction.expectedArgs[arg1];
swappedArgs[arg1] = baseFunction.expectedArgs[arg0];
var swappedFunction = {
bodyNode:baseFunction.bodyNode,
expectedArgs:swappedArgs,
executable:[],
postExec:postExec,
toString: function(){ return "'"+fnName+"'"; },
returnType:object(baseFunction.returnType)
};
SWYM.AddFunctionDeclaration(fnName, cscope, swappedFunction);
}
SWYM.GetTypeFromPatternNode = function(node, cscope)
{
if( node === undefined )
{
return SWYM.AnythingType;
}
else if ( node.type === "node" && node.op.text === "[" )
{
// array/tuple pattern such as [Int, Int]
var elements = [];
if( node.children[1] === undefined )
{
return SWYM.BakedValue(SWYM.jsArray([]));
}
else if( node.children[1].op !== undefined )
{
var opText = node.children[1].op.text;
if( opText === ".." || opText === "..<" || opText === "<.." || opText === "<..<" )
{
return SWYM.RangeArrayType;
}
else if( opText === "," )
{
var result = SWYM.GetTypeFromTupleContentNode( node.children[1], cscope, elements );
return SWYM.TupleTypeOf(elements, undefined, node);
}
}
else
{
return SWYM.TupleTypeOf( [SWYM.GetTypeFromPatternNode(node.children[1], cscope)], undefined, node );
}
}
else
{
var outType;
var debugName;
if( node.type === "decl" )
{
// a declaration such as Int 'x'
if( node.children !== undefined )
{
return SWYM.GetTypeFromPatternNode(node.children[0], cscope);
}
else
{
return SWYM.AnythingType;
}
}
else if( node.type === "name" )
{
// a type literal such as Int
debugName = node.text;
outType = cscope[node.text];
}
else
{
var executable = [];
outType = SWYM.CompileNode(node, cscope, executable);
debugName = "??<FIXME>"; // we really need a node to string function...
}
if( outType === undefined )
{
SWYM.LogError(node, "Unknown type name "+debugName);
return;
}
else if ( outType.baked === undefined || outType.baked.type !== "type" )
{
SWYM.LogError(node, "Name "+debugName+" does not describe a type.");
return;
}
return outType.baked;
}
}
SWYM.GetTypeFromTupleContentNode = function( node, cscope, elements )
{
if( node !== undefined )
{
if( node.op && node.op.text === "," )
{
SWYM.GetTypeFromTupleContentNode( node.children[0], cscope, elements );
SWYM.GetTypeFromTupleContentNode( node.children[1], cscope, elements );
}
else
{
elements.push( SWYM.GetTypeFromPatternNode(node, cscope) );
}
}
}
SWYM.CompileFunctionBody = function(fnName, theFunction, cscope, executable)
{
var theCurrentFunction = object(theFunction);
// settings for use by 'yield' and 'return' statements.
cscope["<withinFunction>"] = theCurrentFunction;
theCurrentFunction.bodyCScope = cscope;
var bodyExecutable = [];
for( var argName in theFunction.expectedArgs )
{
var theArg = theFunction.expectedArgs[argName];
var deconstructNode = theArg.deconstructNode;
if( deconstructNode )
{
var argName = SWYM.CScopeRedirect(cscope, argName);
bodyExecutable.push("#Load");
bodyExecutable.push(argName);
SWYM.CompileDeconstructor(deconstructNode, cscope, bodyExecutable, cscope[argName] );
bodyExecutable.push("#Pop");
}
else if( cscope[argName] === undefined )
{
// this arg wasn't passed in; it better be a default parameter
if( theArg.defaultValueNode )
{
var defaultValueType = SWYM.CompileNode(theArg.defaultValueNode, cscope, bodyExecutable);
bodyExecutable.push("#Store");
bodyExecutable.push(argName);
cscope[argName] = defaultValueType;
if( defaultValueType && theArg.typeCheck )
{
SWYM.TypeCoerce(theArg.typeCheck, defaultValueType, theArg.defaultValueNode, "default argument");
}
}
else
{
SWYM.LogError(-1, "Fsckup: Failed to pass argument "+argName);
}
}
}
var bodyReturnType = SWYM.CompileLValue(theFunction.bodyNode, cscope, bodyExecutable);
if( theCurrentFunction.returnsValue && theCurrentFunction.returnsNoValue )
{
SWYM.LogError(theFunction.bodyNode, "Not all returns from "+fnName+" return a value");
}
else if( theCurrentFunction.returnsValue && theCurrentFunction.yields )
{
SWYM.LogError(theFunction.bodyNode, "Function "+fnName+" cannot both yield and return values");
}
var returnType;
if( theCurrentFunction.yields )
{
SWYM.pushEach(["#Literal", SWYM.jsArray([]), "#Store", "Yielded", "#Pop"], executable);
if( theCurrentFunction.returnsNoValue )
SWYM.pushEach(["#ReceiveReturnNoValue", bodyExecutable], executable);
else
SWYM.pushEach(bodyExecutable, executable);
SWYM.pushEach(["#ClearStack", "#Load", "Yielded"], executable);
// yield keyword will set the return type
returnType = theCurrentFunction.returnType;
}
else if( theCurrentFunction.returnsValue )
{
SWYM.pushEach(["#ReceiveReturnValue", bodyExecutable], executable);
// return keyword will set the return type
returnType = theCurrentFunction.returnType;
}
else if( theCurrentFunction.returnsNoValue )
{
SWYM.pushEach(["#ReceiveReturnNoValue", bodyExecutable], executable);
theCurrentFunction.returnType = SWYM.VoidType;
returnType = SWYM.VoidType;
}
// else if( bodyReturnType && bodyReturnType.multivalue )
// {
// // Force the default return to be a single value
// SWYM.pushEach(["#Native", 1, function(v){return SWYM.ForceSingleValue(v);}], bodyExecutable);
// cscopeFunction.returnType = SWYM.ToSinglevalueType(bodyReturnType);
// }
else
{
SWYM.pushEach(bodyExecutable, executable);
if( theCurrentFunction.returnType !== undefined && theCurrentFunction.returnType.type !== "incomplete" )
{
if( theCurrentFunction.returnType.baked !== undefined && !SWYM.TypeMatches(theCurrentFunction.returnType, bodyReturnType) )
{
// force this return value (for Void, at least... but what about other baked types?)
executable.push("#ClearStack");
executable.push("#Literal");
executable.push(theCurrentFunction.returnType.baked);
}
else
{
SWYM.TypeCoerce(theCurrentFunction.returnType, bodyReturnType, theFunction.bodyNode);
returnType = SWYM.TypeIntersect(theCurrentFunction.returnType, bodyReturnType);
}
}
else
{
returnType = bodyReturnType;
}
}
if( theFunction.postExec !== undefined )
{
SWYM.pushEach(theFunction.postExec, executable);
}
return returnType;
}
SWYM.CScopeRedirect = function(cscope, argName)
{
while ( cscope[argName] && cscope[argName].redirect )
{
argName = cscope[argName].redirect;
}
return argName;
}
// Despite the name, this just constructs a block type.
// The block itself doesn't get compiled yet.
SWYM.CompileLambda = function(braceNode, argNode, cscope, executable)
{
var bodyText = braceNode.op.source.substring( braceNode.op.pos, braceNode.op.endSourcePos+1 );
var debugName = bodyText;
var argText = undefined;
if( argNode !== undefined && argNode.op !== undefined && argNode.op.endSourcePos !== undefined )
{
argText = SWYM.GetSource(argNode.op.pos, argNode.op.endSourcePos+1);
if( argNode !== undefined && argNode.op !== undefined && argNode.op.text === "(" && argNode.children[0] === undefined &&
argNode.children[1] !== undefined )
{
// argument is parenthesized
argNode = argNode.children[1];
}
}
if( argNode !== undefined && argNode.type === "decl" )
{
if( argText === undefined )
{
argText = argNode.text;
}
var argName = argNode.value;
}
else
{
if( argText === undefined )
{
argText = "'it'";
}
var argName = "it";
}
if( argText !== undefined )
{
debugName = argText + "->" + debugName;
}
// The closureInfo object gets embedded into the executable. It contains the data used at
// runtime to construct the actual closure/variable capture value.
// (capturedNames lists the local/global variables that are captured from its scope
// at creation time. It's currently empty - we'll populate this list if & when the closure
// actually gets compiled. Typically when we find a call site. The executable will remain
// undefined unless we find an ambiguous call site - i.e. one where we don't know at compile
// time which block it's actually calling.)
var closureInfo = { type:"closureInfo", capturedNames:[], executable:undefined, debugName:debugName, debugText:bodyText, argName:argName };
executable.push("#Closure");
executable.push(closureInfo);
var blockType = {type:"type",
debugName:"Block("+debugName+")",
nativeType:"Closure", // TODO: should be "Block", it won't become a closure unless we call it ambiguously
bodyText:bodyText,
bodyNode:braceNode.children[1],
needsCompiling: [{
debugName:debugName,
argNode:argNode,
argName:argName,
bodyNode:braceNode.children[1],
cscope:cscope,
closureInfo:closureInfo,
}],
};
return blockType;
}
// This generates an executable from a block type. The block type's closureInfo
// will be modified if appropriate.
SWYM.CompileLambdaInternal = function(closureType, argType, executable, errorNode)
{
if( !closureType.needsCompiling )
{
return SWYM.GetOutType(closureType, argType, errorNode);
}
if( closureType.needsCompiling.length == 1 )
{
return SWYM.CompileLambdaInternalEntry( closureType.needsCompiling[0], argType, executable, errorNode );
}
else
{
var returnType = undefined;
for(var Idx = 0; Idx < closureType.needsCompiling.length; ++Idx )
{
returnType = SWYM.TypeUnify(returnType, SWYM.CompileLambdaInternalEntry( closureType.needsCompiling[Idx], argType, undefined, errorNode ));
}
return returnType;
}
}
SWYM.CompileLambdaInternalEntry = function(compileInfo, argType, executable, errorNode)
{
var argNode = compileInfo.argNode;
var cscope = compileInfo.cscope;
var node = compileInfo.bodyNode;
var closureInfo = compileInfo.closureInfo;
if( executable === undefined )
{
if( closureInfo.executable !== undefined )
{
SWYM.LogError(errorNode, "Tried to recompile a block!");
}
// if we're not compiling a one-off executable, store it in the closureInfo.
executable = [];
closureInfo.executable = executable;
}
var innerCScope;
if( argNode !== undefined && SWYM.TypeMatches(SWYM.VoidType, argType) )
{
SWYM.LogError(errorNode, "This block requires a non-Void argument.");
}
var innerCScope = object(cscope);
var argName;
// argument is parenthesized
if( argNode !== undefined && argNode.op !== undefined && argNode.op.text === "(" && argNode.children[0] === undefined &&
argNode.children[1] !== undefined )
{
argNode = argNode.children[1];
}
if( argNode !== undefined )
{
var requiredArgType = SWYM.GetTypeFromPatternNode( argNode, cscope );
SWYM.TypeCoerce(requiredArgType, argType, argNode? argNode: node, "Block parameter type");
}
if( argNode !== undefined && argNode.type === "decl" )
{
argName = argNode.value;
innerCScope["__default"] = SWYM.VoidType;
innerCScope["it"] = SWYM.VoidType;
}
else if ( argNode !== undefined && argNode.op !== undefined && (argNode.op.text === "[" || argNode.op.text === "{") )
{
// destructuring an array/table arg
argName = "it";
innerCScope["__default"] = {redirect:"it"};
executable.push("#Load");
executable.push("it");
SWYM.CompileDeconstructor(argNode, cscope, executable, argType);
executable.push("#Pop");
}
else
{
argName = "it";
innerCScope["__default"] = {redirect:"it"};
if( argNode !== undefined && requiredArgType === undefined )
{
SWYM.LogError(argNode, "Don't understand argument name "+argNode);
return {type:"novalues"};
}
}
executable.unshift(argName);
executable.unshift("#ArgStore");
innerCScope[argName] = argType;
var returnType = SWYM.CompileNode(node, innerCScope, executable);
// a simple optimization for small executables: strip off the #ArgStore instruction if it's easy to do so.
if( executable.length < 10 && executable[0] === "#ArgStore" )
{
var numArgLoads = 0;
for( var Idx = executable.length-1; Idx >= 2; --Idx )
{
if( executable[Idx] === "#Load" && executable[Idx+1] === argName )
{
numArgLoads++;
}
else if( SWYM.ScopeAccessorInstructions[executable[Idx]] === true )
{
numArgLoads = -1;
break;
}
}
if( numArgLoads === 0 )
{
// this executable doesn't care about the argument passed in at all
executable.shift();
executable[0] = "#ClearStack";
}
else if( numArgLoads === 1 && executable[2] === "#Load" && executable[3] === argName )
{
// this executable loads its argument exactly once, at the beginning
// so we might as well omit the #ArgStore and #Load instructions.
executable.shift();
executable.shift();
executable.shift();
executable.shift();
}
}
// TODO: closureInfo should be populated with the executable, and the
// list of variables that were captured from the containing scope.
return returnType;
/* executableBlock.argName = argName;
executableBlock.body = bodyExecutable;
//TODO: Blocks will probably want more members, such as "parseTree".
var closureType = {type:"type",
debugName:"Closure("+debugName+")",
nativeType:"closure",
argNode:argNode,
bodyNode:braceNode.children[1],
cscope:cscope,
needsCompiling:true,
closureInfo:closureInfo,
};
return closureType;
return {type:"type",
nativeType:"closure" argType:argType, outType:returnType, baked:executableBlock};
*/
}
SWYM.CompileClosureCall = function(argType, argExecutable, closureType, closureExecutable, cscope, executable, errorNode)
{
if( !closureType.needsCompiling )
{
SWYM.pushEach(argExecutable, executable);
SWYM.pushEach(closureExecutable, executable);
executable.push("#ClosureCall");
return SWYM.GetOutType(closureType, argType, errorNode);
}
var numArgReferences = 0;
var numClosureReferences = 0;
var cannotInline = false;
var bodyExecutable = [];
var returnType = SWYM.CompileLambdaInternal(closureType, argType, bodyExecutable);
if( closureType && closureType.baked && closureType.baked.body )
{
var closureBody = closureType.baked.body;
// see if we can cheaply and safely inline this
for( var Idx = 0; Idx < closureBody.length; ++Idx )
{
if( closureBody[Idx] === "#Load")
{
if( closureBody[Idx+1] === closureType.baked.argName )
{
++numArgReferences;
}
else
{
++numClosureReferences;
}
}
else if( SWYM.ScopeAccessorInstructions[closureBody[Idx]] === true )
{
cannotInline = true;
}
}
}
else
{
cannotInline = true;
}
if( cannotInline || numArgReferences > 1 || numClosureReferences > 1 )
{
// not suitable for inlining
/*if( numClosureReferences === 0 )
{
SWYM.pushEach(argExecutable, executable);
executable.push("#FnCall");
executable.push("DO:"+SWYM.TypeToString(closureType));
executable.push([closureType.argName]);
executable.push(bodyExecutable);
}
else
{*/
SWYM.pushEach(argExecutable, executable);
SWYM.pushEach(closureExecutable, executable);
executable.push("#ClosureExec");
executable.push(bodyExecutable);
//}
}
else
{
// insert the args into the executable body
for( var Idx = 0; Idx < closureBody.length; ++Idx )
{
if( closureBody[Idx] === "#Load")
{
if( closureBody[Idx+1] === closureType.baked.argName )
{
SWYM.pushEach(argExecutable, executable);
++Idx;
}
else
{
SWYM.pushEach(closureExecutable, executable);
executable.push("#LoadFromClosure");
executable.push(closureBody[Idx+1]);
++Idx;
}
}
else
{
executable.push(closureBody[Idx]);
}
}
}
return returnType;
}
SWYM.ScopeAccessorInstructions = {
"#Store":true,
"#Overwrite":true,
"#Closure":true,
"#Etc":true,
"#EtcSequence":true,
"#Return":true,
"#IfElse":true,
};
// Compile deconstructing expressions such as ['x','y']->{ x+y }
// (well, only the part in square brackets.)
SWYM.CompileDeconstructor = function(templateNode, cscope, executable, argType)
{
if( templateNode.type === "decl" )
{
executable.push("#Store");
executable.push(templateNode.value);
cscope[templateNode.value] = argType;
}
else if( templateNode.op && templateNode.op.text === ":" && templateNode.children[1] && templateNode.children[1].type === "decl" )
{
executable.push("#Store");
executable.push(templateNode.children[1].value);
//TODO: handle this explicit type declaration
cscope[templateNode.children[1].value] = argType;
}
else if( templateNode.op && templateNode.op.text === "[" )
{
var opName = undefined;
if( templateNode.children[1] && templateNode.children[1].op )
{
opName = templateNode.children[1].op.text;
}
if( opName === ".." )
{
// [first..last] range array deconstructor
SWYM.CompileRangeDeconstructor(templateNode.children[1], false, false, cscope, executable, argType);
}
else if( opName === "..<" )
{
// [first..<exclude] range array deconstructor
SWYM.CompileRangeDeconstructor(templateNode.children[1], false, true, cscope, executable, argType);
}
else if( opName === "<.." )
{
// [exclude<..last] range array deconstructor
SWYM.CompileRangeDeconstructor(templateNode.children[1], true, false, cscope, executable, argType);
}
else if( opName === "<..<" )
{
// [exclude<..<exclude] range array deconstructor
SWYM.CompileRangeDeconstructor(templateNode.children[1], true, true, cscope, executable, argType);
}
else
{
SWYM.CompileArrayDeconstructor(templateNode.children[1], 0, cscope, executable, argType);
}
}
else if( templateNode.op && templateNode.op.text === "{" )
{
SWYM.CompileTableDeconstructor(templateNode.children[1], cscope, executable, argType);
}
else
{
//SWYM.LogError(templateNode, "Unexpected structure in argument declaration")
}
}
SWYM.CompileRangeDeconstructor = function(rangeNode, overlapStart, overlapEnd, cscope, executable, argType)
{
SWYM.TypeCoerce(SWYM.RangeArrayType, argType, rangeNode);
if( rangeNode.children[0] !== undefined && rangeNode.children[1] !== undefined )
{
executable.push("#Dup");
}
if( rangeNode.children[0] !== undefined )
{
if( rangeNode.children[0].type !== "decl" )
{
SWYM.LogError(templateNode, "Unexpected expression in range deconstructor")
return;
}
executable.push("#Native");
executable.push(1);
if( overlapStart )
executable.push(function(rangeArray){ return rangeArray.first-1; });
else
executable.push(function(rangeArray){ return rangeArray.first; });
executable.push("#Store");
executable.push(rangeNode.children[0].value);
executable.push("#Pop");
cscope[rangeNode.children[0].value] = SWYM.IntType;
}
if( rangeNode.children[1] !== undefined )
{
if( rangeNode.children[1].type !== "decl" )
{
SWYM.LogError(templateNode, "Unexpected expression in range deconstructor")
return;
}
executable.push("#Native");
executable.push(1);
if( overlapEnd )
executable.push(function(rangeArray){ return rangeArray.last+1; });
else
executable.push(function(rangeArray){ return rangeArray.last; });
executable.push("#Store");
executable.push(rangeNode.children[1].value);
executable.push("#Pop");
cscope[rangeNode.children[1].value] = SWYM.IntType;
}
}
SWYM.CompileArrayDeconstructor = function(templateNode, index, cscope, executable, argType)
{
if( templateNode.op && (templateNode.op.text === "," || templateNode.op.text === ";" || templateNode.op.text === "(blank_line)") )
{
index = SWYM.CompileArrayDeconstructor(templateNode.children[0], index, cscope, executable, argType);
index = SWYM.CompileArrayDeconstructor(templateNode.children[1], index, cscope, executable, argType);
return index;
}
else
{
executable.push("#Dup");
executable.push("#Literal");
executable.push(index);
executable.push("#At");
SWYM.CompileDeconstructor(templateNode, cscope, executable, SWYM.GetOutType(argType, SWYM.BakedValue(index)));
executable.push("#Pop");
return index+1;
}
}
SWYM.CompileTableDeconstructor = function(templateNode, cscope, executable, argType)
{
if( templateNode.op && (templateNode.op.text === "," || templateNode.op.text === ";" || templateNode.op.text === "(blank_line)") )
{
SWYM.CompileTableDeconstructor(templateNode.children[0], cscope, executable, argType);
SWYM.CompileTableDeconstructor(templateNode.children[1], cscope, executable, argType);
}
else if( templateNode.type === "decl" )
{
SWYM.CompileTableElementDeconstructor(SWYM.StringWrapper(templateNode.value), templateNode.value, cscope, executable, argType);
}
else if( templateNode.op && templateNode.op.text === ":" && templateNode.children[1] && templateNode.children[1].type === "decl" )
{
//TODO: handle this type declaration
SWYM.CompileTableElementDeconstructor(
SWYM.StringWrapper(templateNode.children[1].value),
templateNode.children[1].value,
cscope,
executable,
argType
);
}
else if( templateNode.op && templateNode.op.text === ":" )
{
//Explicit table key
var tempExecutable = [];
var keyType = SWYM.CompileNode(templateNode.children[0], cscope, tempExecutable);
if( !keyType || !keyType.baked )
{
SWYM.LogError(templateNode.children[0], "For destructuring arguments, table keys must be known at compile time.");
}
else
{
var rhs = templateNode.children[1];
if( rhs.op && rhs.op.text === ":" && rhs.children[1] && rhs.children[1].type === "decl" )
{
// TODO: handle type declaration
SWYM.CompileTableElementDeconstructor(keyType.baked, rhs.children[1].value, cscope, executable, argType);
}
else if( rhs.type === "decl" )
{
SWYM.CompileTableElementDeconstructor(keyType.baked, rhs.value, cscope, executable, argType);
}
else
{
SWYM.LogError(rhs, "Invalid table destructuring declaration.");
}
}
}
else
{
SWYM.LogError(templateNode, "Unexpected structure in argument table declaration")
}
}
SWYM.CompileTableElementDeconstructor = function(readName, writeName, cscope, executable, argType)
{
executable.push("#Dup");
executable.push("#Literal");
executable.push(readName);
executable.push("#At");
executable.push("#Store");
executable.push(writeName);
executable.push("#Pop");
// TODO: get the specific type from the table, if available?
cscope[writeName] = SWYM.GetOutType(argType);
}
SWYM.AddKeyAndValue = function(keyNode, valueNode, keyNodes, valueNodes)
{
if( keyNode && keyNode.op && keyNode.op.text === ":" )
{
SWYM.AddKeyAndValue(keyNode.children[0], valueNode, keyNodes, valueNodes);
SWYM.AddKeyAndValue(keyNode.children[1], valueNode, keyNodes, valueNodes);
}
else
{
keyNodes.push(keyNode);
valueNodes.push(valueNode);
}
}
SWYM.CollectKeysAndValues = function(node, keyNodes, valueNodes)
{
if( node && node.op && (node.op.text === "," || node.op.text === ";" || node.op.text === "(blank_line)") )
{
SWYM.CollectKeysAndValues(node.children[0], keyNodes, valueNodes);
SWYM.CollectKeysAndValues(node.children[1], keyNodes, valueNodes);
}
else if( node && node.op && node.op.text === ":" )
{
SWYM.AddKeyAndValue(node.children[0], node.children[1], keyNodes, valueNodes);
}
else if( node && node.op && node.op.text === "->" )
{
if( node.children[0] && node.children[0].type === "decl" )
{
SWYM.AddKeyAndValue(node.children[0], node.children[1], keyNodes, valueNodes);
}
else
{
var newDecl = SWYM.NewToken("decl", node.children[0].pos, "'it'", "it");
newDecl.children[0] = node.children[0];
SWYM.AddKeyAndValue(newDecl, node.children[1], keyNodes, valueNodes);
}
}
else if( node && node.type === "decl" )
{
SWYM.AddKeyAndValue(node, undefined, keyNodes, valueNodes);
}
else if( node )
{
SWYM.LogError(node, "Error: each element of a table must be a 'key:value' declaration.");
}
}
SWYM.AddClassMember = function(bodyNode, nameNodes, defaultValueNodes)
{
if( bodyNode && bodyNode.op && bodyNode.op.text === "=" )
{
nameNodes.push(bodyNode.children[0]);
defaultValueNodes.push(bodyNode.children[1]);
}
else
{
nameNodes.push(bodyNode);
defaultValueNodes.push(undefined);
}
}
SWYM.CollectClassMembers = function(node, nameNodes, defaultValueNodes)
{
if( node && node.op && (node.op.text === "," || node.op.text === ";" || node.op.text === "(blank_line)") )
{
SWYM.CollectClassMembers(node.children[0], nameNodes, defaultValueNodes);
SWYM.CollectClassMembers(node.children[1], nameNodes, defaultValueNodes);
}
else if( node && (node.type === "decl" || (node.op && node.op.text === "=")) )
{
SWYM.AddClassMember(node, nameNodes, defaultValueNodes);
}
else if( node )
{
SWYM.LogError(node, "Error: Expected a member declaration, got "+node.text+".");
}
}
SWYM.ConvertSeparatorsToCommas = function(node)
{
if( node && node.op && (node.op.text === ";" || node.op.text === "(blank_line)") )
{
node.op.behaviour = SWYM.operators[","];
node.op.text = ",";
node.children = [
SWYM.ConvertSeparatorsToCommas(node.children[0]),
SWYM.ConvertSeparatorsToCommas(node.children[1])
]
}
return node;
}
SWYM.CompileTable = function(node, cscope, executable)
{
if( node.type === "etc" )
{
return SWYM.CompileEtc(node, cscope, executable);
}
var keyNodes = [];
var valueNodes = [];
SWYM.CollectKeysAndValues(node, keyNodes, valueNodes);
if( keyNodes.length != valueNodes.length )
{
SWYM.LogError(node, "Fsckup: inconsistent numbers of keys and values in table!?");
}
var elementExecutable = [];
var bakedKeys = [];
var bakedValues = [];
var accessors = {};
var commonKeyType = SWYM.DontCareType;
var commonValueType = SWYM.DontCareType;
var elseExecutable = undefined;
var elseValue = undefined;
var keysExecutable = [];
var numKeys = 0;
for( var idx = 0; idx < keyNodes.length; ++idx )
{
if( keyNodes[idx] && keyNodes[idx].type === "name" && keyNodes[idx].text === "else" )
{
if( elseExecutable === undefined )
{
elseExecutable = [];
var elseType = SWYM.CompileNode(valueNodes[idx], cscope, elseExecutable);
commonValueType = SWYM.TypeUnify(commonValueType, elseType);
if( elseType && elseType.baked !== undefined )
{
elseValue = elseType.baked;
commonValueType = SWYM.TypeUnify(commonValueType, elseType);
}
else
{
bakedValues = undefined;
}
continue;
}
else
{
SWYM.LogError(keyNodes[idx], "Table has too many 'else' clauses.");
continue;
}
}
var valueType;
if( keyNodes[idx] && keyNodes[idx].type === "decl" )
{
var elementCScope = cscope;
if( keyNodes[idx].value !== "it" )
{
var elementCScope = object(cscope);
elementCScope[keyNodes[idx].value] = {redirect:"it"};
}
valueType = SWYM.CompileNode(valueNodes[idx], elementCScope, elementExecutable);
}
else
{
valueType = SWYM.CompileNode(valueNodes[idx], cscope, elementExecutable);
}
commonValueType = SWYM.TypeUnify(commonValueType, valueType);
if( bakedValues )
{
if( valueType && valueType.baked !== undefined )
{
bakedValues[idx] = valueType.baked;
}
else
{
bakedValues = undefined;
}
}
// handle single quoted 'declaration' keys
if( keyNodes[idx] && keyNodes[idx].type === "decl" )
{
if( accessors[keyNodes[idx].value] !== undefined )
{
SWYM.LogError(keyNodes[idx], "Duplicate declaration of "+keyNodes[idx].text+" in this table.");
}
else
{
accessors[keyNodes[idx].value] = valueType;
}
// replace the declaration with a string literal
keyNodes[idx] = SWYM.NewToken("literal", keyNodes[idx].pos, keyNodes[idx].text, keyNodes[idx].value);
}
var keyType = SWYM.CompileNode(keyNodes[idx], cscope, keysExecutable);
commonKeyType = SWYM.TypeUnify(commonKeyType, keyType);
numKeys++;
if( bakedKeys === undefined || keyType === undefined || keyType.baked === undefined )
{
//SWYM.LogError(keyNodes[idx], "Table keys must be compile-time constants (at the moment).");
bakedKeys = undefined;
}
else
{
bakedKeys[idx] = keyType.baked;
}
if( valueType && valueType.multivalueOf !== undefined )
SWYM.LogError(valueNodes[idx], "Error: json-style object declarations may not contain multi-values.");
}
if( bakedKeys === undefined )
{
SWYM.pushEach(keysExecutable, executable);
executable.push("#CreateArray");
executable.push(numKeys);
SWYM.pushEach(elementExecutable, executable);
executable.push("#CreateArray");
executable.push(numKeys);
if( elseExecutable !== undefined )
{
executable.push("#Closure");
executable.push({"type":"closureInfo", "argName":"it", "debugName":"else:"});
executable.push("#Native");
executable.push(3);
executable.push(function(keys, values, elseClosure)
{
var contents = {};
// NB inserting the values in reverse order, so that the first listed items take precedence
for(var Idx = numKeys-1; Idx >= 0; --Idx )
{
contents[ SWYM.ToTerseString(keys[Idx]) ] = values[Idx];
}
return {type:"table",
run:function(key)
{
var result = this.jsTable[ SWYM.ToTerseString(key) ];
if( result !== undefined )
{
return result;
}
else
{
return SWYM.ClosureExec(elseClosure, key, elseExecutable);
}
},
keys:keys,
jsTable:contents,
}
});
}
else
{
executable.push("#Native");
executable.push(2);
executable.push(function(keys, values)
{
var contents = {};
// NB inserting the values in reverse order, so that the first listed items take precedence
for(var Idx = numKeys-1; Idx >= 0; --Idx )
{
contents[ SWYM.ToTerseString(keys[Idx]) ] = values[Idx];
}
return {type:"table",
run:function(key)
{
var result = this.jsTable[ SWYM.ToTerseString(key) ];
if( result !== undefined )
return result;
SWYM.LogError(-1, "Invalid table lookup. No key equals "+SWYM.ToDebugString(key));
},
keys:keys,
jsTable:contents,
}
});
}
var resultType = SWYM.TableTypeFromTo(commonKeyType, commonValueType, accessors);
}
else
{
bakedKeys = SWYM.jsArray(bakedKeys);
if( SWYM.TypeMatches(SWYM.StringType, commonKeyType) )
{
var constructor = function(values, els)
{
var table = {};
for( var Idx = 0; Idx < values.length; ++Idx )
{
table[ SWYM.ToTerseString(bakedKeys[Idx]) ] = values.run(Idx);
}
return {type:"table",
run:function(key)
{
var result = this.jsTable[ SWYM.ToTerseString(key) ];
if( result !== undefined )
return result;
if( this.elseValue !== undefined )
return this.elseValue;
else
SWYM.LogError(-1, "Invalid table lookup. No key equals "+SWYM.ToDebugString(key));
},
keys:bakedKeys,
jsTable:table,
elseValue:els
}
};
}
else if( SWYM.TypeMatches(SWYM.NumberType, commonKeyType) )
{
var constructor = function(values, els)
{
var table = {};
for( var Idx = 0; Idx < values.length; ++Idx )
{
table[bakedKeys[Idx]] = values.run(Idx);
}
return {type:"table",
run:function(key)
{
var result = this.jsTable[key];
if( result !== undefined )
return result;
if( this.elseValue !== undefined )
return this.elseValue;
else
SWYM.LogError(-1, "Invalid table lookup. No key equals "+SWYM.ToDebugString(key));
},
keys:bakedKeys,
jsTable:table,
elseValue:els
}
};
}
else
{
var constructor = function(values, els)
{
return {type:"table",
run:function(key)
{
// not very happy with this. Use a hash.
for( var idx = 0; idx < this.keys.length; ++idx )
{
if( SWYM.IsEqual(this.keys.run(idx), key) )
{
return this.values[idx];
}
}
// FIXME: should give an out of bounds error
if( this.elseValue !== undefined )
return this.elseValue;
else
SWYM.LogError(-1, "Invalid table lookup. No key equals "+SWYM.ToDebugString(key));
},
keys:bakedKeys,
values:values,
elseValue:els
}
};
}
if ( bakedValues !== undefined && (elseType === undefined || elseType.baked !== undefined) )
{
var bakedTable = constructor(SWYM.jsArray(bakedValues), elseValue);
executable.push("#Literal");
executable.push(bakedTable);
var resultType;
if( elseValue !== undefined )
resultType = SWYM.TableTypeFromTo(SWYM.AnythingType, commonValueType, accessors);
else
resultType = SWYM.TableTypeFromTo(commonKeyType, commonValueType, accessors);
resultType.baked = bakedTable;
}
else if ( elseExecutable !== undefined )
{
SWYM.pushEach(elseExecutable, executable);
SWYM.pushEach(elementExecutable, executable);
executable.push("#Native");
executable.push(bakedKeys.length+1);
executable.push(function()
{
var args = Array.prototype.slice.call(arguments);
var elseValue = args[0];
args.shift();
return constructor(SWYM.jsArray(args), elseValue);
});
// TODO: What if the else executable doesn't actually accept AnythingType?
// Defer compilation of the else executable so that we know what argument type is being passed?
var resultType = SWYM.TableTypeFromTo(SWYM.AnythingType, commonValueType, accessors);
}
else
{
SWYM.pushEach(elementExecutable, executable);
executable.push("#Native");
executable.push(bakedKeys.length);
executable.push(function()
{
var args = Array.prototype.slice.call(arguments);
return constructor(SWYM.jsArray(args), undefined);
});
var resultType = SWYM.TableTypeFromTo(commonKeyType, commonValueType, accessors);
}
}
return resultType;
}
SWYM.CompileClassBody = function(node, cscope, defaultValueTable)
{
var nameNodes = [];
var defaultValueNodes = [];
SWYM.CollectClassMembers(node, nameNodes, defaultValueNodes);
if( nameNodes.length !== defaultValueNodes.length )
{
SWYM.LogError(node, "Fsckup: inconsistent number of names and values in struct!?");
}
var memberTypes = {};
var unusedExecutable = [];
for( var idx = 0; idx < nameNodes.length; ++idx )
{
if( !nameNodes[idx] )
{
SWYM.LogError(node, "Invalid member declaration - missing name");
}
else if( nameNodes[idx].type !== "decl" )
{
SWYM.LogError(node, "Invalid member declaration "+nameNodes[idx]);
}
else
{
var memberName = nameNodes[idx].value;
defaultValueTable[memberName] = defaultValueNodes[idx];
var typeNode = nameNodes[idx].children === undefined? undefined: nameNodes[idx].children[0];
if( typeNode === undefined )
{
memberTypes[memberName] = undefined;
}
else
{
var typeType = SWYM.CompileNode(typeNode, cscope, unusedExecutable);
if( !typeType || !typeType.baked || typeType.baked.type !== "type" )
{
SWYM.LogError(typeNode, "Type declaration for "+memberName+" is not a valid type!");
}
else
{
memberTypes[memberName] = typeType.baked;
}
}
}
}
return memberTypes;
}
SWYM.CompileEtc = function(parsetree, cscope, executable)
{
var isTable = ( parsetree.baseCase.op && parsetree.baseCase.op.text === ":" );
/* if( parsetree.body.etcExpandAround !== undefined )
{
//To do an ExpandAround, the body must be parsed into a function that
//we can apply multiple times.
var baseType = SWYM.CompileNode(parsetree.body.children[parsetree.body.etcExpandAround], cscope, etcExecutable);
var baseNode = parsetree.body.children[parsetree.body.etcExpandAround];
parsetree.body.children[parsetree.body.etcExpandAround] = SWYM.NewToken("name", baseNode.pos, "<prevEtc>");
var stepCScope = object(cscope);
stepCScope["<prevEtc>"] = baseType;
stepExecutable = [];
var bodyType = SWYM.CompileNode(parsetree.body, stepCScope, stepExecutable);
}
else if( parsetree.body.op && parsetree.body.op.text === ":" )
{
var keyType = SWYM.CompileNode(parsetree.body.children[0], cscope, etcExecutable);
var valueType = SWYM.CompileNode(parsetree.body.children[1], cscope, etcExecutable);
bodyType = SWYM.TableTypeFromTo(keyType, valueType);
etcExecutable.push("#CreateArray");
etcExecutable.push(2);
}
else*/
// {
// }
var haltExecutable = [];
var haltCondition = undefined;
var limitTimesExecutable = undefined;
cscope["__n"] = SWYM.IntType;
if( parsetree.etcType === "etc**" )
{
limitTimesExecutable = [];
var limitType = SWYM.CompileNode(parsetree.rhs, cscope, limitTimesExecutable);
SWYM.TypeCoerce(SWYM.IntType, limitType, parsetree, "etc** number of times");
}
else if( parsetree.rhs )
{
SWYM.CompileNode(parsetree.rhs, cscope, haltExecutable);
if( parsetree.etcType === "etc..<" )
{
haltCondition = function(value, haltValue){ return value >= haltValue; }
}
else if( parsetree.etcType === "etc..<=" )
{
haltCondition = function(value, haltValue){ return value > haltValue; }
}
else if( parsetree.etcType !== "etc" )
{
haltCondition = function(value, haltValue){ if( SWYM.IsEqual(value, haltValue) ){ SWYM.g_etcState.halt = true; } return false; }
}
else
{
SWYM.LogError(parsetree, "Fsckup: can't have a rhs on a "+parsetree.op.etc);
}
}
var baseCaseExecutable = [];
var baseCaseType;
if( isTable )
{
var baseKeyType = SWYM.CompileNode(parsetree.baseCase.children[0], cscope, baseCaseExecutable);
var baseValueType = SWYM.CompileNode(parsetree.baseCase.children[1], cscope, baseCaseExecutable);
baseCaseExecutable.push("#CreateArray");
baseCaseExecutable.push(2);
baseCaseType = SWYM.TableTypeFromTo(baseKeyType, baseValueType);
}
else
{
baseCaseType = SWYM.CompileNode(parsetree.baseCase, cscope, baseCaseExecutable);
}
var etcScope = object(cscope);
etcScope["<etcSoFar>"] = baseCaseType;
if( parsetree.body.type === "fnnode" )
{
// Handle recursive function calls. This includes function-operators, such as + - * /
var etcExecutable = [];
var bodyType = SWYM.CompileNode(parsetree.body, etcScope, etcExecutable);
if( parsetree.mergedBaseCase )
{
// there's no base case provided, so we'll use the function's identity value instead.
baseCaseExecutable = ["#Literal", SWYM.GetFunctionIdentity(parsetree.body, etcScope)];
}
// main executable needs to initialize etcSoFar.
SWYM.pushEach(baseCaseExecutable, executable);
executable.push("#Store");
executable.push("<etcSoFar>");
executable.push("#Pop");
executable.push("#EtcSimple");
executable.push(limitTimesExecutable);
executable.push(etcExecutable);
executable.push(haltExecutable);
executable.push(haltCondition);
executable.push("#Load");
executable.push("<etcSoFar>");
// etcExecutable needs to write its result to <etcSoFar>.
// etcExecutable.push("#Overwrite");
// etcExecutable.push("<etcSoFar>");
return bodyType;
}
// it's not a function, so body must be an operator node
var initialExecutable;
var initialType;
if( parsetree.mergedBaseCase )
{
initialExecutable = ["#Native", 0, parsetree.body.op.behaviour.identity];
}
else
{
initialExecutable = baseCaseExecutable;
initialType = baseCaseType;
}
var composer = parsetree.body.op.behaviour.infix;
var postProcessor = function(v){return v;}; // null postprocessor
var returnType = parsetree.body.op.behaviour.returnType;
var etcExecutable = [];
if( isTable )
{
// table constructor
var keyType = SWYM.CompileNode(parsetree.body.children[1].children[0], etcScope, etcExecutable);
var valueType = SWYM.CompileNode(parsetree.body.children[1].children[1], etcScope, etcExecutable);
etcExecutable.push("#CreateArray");
etcExecutable.push(2);
//FIXME: don't bother doing ToDebugString on string values?
// also, it might be nice to make this work lazily?
initialExecutable = ["#Native", 0, function(){ return {values:[], keys:[]}; }];
composer = function(fields, pair)
{
// O(N-squared) algorithm for excluding duplicates. :-/ Change to hashtable?
var foundIdx = -1;
for(var Idx = 0; Idx < fields.keys.length; ++Idx)
{
if( SWYM.IsEqual(fields.keys[Idx], pair[0]) )
{
foundIdx = Idx;
break;
}
}
if( foundIdx === -1 )
{
fields.keys.push(pair[0]);
fields.values.push(pair[1]);
}
else
{
fields.values[foundIdx] = pair[1];
}
return fields;
};
postProcessor = function(fields){ return SWYM.CreateTable(fields.keys, fields.values); };
returnType = SWYM.TableTypeFromTo(SWYM.TypeUnify(baseKeyType, keyType), SWYM.TypeUnify(baseValueType, valueType));
}
else
{
var elementType;
if (parsetree.body.children[1] !== undefined)
{
elementType = SWYM.CompileNode(parsetree.body.children[1], etcScope, etcExecutable);
}
else
{
etcExecutable = initialExecutable;
elementType = initialType;
}
if( returnType === undefined && parsetree.body.op.behaviour.getReturnType )
{
returnType = parsetree.body.op.behaviour.getReturnType(bodyType, bodyType);
}
// most interesting operators are overloadable (i.e. treated as fnnodes), so there are
// only a few things we need to handle here. Namely: , && ||
switch(parsetree.body.op.text)
{
case ",": case ";": case "(blank_line)":
if( initialType !== undefined )
{
initialExecutable.push( ( initialType.multivalueOf !== undefined )? "#CopyArray": "#SingletonArray");
}
if( limitTimesExecutable === undefined &&
haltCondition === undefined &&
(parsetree.etcType === "etc.." || parsetree.etcType === "etc," || parsetree.etcType === "etc;") )
{
// lazy array constructor
// FIXME: need a way to detect out-of-bounds termination
executable.push("#Closure");
executable.push({
type:"closureInfo",
debugName:"lazy etc",
debugText:"<etc expression>", //FIXME
argName: "__n",
body:etcExecutable // This won't actually work
});
executable.push("#Native");
executable.push(1)
executable.push(function(lookup)
{
return{
type:"lazyArray",
length:Infinity,
run:function(key){ return SWYM.ClosureCall(lookup,key); }
};
});
return SWYM.ToMultivalueType(elementType, undefined, undefined, SWYM.BakedValue(Infinity));
}
else
{
// eager array constructor
postProcessor = function(list){ return SWYM.jsArray(list); };
if( elementType && elementType.multivalueOf !== undefined )
{
composer = function(list, v){ SWYM.ConcatInPlace( list, v ); return list; };
returnType = SWYM.ToMultivalueType(elementType.multivalueOf);
}
else
{
composer = function(list, v){list.push(v); return list;};
returnType = SWYM.ToMultivalueType(elementType);
}
}
break;
case "&&":
SWYM.TypeCoerce(SWYM.BoolType, elementType, parsetree, "&&etc arguments");
if( elementType && elementType.multivalueOf !== undefined )
{
composer = function(tru, v)
{
var result = SWYM.ResolveBool_Every(v);
if(!result){ SWYM.g_etcState.halt = true; };
return result;
};
}
else
{
composer = function(tru, v)
{
if(!v){ SWYM.g_etcState.halt = true; };
return v;
};
}
break;
case "||":
SWYM.TypeCoerce(SWYM.BoolType, elementType, parsetree, "||etc arguments");
if( elementType && elementType.multivalueOf !== undefined )
{
composer = function(fals, v)
{
var result = SWYM.ResolveBool_Some(v);
if(result){ SWYM.g_etcState.halt = true; };
return result;
};
}
else
{
composer = function(fals, result)
{
if(result){ SWYM.g_etcState.halt = true; };
return result;
};
}
break;
default:
SWYM.LogError(parsetree, "Operator "+parsetree.body.op.text+" cannot be used in etc expressions!");
return SWYM.DontCareType;
}
}
executable.push("#Etc");
executable.push(limitTimesExecutable);
executable.push(etcExecutable);
executable.push(initialExecutable);
executable.push(composer);
executable.push(postProcessor);
executable.push(haltExecutable);
executable.push(haltCondition);
return returnType;
}
SWYM.GetFunctionIdentity = function(parsetree, cscope)
{
var unusedExecutable = [];
var chosenFunction = { __identity:true };
SWYM.CompileFunctionCall(parsetree, cscope, unusedExecutable, chosenFunction);
if( chosenFunction.theFunction === undefined )
{
SWYM.LogError(parsetree, "Function "+parsetree.name+" is unknown!");
}
else
{
var identityArg = chosenFunction.theFunction.expectedArgs["__identity"];
if( identityArg === undefined || identityArg.defaultValueNode === undefined )
{
SWYM.LogError(parsetree, "Function "+parsetree.name+" can't be used in etc expressions because it has no default argument named __identity.");
}
else
{
var identityType = SWYM.CompileNode(identityArg.defaultValueNode, cscope, []);
if( !identityType || identityType.baked === undefined )
{
SWYM.LogError(identityArg.defaultValueNode, "Function "+parsetree.name+" __identity argument has no default value");
}
else
{
return identityType.baked;
}
}
}
return undefined;
}
SWYM.pushEach = function(from, into)
{
if( into === undefined || from === undefined )
{
SWYM.LogError(0, "Fsckup: invalid pushEach");
return;
}
for( var Idx = 0; Idx < from.length; Idx++ )
{
into.push( from[Idx] );
}
}
SWYM.pushFrontEach = function(from, into)
{
for( var Idx = from.length-1; Idx >= 0; Idx-- )
{
into.unshift( from[Idx] );
}
}
SWYM.Each = function(list, body)
{
var result = [];
for( var Idx = 0; Idx < list.length; Idx++ )
{
result.push( body(list[Idx]) );
}
return result;
}
SWYM.selectiveClone = function(basis, members)
{
var result = {};
SWYM.selectiveCopy(basis, result, members);
return result;
}
SWYM.selectiveCopy = function(from, into, members)
{
for( var Idx = 0; Idx < members.length; Idx++ )
{
var member = members[Idx];
if( from[member] !== undefined )
into[member] = from[member];
}
}
SWYM.DeclareAccessor = function(memberName, classType, memberType, cscope)
{
SWYM.AddFunctionDeclaration("fn#"+memberName, cscope,
{
expectedArgs:{"this":{index:0, typeCheck:classType}},
customCompile:function(argTypes, cscope, executable, errorNode)
{
if( argTypes[0] && argTypes[0].multivalueOf !== undefined )
{
executable.push("#MultiNative");
}
else
{
executable.push("#Native");
}
executable.push(1);
executable.push(function(obj){ return obj.members[memberName]; });
var structType;
if( argTypes[0] && argTypes[0].multivalueOf )
structType = argTypes[0].multivalueOf;
else
structType = argTypes[0];
if( structType && structType.memberTypes && structType.memberTypes[memberName] )
{
return structType.memberTypes[memberName];
}
else
{
return memberType;
}
},
});
}
SWYM.DeclareMutator = function(memberName, classType, memberType, cscope)
{
SWYM.AddFunctionDeclaration("fn#"+memberName, cscope,
{
expectedArgs:{"this":{index:0, typeCheck:classType}, "__mutator":{index:1, typeCheck:SWYM.CallableType}},
customCompile:function(argTypes, cscope, executable, errorNode)
{
var mutatorExecutable = [];
var mutatorType = SWYM.CompileLambdaInternal(SWYM.ToSinglevalueType(argTypes[1]), memberType, mutatorExecutable, errorNode);
if( !SWYM.TypeMatches(memberType, mutatorType) )
{
return "Cannot store values of type "+SWYM.TypeToString(mutatorType)+" in a variable of type "+SWYM.TypeToString(memberType);
}
executable.push("#Native");
executable.push(2);
executable.push
(
function(obj, mutator)
{
obj.members[memberName] = SWYM.ClosureExec( mutator, obj.members[memberName], mutatorExecutable );
}
);
return SWYM.VoidType;
},
});
}
SWYM.DeclareNew = function(newStruct, defaultNodes, declCScope)
{
var memberNames = [];
var functionData = {
expectedArgs:{"this":{index:0, typeCheck:SWYM.BakedValue(newStruct)}},
bakeOutArgs:{0:true},
nativeCode:function(/*...args...*/)
{
var members = {};
for( var Idx = 0; Idx < memberNames.length; ++Idx )
{
members[memberNames[Idx]] = arguments[Idx];
}
return {type:"struct", isMutable:newStruct.isMutable, debugName:SWYM.TypeToString(newStruct), structType:newStruct, members:members};
},
getReturnType:function(argTypes)
{
var memberTypes = {};
for( var Idx = 0; Idx < memberNames.length; ++Idx )
{
memberTypes[memberNames[Idx]] = argTypes[Idx+1];
}
var resultType = object(newStruct);
resultType.memberTypes = memberTypes;
return resultType;
}
};
var nextIndex = 0;
for( var memberName in newStruct.memberTypes )
{
functionData.expectedArgs[memberName] = {index:nextIndex+1, typeCheck:newStruct.memberTypes[memberName], defaultValueNode:defaultNodes[memberName], explicitNameRequired:true};
memberNames[nextIndex] = memberName;
nextIndex++;
}
SWYM.AddFunctionDeclaration("fn#new", declCScope, functionData);
}
SWYM.CreateLocal = function(declName, valueType, cscope, executable, errorNode)
{
// the initial value is assumed to be already on the stack
// if( valueType && valueType.multivalueOf !== undefined )
// {
// executable.push( "#ForceSingleValue" );
// valueType = SWYM.ToSinglevalueType(valueType);
// }
executable.push( "#Store" );
executable.push( declName );
if( cscope[declName] !== undefined )
{
SWYM.LogError(errorNode, "Redefinition of \""+declName+"\"");
}
else
{
cscope[declName] = valueType;
}
// If this just declared a type, name the type after this variable name
if( valueType && valueType.baked && valueType.baked.type === "type" && !valueType.baked.debugName )
{
valueType.baked.debugName = declName;
}
}
SWYM.CompileMutableTable = function(keyType, valueType, defaultClosureExecutable, defaultClosureType, executable, errorNode)
{
var defaultExecutable = [];
var defaultResultType = SWYM.CompileLambdaInternal(defaultClosureType, keyType, defaultExecutable, errorNode);
if(valueType === undefined)
valueType = defaultResultType;
else
SWYM.TypeCoerce(valueType, defaultResultType, errorNode);
SWYM.pushEach(defaultClosureExecutable, executable);
executable.push("#Native");
executable.push(1);
executable.push( function(defaultClosure)
{
return {
type:"table",
jsTable:{},
keys:SWYM.jsArray([]),
run:function(key)
{
var keyStr = SWYM.ToTerseString(key);
var result = this.jsTable[ keyStr ];
if( result === undefined )
{
result = SWYM.ClosureExec(defaultClosure, key, defaultExecutable);
this.jsTable[ keyStr ] = result;
this.keys.push(key);
}
return result;
}
}
} );
return SWYM.TableTypeFromTo(keyType, valueType, true);
}
SWYM.onLoad("swymCompile.js");