Mercurial Hosting > nabble
view 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 |
line wrap: on
line source
package nabble.naml.compiler; import fschmidt.util.java.ArrayStack; import fschmidt.util.java.CollectionUtils; import fschmidt.util.java.ObjectUtils; import fschmidt.util.java.Stack; import nabble.naml.dom.Attribute; import nabble.naml.dom.Cdata; import nabble.naml.dom.Container; import nabble.naml.dom.Element; import nabble.naml.dom.ElementName; import nabble.naml.dom.EmptyElement; import nabble.naml.dom.Naml; import nabble.naml.dom.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.CharArrayWriter; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; final class Compiler { private static final Logger logger = LoggerFactory.getLogger(Compiler.class); private Traceable traceable = null; private final List<Traceable> traceables = new ArrayList<Traceable>(); private final StackTrace stackTrace = new StackTrace() { public StackTraceElement pop() { StackTraceElement ste = super.pop(); if( traceable != null ) traceable.stackTrace.push(ste.intern()); return ste; } }; private final Stack<GenericNamespace> stack = new ArrayStack<GenericNamespace>(); private final Program program; private final Map<String,List<Macro>> macros; private final Map<Macro,Macro> overrides; private final Set<String> staticNames; private final GenericNamespace[] base; static Template compile( Program program, String templateName, GenericNamespace[] base ) throws CompileException { return new Compiler(program,base).compile(templateName); } private Compiler( Program program, GenericNamespace[] base ) { this.program = program; this.macros = program.macros(); this.overrides = program.overrides(); this.staticNames = program.staticNames(); this.base = base; } private Template compile( String templateName ) throws CompileException { for( GenericNamespace ns : base ) { pushWithExtensions(ns); } Macro macro = getTemplate(templateName); if( macro==null ) return null; addMeaning(null,macro,null); StackTraceElement stackElem = new StackTraceElement(macro); stackTrace.push( stackElem ); try { MacroScope macroScope = new MacroScope(macro); for( String s : macro.parameters ) { macroScope.addParamChunk( new ParamChunk(s) ); } Chunk chunk = compileMacro( macroScope ); if( chunk instanceof ErrorChunk ) throw ((ErrorChunk)chunk).e; chunk = chunk.intern(); Template template = new Template(this,program,chunk,classes(base),macro); return template; } finally { stackTrace.pop(); cleanUpTraceables(); } } private static Class<?>[] classes(GenericNamespace[] nss) { List<Class<?>> classes = new ArrayList<Class<?>>(); for( GenericNamespace ns : nss ) { if( ns instanceof JavaNamespace ) { JavaNamespace jns = (JavaNamespace)ns; classes.add(jns.cls); } } return classes.toArray(new Class<?>[0]); } private void cleanUpTraceables() { for( Traceable t : traceables ) { t.cleanUp(); } } private void addMeaning(ElementName.Part part,Meaning meaning,MacroScope macroScope) { program.addMeaning(part,meaning,macroScope,base); } private int pushWithExtensions(GenericNamespace gns) { stack.push(gns); if( !(gns instanceof JavaNamespace) ) return 1; JavaNamespace ns = (JavaNamespace)gns; List<JavaNamespace> extensions = program.extensionMap().get(ns.cls); if( extensions == null ) { return 1; } for( JavaNamespace extension : extensions ) { stack.push(extension); } return 1 + extensions.size(); } private int push(JavaCall javaCall,String param) { if( javaCall==null || !javaCall.isScoped(param) ) return 0; return pushWithExtensions(JavaNamespace.getNamespace(javaCall.scopedType())); } private int push(Macro macro) throws CompileException { if( macro.type != Macro.Type.NAMESPACE ) return 0; MacroNamespace ns = new MacroNamespace(macro,stackTrace,macros); stack.push(ns); return 1; } private void pop(int n) { for( ; n > 0; n-- ) { stack.pop(); } } private int whereInStack(String namespace) { if( namespace == null ) throw new NullPointerException("namespace is null"); boolean isTop = true; for( int i = stack.size() - 1; i>=0; i-- ) { GenericNamespace ns = stack.get(i); if( (isTop || ns.isGlobal()) && ns.names().contains(namespace) ) return i; if( isTop && !ns.isTransparent() ) isTop = false; } return -1; } private boolean hasNamespace(String namespace) { return whereInStack(namespace) != -1; } private String accessibleStack() { final int top = stack.size() - 1; boolean isTop = true; List<GenericNamespace> list = new ArrayList<GenericNamespace>(); for( int i=top; i>=0; i-- ) { GenericNamespace ns = stack.get(i); if( ns.isGlobal() || isTop ) { list.add(ns); if( isTop && !ns.isTransparent() ) isTop = false; } } Collections.reverse(list); return list.toString(); } int whereInStack(Class<?> cls) throws CompileException { boolean isTop = true; for( int i = stack.size() - 1; i>=0; i-- ) { GenericNamespace gns = stack.get(i); if( !(gns instanceof JavaNamespace) ) continue; JavaNamespace ns = (JavaNamespace)gns; if( !isTop && !ns.isGlobal() ) continue; if( cls.isAssignableFrom(ns.cls) ) return i; if( isTop && !ns.isTransparent() ) isTop = false; } throw new CompileException(stackTrace,"required namespace '"+JavaNamespace.getNamespace(cls)+"' not found in "+accessibleStack()+" stack = "+stack); } private Macro getTemplate(String templateName) throws CompileException { Set<String> namespacesOnStack = new HashSet<String>(); for( GenericNamespace ns : stack ) { namespacesOnStack.addAll( ns.names() ); } List<Macro> candidates = macros.get(templateName); if( candidates == null ) return null; Macro rtn = null; for( Macro candidate : candidates ) { if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) { if( rtn != null ) throw conflict(candidate,rtn); rtn = candidate; } } return rtn; } private Chunk compileMacro(MacroScope macroScope) throws CompileException { Macro macro = macroScope.macro; for( String ns : macro.requiredNamespaces ) { if( !hasNamespace(ns) ) throw new CompileException(stackTrace,"required namespace '"+ns+"' for "+new StackTraceElement(macro)+" not found in "+accessibleStack()+" stack = "+stack); } if( macroScope.parentScope != null && macro.type==Macro.Type.SUBROUTINE && program.getMacroWhichOverrides(macro)==null ) return compileSubroutine(macro,macroScope); int pushed = push(macro); try { Chunk chunk = consolidate( compile( macro.naml, macroScope ) ); chunk = trimChunk(chunk); if( !(chunk instanceof ErrorChunk) && macroScope.hasVars ) chunk = new VarScope(chunk,macroScope); return chunk; } catch(StackOverflowError e) { throw new CompileException(stackTrace,"stack overflow, probably recursive call"); } finally { pop(pushed); } } private Chunk compileSubroutine(Macro macro,MacroScope macroScope) throws CompileException { Map<String,Stringable> argMap = new HashMap<String,Stringable>(); for( String argName : macroScope.getArgNames() ) { Naml argNaml = macroScope.getArg(argName); Chunk argValue = consolidate( compile(argNaml,macroScope.parentScope) ); if( argValue == Chunk.NULL ) continue; if( argValue instanceof ErrorChunk ) return argValue; Stringable s = argValue==emptyChunk ? new StringableString("") : argValue instanceof StringChunk ? new StringableString(((StringChunk)argValue).s) : new StringableChunk(argValue) ; argMap.put(argName,s); } GenericNamespace[] baseSub = new GenericNamespace[macro.requiredNamespaces.size()]; List<Integer> iStackList = new ArrayList<Integer>(); for( int i=0; i<baseSub.length; i++ ) { String namespace = macro.requiredNamespaces.get(i); int iStack = whereInStack(namespace); if( iStack == -1 ) throw new CompileException(stackTrace,"namespace needed for subroutine '"+namespace+"' not found in "+accessibleStack()+" stack = "+stack); GenericNamespace ns = stack.get(iStack); baseSub[i] = ns; if( ns instanceof JavaNamespace ) iStackList.add( iStack(iStack) ); } int[] aiStack = new int[iStackList.size()]; for( int i=0; i<aiStack.length; i++ ) { aiStack[i] = iStackList.get(i); } return new Subroutine(this,argMap,aiStack,baseSub,stackTrace.peek().commandName()); } private static final ElementName.Part nPart = new ElementName.Part("n").intern(); private static final StringChunk closingTag = new StringChunk(">"); private static final StringChunk closingEmptyTag = new StringChunk("/>"); private List<Chunk> compile(Naml naml,MacroScope macroScope) throws CompileException { final List<Chunk> chunks = new ArrayList<Chunk>(); for( Object obj : naml ) { if( obj instanceof Element ) { Element element = (Element)obj; final ElementName name = element.name(); List<ElementName.Part> parts = name.parts(); if( parts.size() > 1 && parts.get(0) == nPart ) { Chunk chunk = compileElement(element,macroScope); chunks.add(chunk); continue; } else if( parts.size() == 1 && parts.get(0).text() == "t" ) { Chunk chunk = compileTranslation(element,macroScope); chunks.add(chunk); continue; } if( !staticNames.contains(name.toLowerCaseString()) ) { stackTrace.push( new StackTraceElement(macroScope.source(),element) ); try { throw new CompileException(stackTrace,"invalid static tag: "+name); } finally { stackTrace.pop(); } } chunks.add( new StringChunk( "<" + element.name() ) ); for( Attribute attr : element.attributes() ) { Chunk valueChunk = consolidate( compile( attr.value(), macroScope ) ); if( valueChunk instanceof ErrorChunk ) { chunks.add( valueChunk ); } else if( valueChunk == emptyChunk ) { chunks.add( new StringChunk( attr.toString("") ) ); } else if( valueChunk instanceof StringChunk ) { StringChunk stringChunk = (StringChunk)valueChunk; chunks.add( new StringChunk( attr.toString(stringChunk.s) ) ); } else { StackTraceElement stackTraceElement = new StackTraceElement(macroScope.source(),element,("[attr - "+attr.name()+"]")); stackTrace.push(stackTraceElement); try { chunks.add( new DynamicAttr( attr, valueChunk ) ); } finally { stackTrace.pop(); } } } String s = element.spaceAtEndOfOpeningTag(); if( s.length() > 0 ) chunks.add( new StringChunk(s) ); if( element instanceof Container ) { chunks.add( closingTag ); Container container = (Container)element; chunks.addAll( compile(container.contents(),macroScope) ); chunks.add( new StringChunk(container.closingTag()) ); } else { chunks.add( closingEmptyTag ); } continue; } else if( obj instanceof Cdata ) { Cdata cdata = (Cdata)obj; chunks.add( new StringChunk(cdata.text()) ); continue; } String s = obj.toString(); if( s.length() > 0 ) chunks.add( new StringChunk(s) ); } return chunks; } private Chunk compileTranslation(Element element,MacroScope macroScope) throws CompileException { stackTrace.push( new StackTraceElement(macroScope.source(),element) ); try { if( !(element instanceof Container) ) throw new CompileException(stackTrace,"'t' tag may not be empty"); if( element.name().endsWithDot() ) throw new CompileException(stackTrace,"'t' tag may not end with dot"); } finally { stackTrace.pop(); } Container container = (Container)element; Naml contents = container.contents(); String macroName = Macro.translationMacroName(contents,null); stackTrace.push( new StackTraceElement(macroScope.source(),element,macroName) ); try { List<Macro> macroList = macros.get(macroName); if( macroList == null ) { contents = removeTranslationArgs(macroScope.source(),contents); return consolidate( compile(contents,macroScope) ); } if( macroList.size() != 1 ) throw new RuntimeException("invalid - "+macroList); Macro macro = macroList.get(0); MacroScope newScope = macroScope.newScope(macro); getTranslationArgs(macroScope.source(),contents,newScope); addMeaning(container.name().parts().get(0),macro,macroScope); return getMacroChunk2(newScope); } finally { stackTrace.pop(); } } private Naml removeTranslationArgs(Source source,Naml oldContents) throws CompileException { boolean changed = false; Naml newContents = new Naml(); for( Object obj : oldContents ) { if( obj instanceof Element ) { Element element = (Element)obj; ElementName name = element.name(); List<ElementName.Part> parts = name.parts(); if( parts.size() >= 2 && parts.get(0).text() == "t" ) { changed = true; stackTrace.push( new StackTraceElement(source,element) ); try { if( parts.size() == 2 ) { if( name.endsWithDot() ) throw new CompileException(stackTrace,"translation argument cannot end with dot"); if( !(element instanceof Container) ) throw new CompileException(stackTrace,"translation argument cannot be empty"); Container container = (Container)element; newContents.addAll( container.contents() ); } else { List<ElementName.Part> newParts = new ArrayList<ElementName.Part>(); newParts.add(nPart); newParts.addAll( parts.subList(2,parts.size()) ); ElementName newName = new ElementName(name.endsWithDot(),newParts); if( element instanceof Container ) { Container container = (Container)element; Naml newContainerContents = removeTranslationArgs(source,container.contents()); Element newElement = new Container(newName,element.attributes(),"",element.lineNumber(),newContainerContents,""); newContents.add(newElement); } else { Element newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber()); newContents.add(newElement); } } } finally { stackTrace.pop(); } continue; } if( element instanceof Container ) { Container oldContainer = (Container)element; Naml oldContainerContents = oldContainer.contents(); Naml newContainerContents = removeTranslationArgs(source,oldContainerContents); if( newContainerContents != oldContainerContents ) { changed = true; Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,""); newContents.add(newContainer); continue; } } } newContents.add(obj); } return changed ? newContents : oldContents; } private void getTranslationArgs(Source source,Naml oldContents,MacroScope newScope) throws CompileException { for( Object obj : oldContents ) { if( obj instanceof Element ) { Element element = (Element)obj; ElementName name = element.name(); List<ElementName.Part> parts = name.parts(); if( parts.size() >= 2 && parts.get(0).text() == "t" ) { String argName = parts.get(1).text(); stackTrace.push( new StackTraceElement(source,element) ); try { if( parts.size() == 2 ) { if( name.endsWithDot() ) throw new CompileException(stackTrace,"translation argument cannot end with dot"); if( !(element instanceof Container) ) throw new CompileException(stackTrace,"translation argument cannot be empty"); Container container = (Container)element; newScope.addArg( argName, container.contents() ); } else { newScope.addArg( argName, getMacroMultiDotArg(element,2,true) ); } } finally { stackTrace.pop(); } continue; } if( element instanceof Container ) { Container container = (Container)element; getTranslationArgs(source,container.contents(),newScope); continue; } } } } private Chunk compileElement(Element element,MacroScope macroScope) throws CompileException { ElementName name = element.name(); try { if( name.parts().size() == 2 ) { return singleMethodTag(element,macroScope); } else { return multiMethodTag(element,1,macroScope); } } catch(CompileMethodException e) { return new ErrorChunk(e); } } private Chunk singleMethodTag(Element element,MacroScope macroScope) throws CompileException { ElementName.Part part = element.name().parts().get(1); String cmdName = part.text(); StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,cmdName); stackTrace.push(stackElem); try { Chunk chunk = getMacroArgChunk(cmdName,macroScope); if( chunk != null ) { if( element.name().endsWithDot() ) throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter"); if( element instanceof Container ) throw new CompileException(stackTrace,"parameter must be empty element, tag must end with '/'"); if( !element.attributes().isEmpty() ) throw new CompileException(stackTrace,"no arguments are allowed in a parameter"); addMeaning(part,ParamMeaning.INSTANCE,macroScope); return chunk; } Call call = getCommandCall(cmdName,macroScope); if( call instanceof Macro ) { Macro macro = (Macro)call; addMeaning(part,macro,macroScope); return getMacroChunk(macro,element,macroScope,false); } JavaCall javaCall = (JavaCall)call; stackElem.setMethod(javaCall.getMethod()); Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope); if( element.name().endsWithDot() ) { getDotArg( (Container)element, macroScope, javaCall, dynamicArgs ); } else if( element instanceof Container ) { getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs ); } addMeaning(part,javaCall.javaCommand,macroScope); return methodChunk(element,macroScope,javaCall,dynamicArgs); } finally { stackTrace.pop(); } } private Chunk multiMethodTag(Element element,int iCmd,MacroScope macroScope) throws CompileException { ElementName.Part part = element.name().parts().get(iCmd); String methodName = part.text(); StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,methodName); stackTrace.push(stackElem); try { Chunk chunk = getMacroArgChunk(methodName,macroScope); if( chunk != null ) { if( iCmd != element.name().parts().size() - 1 || element.name().endsWithDot() ) throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter"); if( !element.attributes().isEmpty() ) throw new CompileException(stackTrace,"no arguments are allowed in an parameter"); addMeaning(part,ParamMeaning.INSTANCE,macroScope); return chunk; } Call call = getCommandCall(methodName,macroScope); if( iCmd == element.name().parts().size() - 1 ) { if( call instanceof Macro ) { Macro macro = (Macro)call; addMeaning(part,macro,macroScope); return getMacroChunk(macro,element,macroScope,true); } JavaCall javaCall = (JavaCall)call; stackElem.setMethod(javaCall.getMethod()); Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope); if( element.name().endsWithDot() ) getDotArg( (Container)element, macroScope, javaCall, dynamicArgs ); addMeaning(part,javaCall.javaCommand,macroScope); return methodChunk(element,macroScope,javaCall,dynamicArgs); } if( call instanceof Macro ) { Macro macro = (Macro)call; if( macro.dotParam==null ) throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't support multi-method tags"); MacroScope newScope = macroScope.newScope(macro); newScope.addArg(macro.dotParam,getMacroMultiDotArg(element,iCmd+1,false)); if( !element.name().endsWithDot() && iCmd==1 && element instanceof Container ) { getMacroTagArgs( macro, (Container)element, newScope ); } addMeaning(part,macro,macroScope); return getMacroChunk2(newScope); } JavaCall javaCall = (JavaCall)call; stackElem.setMethod(javaCall.getMethod()); String dotParam = javaCall.cmdSpec().dotParameter; if( dotParam==null ) throw new CompileException(stackTrace,"method '"+methodName+"' in "+javaCall.method().getDeclaringClass()+" doesn't support multi-method tags"); int pushed = push(javaCall,dotParam); try { chunk = multiMethodTag(element,iCmd+1,macroScope); chunk = trimChunk(chunk); } finally { pop(pushed); } Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>(); if( iCmd==1 && element instanceof Container && !element.name().endsWithDot() ) { getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs ); } if( dynamicArgs.put( javaCall.cmdSpec().dotParameter, chunk ) != null ) throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument"); addMeaning(part,javaCall.javaCommand,macroScope); return methodChunk(element,macroScope,javaCall,dynamicArgs); } finally { stackTrace.pop(); } } private Map<String,Chunk> attributes(Element element,JavaCall javaCall,MacroScope macroScope) throws CompileException { Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>(); for( Attribute attr : element.attributes() ) { String name = attr.name(); int pushed = push(javaCall,name); try { dynamicArgs.put( name, consolidate( compile(attr.value(),macroScope) ) ); } finally { pop(pushed); } } return dynamicArgs; } private Chunk getMacroArgChunk(String cmdName,MacroScope macroScope) throws CompileException { ParamChunk paramChunk = macroScope.getParamChunk(cmdName); if( paramChunk != null ) return paramChunk; Naml macroArg = macroScope.getArg(cmdName); if( macroArg == null ) return null; return consolidate( compile(macroArg,macroScope.parentScope) ); } private Chunk getMacroChunk(Macro macro,Element element,MacroScope macroScope,boolean isMulti) throws CompileException { MacroScope newScope = macroScope.newScope(macro); for( Attribute attr : element.attributes() ) { String name = attr.name(); if( !macro.parameters.contains(name) ) throw new CompileException(stackTrace,"parameter '"+name+"' not allowed, only use "+macro.parameters); newScope.addArg(name,attr.value()); } if( element instanceof Container ) { Container container = (Container)element; ElementName name = container.name(); if( name.endsWithDot() ) { if( macro.dotParam==null ) throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't have a dot_parameter"); Naml naml = container.contents(); naml = Macro.trim(naml); newScope.addArg( macro.dotParam, naml ); } else if(!isMulti) { getMacroTagArgs( macro, container, newScope ); } } return getMacroChunk2(newScope); } private void getMacroTagArgs(Macro macro,Container container,MacroScope newScope) throws CompileException { for( Object obj : container.contents() ) { if( obj instanceof Element ) { Element element = (Element)obj; ElementName name = element.name(); boolean isSingle = name.parts().size() == 1; String argName = name.parts().get(0).text(); if( !macro.parameters.contains(argName) ) throw new CompileException(stackTrace,"tag '"+argName+"' is not an allowed here, only these are allowed: "+macro.parameters); if( isSingle ) { if( !(element instanceof Container) ) throw new CompileException(stackTrace,"empty tag '"+argName+"' not allowed here, argument expected"); if( !element.attributes().isEmpty() ) throw new CompileException(stackTrace,"tag '"+argName+"' may not have arguments"); Container argContainer = (Container)element; Naml naml = argContainer.contents(); naml = Macro.trim(naml); newScope.addArg(argName,naml); } else { newScope.addArg(argName,getMacroMultiDotArg(element,1,true)); } } else if( !(obj instanceof String) ) { throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here"); } } } private static Naml getMacroMultiDotArg(Element element,int i,boolean allowArgContents) { ElementName name = element.name(); List<ElementName.Part> parts = new ArrayList<ElementName.Part>(); parts.add(nPart); parts.addAll( name.parts().subList(i,name.parts().size()) ); ElementName newName = new ElementName(name.endsWithDot(),parts); Element newElement; if( name.endsWithDot() || allowArgContents && element instanceof Container ) { Container argContainer = (Container)element; newElement = new Container(newName,element.attributes(),"",element.lineNumber(),argContainer.contents(),""); } else { newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber()); } Naml naml = new Naml(); naml.add(newElement); return naml; } private static final Naml nullNaml; static { try { nullNaml = Naml.parser().parse("<n.null/>"); } catch(ParseException e) { logger.error("",e); System.exit(-1); throw new RuntimeException(e); } } private Chunk getMacroChunk2(MacroScope newScope) throws CompileException { Set<String> parameters = new HashSet<String>(newScope.macro.parameters); parameters.removeAll(newScope.getArgNames()); for( String param : parameters ) { newScope.addArg( param, nullNaml ); } return compileMacro(newScope); } private void getDotArg(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs) throws CompileException { String dotParam = javaCall.cmdSpec().dotParameter; if( dotParam==null ) throw new CompileException(stackTrace,"command '"+javaCall.name()+"' doesn't support multi-method tags"); int pushed = push(javaCall,dotParam); try { Chunk block = consolidate( compile( container.contents(), macroScope ) ); block = trimChunk(block); if( dynamicArgs.put( dotParam, block ) != null ) throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument"); } finally { pop(pushed); } } private void getTagArgs(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs) throws CompileException { for( Object obj : container.contents() ) { if( obj instanceof Element ) { Element element = (Element)obj; ElementName name = element.name(); boolean isSingle = name.parts().size() == 1; if( isSingle && !(element instanceof Container) ) throw new CompileException(stackTrace,"empty " + notAllowed(macroScope,element,javaCall) ); String argName = name.parts().get(0).text(); if( !javaCall.cmdSpec().parameters.contains(argName) ) throw new CompileException(stackTrace,notAllowed(macroScope,element,javaCall)); if( isSingle && !element.attributes().isEmpty() ) throw new CompileException(stackTrace,"tag "+argName+" may not have arguments"); int pushed = push(javaCall,argName); try { Chunk block; if( isSingle ) { Container argContainer = (Container)element; block = consolidate( compile( argContainer.contents(), macroScope ) ); } else { block = compileElement( element, macroScope ); } block = trimChunk(block); if( dynamicArgs.put( argName, block ) != null ) throw new CompileException(stackTrace,"duplicate argument: "+argName); } catch(TemplateRuntimeException e) { throw e; } catch(RuntimeException e) { throw new TemplateRuntimeException("in "+name,e); } finally { pop(pushed); } } else if( !(obj instanceof String) ) { throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here"); } } } private static String notAllowed(MacroScope macroScope,Element element,JavaCall javaCall) { Set<String> parameters = javaCall.cmdSpec().parameters; StringBuilder buf = new StringBuilder(); buf .append( "tag " ) .append( new StackTraceElement(macroScope.source(),element) ) .append( " is not allowed here, " ) ; if( parameters.isEmpty() ) { buf.append( "no tags allowed" ); } else { buf.append("only tags "); Iterator<String> iter = parameters.iterator(); buf .append( "<" ) .append( iter.next() ) .append( ">, " ) ; while( iter.hasNext() ) { buf .append( ", <" ) .append( iter.next() ) .append( ">" ) ; } buf.append( " are allowed here" ); } if( javaCall.cmdSpec().dotParameter != null ) buf.append( ", or you may have forgotten the dot at the end of the tag name" ); buf .append( " in tag element '" ) .append( javaCall.name() ) .append( "'" ) ; return buf.toString(); } private Chunk methodChunk(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs) throws CompileException { Chunk methodChunk = methodChunk2(element,macroScope,javaCall,dynamicArgs); if( methodChunk instanceof Block ) { Block block = (Block)methodChunk; Chunk chunk = block.prerun(new CompileTimeRunState()); if( chunk != null ) { return chunk; } } for( Chunk chunk : dynamicArgs.values() ) { if( chunk instanceof ErrorChunk ) return chunk; } return methodChunk; } private Chunk methodChunk2(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs) throws CompileException { Map<String,String> staticArgs = new HashMap<String,String>(); for( Iterator<Map.Entry<String,Chunk>> iter = dynamicArgs.entrySet().iterator(); iter.hasNext(); ) { Map.Entry<String,Chunk> entry = iter.next(); Chunk chunk = entry.getValue(); if( chunk == emptyChunk ) { staticArgs.put( entry.getKey(), "" ); iter.remove(); } else if( chunk instanceof StringChunk ) { StringChunk sc = (StringChunk)entry.getValue(); staticArgs.put( entry.getKey(), sc.s ); iter.remove(); } } if( javaCall.cmdSpec().removeNulls ) removeNulls(dynamicArgs); javaCall.cmdSpec().check(staticArgs,dynamicArgs,stackTrace); if( javaCall.name() == "var" ) { return new GetVar(macroScope,staticArgs,dynamicArgs); } if( javaCall.name() == "set_var" ) { return new SetVar(macroScope,staticArgs,dynamicArgs); } if( javaCall.name() == "uplevel_var" ) { return new GetVar(macroScope.parentScope,staticArgs,dynamicArgs); } if( javaCall.name() == "uplevel_set_var" ) { return new SetVar(macroScope.parentScope,staticArgs,dynamicArgs); } Map<Class,Integer> inStack = new HashMap<Class,Integer>(); for( Class cls : javaCall.cmdSpec().requiredInStack ) { inStack.put( cls, iStack(whereInStack(cls)) ); } if( javaCall.isScoped() ) { if( javaCall.cmdSpec().scopedParameters == null ) throw new CompileException(stackTrace,"a scoped method must specify scoped arguments"); Map<String,Chunk> dynamicArgs2 = new HashMap<String,Chunk>(dynamicArgs); Map<String,Chunk> scopedArgs = new HashMap<String,Chunk>(); for( String scopedParam : javaCall.cmdSpec().scopedParameters ) { Chunk chunk = dynamicArgs2.remove(scopedParam); if( chunk != null ) scopedArgs.put(scopedParam,chunk); } return new Scoped(this,javaCall,inStack,staticArgs,dynamicArgs2,scopedArgs); } return new Block(this,javaCall,inStack,staticArgs,dynamicArgs); } private static void removeNulls(Map<String,Chunk> dynamicArgs) { for( Iterator<Chunk> iter = dynamicArgs.values().iterator(); iter.hasNext(); ) { if( iter.next() == Chunk.NULL ) iter.remove(); } } private int iStack(int i) { if( !(stack.get(i) instanceof JavaNamespace) ) throw new RuntimeException(); int n = 0; for( int j=i; j>=0; j-- ) { if( !stack.get(j).isGlobal() ) { final int size = stack.size(); for( int k=i; k<size; k++ ) { if( stack.get(k) instanceof JavaNamespace ) n++; } return -n; } } for( int k=0; k<i; k++ ) { if( stack.get(k) instanceof JavaNamespace ) n++; } return n; } private Call getCommandCall(String cmdName,MacroScope macroScope) throws CompileException { Call call = null; boolean isTop = true; Set<Class> nsSet = new HashSet<Class>(); Set<String> namespacesOnStack = new HashSet<String>(); for( int i = stack.size() - 1; i>=0; i-- ) { GenericNamespace ns = stack.get(i); if( !isTop && !ns.isGlobal() ) continue; namespacesOnStack.addAll( ns.names() ); if( ns instanceof JavaNamespace ) { JavaNamespace jns = (JavaNamespace)ns; Class cls = jns.cls; JavaCommand javaCmd = JavaCommand.getJavaCommand(cls,cmdName); if( javaCmd != null ) { if( !(call instanceof JavaCall && ((JavaCall)call).javaCommand == javaCmd) ) { i = iStack(i); JavaCall javaCall = new JavaCall(javaCmd,i); if( call != null ) throw conflict(javaCall,call); call = javaCall; program.addMeaning(javaCmd); } } } if( !ns.isTransparent() && isTop ) isTop = false; } List<Macro> candidates = macros.get(cmdName); if( candidates != null ) { for( Macro candidate : candidates ) { if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) { if( call == null ) { call = candidate; } else { if( call.getRequiredNamespaces().containsAll(candidate.getRequiredNamespaces()) ) { if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) ) throw conflict(candidate,call); // keep call } else if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) ) { call = candidate; } else { throw conflict(candidate,call); } } } } } if( cmdName.equals("overridden") ) { if( call != null ) throw new CompileException(stackTrace,"conflict between ("+call+") and 'overridden' command"); call = overrides.get(macroScope.macro); } if( call == null ) throw new CompileMethodException(stackTrace,"macro or method for '"+cmdName+"' not found in "+accessibleStack()+" stack = "+stack); return call; } private CompileException conflict(Call call1,Call call2) { return new CompileException(stackTrace,"conflict between ("+call1+") and ("+call2+")"); } private static Chunk consolidate(List<Chunk> chunks) { List<Chunk> flat = new ArrayList<Chunk>(); for( Chunk chunk : chunks ) { if( chunk instanceof Chunks ) { flat.addAll( Arrays.asList(((Chunks)chunk).chunks) ); } else { flat.add(chunk); } } if( flat.size() == 1 ) return flat.get(0); List<Chunk> merged = new ArrayList<Chunk>(); StringChunk stringChunk = null; StringBuilder buf = new StringBuilder(); for( Chunk chunk : flat ) { if( chunk == emptyChunk ) continue; if( chunk instanceof ErrorChunk ) return chunk; if( chunk instanceof StringChunk ) { StringChunk sc = (StringChunk)chunk; if( stringChunk==null ) { stringChunk = sc; } else { if( buf.length()==0 ) buf.append( stringChunk.s ); buf.append( sc.s ); } } else { if( buf.length() > 0 ) { merged.add( new StringChunk(buf.toString()) ); buf.setLength(0); stringChunk = null; } else if( stringChunk != null ) { merged.add( stringChunk ); stringChunk = null; } merged.add(chunk); } } if( buf.length() > 0 ) { merged.add( new StringChunk(buf.toString()) ); } else if( stringChunk != null ) { merged.add( stringChunk ); } return newChunks(merged); } private static Chunk trimChunk(Chunk chunk) { if( chunk instanceof StringChunk ) { StringChunk sc = (StringChunk)chunk; String s = sc.s.trim(); if( s.length() == 0 ) return emptyChunk; if( s.length() == sc.s.length() ) return sc; return new StringChunk(s); } if( chunk instanceof Chunks ) { boolean changed = false; List<Chunk> chunks = new ArrayList<Chunk>(Arrays.asList(((Chunks)chunk).chunks)); if( chunks.size() >= 2 ) { for( int i=0; i<chunks.size(); ) { Chunk firstChunk = chunks.get(i); if( firstChunk instanceof StringChunk ) { StringChunk sc = (StringChunk)firstChunk; String s = sc.s.replaceAll("^\\s+",""); if( s.length() < sc.s.length() ) { changed = true; if( s.length() == 0 ) { chunks.remove(i); continue; } else { chunks.set(i,new StringChunk(s)); } } } else if( !firstChunk.hasOutput() ) { i++; continue; } break; } for( int i = chunks.size() - 1; i>=0; i-- ) { Chunk lastChunk = chunks.get(i); if( lastChunk instanceof StringChunk ) { StringChunk sc = (StringChunk)lastChunk; String s = sc.s.replaceAll("\\s+$",""); if( s.length() < sc.s.length() ) { changed = true; if( s.length() == 0 ) { chunks.remove(i); continue; } else { chunks.set(i,new StringChunk(s)); } } } else if( !lastChunk.hasOutput() ) { continue; } break; } if( changed ) return newChunks(chunks); } } return chunk; } private static final Chunk emptyChunk = new Chunk() { public void run(IPrintWriter out,RunState runState) {} public boolean hasOutput() { return false; } public String toString() { return "emptyChunk"; } @Override public Chunk intern() { return this; } }; private static final class Chunks implements Chunk { private Chunk[] chunks; private int hash = 0; Chunks(List<Chunk> chunks) { this.chunks = chunks.toArray(new Chunk[chunks.size()]); } public void run(IPrintWriter out,RunState runState) { for( Chunk chunk : chunks ) { chunk.run(out,runState); } } public boolean hasOutput() { for( Chunk chunk : chunks ) { if( chunk.hasOutput() ) return true; } return false; } public String toString() { return "{Chunks: "+chunks+"}"; } @Override public boolean equals(Object obj) { if( this==obj ) return true; if( !(obj instanceof Chunks) ) return false; Chunks c = (Chunks)obj; final int n = chunks.length; if( n != c.chunks.length ) return false; for( int i=0; i<n; i++ ) { if( chunks[i] != c.chunks[i] ) return false; } return true; } @Override public int hashCode() { if( hash == 0 ) hash = Arrays.hashCode(chunks); return hash; } @Override public Chunk intern() { Chunk[] a = new Chunk[chunks.length]; for( int i=0; i<a.length; i++ ) { a[i] = chunks[i].intern(); } chunks = a; return interner.intern(this); } } private static Chunk newChunks(List<Chunk> chunks) { switch( chunks.size() ) { case 0: return emptyChunk; case 1: return chunks.get(0); default: return new Chunks(chunks); } } private static class ErrorChunk implements Chunk { final CompileException e; ErrorChunk(CompileException e) { this.e = e; } public void run(IPrintWriter out,RunState runState) { throw new RuntimeException(e); } public boolean hasOutput() { return false; } /* @Override public boolean equals(Object obj) { throw new RuntimeException("never"); } @Override public int hashCode() { throw new RuntimeException("never"); } */ @Override public Chunk intern() { throw new RuntimeException("never"); } } private static class DynamicAttr implements Chunk { private final Attribute attr; private Chunk chunk; DynamicAttr(Attribute attr,Chunk chunk) { this.attr = attr; this.chunk = chunk; } public void run(IPrintWriter out,RunState runState) { String attrValue = new BlockWrapper(chunk,runState).toString(); if( attrValue != null ) out.print( attr.toString(attrValue) ); } public boolean hasOutput() { return true; } @Override public boolean equals(Object obj) { if( this==obj ) return true; if( !(obj instanceof DynamicAttr) ) return false; DynamicAttr da = (DynamicAttr)obj; return da.attr == attr && da.chunk.equals(chunk); } @Override public int hashCode() { return attr.hashCode() * 31 + chunk.hashCode(); } @Override public Chunk intern() { chunk = chunk.intern(); return interner.intern(this); } } private static class VarScope implements Chunk { private final Macro macro; private Chunk chunk; VarScope(Chunk chunk,MacroScope macroScope) { this.macro = macroScope.macro; this.chunk = chunk; } public void run(IPrintWriter out,RunState runState) { runState.pushVars(macro); try { chunk.run(out,runState); } finally { runState.popVars(macro); } } public boolean hasOutput() { return chunk.hasOutput(); } public String toString() { return "{VarScope}"; } @Override public boolean equals(Object obj) { if( this==obj ) return true; if( !(obj instanceof VarScope) ) return false; VarScope v = (VarScope)obj; return macro == v.macro && chunk.equals(v.chunk); } @Override public int hashCode() { return chunk.hashCode()*31 + macro.hashCode(); } @Override public Chunk intern() { chunk = chunk.intern(); return interner.intern(this); } } private interface Stringable { public String toString(RunState runState); public void internChunk(); } private final static class StringableString implements Stringable { private final String s; StringableString(String s) { this.s = s; } public String toString(RunState runState) { return s; } @Override public boolean equals(Object obj) { if( !(obj instanceof StringableString) ) return false; StringableString ss = (StringableString)obj; return ObjectUtils.equals(ss.s,s); } @Override public int hashCode() { int h = getClass().hashCode(); if( s != null ) h += s.hashCode(); return h; } public void internChunk() {} } private final static class StringableChunk implements Stringable { private Chunk chunk; StringableChunk(Chunk chunk) { this.chunk = chunk; } public String toString(RunState runState) { return new BlockWrapper(chunk,runState).toString(); } @Override public boolean equals(Object obj) { if( !(obj instanceof StringableChunk) ) return false; StringableChunk sc = (StringableChunk)obj; return sc.chunk == chunk; } @Override public int hashCode() { return chunk.hashCode() + getClass().hashCode(); } public void internChunk() { chunk = chunk.intern(); } } private static Stringable getStringable(String name,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) { Chunk chunk = dynamicArgs.get(name); if( chunk != null ) return new StringableChunk(chunk); String s = staticArgs.get(name); return new StringableString(s); } private static final class GetVar implements Chunk { private final Macro macro; private final Stringable name; GetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) { macroScope.hasVars = true; this.macro = macroScope.macro; this.name = getStringable("name",staticArgs,dynamicArgs); } public void run(IPrintWriter out,RunState runState) { String nameS = name.toString(runState); String valueS = runState.getVars(macro).get(nameS); out.print(valueS); } public boolean hasOutput() { return true; } public String toString() { return "{GetVar}"; } @Override public boolean equals(Object obj) { if( !(obj instanceof GetVar) ) return false; GetVar v = (GetVar)obj; return v.macro == macro && v.name.equals(name); } @Override public int hashCode() { int h = macro.hashCode(); h = h * 31 + name.hashCode(); return h; } @Override public Chunk intern() { name.internChunk(); return interner.intern(this); } } private static final class SetVar implements Chunk { private final Macro macro; private final Stringable name; private final Stringable value; SetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) { macroScope.hasVars = true; this.macro = macroScope.macro; this.name = getStringable("name",staticArgs,dynamicArgs); this.value = getStringable("value",staticArgs,dynamicArgs); } public void run(IPrintWriter out,RunState runState) { String nameS = name.toString(runState); String valueS = value.toString(runState); runState.getVars(macro).put(nameS,valueS); } public boolean hasOutput() { return false; } public String toString() { return "{SetVar}"; } @Override public boolean equals(Object obj) { if( !(obj instanceof SetVar) ) return false; SetVar v = (SetVar)obj; return v.macro == macro && v.name.equals(name) && v.value.equals(value); } @Override public int hashCode() { int h = macro.hashCode(); h = h * 31 + name.hashCode(); h = h * 31 + value.hashCode(); return h; } @Override public Chunk intern() { name.internChunk(); value.internChunk(); return interner.intern(this); } } private static final Set<JavaCommand> badMethods = new CopyOnWriteArraySet<JavaCommand>(); private static final Map<JavaCommand,Set<String>> badParams = new ConcurrentHashMap<JavaCommand,Set<String>>(); private static class CompileTimeIntepreterException extends RuntimeException {} private static class CompileTimeArgException extends RuntimeException { final String argName; CompileTimeArgException(String argName) { this.argName = argName; } } private static class CompileTimeScopedException extends RuntimeException { CompileTimeScopedException(CompileTimeIntepreterException e) { super(e); } } private static class ChunkWrapper { final String argName; final Chunk chunk; ChunkWrapper(String argName,Chunk chunk) { this.argName = argName; this.chunk = chunk; } public String toString() { throw new CompileTimeArgException(argName); } } static abstract class Traceable { StackTrace stackTrace = new StackTrace(); Traceable(Compiler compiler) { compiler.traceable = this; compiler.traceables.add(this); } private void cleanUp() { Collections.reverse(stackTrace); stackTrace = stackTrace.intern(); } } static boolean nestedEquals(Map<String,Chunk> map1,Map<String,Chunk> map2) { if( map1.size() != map2.size() ) return false; for( Map.Entry<String,Chunk> entry : map1.entrySet() ) { if( entry.getValue() != map2.get(entry.getKey()) ) return false; } return true; } private static class Block extends Traceable implements Chunk { final JavaCall method; final Map<Class,Integer> inStack; private final Map<String,String> staticArgs; private Map<String,Chunk> dynamicArgs; private final boolean hasOutput; private int hash = 0; Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) { this(compiler,method,inStack,staticArgs,dynamicArgs,Collections.<String>emptySet()); } Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Set<String> scopedArgs) { super(compiler); this.method = method; this.inStack = CollectionUtils.optimizeMap(inStack); this.staticArgs = CollectionUtils.optimizeMap(staticArgs); this.dynamicArgs = CollectionUtils.optimizeMap(dynamicArgs); Set<String> outputtedParameters = method.cmdSpec().outputtedParameters; if( outputtedParameters == null ) { this.hasOutput = true; } else { boolean hasOutput = false; Set<String> staticArgNames = staticArgs.keySet(); for( String s : outputtedParameters ) { if( staticArgNames.contains(s) ) { hasOutput = true; break; } Chunk chunk = dynamicArgs.get(s); if( chunk == null ) { if( scopedArgs.contains(s) ) { hasOutput = true; break; } } else if( chunk.hasOutput() ) { hasOutput = true; break; } } this.hasOutput = hasOutput; } } private void invoke(RunState runState,IPrintWriter out,InterpreterImpl interp) throws IllegalAccessException, InvocationTargetException { try { method.invoke(runState,out,interp); } finally { interp.close(); } } private Chunk prerun(RunState runState) throws CompileException { try { JavaCommand javaCommand = method.javaCommand; if( !javaCommand.isStatic || badMethods.contains(javaCommand) ) return null; { Set<String> params = badParams.get(javaCommand); if( params != null ) { Set<String> chunkSet = dynamicArgs.keySet(); for( String param : params ) { if( chunkSet.contains(param) ) return null; } } } CompilerPrintWriter out = new CompilerPrintWriter(new CharArrayWriter()); // RunState runState = new CompileTimeRunState(); try { Map<String,Object> args = new HashMap<String,Object>(staticArgs); for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) { String argName = entry.getKey(); Chunk argValue = entry.getValue(); args.put( argName, new ChunkWrapper(argName,argValue) ); } invoke(runState,out,newInterpreter(runState,args)); out.close(); return Compiler.consolidate(out.chunks); } catch(InvocationTargetException e) { Throwable cause = e.getCause(); if( cause instanceof CompileTimeIntepreterException ) { badMethods.add(javaCommand); logger.debug("bad method: "+javaCommand.method,cause); } else if( cause instanceof CompileTimeArgException ) { CompileTimeArgException ex = (CompileTimeArgException)cause; Set<String> params = badParams.get(javaCommand); if( params == null ) { params = new CopyOnWriteArraySet<String>(); badParams.put(javaCommand,params); } params.add(ex.argName); // logger.debug("bad argument '"+ex.argName+"' in "+javaCommand.method); } else if( cause instanceof CompileTimeScopedException ) { logger.debug("ignoring: "+javaCommand.method,cause); } else { throw e; } } return null; } catch(InvocationTargetException e) { throw compileFix(e); } catch(IllegalAccessException e) { throw new TemplateRuntimeException(e); } } public void run(IPrintWriter out,RunState runState) { Stack<StackTrace> stack = StackTrace.stack(); stack.push(stackTrace); try { if( dynamicArgs.isEmpty() ) { invoke(runState,out,newInterpreter(runState,staticArgs)); } else { Map<String,Object> args = new HashMap<String,Object>(staticArgs); for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) { args.put( entry.getKey(), new BlockWrapper(entry.getValue(),runState) ); } invoke(runState,out,newInterpreter(runState,args)); } } catch(IllegalAccessException e) { throw new TemplateRuntimeException(e); } catch(InvocationTargetException e) { throw interpFix(e); } finally { stack.pop(); } } public boolean hasOutput() { return hasOutput; } InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) { return new InterpreterImpl(method.cmdSpec(),runState,inStack,args); } public String toString() { return "{Block: "+stackTrace.peek()+"}"; } @Override public boolean equals(Object obj) { if( this==obj ) return true; if( !(obj instanceof Block) ) return false; return equals((Block)obj); } boolean equals(Block block) { return block.method.equals(method) && block.inStack.equals(inStack) && block.staticArgs.equals(staticArgs) && nestedEquals(block.dynamicArgs,dynamicArgs) && block.getClass().equals(getClass()) ; } @Override public int hashCode() { if( hash == 0 ) hash = calcHash(); return hash; } int calcHash() { int h = method.hashCode(); h = h * 31 + inStack.hashCode(); h = h * 31 + staticArgs.hashCode(); h = h * 31 + dynamicArgs.hashCode(); return h; } void internMembers() { Map<String,Chunk> m = new HashMap<String,Chunk>(); for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) { m.put(entry.getKey(),entry.getValue().intern()); } dynamicArgs = CollectionUtils.optimizeMap(m); } @Override public final Chunk intern() { internMembers(); return interner.intern(this); } } private static final class Scoped extends Block { private Map<String,Chunk> scopedArgs; Scoped(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Map<String,Chunk> scopedArgs) { super(compiler,method,inStack,staticArgs,dynamicArgs,scopedArgs.keySet()); this.scopedArgs = CollectionUtils.optimizeMap(scopedArgs); } InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) { return new ScopedInterpreterImpl<Object>(method.cmdSpec(),runState,inStack,args,scopedArgs); } boolean equals(Block block) { if( !super.equals(block) ) return false; Scoped scoped = (Scoped)block; return nestedEquals(scoped.scopedArgs,scopedArgs); } int calcHash() { return super.calcHash() * 31 + scopedArgs.hashCode(); } void internMembers() { super.internMembers(); Map<String,Chunk> m = new HashMap<String,Chunk>(); for( Map.Entry<String,Chunk> entry : scopedArgs.entrySet() ) { m.put(entry.getKey(),entry.getValue().intern()); } scopedArgs = CollectionUtils.optimizeMap(m); } } private static final class Subroutine extends Traceable implements Chunk { private final Map<String,Stringable> argMap; private final int[] aiStack; private final GenericNamespace[] base; private final String commandName; private Template template = null; private int hash = 0; Subroutine(Compiler compiler,Map<String,Stringable> argMap,int[] aiStack,GenericNamespace[] base,String commandName) { super(compiler); this.argMap = argMap; this.aiStack = aiStack; this.base = base; this.commandName = commandName; } public void run(IPrintWriter out,RunState runState) { Stack<StackTrace> stackTraces = StackTrace.stack(); stackTraces.push(stackTrace); try { synchronized(this) { if( template == null ) { try { template = runState.template().program().getTemplate(commandName,base); } catch(CompileException e) { throw new TemplateRuntimeException(e); } if( template == null ) throw new TemplateRuntimeException("couldn't find: "+commandName); } } Map<String,Object> args = new HashMap<String,Object>(); for( Map.Entry<String,Stringable> entry : argMap.entrySet() ) { String value = entry.getValue().toString(runState); if( value != null ) args.put( entry.getKey(), value ); } Object[] stack = new Object[aiStack.length]; for( int i=0; i<aiStack.length; i++ ) { stack[i] = runState.getFromStack(aiStack[i]); } template.run( runState.callDepth()+1, runState.getEncoder(), out, args, stack ); } finally { stackTraces.pop(); } } public boolean hasOutput() { return true; } public String toString() { return "{Subroutine: "+commandName+"}"; } /* @Override public boolean equals(Object obj) { if( this==obj ) return true; if( !(obj instanceof Subroutine) ) return false; Subroutine sub = (Subroutine)obj; return sub.commandName.equals(commandName) && sub.argMap.equals(argMap) && Arrays.equals(sub.aiStack,aiStack) && Arrays.equals(sub.base,base) ; } @Override public int hashCode() { if( hash == 0 ) { int h = commandName.hashCode(); h = h * 31 + argMap.hashCode(); h = h * 31 + Arrays.hashCode(aiStack); h = h * 31 + Arrays.hashCode(base); hash = h; } return hash; } */ @Override public final Chunk intern() { for( Stringable s : argMap.values() ) { s.internChunk(); } return interner.intern(this); } } private static CompileException compileFix(InvocationTargetException e) { Throwable t = e.getCause(); if( t instanceof Error ) throw (Error)t; if( t instanceof TemplateRuntimeException ) throw (TemplateRuntimeException)t; if( t instanceof CompileException ) return (CompileException)t; throw new TemplateRuntimeException((Exception)t); } // from old CompilerDataImpl /* private Chunk runHandler(Block block) throws CompileException { JavaCommand javaCommand = block.method.javaCommand; if( !javaCommand.isStatic || badMethods.contains(javaCommand) ) return null; CompilerPrintWriter writer = new CompilerPrintWriter(new CharArrayWriter()); RunState runState = new CompileTimeRunState(); try { block.run(writer,runState); writer.close(); return Compiler.consolidate(writer.chunks); } catch(CompileTimeIntepreterException e) { badMethods.add(javaCommand); logger.info("bad method: "+javaCommand.method); } catch(CompileTimeArgException e) { //e.printStackTrace(); // nothing } return null; } */ private static class CompilerPrintWriter extends TemplatePrintWriter { private final CharArrayWriter sw; private boolean isNull = false; final List<Chunk> chunks = new ArrayList<Chunk>(); CompilerPrintWriter(CharArrayWriter sw) { super(sw); this.sw = sw; } public void print(Object obj) { if( obj == null ) // return; throw new RuntimeException("why"); if( obj instanceof ChunkWrapper ) { addStringChunk(); chunks.add( ((ChunkWrapper)obj).chunk ); } else if( obj instanceof BlockWrapper ) { // should only happen for scoped args try { super.print(obj); } catch(CompileTimeIntepreterException e) { throw new CompileTimeScopedException(e); } } else { super.print(obj); } } public void print(String s) { if( s == null && sw.size() == 0 && chunks.isEmpty() && !isNull ) { isNull = true; return; } super.print(s); } public void close() { super.close(); addStringChunk(); if( isNull ) { if( !chunks.isEmpty() ) throw new NullPointerException("null written to stream"); chunks.add(Chunk.NULL); } } private void addStringChunk() { if( sw.size() > 0 ) { chunks.add( new StringChunk(sw.toString()) ); sw.reset(); } } } private final class CompileTimeRunState implements RunState { private final Stack<Object> stack = new ArrayStack<Object>(); CompileTimeRunState() { int n = Compiler.this.stack.size(); while( n-- > 0 ) { stack.push(null); } } public Template template() { throw new CompileTimeIntepreterException(); } public Program program() { return program; } public int callDepth() { throw new CompileTimeIntepreterException(); } public void putArg(String name,String value) { throw new CompileTimeIntepreterException(); } public String getArg(String name) { throw new CompileTimeIntepreterException(); } public Object getNamespace(String key) { throw new CompileTimeIntepreterException(); } public String saveNamespace(Object namespace) { throw new CompileTimeIntepreterException(); } public Object getFromStack(int i) { if( i < 0 ) i = stack.size() + i; Object obj = stack.get(i); if( obj == null ) throw new CompileTimeIntepreterException(); return obj; } public int push(Object scope) { return RunStateImpl.push(stack,program.extensionMap(),scope); } public void pop(int n) { RunStateImpl.pop(stack,n); } public boolean hasNamespace(String namespace) { return Compiler.this.hasNamespace(namespace); } public boolean isInCommandStack(String commandName) { for( StackTraceElement stackElem : stackTrace ) { if( commandName.equals(stackElem.commandName()) ) return true; } return false; } public Encoder getEncoder() { return Encoder.TEXT; } public void setEncoder(Encoder encoder) { if( encoder != Encoder.TEXT ) throw new CompileTimeIntepreterException(); } public Map<String,String> getVars(Macro macro) { throw new CompileTimeIntepreterException(); } public void pushVars(Macro macro) { throw new CompileTimeIntepreterException(); } public void popVars(Macro macro) { throw new CompileTimeIntepreterException(); } } static RuntimeException interpFix(InvocationTargetException e) { Throwable t = e.getCause(); if( t instanceof Error ) throw (Error)t; if( t instanceof TemplateRuntimeException ) return (TemplateRuntimeException)t; if( t instanceof ExitException ) return (ExitException)t; return new TemplateRuntimeException((Exception)t); } }