diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/pom.xml b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/pom.xml
index 1bc9d8221f1..f91d7103f91 100644
--- a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/pom.xml
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/pom.xml
@@ -118,7 +118,11 @@
legend-engine-pure-ide-light-pure
runtime
-
+
+ org.finos.legend.engine
+ legend-engine-pure-ide-light-interpreted-functions
+ ${project.version}
+
org.finos.legend.engine
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/PureIDEServer.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/PureIDEServer.java
index e88af292e41..989a8d68e98 100644
--- a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/PureIDEServer.java
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/PureIDEServer.java
@@ -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;
@@ -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));
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/api/debug/Debugging.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/api/debug/Debugging.java
new file mode 100644
index 00000000000..2d14bf35907
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/api/debug/Debugging.java
@@ -0,0 +1,113 @@
+// 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.InputStreamReader;
+import java.util.List;
+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.impl.factory.Maps;
+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.execution.FunctionExecution;
+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 debugging(@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");
+ List args = (List) extraParams.get("args");
+
+ if (args.isEmpty())
+ {
+ args.add("summary");
+ }
+
+ switch (args.get(0))
+ {
+ case "summary":
+ return debugSummary(debugState);
+ case "abort":
+ return debugAbort(debugState);
+ default: // no command is shortcut for evaluation
+ return debugEvaluate(debugState, String.join(" ", args));
+ }
+ }
+ else
+ {
+ return Response.status(Response.Status.BAD_REQUEST).entity("Environment does not support debug!").build();
+ }
+ }
+
+ private Response debugAbort(DebugState debugState)
+ {
+ debugState.abort();
+ return Response.ok(getText("aborting execution...")).build();
+ }
+
+ private Response debugSummary(DebugState debugState)
+ {
+ return Response.ok(getText(debugState.getSummary())).build();
+ }
+
+ private Response debugEvaluate(DebugState debugState, String command)
+ {
+ try
+ {
+ return Response.ok(getText(debugState.evaluate(command))).build();
+ }
+ catch (Exception e)
+ {
+ return Response.status(Response.Status.BAD_REQUEST).entity(getText(e.getMessage())).build();
+ }
+ }
+
+ private static Map getText(String value)
+ {
+ return Maps.fixedSize.of("text", value);
+ }
+}
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/session/PureSession.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/session/PureSession.java
index 4fe72148b92..1c72279d37d 100644
--- a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/session/PureSession.java
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-http-server/src/main/java/org/finos/legend/engine/ide/session/PureSession.java
@@ -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;
@@ -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())
{
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/pom.xml b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/pom.xml
new file mode 100644
index 00000000000..96849249bab
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+ org.finos.legend.engine
+ legend-engine-pure-ide
+ 4.67.9-SNAPSHOT
+
+ 4.0.0
+
+ legend-engine-pure-ide-light-interpreted-functions
+ jar
+
+
+
+ org.finos.legend.engine
+ legend-engine-pure-ide-light-pure
+ runtime
+
+
+
+ org.finos.legend.pure
+ legend-pure-m4
+
+
+ org.finos.legend.pure
+ legend-pure-m3-core
+
+
+
+ org.finos.legend.pure
+ legend-pure-runtime-java-engine-interpreted
+
+
+
+ org.eclipse.collections
+ eclipse-collections-api
+
+
+ org.eclipse.collections
+ eclipse-collections
+
+
+
+
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/PureIDEExtensionInterpreted.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/PureIDEExtensionInterpreted.java
new file mode 100644
index 00000000000..9414cd46880
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/PureIDEExtensionInterpreted.java
@@ -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();
+ }
+}
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugPureIDE.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugPureIDE.java
new file mode 100644
index 00000000000..1020979e59e
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugPureIDE.java
@@ -0,0 +1,79 @@
+// 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.debug;
+
+import java.util.concurrent.CountDownLatch;
+import org.eclipse.collections.api.list.ListIterable;
+import org.eclipse.collections.api.map.MutableMap;
+import org.eclipse.collections.api.stack.MutableStack;
+import org.finos.legend.pure.m3.compiler.Context;
+import org.finos.legend.pure.m3.coreinstance.meta.pure.metamodel.valuespecification.InstanceValue;
+import org.finos.legend.pure.m3.exception.PureAssertFailException;
+import org.finos.legend.pure.m3.exception.PureExecutionException;
+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.PrimitiveUtilities;
+import org.finos.legend.pure.m3.navigation.ProcessorSupport;
+import org.finos.legend.pure.m3.navigation.ValueSpecificationBootstrap;
+import org.finos.legend.pure.m4.ModelRepository;
+import org.finos.legend.pure.m4.coreinstance.CoreInstance;
+import org.finos.legend.pure.runtime.java.interpreted.ExecutionSupport;
+import org.finos.legend.pure.runtime.java.interpreted.FunctionExecutionInterpreted;
+import org.finos.legend.pure.runtime.java.interpreted.VariableContext;
+import org.finos.legend.pure.runtime.java.interpreted.natives.InstantiationContext;
+import org.finos.legend.pure.runtime.java.interpreted.natives.NativeFunction;
+import org.finos.legend.pure.runtime.java.interpreted.profiler.Profiler;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Stack;
+
+public class DebugPureIDE extends NativeFunction
+{
+ private final FunctionExecutionInterpreted functionExecution;
+
+ private final ModelRepository repository;
+
+ public DebugPureIDE(FunctionExecutionInterpreted functionExecution, ModelRepository modelRepository)
+ {
+ this.functionExecution = functionExecution;
+ this.repository = modelRepository;
+ }
+
+ @Override
+ public CoreInstance execute(ListIterable extends CoreInstance> params, Stack> resolvedTypeParameters, Stack> resolvedMultiplicityParameters, VariableContext variableContext, MutableStack functionExpressionCallStack, Profiler profiler, InstantiationContext instantiationContext, ExecutionSupport executionSupport, Context context, ProcessorSupport processorSupport) throws PureExecutionException
+ {
+ if (this.functionExecution instanceof FunctionExecutionInterpretedWithDebugSupport)
+ {
+ FunctionExecutionInterpretedWithDebugSupport debugSupport = (FunctionExecutionInterpretedWithDebugSupport) this.functionExecution;
+ DebugState state = new DebugState(
+ debugSupport,
+ variableContext.getParent(), // get out of the debug function...
+ functionExpressionCallStack
+ );
+ state.debug();
+
+ if (state.aborted())
+ {
+ throw new PureExecutionException("Aborting execution...");
+ }
+ }
+
+ return ValueSpecificationBootstrap.newBooleanLiteral(this.repository, true, this.functionExecution.getProcessorSupport());
+ }
+}
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugState.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugState.java
new file mode 100644
index 00000000000..6508e4e1c92
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/DebugState.java
@@ -0,0 +1,148 @@
+// 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.debug;
+
+import java.io.ByteArrayOutputStream;
+import java.util.concurrent.CountDownLatch;
+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.tuple.Tuples;
+import org.finos.legend.pure.m3.exception.PureExecutionException;
+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.ProcessorSupport;
+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;
+
+public class DebugState
+{
+ private final CountDownLatch latch;
+
+ private final MutableStack functionExpressionCallStack;
+
+ private final FunctionExecutionInterpretedWithDebugSupport debugSupport;
+ private final MutableList> variables;
+ private final String variablesTypeAndMultiplicity;
+ private volatile boolean abort;
+
+ public DebugState(FunctionExecutionInterpretedWithDebugSupport debugSupport, VariableContext variableContext, MutableStack functionExpressionCallStack)
+ {
+ this.debugSupport = debugSupport;
+ this.latch = new CountDownLatch(1);
+ this.functionExpressionCallStack = functionExpressionCallStack;
+ this.variables = computeVariables(variableContext);
+ this.variablesTypeAndMultiplicity = computeVariablesTypeAndMultiplicity(this.debugSupport, this.variables);
+ this.debugSupport.setDebugState(this);
+ }
+
+ public void release()
+ {
+ this.debugSupport.setDebugState(null);
+ this.latch.countDown();
+ }
+
+ public void debug()
+ {
+ try
+ {
+ this.latch.await();
+ }
+ catch (Exception e)
+ {
+ // todo?
+ }
+ }
+
+ public void abort()
+ {
+ this.abort = true;
+ this.release();
+ }
+
+ public boolean aborted()
+ {
+ return this.abort;
+ }
+
+ public String getSummary()
+ {
+ PureExecutionException debugLocation = new PureExecutionException(this.functionExpressionCallStack.isEmpty() ? null : this.functionExpressionCallStack.peek().getSourceInformation(), "debug location", functionExpressionCallStack);
+ StringBuilder appendable = new StringBuilder();
+ debugLocation.printPureStackTrace(appendable, "", this.debugSupport.getProcessorSupport());
+ return "Variables: " + this.variablesTypeAndMultiplicity + "\n\n" + appendable;
+ }
+
+ public String evaluate(String command)
+ {
+ Source inMemoryCodeBlock = this.debugSupport.getPureRuntime().createInMemoryCodeBlock("{" + variablesTypeAndMultiplicity + "|\n" + command + "\n}");
+
+ IncrementalCompiler incrementalCompiler = this.debugSupport.getPureRuntime().getIncrementalCompiler();
+ IncrementalCompiler.IncrementalCompilerTransaction transaction = incrementalCompiler.newTransaction(false);
+ try (ThreadLocalTransactionContext ignore = transaction.openInCurrentThread())
+ {
+ incrementalCompiler.compileInCurrentTransaction(inMemoryCodeBlock);
+ }
+
+ ListIterable newInstances = inMemoryCodeBlock.getNewInstances();
+
+ CoreInstance result = this.debugSupport.startRaw(newInstances.get(0), Lists.fixedSize.of());
+ CoreInstance lambda = Instance.getValueForMetaPropertyToOneResolved(result, M3Properties.values, this.debugSupport.getProcessorSupport());
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ this.debugSupport.start(lambda, variables.collect(Pair::getTwo), out, this.debugSupport.newOutputWriter());
+ return out.toString();
+ }
+
+ private static String computeVariablesTypeAndMultiplicity(FunctionExecutionInterpretedWithDebugSupport debugSupport, MutableList> variables)
+ {
+ return variables.collect(x -> x.getOne() + ":" + computeVariableTypeAndMultiplicity(debugSupport, x.getTwo())).makeString(", ");
+ }
+
+ private static MutableList> computeVariables(VariableContext variableContext)
+ {
+ return variableContext.getVariableNames()
+ .asLazy()
+ .collect(x -> Tuples.pair(x, variableContext.getValue(x))).select(x -> x.getTwo() != null)
+ .toList();
+ }
+
+ private static String computeVariableTypeAndMultiplicity(FunctionExecutionInterpretedWithDebugSupport debugSupport, CoreInstance coreInstance)
+ {
+ // todo the GenericType.print has a bug with type arguments, and functions get printed wrong!
+ // ie. meta::pure::metamodel::function::ConcreteFunctionDefinition< {meta::pure::metamodel::function::Function<{->X[o]}>[1]->X[o]}>
+ String type;
+ ProcessorSupport processorSupport = debugSupport.getProcessorSupport();
+ if (processorSupport.type_subTypeOf(coreInstance.getValueForMetaPropertyToOne(M3Properties.genericType).getValueForMetaPropertyToOne(M3Properties.rawType), debugSupport.getPureRuntime().getCoreInstance(M3Paths.Function)))
+ {
+ type = "Function";
+ }
+ else
+ {
+ type = GenericType.print(coreInstance.getValueForMetaPropertyToOne(M3Properties.genericType), true, processorSupport);
+ }
+ String multiplicity = Multiplicity.print(coreInstance.getValueForMetaPropertyToOne(M3Properties.multiplicity));
+ return type + multiplicity;
+ }
+}
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/FunctionExecutionInterpretedWithDebugSupport.java b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/FunctionExecutionInterpretedWithDebugSupport.java
new file mode 100644
index 00000000000..1641414605f
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/java/org/finos/legend/engine/pure/ide/interpreted/debug/FunctionExecutionInterpretedWithDebugSupport.java
@@ -0,0 +1,122 @@
+// 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.debug;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import org.eclipse.collections.api.list.ListIterable;
+import org.finos.legend.pure.m3.exception.PureExecutionException;
+import org.finos.legend.pure.m3.execution.OutputWriter;
+import org.finos.legend.pure.m3.navigation.M3Properties;
+import org.finos.legend.pure.m3.navigation.ValueSpecificationBootstrap;
+import org.finos.legend.pure.m3.statelistener.VoidExecutionActivityListener;
+import org.finos.legend.pure.m4.coreinstance.CoreInstance;
+import org.finos.legend.pure.runtime.java.interpreted.FunctionExecutionInterpreted;
+
+public class FunctionExecutionInterpretedWithDebugSupport extends FunctionExecutionInterpreted
+{
+ private volatile CompletableFuture currentExecution;
+ private volatile CompletableFuture resultHandler;
+ private volatile DebugState debugState;
+
+ public FunctionExecutionInterpretedWithDebugSupport()
+ {
+ super(VoidExecutionActivityListener.VOID_EXECUTION_ACTIVITY_LISTENER);
+ }
+
+ public DebugState getDebugState()
+ {
+ return this.debugState;
+ }
+
+ public void setDebugState(DebugState debugState)
+ {
+ if (debugState != null && this.debugState != null)
+ {
+ throw new IllegalStateException("Debug session already exists?");
+ }
+
+ this.debugState = debugState;
+
+ if (debugState != null)
+ {
+ this.getConsole().print("Entering debug mode. Use terminal to introspect debug state. F9 to continue execution.\n\nDebug summary:\n");
+ this.getConsole().print(debugState.getSummary());
+ this.resultHandler.complete(null);
+ }
+ }
+
+ @Override
+ public CoreInstance start(CoreInstance function, ListIterable extends CoreInstance> arguments)
+ {
+ this.resultHandler = new CompletableFuture<>();
+
+ if (this.currentExecution == null)
+ {
+ this.currentExecution = CompletableFuture.supplyAsync(() -> this.startRaw(function, arguments));
+ this.currentExecution.whenComplete((v, e) ->
+ {
+ if (e != null)
+ {
+ this.resultHandler.completeExceptionally(e);
+ }
+ else
+ {
+ this.resultHandler.complete(v);
+ }
+ this.currentExecution = null;
+ });
+ }
+ else
+ {
+ this.debugState.release();
+ }
+
+ try
+ {
+ return this.resultHandler.join();
+ }
+ catch (CompletionException e)
+ {
+ throw (RuntimeException) e.getCause();
+ }
+ }
+
+ public CoreInstance startRaw(CoreInstance function, ListIterable extends CoreInstance> arguments)
+ {
+ return super.start(function, arguments);
+ }
+
+ @Override
+ public void start(CoreInstance func, ListIterable extends CoreInstance> arguments, OutputStream
+ outputStream, OutputWriter writer)
+ {
+ CoreInstance result = this.startRaw(func, arguments);
+
+ try
+ {
+ ListIterable extends CoreInstance> values = result.getValueForMetaPropertyToMany(M3Properties.values);
+ writer.write(values, outputStream);
+ }
+ catch (IOException e)
+ {
+ throw new UncheckedIOException("Failed to write to output stream", e);
+ }
+ }
+}
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/resources/META-INF/services/org.finos.legend.pure.runtime.java.interpreted.extension.InterpretedExtension b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/resources/META-INF/services/org.finos.legend.pure.runtime.java.interpreted.extension.InterpretedExtension
new file mode 100644
index 00000000000..5c1cbdf66fd
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-interpreted-functions/src/main/resources/META-INF/services/org.finos.legend.pure.runtime.java.interpreted.extension.InterpretedExtension
@@ -0,0 +1 @@
+org.finos.legend.engine.pure.ide.interpreted.PureIDEExtensionInterpreted
\ No newline at end of file
diff --git a/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-pure/src/main/resources/pure_ide/debug.pure b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-pure/src/main/resources/pure_ide/debug.pure
new file mode 100644
index 00000000000..59a0bb9f3b9
--- /dev/null
+++ b/legend-engine-pure/legend-engine-pure-ide/legend-engine-pure-ide-light-pure/src/main/resources/pure_ide/debug.pure
@@ -0,0 +1,15 @@
+// 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.
+
+native function meta::pure::ide::debug():Nil[0];
\ No newline at end of file
diff --git a/legend-engine-pure/legend-engine-pure-ide/pom.xml b/legend-engine-pure/legend-engine-pure-ide/pom.xml
index 6fcf666a11c..863c57109d1 100644
--- a/legend-engine-pure/legend-engine-pure-ide/pom.xml
+++ b/legend-engine-pure/legend-engine-pure-ide/pom.xml
@@ -30,5 +30,6 @@
legend-engine-pure-ide-light-http-server
legend-engine-pure-ide-light-metadata-pure
legend-engine-pure-ide-light-pure
+ legend-engine-pure-ide-light-interpreted-functions
\ No newline at end of file