You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@velocity.apache.org by nb...@apache.org on 2008/08/13 02:07:26 UTC
svn commit: r685390 [2/2] - in /velocity/engine/trunk:
src/java/org/apache/velocity/context/ src/java/org/apache/velocity/runtime/
src/java/org/apache/velocity/runtime/directive/
src/java/org/apache/velocity/runtime/parser/node/ src/java/org/apache/vel...
Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java?rev=685390&r1=685389&r2=685390&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/directive/VelocimacroProxy.java Tue Aug 12 17:07:23 2008
@@ -19,28 +19,20 @@
* under the License.
*/
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.StringReader;
import java.io.Writer;
-import java.util.HashMap;
import org.apache.commons.lang.StringUtils;
import org.apache.velocity.context.InternalContextAdapter;
-import org.apache.velocity.context.VMContext;
+import org.apache.velocity.context.ProxyVMContext;
+import org.apache.velocity.exception.MacroOverflowException;
import org.apache.velocity.exception.MethodInvocationException;
-import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.TemplateInitException;
-import org.apache.velocity.exception.MacroOverflowException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.RuntimeServices;
-import org.apache.velocity.runtime.parser.ParseException;
-import org.apache.velocity.runtime.parser.ParserTreeConstants;
-import org.apache.velocity.runtime.parser.Token;
import org.apache.velocity.runtime.parser.node.ASTDirective;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.SimpleNode;
-import org.apache.velocity.runtime.visitor.VMReferenceMungeVisitor;
/**
* VelocimacroProxy.java
@@ -52,21 +44,16 @@
*/
public class VelocimacroProxy extends Directive
{
- private String macroName = "";
- private String macroBody = "";
+ private String macroName;
private String[] argArray = null;
+ private String[] literalArgArray = null;
private SimpleNode nodeTree = null;
private int numMacroArgs = 0;
- private String namespace = "";
-
- private boolean init = false;
- private String[] callingArgs;
- private int[] callingArgTypes;
- private HashMap proxyArgHash = new HashMap();
-
+ private boolean preInit = false;
private boolean strictArguments;
+ private boolean localContextScope = false;
private int maxCallDepth;
-
+
/**
* Return name of this Velocimacro.
* @return The name of this Velocimacro.
@@ -77,8 +64,7 @@
}
/**
- * Velocimacros are always LINE
- * type directives.
+ * Velocimacros are always LINE type directives.
* @return The type of this directive.
*/
public int getType()
@@ -87,25 +73,35 @@
}
/**
- * sets the directive name of this VM
+ * sets the directive name of this VM
+ *
* @param name
*/
- public void setName( String name )
+ public void setName(String name)
{
macroName = name;
}
/**
- * sets the array of arguments specified in the macro definition
+ * sets the array of arguments specified in the macro definition
+ *
* @param arr
*/
- public void setArgArray( String [] arr )
+ public void setArgArray(String[] arr)
{
argArray = arr;
+
+ // for performance reasons we precache these strings - they are needed in
+ // "render literal if null" functionality
+ literalArgArray = new String[arr.length];
+ for(int i = 0; i < arr.length; i++)
+ {
+ literalArgArray[i] = ".literal.$" + argArray[i];
+ }
/*
- * get the arg count from the arg array. remember that the arg array
- * has the macro name as it's 0th element
+ * get the arg count from the arg array. remember that the arg array has the macro name as
+ * it's 0th element
*/
numMacroArgs = argArray.length - 1;
@@ -114,13 +110,14 @@
/**
* @param tree
*/
- public void setNodeTree( SimpleNode tree )
+ public void setNodeTree(SimpleNode tree)
{
nodeTree = tree;
}
/**
- * returns the number of ars needed for this VM
+ * returns the number of ars needed for this VM
+ *
* @return The number of ars needed for this VM
*/
public int getNumArgs()
@@ -129,181 +126,146 @@
}
/**
- * Sets the orignal macro body. This is simply the cat of the macroArray, but the
- * Macro object creates this once during parsing, and everyone shares it.
- * Note : it must not be modified.
- * @param mb
- */
- public void setMacrobody( String mb )
- {
- macroBody = mb;
- }
-
- /**
- * @param ns
- */
- public void setNamespace( String ns )
- {
- this.namespace = ns;
- }
-
- /**
- * Renders the macro using the context
- * @param context
- * @param writer
- * @param node
+ * Renders the macro using the context.
+ *
+ * @param context Current rendering context
+ * @param writer Writer for output
+ * @param node AST that calls the macro
* @return True if the directive rendered successfully.
* @throws IOException
* @throws MethodInvocationException
* @throws MacroOverflowException
*/
- public boolean render( InternalContextAdapter context, Writer writer, Node node)
- throws IOException, MethodInvocationException, MacroOverflowException
+ public boolean render(InternalContextAdapter context, Writer writer, Node node)
+ throws IOException, MethodInvocationException, MacroOverflowException
{
- try
- {
- /*
- * it's possible the tree hasn't been parsed yet, so get
- * the VMManager to parse and init it
- */
-
- if (nodeTree != null)
- {
- synchronized(this)
- {
- if ( !init )
- {
- nodeTree.init( context, rsvc);
- init = true;
- }
- }
-
- /*
- * wrap the current context and add the VMProxyArg objects
- */
+ // wrap the current context and add the macro arguments
- VMContext vmc = new VMContext( context, rsvc );
+ // the creation of this context is a major bottleneck (incl 2x HashMap)
+ final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope);
- for( int i = 1; i < argArray.length; i++)
- {
- /*
- * we can do this as VMProxyArgs don't change state. They change
- * the context.
- */
+ int callArguments = node.jjtGetNumChildren();
- VMProxyArg arg = (VMProxyArg) proxyArgHash.get( argArray[i] );
- vmc.addVMProxyArg( arg );
- }
+ if (callArguments > 0)
+ {
+ // the 0th element is the macro name
+ for (int i = 1; i < argArray.length; i++)
+ {
+ Node macroCallArgument = node.jjtGetChild(i - 1);
/*
- * check that we aren't already at the max call depth
+ * literalArgArray[i] is needed for "render literal if null" functionality.
+ * The value is used in ASTReference render-method.
+ *
+ * The idea is to avoid generating the literal until absolutely necessary.
+ *
+ * This makes VMReferenceMungeVisitor obsolete and it would not work anyway
+ * when the macro AST is shared
*/
- if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
- {
- String templateName = vmc.getCurrentTemplateName();
- Object[] stack = vmc.getMacroNameStack();
+ vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], macroCallArgument);
+ }
+ }
+
+ /*
+ * check that we aren't already at the max call depth
+ */
+ if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth())
+ {
+ String templateName = vmc.getCurrentTemplateName();
+ Object[] stack = vmc.getMacroNameStack();
- String message = "Max calling depth of "+maxCallDepth+
- " was exceeded in Template:" + templateName +
- " and Macro:" + macroName + " with Call Stack:";
- for (int i = 0; i < stack.length; i++)
- {
- if (i != 0)
- {
- message += "->";
- }
- message += stack[i];
- }
- rsvc.getLog().error(message);
-
- try
- {
- throw new MacroOverflowException(message);
- }
- finally
- {
- // clean out the macro stack, since we just broke it
- while (vmc.getCurrentMacroCallDepth() > 0)
- {
- vmc.popCurrentMacroName();
- }
- }
+ String message = "Max calling depth of " + maxCallDepth + " was exceeded in Template:"
+ + templateName + " and Macro:" + macroName + " with Call Stack:";
+ for (int i = 0; i < stack.length; i++)
+ {
+ if (i != 0)
+ {
+ message += "->";
}
+ message += stack[i];
+ }
+ rsvc.getLog().error(message);
- /*
- * now render the VM
- */
- vmc.pushCurrentMacroName(macroName);
- nodeTree.render( vmc, writer );
- vmc.popCurrentMacroName();
+ try
+ {
+ throw new MacroOverflowException(message);
}
- else
+ finally
{
- rsvc.getLog().error("VM error " + macroName + ". Null AST");
+ // clean out the macro stack, since we just broke it
+ while (vmc.getCurrentMacroCallDepth() > 0)
+ {
+ vmc.popCurrentMacroName();
+ }
}
}
- /*
- * if it's a MIE, it came from the render.... throw it...
- */
- catch( MethodInvocationException e )
+ try
+ {
+ // render the velocity macro
+ vmc.pushCurrentMacroName(macroName);
+ nodeTree.render(vmc, writer);
+ vmc.popCurrentMacroName();
+ }
+ catch (MethodInvocationException e)
{
throw e;
}
-
- /**
- * pass through application level runtime exceptions
- */
- catch( RuntimeException e )
+ catch (RuntimeException e)
{
throw e;
}
-
- catch ( Exception e )
+ catch (Exception e)
{
-
- rsvc.getLog().error("VelocimacroProxy.render() : exception VM = #" +
- macroName + "()", e);
+ rsvc.getLog().error("VelocimacroProxy.render() : exception VM = #" + macroName + "()",
+ e);
}
-
return true;
}
/**
- * The major meat of VelocimacroProxy, init() checks the # of arguments, patches the
- * macro body, renders the macro into an AST, and then inits the AST, so it is ready
- * for quick rendering. Note that this is only AST dependant stuff. Not context.
+ * The major meat of VelocimacroProxy, init() checks the # of arguments.
+ *
* @param rs
* @param context
* @param node
* @throws TemplateInitException
*/
- public void init( RuntimeServices rs, InternalContextAdapter context, Node node)
- throws TemplateInitException
+ public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
+ throws TemplateInitException
{
- super.init( rs, context, node );
+ // there can be multiple threads here so avoid double inits
+ synchronized (this)
+ {
+ if (!preInit)
+ {
+ super.init(rs, context, node);
- /**
- * Throw exception for invalid number of arguments?
- */
- strictArguments = rs.getConfiguration().getBoolean(RuntimeConstants.VM_ARGUMENTS_STRICT,false);
+ // this is a very expensive call (ExtendedProperties is very slow)
+ strictArguments = rs.getConfiguration().getBoolean(
+ RuntimeConstants.VM_ARGUMENTS_STRICT, false);
- /*
- * get the macro call depth limit
- */
- maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
-
- /*
- * how many args did we get?
- */
+ // support for local context scope feature, where all references are local
+ // we do not have to check this at every invocation of ProxyVMContext
+ localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false);
- int i = node.jjtGetNumChildren();
+ // get the macro call depth limit
+ maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH);
- /*
- * right number of args?
- */
+ // initialize the parsed AST
+ // since this is context independent we need to do this only once so
+ // do it here instead of the render method
+ nodeTree.init(context, rs);
+
+ preInit = true;
+ }
+ }
- if ( getNumArgs() != i )
+ // check how many arguments we got
+ int i = node.jjtGetNumChildren();
+
+ // Throw exception for invalid number of arguments?
+ if (getNumArgs() != i)
{
// If we have a not-yet defined macro, we do get no arguments because
// the syntax tree looks different than with a already defined macro.
@@ -312,31 +274,26 @@
// Check for that, if it is true, suppress the error message.
// Fixes VELOCITY-71.
- for (Node parent = node.jjtGetParent(); parent != null; )
+ for (Node parent = node.jjtGetParent(); parent != null;)
{
- if ((parent instanceof ASTDirective) &&
- StringUtils.equals(((ASTDirective) parent).getDirectiveName(), "macro"))
+ if ((parent instanceof ASTDirective)
+ && StringUtils.equals(((ASTDirective) parent).getDirectiveName(), "macro"))
{
return;
}
parent = parent.jjtGetParent();
}
-
- String errormsg = "VM #" + macroName + ": error : too " +
- ((getNumArgs() > i) ? "few" : "many") +
- " arguments to macro. Wanted " + getNumArgs() +
- " got " + i;
+
+ String errormsg = "VM #" + macroName + ": error : too "
+ + ((getNumArgs() > i) ? "few" : "many") + " arguments to macro. Wanted "
+ + getNumArgs() + " got " + i;
if (strictArguments)
{
/**
- * indicate col/line assuming it starts at 0 - this will be
- * corrected one call up
+ * indicate col/line assuming it starts at 0 - this will be corrected one call up
*/
- throw new TemplateInitException(errormsg,
- context.getCurrentTemplateName(),
- 0,
- 0);
+ throw new TemplateInitException(errormsg, context.getCurrentTemplateName(), 0, 0);
}
else
{
@@ -344,185 +301,6 @@
return;
}
}
-
- /*
- * get the argument list to the instance use of the VM
- */
-
- callingArgs = getArgArray( node );
-
- /*
- * now proxy each arg in the context
- */
-
- setupMacro( callingArgs, callingArgTypes );
- }
-
- /**
- * basic VM setup. Sets up the proxy args for this
- * use, and parses the tree
- * @param callArgs
- * @param callArgTypes
- * @return True if the proxy was setup successfully.
- */
- public boolean setupMacro( String[] callArgs, int[] callArgTypes )
- {
- setupProxyArgs( callArgs, callArgTypes );
- parseTree( callArgs );
-
- return true;
- }
-
- /**
- * parses the macro. We need to do this here, at init time, or else
- * the local-scope template feature is hard to get to work :)
- * @param callArgs
- */
- private void parseTree( String[] callArgs )
- {
- try
- {
- /*
- * now parse the macro - and don't dump the namespace
- */
-
- nodeTree = rsvc.parse(new StringReader(macroBody), namespace, false );
-
- /*
- * now, to make null references render as proper schmoo
- * we need to tweak the tree and change the literal of
- * the appropriate references
- *
- * we only do this at init time, so it's the overhead
- * is irrelevant
- */
-
- HashMap hm = new HashMap();
-
- for( int i = 1; i < argArray.length; i++)
- {
- String arg = callArgs[i-1];
-
- /*
- * if the calling arg is indeed a reference
- * then we add to the map. We ignore other
- * stuff
- */
-
- if (arg.charAt(0) == '$')
- {
- hm.put( argArray[i], arg );
- }
- }
-
- /*
- * now make one of our reference-munging visitor, and
- * let 'er rip
- */
-
- VMReferenceMungeVisitor v = new VMReferenceMungeVisitor( hm );
- nodeTree.jjtAccept( v, null );
- }
-
- /**
- * Error in evaluating macro? Throw runtime exception
- */
- catch (ParseException pex)
- {
- throw new ParseErrorException(pex);
- }
- /**
- * pass through application level runtime exceptions
- */
- catch( RuntimeException e )
- {
- throw e;
- }
- catch ( Exception e )
- {
- rsvc.getLog().error("VelocimacroManager.parseTree() : exception " +
- macroName, e);
- }
- }
-
- private void setupProxyArgs( String[] callArgs, int [] callArgTypes )
- {
- /*
- * for each of the args, make a ProxyArg
- */
-
- for( int i = 1; i < argArray.length; i++)
- {
- VMProxyArg arg = new VMProxyArg( rsvc, argArray[i], callArgs[i-1], callArgTypes[i-1] );
- proxyArgHash.put( argArray[i], arg );
- }
- }
-
- /**
- * gets the args to the VM from the instance-use AST
- * @param node
- * @return array of arguments
- */
- private String[] getArgArray( Node node )
- {
- int numArgs = node.jjtGetNumChildren();
-
- String args[] = new String[ numArgs ];
- callingArgTypes = new int[numArgs];
-
- /*
- * eat the args
- */
- int i = 0;
- Token t = null;
- Token tLast = null;
-
- while( i < numArgs )
- {
- args[i] = "";
- /*
- * we want string literalss to lose the quotes. #foo( "blargh" ) should have 'blargh' patched
- * into macro body. So for each arg in the use-instance, treat the stringlierals specially...
- */
-
- callingArgTypes[i] = node.jjtGetChild(i).getType();
-
-
- if (false && node.jjtGetChild(i).getType() == ParserTreeConstants.JJTSTRINGLITERAL )
- {
- args[i] += node.jjtGetChild(i).getFirstToken().image.substring(1, node.jjtGetChild(i).getFirstToken().image.length() - 1);
- }
- else
- {
- /*
- * just wander down the token list, concatenating everything together
- */
- t = node.jjtGetChild(i).getFirstToken();
- tLast = node.jjtGetChild(i).getLastToken();
-
- while( t != tLast )
- {
- args[i] += t.image;
- t = t.next;
- }
-
- /*
- * don't forget the last one... :)
- */
- args[i] += t.image;
- }
- i++;
- }
- return args;
}
}
-
-
-
-
-
-
-
-
-
Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java?rev=685390&r1=685389&r2=685390&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/parser/node/ASTReference.java Tue Aug 12 17:07:23 2008
@@ -276,8 +276,8 @@
* @throws IOException
* @throws MethodInvocationException
*/
- public boolean render(InternalContextAdapter context, Writer writer)
- throws IOException, MethodInvocationException
+ public boolean render(InternalContextAdapter context, Writer writer) throws IOException,
+ MethodInvocationException
{
if (referenceType == RUNT)
@@ -292,21 +292,25 @@
Object value = execute(null, context);
+ String localNullString = null;
+
/*
- * if this reference is escaped (\$foo) then we want to do one of two things :
- * 1) if this is a reference in the context, then we want to print $foo
- * 2) if not, then \$foo (its considered schmoo, not VTL)
+ * if this reference is escaped (\$foo) then we want to do one of two things : 1) if this is
+ * a reference in the context, then we want to print $foo 2) if not, then \$foo (its
+ * considered schmoo, not VTL)
*/
if (escaped)
{
+ localNullString = getNullString(context);
+
if (value == null)
{
if (context.getAllowRendering())
{
writer.write(escPrefix);
writer.write("\\");
- writer.write(nullString);
+ writer.write(localNullString);
}
}
else
@@ -314,7 +318,7 @@
if (context.getAllowRendering())
{
writer.write(escPrefix);
- writer.write(nullString);
+ writer.write(localNullString);
}
}
@@ -322,12 +326,12 @@
}
/*
- * the normal processing
- *
- * if we have an event cartridge, get a new value object
+ * the normal processing
+ *
+ * if we have an event cartridge, get a new value object
*/
- value = EventHandlerUtil.referenceInsert(rsvc, context, literal(), value);
+ value = EventHandlerUtil.referenceInsert(rsvc, context, literal(), value);
String toString = null;
if (value != null)
@@ -335,38 +339,35 @@
toString = value.toString();
}
-
- /*
- * if value is null...
- */
-
- if ( value == null || toString == null)
+ if (value == null || toString == null)
{
/*
- * write prefix twice, because it's schmoo, so the \ don't escape each other...
+ * write prefix twice, because it's schmoo, so the \ don't escape each other...
*/
if (context.getAllowRendering())
{
+ if (localNullString == null)
+ localNullString = getNullString(context);
+
writer.write(escPrefix);
writer.write(escPrefix);
writer.write(morePrefix);
- writer.write(nullString);
+ writer.write(localNullString);
}
if (logOnNull && referenceType != QUIET_REFERENCE && log.isDebugEnabled())
{
- log.debug("Null reference [template '"
- + context.getCurrentTemplateName() + "', line "
- + this.getLine() + ", column " + this.getColumn()
- + "] : " + this.literal() + " cannot be resolved.");
+ log.debug("Null reference [template '" + context.getCurrentTemplateName()
+ + "', line " + this.getLine() + ", column " + this.getColumn() + "] : "
+ + this.literal() + " cannot be resolved.");
}
return true;
}
else
{
/*
- * non-null processing
+ * non-null processing
*/
if (context.getAllowRendering())
@@ -381,6 +382,27 @@
}
/**
+ * This method helps to implement the "render literal if null" functionality.
+ *
+ * VelocimacroProxy saves references to macro arguments (AST nodes) so that if we have a macro
+ * #foobar($a $b) then there is key "$a.literal" which points to the literal presentation of the
+ * argument provided to variable $a. If the value of $a is null, we render the string that was
+ * provided as the argument.
+ *
+ * @param context
+ * @return
+ */
+ private String getNullString(InternalContextAdapter context)
+ {
+ Object callingArgument = context.get(".literal." + nullString);
+
+ if (callingArgument != null)
+ return ((Node) callingArgument).literal();
+ else
+ return nullString;
+ }
+
+ /**
* Computes boolean value of this reference
* Returns the actual value of reference return type
* boolean, and 'true' if value is not null
@@ -666,6 +688,7 @@
* we are working with.
*/
+ // FIXME: this is the key to render nulls as literals, we need to look at context(refname+".literal")
nullString = literal();
if (t.image.startsWith("$!"))
@@ -773,7 +796,8 @@
{
if (literal != null)
return literal;
-
+
+ // this value could be cached in this.literal but it increases memory usage
return super.literal();
}
}
Modified: velocity/engine/trunk/src/java/org/apache/velocity/runtime/visitor/VMReferenceMungeVisitor.java
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/src/java/org/apache/velocity/runtime/visitor/VMReferenceMungeVisitor.java?rev=685390&r1=685389&r2=685390&view=diff
==============================================================================
--- velocity/engine/trunk/src/java/org/apache/velocity/runtime/visitor/VMReferenceMungeVisitor.java (original)
+++ velocity/engine/trunk/src/java/org/apache/velocity/runtime/visitor/VMReferenceMungeVisitor.java Tue Aug 12 17:07:23 2008
@@ -29,6 +29,8 @@
* to preserve the 'render literal if null' behavior w/o making
* the VMProxy stuff more complicated than it is already.
*
+ * @deprecated not needed anymore in shared AST macro implementation
+ *
* @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
* @version $Id$
*/
Modified: velocity/engine/trunk/test/macroforwarddefine/compare/velocity.log.cmp
URL: http://svn.apache.org/viewvc/velocity/engine/trunk/test/macroforwarddefine/compare/velocity.log.cmp?rev=685390&r1=685389&r2=685390&view=diff
==============================================================================
--- velocity/engine/trunk/test/macroforwarddefine/compare/velocity.log.cmp (original)
+++ velocity/engine/trunk/test/macroforwarddefine/compare/velocity.log.cmp Tue Aug 12 17:07:23 2008
@@ -1,16 +1,8 @@
- [error] VM #test1: error : too few arguments to macro. Wanted 1 got 0
- [error] VM error test1. Null AST
- [error] VM #test2: error : too few arguments to macro. Wanted 1 got 0
- [error] VM error test2. Null AST
- [error] VM #test3: error : too few arguments to macro. Wanted 1 got 0
- [error] VM error test3. Null AST
- [error] VM #test4: error : too few arguments to macro. Wanted 1 got 0
- [error] VM error test4. Null AST
- [error] VM #test1: error : too many arguments to macro. Wanted 1 got 2
- [error] VM error test1. Null AST
- [error] VM #test2: error : too many arguments to macro. Wanted 1 got 2
- [error] VM error test2. Null AST
- [error] VM #test3: error : too many arguments to macro. Wanted 1 got 2
- [error] VM error test3. Null AST
- [error] VM #test4: error : too many arguments to macro. Wanted 1 got 2
- [error] VM error test4. Null AST
+ [error] VM #test1: error : too few arguments to macro. Wanted 1 got 0
+ [error] VM #test2: error : too few arguments to macro. Wanted 1 got 0
+ [error] VM #test3: error : too few arguments to macro. Wanted 1 got 0
+ [error] VM #test4: error : too few arguments to macro. Wanted 1 got 0
+ [error] VM #test1: error : too many arguments to macro. Wanted 1 got 2
+ [error] VM #test2: error : too many arguments to macro. Wanted 1 got 2
+ [error] VM #test3: error : too many arguments to macro. Wanted 1 got 2
+ [error] VM #test4: error : too many arguments to macro. Wanted 1 got 2