comparison src/nabble/naml/compiler/Compiler.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.ArrayStack;
4 import fschmidt.util.java.CollectionUtils;
5 import fschmidt.util.java.ObjectUtils;
6 import fschmidt.util.java.Stack;
7 import nabble.naml.dom.Attribute;
8 import nabble.naml.dom.Cdata;
9 import nabble.naml.dom.Container;
10 import nabble.naml.dom.Element;
11 import nabble.naml.dom.ElementName;
12 import nabble.naml.dom.EmptyElement;
13 import nabble.naml.dom.Naml;
14 import nabble.naml.dom.ParseException;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17
18 import java.io.CharArrayWriter;
19 import java.lang.reflect.InvocationTargetException;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.CopyOnWriteArraySet;
31
32
33 final class Compiler {
34 private static final Logger logger = LoggerFactory.getLogger(Compiler.class);
35
36 private Traceable traceable = null;
37 private final List<Traceable> traceables = new ArrayList<Traceable>();
38 private final StackTrace stackTrace = new StackTrace() {
39
40 public StackTraceElement pop() {
41 StackTraceElement ste = super.pop();
42 if( traceable != null )
43 traceable.stackTrace.push(ste.intern());
44 return ste;
45 }
46
47 };
48 private final Stack<GenericNamespace> stack = new ArrayStack<GenericNamespace>();
49 private final Program program;
50 private final Map<String,List<Macro>> macros;
51 private final Map<Macro,Macro> overrides;
52 private final Set<String> staticNames;
53 private final GenericNamespace[] base;
54
55 static Template compile( Program program, String templateName, GenericNamespace[] base )
56 throws CompileException
57 {
58 return new Compiler(program,base).compile(templateName);
59 }
60
61 private Compiler( Program program, GenericNamespace[] base ) {
62 this.program = program;
63 this.macros = program.macros();
64 this.overrides = program.overrides();
65 this.staticNames = program.staticNames();
66 this.base = base;
67 }
68
69 private Template compile( String templateName )
70 throws CompileException
71 {
72 for( GenericNamespace ns : base ) {
73 pushWithExtensions(ns);
74 }
75 Macro macro = getTemplate(templateName);
76 if( macro==null )
77 return null;
78 addMeaning(null,macro,null);
79 StackTraceElement stackElem = new StackTraceElement(macro);
80 stackTrace.push( stackElem );
81 try {
82 MacroScope macroScope = new MacroScope(macro);
83 for( String s : macro.parameters ) {
84 macroScope.addParamChunk( new ParamChunk(s) );
85 }
86 Chunk chunk = compileMacro( macroScope );
87 if( chunk instanceof ErrorChunk )
88 throw ((ErrorChunk)chunk).e;
89 chunk = chunk.intern();
90 Template template = new Template(this,program,chunk,classes(base),macro);
91 return template;
92 } finally {
93 stackTrace.pop();
94 cleanUpTraceables();
95 }
96 }
97
98 private static Class<?>[] classes(GenericNamespace[] nss) {
99 List<Class<?>> classes = new ArrayList<Class<?>>();
100 for( GenericNamespace ns : nss ) {
101 if( ns instanceof JavaNamespace ) {
102 JavaNamespace jns = (JavaNamespace)ns;
103 classes.add(jns.cls);
104 }
105 }
106 return classes.toArray(new Class<?>[0]);
107 }
108
109 private void cleanUpTraceables() {
110 for( Traceable t : traceables ) {
111 t.cleanUp();
112 }
113 }
114
115 private void addMeaning(ElementName.Part part,Meaning meaning,MacroScope macroScope) {
116 program.addMeaning(part,meaning,macroScope,base);
117 }
118
119 private int pushWithExtensions(GenericNamespace gns) {
120 stack.push(gns);
121 if( !(gns instanceof JavaNamespace) )
122 return 1;
123 JavaNamespace ns = (JavaNamespace)gns;
124 List<JavaNamespace> extensions = program.extensionMap().get(ns.cls);
125 if( extensions == null ) {
126 return 1;
127 }
128 for( JavaNamespace extension : extensions ) {
129 stack.push(extension);
130 }
131 return 1 + extensions.size();
132 }
133
134 private int push(JavaCall javaCall,String param) {
135 if( javaCall==null || !javaCall.isScoped(param) )
136 return 0;
137 return pushWithExtensions(JavaNamespace.getNamespace(javaCall.scopedType()));
138 }
139
140 private int push(Macro macro) throws CompileException {
141 if( macro.type != Macro.Type.NAMESPACE )
142 return 0;
143 MacroNamespace ns = new MacroNamespace(macro,stackTrace,macros);
144 stack.push(ns);
145 return 1;
146 }
147
148 private void pop(int n) {
149 for( ; n > 0; n-- ) {
150 stack.pop();
151 }
152 }
153
154 private int whereInStack(String namespace) {
155 if( namespace == null )
156 throw new NullPointerException("namespace is null");
157 boolean isTop = true;
158 for( int i = stack.size() - 1; i>=0; i-- ) {
159 GenericNamespace ns = stack.get(i);
160 if( (isTop || ns.isGlobal()) && ns.names().contains(namespace) )
161 return i;
162 if( isTop && !ns.isTransparent() )
163 isTop = false;
164 }
165 return -1;
166 }
167
168 private boolean hasNamespace(String namespace) {
169 return whereInStack(namespace) != -1;
170 }
171
172 private String accessibleStack() {
173 final int top = stack.size() - 1;
174 boolean isTop = true;
175 List<GenericNamespace> list = new ArrayList<GenericNamespace>();
176 for( int i=top; i>=0; i-- ) {
177 GenericNamespace ns = stack.get(i);
178 if( ns.isGlobal() || isTop ) {
179 list.add(ns);
180 if( isTop && !ns.isTransparent() )
181 isTop = false;
182 }
183 }
184 Collections.reverse(list);
185 return list.toString();
186 }
187
188 int whereInStack(Class<?> cls)
189 throws CompileException
190 {
191 boolean isTop = true;
192 for( int i = stack.size() - 1; i>=0; i-- ) {
193 GenericNamespace gns = stack.get(i);
194 if( !(gns instanceof JavaNamespace) )
195 continue;
196 JavaNamespace ns = (JavaNamespace)gns;
197 if( !isTop && !ns.isGlobal() )
198 continue;
199 if( cls.isAssignableFrom(ns.cls) )
200 return i;
201 if( isTop && !ns.isTransparent() )
202 isTop = false;
203 }
204 throw new CompileException(stackTrace,"required namespace '"+JavaNamespace.getNamespace(cls)+"' not found in "+accessibleStack()+" stack = "+stack);
205 }
206
207 private Macro getTemplate(String templateName)
208 throws CompileException
209 {
210 Set<String> namespacesOnStack = new HashSet<String>();
211 for( GenericNamespace ns : stack ) {
212 namespacesOnStack.addAll( ns.names() );
213 }
214 List<Macro> candidates = macros.get(templateName);
215 if( candidates == null )
216 return null;
217 Macro rtn = null;
218 for( Macro candidate : candidates ) {
219 if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) {
220 if( rtn != null )
221 throw conflict(candidate,rtn);
222 rtn = candidate;
223 }
224 }
225 return rtn;
226 }
227
228 private Chunk compileMacro(MacroScope macroScope)
229 throws CompileException
230 {
231 Macro macro = macroScope.macro;
232 for( String ns : macro.requiredNamespaces ) {
233 if( !hasNamespace(ns) )
234 throw new CompileException(stackTrace,"required namespace '"+ns+"' for "+new StackTraceElement(macro)+" not found in "+accessibleStack()+" stack = "+stack);
235 }
236 if( macroScope.parentScope != null && macro.type==Macro.Type.SUBROUTINE && program.getMacroWhichOverrides(macro)==null )
237 return compileSubroutine(macro,macroScope);
238 int pushed = push(macro);
239 try {
240 Chunk chunk = consolidate( compile( macro.naml, macroScope ) );
241 chunk = trimChunk(chunk);
242 if( !(chunk instanceof ErrorChunk) && macroScope.hasVars )
243 chunk = new VarScope(chunk,macroScope);
244 return chunk;
245 } catch(StackOverflowError e) {
246 throw new CompileException(stackTrace,"stack overflow, probably recursive call");
247 } finally {
248 pop(pushed);
249 }
250 }
251
252 private Chunk compileSubroutine(Macro macro,MacroScope macroScope)
253 throws CompileException
254 {
255 Map<String,Stringable> argMap = new HashMap<String,Stringable>();
256 for( String argName : macroScope.getArgNames() ) {
257 Naml argNaml = macroScope.getArg(argName);
258 Chunk argValue = consolidate( compile(argNaml,macroScope.parentScope) );
259 if( argValue == Chunk.NULL )
260 continue;
261 if( argValue instanceof ErrorChunk )
262 return argValue;
263 Stringable s = argValue==emptyChunk ? new StringableString("")
264 : argValue instanceof StringChunk ? new StringableString(((StringChunk)argValue).s)
265 : new StringableChunk(argValue)
266 ;
267 argMap.put(argName,s);
268 }
269 GenericNamespace[] baseSub = new GenericNamespace[macro.requiredNamespaces.size()];
270 List<Integer> iStackList = new ArrayList<Integer>();
271 for( int i=0; i<baseSub.length; i++ ) {
272 String namespace = macro.requiredNamespaces.get(i);
273 int iStack = whereInStack(namespace);
274 if( iStack == -1 )
275 throw new CompileException(stackTrace,"namespace needed for subroutine '"+namespace+"' not found in "+accessibleStack()+" stack = "+stack);
276 GenericNamespace ns = stack.get(iStack);
277 baseSub[i] = ns;
278 if( ns instanceof JavaNamespace )
279 iStackList.add( iStack(iStack) );
280 }
281 int[] aiStack = new int[iStackList.size()];
282 for( int i=0; i<aiStack.length; i++ ) {
283 aiStack[i] = iStackList.get(i);
284 }
285 return new Subroutine(this,argMap,aiStack,baseSub,stackTrace.peek().commandName());
286 }
287
288
289 private static final ElementName.Part nPart = new ElementName.Part("n").intern();
290 private static final StringChunk closingTag = new StringChunk(">");
291 private static final StringChunk closingEmptyTag = new StringChunk("/>");
292
293 private List<Chunk> compile(Naml naml,MacroScope macroScope)
294 throws CompileException
295 {
296 final List<Chunk> chunks = new ArrayList<Chunk>();
297 for( Object obj : naml ) {
298 if( obj instanceof Element ) {
299 Element element = (Element)obj;
300 final ElementName name = element.name();
301 List<ElementName.Part> parts = name.parts();
302 if( parts.size() > 1 && parts.get(0) == nPart ) {
303 Chunk chunk = compileElement(element,macroScope);
304 chunks.add(chunk);
305 continue;
306 } else if( parts.size() == 1 && parts.get(0).text() == "t" ) {
307 Chunk chunk = compileTranslation(element,macroScope);
308 chunks.add(chunk);
309 continue;
310 }
311 if( !staticNames.contains(name.toLowerCaseString()) ) {
312 stackTrace.push( new StackTraceElement(macroScope.source(),element) );
313 try {
314 throw new CompileException(stackTrace,"invalid static tag: "+name);
315 } finally {
316 stackTrace.pop();
317 }
318 }
319 chunks.add( new StringChunk( "<" + element.name() ) );
320 for( Attribute attr : element.attributes() ) {
321 Chunk valueChunk = consolidate( compile( attr.value(), macroScope ) );
322 if( valueChunk instanceof ErrorChunk ) {
323 chunks.add( valueChunk );
324 } else if( valueChunk == emptyChunk ) {
325 chunks.add( new StringChunk( attr.toString("") ) );
326 } else if( valueChunk instanceof StringChunk ) {
327 StringChunk stringChunk = (StringChunk)valueChunk;
328 chunks.add( new StringChunk( attr.toString(stringChunk.s) ) );
329 } else {
330 StackTraceElement stackTraceElement = new StackTraceElement(macroScope.source(),element,("[attr - "+attr.name()+"]"));
331 stackTrace.push(stackTraceElement);
332 try {
333 chunks.add( new DynamicAttr( attr, valueChunk ) );
334 } finally {
335 stackTrace.pop();
336 }
337 }
338 }
339 String s = element.spaceAtEndOfOpeningTag();
340 if( s.length() > 0 )
341 chunks.add( new StringChunk(s) );
342 if( element instanceof Container ) {
343 chunks.add( closingTag );
344 Container container = (Container)element;
345 chunks.addAll( compile(container.contents(),macroScope) );
346 chunks.add( new StringChunk(container.closingTag()) );
347 } else {
348 chunks.add( closingEmptyTag );
349 }
350 continue;
351 } else if( obj instanceof Cdata ) {
352 Cdata cdata = (Cdata)obj;
353 chunks.add( new StringChunk(cdata.text()) );
354 continue;
355 }
356 String s = obj.toString();
357 if( s.length() > 0 )
358 chunks.add( new StringChunk(s) );
359 }
360 return chunks;
361 }
362
363 private Chunk compileTranslation(Element element,MacroScope macroScope)
364 throws CompileException
365 {
366 stackTrace.push( new StackTraceElement(macroScope.source(),element) );
367 try {
368 if( !(element instanceof Container) )
369 throw new CompileException(stackTrace,"'t' tag may not be empty");
370 if( element.name().endsWithDot() )
371 throw new CompileException(stackTrace,"'t' tag may not end with dot");
372 } finally {
373 stackTrace.pop();
374 }
375 Container container = (Container)element;
376 Naml contents = container.contents();
377 String macroName = Macro.translationMacroName(contents,null);
378 stackTrace.push( new StackTraceElement(macroScope.source(),element,macroName) );
379 try {
380 List<Macro> macroList = macros.get(macroName);
381 if( macroList == null ) {
382 contents = removeTranslationArgs(macroScope.source(),contents);
383 return consolidate( compile(contents,macroScope) );
384 }
385 if( macroList.size() != 1 )
386 throw new RuntimeException("invalid - "+macroList);
387 Macro macro = macroList.get(0);
388 MacroScope newScope = macroScope.newScope(macro);
389 getTranslationArgs(macroScope.source(),contents,newScope);
390 addMeaning(container.name().parts().get(0),macro,macroScope);
391 return getMacroChunk2(newScope);
392 } finally {
393 stackTrace.pop();
394 }
395 }
396
397 private Naml removeTranslationArgs(Source source,Naml oldContents) throws CompileException {
398 boolean changed = false;
399 Naml newContents = new Naml();
400 for( Object obj : oldContents ) {
401 if( obj instanceof Element ) {
402 Element element = (Element)obj;
403 ElementName name = element.name();
404 List<ElementName.Part> parts = name.parts();
405 if( parts.size() >= 2 && parts.get(0).text() == "t" ) {
406 changed = true;
407 stackTrace.push( new StackTraceElement(source,element) );
408 try {
409 if( parts.size() == 2 ) {
410 if( name.endsWithDot() )
411 throw new CompileException(stackTrace,"translation argument cannot end with dot");
412 if( !(element instanceof Container) )
413 throw new CompileException(stackTrace,"translation argument cannot be empty");
414 Container container = (Container)element;
415 newContents.addAll( container.contents() );
416 } else {
417 List<ElementName.Part> newParts = new ArrayList<ElementName.Part>();
418 newParts.add(nPart);
419 newParts.addAll( parts.subList(2,parts.size()) );
420 ElementName newName = new ElementName(name.endsWithDot(),newParts);
421 if( element instanceof Container ) {
422 Container container = (Container)element;
423 Naml newContainerContents = removeTranslationArgs(source,container.contents());
424 Element newElement = new Container(newName,element.attributes(),"",element.lineNumber(),newContainerContents,"");
425 newContents.add(newElement);
426 } else {
427 Element newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber());
428 newContents.add(newElement);
429 }
430 }
431 } finally {
432 stackTrace.pop();
433 }
434 continue;
435 }
436 if( element instanceof Container ) {
437 Container oldContainer = (Container)element;
438 Naml oldContainerContents = oldContainer.contents();
439 Naml newContainerContents = removeTranslationArgs(source,oldContainerContents);
440 if( newContainerContents != oldContainerContents ) {
441 changed = true;
442 Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,"");
443 newContents.add(newContainer);
444 continue;
445 }
446 }
447 }
448 newContents.add(obj);
449 }
450 return changed ? newContents : oldContents;
451 }
452
453 private void getTranslationArgs(Source source,Naml oldContents,MacroScope newScope) throws CompileException {
454 for( Object obj : oldContents ) {
455 if( obj instanceof Element ) {
456 Element element = (Element)obj;
457 ElementName name = element.name();
458 List<ElementName.Part> parts = name.parts();
459 if( parts.size() >= 2 && parts.get(0).text() == "t" ) {
460 String argName = parts.get(1).text();
461 stackTrace.push( new StackTraceElement(source,element) );
462 try {
463 if( parts.size() == 2 ) {
464 if( name.endsWithDot() )
465 throw new CompileException(stackTrace,"translation argument cannot end with dot");
466 if( !(element instanceof Container) )
467 throw new CompileException(stackTrace,"translation argument cannot be empty");
468 Container container = (Container)element;
469 newScope.addArg( argName, container.contents() );
470 } else {
471 newScope.addArg( argName, getMacroMultiDotArg(element,2,true) );
472 }
473 } finally {
474 stackTrace.pop();
475 }
476 continue;
477 }
478 if( element instanceof Container ) {
479 Container container = (Container)element;
480 getTranslationArgs(source,container.contents(),newScope);
481 continue;
482 }
483 }
484 }
485 }
486
487 private Chunk compileElement(Element element,MacroScope macroScope)
488 throws CompileException
489 {
490 ElementName name = element.name();
491 try {
492 if( name.parts().size() == 2 ) {
493 return singleMethodTag(element,macroScope);
494 } else {
495 return multiMethodTag(element,1,macroScope);
496 }
497 } catch(CompileMethodException e) {
498 return new ErrorChunk(e);
499 }
500 }
501
502 private Chunk singleMethodTag(Element element,MacroScope macroScope)
503 throws CompileException
504 {
505 ElementName.Part part = element.name().parts().get(1);
506 String cmdName = part.text();
507 StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,cmdName);
508 stackTrace.push(stackElem);
509 try {
510 Chunk chunk = getMacroArgChunk(cmdName,macroScope);
511 if( chunk != null ) {
512 if( element.name().endsWithDot() )
513 throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter");
514 if( element instanceof Container )
515 throw new CompileException(stackTrace,"parameter must be empty element, tag must end with '/'");
516 if( !element.attributes().isEmpty() )
517 throw new CompileException(stackTrace,"no arguments are allowed in a parameter");
518 addMeaning(part,ParamMeaning.INSTANCE,macroScope);
519 return chunk;
520 }
521 Call call = getCommandCall(cmdName,macroScope);
522 if( call instanceof Macro ) {
523 Macro macro = (Macro)call;
524 addMeaning(part,macro,macroScope);
525 return getMacroChunk(macro,element,macroScope,false);
526 }
527 JavaCall javaCall = (JavaCall)call;
528 stackElem.setMethod(javaCall.getMethod());
529 Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope);
530 if( element.name().endsWithDot() ) {
531 getDotArg( (Container)element, macroScope, javaCall, dynamicArgs );
532 } else if( element instanceof Container ) {
533 getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs );
534 }
535 addMeaning(part,javaCall.javaCommand,macroScope);
536 return methodChunk(element,macroScope,javaCall,dynamicArgs);
537 } finally {
538 stackTrace.pop();
539 }
540 }
541
542 private Chunk multiMethodTag(Element element,int iCmd,MacroScope macroScope)
543 throws CompileException
544 {
545 ElementName.Part part = element.name().parts().get(iCmd);
546 String methodName = part.text();
547 StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,methodName);
548 stackTrace.push(stackElem);
549 try {
550 Chunk chunk = getMacroArgChunk(methodName,macroScope);
551 if( chunk != null ) {
552 if( iCmd != element.name().parts().size() - 1 || element.name().endsWithDot() )
553 throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter");
554 if( !element.attributes().isEmpty() )
555 throw new CompileException(stackTrace,"no arguments are allowed in an parameter");
556 addMeaning(part,ParamMeaning.INSTANCE,macroScope);
557 return chunk;
558 }
559 Call call = getCommandCall(methodName,macroScope);
560 if( iCmd == element.name().parts().size() - 1 ) {
561 if( call instanceof Macro ) {
562 Macro macro = (Macro)call;
563 addMeaning(part,macro,macroScope);
564 return getMacroChunk(macro,element,macroScope,true);
565 }
566 JavaCall javaCall = (JavaCall)call;
567 stackElem.setMethod(javaCall.getMethod());
568 Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope);
569 if( element.name().endsWithDot() )
570 getDotArg( (Container)element, macroScope, javaCall, dynamicArgs );
571 addMeaning(part,javaCall.javaCommand,macroScope);
572 return methodChunk(element,macroScope,javaCall,dynamicArgs);
573 }
574 if( call instanceof Macro ) {
575 Macro macro = (Macro)call;
576 if( macro.dotParam==null )
577 throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't support multi-method tags");
578 MacroScope newScope = macroScope.newScope(macro);
579 newScope.addArg(macro.dotParam,getMacroMultiDotArg(element,iCmd+1,false));
580 if( !element.name().endsWithDot() && iCmd==1 && element instanceof Container ) {
581 getMacroTagArgs( macro, (Container)element, newScope );
582 }
583 addMeaning(part,macro,macroScope);
584 return getMacroChunk2(newScope);
585 }
586 JavaCall javaCall = (JavaCall)call;
587 stackElem.setMethod(javaCall.getMethod());
588 String dotParam = javaCall.cmdSpec().dotParameter;
589 if( dotParam==null )
590 throw new CompileException(stackTrace,"method '"+methodName+"' in "+javaCall.method().getDeclaringClass()+" doesn't support multi-method tags");
591 int pushed = push(javaCall,dotParam);
592 try {
593 chunk = multiMethodTag(element,iCmd+1,macroScope);
594 chunk = trimChunk(chunk);
595 } finally {
596 pop(pushed);
597 }
598 Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>();
599 if( iCmd==1 && element instanceof Container && !element.name().endsWithDot() ) {
600 getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs );
601 }
602 if( dynamicArgs.put( javaCall.cmdSpec().dotParameter, chunk ) != null )
603 throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument");
604 addMeaning(part,javaCall.javaCommand,macroScope);
605 return methodChunk(element,macroScope,javaCall,dynamicArgs);
606 } finally {
607 stackTrace.pop();
608 }
609 }
610
611 private Map<String,Chunk> attributes(Element element,JavaCall javaCall,MacroScope macroScope) throws CompileException {
612 Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>();
613 for( Attribute attr : element.attributes() ) {
614 String name = attr.name();
615 int pushed = push(javaCall,name);
616 try {
617 dynamicArgs.put( name, consolidate( compile(attr.value(),macroScope) ) );
618 } finally {
619 pop(pushed);
620 }
621 }
622 return dynamicArgs;
623 }
624
625 private Chunk getMacroArgChunk(String cmdName,MacroScope macroScope)
626 throws CompileException
627 {
628 ParamChunk paramChunk = macroScope.getParamChunk(cmdName);
629 if( paramChunk != null )
630 return paramChunk;
631 Naml macroArg = macroScope.getArg(cmdName);
632 if( macroArg == null )
633 return null;
634 return consolidate( compile(macroArg,macroScope.parentScope) );
635 }
636
637 private Chunk getMacroChunk(Macro macro,Element element,MacroScope macroScope,boolean isMulti)
638 throws CompileException
639 {
640 MacroScope newScope = macroScope.newScope(macro);
641 for( Attribute attr : element.attributes() ) {
642 String name = attr.name();
643 if( !macro.parameters.contains(name) )
644 throw new CompileException(stackTrace,"parameter '"+name+"' not allowed, only use "+macro.parameters);
645 newScope.addArg(name,attr.value());
646 }
647 if( element instanceof Container ) {
648 Container container = (Container)element;
649 ElementName name = container.name();
650 if( name.endsWithDot() ) {
651 if( macro.dotParam==null )
652 throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't have a dot_parameter");
653 Naml naml = container.contents();
654 naml = Macro.trim(naml);
655 newScope.addArg( macro.dotParam, naml );
656 } else if(!isMulti) {
657 getMacroTagArgs( macro, container, newScope );
658 }
659 }
660 return getMacroChunk2(newScope);
661 }
662
663 private void getMacroTagArgs(Macro macro,Container container,MacroScope newScope)
664 throws CompileException
665 {
666 for( Object obj : container.contents() ) {
667 if( obj instanceof Element ) {
668 Element element = (Element)obj;
669 ElementName name = element.name();
670 boolean isSingle = name.parts().size() == 1;
671 String argName = name.parts().get(0).text();
672 if( !macro.parameters.contains(argName) )
673 throw new CompileException(stackTrace,"tag '"+argName+"' is not an allowed here, only these are allowed: "+macro.parameters);
674 if( isSingle ) {
675 if( !(element instanceof Container) )
676 throw new CompileException(stackTrace,"empty tag '"+argName+"' not allowed here, argument expected");
677 if( !element.attributes().isEmpty() )
678 throw new CompileException(stackTrace,"tag '"+argName+"' may not have arguments");
679 Container argContainer = (Container)element;
680 Naml naml = argContainer.contents();
681 naml = Macro.trim(naml);
682 newScope.addArg(argName,naml);
683 } else {
684 newScope.addArg(argName,getMacroMultiDotArg(element,1,true));
685 }
686 } else if( !(obj instanceof String) ) {
687 throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here");
688 }
689 }
690 }
691
692 private static Naml getMacroMultiDotArg(Element element,int i,boolean allowArgContents) {
693 ElementName name = element.name();
694 List<ElementName.Part> parts = new ArrayList<ElementName.Part>();
695 parts.add(nPart);
696 parts.addAll( name.parts().subList(i,name.parts().size()) );
697 ElementName newName = new ElementName(name.endsWithDot(),parts);
698 Element newElement;
699 if( name.endsWithDot() || allowArgContents && element instanceof Container ) {
700 Container argContainer = (Container)element;
701 newElement = new Container(newName,element.attributes(),"",element.lineNumber(),argContainer.contents(),"");
702 } else {
703 newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber());
704 }
705 Naml naml = new Naml();
706 naml.add(newElement);
707 return naml;
708 }
709
710 private static final Naml nullNaml;
711 static {
712 try {
713 nullNaml = Naml.parser().parse("<n.null/>");
714 } catch(ParseException e) {
715 logger.error("",e);
716 System.exit(-1);
717 throw new RuntimeException(e);
718 }
719 }
720
721 private Chunk getMacroChunk2(MacroScope newScope)
722 throws CompileException
723 {
724 Set<String> parameters = new HashSet<String>(newScope.macro.parameters);
725 parameters.removeAll(newScope.getArgNames());
726 for( String param : parameters ) {
727 newScope.addArg( param, nullNaml );
728 }
729 return compileMacro(newScope);
730 }
731
732 private void getDotArg(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
733 throws CompileException
734 {
735 String dotParam = javaCall.cmdSpec().dotParameter;
736 if( dotParam==null )
737 throw new CompileException(stackTrace,"command '"+javaCall.name()+"' doesn't support multi-method tags");
738 int pushed = push(javaCall,dotParam);
739 try {
740 Chunk block = consolidate( compile( container.contents(), macroScope ) );
741 block = trimChunk(block);
742 if( dynamicArgs.put( dotParam, block ) != null )
743 throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument");
744 } finally {
745 pop(pushed);
746 }
747 }
748
749 private void getTagArgs(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
750 throws CompileException
751 {
752 for( Object obj : container.contents() ) {
753 if( obj instanceof Element ) {
754 Element element = (Element)obj;
755 ElementName name = element.name();
756 boolean isSingle = name.parts().size() == 1;
757 if( isSingle && !(element instanceof Container) )
758 throw new CompileException(stackTrace,"empty " + notAllowed(macroScope,element,javaCall) );
759 String argName = name.parts().get(0).text();
760 if( !javaCall.cmdSpec().parameters.contains(argName) )
761 throw new CompileException(stackTrace,notAllowed(macroScope,element,javaCall));
762 if( isSingle && !element.attributes().isEmpty() )
763 throw new CompileException(stackTrace,"tag "+argName+" may not have arguments");
764 int pushed = push(javaCall,argName);
765 try {
766 Chunk block;
767 if( isSingle ) {
768 Container argContainer = (Container)element;
769 block = consolidate( compile( argContainer.contents(), macroScope ) );
770 } else {
771 block = compileElement( element, macroScope );
772 }
773 block = trimChunk(block);
774 if( dynamicArgs.put( argName, block ) != null )
775 throw new CompileException(stackTrace,"duplicate argument: "+argName);
776 } catch(TemplateRuntimeException e) {
777 throw e;
778 } catch(RuntimeException e) {
779 throw new TemplateRuntimeException("in "+name,e);
780 } finally {
781 pop(pushed);
782 }
783 } else if( !(obj instanceof String) ) {
784 throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here");
785 }
786 }
787 }
788
789 private static String notAllowed(MacroScope macroScope,Element element,JavaCall javaCall) {
790 Set<String> parameters = javaCall.cmdSpec().parameters;
791 StringBuilder buf = new StringBuilder();
792 buf
793 .append( "tag " )
794 .append( new StackTraceElement(macroScope.source(),element) )
795 .append( " is not allowed here, " )
796 ;
797 if( parameters.isEmpty() ) {
798 buf.append( "no tags allowed" );
799 } else {
800 buf.append("only tags ");
801 Iterator<String> iter = parameters.iterator();
802 buf
803 .append( "<" )
804 .append( iter.next() )
805 .append( ">, " )
806 ;
807 while( iter.hasNext() ) {
808 buf
809 .append( ", <" )
810 .append( iter.next() )
811 .append( ">" )
812 ;
813 }
814 buf.append( " are allowed here" );
815 }
816 if( javaCall.cmdSpec().dotParameter != null )
817 buf.append( ", or you may have forgotten the dot at the end of the tag name" );
818 buf
819 .append( " in tag element '" )
820 .append( javaCall.name() )
821 .append( "'" )
822 ;
823 return buf.toString();
824 }
825
826 private Chunk methodChunk(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
827 throws CompileException
828 {
829 Chunk methodChunk = methodChunk2(element,macroScope,javaCall,dynamicArgs);
830 if( methodChunk instanceof Block ) {
831 Block block = (Block)methodChunk;
832 Chunk chunk = block.prerun(new CompileTimeRunState());
833 if( chunk != null ) {
834 return chunk;
835 }
836 }
837 for( Chunk chunk : dynamicArgs.values() ) {
838 if( chunk instanceof ErrorChunk )
839 return chunk;
840 }
841 return methodChunk;
842 }
843
844 private Chunk methodChunk2(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
845 throws CompileException
846 {
847 Map<String,String> staticArgs = new HashMap<String,String>();
848 for( Iterator<Map.Entry<String,Chunk>> iter = dynamicArgs.entrySet().iterator(); iter.hasNext(); ) {
849 Map.Entry<String,Chunk> entry = iter.next();
850 Chunk chunk = entry.getValue();
851 if( chunk == emptyChunk ) {
852 staticArgs.put( entry.getKey(), "" );
853 iter.remove();
854 } else if( chunk instanceof StringChunk ) {
855 StringChunk sc = (StringChunk)entry.getValue();
856 staticArgs.put( entry.getKey(), sc.s );
857 iter.remove();
858 }
859 }
860 if( javaCall.cmdSpec().removeNulls )
861 removeNulls(dynamicArgs);
862 javaCall.cmdSpec().check(staticArgs,dynamicArgs,stackTrace);
863 if( javaCall.name() == "var" ) {
864 return new GetVar(macroScope,staticArgs,dynamicArgs);
865 }
866 if( javaCall.name() == "set_var" ) {
867 return new SetVar(macroScope,staticArgs,dynamicArgs);
868 }
869 if( javaCall.name() == "uplevel_var" ) {
870 return new GetVar(macroScope.parentScope,staticArgs,dynamicArgs);
871 }
872 if( javaCall.name() == "uplevel_set_var" ) {
873 return new SetVar(macroScope.parentScope,staticArgs,dynamicArgs);
874 }
875 Map<Class,Integer> inStack = new HashMap<Class,Integer>();
876 for( Class cls : javaCall.cmdSpec().requiredInStack ) {
877 inStack.put( cls, iStack(whereInStack(cls)) );
878 }
879 if( javaCall.isScoped() ) {
880 if( javaCall.cmdSpec().scopedParameters == null )
881 throw new CompileException(stackTrace,"a scoped method must specify scoped arguments");
882 Map<String,Chunk> dynamicArgs2 = new HashMap<String,Chunk>(dynamicArgs);
883 Map<String,Chunk> scopedArgs = new HashMap<String,Chunk>();
884 for( String scopedParam : javaCall.cmdSpec().scopedParameters ) {
885 Chunk chunk = dynamicArgs2.remove(scopedParam);
886 if( chunk != null )
887 scopedArgs.put(scopedParam,chunk);
888 }
889 return new Scoped(this,javaCall,inStack,staticArgs,dynamicArgs2,scopedArgs);
890 }
891 return new Block(this,javaCall,inStack,staticArgs,dynamicArgs);
892 }
893
894 private static void removeNulls(Map<String,Chunk> dynamicArgs) {
895 for( Iterator<Chunk> iter = dynamicArgs.values().iterator(); iter.hasNext(); ) {
896 if( iter.next() == Chunk.NULL )
897 iter.remove();
898 }
899 }
900
901 private int iStack(int i) {
902 if( !(stack.get(i) instanceof JavaNamespace) )
903 throw new RuntimeException();
904 int n = 0;
905 for( int j=i; j>=0; j-- ) {
906 if( !stack.get(j).isGlobal() ) {
907 final int size = stack.size();
908 for( int k=i; k<size; k++ ) {
909 if( stack.get(k) instanceof JavaNamespace )
910 n++;
911 }
912 return -n;
913 }
914 }
915 for( int k=0; k<i; k++ ) {
916 if( stack.get(k) instanceof JavaNamespace )
917 n++;
918 }
919 return n;
920 }
921
922 private Call getCommandCall(String cmdName,MacroScope macroScope)
923 throws CompileException
924 {
925 Call call = null;
926 boolean isTop = true;
927 Set<Class> nsSet = new HashSet<Class>();
928 Set<String> namespacesOnStack = new HashSet<String>();
929 for( int i = stack.size() - 1; i>=0; i-- ) {
930 GenericNamespace ns = stack.get(i);
931 if( !isTop && !ns.isGlobal() )
932 continue;
933 namespacesOnStack.addAll( ns.names() );
934 if( ns instanceof JavaNamespace ) {
935 JavaNamespace jns = (JavaNamespace)ns;
936 Class cls = jns.cls;
937 JavaCommand javaCmd = JavaCommand.getJavaCommand(cls,cmdName);
938 if( javaCmd != null ) {
939 if( !(call instanceof JavaCall && ((JavaCall)call).javaCommand == javaCmd) ) {
940 i = iStack(i);
941 JavaCall javaCall = new JavaCall(javaCmd,i);
942 if( call != null )
943 throw conflict(javaCall,call);
944 call = javaCall;
945 program.addMeaning(javaCmd);
946 }
947 }
948 }
949 if( !ns.isTransparent() && isTop )
950 isTop = false;
951 }
952 List<Macro> candidates = macros.get(cmdName);
953 if( candidates != null ) {
954 for( Macro candidate : candidates ) {
955 if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) {
956 if( call == null ) {
957 call = candidate;
958 } else {
959 if( call.getRequiredNamespaces().containsAll(candidate.getRequiredNamespaces()) ) {
960 if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) )
961 throw conflict(candidate,call);
962 // keep call
963 } else if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) ) {
964 call = candidate;
965 } else {
966 throw conflict(candidate,call);
967 }
968 }
969 }
970 }
971 }
972 if( cmdName.equals("overridden") ) {
973 if( call != null )
974 throw new CompileException(stackTrace,"conflict between ("+call+") and 'overridden' command");
975 call = overrides.get(macroScope.macro);
976 }
977 if( call == null )
978 throw new CompileMethodException(stackTrace,"macro or method for '"+cmdName+"' not found in "+accessibleStack()+" stack = "+stack);
979 return call;
980 }
981
982 private CompileException conflict(Call call1,Call call2) {
983 return new CompileException(stackTrace,"conflict between ("+call1+") and ("+call2+")");
984 }
985
986 private static Chunk consolidate(List<Chunk> chunks) {
987 List<Chunk> flat = new ArrayList<Chunk>();
988 for( Chunk chunk : chunks ) {
989 if( chunk instanceof Chunks ) {
990 flat.addAll( Arrays.asList(((Chunks)chunk).chunks) );
991 } else {
992 flat.add(chunk);
993 }
994 }
995 if( flat.size() == 1 )
996 return flat.get(0);
997 List<Chunk> merged = new ArrayList<Chunk>();
998 StringChunk stringChunk = null;
999 StringBuilder buf = new StringBuilder();
1000 for( Chunk chunk : flat ) {
1001 if( chunk == emptyChunk )
1002 continue;
1003 if( chunk instanceof ErrorChunk )
1004 return chunk;
1005 if( chunk instanceof StringChunk ) {
1006 StringChunk sc = (StringChunk)chunk;
1007 if( stringChunk==null ) {
1008 stringChunk = sc;
1009 } else {
1010 if( buf.length()==0 )
1011 buf.append( stringChunk.s );
1012 buf.append( sc.s );
1013 }
1014 } else {
1015 if( buf.length() > 0 ) {
1016 merged.add( new StringChunk(buf.toString()) );
1017 buf.setLength(0);
1018 stringChunk = null;
1019 } else if( stringChunk != null ) {
1020 merged.add( stringChunk );
1021 stringChunk = null;
1022 }
1023 merged.add(chunk);
1024 }
1025 }
1026 if( buf.length() > 0 ) {
1027 merged.add( new StringChunk(buf.toString()) );
1028 } else if( stringChunk != null ) {
1029 merged.add( stringChunk );
1030 }
1031 return newChunks(merged);
1032 }
1033
1034 private static Chunk trimChunk(Chunk chunk) {
1035 if( chunk instanceof StringChunk ) {
1036 StringChunk sc = (StringChunk)chunk;
1037 String s = sc.s.trim();
1038 if( s.length() == 0 )
1039 return emptyChunk;
1040 if( s.length() == sc.s.length() )
1041 return sc;
1042 return new StringChunk(s);
1043 }
1044 if( chunk instanceof Chunks ) {
1045 boolean changed = false;
1046 List<Chunk> chunks = new ArrayList<Chunk>(Arrays.asList(((Chunks)chunk).chunks));
1047 if( chunks.size() >= 2 ) {
1048 for( int i=0; i<chunks.size(); ) {
1049 Chunk firstChunk = chunks.get(i);
1050 if( firstChunk instanceof StringChunk ) {
1051 StringChunk sc = (StringChunk)firstChunk;
1052 String s = sc.s.replaceAll("^\\s+","");
1053 if( s.length() < sc.s.length() ) {
1054 changed = true;
1055 if( s.length() == 0 ) {
1056 chunks.remove(i);
1057 continue;
1058 } else {
1059 chunks.set(i,new StringChunk(s));
1060 }
1061 }
1062 } else if( !firstChunk.hasOutput() ) {
1063 i++;
1064 continue;
1065 }
1066 break;
1067 }
1068 for( int i = chunks.size() - 1; i>=0; i-- ) {
1069 Chunk lastChunk = chunks.get(i);
1070 if( lastChunk instanceof StringChunk ) {
1071 StringChunk sc = (StringChunk)lastChunk;
1072 String s = sc.s.replaceAll("\\s+$","");
1073 if( s.length() < sc.s.length() ) {
1074 changed = true;
1075 if( s.length() == 0 ) {
1076 chunks.remove(i);
1077 continue;
1078 } else {
1079 chunks.set(i,new StringChunk(s));
1080 }
1081 }
1082 } else if( !lastChunk.hasOutput() ) {
1083 continue;
1084 }
1085 break;
1086 }
1087 if( changed )
1088 return newChunks(chunks);
1089 }
1090 }
1091 return chunk;
1092 }
1093
1094 private static final Chunk emptyChunk = new Chunk() {
1095
1096 public void run(IPrintWriter out,RunState runState) {}
1097
1098 public boolean hasOutput() {
1099 return false;
1100 }
1101
1102 public String toString() {
1103 return "emptyChunk";
1104 }
1105
1106 @Override public Chunk intern() {
1107 return this;
1108 }
1109 };
1110
1111 private static final class Chunks implements Chunk {
1112 private Chunk[] chunks;
1113 private int hash = 0;
1114
1115 Chunks(List<Chunk> chunks) {
1116 this.chunks = chunks.toArray(new Chunk[chunks.size()]);
1117 }
1118
1119 public void run(IPrintWriter out,RunState runState) {
1120 for( Chunk chunk : chunks ) {
1121 chunk.run(out,runState);
1122 }
1123 }
1124
1125 public boolean hasOutput() {
1126 for( Chunk chunk : chunks ) {
1127 if( chunk.hasOutput() )
1128 return true;
1129 }
1130 return false;
1131 }
1132
1133 public String toString() {
1134 return "{Chunks: "+chunks+"}";
1135 }
1136
1137 @Override public boolean equals(Object obj) {
1138 if( this==obj )
1139 return true;
1140 if( !(obj instanceof Chunks) )
1141 return false;
1142 Chunks c = (Chunks)obj;
1143 final int n = chunks.length;
1144 if( n != c.chunks.length )
1145 return false;
1146 for( int i=0; i<n; i++ ) {
1147 if( chunks[i] != c.chunks[i] )
1148 return false;
1149 }
1150 return true;
1151 }
1152
1153 @Override public int hashCode() {
1154 if( hash == 0 )
1155 hash = Arrays.hashCode(chunks);
1156 return hash;
1157 }
1158
1159 @Override public Chunk intern() {
1160 Chunk[] a = new Chunk[chunks.length];
1161 for( int i=0; i<a.length; i++ ) {
1162 a[i] = chunks[i].intern();
1163 }
1164 chunks = a;
1165 return interner.intern(this);
1166 }
1167 }
1168
1169 private static Chunk newChunks(List<Chunk> chunks) {
1170 switch( chunks.size() ) {
1171 case 0:
1172 return emptyChunk;
1173 case 1:
1174 return chunks.get(0);
1175 default:
1176 return new Chunks(chunks);
1177 }
1178 }
1179
1180 private static class ErrorChunk implements Chunk {
1181 final CompileException e;
1182
1183 ErrorChunk(CompileException e) {
1184 this.e = e;
1185 }
1186
1187 public void run(IPrintWriter out,RunState runState) {
1188 throw new RuntimeException(e);
1189 }
1190
1191 public boolean hasOutput() {
1192 return false;
1193 }
1194 /*
1195 @Override public boolean equals(Object obj) {
1196 throw new RuntimeException("never");
1197 }
1198
1199 @Override public int hashCode() {
1200 throw new RuntimeException("never");
1201 }
1202 */
1203 @Override public Chunk intern() {
1204 throw new RuntimeException("never");
1205 }
1206 }
1207
1208 private static class DynamicAttr implements Chunk {
1209 private final Attribute attr;
1210 private Chunk chunk;
1211
1212 DynamicAttr(Attribute attr,Chunk chunk) {
1213 this.attr = attr;
1214 this.chunk = chunk;
1215 }
1216
1217 public void run(IPrintWriter out,RunState runState) {
1218 String attrValue = new BlockWrapper(chunk,runState).toString();
1219 if( attrValue != null )
1220 out.print( attr.toString(attrValue) );
1221 }
1222
1223 public boolean hasOutput() {
1224 return true;
1225 }
1226
1227 @Override public boolean equals(Object obj) {
1228 if( this==obj )
1229 return true;
1230 if( !(obj instanceof DynamicAttr) )
1231 return false;
1232 DynamicAttr da = (DynamicAttr)obj;
1233 return da.attr == attr && da.chunk.equals(chunk);
1234 }
1235
1236 @Override public int hashCode() {
1237 return attr.hashCode() * 31 + chunk.hashCode();
1238 }
1239
1240 @Override public Chunk intern() {
1241 chunk = chunk.intern();
1242 return interner.intern(this);
1243 }
1244 }
1245
1246
1247 private static class VarScope implements Chunk {
1248 private final Macro macro;
1249 private Chunk chunk;
1250
1251 VarScope(Chunk chunk,MacroScope macroScope) {
1252 this.macro = macroScope.macro;
1253 this.chunk = chunk;
1254 }
1255
1256 public void run(IPrintWriter out,RunState runState) {
1257 runState.pushVars(macro);
1258 try {
1259 chunk.run(out,runState);
1260 } finally {
1261 runState.popVars(macro);
1262 }
1263 }
1264
1265 public boolean hasOutput() {
1266 return chunk.hasOutput();
1267 }
1268
1269 public String toString() {
1270 return "{VarScope}";
1271 }
1272
1273 @Override public boolean equals(Object obj) {
1274 if( this==obj )
1275 return true;
1276 if( !(obj instanceof VarScope) )
1277 return false;
1278 VarScope v = (VarScope)obj;
1279 return macro == v.macro && chunk.equals(v.chunk);
1280 }
1281
1282 @Override public int hashCode() {
1283 return chunk.hashCode()*31 + macro.hashCode();
1284 }
1285
1286 @Override public Chunk intern() {
1287 chunk = chunk.intern();
1288 return interner.intern(this);
1289 }
1290 }
1291
1292 private interface Stringable {
1293 public String toString(RunState runState);
1294 public void internChunk();
1295 }
1296
1297 private final static class StringableString implements Stringable {
1298 private final String s;
1299
1300 StringableString(String s) {
1301 this.s = s;
1302 }
1303
1304 public String toString(RunState runState) {
1305 return s;
1306 }
1307
1308 @Override public boolean equals(Object obj) {
1309 if( !(obj instanceof StringableString) )
1310 return false;
1311 StringableString ss = (StringableString)obj;
1312 return ObjectUtils.equals(ss.s,s);
1313 }
1314
1315 @Override public int hashCode() {
1316 int h = getClass().hashCode();
1317 if( s != null )
1318 h += s.hashCode();
1319 return h;
1320 }
1321
1322 public void internChunk() {}
1323 }
1324
1325 private final static class StringableChunk implements Stringable {
1326 private Chunk chunk;
1327
1328 StringableChunk(Chunk chunk) {
1329 this.chunk = chunk;
1330 }
1331
1332 public String toString(RunState runState) {
1333 return new BlockWrapper(chunk,runState).toString();
1334 }
1335
1336 @Override public boolean equals(Object obj) {
1337 if( !(obj instanceof StringableChunk) )
1338 return false;
1339 StringableChunk sc = (StringableChunk)obj;
1340 return sc.chunk == chunk;
1341 }
1342
1343 @Override public int hashCode() {
1344 return chunk.hashCode() + getClass().hashCode();
1345 }
1346
1347 public void internChunk() {
1348 chunk = chunk.intern();
1349 }
1350 }
1351
1352 private static Stringable getStringable(String name,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
1353 Chunk chunk = dynamicArgs.get(name);
1354 if( chunk != null )
1355 return new StringableChunk(chunk);
1356 String s = staticArgs.get(name);
1357 return new StringableString(s);
1358 }
1359
1360 private static final class GetVar implements Chunk {
1361 private final Macro macro;
1362 private final Stringable name;
1363
1364 GetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
1365 macroScope.hasVars = true;
1366 this.macro = macroScope.macro;
1367 this.name = getStringable("name",staticArgs,dynamicArgs);
1368 }
1369
1370 public void run(IPrintWriter out,RunState runState) {
1371 String nameS = name.toString(runState);
1372 String valueS = runState.getVars(macro).get(nameS);
1373 out.print(valueS);
1374 }
1375
1376 public boolean hasOutput() {
1377 return true;
1378 }
1379
1380 public String toString() {
1381 return "{GetVar}";
1382 }
1383
1384 @Override public boolean equals(Object obj) {
1385 if( !(obj instanceof GetVar) )
1386 return false;
1387 GetVar v = (GetVar)obj;
1388 return v.macro == macro && v.name.equals(name);
1389 }
1390
1391 @Override public int hashCode() {
1392 int h = macro.hashCode();
1393 h = h * 31 + name.hashCode();
1394 return h;
1395 }
1396
1397 @Override public Chunk intern() {
1398 name.internChunk();
1399 return interner.intern(this);
1400 }
1401 }
1402
1403 private static final class SetVar implements Chunk {
1404 private final Macro macro;
1405 private final Stringable name;
1406 private final Stringable value;
1407
1408 SetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
1409 macroScope.hasVars = true;
1410 this.macro = macroScope.macro;
1411 this.name = getStringable("name",staticArgs,dynamicArgs);
1412 this.value = getStringable("value",staticArgs,dynamicArgs);
1413 }
1414
1415 public void run(IPrintWriter out,RunState runState) {
1416 String nameS = name.toString(runState);
1417 String valueS = value.toString(runState);
1418 runState.getVars(macro).put(nameS,valueS);
1419 }
1420
1421 public boolean hasOutput() {
1422 return false;
1423 }
1424
1425 public String toString() {
1426 return "{SetVar}";
1427 }
1428
1429 @Override public boolean equals(Object obj) {
1430 if( !(obj instanceof SetVar) )
1431 return false;
1432 SetVar v = (SetVar)obj;
1433 return v.macro == macro && v.name.equals(name) && v.value.equals(value);
1434 }
1435
1436 @Override public int hashCode() {
1437 int h = macro.hashCode();
1438 h = h * 31 + name.hashCode();
1439 h = h * 31 + value.hashCode();
1440 return h;
1441 }
1442
1443 @Override public Chunk intern() {
1444 name.internChunk();
1445 value.internChunk();
1446 return interner.intern(this);
1447 }
1448 }
1449
1450
1451 private static final Set<JavaCommand> badMethods = new CopyOnWriteArraySet<JavaCommand>();
1452 private static final Map<JavaCommand,Set<String>> badParams = new ConcurrentHashMap<JavaCommand,Set<String>>();
1453
1454 private static class CompileTimeIntepreterException extends RuntimeException {}
1455
1456 private static class CompileTimeArgException extends RuntimeException {
1457 final String argName;
1458
1459 CompileTimeArgException(String argName) {
1460 this.argName = argName;
1461 }
1462 }
1463
1464 private static class CompileTimeScopedException extends RuntimeException {
1465
1466 CompileTimeScopedException(CompileTimeIntepreterException e) {
1467 super(e);
1468 }
1469 }
1470
1471 private static class ChunkWrapper {
1472 final String argName;
1473 final Chunk chunk;
1474
1475 ChunkWrapper(String argName,Chunk chunk) {
1476 this.argName = argName;
1477 this.chunk = chunk;
1478 }
1479
1480 public String toString() {
1481 throw new CompileTimeArgException(argName);
1482 }
1483 }
1484
1485 static abstract class Traceable {
1486 StackTrace stackTrace = new StackTrace();
1487
1488 Traceable(Compiler compiler) {
1489 compiler.traceable = this;
1490 compiler.traceables.add(this);
1491 }
1492
1493 private void cleanUp() {
1494 Collections.reverse(stackTrace);
1495 stackTrace = stackTrace.intern();
1496 }
1497 }
1498
1499 static boolean nestedEquals(Map<String,Chunk> map1,Map<String,Chunk> map2) {
1500 if( map1.size() != map2.size() )
1501 return false;
1502 for( Map.Entry<String,Chunk> entry : map1.entrySet() ) {
1503 if( entry.getValue() != map2.get(entry.getKey()) )
1504 return false;
1505 }
1506 return true;
1507 }
1508
1509 private static class Block extends Traceable implements Chunk {
1510 final JavaCall method;
1511 final Map<Class,Integer> inStack;
1512 private final Map<String,String> staticArgs;
1513 private Map<String,Chunk> dynamicArgs;
1514 private final boolean hasOutput;
1515 private int hash = 0;
1516
1517 Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
1518 this(compiler,method,inStack,staticArgs,dynamicArgs,Collections.<String>emptySet());
1519 }
1520
1521 Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Set<String> scopedArgs) {
1522 super(compiler);
1523 this.method = method;
1524 this.inStack = CollectionUtils.optimizeMap(inStack);
1525 this.staticArgs = CollectionUtils.optimizeMap(staticArgs);
1526 this.dynamicArgs = CollectionUtils.optimizeMap(dynamicArgs);
1527 Set<String> outputtedParameters = method.cmdSpec().outputtedParameters;
1528 if( outputtedParameters == null ) {
1529 this.hasOutput = true;
1530 } else {
1531 boolean hasOutput = false;
1532 Set<String> staticArgNames = staticArgs.keySet();
1533 for( String s : outputtedParameters ) {
1534 if( staticArgNames.contains(s) ) {
1535 hasOutput = true;
1536 break;
1537 }
1538 Chunk chunk = dynamicArgs.get(s);
1539 if( chunk == null ) {
1540 if( scopedArgs.contains(s) ) {
1541 hasOutput = true;
1542 break;
1543 }
1544 } else if( chunk.hasOutput() ) {
1545 hasOutput = true;
1546 break;
1547 }
1548 }
1549 this.hasOutput = hasOutput;
1550 }
1551 }
1552
1553 private void invoke(RunState runState,IPrintWriter out,InterpreterImpl interp)
1554 throws IllegalAccessException, InvocationTargetException
1555 {
1556 try {
1557 method.invoke(runState,out,interp);
1558 } finally {
1559 interp.close();
1560 }
1561 }
1562
1563 private Chunk prerun(RunState runState)
1564 throws CompileException
1565 {
1566 try {
1567 JavaCommand javaCommand = method.javaCommand;
1568 if( !javaCommand.isStatic || badMethods.contains(javaCommand) )
1569 return null;
1570 {
1571 Set<String> params = badParams.get(javaCommand);
1572 if( params != null ) {
1573 Set<String> chunkSet = dynamicArgs.keySet();
1574 for( String param : params ) {
1575 if( chunkSet.contains(param) )
1576 return null;
1577 }
1578 }
1579 }
1580 CompilerPrintWriter out = new CompilerPrintWriter(new CharArrayWriter());
1581 // RunState runState = new CompileTimeRunState();
1582 try {
1583 Map<String,Object> args = new HashMap<String,Object>(staticArgs);
1584 for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
1585 String argName = entry.getKey();
1586 Chunk argValue = entry.getValue();
1587 args.put( argName, new ChunkWrapper(argName,argValue) );
1588 }
1589 invoke(runState,out,newInterpreter(runState,args));
1590 out.close();
1591 return Compiler.consolidate(out.chunks);
1592 } catch(InvocationTargetException e) {
1593 Throwable cause = e.getCause();
1594 if( cause instanceof CompileTimeIntepreterException ) {
1595 badMethods.add(javaCommand);
1596 logger.debug("bad method: "+javaCommand.method,cause);
1597 } else if( cause instanceof CompileTimeArgException ) {
1598 CompileTimeArgException ex = (CompileTimeArgException)cause;
1599 Set<String> params = badParams.get(javaCommand);
1600 if( params == null ) {
1601 params = new CopyOnWriteArraySet<String>();
1602 badParams.put(javaCommand,params);
1603 }
1604 params.add(ex.argName);
1605 // logger.debug("bad argument '"+ex.argName+"' in "+javaCommand.method);
1606 } else if( cause instanceof CompileTimeScopedException ) {
1607 logger.debug("ignoring: "+javaCommand.method,cause);
1608 } else {
1609 throw e;
1610 }
1611 }
1612 return null;
1613 } catch(InvocationTargetException e) {
1614 throw compileFix(e);
1615 } catch(IllegalAccessException e) {
1616 throw new TemplateRuntimeException(e);
1617 }
1618 }
1619
1620 public void run(IPrintWriter out,RunState runState) {
1621 Stack<StackTrace> stack = StackTrace.stack();
1622 stack.push(stackTrace);
1623 try {
1624 if( dynamicArgs.isEmpty() ) {
1625 invoke(runState,out,newInterpreter(runState,staticArgs));
1626 } else {
1627 Map<String,Object> args = new HashMap<String,Object>(staticArgs);
1628 for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
1629 args.put( entry.getKey(), new BlockWrapper(entry.getValue(),runState) );
1630 }
1631 invoke(runState,out,newInterpreter(runState,args));
1632 }
1633 } catch(IllegalAccessException e) {
1634 throw new TemplateRuntimeException(e);
1635 } catch(InvocationTargetException e) {
1636 throw interpFix(e);
1637 } finally {
1638 stack.pop();
1639 }
1640 }
1641
1642 public boolean hasOutput() {
1643 return hasOutput;
1644 }
1645
1646 InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) {
1647 return new InterpreterImpl(method.cmdSpec(),runState,inStack,args);
1648 }
1649
1650 public String toString() {
1651 return "{Block: "+stackTrace.peek()+"}";
1652 }
1653
1654 @Override public boolean equals(Object obj) {
1655 if( this==obj )
1656 return true;
1657 if( !(obj instanceof Block) )
1658 return false;
1659 return equals((Block)obj);
1660 }
1661
1662 boolean equals(Block block) {
1663 return block.method.equals(method)
1664 && block.inStack.equals(inStack)
1665 && block.staticArgs.equals(staticArgs)
1666 && nestedEquals(block.dynamicArgs,dynamicArgs)
1667 && block.getClass().equals(getClass())
1668 ;
1669 }
1670
1671 @Override public int hashCode() {
1672 if( hash == 0 )
1673 hash = calcHash();
1674 return hash;
1675 }
1676
1677 int calcHash() {
1678 int h = method.hashCode();
1679 h = h * 31 + inStack.hashCode();
1680 h = h * 31 + staticArgs.hashCode();
1681 h = h * 31 + dynamicArgs.hashCode();
1682 return h;
1683 }
1684
1685 void internMembers() {
1686 Map<String,Chunk> m = new HashMap<String,Chunk>();
1687 for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
1688 m.put(entry.getKey(),entry.getValue().intern());
1689 }
1690 dynamicArgs = CollectionUtils.optimizeMap(m);
1691 }
1692
1693 @Override public final Chunk intern() {
1694 internMembers();
1695 return interner.intern(this);
1696 }
1697 }
1698
1699 private static final class Scoped extends Block {
1700 private Map<String,Chunk> scopedArgs;
1701
1702 Scoped(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Map<String,Chunk> scopedArgs) {
1703 super(compiler,method,inStack,staticArgs,dynamicArgs,scopedArgs.keySet());
1704 this.scopedArgs = CollectionUtils.optimizeMap(scopedArgs);
1705 }
1706
1707 InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) {
1708 return new ScopedInterpreterImpl<Object>(method.cmdSpec(),runState,inStack,args,scopedArgs);
1709 }
1710
1711 boolean equals(Block block) {
1712 if( !super.equals(block) )
1713 return false;
1714 Scoped scoped = (Scoped)block;
1715 return nestedEquals(scoped.scopedArgs,scopedArgs);
1716 }
1717
1718 int calcHash() {
1719 return super.calcHash() * 31 + scopedArgs.hashCode();
1720 }
1721
1722 void internMembers() {
1723 super.internMembers();
1724 Map<String,Chunk> m = new HashMap<String,Chunk>();
1725 for( Map.Entry<String,Chunk> entry : scopedArgs.entrySet() ) {
1726 m.put(entry.getKey(),entry.getValue().intern());
1727 }
1728 scopedArgs = CollectionUtils.optimizeMap(m);
1729 }
1730 }
1731
1732 private static final class Subroutine extends Traceable implements Chunk {
1733 private final Map<String,Stringable> argMap;
1734 private final int[] aiStack;
1735 private final GenericNamespace[] base;
1736 private final String commandName;
1737 private Template template = null;
1738 private int hash = 0;
1739
1740 Subroutine(Compiler compiler,Map<String,Stringable> argMap,int[] aiStack,GenericNamespace[] base,String commandName) {
1741 super(compiler);
1742 this.argMap = argMap;
1743 this.aiStack = aiStack;
1744 this.base = base;
1745 this.commandName = commandName;
1746 }
1747
1748 public void run(IPrintWriter out,RunState runState) {
1749 Stack<StackTrace> stackTraces = StackTrace.stack();
1750 stackTraces.push(stackTrace);
1751 try {
1752 synchronized(this) {
1753 if( template == null ) {
1754 try {
1755 template = runState.template().program().getTemplate(commandName,base);
1756 } catch(CompileException e) {
1757 throw new TemplateRuntimeException(e);
1758 }
1759 if( template == null )
1760 throw new TemplateRuntimeException("couldn't find: "+commandName);
1761 }
1762 }
1763 Map<String,Object> args = new HashMap<String,Object>();
1764 for( Map.Entry<String,Stringable> entry : argMap.entrySet() ) {
1765 String value = entry.getValue().toString(runState);
1766 if( value != null )
1767 args.put( entry.getKey(), value );
1768 }
1769 Object[] stack = new Object[aiStack.length];
1770 for( int i=0; i<aiStack.length; i++ ) {
1771 stack[i] = runState.getFromStack(aiStack[i]);
1772 }
1773 template.run( runState.callDepth()+1, runState.getEncoder(), out, args, stack );
1774 } finally {
1775 stackTraces.pop();
1776 }
1777 }
1778
1779 public boolean hasOutput() {
1780 return true;
1781 }
1782
1783 public String toString() {
1784 return "{Subroutine: "+commandName+"}";
1785 }
1786 /*
1787 @Override public boolean equals(Object obj) {
1788 if( this==obj )
1789 return true;
1790 if( !(obj instanceof Subroutine) )
1791 return false;
1792 Subroutine sub = (Subroutine)obj;
1793 return sub.commandName.equals(commandName)
1794 && sub.argMap.equals(argMap)
1795 && Arrays.equals(sub.aiStack,aiStack)
1796 && Arrays.equals(sub.base,base)
1797 ;
1798 }
1799
1800 @Override public int hashCode() {
1801 if( hash == 0 ) {
1802 int h = commandName.hashCode();
1803 h = h * 31 + argMap.hashCode();
1804 h = h * 31 + Arrays.hashCode(aiStack);
1805 h = h * 31 + Arrays.hashCode(base);
1806 hash = h;
1807 }
1808 return hash;
1809 }
1810 */
1811 @Override public final Chunk intern() {
1812 for( Stringable s : argMap.values() ) {
1813 s.internChunk();
1814 }
1815 return interner.intern(this);
1816 }
1817 }
1818
1819 private static CompileException compileFix(InvocationTargetException e) {
1820 Throwable t = e.getCause();
1821 if( t instanceof Error )
1822 throw (Error)t;
1823 if( t instanceof TemplateRuntimeException )
1824 throw (TemplateRuntimeException)t;
1825 if( t instanceof CompileException )
1826 return (CompileException)t;
1827 throw new TemplateRuntimeException((Exception)t);
1828 }
1829
1830
1831 // from old CompilerDataImpl
1832 /*
1833 private Chunk runHandler(Block block)
1834 throws CompileException
1835 {
1836 JavaCommand javaCommand = block.method.javaCommand;
1837 if( !javaCommand.isStatic || badMethods.contains(javaCommand) )
1838 return null;
1839 CompilerPrintWriter writer = new CompilerPrintWriter(new CharArrayWriter());
1840 RunState runState = new CompileTimeRunState();
1841 try {
1842 block.run(writer,runState);
1843 writer.close();
1844 return Compiler.consolidate(writer.chunks);
1845 } catch(CompileTimeIntepreterException e) {
1846 badMethods.add(javaCommand);
1847 logger.info("bad method: "+javaCommand.method);
1848 } catch(CompileTimeArgException e) {
1849 //e.printStackTrace();
1850 // nothing
1851 }
1852 return null;
1853 }
1854 */
1855 private static class CompilerPrintWriter extends TemplatePrintWriter {
1856 private final CharArrayWriter sw;
1857 private boolean isNull = false;
1858 final List<Chunk> chunks = new ArrayList<Chunk>();
1859
1860 CompilerPrintWriter(CharArrayWriter sw) {
1861 super(sw);
1862 this.sw = sw;
1863 }
1864
1865 public void print(Object obj) {
1866 if( obj == null )
1867 // return;
1868 throw new RuntimeException("why");
1869 if( obj instanceof ChunkWrapper ) {
1870 addStringChunk();
1871 chunks.add( ((ChunkWrapper)obj).chunk );
1872 } else if( obj instanceof BlockWrapper ) { // should only happen for scoped args
1873 try {
1874 super.print(obj);
1875 } catch(CompileTimeIntepreterException e) {
1876 throw new CompileTimeScopedException(e);
1877 }
1878 } else {
1879 super.print(obj);
1880 }
1881 }
1882
1883 public void print(String s) {
1884 if( s == null && sw.size() == 0 && chunks.isEmpty() && !isNull ) {
1885 isNull = true;
1886 return;
1887 }
1888 super.print(s);
1889 }
1890
1891 public void close() {
1892 super.close();
1893 addStringChunk();
1894 if( isNull ) {
1895 if( !chunks.isEmpty() )
1896 throw new NullPointerException("null written to stream");
1897 chunks.add(Chunk.NULL);
1898 }
1899 }
1900
1901 private void addStringChunk() {
1902 if( sw.size() > 0 ) {
1903 chunks.add( new StringChunk(sw.toString()) );
1904 sw.reset();
1905 }
1906 }
1907 }
1908
1909 private final class CompileTimeRunState implements RunState {
1910 private final Stack<Object> stack = new ArrayStack<Object>();
1911
1912 CompileTimeRunState() {
1913 int n = Compiler.this.stack.size();
1914 while( n-- > 0 ) {
1915 stack.push(null);
1916 }
1917 }
1918
1919 public Template template() {
1920 throw new CompileTimeIntepreterException();
1921 }
1922
1923 public Program program() {
1924 return program;
1925 }
1926
1927 public int callDepth() {
1928 throw new CompileTimeIntepreterException();
1929 }
1930
1931 public void putArg(String name,String value) {
1932 throw new CompileTimeIntepreterException();
1933 }
1934
1935 public String getArg(String name) {
1936 throw new CompileTimeIntepreterException();
1937 }
1938
1939 public Object getNamespace(String key) {
1940 throw new CompileTimeIntepreterException();
1941 }
1942
1943 public String saveNamespace(Object namespace) {
1944 throw new CompileTimeIntepreterException();
1945 }
1946
1947 public Object getFromStack(int i) {
1948 if( i < 0 )
1949 i = stack.size() + i;
1950 Object obj = stack.get(i);
1951 if( obj == null )
1952 throw new CompileTimeIntepreterException();
1953 return obj;
1954 }
1955
1956 public int push(Object scope) {
1957 return RunStateImpl.push(stack,program.extensionMap(),scope);
1958 }
1959
1960 public void pop(int n) {
1961 RunStateImpl.pop(stack,n);
1962 }
1963
1964 public boolean hasNamespace(String namespace) {
1965 return Compiler.this.hasNamespace(namespace);
1966 }
1967
1968 public boolean isInCommandStack(String commandName) {
1969 for( StackTraceElement stackElem : stackTrace ) {
1970 if( commandName.equals(stackElem.commandName()) )
1971 return true;
1972 }
1973 return false;
1974 }
1975
1976 public Encoder getEncoder() {
1977 return Encoder.TEXT;
1978 }
1979
1980 public void setEncoder(Encoder encoder) {
1981 if( encoder != Encoder.TEXT )
1982 throw new CompileTimeIntepreterException();
1983 }
1984
1985 public Map<String,String> getVars(Macro macro) {
1986 throw new CompileTimeIntepreterException();
1987 }
1988
1989 public void pushVars(Macro macro) {
1990 throw new CompileTimeIntepreterException();
1991 }
1992
1993 public void popVars(Macro macro) {
1994 throw new CompileTimeIntepreterException();
1995 }
1996
1997 }
1998
1999 static RuntimeException interpFix(InvocationTargetException e) {
2000 Throwable t = e.getCause();
2001 if( t instanceof Error )
2002 throw (Error)t;
2003 if( t instanceof TemplateRuntimeException )
2004 return (TemplateRuntimeException)t;
2005 if( t instanceof ExitException )
2006 return (ExitException)t;
2007 return new TemplateRuntimeException((Exception)t);
2008 }
2009
2010 }