comparison src/nabble/naml/compiler/Macro.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:7ecd1a4ef557
1 package nabble.naml.compiler;
2
3 import fschmidt.util.java.CollectionUtils;
4 import nabble.naml.dom.Attribute;
5 import nabble.naml.dom.Cdata;
6 import nabble.naml.dom.Container;
7 import nabble.naml.dom.Element;
8 import nabble.naml.dom.ElementName;
9 import nabble.naml.dom.EmptyElement;
10 import nabble.naml.dom.Naml;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.ListIterator;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.regex.Pattern;
24
25
26 public final class Macro implements Call, Meaning {
27 private static final Logger logger = LoggerFactory.getLogger(Macro.class);
28
29 public enum Type {
30 MACRO, SUBROUTINE, TRANSLATION, NAMESPACE;
31
32 final String tagName;
33
34 Type() {
35 this.tagName = name().toLowerCase();
36 }
37 };
38
39 static final String OVERRIDE_PREFIX = "override_";
40
41 public final Source source;
42 public final Element element;
43 final String name;
44 final Set<String> parameters = new HashSet<String>();
45 final String dotParam;
46 final List<String> requiredNamespaces;
47 final boolean isOverride;
48 final Naml naml;
49 final Type type;
50 final String extendsNs;
51 private final String id;
52
53 private static final ElementName fromName = new ElementName("from");
54 private static final ElementName toName = new ElementName("to");
55 private static final ElementName doName = new ElementName("n.do");
56
57 Macro(Source source,Element element,StackTrace stackTrace,Map<String,Integer> macroCount) throws CompileException {
58 this.source = source;
59 this.element = element;
60 Map<String,Attribute> attrs = element.attributeMap();
61 String tagName = element.name().parts().get(0).text();
62 if( tagName.startsWith(OVERRIDE_PREFIX) ) {
63 this.isOverride = true;
64 tagName = tagName.substring(OVERRIDE_PREFIX.length());
65 } else {
66 this.isOverride = false;
67 }
68 if( tagName.equals(Type.NAMESPACE.tagName) ) {
69 this.type = Type.NAMESPACE;
70 if( !(element instanceof EmptyElement) )
71 throw new CompileException(stackTrace,element.name()+" must be empty");
72 this.name = asString(attrs.remove("name"),stackTrace);
73 if( name==null )
74 throw new CompileException(stackTrace,"'name' attribute required");
75 this.dotParam = "do";
76 this.parameters.add(dotParam);
77 this.requiredNamespaces = Collections.emptyList();
78 this.naml = new Naml();
79 this.naml.add( new EmptyElement(doName,Collections.<Attribute>emptyList(),"",element.lineNumber()) );
80 this.extendsNs = asString(attrs.remove("extends"),stackTrace);
81 } else if( tagName.equals(Type.TRANSLATION.tagName) ) {
82 this.type = Type.TRANSLATION;
83 if( !attrs.isEmpty() )
84 throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates");
85 if( !(element instanceof Container) )
86 throw new CompileException(stackTrace,"empty "+element.name()+" not allowed");
87 Container container = (Container)element;
88 this.dotParam = null;
89 this.requiredNamespaces = Collections.emptyList();
90 Iterator<Object> iter = container.contents().iterator();
91 Object obj = iter.next();
92 if( obj instanceof String )
93 obj = iter.next();
94 Container from = (Container)obj;
95 if( !from.name().equals(fromName) )
96 throw new CompileException(stackTrace,"'from' tag expected");
97 obj = iter.next();
98 if( obj instanceof String )
99 obj = iter.next();
100 Container to = (Container)obj;
101 if( !to.name().equals(toName) )
102 throw new CompileException(stackTrace,"'to' tag expected");
103 this.name = translationMacroName(from.contents(),parameters);
104 this.naml = to.contents();
105 this.extendsNs = null;
106 } else {
107 this.type = tagName.equals(Type.SUBROUTINE.tagName) ? Type.SUBROUTINE : Type.MACRO;
108 if( !(element instanceof Container) )
109 throw new CompileException(stackTrace,"empty "+element.name()+" not allowed");
110 Container container = (Container)element;
111 this.name = asString(attrs.remove("name"),stackTrace);
112 if( name==null )
113 throw new CompileException(stackTrace,"'name' attribute required");
114 if( name.indexOf('-') != -1 )
115 throw new CompileException(stackTrace,"macro name may not contain '-'");
116 parseAttrSet(parameters,asString(attrs.remove("parameters"),stackTrace));
117 String dotParam = null;
118 {
119 List<String> dotParamNames = new ArrayList<String>();
120 parseAttrSet(dotParamNames,asString(attrs.remove("dot_parameter"),stackTrace));
121 if( dotParamNames.size() > 1 )
122 throw new CompileException(stackTrace,"only one dot_parameter allowed");
123 if( dotParamNames.size() == 1 ) {
124 dotParam = dotParamNames.get(0);
125 parameters.add(dotParam);
126 }
127 }
128 this.dotParam = dotParam;
129 for( String s : parameters ) {
130 if( s.indexOf('-') != -1 )
131 throw new CompileException(stackTrace,"macro attibutes may not contain '-'");
132 }
133 List<String> requiredNamespaces = new ArrayList<String>();
134 parseAttrSet(requiredNamespaces,asString(attrs.remove("requires"),stackTrace));
135 this.requiredNamespaces = CollectionUtils.optimizeList(requiredNamespaces);
136 boolean unindent = "true".equals(asString(attrs.remove("unindent"),stackTrace));
137 if( type==Type.SUBROUTINE && this.requiredNamespaces.isEmpty() )
138 throw new CompileException(stackTrace,"subroutine must specify required stack");
139 if( !attrs.isEmpty() )
140 throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates");
141 Naml naml = trim(container.contents());
142 if( unindent )
143 naml = unindent(naml);
144 this.naml = naml;
145 this.extendsNs = null;
146 }
147 {
148 StringBuilder buf = new StringBuilder();
149 buf.append( name );
150 buf.append( '!' ).append( source );
151 Integer count = macroCount.get(this.name);
152 if( count == null ) {
153 macroCount.put(this.name,1);
154 } else {
155 count += 1;
156 macroCount.put(this.name,count);
157 buf.append( '!' ).append( count );
158 }
159 id = buf.toString().intern();
160 }
161 }
162
163 public static String getNameFromId(String id) {
164 return id.substring(0, id.indexOf('!'));
165 }
166
167 public static String getSourceFromId(String id) {
168 int posColon = id.indexOf(':');
169 int posCount = id.indexOf('!', posColon);
170 return id.substring(posColon+1, posCount >= 0? posCount : id.length());
171 }
172
173 private static final Pattern spacePtn = Pattern.compile("\\s+");
174
175 static String translationMacroName(Naml contents,Set<String> parameters) {
176 String text = gutTranslationArgs(contents,parameters).toString();
177 return "translation: " + spacePtn.matcher(text).replaceAll(" ");
178 }
179
180 private static Naml gutTranslationArgs(Naml oldContents,Set<String> parameters) {
181 boolean changed = false;
182 Naml newContents = new Naml();
183 for( Object obj : oldContents ) {
184 if( obj instanceof Element ) {
185 Element element = (Element)obj;
186 ElementName name = element.name();
187 List<ElementName.Part> parts = name.parts();
188 if( parts.size() >= 2 && "t".equals(parts.get(0).text()) ) {
189 changed = true;
190 if( parts.size() > 2 )
191 name = new ElementName( false, parts.subList(0,2) );
192 Element newElement = new EmptyElement(name,Collections.<Attribute>emptyList(),"",element.lineNumber());
193 newContents.add(newElement);
194 if( parameters != null )
195 parameters.add(parts.get(1).text());
196 continue;
197 }
198 if( element instanceof Container ) {
199 Container oldContainer = (Container)element;
200 Naml oldContainerContents = oldContainer.contents();
201 Naml newContainerContents = gutTranslationArgs(oldContainerContents,parameters);
202 if( newContainerContents != oldContainerContents ) {
203 changed = true;
204 Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,"");
205 newContents.add(newContainer);
206 continue;
207 }
208 }
209 }
210 newContents.add(obj);
211 }
212 return changed ? newContents : oldContents;
213 }
214
215 private static String asString(Attribute attr,StackTrace stackTrace) throws CompileException {
216 if( attr == null )
217 return null;
218 Naml value = attr.value();
219 if( value.isEmpty() )
220 throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have a value");
221 Object obj = value.get(0);
222 if( value.size() > 1 || !(obj instanceof String) )
223 throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have string value");
224 String s = (String)obj;
225 if( s.length()==0 )
226 throw new CompileException(stackTrace,"attribute '"+attr.name()+"' may not be empty");
227 return s;
228 }
229
230 private static void parseAttrSet(Collection<String> col,String attrS) {
231 if( attrS != null ) {
232 for( String s : attrS.split(",") ) {
233 s = s.trim();
234 if( !s.equals("") )
235 col.add(s);
236 }
237 }
238 }
239
240 static Naml trim(Naml naml) {
241 if( naml.size()==0 )
242 return naml;
243 boolean isChanged = false;
244 Object firstObj = naml.get(0);
245 if( firstObj instanceof String ) {
246 String s = (String)firstObj;
247 int i = 0;
248 if( Character.isWhitespace(s.charAt(i)) ) {
249 isChanged = true;
250 naml = new Naml(naml);
251 while( ++i < s.length() && Character.isWhitespace(s.charAt(i)) );
252 if( i == s.length() ) {
253 naml.remove(0);
254 if( naml.size()==0 )
255 return naml;
256 } else {
257 naml.set(0,s.substring(i));
258 }
259 }
260 }
261 int last = naml.size() - 1;
262 Object lastObj = naml.get(last);
263 if( lastObj instanceof String ) {
264 String s = (String)lastObj;
265 int i = s.length() - 1;
266 if( Character.isWhitespace(s.charAt(i)) ) {
267 if( !isChanged )
268 naml = new Naml(naml);
269 while( --i >= 0 && Character.isWhitespace(s.charAt(i)) );
270 if( i == -1 ) {
271 naml.remove(last);
272 } else {
273 naml.set(last,s.substring(0,i+1));
274 }
275 }
276 }
277 return naml;
278 }
279
280 private static final Pattern indentPtn = Pattern.compile("([\\r\\n])[ \\t]+");
281
282 private static Naml unindent(Naml naml) {
283 boolean isChanged = false;
284 Naml newNaml = new Naml(naml);
285 for( ListIterator<Object> iter = newNaml.listIterator(); iter.hasNext(); ) {
286 Object obj = iter.next();
287 if( obj instanceof String ) {
288 String s = (String)obj;
289 String s2 = indentPtn.matcher(s).replaceAll("$1");
290 if( !s.equals(s2) ) {
291 isChanged = true;
292 iter.set(s2);
293 }
294 } else if( obj instanceof Cdata ) {
295 Cdata cdata = (Cdata)obj;
296 String s = cdata.text();
297 String s2 = indentPtn.matcher(s).replaceAll("$1");
298 if( !s.equals(s2) ) {
299 isChanged = true;
300 iter.set(new Cdata(s2));
301 }
302 } else if( obj instanceof Container ) {
303 Container c = (Container)obj;
304 Naml contents = c.contents();
305 Naml contents2 = unindent(contents);
306 if( contents != contents2 ) {
307 isChanged = true;
308 Container c2 = new Container(c.name(),c.attributes(),c.spaceAtEndOfClosingTag(),c.lineNumber(),contents2,c.spaceAtEndOfClosingTag());
309 iter.set(c2);
310 }
311 }
312 }
313 return isChanged ? newNaml : naml;
314 }
315
316 @Override public String getName() {
317 return name;
318 }
319
320 @Override public Collection<String> getRequiredNamespaces() {
321 return requiredNamespaces;
322 }
323
324 @Override public String getId() {
325 return id;
326 }
327
328 @Override public String toString() {
329 return name + " - " + source;
330 }
331
332 public Type getType() {
333 return type;
334 }
335
336 public boolean isOverride() {
337 return isOverride;
338 }
339
340 public String[] getParameterNames() {
341 return parameters.toArray(new String[0]);
342 }
343 /*
344 @Override public boolean equals(Object obj) {
345 if( obj == this )
346 return true;
347 if( !(obj instanceof Macro) )
348 return false;
349 Macro m = (Macro)obj;
350 return m.key.equals(key) && m.source==source;
351 }
352
353 @Override public int hashCode() {
354 return key.hashCode() + 31*source.id.hashCode();
355 }
356 */
357 }