public static IEntityInstance Evaluated(this INode node, ComputationContext ctx, EvaluationCall evalCall) { var evaluable = node as IEvaluable; if (evaluable != null && evaluable.IsComputed) { return(evaluable.Evaluation?.Components ?? Environment.JokerInstance); } if (!ctx.AddVisited(node)) { if (evaluable != null) { if (!evaluable.IsComputed) { ctx.AddError(ErrorCode.CircularReference, node); } } return(evaluable?.Evaluation?.Components ?? Environment.JokerInstance); } // todo: this is hacky, redesign this // some evaluation jumps out from their natural scope (like function) and evaluate "external" fields // to prevent keeping local names in this external context we added flag to mark the natural, nested call // or cross jump, in the latter case we reset the local names registry if (evalCall == EvaluationCall.AdHocCrossJump) { ctx.EvalLocalNames = null; } INameRegistryExtension.EnterNode(node, ref ctx.EvalLocalNames, () => new NameRegistry(ctx.Env.Options.ScopeShadowing)); { var bindable = node as ILocalBindable; if (bindable != null && bindable.Name != null) { // hackerish exception for variables being transformed as functor fields if (bindable.Owner != null) { if (ctx.EvalLocalNames != null) { if (!ctx.EvalLocalNames.Add(bindable)) { ctx.AddError(ErrorCode.NameAlreadyExists, bindable.Name); } else if (bindable.Name.Name == NameFactory.RecurFunctionName || bindable.Name.Name == NameFactory.BaseVariableName || bindable.Name.Name == NameFactory.SuperFunctionName) { ctx.AddError(ErrorCode.ReservedName, bindable.Name); } } } } } if (evaluable == null || !evaluable.IsComputed) { if (node is ICustomComputable custom) { custom.CustomEvaluate(ctx); } else { node.ChildrenNodes.ForEach(it => Evaluated(it, ctx, EvaluationCall.Nested)); (node as IComputable)?.Evaluate(ctx); } } if (node is IScope && ctx.EvalLocalNames != null) { foreach (LocalInfo bindable_info in ctx.EvalLocalNames.RemoveLayer()) { if (bindable_info.Bindable is VariableDeclaration decl) { if (!bindable_info.Read) { ctx.AddError(ErrorCode.BindableNotUsed, decl.Name); } } else if (!bindable_info.Used) { // do not report regular variables here, because we have to make difference between // reading and assigning, loop label does not have such distinction // and function parameter is always assigned if (bindable_info.Bindable is IAnchor || (bindable_info.Bindable is FunctionParameter param && param.UsageMode == ExpressionReadMode.ReadRequired)) { ctx.AddError(ErrorCode.BindableNotUsed, bindable_info.Bindable.Name); } } } } ctx.RemoveVisited(node); return(evaluable?.Evaluation?.Components ?? Environment.JokerInstance); }
public static ValidationData Validated(this INode node, ComputationContext ctx) { var evaluable = node as IEvaluable; if (evaluable != null && evaluable.Validation != null) { return(evaluable.Validation); } if (!ctx.AddVisited(node)) { return(evaluable?.Validation); } ValidationData result = ValidationData.Create(); INameRegistryExtension.CreateRegistry(INameRegistryExtension.EnterNode(node, ctx.ValAssignTracker), ref ctx.ValAssignTracker, () => new AssignmentTracker()); if (node is Loop) { ++ctx.ValLoopLevel; } { if (node is VariableDeclaration decl) { ctx.ValAssignTracker?.Add(decl, ctx.ValLoopLevel); } } if (node is IExpression expr) { AssignmentTracker parent_tracker = ctx.ValAssignTracker; validateExecutionPath(node, expr.Flow.AlwaysPath, ctx, ref result); if (expr.Flow.ThenMaybePath != null || expr.Flow.ElseMaybePath != null) { var branch_results = new List <ValidationData>(); foreach (ExecutionPath maybes in expr.Flow.ForkMaybePaths) { { AssignmentTracker cont_tracker = null; if (maybes == expr.Flow.ThenMaybePath) { cont_tracker = parent_tracker?.ThenBranch; } else if (maybes == expr.Flow.ElseMaybePath) { cont_tracker = parent_tracker?.ElseBranch; } ctx.ValAssignTracker = (cont_tracker ?? parent_tracker)?.Clone(); } ValidationData branch_result = result.Clone(); validateExecutionPath(node, maybes, ctx, ref branch_result); // time to remove "continue", because we are about to process // step and post-check of the (could be) loop if (node is IAnchor loop) { branch_result.RemoveInterruptionFor(loop, isBreak: false); } if (maybes == expr.Flow.ThenMaybePath && expr.Flow.ThenPostMaybes != null) { validateExecutionPath(node, expr.Flow.ThenPostMaybes, ctx, ref branch_result); } // we need to store the branch or kill it, because few lines below we compute intersection of the branches // so we don't want to intersect with some artifact from the previous tracker if (maybes == expr.Flow.ThenMaybePath) { parent_tracker.ThenBranch = branch_result.IsTerminated ? null : (ctx.ValAssignTracker.ThenBranch ?? ctx.ValAssignTracker); } else if (maybes == expr.Flow.ElseMaybePath) { parent_tracker.ElseBranch = branch_result.IsTerminated ? null : (ctx.ValAssignTracker.ElseBranch ?? ctx.ValAssignTracker); } else { throw new Exception(); } branch_results.Add(branch_result); } if (expr.Flow.ExhaustiveMaybes) { parent_tracker?.MergeInitializations(); } parent_tracker?.MergeAssigments(); result.Combine(branch_results); } ctx.ValAssignTracker = parent_tracker; // restore original tracker foreach (IExpression sub in expr.Flow.Enumerate) { validateReadingValues(sub, ctx); } } node.ChildrenNodes.ForEach(it => Validated(it, ctx)); if (node is IValidable verificable) { verificable.Validate(ctx); } if (node is IFunctionExit) { result.AddExit(); } else if (node is LoopInterrupt loop_interrupt) { result.AddInterruption(loop_interrupt); } else if (node is IAnchor loop) { result.RemoveInterruptionFor(loop, isBreak: true); } else if (node is FunctionDefinition) { result = ValidationData.Create(); // clear it, function scope should not leak any info outside } if (evaluable != null) { evaluable.Validation = result; } ctx.RemoveVisited(node); return((node as IEvaluable)?.Validation); }