Mercurial Hosting > nabble
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 } |