Skip to content

Commit

Permalink
Add support to debug Pure IDE executions
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelbey committed Dec 20, 2024
1 parent 4a29395 commit 148db6d
Show file tree
Hide file tree
Showing 12 changed files with 593 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,11 @@
<artifactId>legend-engine-pure-ide-light-pure</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-pure-ide-light-interpreted-functions</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.finos.legend.engine</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.finos.legend.engine.ide.api.concept.Concept;
import org.finos.legend.engine.ide.api.concept.MovePackageableElements;
import org.finos.legend.engine.ide.api.concept.RenameConcept;
import org.finos.legend.engine.ide.api.debug.Debugging;
import org.finos.legend.engine.ide.api.execution.function.Execute;
import org.finos.legend.engine.ide.api.execution.go.ExecuteGo;
import org.finos.legend.engine.ide.api.execution.test.ExecuteTests;
Expand Down Expand Up @@ -106,6 +107,7 @@ public void run(ServerConfiguration configuration, Environment environment) thro
environment.jersey().register(new Execute(pureSession));
environment.jersey().register(new ExecuteGo(pureSession));
environment.jersey().register(new ExecuteTests(pureSession));
environment.jersey().register(new Debugging(pureSession));

environment.jersey().register(new FindInSources(pureSession));
environment.jersey().register(new FindPureFile(pureSession));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2024 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package org.finos.legend.engine.ide.api.debug;

import io.swagger.annotations.Api;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ListIterable;
import org.eclipse.collections.api.list.MutableList;
import org.eclipse.collections.api.stack.MutableStack;
import org.eclipse.collections.api.tuple.Pair;
import org.eclipse.collections.impl.factory.Maps;
import org.eclipse.collections.impl.tuple.Tuples;
import org.finos.legend.engine.ide.session.PureSession;
import org.finos.legend.engine.pure.ide.interpreted.debug.DebugState;
import org.finos.legend.engine.pure.ide.interpreted.debug.FunctionExecutionInterpretedWithDebugSupport;
import org.finos.legend.pure.m3.exception.PureExecutionException;
import org.finos.legend.pure.m3.execution.Console;
import org.finos.legend.pure.m3.execution.FunctionExecution;
import org.finos.legend.pure.m3.navigation.Instance;
import org.finos.legend.pure.m3.navigation.M3Paths;
import org.finos.legend.pure.m3.navigation.M3Properties;
import org.finos.legend.pure.m3.navigation.generictype.GenericType;
import org.finos.legend.pure.m3.navigation.multiplicity.Multiplicity;
import org.finos.legend.pure.m3.serialization.runtime.IncrementalCompiler;
import org.finos.legend.pure.m3.serialization.runtime.Source;
import org.finos.legend.pure.m4.coreinstance.CoreInstance;
import org.finos.legend.pure.m4.transaction.framework.ThreadLocalTransactionContext;
import org.finos.legend.pure.runtime.java.interpreted.VariableContext;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

@Api(tags = "Debug")
@Path("/")
public class Debugging
{
private final PureSession pureSession;

public Debugging(PureSession pureSession)
{
this.pureSession = pureSession;
}

@POST
@Path("debugging")
public Response debugState(@Context HttpServletRequest request, @Context HttpServletResponse response) throws Exception
{
FunctionExecution functionExecution = pureSession.getFunctionExecution();
if (functionExecution instanceof FunctionExecutionInterpretedWithDebugSupport)
{
FunctionExecutionInterpretedWithDebugSupport debugSupport = (FunctionExecutionInterpretedWithDebugSupport) functionExecution;
DebugState debugState = debugSupport.getDebugState();
if (debugState == null)
{
return Response.ok(getText("Not on debug state!")).build();
}

JSONObject mainObject = (JSONObject) new JSONParser().parse(new InputStreamReader(request.getInputStream()));
JSONObject extraParams = (JSONObject) mainObject.get("extraParams");
String command = (String) extraParams.getOrDefault("command", "summary");
JSONArray args = (JSONArray) extraParams.get("args");

switch (command)
{
case "summary":
return debugStateSummary(debugState);
case "abort":
return debugStateAbort(debugState);
case "eval":
case "evaluate":
return debugStateEvaluate(debugState, String.join(" ", args));
case "var":
case "variable":
return debugStateVariable(debugState, (String) args.get(0));
default: // no command is shortcut for evaluation
args.add(0, command);
return debugStateEvaluate(debugState, String.join(" ", args));
}
}
else
{
return Response.status(Response.Status.BAD_REQUEST).entity("Environment does not support debug!").build();
}
}

private Response debugStateAbort(DebugState debugState)
{
debugState.abort();
return Response.ok(getText("aborting execution...")).build();
}

private Response debugStateVariable(DebugState debugState, String varName)
{
return debugStateEvaluate(debugState, "$" + varName);
}

private Response debugStateSummary(DebugState debugState)
{
MutableStack<CoreInstance> functionExpressionCallStack = debugState.getFunctionExpressionCallStack();
PureExecutionException debugLocation = new PureExecutionException(functionExpressionCallStack.isEmpty() ? null : functionExpressionCallStack.peek().getSourceInformation(), "debug location", functionExpressionCallStack);
StringBuilder appendable = new StringBuilder();
debugLocation.printPureStackTrace(appendable, "", this.pureSession.getFunctionExecution().getProcessorSupport());
return Response.ok(getText("Variables: " + getVariables(debugState).collect(Pair::getOne).makeString(", ") + "\n\n" + appendable)).build();
}

private Response debugStateEvaluate(DebugState debugState, String command)
{
try
{
MutableList<Pair<String, CoreInstance>> variables = getVariables(debugState);
String vars = variables.collect(x -> x.getOne() + ":" + getVariableTypeAndMultiplicity(x.getTwo())).makeString(", ");
Source inMemoryCodeBlock = this.pureSession.getPureRuntime().createInMemoryCodeBlock("{" + vars + "|" + command + "}");

IncrementalCompiler incrementalCompiler = this.pureSession.getPureRuntime().getIncrementalCompiler();
IncrementalCompiler.IncrementalCompilerTransaction transaction = incrementalCompiler.newTransaction(false);
try (ThreadLocalTransactionContext ignore = transaction.openInCurrentThread())
{
incrementalCompiler.compileInCurrentTransaction(inMemoryCodeBlock);
}

ListIterable<CoreInstance> newInstances = inMemoryCodeBlock.getNewInstances();

CoreInstance result = ((FunctionExecutionInterpretedWithDebugSupport) this.pureSession.getFunctionExecution()).startRaw(newInstances.get(0), Lists.fixedSize.of());
CoreInstance lambda = Instance.getValueForMetaPropertyToOneResolved(result, M3Properties.values, this.pureSession.getFunctionExecution().getProcessorSupport());

ByteArrayOutputStream out = new ByteArrayOutputStream();
this.pureSession.getFunctionExecution().start(lambda, variables.collect(Pair::getTwo), out, this.pureSession.getFunctionExecution().newOutputWriter());
return Response.ok(getText(out.toString())).build();
}
catch (Exception e)
{
return Response.status(Response.Status.BAD_REQUEST).entity(getText(e.getMessage())).build();
}
}

private String getVariableTypeAndMultiplicity(CoreInstance coreInstance)
{
// todo the GenericType.pring has a bug with type arguments, and functions get printed wrong!
String type;
if(Instance.instanceOf(coreInstance.getValueForMetaPropertyToOne(M3Properties.values), this.pureSession.getPureRuntime().getCoreInstance(M3Paths.Function), this.pureSession.getFunctionExecution().getProcessorSupport()))
{
type = "Function<Any>";
}
else
{
type = GenericType.print(coreInstance.getValueForMetaPropertyToOne(M3Properties.genericType), true, this.pureSession.getFunctionExecution().getProcessorSupport());
}
String multiplicity = Multiplicity.print(coreInstance.getValueForMetaPropertyToOne(M3Properties.multiplicity));
return type + multiplicity;
}

private static Map<String, String> getText(String value)
{
return Maps.fixedSize.of("text", value);
}

private static MutableList<Pair<String, CoreInstance>> getVariables(DebugState debugState)
{
VariableContext variableContext = debugState.getVariableContext();
return variableContext.getVariableNames().asLazy().collect(x -> Tuples.pair(x, variableContext.getValue(x))).select(x -> x.getTwo() != null).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.finos.legend.engine.ide.SourceLocationConfiguration;
import org.finos.legend.engine.ide.api.execution.test.CallBack;
import org.finos.legend.engine.ide.helpers.response.IDEResponse;
import org.finos.legend.engine.pure.ide.interpreted.debug.FunctionExecutionInterpretedWithDebugSupport;
import org.finos.legend.pure.m3.SourceMutation;
import org.finos.legend.pure.m3.execution.FunctionExecution;
import org.finos.legend.pure.m3.execution.test.TestCollection;
Expand Down Expand Up @@ -81,7 +82,7 @@ public PureSession(SourceLocationConfiguration sourceLocationConfiguration, Muta

this.repos = Lists.mutable.withAll(repos).with(new WelcomeCodeStorage(Paths.get(rootPath)));

this.functionExecution = new FunctionExecutionInterpreted(VoidExecutionActivityListener.VOID_EXECUTION_ACTIVITY_LISTENER);
this.functionExecution = new FunctionExecutionInterpretedWithDebugSupport();

for (String property : System.getProperties().stringPropertyNames())
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2024 Goldman Sachs
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-pure-ide</artifactId>
<version>4.67.8-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>legend-engine-pure-ide-light-interpreted-functions</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.finos.legend.engine</groupId>
<artifactId>legend-engine-pure-ide-light-pure</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.finos.legend.pure</groupId>
<artifactId>legend-pure-m4</artifactId>
</dependency>
<dependency>
<groupId>org.finos.legend.pure</groupId>
<artifactId>legend-pure-m3-core</artifactId>
</dependency>

<dependency>
<groupId>org.finos.legend.pure</groupId>
<artifactId>legend-pure-runtime-java-engine-interpreted</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2024 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package org.finos.legend.engine.pure.ide.interpreted;

import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.tuple.Tuples;
import org.finos.legend.engine.pure.ide.interpreted.debug.DebugPureIDE;
import org.finos.legend.pure.runtime.java.interpreted.extension.BaseInterpretedExtension;
import org.finos.legend.pure.runtime.java.interpreted.extension.InterpretedExtension;

public class PureIDEExtensionInterpreted extends BaseInterpretedExtension
{
public PureIDEExtensionInterpreted()
{
super(Lists.mutable.with(
Tuples.pair("debug__Nil_0_", DebugPureIDE::new)
));
}

public static InterpretedExtension extension()
{
return new PureIDEExtensionInterpreted();
}
}
Loading

0 comments on commit 148db6d

Please sign in to comment.