Skip to content

Commit

Permalink
WIP: Implement receptor effector base class
Browse files Browse the repository at this point in the history
- 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 <jrte.project@gmail.com>
  • Loading branch information
jrte committed Jun 21, 2024
1 parent e0e14cc commit c728630
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 127 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
6 changes: 3 additions & 3 deletions etc/sh/bench
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ while [[ ! "$1" =~ ^(--tsv|[0-9]+) ]]; do
vmargs="$vmargs $1";
shift
if (($#==0)); then
echo "Usage: bench [--tsv] <iteration-count> <model-path> <transducer> <input-path>"
echo "Use --tsv for tab-delimited output per etc/benchmarks/ribose.ods"
echo "Usage: bench [--tsv] <iteration-count> <model-path> <input-path> <transducer>..."
echo "Use --tsv for tab-delimited output"
exit 0
fi
done
Expand All @@ -32,7 +32,7 @@ if [[ "$1" == "--tsv" ]]; then
fi
if (($#<4)); then
echo "Usage: bench [--tsv] <iteration-count> <model-path> <input-path> <transducer>..."
echo "Use --tsv for tab-delimited output per etc/benchmarks/ribose.ods"
echo "Use --tsv for tab-delimited output"
exit 1
fi
n=$1
Expand Down
8 changes: 3 additions & 5 deletions src/com/characterforming/jrte/engine/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -371,8 +370,8 @@ public boolean map(PrintStream mapWriter) {
for (Map.Entry<Bytes, Integer> 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<Bytes, Integer> fieldMap = this.transducerFieldMaps.get(transducerOrdinal);
Bytes[] fields = new Bytes[fieldMap.size()];
for (Entry<Bytes, Integer> e : fieldMap.entrySet())
Expand Down Expand Up @@ -429,12 +428,11 @@ protected boolean checkTargetEffectors(ITarget target, IEffector<?>[] boundFx) {
return checked;
}

protected Argument[][] compileModelParameters(List<String> errors) throws EffectorException {
protected Argument[][] compileModelParameters(List<String> errors) {
Argument[][] effectorArguments = new Argument[this.proxyEffectors.length][];
final Map<Bytes, Integer> effectorMap = this.getEffectorOrdinalMap();
for (int effectorOrdinal = 0; effectorOrdinal < this.proxyEffectors.length; effectorOrdinal++) {
HashMap<Argument, Integer> 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",
Expand Down
44 changes: 19 additions & 25 deletions src/com/characterforming/jrte/engine/ModelCompiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand All @@ -90,7 +89,8 @@ final class HeaderEffector extends BaseReceptorEffector<ModelCompiler> {
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);
}

Expand All @@ -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;
}
}
Expand All @@ -109,7 +110,8 @@ final class TransitionEffector extends BaseReceptorEffector<ModelCompiler> {
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);
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -545,20 +548,18 @@ 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<Transition> outT = null;
for (
outT = this.getTransitions(transition.to);
outT != null && outT.size() == 1 && outT.get(0).tape > 0;
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) {
Expand All @@ -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)
Expand All @@ -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;
Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/com/characterforming/jrte/engine/ModelLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
6 changes: 3 additions & 3 deletions src/com/characterforming/jrte/engine/Transducer.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 41 additions & 33 deletions src/com/characterforming/jrte/engine/Transductor.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion src/com/characterforming/jrte/test/FileRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/com/characterforming/ribose/IEffector.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ public interface IEffector<T extends ITarget> {
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
Expand Down
8 changes: 8 additions & 0 deletions src/com/characterforming/ribose/IOutput.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit c728630

Please sign in to comment.