2

Say you have this sort of system:

const input = [0, [1, 2, [3, 4, [8, 9], 6, 7], 4], [5, 6, 7], 3, 4, [8, 9], 6, 7, [1], 9]
const output = parse(input)
console.log(output.join('-'))

function parse(input) {
  const output = []
  iterate(input, value => {
    check(() => isArray(value),
      () => { // yes array
        const children = parse(value)
        output.push(...children)
      },
      () => { // not array
        check(() => isEven(value), () => {
          output.push(value)
        })
      })
  })
  return output
}

function isArray(value) {
  return Array.isArray(value)
}

function isEven(value) {
  return value % 2 == 0
}

function check(condition, success, failure) {
  if (condition()) success()
  else if (failure) failure()
}

function iterate(array, block) {
  array.forEach(block)
}

Essentially this is like a tree grammar:

parse =
  iterate input, value =>
    check isArray(value)
      call parse(value)
    check isNumber(value)
      check isEven(value)
        push(value)

You can imagine this as JSON:

{
  type: 'function',
  name: 'parse',
  children: [
    {
      type: 'iterate',
      children: [
        {
          type: 'check',
          checker: 'isArray',
          children: [
            {
              type: 'call',
              function: 'parse'
            }
          ]
        },
        {
          type: 'check',
          checker: 'isNumber',
          children: [
            {
              type: 'check',
              checker: 'isEven',
              children: [
                {
                  type: 'push',
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Basically my real problem is I have something like this JSON in a custom programming language, and I want to convert it into a state machine rather than the nested/recursive function calls like we started with in this post.

So my question is, how can you rewrite the recursive function system from the beginning of this post, such that it uses a state machine instead?

You don't have to go all the way and make the state machine from the JSON (which in this example is incomplete, as I don't have a good example to simplify from).

Instead, just if you could show how to rewrite this recursive or nested function system from the beginning of this post as a state machine, that would be all.

What I am imagining is something like this:

let transitions = [
  function1,
  function2,
  ...
]

let transition = transitions[0] // the start function in theory
let currentInput = input
let someStack = []
while (transition) {
  [currentInput, transition] = transition(someStack, currentInput)
}

But I quickly get lost on how I would keep track of the position we are at in the input, as well as how to handle things like iteration (as in the iterator), or "if statements" (as in the check).

function iterate(input) {
  // you can't do the recursive call here,
  // as the original while loop should handle this.
  // but, we should return some reference to the current input,
  // as well as the next transition.
}

If a language must be chosen then doing this in JavaScript would be simplest. But it can even be done in pseudocode to show generally how it's done.

Nadeem Taj
  • 1
  • 4
  • 21
Lance
  • 75,200
  • 93
  • 289
  • 503
  • The question is not clear. The word "parsing" implies that the input is a string and you read character by character. That's not the case in your example where `input` is an already parsed JS value. Please clarify. – Olivier Aug 22 '21 at 09:10
  • Also what's wrong with the recursive approach? – Olivier Aug 22 '21 at 09:16
  • @Olivier by parsing I mean taking some input and converting it into output. My example uses a nested array system (a tree), which is close to my input which I will be "parsing". But if it is easier you can imagine a string instead, either way works for me. There is no inherent problem with the recursive approach necessarily, I just want to know how to do this like a transition machine somehow. For my learnings. – Lance Aug 24 '21 at 00:36
  • 1
    Defunctionalization _might be_ relevant for your problem: https://www.youtube.com/watch?v=wppzFzzD4b8 – artur grzesiak Aug 25 '21 at 12:45
  • Are you just doing a tree rewriting? your input and out both looks like trees. Something like a visitor pattern + stack of strategy pattern may work for that. Should I expand on this? – Marco Servetto Aug 27 '21 at 19:09
  • 1
    Hi, your bounty is expiring soon, you should engage with comments if you want a good answer. Is the set of 'isEven, isNumber isArray' a finite set? It is only 'isArray' that is a 'composite'? – Marco Servetto Aug 28 '21 at 09:36
  • This is a good. https://aclanthology.org/W89-0229.pdf – Nadeem Taj Aug 28 '21 at 10:41
  • @MarcoServetto sorry for the late reply. I'm not sure what you mean by finite set, `isArray` is a function that returns if it is an array, while `isNumber` returns true if a number. – Lance Aug 31 '21 at 00:04

2 Answers2

1

As it was said, you can not use a state machine, but if I understand your example, you do not need one. You want a way to construct predicates from data; those predicates will work on a complex irregular data structure.

  • Some of those predicates will have side effects.
  • Some of those predicates will propagate the operation on sub parts of the data.
  • I'm assuming you have a finite way to express your predicates, so you can compose a finite set of predicates to obtain your desired ones.

If Java streams had a 'recursive' flatMap, that would be enough.

I'm a Java expert so I will write an attempt in Java. The main idea is to have an Op functional interface that maps an Object into a list of objects.

Then you just compute a fix-point.

interface Op{ List<Object> of(Object t); }
class A{
  Op push(List<Object> res){
    return o->{res.add(o);return List.of();};
    }  
  Op isArray(){
    return t->{
      if (!(t instanceof Object[] os)){ return List.of(); }
      return List.of(os);
      };
    }
  Op isNumber(Op more){
    return t->{
      if (!(t instanceof Integer i)){ return List.of(); }
      return more.of(i);
      };
    }
  Op isEven(Op more){
    return t->{
      if (!(t instanceof Integer i) || i%2==1){ return List.of(); }
      return more.of(i);
      };
    }
  void engine1(List<Op> query,Object input){//not recursive
    List<Object>edge=new ArrayList<>();
    List<Object>nextEdge=new ArrayList<>();
    edge.add(input);
    while(!edge.isEmpty()){
      for(var e:edge){
        for(var f:query){
          nextEdge.addAll(f.of(e));
          }
        }
      edge=nextEdge;
      nextEdge=new ArrayList<>();
      }
    }
  void example1() {
    List<Object> res=new ArrayList<>();
    List<Op> query=List.of(isArray(),isEven(push(res)));
    Object input= new Object[]{1,2,new Object[]{3,4},5,6};
    engine1(query,input);
    System.out.println(res);
    }
  public static void main(String[]arg) {
    new A().example1();//this would print 2,6,4
    }
  }

It is unclear from your question if this order is acceptable for you. That is, the visit side effects are Breadth First If not, I think I can get the Deapth First order instead, but it will have to be in 12 hours or so, since it is now getting late in my timezone.

Marco Servetto
  • 684
  • 1
  • 5
  • 14
  • This is a good start, I will have to take some time to study this as I am not a Java expert, but it looks like it's in the right direction I think. Thank you! – Lance Aug 31 '21 at 00:03
0

This is impossible. A state machine cannot recognise/parse a language with a recursive grammar but only a regular language. You would need a stack machine.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 2
    Can you show how to do that? That's what I was looking for and I don't want to ask the same question again with a different title. Had I asked for a pushdown automata, one would have said that you can't build output with an automaton (which only returns true/false). – Lance May 27 '21 at 04:34