From 82678842c61d17b412cf6ab8f9bcb5fc520283e8 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 7 Feb 2024 15:07:38 +0000 Subject: [PATCH 1/7] Document basic script API type. --- .../org/praxislive/script/Bundle.properties | 1 - .../java/org/praxislive/script/Command.java | 16 ++- .../praxislive/script/CommandInstaller.java | 16 ++- .../main/java/org/praxislive/script/Env.java | 51 ++++++++- .../org/praxislive/script/InlineCommand.java | 15 ++- .../java/org/praxislive/script/Namespace.java | 53 +++++++-- .../org/praxislive/script/StackFrame.java | 106 +++++++++++++++++- .../java/org/praxislive/script/Variable.java | 25 ++++- 8 files changed, 247 insertions(+), 36 deletions(-) delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/Bundle.properties diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Bundle.properties b/praxiscore-script/src/main/java/org/praxislive/script/Bundle.properties deleted file mode 100644 index 36623378..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/Bundle.properties +++ /dev/null @@ -1 +0,0 @@ -OpenIDE-Module-Name=praxis.script diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Command.java b/praxiscore-script/src/main/java/org/praxislive/script/Command.java index 27b05bbb..256f67b3 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Command.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Command.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,17 +19,27 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; import java.util.List; import org.praxislive.core.Value; /** - * + * A script command. The script executor will look up commands by name in the + * current {@link Namespace}. Each execution of the command will cause a call + * {@link #createStackFrame(org.praxislive.script.Namespace, java.util.List)}. */ public interface Command { + /** + * Create a StackFrame to execute the command with the provided Namespace + * and arguments. + * + * @param namespace current namespace + * @param args arguments + * @return stack frame to execute command with provided arguments + * @throws ExecutionException if stack frame cannot be created + */ public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException; diff --git a/praxiscore-script/src/main/java/org/praxislive/script/CommandInstaller.java b/praxiscore-script/src/main/java/org/praxislive/script/CommandInstaller.java index f5e661ea..0ca7c09b 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/CommandInstaller.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/CommandInstaller.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,17 +19,25 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; import java.util.Map; /** - * - * + * Service provider interface for other modules to provide commands. + *

+ * Implementations should be registered for {@link ServiceLoader} to load. */ public interface CommandInstaller { + /** + * Called on all registered command installers during initialization of a + * script executor. The implementation should add commands to the provided + * map. The String key is the name used to look up the command in the + * {@link Namespace}. A command might be registered under multiple names. + * + * @param commands map to install commands to + */ public void install(Map commands); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Env.java b/praxiscore-script/src/main/java/org/praxislive/script/Env.java index 0f379b66..62ce696b 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Env.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Env.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,28 +19,69 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; +import org.praxislive.core.Clock; import org.praxislive.core.ControlAddress; import org.praxislive.core.Lookup; import org.praxislive.core.PacketRouter; /** + * Environment context interface passed in to + * {@link StackFrame#process(org.praxislive.script.Env)} and + * {@link InlineCommand#process(org.praxislive.script.Env, org.praxislive.script.Namespace, java.util.List)}. + * An implementation of this interface provides access to various services of + * the script executor required to implement commands. * - * */ public interface Env { + /** + * Name of the context variable that relative component, control and port + * addresses are resolved against. Used and controlled by the {@code @} + * command. + */ public final static String CONTEXT = "_CTXT"; + + /** + * Name of the present working directory variable used to resolve relative + * file paths in various commands. + */ public final static String PWD = "_PWD"; - public abstract Lookup getLookup(); + /** + * Lookup object of the script executor. + * + * @return lookup + */ + public Lookup getLookup(); - public abstract long getTime(); + /** + * Current clock time inside the script executor. Should be used when + * creating calls inside a command, and for any other purpose that the + * current clock time is required. + * + * @see Clock + * @return current clock time + */ + public long getTime(); + /** + * A packet router for sending calls during command execution. + * + * @return packet router + */ public abstract PacketRouter getPacketRouter(); + /** + * The control address of this script executor. Should be used as the from + * address in calls created during command execution. Replies to this + * address will be routed to + * {@link StackFrame#postResponse(org.praxislive.core.Call)} on the active + * command. + * + * @return script executor control address + */ public abstract ControlAddress getAddress(); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java index f03b02be..d700ac47 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,17 +19,26 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; import java.util.List; import org.praxislive.core.Value; /** - * + * Simple subtype of {@link Command} that can be executed and produce a result + * immediately (without child stack frames or making calls). */ public interface InlineCommand extends Command { + /** + * Execute the command with the given environment, namespace and arguments. + * + * @param context current environment + * @param namespace current namespace + * @param args arguments + * @return result + * @throws ExecutionException on error + */ public List process(Env context, Namespace namespace, List args) throws ExecutionException; diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java b/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java index d58f0a21..7028d95f 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,23 +19,58 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; /** - * - * + * A Namespace offers storage of {@link Variable} and {@link Command} by name. + * Namesoaces exist in a hierarchy. Variables and Commands added to this + * namespace usually shadow those from parent namespaces, and are usually + * visible to child namespaces. + *

+ * A Namespace is passed in from the script executor to + * {@link Command#createStackFrame(org.praxislive.script.Namespace, java.util.List)}. */ public interface Namespace { - public abstract Variable getVariable(String id); + /** + * Get the Variable with the given ID, or {@code null} if it doesn't exist + * in this or a parent namespace. + * + * @param id variable ID + * @return named variable, or null if none exists + */ + public Variable getVariable(String id); - public abstract void addVariable(String id, Variable var); + /** + * Add a Variable with the given ID to this Namespace. + * + * @param id variable ID + * @param var variable to add + */ + public void addVariable(String id, Variable var); - public abstract Command getCommand(String id); + /** + * Get the Command with the given ID, or {@code null} if it doesn't exist in + * this or a parent namespace. + * + * @param id command ID + * @return named command, or null if none exists + */ + public Command getCommand(String id); - public abstract void addCommand(String id, Command cmd); + /** + * Add a Command with the given ID to this Namespace. + * + * @param id command ID + * @param cmd command to add + */ + public void addCommand(String id, Command cmd); - public abstract Namespace createChild(); + /** + * Create a child Namespace of this Namespace. + * + * @return child namespace + */ + public Namespace createChild(); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java index 202f52bc..67e01f9a 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,29 +19,123 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; import java.util.List; -import java.util.stream.Collectors; import org.praxislive.core.Call; import org.praxislive.core.Value; /** - * + * A StackFrame used within the script executor pointing to the currently + * executing command. A StackFrame is created for each execution of a Command + * using + * {@link Command#createStackFrame(org.praxislive.script.Namespace, java.util.List)}. + *

+ * A StackFrame should always start off in {@link State#Incomplete}. The script + * executor will call {@link #process(org.praxislive.script.Env)}. During + * processing the StackFrame may evaluate a result, make one or more Calls, or + * create a child StackFrame (eg. from evaluation of another command). + *

+ * If a Call has been made, the state should remain incomplete. Any returning + * call will be passed into {@link #postResponse(org.praxislive.core.Call)}. + *

+ * If a child StackFrame has been returned, the result of its processing will be + * passed into + * {@link #postResponse(org.praxislive.script.StackFrame.State, java.util.List)}. + *

+ * Once a response has been posted, the script executor will check if the + * StackFrame is still marked incomplete. If it is still incomplete, the + * executor will call {@link #process(org.praxislive.script.Env)} again. If it + * has any other state, the state and result will be posted up to the parent + * StackFrame if there is one, or returned as the script result. */ public interface StackFrame { - public static enum State {Incomplete, OK, Error, Break, Continue}; + /** + * Possible states of a StackFrame. All StackFrames start in an incomplete + * state. + */ + public static enum State { + /** + * Incomplete and requires processing. All StackFrames begin in this + * state. + */ + Incomplete, + /** + * Processing finished successfully, and the {@link #result()} is + * available. + */ + OK, + /** + * Processing finished with an error. + */ + Error, + /** + * Special state to control stack unwinding. + * + */ + Break, + /** + * Special state to control stack unwinding. + */ + Continue + }; + + /** + * Get the current state of this StackFrame. + * + * @return current state + */ public State getState(); + /** + * Process the StackFrame. After processing, the StackFrame should have made + * one or more Calls, returned a child StackFrame, or moved out of the + * Incomplete state. + *

+ * Process may be called multiple times if the state is still incomplete + * after this method returns and a response has been posted. + * + * @param env processing environment + * @return child StackFrame or null + */ public StackFrame process(Env env); + /** + * Used by the script executor to post the result of a Call. The StackFrame + * should validate the match ID of the response call against any pending + * calls before processing the call state or arguments. + *

+ * If the state is still incomplete after a response is posted, + * {@link #process(org.praxislive.script.Env)} will be called again. + * + * @param call response call + * @throws IllegalStateException if the state is not incomplete or a call + * response is not expected + */ public void postResponse(Call call); + /** + * Used by the script executor to post the result of a child StackFrame + * returned by {@link #process(org.praxislive.script.Env)}. + *

+ * If the state is still incomplete after a response is posted, + * {@link #process(org.praxislive.script.Env)} will be called again. + * + * @param state the completion state of the child stack frame + * @param args the result of the child stack frame + * @throws IllegalStateException if the state is not incomplete or a child + * stack frame result is not expected + */ public void postResponse(State state, List args); - + + /** + * Access the result of this StackFrame. + * + * @return result + * @throws IllegalStateException if the state is incomplete + */ public List result(); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Variable.java b/praxiscore-script/src/main/java/org/praxislive/script/Variable.java index 682af123..56d987e2 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Variable.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Variable.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,19 +19,34 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script; import org.praxislive.core.Value; /** + * Storage for a value, to be used with {@link Namespace}. Variable + * implementations might be read-only, only settable at certain times, or + * validate their values. * - * */ public interface Variable { - public abstract void setValue(Value value); + /** + * Set the value of this variable. A variable may not be settable + * (read-only) or may me only settable at certain times. A variable might + * validate its value, eg. a particular type, range, etc. + * + * @param value new value, not null + * @throws UnsupportedOperationException if the value cannot be set + * @throws IllegalArgumentException if the value is not valid + */ + public void setValue(Value value); - public abstract Value getValue(); + /** + * Get the current value of the variable. + * + * @return current value + */ + public Value getValue(); } From c11b00729c8033dec6247de8c46f2aa1918ea478 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Thu, 8 Feb 2024 15:05:50 +0000 Subject: [PATCH 2/7] Refactor Script API. Remove script.impl package and move useful classes into the main script API package. Add default methods in Namespace to create variables and constants, hiding the implementation types. Add default method to InlineCommand to create the stack frame and hide the implementation type. Remove ExecutionException. Various other tidy up. --- .../services/CompilerCommandInstaller.java | 2 +- .../hub/net/internal/HubNetCommands.java | 7 +- .../src/main/java/org/praxislive/hub/Hub.java | 4 +- .../src/main/java/module-info.java | 3 - .../{impl => }/AbstractSingleCallFrame.java | 45 +++++++-- .../java/org/praxislive/script/Command.java | 4 +- .../script/{impl => }/ConstantImpl.java | 25 +++-- ...iceImpl.java => DefaultScriptService.java} | 25 ++--- .../praxislive/script/ExecutionException.java | 53 ---------- .../org/praxislive/script/InlineCommand.java | 83 +++++++++++++++- .../java/org/praxislive/script/Namespace.java | 75 +++++++++++++- .../script/{impl => }/ScriptExecutor.java | 46 ++++----- .../script/{impl => }/VariableImpl.java | 27 +++-- .../praxislive/script/ast/CompositeNode.java | 9 +- .../org/praxislive/script/ast/LineNode.java | 15 +-- .../java/org/praxislive/script/ast/Node.java | 13 ++- .../org/praxislive/script/ast/RootNode.java | 5 +- .../praxislive/script/ast/SubcommandNode.java | 5 +- .../praxislive/script/ast/VariableNode.java | 17 +--- .../praxislive/script/commands/ArrayCmds.java | 15 ++- .../praxislive/script/commands/AtCmds.java | 19 ++-- .../script/commands/ConnectionCmds.java | 9 +- .../script/commands/EvalStackFrame.java | 33 ++++--- .../praxislive/script/commands/FileCmds.java | 79 ++++++--------- .../script/commands/ResourceCmds.java | 17 ++-- .../script/commands/ScriptCmds.java | 21 ++-- .../script/commands/VariableCmds.java | 33 +++---- .../script/impl/AbstractInlineCommand.java | 99 ------------------- 28 files changed, 387 insertions(+), 401 deletions(-) rename praxiscore-script/src/main/java/org/praxislive/script/{impl => }/AbstractSingleCallFrame.java (68%) rename praxiscore-script/src/main/java/org/praxislive/script/{impl => }/ConstantImpl.java (69%) rename praxiscore-script/src/main/java/org/praxislive/script/{impl/ScriptServiceImpl.java => DefaultScriptService.java} (87%) delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/ExecutionException.java rename praxiscore-script/src/main/java/org/praxislive/script/{impl => }/ScriptExecutor.java (83%) rename praxiscore-script/src/main/java/org/praxislive/script/{impl => }/VariableImpl.java (66%) delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractInlineCommand.java diff --git a/praxiscore-code-services/src/main/java/org/praxislive/code/services/CompilerCommandInstaller.java b/praxiscore-code-services/src/main/java/org/praxislive/code/services/CompilerCommandInstaller.java index 7992c86e..5eef9894 100644 --- a/praxiscore-code-services/src/main/java/org/praxislive/code/services/CompilerCommandInstaller.java +++ b/praxiscore-code-services/src/main/java/org/praxislive/code/services/CompilerCommandInstaller.java @@ -39,7 +39,7 @@ import org.praxislive.script.Env; import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; -import org.praxislive.script.impl.AbstractSingleCallFrame; +import org.praxislive.script.AbstractSingleCallFrame; /** * diff --git a/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/internal/HubNetCommands.java b/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/internal/HubNetCommands.java index c1725781..2343ebe3 100644 --- a/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/internal/HubNetCommands.java +++ b/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/internal/HubNetCommands.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -32,10 +32,9 @@ import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.StackFrame; -import org.praxislive.script.impl.AbstractSingleCallFrame; +import org.praxislive.script.AbstractSingleCallFrame; /** * @@ -53,7 +52,7 @@ public void install(Map commands) { private final static class ConfigurationCommand implements Command { @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { return new AbstractSingleCallFrame(namespace, args) { @Override protected Call createCall(Env env, List args) throws Exception { diff --git a/praxiscore-hub/src/main/java/org/praxislive/hub/Hub.java b/praxiscore-hub/src/main/java/org/praxislive/hub/Hub.java index 80bcd2f5..4c47d1dc 100644 --- a/praxiscore-hub/src/main/java/org/praxislive/hub/Hub.java +++ b/praxiscore-hub/src/main/java/org/praxislive/hub/Hub.java @@ -43,7 +43,7 @@ import org.praxislive.core.RootHub; import org.praxislive.core.services.Service; import org.praxislive.core.services.Services; -import org.praxislive.script.impl.ScriptServiceImpl; +import org.praxislive.script.DefaultScriptService; /** * Support for configuring and running a {@link RootHub}, along with the @@ -91,7 +91,7 @@ private Hub(Builder builder) { private void extractExtensions(Builder builder, List exts) { exts.add(new DefaultComponentFactoryService()); - exts.add(new ScriptServiceImpl()); + exts.add(new DefaultScriptService()); exts.add(new DefaultTaskService()); exts.addAll(builder.extensions); } diff --git a/praxiscore-script/src/main/java/module-info.java b/praxiscore-script/src/main/java/module-info.java index 4c149f0f..f2ce9404 100644 --- a/praxiscore-script/src/main/java/module-info.java +++ b/praxiscore-script/src/main/java/module-info.java @@ -1,13 +1,10 @@ module org.praxislive.script { - requires java.logging; - requires org.praxislive.core; requires org.praxislive.base; exports org.praxislive.script; - exports org.praxislive.script.impl; uses org.praxislive.script.CommandInstaller; diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractSingleCallFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java similarity index 68% rename from praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractSingleCallFrame.java rename to praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java index 6d76e427..27a1e221 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractSingleCallFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,18 +19,23 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ -package org.praxislive.script.impl; +package org.praxislive.script; import java.util.List; import org.praxislive.core.Call; import org.praxislive.core.Value; +import org.praxislive.core.types.PError; import org.praxislive.core.types.PReference; -import org.praxislive.script.Env; -import org.praxislive.script.Namespace; -import org.praxislive.script.StackFrame; /** - * + * An abstract {@link StackFrame} for commands that need to make a stack frame + * that makes a single call and processes its response. + *

+ * Subclasses should implement + * {@link #createCall(org.praxislive.script.Env, java.util.List)} to create the + * call when required. Subclasses may additionally override + * {@link #processResult(java.util.List)} if the need to alter the return values + * from the call. */ public abstract class AbstractSingleCallFrame implements StackFrame { @@ -81,14 +86,20 @@ public final void postResponse(Call response) { call = null; result = response.args(); if (response.isReply()) { - result = processResult(result); - state = State.OK; + try { + result = processResult(result); + state = State.OK; + } catch (Exception ex) { + result = List.of(PError.of(ex)); + state = State.Error; + } } else { state = State.Error; } } } + @Override public final void postResponse(State state, List args) { throw new IllegalStateException(); } @@ -101,11 +112,27 @@ public final List result() { return result; } + /** + * Create the Call. The call must use {@link Env#getAddress()} as the from + * address, and require a response. + * + * @param env environment for address, time, etc. + * @param args command arguments + * @return call + * @throws Exception on error + */ protected abstract Call createCall(Env env, List args) throws Exception; + /** + * Process the result from the call on a successful response. Unless this + * method is overridden the result of the stack frame will be the result of + * the call. + * + * @param result successful result from call + * @return processed result + */ protected List processResult(List result) { return result; } } - diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Command.java b/praxiscore-script/src/main/java/org/praxislive/script/Command.java index 256f67b3..4e048e46 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Command.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Command.java @@ -38,9 +38,9 @@ public interface Command { * @param namespace current namespace * @param args arguments * @return stack frame to execute command with provided arguments - * @throws ExecutionException if stack frame cannot be created + * @throws Exception if stack frame cannot be created */ public StackFrame createStackFrame(Namespace namespace, List args) - throws ExecutionException; + throws Exception; } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/ConstantImpl.java b/praxiscore-script/src/main/java/org/praxislive/script/ConstantImpl.java similarity index 69% rename from praxiscore-script/src/main/java/org/praxislive/script/impl/ConstantImpl.java rename to praxiscore-script/src/main/java/org/praxislive/script/ConstantImpl.java index 8d832f93..5e5da477 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/ConstantImpl.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ConstantImpl.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,30 +19,37 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ +package org.praxislive.script; - -package org.praxislive.script.impl; - +import java.util.Objects; import org.praxislive.core.Value; -import org.praxislive.script.Variable; /** + * Default constant implementation used by + * {@link Namespace#createConstant(java.lang.String, org.praxislive.core.Value)}. * - * */ -public class ConstantImpl implements Variable { +final class ConstantImpl implements Variable { - Value value; + private final Value value; public ConstantImpl(Value value) { - this.value = value; + this.value = Objects.requireNonNull(value); } + @Override public void setValue(Value value) { throw new UnsupportedOperationException(); } + @Override public Value getValue() { return value; } + + @Override + public String toString() { + return value.toString(); + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptServiceImpl.java b/praxiscore-script/src/main/java/org/praxislive/script/DefaultScriptService.java similarity index 87% rename from praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptServiceImpl.java rename to praxiscore-script/src/main/java/org/praxislive/script/DefaultScriptService.java index 99a567ef..991216b7 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptServiceImpl.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/DefaultScriptService.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2019 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,13 +19,11 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ -package org.praxislive.script.impl; +package org.praxislive.script; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import org.praxislive.base.AbstractRoot; import org.praxislive.core.Call; import org.praxislive.core.Control; @@ -37,22 +35,19 @@ import org.praxislive.core.services.ScriptService; import org.praxislive.core.services.Service; import org.praxislive.core.types.PError; -import org.praxislive.script.Env; /** - * + * A default implementation of {@link ScriptService}. */ -public class ScriptServiceImpl extends AbstractRoot implements RootHub.ServiceProvider { +public final class DefaultScriptService extends AbstractRoot implements RootHub.ServiceProvider { - private static final Logger LOG = Logger.getLogger(ScriptServiceImpl.class.getName()); + private static final System.Logger LOG = System.getLogger(DefaultScriptService.class.getName()); -// private ScriptContext context; -// private ScriptExecutor defaultExecutor; private final Map controls; private final Map contexts; private int exID; - public ScriptServiceImpl() { + public DefaultScriptService() { controls = new HashMap<>(); controls.put(ScriptService.EVAL, new EvalControl()); controls.put(ScriptService.CLEAR, new ClearControl()); @@ -81,7 +76,6 @@ private ScriptExecutor getExecutor(ControlAddress from) { exID++; String id = "_exec_" + exID; EnvImpl env = new EnvImpl(ControlAddress.of(getAddress(), id)); -// ScriptExecutor ex = new ScriptExecutor(env, true); ScriptExecutor ex = new ScriptExecutor(env, from.component()); controls.put(id, new ScriptControl(ex)); contexts.put(from, new ScriptContext(id, ex)); @@ -174,12 +168,12 @@ private EnvImpl(ControlAddress address) { @Override public Lookup getLookup() { - return ScriptServiceImpl.this.getLookup(); + return DefaultScriptService.this.getLookup(); } @Override public long getTime() { - return ScriptServiceImpl.this.getExecutionContext().getTime(); + return DefaultScriptService.this.getExecutionContext().getTime(); } @Override @@ -197,7 +191,8 @@ private class Router implements PacketRouter { @Override public void route(Packet packet) { - LOG.log(Level.FINEST, () -> "Sending Call : ---\n" + packet.toString()); + LOG.log(System.Logger.Level.TRACE, + () -> "Sending Call : ---\n" + packet.toString()); getRouter().route(packet); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ExecutionException.java b/praxiscore-script/src/main/java/org/praxislive/script/ExecutionException.java deleted file mode 100644 index c45e4363..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/ExecutionException.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ - -package org.praxislive.script; - -/** - * - * - */ -public class ExecutionException extends Exception { - - /** - * Creates a new instance of ExecutionException without detail message. - */ - public ExecutionException() { - } - - - /** - * Constructs an instance of ExecutionException with the specified detail message. - * @param msg the detail message. - */ - public ExecutionException(String msg) { - super(msg); - } - - public ExecutionException(Throwable cause) { - super(cause); - } - - public ExecutionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java index d700ac47..e421b0b8 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java @@ -22,7 +22,9 @@ package org.praxislive.script; import java.util.List; +import org.praxislive.core.Call; import org.praxislive.core.Value; +import org.praxislive.core.types.PReference; /** * Simple subtype of {@link Command} that can be executed and produce a result @@ -37,10 +39,87 @@ public interface InlineCommand extends Command { * @param namespace current namespace * @param args arguments * @return result - * @throws ExecutionException on error + * @throws Exception on error */ public List process(Env context, Namespace namespace, List args) - throws ExecutionException; + throws Exception; + /** + * Create a StackFrame to execute the command with the provided Namespace + * and arguments. + *

+ * The default implementation of this method returns an + * {@link InlineStackFrame} with this command, and provided namespace and + * arguments. Implementations can override to further validate the context + * or delegate to another command. + * + * + * @param namespace current namespace + * @param args arguments + * @return stack frame to execute command with provided arguments + * @throws Exception if stack frame cannot be created + */ + @Override + public default InlineStackFrame createStackFrame(Namespace namespace, List args) throws Exception { + return new InlineStackFrame(this, namespace, args); + } + + /** + * A default implementation of StackFrame for use by InlineCommand + * implementations. + */ + public static final class InlineStackFrame implements StackFrame { + + private final InlineCommand command; + private final Namespace namespace; + private final List args; + private State state; + private List result; + + InlineStackFrame(InlineCommand command, Namespace namespace, List args) { + this.command = command; + this.namespace = namespace; + this.args = args; + state = State.Incomplete; + } + + @Override + public State getState() { + return state; + } + + @Override + public StackFrame process(Env env) { + if (state == State.Incomplete) { + try { + result = command.process(env, namespace, args); + state = State.OK; + } catch (Exception ex) { + result = List.of(PReference.of(ex)); + state = State.Error; + } + } + return null; + } + + @Override + public void postResponse(Call call) { + throw new IllegalStateException(); + } + + @Override + public void postResponse(State state, List args) { + throw new IllegalStateException(); + } + + @Override + public List result() { + if (result == null) { + throw new IllegalStateException(); + } + return result; + } + + } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java b/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java index 7028d95f..0e158765 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/Namespace.java @@ -21,9 +21,11 @@ */ package org.praxislive.script; +import org.praxislive.core.Value; + /** * A Namespace offers storage of {@link Variable} and {@link Command} by name. - * Namesoaces exist in a hierarchy. Variables and Commands added to this + * Namespaces exist in a hierarchy. Variables and Commands added to this * namespace usually shadow those from parent namespaces, and are usually * visible to child namespaces. *

@@ -46,6 +48,8 @@ public interface Namespace { * * @param id variable ID * @param var variable to add + * @throws IllegalArgumentException if a variable with that ID already + * exists in this namespace */ public void addVariable(String id, Variable var); @@ -63,6 +67,8 @@ public interface Namespace { * * @param id command ID * @param cmd command to add + * @throws IllegalArgumentException if a command with that ID already exists + * in this namespace */ public void addCommand(String id, Command cmd); @@ -73,4 +79,71 @@ public interface Namespace { */ public Namespace createChild(); + /** + * Create a variable in this namespace with the initial value given. + *

+ * The default implementation of this method creates a new instance of a + * variable implementation, and calls + * {@link #addVariable(java.lang.String, org.praxislive.script.Variable)} to + * register it. + * + * @param id variable ID + * @param value initial value + * @return created variable + * @throws IllegalArgumentException if a variable with that name already + * exists in this namespace + */ + public default Variable createVariable(String id, Value value) { + Variable v = new VariableImpl(value); + addVariable(id, v); + return v; + } + + /** + * Get the variable with the given ID from this namespace or a parent + * namespace, creating and initializing a variable with the provided default + * value if none exists. + *

+ * The default implementation of this method calls + * {@link #getVariable(java.lang.String)} to find a registered variable, and + * if that method returns {@link null} delegates to + * {@link #createVariable(java.lang.String, org.praxislive.core.Value)}. + * + * @param id variable ID + * @param defaultValue default initial value + * @return created variable + */ + public default Variable getOrCreateVariable(String id, Value defaultValue) { + Variable v = getVariable(id); + if (v == null) { + return createVariable(id, defaultValue); + } else { + return v; + } + } + + /** + * Create a constant in this namespace with the initial value given. The + * constant is guaranteed to always return {@code value} from + * {@link Variable#getValue()}, and to always throw + * {@link UnsupportedOperationException} on any call to + * {@link Variable#setValue(org.praxislive.core.Value)}. + *

+ * The default implementation of this method creates a new instance of a + * constant variable implementation, and calls + * {@link #addVariable(java.lang.String, org.praxislive.script.Variable)} to + * register it. + * + * @param id constant name + * @param value constant value + * @return created constant + * @throws IllegalArgumentException if a variable with that name already + * exists in this namespace + */ + public default Variable createConstant(String id, Value value) { + Variable c = new ConstantImpl(value); + addVariable(id, c); + return c; + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptExecutor.java b/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java similarity index 83% rename from praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptExecutor.java rename to praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java index 929338ee..4f9cb8b2 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/ScriptExecutor.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,33 +19,29 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ -package org.praxislive.script.impl; +package org.praxislive.script; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; -import java.util.logging.Logger; import org.praxislive.core.Call; import org.praxislive.core.ComponentAddress; import org.praxislive.core.Lookup; import org.praxislive.core.types.PError; -import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; -import org.praxislive.script.Env; -import org.praxislive.script.Namespace; -import org.praxislive.script.StackFrame; -import org.praxislive.script.Variable; import org.praxislive.script.commands.CoreCommandsInstaller; import org.praxislive.script.commands.ScriptCmds; +import static java.lang.System.Logger.Level; + /** * */ -public class ScriptExecutor { +class ScriptExecutor { + + private static final System.Logger log = System.getLogger(ScriptExecutor.class.getName()); - private final static Logger log = Logger.getLogger(ScriptExecutor.class.getName()); private List stack; private Queue queue; private Env env; @@ -55,8 +51,8 @@ public class ScriptExecutor { public ScriptExecutor(Env env, boolean inline) { this.env = env; - stack = new LinkedList(); - queue = new LinkedList(); + stack = new LinkedList<>(); + queue = new LinkedList<>(); if (inline) { evaluator = ScriptCmds.INLINE_EVAL; } else { @@ -72,7 +68,7 @@ public ScriptExecutor(Env context, final ComponentAddress ctxt) { } private void buildCommandMap() { - commandMap = new HashMap(); + commandMap = new HashMap<>(); CommandInstaller installer = new CoreCommandsInstaller(); installer.install(commandMap); Lookup.SYSTEM.findAll(CommandInstaller.class).forEach(cmds -> cmds.install(commandMap)); @@ -96,7 +92,7 @@ public void flushEvalQueue() { } public void processScriptCall(Call call) { - log.finest("processScriptCall - received :\n" + call); + log.log(Level.TRACE, () -> "processScriptCall - received :\n" + call); if (!stack.isEmpty()) { stack.get(0).postResponse(call); processStack(); @@ -109,14 +105,14 @@ public void processScriptCall(Call call) { private void processStack() { while (!stack.isEmpty()) { StackFrame current = stack.get(0); - log.finest("Processing stack : " + current.getClass() + log.log(Level.TRACE, () -> "Processing stack : " + current.getClass() + "\n Stack Size : " + stack.size()); // if incomplete do round of processing if (current.getState() == StackFrame.State.Incomplete) { StackFrame child = current.process(env); if (child != null) { - log.finest("Pushing to stack" + child.getClass()); + log.log(Level.TRACE, () -> "Pushing to stack" + child.getClass()); stack.add(0, child); continue; } @@ -128,20 +124,19 @@ private void processStack() { return; } else { var args = current.result(); - log.finest("Stack frame complete : " + current.getClass() + log.log(Level.TRACE, () -> "Stack frame complete : " + current.getClass() + "\n Result : " + args + "\n Stack Size : " + stack.size()); stack.remove(0); if (!stack.isEmpty()) { - log.finest("Posting result up stack"); + log.log(Level.TRACE, "Posting result up stack"); stack.get(0).postResponse(state, args); - continue; } else { Call call = queue.poll(); if (state == StackFrame.State.OK) { - log.finest("Sending OK return call"); + log.log(Level.TRACE, "Sending OK return call"); call = call.reply(args); } else { - log.finest("Sending Error return call"); + log.log(Level.TRACE, "Sending Error return call"); call = call.error(args); } env.getPacketRouter().route(call); @@ -177,9 +172,10 @@ private NS() { private NS(NS parent) { this.parent = parent; - variables = new HashMap(); + variables = new HashMap<>(); } + @Override public Variable getVariable(String id) { Variable var = variables.get(id); if (var == null && parent != null) { @@ -189,6 +185,7 @@ public Variable getVariable(String id) { } } + @Override public void addVariable(String id, Variable var) { if (variables.containsKey(id)) { throw new IllegalArgumentException(); @@ -196,14 +193,17 @@ public void addVariable(String id, Variable var) { variables.put(id, var); } + @Override public Command getCommand(String id) { return commandMap.get(id); } + @Override public void addCommand(String id, Command cmd) { throw new UnsupportedOperationException(); } + @Override public Namespace createChild() { return new NS(this); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/VariableImpl.java b/praxiscore-script/src/main/java/org/praxislive/script/VariableImpl.java similarity index 66% rename from praxiscore-script/src/main/java/org/praxislive/script/impl/VariableImpl.java rename to praxiscore-script/src/main/java/org/praxislive/script/VariableImpl.java index df2314c1..346ee6de 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/VariableImpl.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/VariableImpl.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,30 +19,37 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ +package org.praxislive.script; - -package org.praxislive.script.impl; - +import java.util.Objects; import org.praxislive.core.Value; -import org.praxislive.script.Variable; /** + * Default variable implementation used by + * {@link Namespace#createVariable(java.lang.String, org.praxislive.core.Value)}. * - * */ -public class VariableImpl implements Variable { +final class VariableImpl implements Variable { - Value value; + private Value value; public VariableImpl(Value value) { - this.value = value; + this.value = Objects.requireNonNull(value); } + @Override public void setValue(Value value) { - this.value = value; + this.value = Objects.requireNonNull(value); } + @Override public Value getValue() { return value; } + + @Override + public String toString() { + return value.toString(); + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java index 8429f7d2..50f59598 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java @@ -24,7 +24,6 @@ import java.util.List; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; /** @@ -79,7 +78,7 @@ public boolean isDone() { @Override public void writeNextCommand(List args) - throws ExecutionException { + throws Exception { if (namespace == null) { throw new IllegalStateException(); } @@ -91,12 +90,12 @@ public void writeNextCommand(List args) } protected abstract void writeThisNextCommand(List args) - throws ExecutionException; + throws Exception; @Override public void postResponse(List args) - throws ExecutionException { + throws Exception { if (namespace == null) { throw new IllegalStateException(); } @@ -108,7 +107,7 @@ public void postResponse(List args) } protected abstract void postThisResponse(List args) - throws ExecutionException; + throws Exception; @Override public void reset() { diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/LineNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/LineNode.java index 61e90128..99d20561 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/LineNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/LineNode.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -22,18 +22,14 @@ package org.praxislive.script.ast; import java.util.List; -import java.util.logging.Logger; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; /** * - * + * */ public class LineNode extends CompositeNode { - private final static Logger log = Logger.getLogger(LineNode.class.getName()); - private Value[] result; public LineNode(List children) { @@ -46,13 +42,12 @@ protected boolean isThisDone() { } @Override - protected void writeThisNextCommand(List args) - throws ExecutionException { + protected void writeThisNextCommand(List args) + throws Exception { if (result == null) { for (Node child : getChildren()) { child.writeResult(args); } - log.finest("LineNode writing command : " + args.toString()); } else { throw new IllegalStateException(); } @@ -62,7 +57,7 @@ protected void writeThisNextCommand(List args) @Override protected void postThisResponse(List args) { if (result == null) { - result = args.toArray(new Value[args.size()]); + result = args.toArray(Value[]::new); } else { throw new IllegalStateException(); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/Node.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/Node.java index a896bf00..8a96f691 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/Node.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/Node.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -23,7 +23,6 @@ import java.util.List; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; /** @@ -40,17 +39,17 @@ public boolean isDone() { } public void writeNextCommand(List args) - throws ExecutionException { - throw new ExecutionException(); + throws Exception { + throw new Exception(); } public void postResponse(List args) - throws ExecutionException { - throw new ExecutionException(); + throws Exception { + throw new Exception(); } public abstract void writeResult(List args) - throws ExecutionException; + throws Exception; public void reset() { } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java index 72c0ffcd..afd29eab 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -24,7 +24,6 @@ import java.util.List; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; /** * @@ -52,7 +51,7 @@ protected void postThisResponse(List args) { } @Override - public void writeResult(List args) throws ExecutionException { + public void writeResult(List args) throws Exception { Node[] children = getChildren(); if (children.length > 0) { children[children.length - 1].writeResult(args); diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java index d0620029..6fc9660a 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -23,7 +23,6 @@ import java.util.List; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; /** * @@ -52,7 +51,7 @@ protected void postThisResponse(List args) { @Override public void writeResult(List args) - throws ExecutionException { + throws Exception { Node[] children = getChildren(); children[children.length - 1].writeResult(args); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/VariableNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/VariableNode.java index f3e09e6a..d6d80b7c 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/VariableNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/VariableNode.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,24 +19,19 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script.ast; import java.util.List; -import java.util.logging.Logger; import org.praxislive.core.Value; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.Variable; /** * - * + * */ public class VariableNode extends Node { - private final static Logger log = Logger.getLogger(VariableNode.class.getName()); - private String id; private Namespace namespace; @@ -45,7 +40,6 @@ public VariableNode(String id) { throw new NullPointerException(); } this.id = id; - log.finest("Created VariableNode with id : " + id); } @Override @@ -61,18 +55,15 @@ public void reset() { } @Override - public void writeResult(List args) throws ExecutionException { + public void writeResult(List args) throws Exception { if (namespace == null) { throw new IllegalStateException(); } Variable var = namespace.getVariable(id); if (var == null) { - log.finest("VARIABLE NODE : Can't find variable " + id + " in namespace " + namespace); - throw new ExecutionException(); + throw new Exception("Can't find variable " + id + " in namespace " + namespace); } args.add(var.getValue()); } - - } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java index fd9b8a37..04083c70 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,30 +19,29 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script.commands; import java.util.List; -import org.praxislive.script.impl.AbstractInlineCommand; import java.util.Map; import org.praxislive.core.Value; import org.praxislive.core.types.PArray; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; +import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; /** * */ public class ArrayCmds implements CommandInstaller { - + private final static ArrayCmds INSTANCE = new ArrayCmds(); private final static Array ARRAY = new Array(); - private ArrayCmds() {} + private ArrayCmds() { + } public void install(Map commands) { commands.put("array", ARRAY); @@ -52,10 +51,10 @@ public final static ArrayCmds getInstance() { return INSTANCE; } - private static class Array extends AbstractInlineCommand { + private static class Array implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { PArray ar = args.stream().collect(PArray.collector()); return List.of(ar); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java index 03b3e4c7..f6a9ee83 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -22,11 +22,9 @@ package org.praxislive.script.commands; import java.util.List; -import org.praxislive.script.impl.AbstractSingleCallFrame; -import org.praxislive.script.impl.VariableImpl; +import org.praxislive.script.AbstractSingleCallFrame; import java.util.Map; import org.praxislive.core.Value; -import org.praxislive.core.ValueFormatException; import org.praxislive.core.Call; import org.praxislive.core.ComponentAddress; import org.praxislive.core.ComponentType; @@ -36,12 +34,10 @@ import org.praxislive.core.services.ServiceUnavailableException; import org.praxislive.core.services.Services; import org.praxislive.core.types.PError; -import org.praxislive.core.types.PReference; import org.praxislive.core.types.PString; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.StackFrame; @@ -70,10 +66,10 @@ public final static AtCmds getInstance() { private static class At implements Command { @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { if (args.size() < 2) { - throw new ExecutionException(); + throw new Exception(); } try { @@ -96,7 +92,7 @@ public StackFrame createStackFrame(Namespace namespace, List args) throws return new AtStackFrame(namespace, ctxt, null, arg); } } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } @@ -104,7 +100,8 @@ public StackFrame createStackFrame(Namespace namespace, List args) throws private static class NotAt implements Command { - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + @Override + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { return new NotAtStackFrame(namespace, args); } @@ -173,7 +170,7 @@ public StackFrame process(Env env) { stage++; try { Namespace child = namespace.createChild(); - child.addVariable(Env.CONTEXT, new VariableImpl(ctxt)); + child.createConstant(Env.CONTEXT, ctxt); return ScriptCmds.INLINE_EVAL.createStackFrame(child, List.of(script)); } catch (Exception ex) { state = State.Error; diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java index 41b4adc5..52c5c62f 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -22,7 +22,7 @@ package org.praxislive.script.commands; import java.util.List; -import org.praxislive.script.impl.AbstractSingleCallFrame; +import org.praxislive.script.AbstractSingleCallFrame; import java.util.Map; import org.praxislive.core.Call; import org.praxislive.core.ComponentAddress; @@ -34,7 +34,6 @@ import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.StackFrame; @@ -64,7 +63,7 @@ public final static ConnectionCmds getInstance() { private static class Connect implements Command { @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { return new ConnectionStackFrame(namespace, args, true); } } @@ -72,7 +71,7 @@ public StackFrame createStackFrame(Namespace namespace, List args) throws private static class Disconnect implements Command { @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { return new ConnectionStackFrame(namespace, args, false); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java index e0f2a875..4de43e5c 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -23,24 +23,25 @@ import java.util.LinkedList; import java.util.List; -import java.util.logging.Logger; import org.praxislive.core.Value; import org.praxislive.core.Call; import org.praxislive.core.ControlAddress; import org.praxislive.core.types.PError; import org.praxislive.script.Command; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.StackFrame; import org.praxislive.script.ast.RootNode; +import static java.lang.System.Logger.Level; + /** * */ public class EvalStackFrame implements StackFrame { - private final static Logger log = Logger.getLogger(EvalStackFrame.class.getName()); + private static final System.Logger log = System.getLogger(EvalStackFrame.class.getName()); + private Namespace namespace; private RootNode rootNode; private State state; @@ -94,16 +95,16 @@ public void postResponse(Call call) { if (pending != null && pending.matchID() == call.matchID()) { pending = null; if (call.isReply()) { - log.finest("EvalStackFrame - Received valid Return call : \n" + call); + log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Return call : \n" + call); postResponse(call.args()); } else { - log.finest("EvalStackFrame - Received valid Error call : \n" + call); + log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Error call : \n" + call); this.state = State.Error; this.result = call.args(); } doProcess = true; } else { - log.finest("EvalStackFrame - Received invalid call : \n" + call); + log.log(Level.TRACE, () -> "EvalStackFrame - Received invalid call : \n" + call); } } @@ -143,12 +144,12 @@ private void postResponse(List args) { argList.clear(); argList.addAll(args); rootNode.postResponse(argList); - } catch (ExecutionException ex) { + } catch (Exception ex) { state = State.Error;//@TODO proper error reporting } } - private void processResultFromNode() throws ExecutionException { + private void processResultFromNode() throws Exception { argList.clear(); rootNode.writeResult(argList); result = List.copyOf(argList); @@ -157,12 +158,12 @@ private void processResultFromNode() throws ExecutionException { } private StackFrame processNextCommand(Env context) - throws ExecutionException { + throws Exception { argList.clear(); rootNode.writeNextCommand(argList); if (argList.size() < 1) { - throw new ExecutionException(); + throw new Exception(); } Value cmdArg = argList.get(0); if (cmdArg instanceof ControlAddress) { @@ -171,7 +172,7 @@ private StackFrame processNextCommand(Env context) } String cmdStr = cmdArg.toString(); if (cmdStr.isEmpty()) { - throw new ExecutionException(); + throw new Exception(); } Command cmd = namespace.getCommand(cmdStr); if (cmd != null) { @@ -183,17 +184,17 @@ private StackFrame processNextCommand(Env context) return null; } - throw new ExecutionException(); + throw new Exception(); } private void routeCall(Env context, List argList) - throws ExecutionException { + throws Exception { ControlAddress ad = ControlAddress.from(argList.get(0)) - .orElseThrow(ExecutionException::new); + .orElseThrow(Exception::new); argList.remove(0); Call call = Call.create(ad, context.getAddress(), context.getTime(), List.copyOf(argList)); - log.finest("Sending Call" + call); + log.log(Level.TRACE, () -> "Sending Call" + call); pending = call; context.getPacketRouter().route(call); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java index 1ebb6eef..a6cea0a1 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -21,7 +21,6 @@ */ package org.praxislive.script.commands; -import org.praxislive.script.impl.AbstractInlineCommand; import java.io.File; import java.io.IOException; import java.net.URI; @@ -36,10 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; -import org.praxislive.core.ValueFormatException; import org.praxislive.core.Value; import org.praxislive.core.types.PArray; import org.praxislive.core.types.PResource; @@ -47,10 +43,8 @@ import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; +import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; -import org.praxislive.script.Variable; -import org.praxislive.script.impl.VariableImpl; /** * @@ -141,60 +135,57 @@ private static List listFiles(Namespace namespace, String path, String glo } } - private static class FileCmd extends AbstractInlineCommand { + private static class FileCmd implements InlineCommand { @Override - public List process(Env env, Namespace namespace, List args) throws ExecutionException { + public List process(Env env, Namespace namespace, List args) throws Exception { if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } try { return List.of(PResource.of( resolve(namespace, args.get(0).toString()) )); } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } } - private static class FileListCmd extends AbstractInlineCommand { + private static class FileListCmd implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { if (args.size() > 1) { - throw new ExecutionException(); + throw new Exception(); } try { List list; - /*if (args.getSize() == 2) { - list = listFiles(namespace, args.get(0).toString(), args.get(1).toString()); - } else*/ if (args.size() == 1) { list = listFiles(namespace, args.get(0).toString()); } else { list = listFiles(namespace); } - + List ret = list.stream() .map(path -> PResource.of(path.toUri())) .collect(Collectors.toList()); - + return List.of(PArray.of(ret)); } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } } - private static class FileNamesCmd extends AbstractInlineCommand { + private static class FileNamesCmd implements InlineCommand { @Override - public List process(Env env, Namespace namespace, List args) throws ExecutionException { + public List process(Env env, Namespace namespace, List args) throws Exception { if (args.size() > 1) { - throw new ExecutionException(); + throw new Exception(); } try { List list; @@ -206,57 +197,51 @@ public List process(Env env, Namespace namespace, List args) throw } else { list = listFiles(namespace); } - + List ret = list.stream() .map(path -> PString.of(path.getFileName())) .collect(Collectors.toList()); - + return List.of(PArray.of(ret)); } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } } - - private static class CdCmd extends AbstractInlineCommand { + + private static class CdCmd implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } try { URI uri = resolve(namespace, args.get(0).toString()); if ("file".equals(uri.getScheme())) { File d = new File(uri); if (!d.isDirectory()) { - throw new ExecutionException("Not a valid directory"); + throw new Exception("Not a valid directory"); } } PResource dir = PResource.of(uri); - Variable pwd = namespace.getVariable(Env.PWD); - if (pwd != null) { - pwd.setValue(dir); - } else { - pwd = new VariableImpl(dir); - namespace.addVariable(Env.PWD, pwd); - } + namespace.getOrCreateVariable(Env.PWD, dir).setValue(dir); return List.of(dir); } catch (URISyntaxException ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } - + } - - private static class PwdCmd extends AbstractInlineCommand { + + private static class PwdCmd implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { return List.of(PResource.of(getPWD(namespace))); } - - } - + + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java index 4cdf2555..7f91a35b 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -21,7 +21,6 @@ */ package org.praxislive.script.commands; -import org.praxislive.script.impl.AbstractInlineCommand; import java.io.File; import java.util.List; import java.util.Map; @@ -31,7 +30,7 @@ import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; +import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; /** @@ -40,7 +39,7 @@ public class ResourceCmds implements CommandInstaller { private final static ResourceCmds instance = new ResourceCmds(); - + private final static Command LOAD = new LoadCmd(); private ResourceCmds() { @@ -55,21 +54,19 @@ public static ResourceCmds getInstance() { return instance; } - - - private static class LoadCmd extends AbstractInlineCommand { + private static class LoadCmd implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } try { File f = new File(PResource.from(args.get(0)).orElseThrow().value()); String s = Utils.loadStringFromFile(f); return List.of(PString.of(s)); } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java index 1e20157a..c5c168cc 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -29,7 +29,6 @@ import org.praxislive.core.types.PResource; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; -import org.praxislive.script.ExecutionException; import org.praxislive.script.Namespace; import org.praxislive.script.StackFrame; import org.praxislive.script.ast.RootNode; @@ -62,16 +61,16 @@ private static class Eval implements Command { @Override public StackFrame createStackFrame(Namespace namespace, List args) - throws ExecutionException { + throws Exception { if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } String script = args.get(0).toString(); try { RootNode astRoot = ScriptParser.getInstance().parse(script); return new EvalStackFrame(namespace.createChild(), astRoot); } catch (InvalidSyntaxException ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } } @@ -80,16 +79,16 @@ private static class InlineEval implements Command { @Override public StackFrame createStackFrame(Namespace namespace, List args) - throws ExecutionException { + throws Exception { if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } String script = args.get(0).toString(); try { RootNode astRoot = ScriptParser.getInstance().parse(script); return new EvalStackFrame(namespace, astRoot); } catch (InvalidSyntaxException ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } } @@ -97,10 +96,10 @@ public StackFrame createStackFrame(Namespace namespace, List args) private static class Include implements Command { @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { // @TODO - should load in background - call to if (args.size() != 1) { - throw new ExecutionException(); + throw new Exception(); } try { PResource res = PResource.from(args.get(0)).orElseThrow(); @@ -109,7 +108,7 @@ public StackFrame createStackFrame(Namespace namespace, List args) throws RootNode astRoot = ScriptParser.getInstance().parse(script); return new EvalStackFrame(namespace.createChild(), astRoot); } catch (Exception ex) { - throw new ExecutionException(ex); + throw new Exception(ex); } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java index c5314687..fe44807e 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2020 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -22,32 +22,28 @@ package org.praxislive.script.commands; import java.util.List; -import org.praxislive.script.impl.AbstractInlineCommand; -import org.praxislive.script.impl.VariableImpl; import java.util.Map; -import java.util.logging.Logger; import org.praxislive.core.Value; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; +import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; import org.praxislive.script.Variable; +import static java.lang.System.Logger.Level; + /** * */ public class VariableCmds implements CommandInstaller { - private final static VariableCmds instance = new VariableCmds(); - - private final static Command SET = new Set(); - + private static final VariableCmds instance = new VariableCmds(); + private static final Command SET = new Set(); + private final static System.Logger log = System.getLogger(VariableCmds.class.getName()); - private final static Logger log = Logger.getLogger(VariableCmds.class.getName()); - - - private VariableCmds() {} + private VariableCmds() { + } @Override public void install(Map commands) { @@ -58,12 +54,12 @@ public static VariableCmds getInstance() { return instance; } - private static class Set extends AbstractInlineCommand { + private static class Set implements InlineCommand { @Override - public List process(Env context, Namespace namespace, List args) throws ExecutionException { + public List process(Env context, Namespace namespace, List args) throws Exception { if (args.size() != 2) { - throw new ExecutionException(); + throw new Exception(); } String varName = args.get(0).toString(); Value val = args.get(1); @@ -71,9 +67,8 @@ public List process(Env context, Namespace namespace, List args) t if (var != null) { var.setValue(val); } else { - log.finest("SET COMMAND : Adding variable " + varName + " to namespace " + namespace); - var = new VariableImpl(val); - namespace.addVariable(varName, var); + log.log(Level.TRACE, () -> "SET COMMAND : Adding variable " + varName + " to namespace " + namespace); + namespace.createVariable(varName, val); } return List.of(val); diff --git a/praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractInlineCommand.java b/praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractInlineCommand.java deleted file mode 100644 index 3bc69fda..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/impl/AbstractInlineCommand.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ - -package org.praxislive.script.impl; - -import java.util.List; -import org.praxislive.core.Call; -import org.praxislive.core.Value; -import org.praxislive.core.types.PReference; -import org.praxislive.script.Env; -import org.praxislive.script.ExecutionException; -import org.praxislive.script.InlineCommand; -import org.praxislive.script.Namespace; -import org.praxislive.script.StackFrame; - -/** - * - */ -public abstract class AbstractInlineCommand implements InlineCommand { - - - - @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws ExecutionException { - return new InlineStackFrame(namespace, args); - } - - private class InlineStackFrame implements StackFrame { - - private final Namespace namespace; - private final List args; - private State state; - private List result; - - private InlineStackFrame(Namespace namespace, List args) { - this.namespace = namespace; - this.args = args; - state = State.Incomplete; - } - - @Override - public State getState() { - return state; - } - - @Override - public StackFrame process(Env env) { - if (state == State.Incomplete) { - try { - result = AbstractInlineCommand.this.process(env, namespace, args); - state = State.OK; - } catch (ExecutionException ex) { - result = List.of(PReference.of(ex)); - state = State.Error; - } - } - return null; - } - - @Override - public void postResponse(Call call) { - throw new IllegalStateException(); - } - - @Override - public void postResponse(State state, List args) { - throw new IllegalStateException(); - } - - @Override - public List result() { - if (result == null) { - throw new IllegalStateException(); - } - return result; - } - - } - -} From 926a226d4c22a6de9cfd1a757cfcf5618493512f Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Wed, 28 Feb 2024 10:51:59 +0000 Subject: [PATCH 3/7] Add echo command --- .../{VariableCmds.java => BaseCmds.java} | 33 ++++++++++++++----- .../commands/CoreCommandsInstaller.java | 3 +- .../script/commands/EvalStackFrame.java | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) rename praxiscore-script/src/main/java/org/praxislive/script/commands/{VariableCmds.java => BaseCmds.java} (68%) diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java similarity index 68% rename from praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java rename to praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java index fe44807e..86483724 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/VariableCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java @@ -23,7 +23,9 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.praxislive.core.Value; +import org.praxislive.core.types.PString; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; @@ -36,22 +38,24 @@ /** * */ -public class VariableCmds implements CommandInstaller { +public class BaseCmds implements CommandInstaller { - private static final VariableCmds instance = new VariableCmds(); - private static final Command SET = new Set(); - private final static System.Logger log = System.getLogger(VariableCmds.class.getName()); + private static final System.Logger LOG = System.getLogger(BaseCmds.class.getName()); + private static final Set SET = new Set(); + private static final Echo ECHO = new Echo(); + private static final BaseCmds INSTANCE = new BaseCmds(); - private VariableCmds() { + private BaseCmds() { } @Override public void install(Map commands) { commands.put("set", SET); + commands.put("echo", ECHO); } - public static VariableCmds getInstance() { - return instance; + public static BaseCmds getInstance() { + return INSTANCE; } private static class Set implements InlineCommand { @@ -67,11 +71,24 @@ public List process(Env context, Namespace namespace, List args) t if (var != null) { var.setValue(val); } else { - log.log(Level.TRACE, () -> "SET COMMAND : Adding variable " + varName + " to namespace " + namespace); + LOG.log(Level.TRACE, () -> "SET COMMAND : Adding variable " + varName + " to namespace " + namespace); namespace.createVariable(varName, val); } return List.of(val); } } + + private static class Echo implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + return List.of(PString.of( + args.stream() + .map(Value::toString) + .collect(Collectors.joining()))); + } + + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java index 89e38d74..1726404b 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java @@ -32,14 +32,15 @@ */ public class CoreCommandsInstaller implements CommandInstaller { + @Override public void install(Map commands) { + BaseCmds.getInstance().install(commands); ArrayCmds.getInstance().install(commands); AtCmds.getInstance().install(commands); ConnectionCmds.getInstance().install(commands); FileCmds.getInstance().install(commands); ResourceCmds.getInstance().install(commands); ScriptCmds.getInstance().install(commands); - VariableCmds.getInstance().install(commands); } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java index 4de43e5c..9769f552 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java @@ -38,7 +38,7 @@ /** * */ -public class EvalStackFrame implements StackFrame { +class EvalStackFrame implements StackFrame { private static final System.Logger log = System.getLogger(EvalStackFrame.class.getName()); From 88b70df5de229fc18b85b6cd9f24f572566deb46 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Mon, 22 Apr 2024 16:07:23 +0100 Subject: [PATCH 4/7] Add ScriptStackFrame API class with inline and error trapping support. Move and rewrite EvalStackFrame as ScriptStackFrame API. Add ability to trap errors and filter allowed commands. Add flags to eval command for --inline, --trap-errors and --allowed-commands. Add tests for script execution. --- pom.xml | 4 +- praxiscore-script/pom.xml | 14 +- .../org/praxislive/script/ScriptExecutor.java | 42 +- .../praxislive/script/ScriptStackFrame.java | 400 ++++++++++++++++++ .../praxislive/script/ast/CompositeNode.java | 47 +- .../org/praxislive/script/ast/RootNode.java | 17 +- .../praxislive/script/ast/SubcommandNode.java | 6 +- .../praxislive/script/commands/AtCmds.java | 3 +- .../script/commands/EvalStackFrame.java | 202 --------- .../script/commands/ScriptCmds.java | 69 +-- .../script/DefaultScriptServiceTest.java | 308 ++++++++++++++ .../script/ast/AddressNodeTest.java | 102 ++--- 12 files changed, 852 insertions(+), 362 deletions(-) create mode 100644 praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java create mode 100644 praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java diff --git a/pom.xml b/pom.xml index 848b3615..79763f1c 100644 --- a/pom.xml +++ b/pom.xml @@ -144,9 +144,9 @@ org.apache.maven.plugins maven-compiler-plugin - 3.11.0 + 3.12.1 - 17 + 21 diff --git a/praxiscore-script/pom.xml b/praxiscore-script/pom.xml index 106d1bbf..11629de7 100644 --- a/praxiscore-script/pom.xml +++ b/praxiscore-script/pom.xml @@ -12,8 +12,18 @@ - org.junit.vintage - junit-vintage-engine + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.jupiter + junit-jupiter-engine test diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java b/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java index 4f9cb8b2..f88f0192 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ScriptExecutor.java @@ -31,7 +31,6 @@ import org.praxislive.core.Lookup; import org.praxislive.core.types.PError; import org.praxislive.script.commands.CoreCommandsInstaller; -import org.praxislive.script.commands.ScriptCmds; import static java.lang.System.Logger.Level; @@ -42,36 +41,27 @@ class ScriptExecutor { private static final System.Logger log = System.getLogger(ScriptExecutor.class.getName()); - private List stack; - private Queue queue; - private Env env; - private Command evaluator; - private Map commandMap; - private Namespace rootNS; + private final List stack; + private final Queue queue; + private final Env env; + private final Map commandMap; + private final Namespace rootNS; - public ScriptExecutor(Env env, boolean inline) { - this.env = env; + ScriptExecutor(Env context, final ComponentAddress ctxt) { + this.env = context; stack = new LinkedList<>(); queue = new LinkedList<>(); - if (inline) { - evaluator = ScriptCmds.INLINE_EVAL; - } else { - evaluator = ScriptCmds.EVAL; - } + commandMap = buildCommandMap(); rootNS = new NS(); - buildCommandMap(); - } - - public ScriptExecutor(Env context, final ComponentAddress ctxt) { - this(context, true); rootNS.addVariable(Env.CONTEXT, new ConstantImpl(ctxt)); } - private void buildCommandMap() { - commandMap = new HashMap<>(); + private Map buildCommandMap() { + Map map = new HashMap<>(); CommandInstaller installer = new CoreCommandsInstaller(); - installer.install(commandMap); - Lookup.SYSTEM.findAll(CommandInstaller.class).forEach(cmds -> cmds.install(commandMap)); + installer.install(map); + Lookup.SYSTEM.findAll(CommandInstaller.class).forEach(cmds -> cmds.install(map)); + return map; } public void queueEvalCall(Call call) { @@ -150,7 +140,11 @@ private void checkAndStartEval() { Call call = queue.peek(); var args = call.args(); try { - stack.add(0, evaluator.createStackFrame(rootNS, args)); + var script = args.get(0).toString(); + var stackFrame = ScriptStackFrame.forScript(rootNS, script) + .inline() + .build(); + stack.add(0, stackFrame); processStack(); break; } catch (Exception ex) { diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java new file mode 100644 index 00000000..666b477c --- /dev/null +++ b/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java @@ -0,0 +1,400 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.praxislive.core.Value; +import org.praxislive.core.Call; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.syntax.InvalidSyntaxException; +import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PError; +import org.praxislive.core.types.PString; +import org.praxislive.script.ast.RootNode; +import org.praxislive.script.ast.ScriptParser; + +import static java.lang.System.Logger.Level; + +/** + * A stackframe implementation that supports parsing and running of Pcl scripts. + */ +public final class ScriptStackFrame implements StackFrame { + + private static final String TRAP = "_TRAP"; + + private static final System.Logger log = System.getLogger(ScriptStackFrame.class.getName()); + + private final Namespace namespace; + private final RootNode rootNode; + private final boolean trapErrors; + private final List scratchList; + + private State state; + private String activeCommand; + private Call pending; + private List result; + private boolean doProcess; + + private ScriptStackFrame(Namespace namespace, + RootNode rootNode, + boolean trapErrors) { + this.namespace = namespace; + this.rootNode = rootNode; + this.state = State.Incomplete; + this.trapErrors = trapErrors; + this.scratchList = new ArrayList<>(); + rootNode.reset(); + if (trapErrors) { + namespace.createVariable(TRAP, PArray.EMPTY); + } + rootNode.init(namespace); + doProcess = true; + } + + @Override + public State getState() { + return state; + } + + @Override + public StackFrame process(Env context) { + if (state != State.Incomplete) { + throw new IllegalStateException(); + } + if (!doProcess) { + return null; + } + while (!rootNode.isDone() && state == State.Incomplete) { + try { + return processNextCommand(context); + } catch (Exception ex) { + postError(List.of(PError.of(ex))); + } + } + if (rootNode.isDone() && state == State.Incomplete) { + + try { + processResultFromNode(); + } catch (Exception ex) { + result = List.of(PError.of(ex)); + state = State.Error; + } + } + return null; + } + + @Override + public void postResponse(Call call) { + if (pending != null && pending.matchID() == call.matchID()) { + pending = null; + if (call.isReply()) { + log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Return call : \n" + call); + postResponse(call.args()); + } else { + log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Error call : \n" + call); + postError(call.args()); + } + doProcess = true; + } else { + log.log(Level.TRACE, () -> "EvalStackFrame - Received invalid call : \n" + call); + } + + } + + @Override + public void postResponse(State state, List args) { + if (this.state != State.Incomplete) { + throw new IllegalStateException(); + } + switch (state) { + case Incomplete -> + throw new IllegalArgumentException(); + case OK -> + postResponse(args); + case Error -> + postError(args); + default -> { + this.state = state; + this.result = List.copyOf(args); + } + } + doProcess = true; + } + + @Override + public List result() { + if (state == State.Incomplete) { + throw new IllegalStateException(); + } + if (result == null) { + return List.of(); + } else { + return result; + } + } + + private void postResponse(List args) { + try { + scratchList.clear(); + scratchList.addAll(args); + rootNode.postResponse(scratchList); + } catch (Exception ex) { + state = State.Error;//@TODO proper error reporting + } + } + + private void postError(List args) { + Variable trap = namespace.getVariable(TRAP); + if (trap != null) { + rootNode.skipCurrentLine(); + PArray existing = PArray.from(trap.getValue()).orElse(PArray.EMPTY); + Value response = args.isEmpty() ? null : args.getFirst(); + trap.setValue(addErrorToTrap(existing, response)); + } else { + result = List.copyOf(args); + state = State.Error; + } + } + + private PArray addErrorToTrap(PArray trap, Value response) { + String msg; + if (response == null) { + msg = activeCommand + " : Error"; + } else { + msg = PError.from(response) + .map(err -> activeCommand + " : " + err.exceptionType().getSimpleName() + + " : " + err.message()) + .orElse(activeCommand + " : Error : " + response); + } + return Stream.concat(trap.stream(), Stream.of(PString.of(msg))) + .collect(PArray.collector()); + } + + private void processResultFromNode() throws Exception { + scratchList.clear(); + Variable trap = namespace.getVariable(TRAP); + if (trapErrors && trap != null && !trap.getValue().isEmpty()) { + String errors = PArray.from(trap.getValue()) + .orElse(PArray.EMPTY) + .asListOf(String.class) + .stream() + .collect(Collectors.joining("\n")); + result = List.of(PString.of(errors)); + state = State.Error; + } else { + rootNode.writeResult(scratchList); + result = List.copyOf(scratchList); + state = State.OK; + } + } + + private StackFrame processNextCommand(Env context) + throws Exception { + + scratchList.clear(); + rootNode.writeNextCommand(scratchList); + if (scratchList.size() < 1) { + throw new Exception(); + } + Value cmdArg = scratchList.get(0); + activeCommand = cmdArg.toString(); + if (cmdArg instanceof ControlAddress) { + routeCall(context, scratchList); + return null; + } + String cmdStr = cmdArg.toString(); + if (cmdStr.isEmpty()) { + throw new IllegalArgumentException("Empty command"); + } + Command cmd = namespace.getCommand(cmdStr); + if (cmd != null) { + scratchList.remove(0); + return cmd.createStackFrame(namespace, List.copyOf(scratchList)); + } + if (cmdStr.charAt(0) == '/' && cmdStr.lastIndexOf('.') > -1) { + routeCall(context, scratchList); + return null; + } + + throw new IllegalArgumentException("Command not found"); + + } + + private void routeCall(Env context, List argList) + throws Exception { + ControlAddress ad = ControlAddress.from(argList.get(0)) + .orElseThrow(Exception::new); + argList.remove(0); + Call call = Call.create(ad, context.getAddress(), context.getTime(), List.copyOf(argList)); + log.log(Level.TRACE, () -> "Sending Call" + call); + pending = call; + context.getPacketRouter().route(call); + } + + /** + * Create a {@link ScriptStackFrame.Builder} for the provided namespace and + * script. By default the script will be evaluated in a dedicated child + * namespace. Neither the builder or the stack frame are reusable. + * + * @param namespace namespace to run script in + * @param script script to parse and run + * @return builder + * @throws InvalidSyntaxException if the script cannot be parsed + */ + public static Builder forScript(Namespace namespace, String script) { + RootNode root = ScriptParser.getInstance().parse(script); + return new Builder(namespace, root); + } + + /** + * A builder for {@link ScriptStackFrame}. + * + * @see #forScript(org.praxislive.script.Namespace, java.lang.String) + */ + public static class Builder { + + private final Namespace namespace; + private final RootNode root; + + private boolean inline; + private List allowedCommands; + private boolean trapErrors; + + private Builder(Namespace namespace, RootNode root) { + this.namespace = namespace; + this.root = root; + } + + /** + * Run the script directly in the provided namespace rather than a + * child. + * + * @return this for chaining + */ + public Builder inline() { + if (trapErrors) { + throw new IllegalStateException("Inline and trap errors cannot be used together"); + } + this.inline = true; + return this; + } + + /** + * Trap errors. Error messages will be aggregated and script execution + * will attempt to continue. If no allowed commands have been specified, + * an empty list of allowed commands will be set. + * + * @return this for chaining + */ + public Builder trapErrors() { + if (inline) { + throw new IllegalStateException("Inline and trap errors cannot be used together"); + } + this.trapErrors = true; + if (this.allowedCommands == null) { + this.allowedCommands = List.of(); + } + return this; + } + + /** + * Specify a list of allowed commands to filter those available from the + * provided namespace. + * + * @param commands list of allowed commands + * @return this for chaining + */ + public Builder allowedCommands(List commands) { + this.allowedCommands = List.copyOf(commands); + return this; + } + + /** + * Build the ScriptStackFrame. + * + * @return script stackframe + */ + public ScriptStackFrame build() { + Namespace ns; + if (inline) { + ns = namespace; + } else { + ns = namespace.createChild(); + } + if (allowedCommands != null) { + ns = new FilteredNamespace(ns, allowedCommands); + } + return new ScriptStackFrame(ns, root, trapErrors); + } + + } + + private static class FilteredNamespace implements Namespace { + + private final Namespace delegate; + private final List allowed; + + private FilteredNamespace(Namespace delegate, List allowed) { + this.delegate = Objects.requireNonNull(delegate); + this.allowed = List.copyOf(allowed); + } + + @Override + public void addCommand(String id, Command cmd) { + if (allowed.contains(id)) { + delegate.addCommand(id, cmd); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public void addVariable(String id, Variable var) { + delegate.addVariable(id, var); + } + + @Override + public Namespace createChild() { + return new FilteredNamespace(delegate.createChild(), allowed); + } + + @Override + public Command getCommand(String id) { + if (allowed.contains(id)) { + return delegate.getCommand(id); + } else { + return null; + } + } + + @Override + public Variable getVariable(String id) { + return delegate.getVariable(id); + } + + } + +} diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java index 50f59598..ec0267ba 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/CompositeNode.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -19,7 +19,6 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script.ast; import java.util.List; @@ -28,19 +27,20 @@ /** * - * + * */ -public abstract class CompositeNode extends Node { - - private Node[] children; +abstract class CompositeNode extends Node { + + private final List children; + private int active; private Namespace namespace; public CompositeNode(List children) { - this.children = children.toArray(new Node[children.size()]); + this.children = List.copyOf(children); } - @Override + @Override public void init(Namespace namespace) { if (namespace == null) { throw new NullPointerException(); @@ -50,9 +50,7 @@ public void init(Namespace namespace) { } this.namespace = namespace; active = 0; - for (Node child : children) { - child.init(namespace); - } + children.forEach(child -> child.init(namespace)); } @Override @@ -63,8 +61,8 @@ public boolean isDone() { if (active < 0) { return isThisDone(); } else { - while (active < children.length) { - if (!children[active].isDone()) { + while (active < children.size()) { + if (!children.get(active).isDone()) { return false; } active++; @@ -77,13 +75,13 @@ public boolean isDone() { protected abstract boolean isThisDone(); @Override - public void writeNextCommand(List args) + public void writeNextCommand(List args) throws Exception { if (namespace == null) { throw new IllegalStateException(); } if (active >= 0) { - children[active].writeNextCommand(args); + children.get(active).writeNextCommand(args); } else { writeThisNextCommand(args); } @@ -92,15 +90,14 @@ public void writeNextCommand(List args) protected abstract void writeThisNextCommand(List args) throws Exception; - @Override - public void postResponse(List args) + public void postResponse(List args) throws Exception { if (namespace == null) { throw new IllegalStateException(); } if (active >= 0) { - children[active].postResponse(args); + children.get(active).postResponse(args); } else { postThisResponse(args); } @@ -116,11 +113,19 @@ public void reset() { } namespace = null; } - - protected Node[] getChildren() { + + protected List getChildren() { return children; } - + protected void skipActive() { + if (active < 0) { + return; + } + active++; + if (active >= children.size()) { + active = -1; + } + } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java index afd29eab..794a6f2e 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/RootNode.java @@ -19,7 +19,6 @@ * Please visit https://www.praxislive.org if you need additional information or * have any questions. */ - package org.praxislive.script.ast; import java.util.List; @@ -27,7 +26,7 @@ /** * - * + * */ public class RootNode extends CompositeNode { @@ -52,13 +51,17 @@ protected void postThisResponse(List args) { @Override public void writeResult(List args) throws Exception { - Node[] children = getChildren(); - if (children.length > 0) { - children[children.length - 1].writeResult(args); + List children = getChildren(); + if (!children.isEmpty()) { + Node last = children.getLast(); + if (last.isDone()) { + last.writeResult(args); + } } - } - + public void skipCurrentLine() { + skipActive(); + } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java b/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java index 6fc9660a..0c2829c6 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ast/SubcommandNode.java @@ -26,7 +26,7 @@ /** * - * + * */ public class SubcommandNode extends CompositeNode { @@ -52,7 +52,7 @@ protected void postThisResponse(List args) { @Override public void writeResult(List args) throws Exception { - Node[] children = getChildren(); - children[children.length - 1].writeResult(args); + List children = getChildren(); + children.get(children.size() - 1).writeResult(args); } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java index f6a9ee83..f1270eeb 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java @@ -39,6 +39,7 @@ import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; import org.praxislive.script.Namespace; +import org.praxislive.script.ScriptStackFrame; import org.praxislive.script.StackFrame; /** @@ -171,7 +172,7 @@ public StackFrame process(Env env) { try { Namespace child = namespace.createChild(); child.createConstant(Env.CONTEXT, ctxt); - return ScriptCmds.INLINE_EVAL.createStackFrame(child, List.of(script)); + return ScriptStackFrame.forScript(child, script.toString()).inline().build(); } catch (Exception ex) { state = State.Error; result = List.of(PError.of(ex)); diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java deleted file mode 100644 index 9769f552..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/EvalStackFrame.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2024 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ -package org.praxislive.script.commands; - -import java.util.LinkedList; -import java.util.List; -import org.praxislive.core.Value; -import org.praxislive.core.Call; -import org.praxislive.core.ControlAddress; -import org.praxislive.core.types.PError; -import org.praxislive.script.Command; -import org.praxislive.script.Env; -import org.praxislive.script.Namespace; -import org.praxislive.script.StackFrame; -import org.praxislive.script.ast.RootNode; - -import static java.lang.System.Logger.Level; - -/** - * - */ -class EvalStackFrame implements StackFrame { - - private static final System.Logger log = System.getLogger(EvalStackFrame.class.getName()); - - private Namespace namespace; - private RootNode rootNode; - private State state; - private Call pending; - private List result; - private List argList; - private boolean doProcess; - - public EvalStackFrame(Namespace namespace, RootNode rootNode) { - this.namespace = namespace; - this.rootNode = rootNode; - this.state = State.Incomplete; - this.argList = new LinkedList<>(); - rootNode.reset(); - rootNode.init(namespace); - doProcess = true; - } - - @Override - public State getState() { - return state; - } - - @Override - public StackFrame process(Env context) { - if (state != State.Incomplete) { - throw new IllegalStateException(); - } - if (!doProcess) { - return null; - } - try { - if (rootNode.isDone()) { - processResultFromNode(); - return null; - } else { - return processNextCommand(context); - } - } catch (Exception ex) { - result = List.of(PError.of(ex)); - state = State.Error; - return null; - } finally { - doProcess = false; - } - - } - - @Override - public void postResponse(Call call) { - if (pending != null && pending.matchID() == call.matchID()) { - pending = null; - if (call.isReply()) { - log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Return call : \n" + call); - postResponse(call.args()); - } else { - log.log(Level.TRACE, () -> "EvalStackFrame - Received valid Error call : \n" + call); - this.state = State.Error; - this.result = call.args(); - } - doProcess = true; - } else { - log.log(Level.TRACE, () -> "EvalStackFrame - Received invalid call : \n" + call); - } - - } - - @Override - public void postResponse(State state, List args) { - if (this.state != State.Incomplete) { - throw new IllegalStateException(); - } - switch (state) { - case Incomplete: - throw new IllegalArgumentException(); - case OK: - postResponse(args); - break; - default: - this.state = state; - this.result = args; - } - doProcess = true; - } - - @Override - public List result() { - if (state == State.Incomplete) { - throw new IllegalStateException(); - } - if (result == null) { - return List.of(); - } else { - return result; - } - } - - private void postResponse(List args) { - try { - argList.clear(); - argList.addAll(args); - rootNode.postResponse(argList); - } catch (Exception ex) { - state = State.Error;//@TODO proper error reporting - } - } - - private void processResultFromNode() throws Exception { - argList.clear(); - rootNode.writeResult(argList); - result = List.copyOf(argList); - state = State.OK; - - } - - private StackFrame processNextCommand(Env context) - throws Exception { - - argList.clear(); - rootNode.writeNextCommand(argList); - if (argList.size() < 1) { - throw new Exception(); - } - Value cmdArg = argList.get(0); - if (cmdArg instanceof ControlAddress) { - routeCall(context, argList); - return null; - } - String cmdStr = cmdArg.toString(); - if (cmdStr.isEmpty()) { - throw new Exception(); - } - Command cmd = namespace.getCommand(cmdStr); - if (cmd != null) { - argList.remove(0); - return cmd.createStackFrame(namespace, List.copyOf(argList)); - } - if (cmdStr.charAt(0) == '/' && cmdStr.lastIndexOf('.') > -1) { - routeCall(context, argList); - return null; - } - - throw new Exception(); - - } - - private void routeCall(Env context, List argList) - throws Exception { - ControlAddress ad = ControlAddress.from(argList.get(0)) - .orElseThrow(Exception::new); - argList.remove(0); - Call call = Call.create(ad, context.getAddress(), context.getTime(), List.copyOf(argList)); - log.log(Level.TRACE, () -> "Sending Call" + call); - pending = call; - context.getPacketRouter().route(call); - } - -} diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java index c5c168cc..9f6cc67c 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java @@ -22,17 +22,18 @@ package org.praxislive.script.commands; import java.io.File; +import java.util.ArrayDeque; import java.util.List; import java.util.Map; +import java.util.Queue; import org.praxislive.core.Value; -import org.praxislive.core.syntax.InvalidSyntaxException; +import org.praxislive.core.types.PArray; import org.praxislive.core.types.PResource; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Namespace; +import org.praxislive.script.ScriptStackFrame; import org.praxislive.script.StackFrame; -import org.praxislive.script.ast.RootNode; -import org.praxislive.script.ast.ScriptParser; /** * @@ -41,7 +42,6 @@ public class ScriptCmds implements CommandInstaller { private final static ScriptCmds instance = new ScriptCmds(); public final static Command EVAL = new Eval(); - public final static Command INLINE_EVAL = new InlineEval(); public final static Command INCLUDE = new Include(); private ScriptCmds() { @@ -62,34 +62,44 @@ private static class Eval implements Command { @Override public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { - if (args.size() != 1) { - throw new Exception(); + if (args.isEmpty()) { + throw new IllegalArgumentException("No script passed to eval"); } - String script = args.get(0).toString(); - try { - RootNode astRoot = ScriptParser.getInstance().parse(script); - return new EvalStackFrame(namespace.createChild(), astRoot); - } catch (InvalidSyntaxException ex) { - throw new Exception(ex); + Queue queue = new ArrayDeque<>(args); + boolean inline = false; + boolean trap = false; + List allowed = null; + String script = null; + while (!queue.isEmpty()) { + String arg = queue.poll().toString(); + if ("--inline".equals(arg)) { + inline = true; + } else if ("--trap-errors".equals(arg)) { + trap = true; + if (allowed == null) { + allowed = List.of(); + } + } else if ("--allowed-commands".equals(arg)) { + allowed = PArray.from(queue.poll()).orElseThrow().asListOf(String.class); + } else { + script = arg; + break; + } } - } - } - - private static class InlineEval implements Command { - - @Override - public StackFrame createStackFrame(Namespace namespace, List args) - throws Exception { - if (args.size() != 1) { - throw new Exception(); + if (!queue.isEmpty()) { + throw new IllegalArgumentException("Additional arguments after script"); } - String script = args.get(0).toString(); - try { - RootNode astRoot = ScriptParser.getInstance().parse(script); - return new EvalStackFrame(namespace, astRoot); - } catch (InvalidSyntaxException ex) { - throw new Exception(ex); + var bld = ScriptStackFrame.forScript(namespace, script); + if (inline) { + bld.inline(); + } + if (trap) { + bld.trapErrors(); + } + if (allowed != null) { + bld.allowedCommands(allowed); } + return bld.build(); } } @@ -105,8 +115,7 @@ public StackFrame createStackFrame(Namespace namespace, List args) throws PResource res = PResource.from(args.get(0)).orElseThrow(); File file = new File(res.value()); String script = Utils.loadStringFromFile(file); - RootNode astRoot = ScriptParser.getInstance().parse(script); - return new EvalStackFrame(namespace.createChild(), astRoot); + return ScriptStackFrame.forScript(namespace, script).build(); } catch (Exception ex) { throw new Exception(ex); } diff --git a/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java new file mode 100644 index 00000000..1a5f473d --- /dev/null +++ b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java @@ -0,0 +1,308 @@ +package org.praxislive.script; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.praxislive.core.Call; +import org.praxislive.core.Clock; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.Lookup; +import org.praxislive.core.Packet; +import org.praxislive.core.Root; +import org.praxislive.core.RootHub; +import org.praxislive.core.Value; +import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PError; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class DefaultScriptServiceTest { + + private static final boolean VERBOSE = Boolean.getBoolean("praxis.test.verbose"); + private static final int TIMEOUT = Integer.getInteger("praxis.test.timeout", 1000000); + + public DefaultScriptServiceTest() { + } + + @Test + public void testInlineScript() throws Exception { + logTest("testInlineScript"); + String script = """ + set V1 "One" + set V2 "Two" + set V3 $V1 + set RET [array $V1 $V2 $V3] + """; + var root = new DefaultScriptService(); + try (var hub = new RootHubImpl("script", root)) { + hub.start(); + hub.send("/script.eval", "/hub.result", script); + var result = hub.poll(); + logCall("Result received", result); + assertTrue(result.isReply()); + assertEquals(1, result.args().size()); + var expected = Stream.of("One", "Two", "One") + .map(Value::ofObject) + .collect(PArray.collector()); + assertEquals(expected, result.args().get(0)); + } + + } + + @Test + public void testAtScript() throws Exception { + logTest("testAtScript"); + String script = """ + .foo + @ /bar { + set V 42 + @ ./baz test:component { + .value $V + } + } + """; + var root = new DefaultScriptService(); + try (var hub = new RootHubImpl("script", root)) { + hub.start(); + hub.send("/script.eval", "/hub.result", script); + Call call = hub.poll(); + logCall("Call to /hub.foo received", call); + assertTrue(call.isRequest()); + assertEquals("/hub.foo", call.to().toString()); + assertTrue(call.args().isEmpty()); + hub.dispatch(call.reply()); + + call = hub.poll(); + logCall("Call to /bar.add-child received", call); + assertTrue(call.isRequest()); + assertEquals("/bar.add-child", call.to().toString()); + hub.dispatch(call.reply()); + + call = hub.poll(); + logCall("Call to /bar/baz.value received", call); + assertTrue(call.isRequest()); + assertEquals("/bar/baz.value", call.to().toString()); + assertEquals(1, call.args().size()); + assertEquals("42", call.args().get(0).toString()); + hub.dispatch(call.reply()); + + call = hub.poll(); + logCall("Result received", call); + + assertTrue(call.isReply()); + + } + + } + + @Test + public void testEvalInline() throws Exception { + logTest("testEvalInline"); + String script = """ + eval --inline { + set X 42 + /hub.value $X + } + /hub.value $X + eval { + set Y 84 + /hub.value $Y + } + /hub.value $Y + """; + var root = new DefaultScriptService(); + try (var hub = new RootHubImpl("script", root)) { + hub.start(); + hub.send("/script.eval", "/hub.result", script); + + for (String expected : new String[]{"42", "42", "84"}) { + Call call = hub.poll(); + logCall("Value received", call); + assertEquals("/hub.value", call.to().toString()); + assertEquals(expected, call.args().get(0).toString()); + hub.dispatch(call.reply()); + } + + Call call = hub.poll(); + logCall("Expected execution failure", call); + assertTrue(call.isError()); + + } + } + + @Test + public void testEvalTrapErrors() throws Exception { + logTest("testEvalTrapErrors"); + String script = """ + eval --trap-errors { + set X 42 + /hub.value "FOO" + } + """; + var root = new DefaultScriptService(); + try (var hub = new RootHubImpl("script", root)) { + hub.start(); + hub.send("/script.eval", "/hub.result", script); + Call call = hub.poll(); + logCall("Value received", call); + assertEquals("/hub.value", call.to().toString()); + assertEquals("FOO", call.args().get(0).toString()); + hub.dispatch(call.reply()); + call = hub.poll(); + logCall("Error result received", call); + assertTrue(call.isError()); + } + + } + + @Test + public void testEvalTrapErrorsNested() throws Exception { + logTest("testEvalTrapErrorsNested"); + String script = """ + set allowed [array "@"] + eval --trap-errors --allowed-commands $allowed { + @ /bar { + set V 42 + @ ./baz test:component { + .error + .value "FOO" + } + } + set Y [set X] + } + """; + var root = new DefaultScriptService(); + try (var hub = new RootHubImpl("script", root)) { + hub.start(); + hub.send("/script.eval", "/hub.result", script); + Call call = hub.poll(); + logCall("Call to /bar.add-child received", call); + assertTrue(call.isRequest()); + assertEquals("/bar.add-child", call.to().toString()); + hub.dispatch(call.reply()); + + call = hub.poll(); + logCall("Call to /bar/baz.error received", call); + assertTrue(call.isRequest()); + assertEquals("/bar/baz.error", call.to().toString()); + hub.dispatch(call.error(PError.of("BAZ ERROR"))); + + call = hub.poll(); + logCall("Call to /bar/baz.value received", call); + assertTrue(call.isRequest()); + assertEquals("/bar/baz.value", call.to().toString()); + assertEquals(1, call.args().size()); + assertEquals("FOO", call.args().get(0).toString()); + hub.dispatch(call.reply()); + + call = hub.poll(); + logCall("Result received", call); + + assertTrue(call.isError()); + String errors = call.args().get(0).toString(); + if (VERBOSE) { + System.out.println(""); + System.out.println("Completed with errors"); + System.out.println("====================="); + System.out.println(errors); + } + assertTrue(errors.contains("set")); + assertTrue(errors.contains("BAZ ERROR")); + } + + } + + private static void logTest(String testName) { + if (VERBOSE) { + System.out.println(""); + System.out.println(""); + System.out.println(testName); + System.out.println("===================="); + } + } + + private static void logCall(String msg, Call call) { + if (VERBOSE) { + System.out.println(msg); + System.out.println(call); + } + } + + private static class RootHubImpl implements RootHub, AutoCloseable { + + private final String rootID; + private final Root root; + private final BlockingQueue queue; + + private Root.Controller ctrl; + + private RootHubImpl(String rootID, Root root) { + this.rootID = rootID; + this.root = root; + this.queue = new LinkedBlockingQueue<>(); + } + + @Override + public boolean dispatch(Packet packet) { + if (rootID.equals(packet.rootID())) { + return ctrl.submitPacket(packet); + } else { + return queue.offer(packet); + } + } + + @Override + public Clock getClock() { + return System::nanoTime; + } + + @Override + public Lookup getLookup() { + return Lookup.EMPTY; + } + + public void send(String to, String from, Object arg) { + send(to, from, List.of(Value.ofObject(arg))); + } + + public void send(String to, String from, List args) { + dispatch(Call.create(ControlAddress.of(to), + ControlAddress.of(from), + getClock().getTime(), + args + )); + } + + public Call poll() throws InterruptedException, TimeoutException { + Packet p = queue.poll(TIMEOUT, TimeUnit.MILLISECONDS); + if (p instanceof Call c) { + return c; + } + throw new TimeoutException("Call poll timed out"); + } + + public void start() { + ctrl = root.initialize(rootID, this); + ctrl.start(); + } + + @Override + public void close() { + ctrl.shutdown(); + try { + ctrl.awaitTermination(10, TimeUnit.SECONDS); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + } + +} diff --git a/praxiscore-script/src/test/java/org/praxislive/script/ast/AddressNodeTest.java b/praxiscore-script/src/test/java/org/praxislive/script/ast/AddressNodeTest.java index c96eff8f..2b7f9c4b 100644 --- a/praxiscore-script/src/test/java/org/praxislive/script/ast/AddressNodeTest.java +++ b/praxiscore-script/src/test/java/org/praxislive/script/ast/AddressNodeTest.java @@ -1,95 +1,51 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ - package org.praxislive.script.ast; -import org.praxislive.script.ast.AddressNode; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; import org.praxislive.core.Value; import org.praxislive.core.ComponentAddress; import org.praxislive.script.Command; import org.praxislive.script.Namespace; import org.praxislive.script.Variable; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import static org.junit.Assert.*; - -/** - * - * - */ -public class AddressNodeTest { - public AddressNodeTest() { - } +import static org.junit.jupiter.api.Assertions.*; - @BeforeClass - public static void setUpClass() throws Exception { - } - @AfterClass - public static void tearDownClass() throws Exception { - } - - @Before - public void setUp() { - } - - @After - public void tearDown() { - } - -// /** -// * Test of init method, of class AddressNode. -// */ -// @Test -// public void testInit() { -// System.out.println("init"); -// Namespace namespace = null; -// AddressNode instance = null; -// instance.init(namespace); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } -// -// /** -// * Test of reset method, of class AddressNode. -// */ -// @Test -// public void testReset() { -// System.out.println("reset"); -// AddressNode instance = null; -// instance.reset(); -// // TODO review the generated test code and remove the default call to fail. -// fail("The test case is a prototype."); -// } +public class AddressNodeTest { /** * Test of writeResult method, of class AddressNode. */ @Test public void testWriteResult() { - System.out.println("writeResult"); - List args = new ArrayList(); Namespace ns = new NS(); - String[] ads = {"./to/here", "./to/here.control", "./to/here!port", ".control2"}; - for (String ad : ads) { - AddressNode instance = new AddressNode(ad); - instance.init(ns); - instance.writeResult(args); - System.out.println(ad + " : " + args.get(0)); - args.clear(); - } + List scratch = new ArrayList<>(); + List relative = List.of( + "./to/here", + "./to/here.control", + "./to/here!port", + ".control2"); + List absolute = List.of( + "/test/address/to/here", + "/test/address/to/here.control", + "/test/address/to/here!port", + "/test/address.control2"); + List result = relative.stream() + .map(address -> { + var addressNode = new AddressNode(address); + addressNode.init(ns); + scratch.clear(); + addressNode.writeResult(scratch); + return scratch.get(0).toString(); + }).toList(); + assertEquals(absolute, result); + } private class NS implements Namespace, Variable { + @Override public Variable getVariable(String id) { if ("_CTXT".equals(id)) { return this; @@ -97,30 +53,36 @@ public Variable getVariable(String id) { return null; } + @Override public void addVariable(String id, Variable var) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public Namespace createChild() { throw new UnsupportedOperationException("Not supported yet."); } + @Override public void setValue(Value value) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public Value getValue() { return ComponentAddress.of("/test/address"); } + @Override public Command getCommand(String id) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public void addCommand(String id, Command cmd) { throw new UnsupportedOperationException("Not supported yet."); } } -} \ No newline at end of file +} From c3713a262036f43dca4f88d899324fb38237c52b Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Fri, 26 Apr 2024 17:40:32 +0100 Subject: [PATCH 5/7] Add default stackframe factory methods and compound stackframe support. Refactor some complex commands around new stackframe support. Add tests for stackframe support. Tidy up default command registration. --- .../script/AbstractSingleCallFrame.java | 19 +- .../praxislive/script/CompoundStackFrame.java | 173 +++++++++++++ .../praxislive/script/ScriptStackFrame.java | 39 +++ .../org/praxislive/script/StackFrame.java | 133 ++++++++++ .../praxislive/script/commands/ArrayCmds.java | 11 +- .../praxislive/script/commands/AtCmds.java | 230 +++++------------- .../praxislive/script/commands/BaseCmds.java | 11 +- .../script/commands/ConnectionCmds.java | 9 +- .../commands/CoreCommandsInstaller.java | 15 +- .../praxislive/script/commands/FileCmds.java | 31 ++- .../script/commands/ResourceCmds.java | 74 ------ .../script/commands/ScriptCmds.java | 31 +-- .../org/praxislive/script/commands/Utils.java | 51 ---- .../script/DefaultScriptServiceTest.java | 23 +- .../org/praxislive/script/StackFrameTest.java | 196 +++++++++++++++ 15 files changed, 680 insertions(+), 366 deletions(-) create mode 100644 praxiscore-script/src/main/java/org/praxislive/script/CompoundStackFrame.java delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java delete mode 100644 praxiscore-script/src/main/java/org/praxislive/script/commands/Utils.java create mode 100644 praxiscore-script/src/test/java/org/praxislive/script/StackFrameTest.java diff --git a/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java index 27a1e221..d2662932 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java @@ -22,6 +22,7 @@ package org.praxislive.script; import java.util.List; +import java.util.Objects; import org.praxislive.core.Call; import org.praxislive.core.Value; import org.praxislive.core.types.PError; @@ -39,18 +40,22 @@ */ public abstract class AbstractSingleCallFrame implements StackFrame { - private Namespace namespace; - private List args; + private final Namespace namespace; + private final List args; + private State state; private Call call; private List result; protected AbstractSingleCallFrame(Namespace namespace, List args) { - if (namespace == null || args == null) { - throw new NullPointerException(); - } - this.namespace = namespace; - this.args = args; + this.namespace = Objects.requireNonNull(namespace); + this.args = Objects.requireNonNull(args); + state = State.Incomplete; + } + + AbstractSingleCallFrame(List args) { + this.namespace = null; + this.args = Objects.requireNonNull(args); state = State.Incomplete; } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/CompoundStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/CompoundStackFrame.java new file mode 100644 index 00000000..d61111ca --- /dev/null +++ b/praxiscore-script/src/main/java/org/praxislive/script/CompoundStackFrame.java @@ -0,0 +1,173 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; +import java.util.function.Function; +import java.util.function.Supplier; +import org.praxislive.core.Call; +import org.praxislive.core.Value; +import org.praxislive.core.types.PError; + +/** + * + */ +final class CompoundStackFrame implements StackFrame { + + private final Queue, StackFrame>> queue; + + private StackFrame active; + private State state; + private List result; + + CompoundStackFrame(StackFrame base, Function, StackFrame> next) { + queue = new ArrayDeque<>(); + queue.add(next); + active = base; + state = State.Incomplete; + } + + @Override + public State getState() { + if (state != State.Incomplete) { + return state; + } else { + return active.getState(); + } + } + + @Override + public void postResponse(Call call) { + active.postResponse(call); + checkActiveState(); + } + + @Override + public void postResponse(State state, List args) { + active.postResponse(state, args); + checkActiveState(); + } + + @Override + public StackFrame process(Env env) { + while (state == State.Incomplete) { + StackFrame frame = active.process(env); + if (frame != null || active.getState() == State.Incomplete) { + return frame; + } + checkActiveState(); + } + return null; + } + + @Override + public List result() { + if (result != null) { + return result; + } else { + throw new IllegalStateException(); + } + } + + void addStage(Function, StackFrame> stage) { + queue.add(stage); + } + + private void checkActiveState() { + switch (active.getState()) { + case Incomplete -> { + } + case OK -> + nextOrComplete(); + default -> { + state = active.getState(); + result = active.result(); + active = null; + queue.clear(); + } + } + } + + private void nextOrComplete() { + if (!queue.isEmpty()) { + try { + active = queue.remove().apply(active.result()); + } catch (Exception ex) { + state = State.Error; + result = List.of(PError.of(ex)); + active = null; + queue.clear(); + } + } else { + state = State.OK; + result = active.result(); + active = null; + } + } + + static class SupplierStackFrame implements StackFrame { + + private final Supplier> supplier; + + private State state = State.Incomplete; + private List result; + + SupplierStackFrame(Supplier> supplier) { + this.supplier = supplier; + } + + @Override + public State getState() { + return state; + } + + @Override + public void postResponse(Call call) { + throw new UnsupportedOperationException(); + } + + @Override + public void postResponse(State state, List args) { + throw new UnsupportedOperationException(); + } + + @Override + public StackFrame process(Env env) { + try { + result = supplier.get(); + state = State.OK; + } catch (Exception ex) { + result = List.of(PError.of(ex)); + state = State.Error; + } + return null; + } + + @Override + public List result() { + return result; + } + } + +} diff --git a/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java index 666b477c..62970469 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/ScriptStackFrame.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import org.praxislive.core.Value; @@ -282,6 +283,7 @@ public static class Builder { private boolean inline; private List allowedCommands; private boolean trapErrors; + private List> namespaceProcessors; private Builder(Namespace namespace, RootNode root) { this.namespace = namespace; @@ -332,6 +334,39 @@ public Builder allowedCommands(List commands) { return this; } + /** + * Create a constant with the given name and value in the script + * namespace. + * + * @param id constant name + * @param value constant value + * @return this for chaining + */ + public Builder createConstant(String id, Value value) { + addNamespaceProcessor(ns -> ns.createConstant(id, value)); + return this; + } + + /** + * Create a variable with the given name and value in the script + * namespace. + * + * @param id variable name + * @param value variable value + * @return this for chaining + */ + public Builder createVariable(String id, Value value) { + addNamespaceProcessor(ns -> ns.createVariable(id, value)); + return this; + } + + private void addNamespaceProcessor(Consumer processor) { + if (namespaceProcessors == null) { + namespaceProcessors = new ArrayList<>(); + } + namespaceProcessors.add(processor); + } + /** * Build the ScriptStackFrame. * @@ -344,6 +379,10 @@ public ScriptStackFrame build() { } else { ns = namespace.createChild(); } + if (namespaceProcessors != null) { + Namespace nsp = ns; + namespaceProcessors.forEach(p -> p.accept(nsp)); + } if (allowedCommands != null) { ns = new FilteredNamespace(ns, allowedCommands); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java index 67e01f9a..50cca8d8 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/StackFrame.java @@ -22,8 +22,16 @@ package org.praxislive.script; import java.util.List; +import java.util.function.Function; +import java.util.function.UnaryOperator; import org.praxislive.core.Call; +import org.praxislive.core.ControlAddress; import org.praxislive.core.Value; +import org.praxislive.core.services.Service; +import org.praxislive.core.services.ServiceUnavailableException; +import org.praxislive.core.services.Services; +import org.praxislive.core.services.TaskService; +import org.praxislive.core.types.PReference; /** * A StackFrame used within the script executor pointing to the currently @@ -138,4 +146,129 @@ public static enum State { */ public List result(); + /** + * Combine this StackFrame with another created from the result of this + * StackFrame. The returned StackFrame will execute the frames in turn. + *

+ * The default implementation returns a private implementation of a compound + * stackframe. If this method is called on an existing compound stack frame, + * then the stage function will be added to that and {@code this} will be + * returned. + * + * @param stage function to create next stack frame from result + * @return compound stackframe + */ + public default StackFrame andThen(Function, StackFrame> stage) { + if (this instanceof CompoundStackFrame csf) { + csf.addStage(stage); + return this; + } else { + return new CompoundStackFrame(this, stage); + } + } + + /** + * Map the result of this StackFrame with the provided mapping function + * before returning a result or using + * {@link #andThen(java.util.function.Function)}. + *

+ * The default implementation calls + * {@link #andThen(java.util.function.Function)} with a function that + * creates a private implementation of a mapping StackFrame. + * + * @param mapper map value list + * @return mapping stackframe + */ + public default StackFrame andThenMap(UnaryOperator> mapper) { + return andThen(args -> new CompoundStackFrame.SupplierStackFrame( + () -> mapper.apply(args)) + ); + } + + /** + * Create a StackFrame that makes a call to the provided control and returns + * the result. + * + * @param to control address + * @param arg single argument + * @return stackframe + */ + public static StackFrame call(ControlAddress to, Value arg) { + return call(to, List.of(arg)); + } + + /** + * Create a StackFrame that makes a call to the provided control and returns + * the result. + * + * @param to control address + * @param args arguments + * @return stackframe + */ + public static StackFrame call(ControlAddress to, List args) { + return new AbstractSingleCallFrame(args) { + @Override + protected Call createCall(Env env, List args) throws Exception { + return Call.create(to, env.getAddress(), env.getTime(), args); + } + }; + } + + /** + * Create a StackFrame that makes a call to the provided {@link Service} and + * returns the result. The first implementation of the service found in the + * Env lookup will be used. + * + * @param service type of service + * @param control id of control on service + * @param arg single argument + * @return stackframe + * @throws ServiceUnavailableException if no implementation of the service + * is found + * + */ + public static StackFrame serviceCall(Class service, + String control, Value arg) { + return serviceCall(service, control, List.of(arg)); + } + + /** + * Create a StackFrame that makes a call to the provided {@link Service} and + * returns the result. The first implementation of the service found in the + * Env lookup will be used. + * + * @param service type of service + * @param control id of control on service + * @param args arguments + * @return stackframe + * @throws ServiceUnavailableException if no implementation of the service + * is found + */ + public static StackFrame serviceCall(Class service, + String control, List args) { + return new AbstractSingleCallFrame(args) { + @Override + protected Call createCall(Env env, List args) throws Exception { + ControlAddress to = ControlAddress.of( + env.getLookup().find(Services.class) + .flatMap(sm -> sm.locate(service)) + .orElseThrow(ServiceUnavailableException::new), + control + ); + return Call.create(to, env.getAddress(), env.getTime(), args); + } + }; + } + + /** + * Create a StackFrame that executes the provided task asynchronously in the + * default {@link TaskService} and returns the result. + * + * @param task task to execute + * @return stackframe + */ + public static StackFrame async(TaskService.Task task) { + return serviceCall(TaskService.class, TaskService.SUBMIT, PReference.of(task)); + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java index 04083c70..30772f3d 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java @@ -26,7 +26,6 @@ import org.praxislive.core.Value; import org.praxislive.core.types.PArray; import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; @@ -34,23 +33,17 @@ /** * */ -public class ArrayCmds implements CommandInstaller { - - private final static ArrayCmds INSTANCE = new ArrayCmds(); +class ArrayCmds { private final static Array ARRAY = new Array(); private ArrayCmds() { } - public void install(Map commands) { + static void install(Map commands) { commands.put("array", ARRAY); } - public final static ArrayCmds getInstance() { - return INSTANCE; - } - private static class Array implements InlineCommand { @Override diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java index f1270eeb..8e2435f2 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/AtCmds.java @@ -22,21 +22,16 @@ package org.praxislive.script.commands; import java.util.List; -import org.praxislive.script.AbstractSingleCallFrame; import java.util.Map; +import java.util.function.Function; import org.praxislive.core.Value; -import org.praxislive.core.Call; import org.praxislive.core.ComponentAddress; import org.praxislive.core.ComponentType; import org.praxislive.core.ControlAddress; import org.praxislive.core.protocols.ContainerProtocol; import org.praxislive.core.services.RootManagerService; -import org.praxislive.core.services.ServiceUnavailableException; -import org.praxislive.core.services.Services; -import org.praxislive.core.types.PError; import org.praxislive.core.types.PString; import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; import org.praxislive.script.Namespace; import org.praxislive.script.ScriptStackFrame; @@ -45,212 +40,105 @@ /** * */ -public class AtCmds implements CommandInstaller { +class AtCmds { - private final static AtCmds INSTANCE = new AtCmds(); private final static At AT = new At(); private final static NotAt NOT_AT = new NotAt(); private AtCmds() { } - @Override - public void install(Map commands) { + static void install(Map commands) { commands.put("@", AT); commands.put("!@", NOT_AT); } - public final static AtCmds getInstance() { - return INSTANCE; - } - private static class At implements Command { @Override public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { - if (args.size() < 2) { - throw new Exception(); + if (args.size() < 2 || args.size() > 3) { + throw new IllegalArgumentException("Incorrect number of arguments"); } - try { - ComponentAddress ctxt = ComponentAddress.from(args.get(0)) + ComponentAddress ctxt = ComponentAddress.from(args.get(0)) + .orElseThrow(IllegalArgumentException::new); + ComponentType type; + String script; + if (args.size() == 3) { + type = ComponentType.from(args.get(1)) .orElseThrow(IllegalArgumentException::new); - if (args.size() == 3) { - ComponentType type = ComponentType.from(args.get(1)) - .orElseThrow(IllegalArgumentException::new); - return new AtStackFrame(namespace, ctxt, type, args.get(2)); - } else { - Value arg = args.get(1); - if (! arg.toString().contains(" ")) { - try { - ComponentType type = ComponentType.from(arg).get(); - return new AtStackFrame(namespace, ctxt, type, PString.EMPTY); - } catch (Exception ex) { - // fall through - } - } - return new AtStackFrame(namespace, ctxt, null, arg); - } - } catch (Exception ex) { - throw new Exception(ex); - } - - } - } - - private static class NotAt implements Command { - - @Override - public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { - return new NotAtStackFrame(namespace, args); - } - - } - - private static class AtStackFrame implements StackFrame { - - private State state; - private final Namespace namespace; - private final ComponentAddress ctxt; - private final ComponentType type; - private final Value script; - private int stage; - private List result; - private Call active; - - private AtStackFrame(Namespace namespace, ComponentAddress ctxt, - ComponentType type, Value script) { - this.namespace = namespace; - this.ctxt = ctxt; - this.type = type; - this.script = script; - state = State.Incomplete; - if (type == null) { - stage = 2; + script = args.get(2).toString(); } else { - stage = 0; - } - } - - @Override - public State getState() { - return state; - } - - @Override - public StackFrame process(Env env) { - if (stage == 0) { - stage++; - try { - - ControlAddress to; - List args; - int depth = ctxt.depth(); - if (depth == 1) { - to = ControlAddress.of( - env.getLookup().find(Services.class) - .flatMap(sm -> sm.locate(RootManagerService.class)) - .orElseThrow(ServiceUnavailableException::new), - RootManagerService.ADD_ROOT); - args = List.of(PString.of(ctxt.rootID()), type); - } else { - to = ControlAddress.of(ctxt.parent(), - ContainerProtocol.ADD_CHILD); - args = List.of(PString.of(ctxt.componentID(depth - 1)), type); + Value arg = args.get(1); + if (!arg.toString().contains(" ")) { + try { + type = ComponentType.from(arg).get(); + script = null; + } catch (Exception ex) { + type = null; + script = arg.toString(); } - active = Call.create(to, env.getAddress(), env.getTime(), args); - env.getPacketRouter().route(active); - - } catch (Exception ex) { - state = State.Error; - result = List.of(PError.of(ex)); + } else { + type = null; + script = arg.toString(); } } - if (stage == 2) { - stage++; - try { - Namespace child = namespace.createChild(); - child.createConstant(Env.CONTEXT, ctxt); - return ScriptStackFrame.forScript(child, script.toString()).inline().build(); - } catch (Exception ex) { - state = State.Error; - result = List.of(PError.of(ex)); + StackFrame create = null; + if (type != null) { + if (ctxt.depth() == 1) { + create = StackFrame.serviceCall(RootManagerService.class, RootManagerService.ADD_ROOT, + List.of(PString.of(ctxt.rootID()), type)); + } else { + create = StackFrame.call( + ControlAddress.of(ctxt.parent(), ContainerProtocol.ADD_CHILD), + List.of(PString.of(ctxt.componentID()), type)); } } + Function, StackFrame> eval = null; + if (script != null) { + String s = script; + eval = v -> { + return ScriptStackFrame.forScript(namespace, s) + .createConstant(Env.CONTEXT, ctxt) + .build(); + }; - return null; - } - - @Override - public void postResponse(Call call) { - if (active != null && call.matchID() == active.matchID()) { - active = null; - if (call.isReply() && stage == 1) { - stage++; - } else { - result = call.args(); - this.state = State.Error; - } } - } - @Override - public void postResponse(State state, List args) { - if (state == State.OK) { -// if (stage == 1) { -// stage++; -// } else - if (stage == 3) { - this.state = State.OK; - result = args; + if (create != null) { + if (eval != null) { + return create.andThen(eval); + } else { + return create; } + } else if (eval != null) { + return eval.apply(List.of()); } else { - this.state = state; - result = args; - } - } - - @Override - public List result() { - if (result == null) { throw new IllegalStateException(); } - return result; + } } - private static class NotAtStackFrame extends AbstractSingleCallFrame { - - private NotAtStackFrame(Namespace ns, List args) { - super(ns, args); - } + private static class NotAt implements Command { @Override - protected Call createCall(Env env, List args) throws Exception { - ComponentAddress comp = ComponentAddress.from(args.get(0)) + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { + ComponentAddress component = ComponentAddress.from(args.get(0)) .orElseThrow(IllegalArgumentException::new); - if (comp.depth() == 1) { - return createRootRemovalCall(env, comp.rootID()); + if (component.depth() == 1) { + return StackFrame.serviceCall(RootManagerService.class, + RootManagerService.REMOVE_ROOT, + PString.of(component.componentID())); } else { - return createChildRemovalCall(env, comp); + return StackFrame.call(ControlAddress.of(component.parent(), + ContainerProtocol.REMOVE_CHILD), + PString.of(component.componentID())); } } - private Call createRootRemovalCall(Env env, String id) throws Exception { - ControlAddress to = ControlAddress.of( - env.getLookup().find(Services.class) - .flatMap(sm -> sm.locate(RootManagerService.class)) - .orElseThrow(ServiceUnavailableException::new), - RootManagerService.REMOVE_ROOT); - return Call.create(to, env.getAddress(), env.getTime(), PString.of(id)); - } - - private Call createChildRemovalCall(Env env, ComponentAddress comp) throws Exception { - ControlAddress to = ControlAddress.of(comp.parent(), - ContainerProtocol.REMOVE_CHILD); - return Call.create(to, env.getAddress(), env.getTime(), - PString.of(comp.componentID(comp.depth() - 1))); - } } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java index 86483724..d0bc15df 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java @@ -27,7 +27,6 @@ import org.praxislive.core.Value; import org.praxislive.core.types.PString; import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; @@ -38,26 +37,20 @@ /** * */ -public class BaseCmds implements CommandInstaller { +class BaseCmds { private static final System.Logger LOG = System.getLogger(BaseCmds.class.getName()); private static final Set SET = new Set(); private static final Echo ECHO = new Echo(); - private static final BaseCmds INSTANCE = new BaseCmds(); private BaseCmds() { } - @Override - public void install(Map commands) { + static void install(Map commands) { commands.put("set", SET); commands.put("echo", ECHO); } - public static BaseCmds getInstance() { - return INSTANCE; - } - private static class Set implements InlineCommand { @Override diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java index 52c5c62f..6d896317 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ConnectionCmds.java @@ -40,26 +40,21 @@ /** * */ -public class ConnectionCmds implements CommandInstaller { +class ConnectionCmds { - private final static ConnectionCmds instance = new ConnectionCmds(); private final static Connect CONNECT = new Connect(); private final static Disconnect DISCONNECT = new Disconnect(); private ConnectionCmds() { } - public void install(Map commands) { + static void install(Map commands) { commands.put("connect", CONNECT); commands.put("~", CONNECT); commands.put("disconnect", DISCONNECT); commands.put("!~", DISCONNECT); } - public final static ConnectionCmds getInstance() { - return instance; - } - private static class Connect implements Command { @Override diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java index 1726404b..eff593ed 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java @@ -1,7 +1,7 @@ /* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * - * Copyright 2018 Neil C Smith. + * Copyright 2024 Neil C Smith. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 3 only, as @@ -34,13 +34,12 @@ public class CoreCommandsInstaller implements CommandInstaller { @Override public void install(Map commands) { - BaseCmds.getInstance().install(commands); - ArrayCmds.getInstance().install(commands); - AtCmds.getInstance().install(commands); - ConnectionCmds.getInstance().install(commands); - FileCmds.getInstance().install(commands); - ResourceCmds.getInstance().install(commands); - ScriptCmds.getInstance().install(commands); + BaseCmds.install(commands); + ArrayCmds.install(commands); + AtCmds.install(commands); + ConnectionCmds.install(commands); + FileCmds.install(commands); + ScriptCmds.install(commands); } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java index a6cea0a1..4e88a345 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/FileCmds.java @@ -41,39 +41,34 @@ import org.praxislive.core.types.PResource; import org.praxislive.core.types.PString; import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; import org.praxislive.script.Env; import org.praxislive.script.InlineCommand; import org.praxislive.script.Namespace; +import org.praxislive.script.StackFrame; /** * */ -public class FileCmds implements CommandInstaller { - - private final static FileCmds INSTANCE = new FileCmds(); +class FileCmds { private final static Command FILE = new FileCmd(); private final static Command FILE_LIST = new FileListCmd(); private final static Command FILE_NAMES = new FileNamesCmd(); private final static Command CD = new CdCmd(); private final static Command PWD = new PwdCmd(); + private final static Command LOAD = new LoadCmd(); private FileCmds() { } - @Override - public void install(Map commands) { + static void install(Map commands) { commands.put("file", FILE); commands.put("file-list", FILE_LIST); commands.put("file-names", FILE_NAMES); commands.put("ls", FILE_NAMES); commands.put("cd", CD); commands.put("pwd", PWD); - } - - public static FileCmds getInstance() { - return INSTANCE; + commands.put("load", LOAD); } private static URI getPWD(Namespace namespace) { @@ -244,4 +239,20 @@ public List process(Env context, Namespace namespace, List args) t } + private static class LoadCmd implements Command { + + @Override + public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { + if (args.size() != 1) { + throw new IllegalArgumentException("Wrong number of arguments"); + } + Path path = PResource.from(args.get(0)) + .map(PResource::value) + .map(Path::of) + .orElseThrow(IllegalArgumentException::new); + return StackFrame.async(() -> PString.of(Files.readString(path))); + } + + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java deleted file mode 100644 index 7f91a35b..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ResourceCmds.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2024 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ -package org.praxislive.script.commands; - -import java.io.File; -import java.util.List; -import java.util.Map; -import org.praxislive.core.Value; -import org.praxislive.core.types.PString; -import org.praxislive.core.types.PResource; -import org.praxislive.script.Command; -import org.praxislive.script.CommandInstaller; -import org.praxislive.script.Env; -import org.praxislive.script.InlineCommand; -import org.praxislive.script.Namespace; - -/** - * - */ -public class ResourceCmds implements CommandInstaller { - - private final static ResourceCmds instance = new ResourceCmds(); - - private final static Command LOAD = new LoadCmd(); - - private ResourceCmds() { - } - - @Override - public void install(Map commands) { - commands.put("load", LOAD); - } - - public static ResourceCmds getInstance() { - return instance; - } - - private static class LoadCmd implements InlineCommand { - - @Override - public List process(Env context, Namespace namespace, List args) throws Exception { - if (args.size() != 1) { - throw new Exception(); - } - try { - File f = new File(PResource.from(args.get(0)).orElseThrow().value()); - String s = Utils.loadStringFromFile(f); - return List.of(PString.of(s)); - } catch (Exception ex) { - throw new Exception(ex); - } - } - - } -} diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java index 9f6cc67c..a534f5cb 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ScriptCmds.java @@ -21,7 +21,8 @@ */ package org.praxislive.script.commands; -import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayDeque; import java.util.List; import java.util.Map; @@ -29,6 +30,7 @@ import org.praxislive.core.Value; import org.praxislive.core.types.PArray; import org.praxislive.core.types.PResource; +import org.praxislive.core.types.PString; import org.praxislive.script.Command; import org.praxislive.script.CommandInstaller; import org.praxislive.script.Namespace; @@ -38,25 +40,19 @@ /** * */ -public class ScriptCmds implements CommandInstaller { +class ScriptCmds { - private final static ScriptCmds instance = new ScriptCmds(); public final static Command EVAL = new Eval(); public final static Command INCLUDE = new Include(); private ScriptCmds() { } - @Override - public void install(Map commands) { + static void install(Map commands) { commands.put("eval", EVAL); commands.put("include", INCLUDE); } - public static ScriptCmds getInstance() { - return instance; - } - private static class Eval implements Command { @Override @@ -107,18 +103,15 @@ private static class Include implements Command { @Override public StackFrame createStackFrame(Namespace namespace, List args) throws Exception { - // @TODO - should load in background - call to if (args.size() != 1) { - throw new Exception(); - } - try { - PResource res = PResource.from(args.get(0)).orElseThrow(); - File file = new File(res.value()); - String script = Utils.loadStringFromFile(file); - return ScriptStackFrame.forScript(namespace, script).build(); - } catch (Exception ex) { - throw new Exception(ex); + throw new IllegalArgumentException("Wrong number of arguments"); } + Path path = PResource.from(args.get(0)) + .map(PResource::value) + .map(Path::of) + .orElseThrow(IllegalArgumentException::new); + return StackFrame.async(() -> PString.of(Files.readString(path))) + .andThen(v -> ScriptStackFrame.forScript(namespace, v.get(0).toString()).build()); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/Utils.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/Utils.java deleted file mode 100644 index 4a771bc9..00000000 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/Utils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Neil C Smith. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License version 3 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License - * version 3 for more details. - * - * You should have received a copy of the GNU Lesser General Public License version 3 - * along with this work; if not, see http://www.gnu.org/licenses/ - * - * - * Please visit https://www.praxislive.org if you need additional information or - * have any questions. - */ -package org.praxislive.script.commands; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; - -/** - * - * - */ -class Utils { - - private Utils() { - } - - static String loadStringFromFile(File file) throws FileNotFoundException, IOException { - BufferedReader reader = new BufferedReader(new FileReader(file)); - StringBuilder data = new StringBuilder(); - char[] buf = new char[1024]; - int read = 0; - while ((read = reader.read(buf)) != -1) { - data.append(buf, 0, read); - } - reader.close(); - return data.toString(); - - } -} diff --git a/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java index 1a5f473d..f445cbe3 100644 --- a/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java +++ b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java @@ -1,3 +1,24 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ package org.praxislive.script; import java.util.List; @@ -26,7 +47,7 @@ public class DefaultScriptServiceTest { private static final boolean VERBOSE = Boolean.getBoolean("praxis.test.verbose"); - private static final int TIMEOUT = Integer.getInteger("praxis.test.timeout", 1000000); + private static final int TIMEOUT = Integer.getInteger("praxis.test.timeout", 10000); public DefaultScriptServiceTest() { } diff --git a/praxiscore-script/src/test/java/org/praxislive/script/StackFrameTest.java b/praxiscore-script/src/test/java/org/praxislive/script/StackFrameTest.java new file mode 100644 index 00000000..27b341ad --- /dev/null +++ b/praxiscore-script/src/test/java/org/praxislive/script/StackFrameTest.java @@ -0,0 +1,196 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script; + +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.praxislive.core.Call; +import org.praxislive.core.ComponentAddress; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.Lookup; +import org.praxislive.core.Packet; +import org.praxislive.core.PacketRouter; +import org.praxislive.core.services.Service; +import org.praxislive.core.services.Services; +import org.praxislive.core.services.TaskService; +import org.praxislive.core.types.PNumber; +import org.praxislive.core.types.PReference; +import org.praxislive.core.types.PString; +import org.praxislive.script.StackFrame.State; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + */ +public class StackFrameTest { + + private static final boolean VERBOSE = Boolean.getBoolean("praxis.test.verbose"); +// private static final int TIMEOUT = Integer.getInteger("praxis.test.timeout", 10000); + + public StackFrameTest() { + } + + @Test + public void testCallStackFrame() { + logTest("testCallStackFrame"); + var env = new EnvImpl(); + var to = "/test.control"; + var frame = StackFrame.call(ControlAddress.of(to), PString.of("FOO")); + assertEquals(State.Incomplete, frame.getState()); + frame.process(env); + assertEquals(State.Incomplete, frame.getState()); + var call = env.poll(); + logCall("Received call", call); + assertEquals(to, call.to().toString()); + assertEquals(env.getTime(), call.time()); + assertEquals(EnvImpl.ADDRESS, call.from()); + assertEquals("FOO", call.args().get(0).toString()); + frame.postResponse(call.reply(PString.of("BAR"))); + assertEquals(State.OK, frame.getState()); + assertEquals("BAR", frame.result().get(0).toString()); + } + + @Test + public void testCallAndThenMapStackFrame() { + logTest("testCallAndThenMapStackFrame"); + var env = new EnvImpl(); + var to = "/test.control"; + var frame = StackFrame.call(ControlAddress.of(to), PString.of("FOO")) + .andThenMap(args -> { + if ("BAR".equals(args.get(0).toString())) { + return List.of(PString.of("BAZ")); + } else { + return List.of(PString.of("ERROR")); + } + }); + assertEquals(State.Incomplete, frame.getState()); + frame.process(env); + assertEquals(State.Incomplete, frame.getState()); + var call = env.poll(); + logCall("Received call", call); + assertEquals(to, call.to().toString()); + assertEquals(env.getTime(), call.time()); + assertEquals(EnvImpl.ADDRESS, call.from()); + assertEquals("FOO", call.args().get(0).toString()); + frame.postResponse(call.reply(PString.of("BAR"))); + assertEquals(State.Incomplete, frame.getState()); + frame.process(env); + assertEquals(State.OK, frame.getState()); + assertEquals("BAZ", frame.result().get(0).toString()); + } + + @Test + public void testAsyncStackFrame() throws Exception { + logTest("testAsyncStackFrame"); + var env = new EnvImpl(); + var frame = StackFrame.async(() -> PNumber.of(42)); + assertEquals(State.Incomplete, frame.getState()); + frame.process(env); + assertEquals(State.Incomplete, frame.getState()); + var call = env.poll(); + logCall("Received task", call); + assertEquals(EnvImpl.SERVICE, call.to().component()); + assertEquals(TaskService.SUBMIT, call.to().controlID()); + TaskService.Task task = PReference.from(call.args().get(0)) + .flatMap(r -> r.as(TaskService.Task.class)) + .orElseThrow(); + frame.postResponse(call.reply(task.execute())); + assertEquals(State.OK, frame.getState()); + assertEquals(42, PNumber.from(frame.result().get(0)).orElseThrow().toIntValue()); + } + + private static void logTest(String testName) { + if (VERBOSE) { + System.out.println(""); + System.out.println(""); + System.out.println(testName); + System.out.println("===================="); + } + } + + private static void logCall(String msg, Call call) { + if (VERBOSE) { + System.out.println(msg); + System.out.println(call); + } + } + + private static class EnvImpl implements Env { + + private static final ControlAddress ADDRESS = ControlAddress.of("/stack.eval"); + private static final ComponentAddress SERVICE = ComponentAddress.of("/service"); + + private final Queue queue; + private final Lookup lookup; + + private EnvImpl() { + this.queue = new LinkedList<>(); + this.lookup = Lookup.of(new Services() { + @Override + public Optional locate(Class service) { + return Optional.of(SERVICE); + } + + @Override + public Stream locateAll(Class service) { + return locate(service).stream(); + } + }); + } + + @Override + public ControlAddress getAddress() { + return ADDRESS; + } + + @Override + public Lookup getLookup() { + return lookup; + } + + @Override + public PacketRouter getPacketRouter() { + return queue::add; + } + + @Override + public long getTime() { + return 12345; + } + + public Call poll() { + Packet p = queue.poll(); + if (p instanceof Call c) { + return c; + } else { + return null; + } + } + + } + +} From ee1064b3ebed2ebc82bc1c33acde50d89e8c194d Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Fri, 3 May 2024 15:35:35 +0100 Subject: [PATCH 6/7] Add array and map related commands, var and constant commands. --- .../praxislive/script/commands/ArrayCmds.java | 98 ++++++++- .../praxislive/script/commands/BaseCmds.java | 35 +++ .../commands/CoreCommandsInstaller.java | 1 + .../praxislive/script/commands/MapCmds.java | 136 ++++++++++++ .../script/DefaultScriptServiceTest.java | 7 +- .../script/commands/ArrayCmdsTest.java | 206 ++++++++++++++++++ .../script/commands/MapCmdsTest.java | 150 +++++++++++++ .../org/praxislive/script/commands/Utils.java | 143 ++++++++++++ 8 files changed, 772 insertions(+), 4 deletions(-) create mode 100644 praxiscore-script/src/main/java/org/praxislive/script/commands/MapCmds.java create mode 100644 praxiscore-script/src/test/java/org/praxislive/script/commands/ArrayCmdsTest.java create mode 100644 praxiscore-script/src/test/java/org/praxislive/script/commands/MapCmdsTest.java create mode 100644 praxiscore-script/src/test/java/org/praxislive/script/commands/Utils.java diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java index 30772f3d..76cae971 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/ArrayCmds.java @@ -25,6 +25,7 @@ import java.util.Map; import org.praxislive.core.Value; import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PNumber; import org.praxislive.script.Command; import org.praxislive.script.Env; import org.praxislive.script.InlineCommand; @@ -35,23 +36,118 @@ */ class ArrayCmds { - private final static Array ARRAY = new Array(); + private static final Array ARRAY = new Array(); + private static final ArrayGet ARRAY_GET = new ArrayGet(); + private static final ArrayJoin ARRAY_JOIN = new ArrayJoin(); + private static final ArrayRange ARRAY_RANGE = new ArrayRange(); + private static final ArraySize ARRAY_SIZE = new ArraySize(); private ArrayCmds() { } static void install(Map commands) { commands.put("array", ARRAY); + commands.put("array-get", ARRAY_GET); + commands.put("array-join", ARRAY_JOIN); + commands.put("array-range", ARRAY_RANGE); + commands.put("array-size", ARRAY_SIZE); } private static class Array implements InlineCommand { @Override public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.isEmpty()) { + return List.of(PArray.EMPTY); + } PArray ar = args.stream().collect(PArray.collector()); return List.of(ar); } } + private static class ArrayGet implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 2) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PArray array = PArray.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("First argument is not an array")); + + int index = PNumber + .from(args.get(1)) + .orElseThrow(() -> new IllegalArgumentException("Second argument is not a number")) + .toIntValue(); + + return List.of(array.get(index)); + } + + } + + private static class ArrayJoin implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + PArray result = args.stream() + .flatMap(v -> PArray.from(v).stream()) + .flatMap(PArray::stream) + .collect(PArray.collector()); + return List.of(result); + } + + } + + private static class ArrayRange implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() < 2 || args.size() > 3) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PArray array = PArray.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("First argument is not an array")); + + int from, to; + if (args.size() == 2) { + from = 0; + to = PNumber + .from(args.get(1)) + .orElseThrow(() -> new IllegalArgumentException("Second argument is not a number")) + .toIntValue(); + } else { + from = PNumber + .from(args.get(1)) + .orElseThrow(() -> new IllegalArgumentException("Second argument is not a number")) + .toIntValue(); + to = PNumber + .from(args.get(2)) + .orElseThrow(() -> new IllegalArgumentException("Third argument is not a number")) + .toIntValue(); + } + + return List.of(PArray.of(array.asList().subList(from, to))); + } + + } + + private static class ArraySize implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 1) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PArray array = PArray.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("Argument is not an array")); + + return List.of(PNumber.of(array.size())); + } + + } + } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java index d0bc15df..2c659376 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/BaseCmds.java @@ -40,14 +40,19 @@ class BaseCmds { private static final System.Logger LOG = System.getLogger(BaseCmds.class.getName()); + + private static final Constant CONSTANT = new Constant(); private static final Set SET = new Set(); + private static final Var VAR = new Var(); private static final Echo ECHO = new Echo(); private BaseCmds() { } static void install(Map commands) { + commands.put("constant", CONSTANT); commands.put("set", SET); + commands.put("var", VAR); commands.put("echo", ECHO); } @@ -72,6 +77,36 @@ public List process(Env context, Namespace namespace, List args) t } } + private static class Constant implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 2) { + throw new Exception(); + } + String varName = args.get(0).toString(); + Value val = args.get(1); + namespace.createConstant(varName, val); + return List.of(val); + + } + } + + private static class Var implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 2) { + throw new Exception(); + } + String varName = args.get(0).toString(); + Value val = args.get(1); + namespace.createVariable(varName, val); + return List.of(val); + + } + } + private static class Echo implements InlineCommand { @Override diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java index eff593ed..305c555b 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/CoreCommandsInstaller.java @@ -39,6 +39,7 @@ public void install(Map commands) { AtCmds.install(commands); ConnectionCmds.install(commands); FileCmds.install(commands); + MapCmds.install(commands); ScriptCmds.install(commands); } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/commands/MapCmds.java b/praxiscore-script/src/main/java/org/praxislive/script/commands/MapCmds.java new file mode 100644 index 00000000..83078165 --- /dev/null +++ b/praxiscore-script/src/main/java/org/praxislive/script/commands/MapCmds.java @@ -0,0 +1,136 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script.commands; + +import java.util.List; +import java.util.Map; +import org.praxislive.core.Value; +import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PMap; +import org.praxislive.core.types.PNumber; +import org.praxislive.core.types.PString; +import org.praxislive.script.Command; +import org.praxislive.script.Env; +import org.praxislive.script.InlineCommand; +import org.praxislive.script.Namespace; + +/** + * + */ +class MapCmds { + + private static final CreateMap MAP = new CreateMap(); + private static final MapGet MAP_GET = new MapGet(); + private static final MapKeys MAP_KEYS = new MapKeys(); + private static final MapSize MAP_SIZE = new MapSize(); + + private MapCmds() { + } + + static void install(Map commands) { + commands.put("map", MAP); + commands.put("map-get", MAP_GET); + commands.put("map-keys", MAP_KEYS); + commands.put("map-size", MAP_SIZE); + } + + private static class CreateMap implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.isEmpty()) { + return List.of(PMap.EMPTY); + } + + int size = args.size(); + if (size % 2 != 0) { + throw new IllegalArgumentException("Map requires an even number of arguments"); + } + + var builder = PMap.builder(); + for (int i = 0; i < size; i += 2) { + builder.put(args.get(i).toString(), args.get(i + 1)); + } + + return List.of(builder.build()); + } + + } + + private static class MapGet implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 2) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PMap map = PMap.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("Argument is not a map")); + String key = args.get(1).toString(); + + Value result = map.get(key); + if (result == null) { + throw new IllegalArgumentException("Unknown map key"); + } + return List.of(result); + } + + } + + private static class MapKeys implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 1) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PMap map = PMap.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("Argument is not a map")); + + PArray result = map.keys().stream() + .map(PString::of) + .collect(PArray.collector()); + + return List.of(result); + } + + } + + private static class MapSize implements InlineCommand { + + @Override + public List process(Env context, Namespace namespace, List args) throws Exception { + if (args.size() != 1) { + throw new IllegalArgumentException("Incorrect number of arguments"); + } + + PMap map = PMap.from(args.get(0)) + .orElseThrow(() -> new IllegalArgumentException("Argument is not a map")); + + return List.of(PNumber.of(map.size())); + } + + } + +} diff --git a/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java index f445cbe3..3309aa6e 100644 --- a/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java +++ b/praxiscore-script/src/test/java/org/praxislive/script/DefaultScriptServiceTest.java @@ -128,12 +128,13 @@ public void testEvalInline() throws Exception { logTest("testEvalInline"); String script = """ eval --inline { - set X 42 + var X 42 /hub.value $X } + set X [echo $X $X] /hub.value $X eval { - set Y 84 + var Y 84 /hub.value $Y } /hub.value $Y @@ -143,7 +144,7 @@ public void testEvalInline() throws Exception { hub.start(); hub.send("/script.eval", "/hub.result", script); - for (String expected : new String[]{"42", "42", "84"}) { + for (String expected : new String[]{"42", "4242", "84"}) { Call call = hub.poll(); logCall("Value received", call); assertEquals("/hub.value", call.to().toString()); diff --git a/praxiscore-script/src/test/java/org/praxislive/script/commands/ArrayCmdsTest.java b/praxiscore-script/src/test/java/org/praxislive/script/commands/ArrayCmdsTest.java new file mode 100644 index 00000000..9ef5749e --- /dev/null +++ b/praxiscore-script/src/test/java/org/praxislive/script/commands/ArrayCmdsTest.java @@ -0,0 +1,206 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script.commands; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.Value; +import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PBoolean; +import org.praxislive.core.types.PNumber; +import org.praxislive.core.types.PString; +import org.praxislive.script.Command; +import org.praxislive.script.InlineCommand; + +import static org.junit.jupiter.api.Assertions.*; +import static org.praxislive.script.commands.Utils.*; + +/** + * + */ +public class ArrayCmdsTest { + + private static final int V0 = 42; + private static final boolean V1 = true; + private static final String V2 = "FOO"; + private static final PArray V3 = PArray.of( + ControlAddress.of("/root.info"), + ControlAddress.of("/root.meta") + ); + + private static final Map CMDS; + + static { + CMDS = new HashMap<>(); + ArrayCmds.install(CMDS); + } + + @Test + public void testArrayCommand() throws Exception { + logTest("testArrayCommand"); + InlineCommand array = (InlineCommand) CMDS.get("array"); + List values = List.of( + Value.ofObject(V0), + Value.ofObject(V1), + Value.ofObject(V2), + Value.ofObject(V3) + ); + List resultList = array.process(env(), namespace(), values); + logResult("Command result of array", resultList); + assertEquals(1, resultList.size()); + PArray result = PArray.from(resultList.get(0)).orElseThrow(); + assertEquals(4, result.size()); + assertEquals(V0, PNumber.from(result.get(0)).orElseThrow().toIntValue()); + assertEquals(V1, PBoolean.from(result.get(1)).orElseThrow().value()); + assertEquals(V2, PString.from(result.get(2)).orElseThrow().value()); + assertEquals(V3, result.get(3)); + + resultList = array.process(env(), namespace(), List.of()); + logResult("Command result of array no-args", resultList); + assertEquals(1, resultList.size()); + result = PArray.from(resultList.get(0)).orElseThrow(); + assertSame(PArray.EMPTY, result); + } + + @Test + public void testArrayGetCommand() throws Exception { + logTest("testArrayGetCommand"); + InlineCommand arrayGet = (InlineCommand) CMDS.get("array-get"); + PArray array = Stream.of(V0, V1, V2, V3) + .map(Value::ofObject) + .collect(PArray.collector()); + + List resultList = arrayGet.process(env(), namespace(), + List.of(array, PNumber.of(2))); + logResult("Command result of array-get 2", resultList); + assertEquals(1, resultList.size()); + Value result = resultList.get(0); + assertEquals(V2, result.toString()); + resultList = arrayGet.process(env(), namespace(), + List.of(array, PNumber.of(5))); + logResult("Command result of array-get 5", resultList); + assertEquals(1, resultList.size()); + result = resultList.get(0); + assertEquals(V1, PBoolean.from(result).orElseThrow().value()); + resultList = arrayGet.process(env(), namespace(), + List.of(array, PNumber.of(-1))); + logResult("Command result of array-get -1", resultList); + assertEquals(1, resultList.size()); + result = resultList.get(0); + assertEquals(V3, result); + + resultList = arrayGet.process(env(), namespace(), + List.of(PArray.EMPTY, PNumber.of(-1))); + logResult("Command result of array-get -1 on empty array", resultList); + assertEquals(1, resultList.size()); + result = resultList.get(0); + assertSame(PArray.EMPTY, result); + + assertThrows(IllegalArgumentException.class, () -> { + List noResult = arrayGet.process(env(), namespace(), List.of(PNumber.ONE)); + }); + + } + + @Test + public void testArrayJoinCommand() throws Exception { + logTest("testArrayJoinCommand"); + InlineCommand arrayJoin = (InlineCommand) CMDS.get("array-join"); + PArray array1 = Stream.of(V0, V1, V2, V3) + .map(Value::ofObject) + .collect(PArray.collector()); + PArray array2 = Stream.of(V3, V2, V1, V0) + .map(Value::ofObject) + .collect(PArray.collector()); + List resultList = arrayJoin.process(env(), namespace(), + List.of(array1, array2)); + logResult("Command result of array-join", resultList); + assertEquals(1, resultList.size()); + PArray result = PArray.from(resultList.get(0)).orElseThrow(); + assertEquals(8, result.size()); + PArray expected = Stream.of(V0, V1, V2, V3, V3, V2, V1, V0) + .map(Value::ofObject) + .collect(PArray.collector()); + assertEquals(expected, result); + + resultList = arrayJoin.process(env(), namespace(), List.of(array1, PArray.EMPTY)); + logResult("Command result of array-join empty with empty", resultList); + result = PArray.from(resultList.get(0)).orElseThrow(); + assertEquals(array1, result); + + } + + @Test + public void testArrayRangeCommand() throws Exception { + logTest("testArrayRangeCommand"); + InlineCommand arrayRange = (InlineCommand) CMDS.get("array-range"); + PArray array = Stream.of(V0, V1, V2, V3) + .map(Value::ofObject) + .collect(PArray.collector()); + List resultList = arrayRange.process(env(), namespace(), + List.of(array, PNumber.of(3))); + logResult("Command result of array-range 3", resultList); + assertEquals(1, resultList.size()); + PArray result = PArray.from(resultList.get(0)).orElseThrow(); + assertEquals(3, result.size()); + PArray expected = Stream.of(V0, V1, V2) + .map(Value::ofObject) + .collect(PArray.collector()); + assertEquals(expected, result); + resultList = arrayRange.process(env(), namespace(), + List.of(array, PNumber.of(1), PNumber.of(3))); + logResult("Command result of array-range 1 3", resultList); + assertEquals(1, resultList.size()); + result = PArray.from(resultList.get(0)).orElseThrow(); + assertEquals(2, result.size()); + expected = Stream.of(V1, V2) + .map(Value::ofObject) + .collect(PArray.collector()); + assertEquals(expected, result); + + assertThrows(IndexOutOfBoundsException.class, () -> { + List failResult = arrayRange.process(env(), namespace(), + List.of(array, PNumber.of(1), PNumber.of(5))); + }); + + } + + @Test + public void testArraySizeCommand() throws Exception { + logTest("testArraySizeCommand"); + InlineCommand arraySize = (InlineCommand) CMDS.get("array-size"); + PArray array = Stream.of(V0, V1, V2, V3) + .map(Value::ofObject) + .collect(PArray.collector()); + List resultList = arraySize.process(env(), namespace(), + List.of(array)); + logResult("Command result of array-size", resultList); + assertEquals(1, resultList.size()); + int result = PNumber.from(resultList.get(0)).orElseThrow().toIntValue(); + assertEquals(4, result); + } + +} diff --git a/praxiscore-script/src/test/java/org/praxislive/script/commands/MapCmdsTest.java b/praxiscore-script/src/test/java/org/praxislive/script/commands/MapCmdsTest.java new file mode 100644 index 00000000..7dce7264 --- /dev/null +++ b/praxiscore-script/src/test/java/org/praxislive/script/commands/MapCmdsTest.java @@ -0,0 +1,150 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script.commands; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.Value; +import org.praxislive.core.types.PArray; +import org.praxislive.core.types.PBoolean; +import org.praxislive.core.types.PMap; +import org.praxislive.core.types.PNumber; +import org.praxislive.core.types.PString; +import org.praxislive.script.Command; +import org.praxislive.script.InlineCommand; + +import static org.junit.jupiter.api.Assertions.*; +import static org.praxislive.script.commands.Utils.*; + +/** + * + */ +public class MapCmdsTest { + + private static final String K1 = "Key1"; + private static final String K2 = "Key2"; + private static final String K3 = "Key3"; + private static final String K4 = "Key4"; + + private static final int V1 = 42; + private static final boolean V2 = true; + private static final String V3 = "FOO"; + private static final PArray V4 = PArray.of( + ControlAddress.of("/root.info"), + ControlAddress.of("/root.meta") + ); + + private static final PMap BASE_MAP = PMap.of( + K1, V1, K2, V2, K3, V3, K4, V4 + ); + + private static final Map CMDS; + + static { + CMDS = new HashMap<>(); + MapCmds.install(CMDS); + } + + @Test + public void testMapCommand() throws Exception { + logTest("testMapCommand"); + InlineCommand map = (InlineCommand) CMDS.get("map"); + List resultList = map.process(env(), namespace(), + List.of( + PString.of(K1), Value.ofObject(V1), + PString.of(K2), Value.ofObject(V2), + PString.of(K3), Value.ofObject(V3), + PString.of(K4), Value.ofObject(V4) + )); + logResult("Command result of map", resultList); + assertEquals(1, resultList.size()); + PMap result = PMap.from(resultList.get(0)).orElseThrow(); + assertEquals(BASE_MAP, result); + + resultList = map.process(env(), namespace(), List.of()); + logResult("Command result of map-empty", resultList); + assertEquals(1, resultList.size()); + result = PMap.from(resultList.get(0)).orElseThrow(); + assertSame(PMap.EMPTY, result); + + assertThrows(IllegalArgumentException.class, () -> { + List failResult = map.process(env(), namespace(), + List.of( + PString.of(K1), Value.ofObject(V1), + PString.of(K2), Value.ofObject(V2), + PString.of(K3), Value.ofObject(V3), + PString.of(K4) + )); + }); + + } + + @Test + public void testMapGetCommand() throws Exception { + logTest("testMapGetCommand"); + InlineCommand mapGet = (InlineCommand) CMDS.get("map-get"); + List resultList = mapGet.process(env(), namespace(), + List.of(BASE_MAP, PString.of(K3))); + logResult("Command result of map-get key3", resultList); + assertEquals(1, resultList.size()); + assertEquals(V3, resultList.get(0).toString()); + + assertThrows(IllegalArgumentException.class, () -> { + List failResult = mapGet.process(env(), namespace(), + List.of(PMap.EMPTY, PString.of(K3))); + }); + + } + + @Test + public void testMapKeysCommand() throws Exception { + logTest("testMapKeysCommand"); + InlineCommand mapKeys = (InlineCommand) CMDS.get("map-keys"); + List resultList = mapKeys.process(env(), namespace(), List.of(BASE_MAP)); + logResult("Command result of map-keys", resultList); + assertEquals(1, resultList.size()); + PArray result = PArray.from(resultList.get(0)).orElseThrow(); + PArray expected = PArray.of( + PString.of(K1), + PString.of(K2), + PString.of(K3), + PString.of(K4) + ); + assertEquals(expected, result); + } + + @Test + public void testMapSizeCommand() throws Exception { + logTest("testMapSizeCommand"); + InlineCommand mapSize = (InlineCommand) CMDS.get("map-size"); + List resultList = mapSize.process(env(), namespace(), List.of(BASE_MAP)); + logResult("Command result of map-size", resultList); + assertEquals(1, resultList.size()); + int result = PNumber.from(resultList.get(0)).orElseThrow().toIntValue(); + assertEquals(BASE_MAP.size(), result); + } + +} diff --git a/praxiscore-script/src/test/java/org/praxislive/script/commands/Utils.java b/praxiscore-script/src/test/java/org/praxislive/script/commands/Utils.java new file mode 100644 index 00000000..079801ca --- /dev/null +++ b/praxiscore-script/src/test/java/org/praxislive/script/commands/Utils.java @@ -0,0 +1,143 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Neil C Smith. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License version 3 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * version 3 for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this work; if not, see http://www.gnu.org/licenses/ + * + * + * Please visit https://www.praxislive.org if you need additional information or + * have any questions. + */ +package org.praxislive.script.commands; + +import java.util.HashMap; +import java.util.Map; +import org.praxislive.core.ControlAddress; +import org.praxislive.core.Lookup; +import org.praxislive.core.PacketRouter; +import org.praxislive.script.Command; +import org.praxislive.script.Env; +import org.praxislive.script.Namespace; +import org.praxislive.script.Variable; + +/** + * + */ +class Utils { + + private static final boolean VERBOSE = Boolean.getBoolean("praxis.test.verbose"); + + private Utils() { + } + + static Env env() { + return new EmptyEnv(); + } + + static Namespace namespace() { + return new NS(); + } + + static void logTest(String testName) { + if (VERBOSE) { + System.out.println(); + System.out.println(testName); + System.out.println("=================="); + } + } + + static void logResult(String description, Object value) { + if (VERBOSE) { + System.out.println(description); + System.out.println(value); + } + } + + private static class EmptyEnv implements Env { + + @Override + public ControlAddress getAddress() { + return ControlAddress.of("/dev.null"); + } + + @Override + public Lookup getLookup() { + return Lookup.EMPTY; + } + + @Override + public PacketRouter getPacketRouter() { + return p -> { + }; + } + + @Override + public long getTime() { + return System.nanoTime(); + } + + } + + private static class NS implements Namespace { + + private final NS parent; + private final Map variables; + private final Map commands; + + private NS() { + this(null); + } + + private NS(NS parent) { + this.parent = parent; + variables = new HashMap<>(); + commands = Map.of(); + } + + @Override + public Variable getVariable(String id) { + Variable var = variables.get(id); + if (var == null && parent != null) { + return parent.getVariable(id); + } else { + return var; + } + } + + @Override + public void addVariable(String id, Variable var) { + if (variables.containsKey(id)) { + throw new IllegalArgumentException(); + } + variables.put(id, var); + } + + @Override + public Command getCommand(String id) { + return commands.get(id); + } + + @Override + public void addCommand(String id, Command cmd) { + throw new UnsupportedOperationException(); + } + + @Override + public Namespace createChild() { + return new NS(this); + } + + } + +} From 3015f489e1167b403b4eeeb70ab3feb7d9e983cd Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Fri, 3 May 2024 15:55:19 +0100 Subject: [PATCH 7/7] Use PError for wrapping script command exceptions. --- .../java/org/praxislive/script/AbstractSingleCallFrame.java | 3 +-- .../src/main/java/org/praxislive/script/InlineCommand.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java index d2662932..715239ac 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/AbstractSingleCallFrame.java @@ -26,7 +26,6 @@ import org.praxislive.core.Call; import org.praxislive.core.Value; import org.praxislive.core.types.PError; -import org.praxislive.core.types.PReference; /** * An abstract {@link StackFrame} for commands that need to make a stack frame @@ -78,7 +77,7 @@ public final StackFrame process(Env env) { } env.getPacketRouter().route(call); } catch (Exception ex) { - result = List.of(PReference.of(ex)); + result = List.of(PError.of(ex)); state = State.Error; } } diff --git a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java index e421b0b8..6368bddc 100644 --- a/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java +++ b/praxiscore-script/src/main/java/org/praxislive/script/InlineCommand.java @@ -24,7 +24,7 @@ import java.util.List; import org.praxislive.core.Call; import org.praxislive.core.Value; -import org.praxislive.core.types.PReference; +import org.praxislive.core.types.PError; /** * Simple subtype of {@link Command} that can be executed and produce a result @@ -96,7 +96,7 @@ public StackFrame process(Env env) { result = command.process(env, namespace, args); state = State.OK; } catch (Exception ex) { - result = List.of(PReference.of(ex)); + result = List.of(PError.of(ex)); state = State.Error; } }