Mercurial Hosting > nabble
view src/nabble/naml/compiler/Macro.java @ 62:4674ed7d56df default tip
remove n2
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sat, 30 Sep 2023 20:25:29 -0600 |
parents | 7ecd1a4ef557 |
children |
line wrap: on
line source
package nabble.naml.compiler; import fschmidt.util.java.CollectionUtils; import nabble.naml.dom.Attribute; import nabble.naml.dom.Cdata; import nabble.naml.dom.Container; import nabble.naml.dom.Element; import nabble.naml.dom.ElementName; import nabble.naml.dom.EmptyElement; import nabble.naml.dom.Naml; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; public final class Macro implements Call, Meaning { private static final Logger logger = LoggerFactory.getLogger(Macro.class); public enum Type { MACRO, SUBROUTINE, TRANSLATION, NAMESPACE; final String tagName; Type() { this.tagName = name().toLowerCase(); } }; static final String OVERRIDE_PREFIX = "override_"; public final Source source; public final Element element; final String name; final Set<String> parameters = new HashSet<String>(); final String dotParam; final List<String> requiredNamespaces; final boolean isOverride; final Naml naml; final Type type; final String extendsNs; private final String id; private static final ElementName fromName = new ElementName("from"); private static final ElementName toName = new ElementName("to"); private static final ElementName doName = new ElementName("n.do"); Macro(Source source,Element element,StackTrace stackTrace,Map<String,Integer> macroCount) throws CompileException { this.source = source; this.element = element; Map<String,Attribute> attrs = element.attributeMap(); String tagName = element.name().parts().get(0).text(); if( tagName.startsWith(OVERRIDE_PREFIX) ) { this.isOverride = true; tagName = tagName.substring(OVERRIDE_PREFIX.length()); } else { this.isOverride = false; } if( tagName.equals(Type.NAMESPACE.tagName) ) { this.type = Type.NAMESPACE; if( !(element instanceof EmptyElement) ) throw new CompileException(stackTrace,element.name()+" must be empty"); this.name = asString(attrs.remove("name"),stackTrace); if( name==null ) throw new CompileException(stackTrace,"'name' attribute required"); this.dotParam = "do"; this.parameters.add(dotParam); this.requiredNamespaces = Collections.emptyList(); this.naml = new Naml(); this.naml.add( new EmptyElement(doName,Collections.<Attribute>emptyList(),"",element.lineNumber()) ); this.extendsNs = asString(attrs.remove("extends"),stackTrace); } else if( tagName.equals(Type.TRANSLATION.tagName) ) { this.type = Type.TRANSLATION; if( !attrs.isEmpty() ) throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates"); if( !(element instanceof Container) ) throw new CompileException(stackTrace,"empty "+element.name()+" not allowed"); Container container = (Container)element; this.dotParam = null; this.requiredNamespaces = Collections.emptyList(); Iterator<Object> iter = container.contents().iterator(); Object obj = iter.next(); if( obj instanceof String ) obj = iter.next(); Container from = (Container)obj; if( !from.name().equals(fromName) ) throw new CompileException(stackTrace,"'from' tag expected"); obj = iter.next(); if( obj instanceof String ) obj = iter.next(); Container to = (Container)obj; if( !to.name().equals(toName) ) throw new CompileException(stackTrace,"'to' tag expected"); this.name = translationMacroName(from.contents(),parameters); this.naml = to.contents(); this.extendsNs = null; } else { this.type = tagName.equals(Type.SUBROUTINE.tagName) ? Type.SUBROUTINE : Type.MACRO; if( !(element instanceof Container) ) throw new CompileException(stackTrace,"empty "+element.name()+" not allowed"); Container container = (Container)element; this.name = asString(attrs.remove("name"),stackTrace); if( name==null ) throw new CompileException(stackTrace,"'name' attribute required"); if( name.indexOf('-') != -1 ) throw new CompileException(stackTrace,"macro name may not contain '-'"); parseAttrSet(parameters,asString(attrs.remove("parameters"),stackTrace)); String dotParam = null; { List<String> dotParamNames = new ArrayList<String>(); parseAttrSet(dotParamNames,asString(attrs.remove("dot_parameter"),stackTrace)); if( dotParamNames.size() > 1 ) throw new CompileException(stackTrace,"only one dot_parameter allowed"); if( dotParamNames.size() == 1 ) { dotParam = dotParamNames.get(0); parameters.add(dotParam); } } this.dotParam = dotParam; for( String s : parameters ) { if( s.indexOf('-') != -1 ) throw new CompileException(stackTrace,"macro attibutes may not contain '-'"); } List<String> requiredNamespaces = new ArrayList<String>(); parseAttrSet(requiredNamespaces,asString(attrs.remove("requires"),stackTrace)); this.requiredNamespaces = CollectionUtils.optimizeList(requiredNamespaces); boolean unindent = "true".equals(asString(attrs.remove("unindent"),stackTrace)); if( type==Type.SUBROUTINE && this.requiredNamespaces.isEmpty() ) throw new CompileException(stackTrace,"subroutine must specify required stack"); if( !attrs.isEmpty() ) throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates"); Naml naml = trim(container.contents()); if( unindent ) naml = unindent(naml); this.naml = naml; this.extendsNs = null; } { StringBuilder buf = new StringBuilder(); buf.append( name ); buf.append( '!' ).append( source ); Integer count = macroCount.get(this.name); if( count == null ) { macroCount.put(this.name,1); } else { count += 1; macroCount.put(this.name,count); buf.append( '!' ).append( count ); } id = buf.toString().intern(); } } public static String getNameFromId(String id) { return id.substring(0, id.indexOf('!')); } public static String getSourceFromId(String id) { int posColon = id.indexOf(':'); int posCount = id.indexOf('!', posColon); return id.substring(posColon+1, posCount >= 0? posCount : id.length()); } private static final Pattern spacePtn = Pattern.compile("\\s+"); static String translationMacroName(Naml contents,Set<String> parameters) { String text = gutTranslationArgs(contents,parameters).toString(); return "translation: " + spacePtn.matcher(text).replaceAll(" "); } private static Naml gutTranslationArgs(Naml oldContents,Set<String> parameters) { boolean changed = false; Naml newContents = new Naml(); for( Object obj : oldContents ) { if( obj instanceof Element ) { Element element = (Element)obj; ElementName name = element.name(); List<ElementName.Part> parts = name.parts(); if( parts.size() >= 2 && "t".equals(parts.get(0).text()) ) { changed = true; if( parts.size() > 2 ) name = new ElementName( false, parts.subList(0,2) ); Element newElement = new EmptyElement(name,Collections.<Attribute>emptyList(),"",element.lineNumber()); newContents.add(newElement); if( parameters != null ) parameters.add(parts.get(1).text()); continue; } if( element instanceof Container ) { Container oldContainer = (Container)element; Naml oldContainerContents = oldContainer.contents(); Naml newContainerContents = gutTranslationArgs(oldContainerContents,parameters); if( newContainerContents != oldContainerContents ) { changed = true; Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,""); newContents.add(newContainer); continue; } } } newContents.add(obj); } return changed ? newContents : oldContents; } private static String asString(Attribute attr,StackTrace stackTrace) throws CompileException { if( attr == null ) return null; Naml value = attr.value(); if( value.isEmpty() ) throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have a value"); Object obj = value.get(0); if( value.size() > 1 || !(obj instanceof String) ) throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have string value"); String s = (String)obj; if( s.length()==0 ) throw new CompileException(stackTrace,"attribute '"+attr.name()+"' may not be empty"); return s; } private static void parseAttrSet(Collection<String> col,String attrS) { if( attrS != null ) { for( String s : attrS.split(",") ) { s = s.trim(); if( !s.equals("") ) col.add(s); } } } static Naml trim(Naml naml) { if( naml.size()==0 ) return naml; boolean isChanged = false; Object firstObj = naml.get(0); if( firstObj instanceof String ) { String s = (String)firstObj; int i = 0; if( Character.isWhitespace(s.charAt(i)) ) { isChanged = true; naml = new Naml(naml); while( ++i < s.length() && Character.isWhitespace(s.charAt(i)) ); if( i == s.length() ) { naml.remove(0); if( naml.size()==0 ) return naml; } else { naml.set(0,s.substring(i)); } } } int last = naml.size() - 1; Object lastObj = naml.get(last); if( lastObj instanceof String ) { String s = (String)lastObj; int i = s.length() - 1; if( Character.isWhitespace(s.charAt(i)) ) { if( !isChanged ) naml = new Naml(naml); while( --i >= 0 && Character.isWhitespace(s.charAt(i)) ); if( i == -1 ) { naml.remove(last); } else { naml.set(last,s.substring(0,i+1)); } } } return naml; } private static final Pattern indentPtn = Pattern.compile("([\\r\\n])[ \\t]+"); private static Naml unindent(Naml naml) { boolean isChanged = false; Naml newNaml = new Naml(naml); for( ListIterator<Object> iter = newNaml.listIterator(); iter.hasNext(); ) { Object obj = iter.next(); if( obj instanceof String ) { String s = (String)obj; String s2 = indentPtn.matcher(s).replaceAll("$1"); if( !s.equals(s2) ) { isChanged = true; iter.set(s2); } } else if( obj instanceof Cdata ) { Cdata cdata = (Cdata)obj; String s = cdata.text(); String s2 = indentPtn.matcher(s).replaceAll("$1"); if( !s.equals(s2) ) { isChanged = true; iter.set(new Cdata(s2)); } } else if( obj instanceof Container ) { Container c = (Container)obj; Naml contents = c.contents(); Naml contents2 = unindent(contents); if( contents != contents2 ) { isChanged = true; Container c2 = new Container(c.name(),c.attributes(),c.spaceAtEndOfClosingTag(),c.lineNumber(),contents2,c.spaceAtEndOfClosingTag()); iter.set(c2); } } } return isChanged ? newNaml : naml; } @Override public String getName() { return name; } @Override public Collection<String> getRequiredNamespaces() { return requiredNamespaces; } @Override public String getId() { return id; } @Override public String toString() { return name + " - " + source; } public Type getType() { return type; } public boolean isOverride() { return isOverride; } public String[] getParameterNames() { return parameters.toArray(new String[0]); } /* @Override public boolean equals(Object obj) { if( obj == this ) return true; if( !(obj instanceof Macro) ) return false; Macro m = (Macro)obj; return m.key.equals(key) && m.source==source; } @Override public int hashCode() { return key.hashCode() + 31*source.id.hashCode(); } */ }