/// <summary> /// Conceptually, converts a given <paramref name="workspace"/> into "evaluation AST" (which can next be evaluated/interpreted). /// /// In reality, this "evaluation AST" is so tightly coupled with the engine, so this method has no choice but to /// create a big hairball of hosts/controllers/resolvers/contexts/configurations/etc to make evaluation possible. /// /// This method tries to bypass as much of the front-end stuff as possible. For example, it doesn't start evaluation from /// <see cref="FrontEndHost"/>, but instead it creates a single resolver (namely <see cref="DScriptSourceResolver"/> /// and uses that resolver directly to evaluate the AST. /// /// Any errors can be retrieved via the <see cref="CreateTestResult"/> method. /// </summary> public async Task <TestResult <Interpreter> > ConvertNoErrorCheckAsync(Workspace workspace, [CanBeNull] PipGraph oldPipGraph) { var nonPreludeModules = NonPreludeModules(workspace).ToArray(); var moduleRegistry = new ModuleRegistry(SymbolTable); var configStringPath = Path.Combine(SrcRoot.ToString(PathTable), Names.ConfigDsc); var configuration = new ConfigurationImpl() { FrontEnd = { EnableIncrementalFrontEnd = false, ReloadPartialEngineStateWhenPossible = false, UseSpecPublicFacadeAndAstWhenAvailable = false, ConstructAndSaveBindingFingerprint = false, UsePartialEvaluation = false, } }; var frontEndHost = FrontEndHostController.CreateForTesting(FrontEndContext, Engine, moduleRegistry, configStringPath, FrontEndLogger); var frontEnd = new DScriptFrontEnd(FrontEndStatistics, AstLogger, null); frontEnd.InitializeFrontEnd(frontEndHost, FrontEndContext, configuration); var resolver = (DScriptSourceResolver)frontEnd.CreateResolver(KnownResolverKind.DScriptResolverKind); var packages = nonPreludeModules.Select(module => CreatePackageForModule(module)).ToList(); resolver.InitResolverForTesting("Test", packages); frontEndHost.InitializeResolvers(new[] { resolver }); // convert all modules and assert it succeeds var convertTasks = nonPreludeModules.Select(module => frontEndHost.ConvertWorkspaceToEvaluationAsync(workspace)); await Task.WhenAll(convertTasks); // prepare for evaluation var graphBuilder = new PipGraph.Builder( new PipTable(PathTable, SymbolTable, initialBufferSize: 16, maxDegreeOfParallelism: Environment.ProcessorCount, debug: false), new EngineContext(CancellationToken.None, PathTable, SymbolTable, new QualifierTable(PathTable.StringTable), FrontEndContext.FileSystem, new TokenTextTable()), global::BuildXL.Pips.Tracing.Logger.Log, FrontEndContext.LoggingContext, // For tests, allow writes outside of mounts unles defined otherwise new ConfigurationImpl() { Engine = { UnsafeAllowOutOfMountWrites = true } }, new MountPathExpander(PathTable)); IMutablePipGraph pipGraph = oldPipGraph != null ? new PatchablePipGraph(oldPipGraph.DataflowGraph, oldPipGraph.PipTable, graphBuilder, maxDegreeOfParallelism: Environment.ProcessorCount) : (IMutablePipGraph)graphBuilder; frontEndHost.SetState(Engine, pipGraph, configuration); return(new TestResult <Interpreter>(frontEndHost, Diagnostics)); }
/// <summary> /// Evaluates an expression in the current debugger state context /// </summary> internal Possible <ObjectContext, EvaluateFailure> EvaluateExpression(FrameContext frameContext, string expressionString) { var evalState = (EvaluationState)m_state.GetThreadState(frameContext.ThreadId); var context = evalState.Context; var moduleLiteral = evalState.GetEnvForFrame(frameContext.FrameIndex); var frontEnd = new DScriptFrontEnd(new FrontEndStatistics()); frontEnd.InitializeFrontEnd(context.FrontEndHost, context.FrontEndContext, s_configuration); // We clear the logger before using it. var isClear = m_logger.TryClearCapturedDiagnostics(); // This logger should only be used in the context of one thread, so it should always be possible to clear it Contract.Assert(isClear); RuntimeModelContext runtimeModelContext = new RuntimeModelContext( context.FrontEndHost, context.FrontEndContext, m_logger, context.Package); // We recreate the local scope so the expression is parsed using the same local variables indexes // than the context where it is going to be evaluated var localScope = BuildLocalScopeForLocalVars(context, evalState.GetStackEntryForFrame(frameContext.FrameIndex)); var expression = s_parser.ParseExpression(runtimeModelContext, context.Package.Path, expressionString, localScope, useSemanticNameResolution: false); // If parsing failed, we report it and return // VS code only displays the first error that is sent. So we only send the first one. // An alternative would be to concatenate all errors and send them as a single message, but new lines are not respected by VS Code, // so the concatenation is not very legible. // Anyway, the first error should be good enough for almost all cases if (expression == null) { Contract.Assert(runtimeModelContext.Logger.CapturedDiagnostics.Count > 0); var diagnostic = runtimeModelContext.Logger.CapturedDiagnostics[0]; return(new EvaluateFailure(diagnostic)); } // We clear the logger again since it may contain warnings that didn't prevent the parser from finishing successfully isClear = m_logger.TryClearCapturedDiagnostics(); Contract.Assert(isClear); object expressionResult; // We temporary override the context logger so it doesn't affect the normal evaluation using (var expressionContext = new SnippetEvaluationContext(context, m_logger)) { expressionResult = expression.Eval(expressionContext.GetContextForSnippetEvaluation(), moduleLiteral, evalState.GetArgsForFrame(frameContext.FrameIndex)).Value; // If evaluation failed, we report it and return if (expressionResult.IsErrorValue()) { Contract.Assert(context.Logger.CapturedDiagnostics.Count > 0); var diagnostic = context.Logger.CapturedDiagnostics[0]; return(new EvaluateFailure(diagnostic)); } } return(new ObjectContext(context, expressionResult)); }