All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zarbosoft.interface1.events.ReadEventGrammar Maven / Gradle / Ivy

package com.zarbosoft.interface1.events;

import com.zarbosoft.interface1.Configuration;
import com.zarbosoft.interface1.OneshotTypeVisitor;
import com.zarbosoft.interface1.TypeInfo;
import com.zarbosoft.interface1.Walk;
import com.zarbosoft.pidgoon.AbortParse;
import com.zarbosoft.pidgoon.Node;
import com.zarbosoft.pidgoon.events.Grammar;
import com.zarbosoft.pidgoon.events.MatchingEvent;
import com.zarbosoft.pidgoon.events.MatchingEventTerminal;
import com.zarbosoft.pidgoon.events.stores.StackStore;
import com.zarbosoft.pidgoon.nodes.*;
import com.zarbosoft.rendaw.common.Pair;
import com.zarbosoft.rendaw.common.Tuple;
import io.github.classgraph.ScanResult;

import java.lang.reflect.Field;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.zarbosoft.rendaw.common.Common.uncheck;

public class ReadEventGrammar {
	public static Grammar buildGrammar(final ScanResult scanner, final TypeInfo root) {
		final HashSet seen = new HashSet<>();
		final Grammar grammar = new Grammar();
		grammar.add("root", new Union().add(Walk.walkType(scanner, root, null, new OneshotTypeVisitor() {
			@Override
			public Node visitString(Void _i, final TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(null)), s -> {
					final InterfacePrimitiveEvent event = (InterfacePrimitiveEvent) s.top();
					return s.pushStack(event.value);
				});
			}

			@Override
			public Node visitSigned(Void _i, final TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(null)), s -> {
					final InterfacePrimitiveEvent event = (InterfacePrimitiveEvent) s.top();
					try {
						return s.pushStack(Integer.parseInt(event.value));
					} catch (final NumberFormatException e) {
						throw new AbortParse(e);
					}
				});
			}

			@Override
			public Node visitUnsigned(Void _i, final TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(null)), s -> {
					final InterfacePrimitiveEvent event = (InterfacePrimitiveEvent) s.top();
					try {
						return s.pushStack(Integer.parseUnsignedInt(event.value));
					} catch (final NumberFormatException e) {
						throw new AbortParse(e);
					}
				});
			}

			@Override
			public Node visitDouble(Void _i, final TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(null)), s -> {
					final InterfacePrimitiveEvent event = (InterfacePrimitiveEvent) s.top();
					try {
						return s.pushStack(Double.valueOf(event.value));
					} catch (final NumberFormatException e) {
						throw new AbortParse(e);
					}
				});
			}

			@Override
			public Node visitBoolean(Void _i, final TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(null)), s -> {
					final InterfacePrimitiveEvent event = (InterfacePrimitiveEvent) s.top();
					if (event.value.equals("true"))
						return s.pushStack(true);
					else if (event.value.equals("false"))
						return s.pushStack(false);
					else
						throw new AbortParse(String.format("Invalid value [%s]", event.value));
				});
			}

			@Override
			public Node visitEnum(Void _i, TypeInfo field) {
				final Union union = new Union();
				Walk.enumValues((Class) field.type).forEach(pair -> {
					union.add(new Operator(new MatchingEventTerminal(new InterfacePrimitiveEvent(Walk.decideName(
							pair.second))),
							store -> store.pushStack(pair.first)
					));
				});
				return union;
			}

			@Override
			public Node visitListEnd(Void _i, final TypeInfo field, final Node inner) {
				return new Sequence()
						.add(new Operator(new MatchingEventTerminal(new InterfaceArrayOpenEvent()),
								s -> s.pushStack(0)
						))
						.add(new Repeat(new Operator(inner, s -> {
							Object temp = s.stackTop();
							s = (StackStore) s.popStack();
							Integer count = s.stackTop();
							s = (StackStore) s.popStack();
							return s.pushStack(temp).pushStack(count + 1);
						})))
						.add(new Operator(new MatchingEventTerminal(new InterfaceArrayCloseEvent()), s -> {
							final List out = new ArrayList();
							s = (StackStore) StackStore.stackPopSingleList(s, out::add);
							Collections.reverse(out);
							return s.pushStack(out);
						}));
			}

			@Override
			public Node visitSetEnd(Void _i, final TypeInfo field, final Node inner) {
				return new Sequence()
						.add(new Operator(new MatchingEventTerminal(new InterfaceArrayOpenEvent()),
								s -> s.pushStack(0)
						))
						.add(new Repeat(new Operator(inner, s -> {
							Object temp = s.stackTop();
							s = (StackStore) s.popStack();
							Integer count = s.stackTop();
							s = (StackStore) s.popStack();
							return s.pushStack(temp).pushStack(count + 1);
						})))
						.add(new Operator(new MatchingEventTerminal(new InterfaceArrayCloseEvent()), s -> {
							final Set out = new HashSet();
							s = (StackStore) StackStore.stackPopSingleList(s, (Consumer) out::add);
							return s.pushStack(out);
						}));
			}

			@Override
			public Node visitMapEnd(Void _i, final TypeInfo field, final Node innerKey, final Node innerValue) {
				return new Sequence()
						.add(new Operator(new MatchingEventTerminal(new InterfaceObjectOpenEvent()),
								s -> s.pushStack(0)
						))
						.add(new Repeat(new Sequence()
								.add(new Operator(new MatchingEventTerminal(new InterfaceKeyEvent(null)),
										store -> store.pushStack(((InterfaceKeyEvent) store.top()).value)
								))
								.add(new Operator(innerValue, StackStore::stackDoubleElement))))
						.add(new Operator(new MatchingEventTerminal(new InterfaceObjectCloseEvent()), s -> {
							final Map out = new HashMap();
							s = (StackStore) StackStore.>stackPopSingleList(s,
									p -> out.put(p.first, p.second)
							);
							return s.pushStack(out);
						}));
			}

			@Override
			public Node visitAbstractEnd(
					Void _i, final TypeInfo field, final List, Node>> derived
			) {
				final Class def;
				if (field != null)
					def = field.field.getAnnotation(Configuration.class).typeless();
				else
					def = null;
				final Tuple key =
						new Tuple<>(field.type, derived.stream().map(p -> p.first).collect(Collectors.toSet()), def);
				if (!seen.contains(key)) {
					seen.add(key);
					final Union out = new Union();
					derived.stream().forEach(s -> {
						if (s.first.equals(def))
							out.add(s.second);
						out.add(new Sequence()
								.add(new MatchingEventTerminal(new InterfaceTypeEvent(Walk
										.decideName(s.first)
										.toLowerCase())))
								.add(s.second));
					});
					grammar.add(key, out);
				}
				return new Reference(key);
			}

			@Override
			public Node visitConcreteShort(Void _i, final TypeInfo field) {
				return new Reference(field.type.getTypeName());
			}

			@Override
			public void visitConcrete(
					Void _i, final TypeInfo field, final List> fields
			) {
				final Sequence seq = new Sequence();
				{
					seq.add(new Operator(new MatchingEventTerminal(new InterfaceObjectOpenEvent()),
							s -> s.pushStack(0)
					));
					final com.zarbosoft.pidgoon.nodes.Set set = new com.zarbosoft.pidgoon.nodes.Set();
					fields.forEach(f -> {
						set.add(new Operator(new Sequence()
								.add(new MatchingEventTerminal(new InterfaceKeyEvent(Walk.decideName(f.first.field))))
								.add(f.second), s -> {
							s = (StackStore) s.pushStack(f.first);
							return StackStore.stackDoubleElement(s);
						}), fieldIsRequired(f.first));
					});
					seq.add(set);
					seq.add(new MatchingEventTerminal(new InterfaceObjectCloseEvent()));
				}
				final Node topNode;
				final List> minimalFields2 =
						fields.stream().filter(f -> fieldIsRequired(f.first)).collect(Collectors.toList());
				final List> minimalFields;
				if (minimalFields2.size() == 0)
					minimalFields = fields;
				else
					minimalFields = minimalFields2;
				if (minimalFields.size() == 1) {
					final Union temp = new Union();
					temp.add(seq);
					temp.add(new Operator(minimalFields.iterator().next().second, s -> {
						final Object value = s.stackTop();
						s = (StackStore) s.popStack();
						return s.pushStack(new Pair<>(value, minimalFields.iterator().next().first)).pushStack(1);
					}));
					topNode = temp;
				} else {
					topNode = seq;
				}
				grammar.add(field.type.getTypeName(), new Operator(topNode, s -> {
					final Object out = uncheck(((Class) field.type)::newInstance);
					s = (StackStore) StackStore.>stackPopSingleList(s, (pair) -> {
						uncheck(() -> {
							// Something crazy going on here; changes with assignment via reflection were only visible
							// via reflection sometimes (nondeterministic).  Using a setter instead fixed this.
							// Total hack.
							try {
								out
										.getClass()
										.getMethod(pair.second.getName(), pair.second.getType())
										.invoke(out, pair.first);
							} catch (final NoSuchMethodException e) {
								pair.second.set(out, pair.first);
							}
						});
					});
					return s.pushStack(out);
				}));
			}

			@Override
			public Node visitOther(Void _i, TypeInfo field) {
				return new Operator(new MatchingEventTerminal(new MatchingEvent() {
					@Override
					public boolean matches(final MatchingEvent event) {
						return event instanceof InterfaceOtherEvent &&
								((Class) field.type).isAssignableFrom(((InterfaceOtherEvent) event).value.getClass());
					}
				}), s -> {
					final InterfaceOtherEvent event = (InterfaceOtherEvent) s.top();
					return s.pushStack(event.value);
				});
			}
		})).add(new Operator(store -> store.pushStack(null))));
		return grammar;
	}

	private static boolean fieldIsRequired(final TypeInfo field) {
		if (Collection.class.isAssignableFrom((Class) field.type))
			return false;
		if (Map.class.isAssignableFrom((Class) field.type))
			return false;
		final Configuration annotation = field.field.getAnnotation(Configuration.class);
		if (annotation == null)
			return false;
		if (annotation.optional())
			return false;
		return true;
	}
}