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

org.textmapper.tool.compiler.TMFieldMapper Maven / Gradle / Ivy

There is a newer version: 0.10.0
Show newest version
/**
 * Copyright 2002-2017 Evgeny Gryaznov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.textmapper.tool.compiler;

import org.textmapper.lapg.api.ProcessingStatus;
import org.textmapper.lapg.api.SourceElement;
import org.textmapper.lapg.api.Symbol;
import org.textmapper.lapg.api.ast.*;
import org.textmapper.lapg.api.builder.AstBuilder;
import org.textmapper.lapg.api.builder.GrammarMapper;
import org.textmapper.lapg.api.rule.*;
import org.textmapper.lapg.util.RhsUtil;
import org.textmapper.lapg.util.TypesUtil;

import java.util.*;

public class TMFieldMapper {

	private static final MarkerType BOOL_OR_ENUM = new MarkerType();

	private final ProcessingStatus status;
	private final AstBuilder builder;
	private final GrammarMapper mapper;
	private final boolean allowTypeAny;

	public TMFieldMapper(ProcessingStatus status, AstBuilder builder,
						 GrammarMapper mapper, boolean allowTypeAny) {
		this.status = status;
		this.builder = builder;
		this.mapper = mapper;
		this.allowTypeAny = allowTypeAny;
	}

	private void error(SourceElement element, String message) {
		status.report(ProcessingStatus.KIND_ERROR, message, element);
	}

	public void mapFields(AstClass cl, RhsPart def) {
		DefaultMappingContext context = new DefaultMappingContext();
		traverseFields(def, context, new int[]{0}, true);
		traverseFields(def, context, new int[]{0}, false);

		Collections.sort(context.result);
		for (FieldDescriptor fd : context.result) {
			AstType type = fd.type;
			Map members = new LinkedHashMap<>();
			if (type == BOOL_OR_ENUM) {
				for (FieldMapping m = fd.firstMapping; m != null; m = m.next) {
					Symbol target = m.sym.getTarget();
					members.put(target, null);
				}
				if (members.size() > 1) {
					AstEnum enum_ = builder.addEnum(
							builder.uniqueName(cl, fd.baseName + "_kind", false),
							cl, fd.firstMapping.origin);
					final Symbol[] enumMembers =
							members.keySet().toArray(new Symbol[members.size()]);
					for (Symbol enumMember : enumMembers) {
						final AstEnumMember astEnumMember = builder.addMember(
								builder.uniqueName(enum_, TMDataUtil.getId(enumMember), true),
								enum_, null /* TODO ??? */);
						members.put(enumMember, astEnumMember);
					}
					type = enum_;
				} else {
					type = AstType.BOOL;
				}
			}

			AstField field = builder.addField(builder.uniqueName(cl, fd.baseName, true), type, fd
					.nullable, cl, fd.firstMapping.origin);
			for (FieldMapping m = fd.firstMapping; m != null; m = m.next) {
				Object value = TMDataUtil.getLiteral(m.sym);
				if (fd.type == BOOL_OR_ENUM) {
					if (type == AstType.BOOL) {
						value = Boolean.TRUE;
					} else {
						// TODO handle template vars
						value = members.get(m.sym.getTarget());
					}
				}

				mapper.map(m.sym, field, value, m.addition);
			}
		}

	}

	private void traverseFields(RhsPart part, MappingContext context,
								int[] index, boolean withAlias) {
		if (part instanceof RhsOptional) {
			traverseFields(((RhsOptional) part).getPart(), context, index, withAlias);

		} else if (part instanceof RhsSet) {
			// sets do not contain any useful information for ASTs

		} else if (part instanceof RhsUnordered || part instanceof RhsSequence) {
			RhsPart[] parts = part instanceof RhsUnordered
					? ((RhsUnordered) part).getParts()
					: ((RhsSequence) part).getParts();
			for (RhsPart p : parts) {
				traverseFields(p, context, index, withAlias);
			}

		} else if (part instanceof RhsChoice) {
			ChoiceMappingContext choiceContext = new ChoiceMappingContext(context);
			RhsPart[] parts = ((RhsChoice) part).getParts();
			for (RhsPart p : parts) {
				traverseFields(p, choiceContext, index, withAlias);
				choiceContext.reset();
			}

		} else if (part instanceof RhsAssignment || part instanceof RhsCast ||
				part instanceof RhsSymbol) {
			// field is almost here
			RhsAssignment assignment = RhsUtil.getAssignment(part);
			RhsPart unwrapped = RhsUtil.unwrapEx(part, true, true, true);
			if (!(unwrapped instanceof RhsSymbol)) {
				error(part, (part instanceof RhsAssignment ? "assignment" : "cast") +
						" is not expected here");
				return;
			}

			index[0]++;
			if (withAlias && assignment == null || !withAlias && assignment != null) {
				return;
			}

			RhsSymbol ref = (RhsSymbol) unwrapped;
			// TODO create an interface if this is a TemplateVar

			AstType type = RhsUtil.getCastType(part);
			if (type == null) {
				final Object literal = TMDataUtil.getLiteral(ref);
				if (literal != null) {
					type = literal instanceof String ? AstType.STRING :
							literal instanceof Integer ? AstType.INT : AstType.BOOL;
				}
			}
			if (type == null) {
				if (!TMMapper.isConstantOrVoid(ref)) {
					type = ref.getTarget().getType();
				}
				if (type == null && assignment != null) {
					type = BOOL_OR_ENUM;
				}
			}

			if (type != null) {
				context.addMapping(assignment != null ? assignment.getName() : null, type, ref,
						index[0], assignment != null && assignment.isAddition(), part);
			}
		} else {
			throw new IllegalArgumentException();
		}
	}

	private static String getFieldBaseName(RhsSymbol sym) {
		// TODO handle template vars
		final String id = TMDataUtil.getId(sym.getTarget());
		return id.endsWith("opt") && id.length() > 3 ? id.substring(0, id.length() - 3) : id;
	}

	private interface MappingContext {
		FieldDescriptor addMapping(String alias, AstType type, RhsSymbol sym, int symIndex,
								   boolean isAddition, SourceElement origin);
	}

	private class DefaultMappingContext implements MappingContext {

		private List result = new ArrayList<>();
		private Map> fieldsMap = new HashMap<>();

		@Override
		public FieldDescriptor addMapping(String alias, AstType type, RhsSymbol sym, int symIndex,
										  boolean isAddition, SourceElement origin) {
			// TODO handle template vars
			FieldId id = alias != null ? new FieldId(alias) :
					new FieldId(isAddition, sym.getTarget(), type);
			Collection fields = fieldsMap.get(id);
			if (fields == null) {
				fields = new ArrayList<>();
				fieldsMap.put(id, fields);
			}
			FieldDescriptor fd = new FieldDescriptor(alias != null ? alias : getFieldBaseName(sym));
			fd.addMapping(new FieldMapping(sym, symIndex, isAddition, origin), type);
			result.add(fd);
			fields.add(fd);
			return fd;
		}
	}

	private class ChoiceMappingContext implements MappingContext {

		private final MappingContext parent;
		private Map> localMap = new HashMap<>();
		private Set used;

		private ChoiceMappingContext(MappingContext parent) {
			this.parent = parent;
		}

		@Override
		public FieldDescriptor addMapping(String alias, AstType type, RhsSymbol sym, int symIndex,
										  boolean isAddition, SourceElement origin) {
			// TODO handle template vars
			FieldId id = alias != null ? new FieldId(alias) :
					new FieldId(isAddition, sym.getTarget(), type);
			Collection fds = localMap.get(id);
			if (used == null) {
				used = new HashSet<>();
			}
			if (fds != null) {
				for (FieldDescriptor fd : fds) {
					if (used.add(fd)) {
						// reusing field from a previous alternative
						fd.addMapping(new FieldMapping(sym, symIndex, isAddition, origin), type);
						return fd;
					}
				}
			} else {
				fds = new ArrayList<>();
				localMap.put(id, fds);
			}

			FieldDescriptor fd = parent.addMapping(alias, type, sym, symIndex, isAddition, origin);
			fds.add(fd);
			used.add(fd);
			return fd;
		}

		private void reset() {
			// TODO detect nullables
			used = null;
		}
	}

	private class FieldDescriptor implements Comparable {
		private AstType type;
		private final String baseName;
		private FieldMapping firstMapping;
		private boolean nullable = true;

		private FieldDescriptor(String baseName) {
			this.baseName = baseName;
		}

		private void addMapping(FieldMapping mapping, AstType type) {
			if (firstMapping == null) {
				firstMapping = mapping;
				this.type = type;
				return;
			}
			if (this.type != type) {
				AstType commonType = TypesUtil.getJoinType(this.type, type);
				if (commonType == null) {
					if (allowTypeAny) {
						commonType = AstType.ANY;
					} else {
						error(mapping.origin, "cannot deduce type for `" + baseName +
								"': no common type for " + this.type.toString() + " and " +
								type.toString());
						// Ignore mapping.
						return;
					}
				}
				this.type = commonType;
			}
			mapping.next = firstMapping.next;
			firstMapping.next = mapping;
		}

		@Override
		public int compareTo(Object o) {
			return new Integer(firstMapping.symbolIndex).compareTo(((FieldDescriptor) o)
					.firstMapping.symbolIndex);
		}
	}

	private static class FieldMapping {
		private final RhsSymbol sym;
		private final int symbolIndex;
		private final boolean addition;
		private final SourceElement origin;
		private FieldMapping next;

		private FieldMapping(RhsSymbol sym, int symbolIndex, boolean isAddition,
							 SourceElement origin) {
			this.sym = sym;
			this.symbolIndex = symbolIndex;
			this.addition = isAddition;
			this.origin = origin;
		}
	}

	private static class FieldId {
		private final Symbol sym;
		private final AstType type;
		private final boolean addList;
		private final String alias;

		private FieldId(String alias) {
			this.alias = alias;
			this.type = null;
			this.addList = false;
			this.sym = null;
		}

		private FieldId(boolean isAddition, Symbol ref, AstType type) {
			this.alias = null;
			this.sym = ref;
			if (!isAddition && type instanceof AstList) {
				this.addList = true;
				this.type = ((AstList) type).getInner();
			} else {
				this.addList = isAddition;
				this.type = type;
			}
		}

		@Override
		public boolean equals(Object o) {
			if (this == o) return true;
			if (o == null || getClass() != o.getClass()) return false;

			FieldId fieldId = (FieldId) o;

			if (alias != null ? !alias.equals(fieldId.alias) : fieldId.alias != null) return false;
			if (addList != fieldId.addList) return false;
			if (sym != null ? !sym.equals(fieldId.sym) : fieldId.sym != null) return false;
			if (type != null ? !type.equals(fieldId.type) : fieldId.type != null) return false;

			return true;
		}

		@Override
		public int hashCode() {
			int result = sym != null ? sym.hashCode() : 0;
			result = 31 * result + (type != null ? type.hashCode() : 0);
			result = 31 * result + (addList ? 1 : 0);
			result = 31 * result + (alias != null ? alias.hashCode() : 0);
			return result;
		}
	}

	private static final class MarkerType implements AstType {
		public MarkerType() {
		}

		@Override
		public boolean isSubtypeOf(AstType another) {
			return another == this;
		}

		@Override
		public String toString() {
			return "marker type";
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy