When you say:
\newmacro\testmacro[1]{foo --- #1 ---}
You end up with:
\def\testmacro{\macro@write{\testmacro}{[1]{foo --- #1 ---}}}}
So you try to define a macro without any arguments but still use #1
in it. This leads to the Illegal parameter number
error message.
In order to permit #1
, #2
, etc. you would need to define the macro with the right number of arguments. You can define the macro first with nine arguments to avoid any Illegal parameter number
error. Then you expand it with ##1
etc. as arguments, so you basically replace #1
with ##1
, etc., and define this to the same macro but without arguments. Because all this happens inside another macro \newmacro
all #
must be doubled:
\def\newmacro#1[#2]#3{%
\def#1##1##2##3##4##5##6##7##8##9{\macro@write{#1}{[#2]{#3}}}% Define macro in a safe way
\expandafter\def\expandafter#1\expandafter{#1{####1}{####2}{####3}{####4}{####5}{####6}{####7}{####8}{####9}}% Replace `#1` with `##1` etc.
}
Then, however, you have the issues of ##
in your output file. This can be best avoided by again defining the macro with nine arguments and print the \meaning
with the prefix stripped, before defining it to its true definition:
\def\macro@write#1#2{%
\def#1##1##2##3##4##5##6##7##8##9{#2}%
\immediate\write\macro@outfile{\string\renewcommand\string#1\expandafter\strip@prefix\meaning#1}%
\renewcommand#1#2%
#1%
}
The \jobname.macros
files then holds:
\renewcommand\testmacro[1]{foo(#1)}
\renewcommand\testmacro[1]{foo --- #1 ---}
However, this breaks when you now use ##1
inside the macro definition, e.g:
\newmacro\testmacro[1]{foo --- #1 ---\def\A##1{(##1)}}
So instead you would recommend the following approach: Define the macro to a different name and copy that definition to the real name inside \macro@write
. You avoid thereby all issues with escaping of macro arguments.
You can use \csname
with \string#1
to build a second macro name which is unique to the official macro name.
\documentclass{article}
\makeatletter
\newwrite\macro@outfile
\immediate\openout\macro@outfile=\jobname.macros%
\typeout{Writing extra macros to file \jobname.macros}%
\def\newmacro#1[#2]#3{%
\expandafter\newcommand\csname @\string#1\endcsname[#2]{#3}%
\newcommand#1{\macro@write{#1}{#2}}%
}
\def\macro@write#1#2{%
\expandafter\let\expandafter#1\csname @\string#1\endcsname
\expandafter\let\csname @\string#1\endcsname\@undefined
\immediate\write\macro@outfile{\string\newcommand\string#1[#2]{\expandafter\strip@prefix\meaning#1}}%
#1%
}
\makeatother
\begin{document}
\newmacro\testmacroa[1]{foo(#1)}
\testmacroa{1}
\testmacroa{2}
\newmacro\testmacrob[1]{foo --- #1 ---}
\testmacrob{3}
\testmacrob{4}
\newmacro\testmacroc[1]{foo --- #1 ---\def\A##1{(##1)}}
\testmacroc{3}
\testmacroc{4}
\end{document}
I took the liberty to replace \renewcommand
with \newcommand
because its seems to be more logically for a \newmacro
macro. If you really want \renewcommand
, i.e. allow \newmacro
to redefine its macro, then define \newmacro
like:
\def\newmacro#1[#2]#3{%
\expandafter\let\csname @\string#1\endcsname\empty
\expandafter\renewcommand\csname @\string#1\endcsname[#2]{#3}%
\def#1{\macro@write{#1}{#2}}%
}
The first line ensures that the macro exists in any case before \renewcommand
is used.
Here is one way you can do this:
Notes:
This updated version properly handles integer and decimal coefficients of zero (+0, 0.00) or one with leading +
signs, spurious spaces, and non-numbers in the input (as per the second section of the table above).
newtoggle
from the etoolbox
package was used as I prefer that syntax versus the \newif
syntax. But if you don't want to include an additional package it should be pretty straightforward to adapt this to use \newif
or some other conditional methods. This toggle keeps track if a term has already been printed to ensure that a leading positive term does not have a +
.
I used the xstring
package to determine if the number had a leading minus sign, or was zero, but again, this could easily be adapted to not use that package as well.
I would recommend that you use this macro as $\vecty$
- that is you explicitly enter math mode, as used below. If however, you wish to use it without having to enter math mode as per your original question, you should not surround it with dollar signs, but instead use
\ensuremath{\LeadingSign #1 #2}
as then it will be useable inside or outside of math mode. To repeat, I am not recommending this as this is clearly a math mode macro. Please see When not to use \ensuremath for math macro? if you disagree.
Further Enhancements:
- Should
\vecty
be required to typeset more than three components, one can easily add additional calls to \Display{}{}
for each of the components -- no other changes should be required. Note that this has not been tested.
- This does not remove the coefficient equal to one, nor eliminate the term if the coefficient is zero for the case of non numerical input: For example
\vecty{-1x}{+0y}{1z}
.
- Could use
pgf
math to do numerical processing of the input so that basic expressions in the coefficients could be simplified. The pgf
math functions could also be used to convert -0.5
to -\frac{1}{2}
.
Code:
\documentclass{article}
\usepackage{amsmath}
\usepackage{xstring}
\usepackage{etoolbox}
\newcommand*{\LeadingSign}{}%
\newcommand*{\CleanedCoefficientWithNoSpaces}{}% coefficient with spaces & "+" removed
\newcommand*{\CleanedCoefficient}{}% digit "1" removed if coefficient is 1, or -1
\newcommand*{\Display}[2]{%
% #1 = coefficient (may not be a number)
% #2 = paramater
%
% ---------------------------------------------------------------- Clean input
\StrSubstitute{#1}{ }{}[\CoefficientWithNoSpaces]% eliminate any spaces
\IfBeginWith{\CoefficientWithNoSpaces}{+}{% eliminate any leading + sign
\StrSubstitute{\CoefficientWithNoSpaces}{+}{}[\CleanedCoefficientWithNoSpaces]%
}{%
\renewcommand*{\CleanedCoefficientWithNoSpaces}{\CoefficientWithNoSpaces}%
}%
% ---------------------------------------------------------------- 1, +1, -1 issue
% If the coefficient is 1, +1, or -1, we need to supress the digit
\IfEq{\CleanedCoefficientWithNoSpaces}{1}{% coefficient equivalent to +1
\renewcommand*{\CleanedCoefficient}{}% eliminate the digit
}{%
\IfEq{\CoefficientWithNoSpaces}{-1}{% coefficient equivalent to -1
\renewcommand*{\CleanedCoefficient}{-}% eliminate the digit (leave sign)
}{%
% This is the case of the coefficient not being +1, or -1, so use as is
\renewcommand*{\CleanedCoefficient}{\CleanedCoefficientWithNoSpaces}%
%
% The issue of the leading sign is dealt with below. Could have moved the
% leading sign handling here (with some adjustments above), but that would
% have made the code a little harder to read. There is already enough
% illiteracy in the world. :-)
}%
}%
% ---------------------------------------------------------------- Leading sign
% We don't want a + sign for the very first term printed.
\renewcommand*{\LeadingSign}{}% initalize
\IfBeginWith{\CleanedCoefficientWithNoSpaces}{-}{%
% use default empty value (sign is part of number)
% or for the case of -1, this has already been set above
}{%
\iftoggle{PrintedFirstTerm}{%
\renewcommand*{\LeadingSign}{+}%
}{%
% Since a leading term of this vector has not yet been printed
% we do not add a + sign
}%
}%
% ---------------------------------------------------------------- Print
\IfEq{#1}{0}{}{%
\LeadingSign \CleanedCoefficient #2%
\global\toggletrue{PrintedFirstTerm}% so next term can have + sign
}%
}%
\newtoggle{PrintedFirstTerm}
\newcommand*{\vecty}[3]{%
\global\togglefalse{PrintedFirstTerm}%
\Display{#1}{\hat{i}} %
\Display{#2}{\hat{j}}%
\Display{#3}{\hat{k}}%
\iftoggle{PrintedFirstTerm}{}{\mathbf{0}}% could also use \vec{0} here
}%
\begin{document}
\[
\begin{array}{ll}
\verb|$\vecty{1}{2}{3}$| & \vecty{1}{2}{3} \\
\verb|$\vecty{0}{3}{-3}$| & \vecty{0}{3}{-3} \\
\verb|$\vecty{0}{0}{0}$| & \vecty{0}{0}{0} \\
\verb|$\vecty{0}{0}{3}$| & \vecty{0}{0}{3} \\
\verb|$\vecty{1}{2}{0}$| & \vecty{1}{2}{0} \\
\verb|$\vecty{0}{3}{3}$| & \vecty{0}{3}{3} \\
\verb|$\vecty{3}{0}{0}$| & \vecty{3}{0}{0} \\
\verb|$\vecty{-1}{1}{0}$| & \vecty{-1}{1}{0} \\[1.5ex]
\verb|$\vecty{- 1}{ - 1}{+ 0}$| & \vecty{- 1}{ - 1}{+ 0}\\
\verb|$\vecty{-1}{-1.0}{+2}$| & \vecty{-1}{-1.0}{+2}\\
\verb|$\vecty{-0.0}{+0}{0}$| & \vecty{-0.0}{+0}{0}\\
\verb|$\vecty{-0}{+x}{y}$| & \vecty{-0}{+x}{y}\\
\verb|$\vecty{-x}{+7}{y}$| & \vecty{-x}{+7}{y}\\
\end{array}
\]
\end{document}
Best Answer
The
stackengine
package already supports this construction with\bracketVectorstack
, which I bundle in the form asked for by the OP. Of course, it is not actually usingbmatrix
.Note that if matrix construction is needed rather than vectors, look for the extensions provided by the
tabstackengine
package. Also, the inter-row baselineskip can be altered with, for example,\setstackgap{L}{1.2\baselineskip}
.