public static void Bind(BindingContext context, Function function) { if (function.Type.Return == null) throw new InvalidOperationException("Can only bind functions whose type is already bound."); var scope = new Scope(); // create a local slot for the arg if there is one if (function.Type.Parameter.Unbound != Decl.Unit) { scope.Define(context.NameGenerator.Generate(), Decl.Unit /* ignored */, false); } var binder = new FunctionBinder(function, context, scope); // bind the function function.Bind(context, binder); // make sure declared return type matches actual return type if (!DeclComparer.TypesMatch(function.Type.Return.Bound, function.Body.Bound.Type)) { if (function.Body.Bound.Type == Decl.EarlyReturn) { //### bob: should find the return expr throw new CompileException(function.Position, "Unneeded explicit \"return\"."); } else { throw new CompileException(function.Position, String.Format("{0} is declared to return {1} but is returning {2}.", function.Name, function.Type.Return.Bound, function.Body.Bound.Type)); } } }
public ICallable Instantiate(BindingContext context, IEnumerable <IBoundDecl> typeArgs, IBoundDecl argType) { // should have three args: an int, an array, and a value var argTuple = argType as BoundTupleType; if (argTuple == null) { return(null); } // index if (argTuple.Fields[0] != Decl.Int) { return(null); } // array var arrayType = argTuple.Fields[1] as BoundArrayType; if (arrayType == null) { return(null); } // value if (!DeclComparer.TypesMatch(argTuple.Fields[2], arrayType.ElementType)) { return(null); } // make the intrinsic return(new Intrinsic("__Call<-", OpCode.StoreArray, FuncType.Create(argTuple, Decl.Unit))); }
IBoundExpr IUnboundExprVisitor<IBoundExpr>.Visit(ArrayExpr expr) { var elementType = (IBoundDecl)null; if (expr.ElementType != null) { elementType = TypeBinder.Bind(mContext, expr.ElementType); } var elements = expr.Elements.Accept(this); // infer the type from the elements if (elementType == null) { var index = 0; foreach (var element in elements) { if (elementType == null) { // take the type of the first elementType = element.Type; } else { // make sure the others match if (!DeclComparer.TypesMatch(elementType, element.Type)) throw new CompileException(expr.Position, String.Format("Array elements must all be the same type. Array is type {0}, but element {1} is type {2}.", elementType, index, element.Type)); } index++; } } // build a structure for the array var fields = new List<IBoundExpr>(); fields.Add(new IntExpr(expr.Elements.Count)); fields.AddRange(elements); return new BoundTupleExpr(fields, new BoundArrayType(elementType)); }
public ICallable Instantiate(BindingContext context, IEnumerable <IBoundDecl> typeArgs, IBoundDecl argType) { bool canInfer; var genericContext = BuildContext(context, BaseType.Type.Parameter.Unbound, argType, ref typeArgs, out canInfer); // bail if we couldn't get the right number of type arguments if ((typeArgs == null) || (TypeParameters.Count != typeArgs.Count())) { return(null); } // create a new bound function type with the type arguments applied FuncType funcType = BaseType.Type.CloneFunc(); TypeBinder.Bind(genericContext, funcType); // make sure the concrete argument types of this instance match what we're actually given if (!DeclComparer.TypesMatch(funcType.Parameter.Bound, argType)) { return(null); } // create a new unbound function with the proper type Function instance = new Function(BaseType.Position, BaseType.BaseName, funcType, BaseType.ParamNames, BaseType.Body.Unbound, typeArgs, canInfer); instance.BindSearchSpace(BaseType.SearchSpace); // don't instantiate it multiple times // note that this must happen *before* the function is bound, in case the // newly instantiated generic function is recursive. context.Compiler.Functions.Add(instance); // bind it with the type arguments in context FunctionBinder.Bind(genericContext, instance); return(instance); }
private bool TryInferParam(IBoundDecl argType) { // see if the parameter type on top of the stack is a generic type NamedType named = ParamType as NamedType; if (named == null) { return(false); } // see if the named type is a generic type (instead of an actual concrete named type) int typeParamIndex = mTypeParamNames.IndexOf(named.Name); if (typeParamIndex == -1) { return(false); } // it is, so infer it from the arg if (mTypeArguments[typeParamIndex] == null) { // first time inferring the arg mTypeArguments[typeParamIndex] = argType; } else { // already inferred, make sure it matches if (!DeclComparer.TypesMatch(mTypeArguments[typeParamIndex], argType)) { // can't infer the same type parameter to multiple different types // example: Foo'A (a A, b A) // Foo (123, "string") mFailed = true; } } return(true); }
IBoundExpr IUnboundExprVisitor<IBoundExpr>.Visit(IfExpr expr) { var bound = new BoundIfExpr( expr.Condition.Accept(this), expr.ThenBody.Accept(this), (expr.ElseBody == null) ? null : expr.ElseBody.Accept(this)); if (bound.Condition.Type != Decl.Bool) { throw new CompileException(expr.Position, String.Format( "Condition of if/then/else is returning type {0} but should be Bool.", bound.Condition.Type)); } if (bound.ElseBody != null) { // has an else, so make sure both arms match if (!DeclComparer.TypesMatch(bound.ThenBody.Type, bound.ElseBody.Type)) { throw new CompileException(expr.Position, String.Format( "Branches of if/then/else do not return the same type. Then arm returns {0} while else arm returns {1}.", bound.ThenBody.Type, bound.ElseBody.Type)); } } else { // no else, so make the then body doesn't return a value if ((bound.ThenBody.Type != Decl.Unit) && (bound.ThenBody.Type != Decl.EarlyReturn)) { throw new CompileException(expr.Position, String.Format( "Body of if/then is returning type {0} but must be Unit (or a return) if there is no else branch.", bound.ThenBody.Type)); } } return bound; }
IBoundExpr IUnboundExprVisitor<IBoundExpr>.Visit(CallExpr expr) { var namedTarget = expr.Target as NameExpr; // see if it's a macro call before binding the arg if ((namedTarget != null) && (mContext.Compiler.MacroProcessor != null)) { IUnboundExpr macroResult = mContext.Compiler.MacroProcessor.Process(namedTarget.Name, expr.Arg); // if it was a macro call, bind the result of it if (macroResult != null) return macroResult.Accept(this); } //### bob: handle array constructors. hack! should be intrinsic if ((namedTarget != null) && (namedTarget.Name == "ArrayOf")) { // handle ArrayOf[Int] if ((expr.Arg is UnitExpr) && (namedTarget.TypeArgs.Count == 1)) { return new ArrayExpr(namedTarget.Position, namedTarget.TypeArgs[0]).Accept(this); } // handle ArrayOf (1, 2, 3) var elements = (IEnumerable<IUnboundExpr>)(new IUnboundExpr[] { expr.Arg }); if (expr.Arg is TupleExpr) { elements = ((TupleExpr)expr.Arg).Fields; } return new ArrayExpr(namedTarget.Position, elements).Accept(this); } var boundArg = expr.Arg.Accept(this); // see if we're accessing a record field BoundRecordType recordType = boundArg.Type as BoundRecordType; if ((namedTarget != null) && (recordType != null) && recordType.Fields.ContainsKey(namedTarget.Name)) { // find the index of the field //### bob: ToList() here is a gross hack. var index = recordType.Fields.Keys.ToList().IndexOf(namedTarget.Name); // bind it return new LoadExpr(boundArg, recordType.Fields[namedTarget.Name], index); } if (namedTarget != null) { return mContext.ResolveName(mFunction, Scope, namedTarget.Position, namedTarget.Name, namedTarget.TypeArgs, boundArg); } IBoundExpr target = expr.Target.Accept(this); // see if we're calling a function FuncType funcType = target.Type as FuncType; if (funcType != null) { // check that args match if (!DeclComparer.TypesMatch(funcType.Parameter.Bound, boundArg.Type)) { throw new CompileException(expr.Position, "Argument types passed to evaluated function reference do not match function's parameter types."); } // simply apply the arg to the bound expression return new BoundCallExpr(target, boundArg); } // see if we're accessing a tuple field var tupleType = boundArg.Type as BoundTupleType; if ((tupleType != null) && (target.Type == Decl.Int)) { var index = target as IntExpr; if (index == null) throw new CompileException(expr.Position, "Tuple fields can only be accessed using a literal index, not an int expression."); // make sure the field is in range if ((index.Value < 0) || (index.Value >= tupleType.Fields.Count)) throw new CompileException(expr.Position, String.Format("Cannot access field {0} because the tuple only has {1} fields.", index.Value, tupleType.Fields.Count)); // bind it return new LoadExpr(boundArg, tupleType.Fields[index.Value], index.Value); } // not calling a function, so try to desugar to a __Call var callArg = new BoundTupleExpr(new IBoundExpr[] { target, boundArg }); var call = mContext.ResolveFunction(mFunction, expr.Target.Position, "__Call", new IUnboundDecl[0], callArg); if (call != null) return call; throw new CompileException(expr.Position, "Target of call is not a function."); }
/// <summary> /// Resolves and binds a reference to a name. /// </summary> /// <param name="function">The function being compiled.</param> /// <param name="scope">The scope in which the name is being bound.</param> /// <param name="name">The name being resolved. May or may not be fully-qualified.</param> /// <param name="typeArgs">The type arguments being applied to the name. For /// example, resolving "foo'(int, bool)" would pass in {int, bool} here.</param> /// <param name="arg">The argument being applied to the name.</param> /// <returns></returns> public IBoundExpr ResolveName(Function function, Scope scope, Position position, string name, IList <IUnboundDecl> typeArgs, IBoundExpr arg) { IBoundDecl argType = null; if (arg != null) { argType = arg.Type; } IBoundExpr resolved = null; // see if it's an argument if (function.ParamNames.Contains(name)) { // load the argument resolved = new LoadExpr(new LocalsExpr(), function.ParameterType, 0); if (function.ParamNames.Count > 1) { // function takes multiple parameters, so load it from the tuple var paramTuple = (BoundTupleType)function.ParameterType; var argIndex = (byte)function.ParamNames.IndexOf(name); resolved = new LoadExpr(resolved, paramTuple.Fields[argIndex], argIndex); } } // see if it's a local if (scope.Contains(name)) { var local = scope[name]; // just load the value resolved = new LoadExpr(new LocalsExpr(), scope[name]); } // if we resolved to a local name, handle it if (resolved != null) { if (typeArgs.Count > 0) { throw new CompileException(position, "Cannot apply type arguments to a local variable or function argument."); } // if the local or argument is holding a function reference and we're passed args, call it if (argType != null) { var funcType = resolved.Type as FuncType; if (funcType != null) { // check that args match if (!DeclComparer.TypesMatch(funcType.Parameter.Bound, argType)) { throw new CompileException(position, "Argument types passed to local function reference do not match function's parameter types."); } // call it resolved = new BoundCallExpr(resolved, arg); } else { // not calling a function, so try to desugar to a __Call var callArg = new BoundTupleExpr(new IBoundExpr[] { resolved, arg }); resolved = ResolveFunction(function, position, "__Call", new IUnboundDecl[0], callArg); if (resolved == null) { throw new CompileException(position, "Cannot call a local variable or argument that is not a function reference, and could not find a matching __Call."); } } } return(resolved); } // implicitly apply () as the argument if no other argument was provided. // note that we do this *after* checking for locals because locals aren't // implicitly applied. since most locals aren't functions anyway, it won't // matter in most cases, and in cases where a local holds a function, the // user will mostly likely want to treat that function like a value: return // it, pass it around, etc. if (arg == null) { arg = new UnitExpr(Position.None); argType = arg.Type; } return(ResolveFunction(function, position, name, typeArgs, arg)); }