From c728630555460981ea5053203965257dd3199248 Mon Sep 17 00:00:00 2001 From: jrte Date: Fri, 21 Jun 2024 06:33:19 -0300 Subject: [PATCH] WIP: Implement receptor effector base class - models must be rebuilt - changes in Transducer, Transductor - changed encoding of actions in transition matrix - was (effector << 16 | parameter index) - now (parameter << 16 | effector index) - wip for receptor effectors - add constructor parameter to receive bound transducer name - add constructor parameter to receive ordered list of receiver field names - add resetReceivers() method to reset receiver fields to default (initial) values - add Receiver.toString() - add IEffector.isProxy() - proxy effectors are involved in parameter compilation only - Model no longer calls setOutput(IOutput) for proxy effectors - implemented in BaseEffector and Model - add IOutput.isTransducerRunning(transducerName) - receptor effector asserts if bound transducer not top of transducer stack - implemented in Transductor - remove redundant abstract overrides from BaseParametric effector - fix up error message in FileRunner Signed-off-by: jrte --- README.md | 2 +- etc/sh/bench | 6 +- .../characterforming/jrte/engine/Model.java | 8 +- .../jrte/engine/ModelCompiler.java | 44 +++---- .../jrte/engine/ModelLoader.java | 2 +- .../jrte/engine/Transducer.java | 6 +- .../jrte/engine/Transductor.java | 74 ++++++------ .../jrte/test/FileRunner.java | 4 +- .../characterforming/ribose/IEffector.java | 10 ++ src/com/characterforming/ribose/IOutput.java | 8 ++ .../ribose/base/BaseEffector.java | 10 +- .../ribose/base/BaseParametricEffector.java | 9 -- .../ribose/base/BaseReceptorEffector.java | 109 ++++++++++++------ .../ribose/base/Receiver.java | 25 ++-- 14 files changed, 190 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 9b6c78c..4bd14e2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Ginr is the star of the ribose circus. It was developed by J Howard Johnson at t Ginr subsequently disappeared from the public domain and has only recently been [published](https://github.com/ntozubod/ginr) with an open source license on GitHub. It has been upgraded with substantial improvements, including 32-bit state and symbol enumerators and compiler support for transcoding Unicode symbols to UTF-8 bytes. It provides a [full complement of algebraic operators](https://github.com/jrte/ribose/wiki/Ginr) that can be applied to reliably produce very complex (and very large) automata. Large and complex patterns can be decomposed into smaller and simpler patterns, compiled to FSTs, and reconstituted on ribose runtime stacks, just as complex procedural algorithms in Java are decomposed into simpler methods that Java threads orchestrate on call stacks in the JVM runtime. ## Ribose -Ribose is a proof of concept exercise intended to demonstrate the general idea of pattern oriented information processing. It specializes ginr to express transducers using terms of the form ``(A b, X[`Y` ...] ...)``, where `A` is a pattern involving input symbols, `b` is an input symbol that is not a prefix of `A`, `X` is an effector that is invoked when `b` is read, and \``Y ...`\` is a list of parameters bound to `X`. +Ribose is a proof of concept exercise intended to demonstrate the general idea of pattern oriented information processing. It specializes ginr to express transducers using terms of the form ``(A b, X[`Y` ...] ...)``, where `A` is a pattern involving input symbols, `b` is an input symbol that is not a prefix of `A`, `X` is the first effector invoked when `b` is read, and \``Y ...`\` is a list of parameters bound to `X`. ## An Overview and an Example Ribose suggests a pattern-oriented approach to information that minimizes dependency on external libraries and could reduce complexity, vulnerability and development and runtime costs in information workflows. Ribose generalizes the *transducer* design pattern that is commonly applied to `filter`, `map` and `reduce` collections of data in functional programming paradigms. Common usage of this design pattern treats the presentation of inputs as a simple series **T\*** without structure. Ribose extends and refines this design pattern, allowing transducers to precisely navigate (filter), map (select effect vector) and reduce (execute effect vector) complex information under the direction of syntactic cues in the input. diff --git a/etc/sh/bench b/etc/sh/bench index 9e20f0d..d3b1386 100755 --- a/etc/sh/bench +++ b/etc/sh/bench @@ -20,8 +20,8 @@ while [[ ! "$1" =~ ^(--tsv|[0-9]+) ]]; do vmargs="$vmargs $1"; shift if (($#==0)); then - echo "Usage: bench [--tsv] " - echo "Use --tsv for tab-delimited output per etc/benchmarks/ribose.ods" + echo "Usage: bench [--tsv] ..." + echo "Use --tsv for tab-delimited output" exit 0 fi done @@ -32,7 +32,7 @@ if [[ "$1" == "--tsv" ]]; then fi if (($#<4)); then echo "Usage: bench [--tsv] ..." - echo "Use --tsv for tab-delimited output per etc/benchmarks/ribose.ods" + echo "Use --tsv for tab-delimited output" exit 1 fi n=$1 diff --git a/src/com/characterforming/jrte/engine/Model.java b/src/com/characterforming/jrte/engine/Model.java index 21e09b3..81b3de8 100644 --- a/src/com/characterforming/jrte/engine/Model.java +++ b/src/com/characterforming/jrte/engine/Model.java @@ -50,7 +50,6 @@ import com.characterforming.ribose.base.Bytes; import com.characterforming.ribose.base.Codec; import com.characterforming.ribose.base.CompilationException; -import com.characterforming.ribose.base.EffectorException; import com.characterforming.ribose.base.ModelException; import com.characterforming.ribose.base.Signal; @@ -371,8 +370,8 @@ public boolean map(PrintStream mapWriter) { for (Map.Entry m : this.transducerOrdinalMap.entrySet()) transducerIndex[m.getValue()] = m.getKey(); for (int transducerOrdinal = 0; transducerOrdinal < transducerIndex.length; transducerOrdinal++) { - mapWriter.printf("%1$6d transducer %2$s%n", transducerOrdinal, - transducerIndex[transducerOrdinal].asString()); + mapWriter.printf("%1$6d transducer %2$s @ offset %3$d%n", transducerOrdinal, + transducerIndex[transducerOrdinal].asString(), this.transducerOffsetIndex[transducerOrdinal]); Map fieldMap = this.transducerFieldMaps.get(transducerOrdinal); Bytes[] fields = new Bytes[fieldMap.size()]; for (Entry e : fieldMap.entrySet()) @@ -429,12 +428,11 @@ protected boolean checkTargetEffectors(ITarget target, IEffector[] boundFx) { return checked; } - protected Argument[][] compileModelParameters(List errors) throws EffectorException { + protected Argument[][] compileModelParameters(List errors) { Argument[][] effectorArguments = new Argument[this.proxyEffectors.length][]; final Map effectorMap = this.getEffectorOrdinalMap(); for (int effectorOrdinal = 0; effectorOrdinal < this.proxyEffectors.length; effectorOrdinal++) { HashMap parametersMap = this.effectorParametersMaps.get(effectorOrdinal); - this.proxyEffectors[effectorOrdinal].setOutput(this.proxyTransductor); if (this.proxyEffectors[effectorOrdinal] instanceof BaseParametricEffector parametricEffector) { if (parametersMap != null) { assert parametersMap != null: String.format("Effector parameters map is null for %1$s effector", diff --git a/src/com/characterforming/jrte/engine/ModelCompiler.java b/src/com/characterforming/jrte/engine/ModelCompiler.java index 55b2e5e..8b6bc9d 100755 --- a/src/com/characterforming/jrte/engine/ModelCompiler.java +++ b/src/com/characterforming/jrte/engine/ModelCompiler.java @@ -80,7 +80,6 @@ public final class ModelCompiler extends Model implements ITarget, AutoCloseable private int[][][] kernelMatrix; private int transition; - record Transition (int from, int to, int tape, Bytes symbol, boolean isFinal) {} record Header (int version, int tapes, int transitions, int states, int symbols) {} @@ -90,7 +89,8 @@ final class HeaderEffector extends BaseReceptorEffector { public int version = -1, tapes = -1, transitions = -1, states = -1, symbols = -1; HeaderEffector(ModelCompiler compiler) throws CharacterCodingException { - super(compiler, "header"); + super(compiler, "header", "Automaton", + new String[] { "version", "tapes", "transitions", "states", "symbols" }); super.setEffector(this); } @@ -99,6 +99,7 @@ public int invoke(int parameterIndex) throws EffectorException { int rtx = super.invoke(parameterIndex); super.getTarget().putHeader(new Header( this.version, this.tapes, this.transitions, this.states, this.symbols)); + super.resetReceivers(parameterIndex); return rtx; } } @@ -109,7 +110,8 @@ final class TransitionEffector extends BaseReceptorEffector { public byte[] symbol = Bytes.EMPTY_BYTES; TransitionEffector(ModelCompiler compiler) throws CharacterCodingException { - super(compiler, "transition"); + super(compiler, "transition", "Automaton", + new String[] { "from","to","tape", "length","symbol" }); super.setEffector(this); } @@ -119,6 +121,7 @@ public int invoke(int parameterIndex) throws EffectorException { boolean isFinal = this.to == 1 && this.tape == 0 && this.symbol.length == 0; ModelCompiler.this.putTransition(new Transition( this.from, this.to, this.tape, new Bytes(this.symbol), isFinal)); + super.resetReceivers(parameterIndex); return rtx; } } @@ -409,8 +412,8 @@ void saveTransducer() throws ModelException, CharacterCodingException { final int fieldCount = super.getFieldCount(this.transducerOrdinal); double sparsity = 100 * (1 - ((double)nTransitions / (double)(nStates * nInputs))); String info = String.format( - "%1$21s %2$5d input classes %3$5d states %4$5d transitions; %5$5d fields; (%6$.0f%% nul)", - this.getTransducerName()+":", nInputs, nStates, transitionCount, fieldCount, sparsity); + "%1$21s: %2$5d input classes %3$5d states %4$5d transitions; %5$5d fields; (%6$.0f%% nul)", + this.getTransducerName(), nInputs, nStates, transitionCount, fieldCount, sparsity); super.rtcLogger.log(Level.INFO, () -> info); System.out.println(info); } @@ -545,11 +548,10 @@ private Chain chain(final Transition transition) { : "Invalid tape number for chain(InrTransition) : " + transition.toString(); if (transition.isFinal) return null; - boolean fail = false; - int effectorOrdinal = -1; - int effectorPos = 0, parameterPos = 0; - byte[][] parameterList = new byte[8][]; - int[] effectorVector = new int[8]; + int errorCount = this.errors.size(); + int effectorOrdinal = -1, effectorPos = 0, parameterPos = 0; + byte[][] parameterList = new byte[16][]; + int[] effectorVector = new int[16]; ArrayList outT = null; for ( outT = this.getTransitions(transition.to); @@ -557,8 +559,7 @@ private Chain chain(final Transition transition) { outT = this.getTransitions(outT.get(0).to) ) { final Transition t = outT.get(0); - switch (t.tape) { - case 1: + if (t.tape == 1) { if ((effectorPos + 3) >= effectorVector.length) effectorVector = Arrays.copyOf(effectorVector, effectorVector.length > 4 ? (effectorVector.length * 3) >> 1 : 5); if (effectorOrdinal >= 0 && parameterPos > 0) { @@ -574,27 +575,20 @@ private Chain chain(final Transition transition) { effectorOrdinal = super.getEffectorOrdinal(effectorSymbol); if (effectorOrdinal >= 0) effectorVector[effectorPos++] = effectorOrdinal; - else { + else this.addError(String.format("%1$s: Unrecognized effector '%2$s'", this.getTransducerName(), effectorSymbol.toString())); - fail = true; - } parameterPos = 0; - break; - case 2: + } else if (t.tape == 2) { if (effectorOrdinal >= 0) { if (parameterPos >= parameterList.length) parameterList = Arrays.copyOf(parameterList, parameterList.length > 4 ? (parameterList.length * 3) >> 1 : 5); parameterList[parameterPos] = t.symbol.bytes(); ++parameterPos; } - break; - default: + } else this.addError(String.format("%1$s: Invalid tape number %2$d (tape 1 or 2 expected)", this.getTransducerName(), t.tape)); - fail = true; - break; - } } int outS = -1; if (outT == null || outT.isEmpty() || outT.get(0).isFinal) @@ -605,9 +599,8 @@ else if (outT.get(0).tape == 0) assert outT.size() > 1; this.addError(String.format(AMBIGUOUS_STATE_MESSAGE, this.getTransducerName(), outT.get(0).from)); - fail = true; } - if (!fail) { + if (this.errors.size() == errorCount) { assert effectorVector.length > (effectorPos + 2); assert effectorPos == 0 || effectorOrdinal == effectorVector[effectorPos - 1]; assert parameterPos == 0 || effectorPos > 0; @@ -621,8 +614,9 @@ effectorOrdinal, new Argument(this.transducerOrdinal, assert effectorVector.length >= effectorPos; if (effectorVector.length > effectorPos) effectorVector = Arrays.copyOf(effectorVector, effectorPos); + return new Chain(effectorVector, outS); } - return fail? null : new Chain(effectorVector, outS); + return null; } private Integer[] getInrStates() { diff --git a/src/com/characterforming/jrte/engine/ModelLoader.java b/src/com/characterforming/jrte/engine/ModelLoader.java index 6741d8e..4c0c07e 100644 --- a/src/com/characterforming/jrte/engine/ModelLoader.java +++ b/src/com/characterforming/jrte/engine/ModelLoader.java @@ -283,7 +283,7 @@ public void decompile(final String transducerName) else { int effectorOrdinal = -1 * effectorVectors[index++]; if (super.proxyEffectors[effectorOrdinal] instanceof BaseParametricEffector effector) { - int parameterOrdinal = Transducer.parameter(effectorVectors[index++]); + int parameterOrdinal = effectorVectors[index++]; System.out.printf(" %s[", effectorNames[effectorOrdinal]); System.out.printf(" %s ]", effector.showParameterTokens(parameterOrdinal)); } diff --git a/src/com/characterforming/jrte/engine/Transducer.java b/src/com/characterforming/jrte/engine/Transducer.java index f8abc39..1ab333d 100644 --- a/src/com/characterforming/jrte/engine/Transducer.java +++ b/src/com/characterforming/jrte/engine/Transducer.java @@ -77,17 +77,17 @@ static int action(long transition) { // encode effector and paramter ordinals in action commponent of transition matrix cell static int action(int effect, int parameter) { - return (effect << 16) | parameter; + return ((parameter + 1) << 16) | effect; } // decode effector ordinal from action commponent of transition matrix cell static int effector(int action) { - return action >>> 16; + return action & 0xffff; } // decode parameter ordinal from action commponent of transition matrix cell static int parameter(int action) { - return action & 0xffff; + return (action >>> 16) - 1; } // decode signal from RTX_SIGNAL code from an effector diff --git a/src/com/characterforming/jrte/engine/Transductor.java b/src/com/characterforming/jrte/engine/Transductor.java index 4437f3b..48fa9af 100644 --- a/src/com/characterforming/jrte/engine/Transductor.java +++ b/src/com/characterforming/jrte/engine/Transductor.java @@ -228,6 +228,11 @@ public int getTransducerFieldndex() throws EffectorException { throw new EffectorException("Not valid for proxy transductor"); } + @Override + public boolean isTransducerRunning(String transducerName) { + return this.transducerStack.peek().transducer.getName().equals(transducerName); + } + @Override // @see com.characterforming.ribose.IOutput#asString(int, String) public String asString(int fieldOrdinal, String defaultValue) throws EffectorException { if (!this.isProxy) { @@ -415,7 +420,9 @@ public float asFloat(int fieldOrdinal, float defaultValue) throws EffectorExcept @Override // @see com.characterforming.ribose.IOutput#isProxy() public boolean isProxy() { - return this.isProxy; + if (this.effectors != null && this.effectors[0] != null) + return this.effectors[0].isProxy(); + return true; } @Override // @see com.characterforming.ribose.IOutput#rtcLogger() @@ -558,38 +565,39 @@ public ITransductor run() throws EffectorException, DomainErrorException { } int action = NIL; -S: do { - switch (this.matchMode) { - // trap runs in (nil* paste*)* effector space - case MATCH_NONE: +S: do + if (this.matchMode == MATCH_NONE) do { final long transition = transitionMatrix[state + inputFilter[token]]; last = state; state = Transducer.state(transition); action = Transducer.action(transition); - if (action == PASTE) - this.selection.paste((byte)token); - else if (action != NIL) - break S; - token = input.position < input.limit ? input.array[input.position++] & 0xff : -1; - } while (token >= 0); - break; - // absorb self-referencing (msum,mscan) or sequential (mproduct) transitions with nil effect - case MATCH_SUM: - token = sumTrap(input, token); - break; - case MATCH_PRODUCT: - token = productTrap(input, token); - break; - case MATCH_SCAN: - token = scanTrap(input, token); - break; - default: - assert false; - break; + if (action != NIL) { + if (action == PASTE) + this.selection.paste((byte)token); + else if (action == SELECT) { + this.selected = Model.ANONYMOUS_FIELD_ORDINAL; + this.selection = this.transducerStack.value(this.selected); + } + else break S; + } + if (input.position < input.limit) + token = input.array[input.position++] & 0xff; + else + continue I; + } while (true); + else { + if (this.matchMode == MATCH_SCAN) + token = scanTrap(input, token); + else if (this.matchMode == MATCH_SUM) + token = sumTrap(input, token); + else if (this.matchMode == MATCH_PRODUCT) + token = productTrap(input, token); + else + assert false; + if (token < 0) + continue I; } - } while (token >= 0); - if (token < 0) - continue I; + while (true); // effect action and check for transducer or input stack adjustment int aftereffects = IEffector.RTX_NONE; @@ -712,17 +720,17 @@ private int effect(int action, int token) // invoke a scalar effector or vector of effectors and record side effects on transducer and input stacks private int effect(int action, int token, int[] effectorVector) throws IOException, EffectorException { - assert action < 0; - int index = 0 - action; int aftereffects = IEffector.RTX_NONE; + int index = 0 - action; + assert index > 0; E: do { action = effectorVector[index++]; - if (action < 0 ) { + if (action > 0 ) + aftereffects |= this.effect(action, token); + else if (action < 0) { if (this.effectors[0 - action] instanceof IParametricEffector e) aftereffects |= e.invoke(effectorVector[index++]); else assert false; - } else if (action != NUL) { - aftereffects |= this.effect(action, token); } else break E; } while (true); diff --git a/src/com/characterforming/jrte/test/FileRunner.java b/src/com/characterforming/jrte/test/FileRunner.java index 74fb3b0..106cc06 100644 --- a/src/com/characterforming/jrte/test/FileRunner.java +++ b/src/com/characterforming/jrte/test/FileRunner.java @@ -76,7 +76,9 @@ public static void main(final String[] args) { long byteLength = (int)f.length(); assert byteLength < Integer.MAX_VALUE; if (byteLength <= 0 || byteLength > Integer.MAX_VALUE) { - System.out.println(String.format("Input file is empty or way too big: %s", inputPath)); + System.out.println(String.format( + "Input file is empty or way too big: %1$s (%2$d bytes)", + inputPath, byteLength)); System.exit(1); } int exitCode = 1; diff --git a/src/com/characterforming/ribose/IEffector.java b/src/com/characterforming/ribose/IEffector.java index db6696f..db37698 100644 --- a/src/com/characterforming/ribose/IEffector.java +++ b/src/com/characterforming/ribose/IEffector.java @@ -126,6 +126,16 @@ public interface IEffector { void setOutput(IOutput output) throws EffectorException; + /** + * Check whether this is a proxy effector, instantiated in the model for parameter + * compilation. Proxy effectors do not receive {@link #setOutput(IOutput)} and are + * passivated, losing target access through {@link #getTarget()}, after compilation + * is complete. + * + * @return true if this is a proxy effector + */ + boolean isProxy(); + /** * This method is invoked at runtime when triggered by an input transition. Normally * implementation will return {@code IEffector.RTX_NONE}, which has no effect. In some diff --git a/src/com/characterforming/ribose/IOutput.java b/src/com/characterforming/ribose/IOutput.java index b78983a..3572b16 100755 --- a/src/com/characterforming/ribose/IOutput.java +++ b/src/com/characterforming/ribose/IOutput.java @@ -98,6 +98,14 @@ public interface IOutput { */ boolean isProxy(); + /** + * Check whether a specific transducer is running + * + * @param transducerName the UTF-8 encoding of the transducer name + * @return true if the transducer is running + */ + boolean isTransducerRunning(String transducerName); + /** * Get a localized field ordinal from an effector parameter token. This method may * be called by proxy effectors during paramter compilation. The localized ordinal diff --git a/src/com/characterforming/ribose/base/BaseEffector.java b/src/com/characterforming/ribose/base/BaseEffector.java index 0d8d38d..e72ca76 100644 --- a/src/com/characterforming/ribose/base/BaseEffector.java +++ b/src/com/characterforming/ribose/base/BaseEffector.java @@ -67,6 +67,8 @@ protected BaseEffector(final T target, final String name) */ @Override // @see com.characterforming.ribose.base.IEffector#nvoke() public int invoke() throws EffectorException { + assert !this.isProxy() + : String.format("Proxy effector '%1$s' cannot receive invoke()", this.name); return IEffector.RTX_NONE; } @@ -83,6 +85,11 @@ public void setOutput(IOutput output) this.output = output; } + @Override // @see com.characterforming.ribose.base.IEffector#isProxy() + public boolean isProxy() { + return this.output == null; + } + @Override // @see com.characterforming.ribose.base.IEffector#getName() public final Bytes getName() { return this.name; @@ -100,7 +107,8 @@ public String toString() { @Override // com.characterforming.ribose.IEffector#passivate() public void passivate() { + assert this.isProxy() + : String.format("Live effector '%1$s' cannot receive passivate()", this.name); this.target = null; - this.output = null; } } diff --git a/src/com/characterforming/ribose/base/BaseParametricEffector.java b/src/com/characterforming/ribose/base/BaseParametricEffector.java index a376948..c3cb1d3 100644 --- a/src/com/characterforming/ribose/base/BaseParametricEffector.java +++ b/src/com/characterforming/ribose/base/BaseParametricEffector.java @@ -68,15 +68,6 @@ protected BaseParametricEffector(final T target, final String name) super(target, name); } - @Override // @see com.characterforming.ribose.base.IParametricEffector#nvoke(int) - public abstract int invoke(int parameterIndex) throws EffectorException; - - @Override // @see com.characterforming.ribose.base.IParametricEffector#allocateParameters(int) - public abstract P[] allocateParameters(int parameterCount); - - @Override // @see com.characterforming.ribose.base.IParametricEffector#compileParameter(IToken[]) - public abstract P compileParameter(IToken[] parameterTokens) throws TargetBindingException; - @Override public String showParameterType() { return this.parameters != null && this.parameters.length > 0 diff --git a/src/com/characterforming/ribose/base/BaseReceptorEffector.java b/src/com/characterforming/ribose/base/BaseReceptorEffector.java index 7557131..be7c210 100644 --- a/src/com/characterforming/ribose/base/BaseReceptorEffector.java +++ b/src/com/characterforming/ribose/base/BaseReceptorEffector.java @@ -26,6 +26,7 @@ import com.characterforming.ribose.IEffector; import com.characterforming.ribose.ITarget; import com.characterforming.ribose.IToken; +import com.characterforming.ribose.IOutput; /** * Base class for receptor effectors, which map transducer fields to Java receiver fields @@ -39,15 +40,14 @@ * method; subclasses may override this with their own {@code invoke()} implementation. *

* A receptor effector can have only one parametric form, which lists the transducer - * fields to be received. It can be called from any transducer that expresses the same - * fields, but the same fields must be supplied in each case. Subclass receiver fields - * must be {@code public} and may be of any primitive Java type or a {@code byte[]} or - * {@code char[]} array. When the receptor effector is invoked it calls {@link - * BaseReceptorEffector#invoke(int)}, which overwrites the receiver fields in the - * effector with the converted value from the respective transducer fields, or with - * the default value if a transducer field is empty. The effector immediately dispatches - * the receiver field values into the target. The receiver field values are then stale - * and should not be used outside the scope of the effector's {@code #invoke()} method. + * fields to be received, and can only be called from the transducer that expresses those + * fields. Subclass receiver fields must be {@code public} and may be of any primitive + * Java type or a {@code byte[]} or {@code char[]} array. When the receptor effector s + * invoked it calls {@link BaseReceptorEffector#invoke(int)}, which overwrites the + * receiver fields in the effector with the converted value from the respective transducer + * fields, or with the default value if a transducer field is empty. The effector immediately + * dispatches the receiver field values into the target. The receiver field values are then + * stale and should not be used outside the scope of the effector's {@code #invoke()} method. *

* The subclass effector must call {@link setEffector(BaseReceptorEffector)} from * its constructor after calling the constructor for this base class. @@ -61,8 +61,9 @@ * public int version = -1, tapes = -1, transitions = -1, states = -1, symbols = -1; * * HeaderEffector(ModelCompiler compiler) throws CharacterCodingException { - * // compiler is the effector's target (super.getTarget()) - * super(compiler, "header"); + * // compiler is the effector's target, automaton is the name of the transducer + * super(compiler, "header", "automaton", + * new String[] { "version", "tapes", "transitions", "states", "symbols" }); * // superclass uses reflection to identify receiver fields and default values * super.setEffector(this); * } @@ -83,16 +84,26 @@ public abstract class BaseReceptorEffector extends BaseParametricEffector { private BaseReceptorEffector effector; + private byte[][] receptorFieldNames; + private final String transducerName; /** * Constructor * * @param target the transductor target bound to the effector * @param effectorName the effector name + * @param transducerName the name of the transducer the subclass is bound to + * @param receiverFieldNames enumerates subclass receiver fields * @throws CharacterCodingException if the effector name can not be encoded */ - protected BaseReceptorEffector(final T target, final String effectorName) throws CharacterCodingException { + protected BaseReceptorEffector(T target, String effectorName, String transducerName, String[] receiverFieldNames) + throws CharacterCodingException { super(target, effectorName); + this.transducerName = transducerName; + this.receptorFieldNames = new byte[receiverFieldNames.length][]; + for (int i = 0; i < receiverFieldNames.length; i++) + this.receptorFieldNames[i] = Codec.encode(receiverFieldNames[i]).bytes(); + this.effector = null; } /** @@ -108,42 +119,48 @@ protected void setEffector(BaseReceptorEffector effector) { @Override // @see com.characterforming.ribose.IParametricEffector#invoke(int) public int invoke(int parameter) throws EffectorException { - assert parameter == 0; - for (Receiver r : super.parameters[0]) { + assert parameter == 0 && super.parameters.length == 1; + assert super.output.isTransducerRunning(this.transducerName); + for (Receiver r : super.parameters[parameter]) { try { + final Field fld = r.field(); + final int idx = r.fieldIndex(); + final Object def = r.defaultValue(); + final IEffector eff = this.effector; + final IOutput out = super.output; switch (r.type()) { case BOOLEAN: - r.field().setBoolean(this.effector, super.output.asBoolean(r.fieldIndex(), (Boolean)r.defaultValue())); + fld.setBoolean(eff, out.asBoolean(idx, (boolean)def)); break; case BYTE: - r.field().setByte(this.effector, super.output.asByte(r.fieldIndex(), (Byte)r.defaultValue())); + fld.setByte(eff, out.asByte(idx, (byte)def)); break; case BYTES: - r.field().set(this.effector, super.output.asBytes(r.fieldIndex(), (byte[])r.defaultValue())); + fld.set(eff, out.asBytes(idx, (byte[])def)); break; case CHAR: - r.field().setChar(this.effector, super.output.asChar(r.fieldIndex(), (Character)r.defaultValue())); + fld.setChar(eff, out.asChar(idx, (char)def)); break; case CHARS: - r.field().set(this.effector, super.output.asChars(r.fieldIndex(), (char[])r.defaultValue())); + fld.set(eff, out.asChars(idx, (char[])def)); break; case STRING: - r.field().set(this.effector, super.output.asString(r.fieldIndex(), (String)r.defaultValue())); + fld.set(eff, out.asString(idx, (String)def)); break; case SHORT: - r.field().setShort(this.effector, super.output.asShort(r.fieldIndex(), (Short)r.defaultValue())); + fld.setShort(eff, out.asShort(idx, (short)def)); break; case INT: - r.field().setInt(this.effector, super.output.asInteger(r.fieldIndex(), (Integer)r.defaultValue())); + fld.setInt(eff, out.asInteger(idx, (int)def)); break; case LONG: - r.field().setLong(this.effector, super.output.asLong(r.fieldIndex(), (Long)r.defaultValue())); + fld.setLong(eff, out.asLong(idx, (long)def)); break; case DOUBLE: - r.field().setDouble(this.effector, super.output.asDouble(r.fieldIndex(), (Double)r.defaultValue())); + fld.setDouble(eff, out.asDouble(idx, (double)def)); break; case FLOAT: - r.field().set(this.effector, super.output.asFloat(r.fieldIndex(), (Float)r.defaultValue())); + fld.setFloat(eff, out.asFloat(idx, (float)def)); break; default: throw new EffectorException(String.format( @@ -171,7 +188,7 @@ public Receiver[] compileParameter(final IToken[] parameterList) throws TargetBi "%1$s.%2$s[]: receptor effector field list cannot be overridden", super.target.getName(), super.getName())); int receiver = 0; - Receiver[] receivers = super.parameters[0] = new Receiver[parameterList.length]; + Receiver[] receivers = new Receiver[parameterList.length]; for (IToken token : parameterList) if (token.isField()) { String fieldName = "?"; @@ -197,33 +214,51 @@ public Receiver[] compileParameter(final IToken[] parameterList) throws TargetBi return receivers; } + /*** + * Reset receiver fields to default (initial) values after dispatching received + * values in {@link #invoke(int)}. + * + * @param parameterIndex identifies a subset of fields to reset + * @throws EffectorException if the field is inaccessible + */ + protected void resetReceivers(int parameterIndex) throws EffectorException { + for (Receiver r : this.parameters[parameterIndex]) + try { + r.field().set(this.effector, r.defaultValue()); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new EffectorException(String.format( + "%1$s.%2$s: unable to reset field", + this.getName(), r.field().getName())); + } + } + private Receiver newFieldReceiver(Field field, int fieldOrdinal) throws TargetBindingException { Class type = field.getType(); try { if (!type.isArray()) { - if (type.equals(boolean.class)) + if (boolean.class.equals(type)) return new Receiver(Receiver.FieldType.BOOLEAN, field, field.getBoolean(this.effector), fieldOrdinal); - if (type.equals(byte.class)) + if (byte.class.equals(type)) return new Receiver(Receiver.FieldType.BYTE, field, field.getByte(this.effector), fieldOrdinal); - if (type.equals(char.class)) + if (char.class.equals(type)) return new Receiver(Receiver.FieldType.CHAR, field, field.getChar(this.effector), fieldOrdinal); - if (type.equals(String.class)) + if (String.class.equals(type)) return new Receiver(Receiver.FieldType.STRING, field, field.get(this.effector), fieldOrdinal); - if (type.equals(short.class)) + if (short.class.equals(type)) return new Receiver(Receiver.FieldType.SHORT, field, field.getShort(this.effector), fieldOrdinal); - if (type.equals(int.class)) + if (int.class.equals(type)) return new Receiver(Receiver.FieldType.INT, field, field.getInt(this.effector), fieldOrdinal); - if (type.equals(long.class)) + if (long.class.equals(type)) return new Receiver(Receiver.FieldType.LONG, field, field.getLong(this.effector), fieldOrdinal); - if (type.equals(float.class)) + if (float.class.equals(type)) return new Receiver(Receiver.FieldType.FLOAT, field, field.getFloat(this.effector), fieldOrdinal); - if (type.equals(double.class)) + if (double.class.equals(type)) return new Receiver(Receiver.FieldType.DOUBLE, field, field.getDouble(this.effector), fieldOrdinal); } else { Class elementType = type.getComponentType(); - if (elementType.equals(byte.class)) + if (byte.class.equals(elementType)) return new Receiver(Receiver.FieldType.BYTES, field, field.get(this.effector), fieldOrdinal); - if (elementType.equals(char.class)) + if (char.class.equals(elementType)) return new Receiver(Receiver.FieldType.CHARS, field, field.get(this.effector), fieldOrdinal); } } catch (IllegalArgumentException | IllegalAccessException e) { diff --git a/src/com/characterforming/ribose/base/Receiver.java b/src/com/characterforming/ribose/base/Receiver.java index b194e07..a35933b 100644 --- a/src/com/characterforming/ribose/base/Receiver.java +++ b/src/com/characterforming/ribose/base/Receiver.java @@ -22,17 +22,20 @@ import java.lang.reflect.Field; /*** - * Maps transducer fields to Java primitive fields expressed by an - * effector that subclasses {@link BaseReceptorEffectore {@code - * fieldIndex} parameter indicates the offset to the field within the - * transductor stack frame. {@code Receptor} instances are instantiated - * and used internally and should not be used directly by application code. - * The {@code defaultValue} parameter is supplied by the initial value of - * the field in the receptor effector. + * Maps transducer fields to Java primitive fields expressed by an effector that + * subclasses {@link BaseReceptorEffectore}. The {@code defaultValue} parameter + * is supplied by the initial value of the field in the receptor effector. + *
    + *
  • type: ({@link FieldType}) Java primitive type, byte[] or char[] array
  • + *
  • field: ({@link Field}) the subclass field, reflected back
  • + *
  • defaultValue: (Object) default field value, applied to {@code field} on reset
  • + *
  • fieldIndex: (int) ofset to field value in transducer stack frame
  • + *
*/ record Receiver(FieldType type, Field field, Object defaultValue, int fieldIndex) { + /** Enumeration of receiver field types */ - public enum FieldType { + enum FieldType { /** Unknown type */ UNKNOWN, /** boolean type */ @@ -58,4 +61,10 @@ public enum FieldType { /** double type */ DOUBLE } + + @Override + public String toString() { + return String.format("%1$s %2$s: field index=%3$d, default=%4$s", + type.toString(), field.getName(), fieldIndex, defaultValue); + } } \ No newline at end of file