org.textmapper.tool.compiler.TMMapper Maven / Gradle / Ivy
/**
* Copyright 2002-2016 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.LapgCore;
import org.textmapper.lapg.api.*;
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.builder.GrammarFacade;
import org.textmapper.lapg.util.NonterminalUtil;
import org.textmapper.lapg.util.RhsUtil;
import org.textmapper.lapg.util.TypesUtil;
import org.textmapper.tool.compiler.TMTypeHint.Kind;
import java.util.*;
import java.util.Map.Entry;
/**
* evgeny, 1/29/13
*/
public class TMMapper {
private final Grammar grammar;
private final ProcessingStatus status;
private final boolean allowTypeAny;
private final GrammarMapper mapper;
private final AstBuilder builder;
private final Map decorators = new HashMap<>();
private final Map> typeListeners = new HashMap<>();
private final Queue postProcessors = new LinkedList<>();
private final Map> classContent = new LinkedHashMap<>();
private final Map aliasToClass = new HashMap<>();
private List unmapped;
public TMMapper(Grammar grammar, ProcessingStatus status, boolean allowTypeAny) {
this.grammar = grammar;
this.status = status;
this.allowTypeAny = allowTypeAny;
this.mapper = LapgCore.createMapper(grammar);
this.builder = LapgCore.createAstBuilder();
}
public AstModel deriveAST() {
collectUnmapped();
rewriteLists();
mapVoid();
mapEnums();
mapDecorators();
mapInterfaces();
mapClasses();
mapCustomTypeClasses();
mapLists();
mapFields();
assert unmapped.isEmpty();
if (!typeListeners.isEmpty()) {
for (Symbol s : typeListeners.keySet()) {
error(s, "cannot compute AST type (detected a cycle in type dependencies)");
}
} else {
postProcessors.forEach(Runnable::run);
}
return builder.create();
}
public void detectListsOnly() {
collectUnmapped();
rewriteLists();
}
private void rewriteLists() {
for (Nonterminal n : unmapped) {
if (hasInterfaceHint(n) || hasClassHint(n) || TMDataUtil.getCustomType(n) != null) {
continue;
}
GrammarFacade.rewriteAsList(n);
}
}
private void mapVoid() {
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (isVoid(n)) {
mapper.map(n, VoidType.INSTANCE);
i.remove();
}
}
}
public static boolean isVoid(Nonterminal n) {
if (hasVoidHint(n)) return true;
if (hasClassHint(n)) return false;
RhsPart def = RhsUtil.unwrap(n.getDefinition());
if (def instanceof RhsSymbol) {
Symbol sym = ((RhsSymbol) def).getTarget();
if (sym instanceof Terminal) {
return ((Terminal) sym).isConstant();
}
// TODO infinite recursion?
return isVoid((Nonterminal) sym);
}
return false;
}
private void mapNonterm(Nonterminal n, AstType type) {
if (type == null || n == null) {
throw new NullPointerException();
}
List listeners = typeListeners.remove(n);
mapper.map(n, type);
if (listeners != null) {
listeners.forEach(Runnable::run);
}
}
private void whenMapped(Symbol s, Runnable r) {
if (s.getType() != null || !(s instanceof Nonterminal)) {
r.run();
} else {
List listeners = typeListeners.get(s);
if (listeners == null) {
listeners = new ArrayList<>();
typeListeners.put(s, listeners);
}
listeners.add(r);
}
}
private void collectUnmapped() {
unmapped = new LinkedList<>();
for (Symbol sym : grammar.getSymbols()) {
if (!(sym instanceof Nonterminal) || sym.getType() != null) continue;
unmapped.add((Nonterminal) sym);
}
}
private void mapEnums() {
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (hasClassHint(n) || TMDataUtil.getCustomType(n) != null ||
n.getDefinition() instanceof RhsList) {
continue;
}
RhsPart definition = RhsUtil.unwrap(n.getDefinition());
if (definition instanceof RhsChoice) {
RhsChoice alt = (RhsChoice) n.getDefinition();
boolean isEnum = true;
for (RhsPart part : alt.getParts()) {
if (!(isConstantOrVoid(part))) {
isEnum = false;
break;
}
}
if (isEnum) {
AstEnum astEnum = builder.addEnum(getNonterminalTypeName(n, null), null, n);
mapNonterm(n, astEnum);
for (RhsPart part : alt.getParts()) {
RhsPart p = RhsUtil.unwrap(part);
String memberName = null;
if (p instanceof RhsAssignment) {
memberName = ((RhsAssignment) p).getName();
p = ((RhsAssignment) p).getPart();
}
RhsSymbol term = (RhsSymbol) p;
if (memberName == null) {
memberName = TMDataUtil.getId(term.getTarget());
}
AstEnumMember member = builder.addMember(
builder.uniqueName(astEnum, memberName, true), astEnum, part);
mapper.map(term, null, member, false);
}
i.remove();
continue;
}
}
RhsPart single = RhsUtil.unwrapEx(definition, true, false, false);
if (single instanceof RhsSymbol && isConstantOrVoid(single)) {
mapNonterm(n, AstType.BOOL);
mapper.map((RhsSymbol) single, null, Boolean.TRUE, false);
i.remove();
}
}
}
private String getAliasId(Nonterminal n, String ruleAlias) {
if (ruleAlias != null && !ruleAlias.startsWith("_")) {
return ruleAlias;
}
String id = TMDataUtil.getId(n);
return id + "#" + (ruleAlias == null ? "" : ruleAlias);
}
private String getNonterminalTypeName(Nonterminal n, String suffix) {
String id = TMDataUtil.getId(n);
if (suffix != null) {
id = suffix.startsWith("_") ? id + suffix : suffix;
}
return builder.uniqueName(null, id, false);
}
private void mapDecorators() {
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (n.getDefinition() instanceof RhsList) continue;
if (hasClassHint(n) || TMDataUtil.getCustomType(n) != null || hasInterfaceHint(n)) {
continue;
}
RhsPart r = null;
for (RhsSequence rule : NonterminalUtil.getRules(n)) {
if (RhsUtil.isEmpty(rule)) continue;
if (r == null) {
r = RhsUtil.unwrapOpt(RhsUtil.unwrap(rule));
} else {
// more than one non-empty rule => not a decorator
r = null;
break;
}
}
if (r != null) {
RhsSymbol master = getMasterSymbol(r);
if (master != null) {
Symbol target = master.getTarget();
assert target != null;
if (target instanceof Terminal &&
(target.getType() == null || ((Terminal) target).isSoft())) {
// cannot map a terminal without a type, ignoring decorator
continue;
}
decorators.put(n, target);
whenMapped(target, () -> mapNonterm(n, target.getType()));
postProcessors.add(() -> mapper.map(master, null, null, false));
i.remove();
}
}
}
}
private static RhsPart withoutConstants(RhsPart part) {
if (part instanceof RhsSequence) {
RhsPart varPart = null;
for (RhsPart p_ : ((RhsSequence) part).getParts()) {
RhsPart p = RhsUtil.unwrap(p_);
if (p instanceof RhsSymbol && isConstantOrVoid(p)) continue;
if (varPart != null) return null;
varPart = p;
}
return varPart == null ? null : withoutConstants(varPart);
} else if (part instanceof RhsUnordered || part instanceof RhsChoice ||
part instanceof RhsList) {
return null;
}
return part;
}
static boolean isConstantOrVoid(RhsPart part) {
part = RhsUtil.unwrapEx(part, false, false, true);
if (part instanceof RhsSymbol) {
RhsSymbol sym = (RhsSymbol) part;
return RhsUtil.isConstant(sym) || sym.getTarget() instanceof Nonterminal &&
sym.getTarget().getType() instanceof VoidType;
}
return false;
}
private void addExtends(Nonterminal n, AstClass baseClass) {
whenMapped(n, () -> {
AstClass cl = (AstClass) n.getType();
try {
builder.addExtends(cl, baseClass);
} catch (IllegalArgumentException ex) {
error(n, n.getNameText() + ": " + cl.getName() + " cannot extend " +
baseClass.getName() +
" (would introduce a cycle in the inheritance hierarchy)");
}
});
}
private void addInterface(AstClass cl, Nonterminal baseInterface,
SourceElement origin) {
whenMapped(baseInterface, () -> {
boolean isInterface = baseInterface.getType() instanceof AstClass &&
((AstClass)baseInterface.getType()).isInterface();
if (!isInterface) {
error(origin, cl.getName() + " cannot extend " + baseInterface.getNameText() +
" (interface is expected)");
return;
}
AstClass superInterface = (AstClass) baseInterface.getType();
try {
builder.addExtends(cl, superInterface);
} catch (IllegalArgumentException ex) {
error(origin, cl.getName() + " cannot extend " + superInterface.getName() +
" (would introduce a cycle in the inheritance hierarchy)");
}
});
}
private void mapInterfaces() {
List passSymbols = new ArrayList<>();
Set extList = new LinkedHashSet<>();
List customRuleList = new ArrayList<>();
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (n.getDefinition() instanceof RhsList || TMDataUtil.getCustomType(n) != null ||
hasClassHint(n)) {
continue;
}
extList.clear();
customRuleList.clear();
passSymbols.clear();
boolean hasNamedRules = false;
boolean isInterface = true;
for (RhsSequence rule : NonterminalUtil.getRules(n)) {
RhsSymbol master = getMasterSymbol(rule);
if (master != null) {
Symbol target = unwrapDecorators(master.getTarget());
if (supportsExtending(target)) {
passSymbols.add(master);
extList.add((Nonterminal) target);
continue;
}
}
String ruleAlias = rule.getName();
if (ruleAlias == null && !hasInterfaceHint(n)) {
isInterface = false;
break;
}
hasNamedRules |= (ruleAlias != null);
customRuleList.add(rule);
}
if (isInterface && (!passSymbols.isEmpty() || hasInterfaceHint(n) && hasNamedRules)) {
AstClass interfaceClass = builder.addInterface(
getNonterminalTypeName(n, null), null, n);
List superInterfaces = TMDataUtil.getImplements(n);
if (superInterfaces != null) {
for (Nonterminal si : superInterfaces) {
addInterface(interfaceClass, si, n);
}
}
mapNonterm(n, interfaceClass);
for (Nonterminal nonterminal : extList) {
addExtends(nonterminal, interfaceClass);
}
for (RhsSymbol sym : passSymbols) {
mapper.map(sym, null, null, false);
}
for (RhsSequence rulePart : customRuleList) {
String ruleAlias = rulePart.getName() == null ? "_Impl" : rulePart.getName();
String aliasId = getAliasId(n, ruleAlias);
AstClass ruleClass = aliasToClass.get(aliasId);
if (ruleClass == null) {
ruleClass = builder.addClass(getNonterminalTypeName(n, ruleAlias), null, n);
builder.addExtends(ruleClass, interfaceClass);
aliasToClass.put(aliasId, ruleClass);
}
mapClass(ruleClass, rulePart);
mapper.map(rulePart, null, ruleClass, false);
}
i.remove();
} else if (hasInterfaceHint(n)) {
status.report(ProcessingStatus.KIND_ERROR, "interface hint was ignored", n);
}
}
}
private boolean supportsExtending(Symbol sym) {
sym = unwrapDecorators(sym);
if (!(sym instanceof Nonterminal)) return false;
Nonterminal n = (Nonterminal) sym;
if (n.getDefinition() instanceof RhsList) return false;
AstType type = sym.getType();
return type == null || type instanceof AstClass /* TODO && !((AstClass) type).isSealed()*/;
}
private Symbol unwrapDecorators(Symbol sym) {
assert sym != null;
Symbol curr = sym;
Symbol next = decorators.get(curr);
if (next == null) {
return sym;
}
Set seen = new HashSet<>();
while (next != null) {
if (!seen.add(next)) {
// TODO handle, report etc.
throw new IllegalStateException("cycle in decorators: " + seen.toString());
}
curr = next;
next = decorators.get(curr);
}
return curr;
}
private RhsSymbol getMasterSymbol(RhsPart rule) {
RhsPart r = RhsUtil.unwrap(rule);
RhsPart master = withoutConstants(r);
if (master instanceof RhsSymbol &&
(master == r || TMDataUtil.hasProperty(master, "pass"))) {
return (RhsSymbol) master;
}
return null;
}
private void mapClasses() {
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (n.getDefinition() instanceof RhsList || TMDataUtil.getCustomType(n) != null) {
continue;
}
AstClass cl = builder.addClass(getNonterminalTypeName(n, null), null, n);
List superInterfaces = TMDataUtil.getImplements(n);
if (superInterfaces != null) {
for (Nonterminal si : superInterfaces) {
addInterface(cl, si, n);
}
}
mapNonterm(n, cl);
mapClass(cl, RhsUtil.unwrap(n.getDefinition()));
i.remove();
}
}
/**
* Maps `A returns B` nonterminals.
*/
private void mapCustomTypeClasses() {
Iterator i = unmapped.iterator();
Map customTypes = new LinkedHashMap<>();
while (i.hasNext()) {
Nonterminal n = i.next();
if (n.getDefinition() instanceof RhsList) continue;
Nonterminal customType = TMDataUtil.getCustomType(n);
assert customType != null;
if (decorators.containsKey(customType)) {
i.remove();
error(n, "custom type cannot refer a decorator nonterminal");
mapNonterm(n, builder.addClass(getNonterminalTypeName(n, null), null, n));
continue;
}
AstType mappedType = customType.getType();
if (!(mappedType instanceof AstClass)) {
i.remove();
error(n, "type for `" + n.getNameText() + "' is not a classifier");
mapNonterm(n, builder.addClass(getNonterminalTypeName(n, null), null, n));
continue;
}
AstClass cl = (AstClass) mappedType;
i.remove();
customTypes.put(n, cl);
}
for (Entry e : customTypes.entrySet()) {
mapCustomTypeNonterm(e.getKey(), e.getValue(), customTypes);
}
}
private void mapCustomTypeNonterm(Nonterminal n, AstClass cl,
Map customTypes) {
if (!cl.isInterface()) {
mapNonterm(n, cl);
mapClass(cl, RhsUtil.unwrap(n.getDefinition()));
return;
}
boolean mapped = false;
Set usedClasses = new HashSet<>();
Map mappedRules = new HashMap<>();
for (RhsSequence rule : NonterminalUtil.getRules(n)) {
RhsSymbol master = getMasterSymbol(rule);
if (master != null) {
Symbol target = unwrapDecorators(master.getTarget());
AstType masterType = target.getType();
if (masterType == null && target instanceof Nonterminal) {
masterType = customTypes.get(target);
}
if (masterType != null && masterType.isSubtypeOf(cl)) {
if (!mapped) {
mapNonterm(n, cl);
mapped = true;
}
mapper.map(master, null, null, false);
continue;
}
}
String ruleAlias = rule.getName();
String aliasId = getAliasId(n, ruleAlias);
AstClass ruleClass = aliasToClass.get(aliasId);
if (ruleClass == null) {
ruleClass = builder.addClass(getNonterminalTypeName(n, ruleAlias), null, n);
aliasToClass.put(aliasId, ruleClass);
}
if (usedClasses.add(ruleClass)) {
builder.addExtends(ruleClass, cl);
}
mapClass(ruleClass, rule);
mappedRules.put(rule, ruleClass);
}
if (!mapped) {
if (usedClasses.size() == 1) {
cl = usedClasses.iterator().next();
}
mapNonterm(n, cl);
}
for (Entry e : mappedRules.entrySet()) {
mapper.map(e.getKey(), null, e.getValue(), false);
}
}
private void mapLists() {
List rhsSymbols = new ArrayList<>();
Iterator i = unmapped.iterator();
while (i.hasNext()) {
Nonterminal n = i.next();
if (!(n.getDefinition() instanceof RhsList)) continue;
RhsList list = (RhsList) n.getDefinition();
// Is there already exists a type for the list element?
rhsSymbols.clear();
TypeOrSymbolHandle typeOrSymbol = getTypeOrUnresolvedSymbol(
list.getElement(), rhsSymbols);
if (typeOrSymbol != null && list.getCustomInitialElement() != null) {
typeOrSymbol = typeOrSymbol.merge(getTypeOrUnresolvedSymbol(
list.getCustomInitialElement(), rhsSymbols));
}
// Can we proceed without a separate class?
if (typeOrSymbol != null) {
if (typeOrSymbol.getType() != null) {
// Symbol elements have a common type.
if (typeOrSymbol.getType() instanceof VoidType) {
mapper.map(n, VoidType.INSTANCE);
} else {
mapNonterm(n, builder.list(typeOrSymbol.getType(), list.isNonEmpty(), n));
for (RhsSymbol sym : rhsSymbols) {
mapper.map(sym, null, null, true);
}
}
} else {
// We got some unresolved symbol, which usually means list>
Symbol listElement = typeOrSymbol.getUnresolvedSymbol();
if (listElement instanceof Terminal && listElement.getType() == null) {
error(listElement, "terminal symbol must have a type");
} else {
whenMapped(listElement, () -> {
if (listElement.getType() instanceof VoidType) {
mapper.map(n, VoidType.INSTANCE);
return;
}
mapNonterm(n, builder.list(
listElement.getType(), list.isNonEmpty(), n));
for (RhsSymbol sym : rhsSymbols) {
mapper.map(sym, null, null, true);
}
});
}
}
} else {
AstClass elementClass = builder.addClass(
getNonterminalTypeName(n, "_item"), null, n);
mapNonterm(n, builder.list(elementClass, list.isNonEmpty(), n));
mapper.map(list.getElement(), null, elementClass, true);
if (list.getCustomInitialElement() != null) {
mapper.map(list.getCustomInitialElement(), null, elementClass, true);
}
mapClass(elementClass, list.getElement(), list.getCustomInitialElement());
}
i.remove();
}
}
private void mapClass(AstClass cl, RhsPart... parts) {
List rhsParts = classContent.get(cl);
if (rhsParts == null) {
rhsParts = new ArrayList<>();
classContent.put(cl, rhsParts);
}
for (RhsPart part : parts) {
if (part != null) {
rhsParts.add(part);
}
}
}
private void mapFields() {
TMFieldMapper fieldMapper = new TMFieldMapper(status, builder, mapper, allowTypeAny);
for (AstClass cl : classContent.keySet()) {
List rhsParts = classContent.get(cl);
if (rhsParts == null || rhsParts.isEmpty()) continue;
RhsPart def = rhsParts.size() == 1
? rhsParts.get(0)
: RhsUtil.asChoice(rhsParts.toArray(new RhsPart[rhsParts.size()]));
fieldMapper.mapFields(cl, def);
}
}
private void error(SourceElement element, String message) {
status.report(ProcessingStatus.KIND_ERROR, message, element);
}
/**
* Returns AST type of the given part (if exists). Builds a list of all RhsSymbols that
* contribute to the returned type.
*/
private TypeOrSymbolHandle getTypeOrUnresolvedSymbol(RhsPart part, List out) {
part = RhsUtil.unwrap(part);
RhsCast cast = null;
boolean optional = false;
while (part != null) {
if (part instanceof RhsSequence) {
part = RhsUtil.unwrap(withoutConstants(part));
} else if (part instanceof RhsOptional) {
part = RhsUtil.unwrap(((RhsOptional) part).getPart());
optional = true;
} else if (part instanceof RhsChoice) {
TypeOrSymbolHandle result = null;
for (RhsPart p : ((RhsChoice) part).getParts()) {
TypeOrSymbolHandle update = getTypeOrUnresolvedSymbol(p, out);
if (update == null) {
if (!RhsUtil.isEmpty(RhsUtil.unwrap(p))) return null;
optional = true;
continue;
}
result = result == null ? update : result.merge(update);
if (result == null) break;
}
if (optional && result != null) {
result = result.toOptional();
}
return result;
} else if (part instanceof RhsCast) {
cast = (RhsCast) part;
part = RhsUtil.unwrap(cast.getPart());
} else if (part instanceof RhsSymbol) {
// TODO handle template vars (create an AST type for it)
Symbol sym = cast != null ? cast.getTarget() : ((RhsSymbol) part).getTarget();
out.add((RhsSymbol) part);
return new TypeOrSymbolHandle(sym, optional);
} else {
part = null;
}
}
return null;
}
private static boolean hasClassHint(Nonterminal n) {
TMTypeHint typeHint = TMDataUtil.getTypeHint(n);
return typeHint != null && typeHint.getKind() == Kind.CLASS ||
TMDataUtil.hasProperty(n, "_class");
}
private static boolean hasInterfaceHint(Nonterminal n) {
TMTypeHint typeHint = TMDataUtil.getTypeHint(n);
return typeHint != null && typeHint.getKind() == Kind.INTERFACE ||
TMDataUtil.hasProperty(n, "_interface");
}
private static boolean hasVoidHint(Nonterminal n) {
TMTypeHint typeHint = TMDataUtil.getTypeHint(n);
return typeHint != null && typeHint.getKind() == Kind.VOID ||
TMDataUtil.hasProperty(n, "noast");
}
private final class TypeOrSymbolHandle {
// One of the following two is not null.
private final Symbol unresolvedSymbol;
private final AstType type;
private final boolean isNullable;
private TypeOrSymbolHandle(Symbol symbol, boolean optional) {
this(symbol.getType() == null ? symbol : null, symbol.getType(), optional);
}
private TypeOrSymbolHandle(Symbol symbol, AstType type, boolean optional) {
assert (symbol == null) ^ (type == null);
this.unresolvedSymbol = symbol;
this.type = type;
this.isNullable = optional;
}
public TypeOrSymbolHandle merge(TypeOrSymbolHandle other) {
if (other == null) return null;
if (unresolvedSymbol != null && other.unresolvedSymbol == unresolvedSymbol) {
return isNullable ? this : other;
}
if (type != null && other.type != null) {
AstType common = TypesUtil.getJoinType(type, other.type);
if (common != null) {
return new TypeOrSymbolHandle(null, common, isNullable || other.isNullable);
}
}
return null;
}
public TypeOrSymbolHandle toOptional() {
return new TypeOrSymbolHandle(unresolvedSymbol, type, true);
}
public boolean isNullable() {
return isNullable;
}
public Symbol getUnresolvedSymbol() {
return unresolvedSymbol;
}
public AstType getType() {
return type;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy