public IFunction CallInternal(IFunction[] arguments, string output, CompilationContext context) { if (arguments.Length < 2) { return(context.LogError(6, "memberwise requires at least 2 arguments")); } if (arguments.Any(a => a is AnyType)) { return(AnyType.Instance); } if (arguments.Skip(1).Any(a => a.Inputs?.Length != 0)) { return(context.LogError(14, "one or more memberwise arguments are functions")); } if (arguments.Skip(1).Any(a => a.Outputs == null)) { return(context.LogError(14, "one or more memberwise arguments is non-introspectable")); } // TODO: Check all have same input signature? return(new MapFunction(arguments[0], arguments.Skip(1).ToArray())); }
/// <summary> /// Converts an Element Array instance (with count and index members) to /// a fixed-size list of Functions by evaluating each index. /// </summary> /// <returns>The evaluated array, or an empty array if there was an error</returns> private static IFunction[] EvaluateArray(IFunction function, CompilationContext context) { if (function == null) { throw new ArgumentNullException(nameof(function)); } if (function.Inputs?.Length != 0) { context.LogError($"{function} needs to be an array, but it has inputs"); return(Array.Empty <IFunction>()); } var countExpr = function.Call("count", context).AsExpression(context); if (countExpr != null) { countExpr = ConstantFolding.Optimize(countExpr); } var count = (countExpr as Constant)?.Value; if (!count.HasValue) { context.LogError($"{function}'s count is not constant"); return(Array.Empty <IFunction>()); } var index = function.Call("index", context); return(Enumerable.Range(0, (int)count.Value) .Select(i => index.Call(new[] { new Constant(i) }, context)) .ToArray()); }
/// <summary> /// Calculates the serialized size of the given Function or Type /// </summary> /// <param name="value">The value or type in question</param> /// <param name="info">Where to log error messages</param> /// <returns>The size of the structure in singles, or null if there was a problem</returns> public static int?GetSize(this IFunction value, CompilationContext info) { if (value == Error.Instance) { return(null); } if (value is SerializableType) { return(0); } if (value.IsLeaf() || value.AsExpression(info) != null) { return(1); } if (value.Inputs?.Length != 0) { info.LogError($"Cannot serialize {value} because it has inputs"); return(null); } if (value.Outputs == null) { info.LogError($"Cannot serialize {value} because it is not introspectable"); return(null); } return(value.Outputs.Select(o => GetSize(value.Call(o.Name, info), info)) // NB: An ordinary 'Sum' will treat null as 0 .Aggregate((int?)0, (a, b) => (a == null || b == null) ? null : a + b)); }
public override IFunction CallInternal(IFunction[] arguments, string output, CompilationContext context) { if (IsNamespace && arguments.Length == 0) { return(CompileIntermediate(arguments, output, context)); } else if (IsNamespace && !IsClass) { return(context.LogError($"This is a Namespace, so it has no constructor")); } if (IsClass && arguments.Length == Inputs.Length && Outputs.All(p => p.Name != output) && NamespaceMembers.Contains(output)) { var memberFunction = CompileIntermediate(Array.Empty <IFunction>(), output, context); if (memberFunction.Inputs.Length > 0 && memberFunction.Inputs[0].Name == "this") { var classInstance = this.Call(arguments, context); return(classInstance.AsMethod(memberFunction, MakeCallSite(Ast), context)); } } context.Push(MakeCallSite(Ast)); if (this.CheckArguments(arguments, output, context) != null) { context.Pop(); return(Error.Instance); } context.Pop(); if (IsClass) { return(arguments[Array.FindIndex(Outputs, p => p.Name == output)]); } var outputPort = Outputs.First(p => p.Name == output); var outValue = CompileIntermediate(arguments, output, context); var success = outputPort.Type.SatisfiedBy(outValue, context); return(success switch { false => context.LogError("ELE0008", $"Output `{outputPort}` was not satisfied by its value `{outValue}` (See previous errors)"), null => Error.Instance, _ => outValue });
/// <summary> /// Compiles a single expression (e.g. part of an expression list) /// </summary> private IFunction CompileExpression(IFunction?previous, Match exprAst, CompilationStack stack, CompilationContext context) { switch (exprAst.Name) { case ElementAST.NumberExpression: return(new Constant((float)exprAst.Value)); case ElementAST.VariableExpression: context.Push(MakeCallSite(exprAst)); var variable = CompileFunction(exprAst.Text, stack, context); context.Pop(); return(variable); case ElementAST.SubExpression: return(previous.Call(exprAst[ElementAST.SubExpressionName].Text, context, MakeCallSite(exprAst))); case ElementAST.CallExpression: // This is called a *lot*, so try to make it high performance: var args = exprAst[ElementAST.CallArguments].Matches; var argList = new IFunction[args.Count]; for (var i = 0; i < argList.Length; i++) { argList[i] = CompileExpressionList(args[i], stack, context); } return(previous.Call(argList, context, MakeCallSite(exprAst))); default: return(context.LogError($"Unknown expression {exprAst}")); } }
public IFunction CallInternal(IFunction[] arguments, string name, CompilationContext context) { if (arguments.Length == 0) { return(context.LogError("ELE2000")); } switch (name) { case "count": return(new Constant(arguments.Length)); case "index": return(new IndexFunction(arguments)); default: return(context.LogError($"No output named {name}")); } }
/// <summary> /// Adds a type to the global scope /// </summary> public void AddType(INamedType type, CompilationContext context) { if (FindType(type.Name, context) != null) { context.LogError("ELE0002", $"Duplicate type named {type.Name}"); } _types.Add(type); }
/// <summary> /// Adds a function to the global scope /// </summary> public void AddFunction(INamedFunction function, CompilationContext context) { if (GetFunction(function.Name, context) != null) { context.LogError("ELE0002", $"Duplicate function named {function.Name}"); } _functions.Add(function); }
/// <summary> /// Checks that the proposed arguments to a function are valid. /// </summary> /// <param name="function">The function about to be called</param> /// <param name="arguments">Ordered list of proposed inputs</param> /// <param name="output">The proposed output</param> /// <param name="info">The place to log messages on failure</param> /// <returns>Null on success, or Abort on failure (this allows using the null-coalesce operator)</returns> public static IFunction CheckArguments(this IFunction function, IFunction[] arguments, string output, CompilationContext info) { var success = true; var inputs = function.Inputs; if (inputs != null) { if (arguments.Length != inputs.Length) { info.LogError($"{function} Expected {inputs.Length} arguments but got {arguments.Length}"); success = false; } else { for (var i = 0; i < inputs.Length; i++) { var arg = inputs[i].Type.SatisfiedBy(arguments[i], info); if (arg == false) { info.LogError( $"{function} Input {i} ({inputs[i]}) not satisfied by {arguments[i]} (see previous errors)"); } if (arg != true) { success = false; } } } } if (output != null && function.Outputs != null && Array.FindIndex(function.Outputs, p => p.Name == output) < 0) { info.LogError($"Couldn't find output `{output}` in {function}"); success = false; } if (arguments.Any(a => a is Error)) { return(Error.Instance); } return(success ? null : Error.Instance); }
public DeserializedStructure(IFunction structure, Func <Expression> data, CompilationContext context) { if (structure.Inputs.Length > 0) { context.LogError("ELE0001", $"Cannot deserialize {structure} because it has inputs"); } Outputs = structure.Outputs; _outputValues = Outputs.Select(o => structure.Call(o.Name, context).Deserialize(data, context)).ToArray(); }
/// <summary> /// See the other overload of Serialize, but this time it takes a Queue rather than making a new array. /// </summary> /// <param name="value"></param> /// <param name="dataOut">The queue to append new values to</param> /// <param name="context"></param> public static void Serialize(this IFunction value, Queue <Expression> dataOut, CompilationContext context) { var expr = value.AsExpression(context); if (expr != null) { dataOut.Enqueue(expr); return; } if (value.IsLeaf()) { dataOut.Enqueue(Constant.Zero); return; } if (value is SerializableType) { return; } if (value.Inputs?.Length != 0) { context.LogError($"Cannot serialize {value} because it has inputs"); return; } if (value.Outputs == null) { context.LogError($"{value} doesn't have known outputs"); return; } // TODO: Arrays here? foreach (var output in value.Outputs) { Serialize(value.Call(output.Name, context), dataOut, context); } }
private PortInfo ParsePort(Match m, bool isReturn, CompilationContext info) { var ret = new PortInfo { Name = isReturn ? "return" : m[ElementAST.PortName].Text, Type = m[ElementAST.PortType] ? Parent.FindType(m[ElementAST.PortType].Text, info) : AnyType.Instance }; if (ret.Type == null) { info.LogError(7, $"{this}: Type {m[ElementAST.PortType]} not found"); ret.Type = AnyType.Instance; } return ret; }
public IFunction CompileFunction(string name, CompilationStack stack, CompilationContext context) { // First try to get a cached/inputted value... if (!stack.GetLocal(name, out var value)) { // If that fails, look in the list of drivers: if (_drivers != null) { if (_drivers.TryGetValue(name, out var statement)) { if (statement[ElementAST.TypeStatement]) // Check if this statement is a type declaration { var type = FindType(name, context); value = new Constructor(type, context); } else { value = new CustomFunction(this, statement, stack, context, Source); } stack.Add(name, value); } } // If there's no driver list (i.e. during an assignment), then the only output is 'return' // Since there's no other drivers we can assume else if (name == "return") { value = CompileExpressionList(_astAssign, stack, context); stack.Add(name, value); } } // Failing the above, try to find the value including parents in the stack // if we still cannot find a value, try using the captured compilation stack if (value == null && !stack.Get(name, out value) && _capturedCompilationStack != null) { value = Parent.CompileFunction(name, _capturedCompilationStack, context); } if (value == null) { return(context.LogError("ELE0007", name)); } return(value.ResolveReturns(context, null)); // TODO: Keep variable information here? }
/// <summary> /// See the other overload of Deserialize, but this time it takes a delegate to generate new values /// rather than a fixed list. /// </summary> /// <returns>The new, deserialized structure mapped to the provided data.</returns> public static IFunction Deserialize(this IFunction function, Func <Expression> data, CompilationContext context) { if (function.IsLeaf()) { return(data()); } if (function is SerializableType) { return(function); } if (function?.Inputs == null) { context.LogError("ELE0001", $"Cannot deserialize `{function}` outputs because it's input ports have no defined type"); } return(new DeserializedStructure(function, data, new CompilationContext())); }
IFunction IFunction.CallInternal(IFunction[] arguments, string output, CompilationContext context) => context.LogError(9999, "Tried to call a leaf value");
IFunction IFunction.CallInternal(IFunction[] arguments, string output, CompilationContext context) => context.LogError("Can't call a number");
public bool?SatisfiedBy(IFunction value, CompilationContext info) { var inputs = Inputs.Select(p => p.Type).ToArray(); // TODO: A better way of doing this! var outPorts = Outputs; // We are a function type if (inputs.Length > 0) { var called = this.Call(inputs, info); value = value.Call(inputs, info); // Calling this type returned another type if (called is IType type) { return(type.SatisfiedBy(value, info)); } // Calling the type returned something that isn't introspectable if (called.Outputs == null) { // TODO: Check this. What about Abort? info.LogError(14, $"Output of {value} is not introspectable"); return(false); } // Turns a structural thing into a bare list // Was required in past due to ambiguity between structural thing and multiple returns outPorts = called.Outputs.Select(o => new PortInfo { Name = o.Name, Type = (IType)called.Call(o.Name, info) }) .ToArray(); } // Now we check type satisfaction for each output port bool?success = true; foreach (var o in outPorts) { var val = value.Call(o.Name, info); if (val is CompilationError) { success = null; } else { var thisSuccess = o.Type.SatisfiedBy(val, info); if (thisSuccess == false) { info.LogError(14, $"Output {o} not satisfied by {val}"); success = false; } else { success &= thisSuccess; } } } return(success); }