diff options
author | Karl Berry <karl@freefriends.org> | 2010-06-03 00:05:53 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2010-06-03 00:05:53 +0000 |
commit | 66d458ffb4296f606adea295988eec449f55621e (patch) | |
tree | 3cbd19eeac92cfa52aff17515893b571d0bd343c /Master/texmf-dist/doc | |
parent | 38164db6bdc7213159b596311fb51c3c5fe73e6a (diff) |
new support package newcommand 2.0 (2jun10)
git-svn-id: svn://tug.org/texlive/trunk@18704 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc')
-rw-r--r-- | Master/texmf-dist/doc/latex/newcommand/README | 58 | ||||
-rw-r--r-- | Master/texmf-dist/doc/latex/newcommand/newcommand.pdf | bin | 0 -> 404178 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/latex/newcommand/newcommand.py | 711 | ||||
-rw-r--r-- | Master/texmf-dist/doc/latex/newcommand/newcommand.tex | 742 | ||||
-rw-r--r-- | Master/texmf-dist/doc/latex/newcommand/spark.py | 566 |
5 files changed, 2077 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/latex/newcommand/README b/Master/texmf-dist/doc/latex/newcommand/README new file mode 100644 index 00000000000..09bc10e65f2 --- /dev/null +++ b/Master/texmf-dist/doc/latex/newcommand/README @@ -0,0 +1,58 @@ + +------------------------------------+ + | NEWCOMMAND.PY | + | | + | More flexible argument processing | + | than what \newcommand provides | + | | + | By Scott Pakin, scott+nc@pakin.org | + +------------------------------------+ + + +Description +----------- + +LaTeX's \newcommand is fairly limited in the way it processes optional +arguments, but the TeX alternative, a batch of \defs and \futurelets, +can be overwhelming to the casual LaTeX user. newcommand.py is a +Python program that automatically generates LaTeX macro definitions +for macros that require more powerful argument processing than +\newcommand can handle. newcommand.py is intended for LaTeX advanced +beginners (i.e., those who know how to use \newcommand but not +internal LaTeX2e commands such as \@ifnextchar) and for more advanced +users who want to save some typing when defining complex macros. + +With newcommand.py, the user specifies a template for a macro's +arguments. newcommand.py then custom-generates a macro definition +according to the user's specifications and includes a user-friendly +"Put code here" comment to indicate where the macro's main code should +appear. newcommand.py supports arbitrary interleavings of required +and optional arguments, starred macros, mandatory literal text, macros +with more than nine arguments, optional arguments delimited by +parentheses instead of square brackets, and optional arguments whose +value defaults to the value given for a prior argument. The generated +macros can easily be pasted into a LaTeX document and edited as +desired. + + +Installation +------------ + +You'll need a Python interpreter (http://www.python.org/). Besides +that, just make sure that newcommand.py is placed somewhere in your +path and that spark.py is somewhere that newcommand.py can find it. + + +Copyright and license +--------------------- + +Copyright (C) 2010 Scott Pakin, scott+nc@pakin.org + +This package may be distributed and/or modified under the conditions +of the LaTeX Project Public License, either version 1.3c of this +license or (at your option) any later version. The latest version of +this license is in: + + http://www.latex-project.org/lppl.txt + +and version 1.3c or later is part of all distributions of LaTeX version +2006/05/20 or later. diff --git a/Master/texmf-dist/doc/latex/newcommand/newcommand.pdf b/Master/texmf-dist/doc/latex/newcommand/newcommand.pdf Binary files differnew file mode 100644 index 00000000000..3a908ba4dac --- /dev/null +++ b/Master/texmf-dist/doc/latex/newcommand/newcommand.pdf diff --git a/Master/texmf-dist/doc/latex/newcommand/newcommand.py b/Master/texmf-dist/doc/latex/newcommand/newcommand.py new file mode 100644 index 00000000000..e14995926c8 --- /dev/null +++ b/Master/texmf-dist/doc/latex/newcommand/newcommand.py @@ -0,0 +1,711 @@ +#! /usr/bin/env python + +# ----------------------------------------------------------------------- +# Convert a macro prototype to a LaTeX \newcommand +# By Scott Pakin <scott+nc@pakin.org> +# ----------------------------------------------------------------------- +# Copyright (C) 2010 Scott Pakin, scott+nc@pakin.org +# +# This package may be distributed and/or modified under the conditions +# of the LaTeX Project Public License, either version 1.3c of this +# license or (at your option) any later version. The latest version of +# this license is in: +# +# http://www.latex-project.org/lppl.txt +# +# and version 1.3c or later is part of all distributions of LaTeX version +# 2006/05/20 or later. +# ----------------------------------------------------------------------- + +from spark import GenericScanner, GenericParser, GenericASTTraversal +import re +import copy + +class ParseError(Exception): + "Represent any error that occurs during processing." + pass + + +class Token: + "Represent a single lexed token." + + def __init__(self, type, charOffset, attr=None): + self.type = type + self.attr = attr + self.charOffset = charOffset + + def __cmp__(self, o): + return cmp(self.type, o) + + def __str__(self): + return self.attr + + +class AST: + "Represent an abstract syntax tree." + + def __init__(self, type, charOffset, attr=None, kids=[]): + self.type = type + self.charOffset = charOffset + self.attr = attr + self.kids = kids + + def __getitem__(self, child): + return self.kids[child] + + def __len__(self): + return len(self.kids) + + +class CmdScanner(GenericScanner): + "Defines a lexer for macro prototypes." + + def __init__(self): + GenericScanner.__init__(self) + self.charOffset = 0 + + def tokenize(self, input): + self.rv = [] + GenericScanner.tokenize(self, input) + return self.rv + + def t_whitespace(self, whiteSpace): + r' [\s\r\n]+ ' + self.charOffset = self.charOffset + len(whiteSpace) + + def t_command(self, cmd): + r' MACRO ' + self.rv.append(Token(type='command', + attr=cmd, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(cmd) + + def t_argument_type(self, arg): + r' OPT ' + self.rv.append(Token(type='argtype', + attr=arg, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(arg) + + def t_argument(self, arg): + r' \#\d+ ' + self.rv.append(Token(type='argument', + attr=arg, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(arg) + + def t_equal(self, equal): + r' = ' + self.rv.append(Token(type=equal, + attr=equal, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(equal) + + def t_quoted(self, quoted): + r' \{[^\}]*\} ' + self.rv.append(Token(type='quoted', + attr=quoted, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(quoted) + + def t_identifier(self, ident): + r' [A-Za-z]+ ' + self.rv.append(Token(type='ident', + attr=ident, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(ident) + + def t_delimiter(self, delim): + r' [()\[\]] ' + self.rv.append(Token(type='delim', + attr=delim, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(delim) + + def t_other(self, other): + r' [^()\[\]\{\}\#\s\r\n]+ ' + self.rv.append(Token(type='other', + attr=other, + charOffset=self.charOffset)) + self.charOffset = self.charOffset + len(other) + + +class CmdParser(GenericParser): + "Defines a parser for macro prototypes." + + def __init__(self, start='decl'): + GenericParser.__init__(self, start) + + def error(self, token): + raise ParseError, \ + ("Syntax error", 1+token.charOffset) + + def p_optarg(self, args): + ' optarg ::= argtype delim defvals delim ' + return AST(type='optarg', + charOffset=args[0].charOffset, + attr=(args[1].attr, args[3].attr), + kids=[args[2]]) + + def p_rawtext(self, args): + ' rawtext ::= other ' + return AST(type='rawtext', + charOffset=args[0].charOffset, + attr=args[0].attr) + + def p_defval(self, args): + ' defval ::= argument = quoted ' + return AST(type='defval', + charOffset=args[0].charOffset, + attr=(args[0].attr, args[2].attr)) + + def p_defvals_1(self, args): + ' defvals ::= defval ' + return AST(type='defvals', + charOffset=args[0].charOffset, + kids=args) + + def p_defvals_2(self, args): + ''' + defvals ::= defval rawtext defvals + defvals ::= defval ident defvals + defvals ::= defval quoted defvals + ''' + return AST(type='defvals', + charOffset=args[0].charOffset, + attr=(args[1].type, args[1].attr, args[1].charOffset), + kids=[args[0],args[2]]) + + # Top-level macro argument + def p_arg_1(self, args): + ''' + arg ::= quoted + arg ::= argument + ''' + return AST(type='arg', + charOffset=args[0].charOffset, + attr=[args[0].type]+[args[0].attr]) + + def p_arg_2(self, args): + ' arg ::= optarg ' + return AST(type='arg', + charOffset=args[0].charOffset, + attr=[args[0].type]+[args[0].attr], + kids=args[0].kids) + + def p_arg_3(self, args): + ' arg ::= rawtext ' + if args[0].attr != "*": + raise ParseError, \ + ('Literal text must be quoted between "{" and "}"', + args[0].charOffset + 1) + return AST(type='arg', + charOffset=args[0].charOffset, + attr=[args[0].type]+[args[0].attr]) + + def p_arglist_1(self, args): + ' arglist ::= arg ' + return AST(type='arglist', + charOffset=args[0].charOffset, + kids=args) + + def p_arglist_2(self, args): + ' arglist ::= arg arglist ' + return AST(type='arglist', + charOffset=args[0].charOffset, + kids=args) + + def p_decl_1(self, args): + ' decl ::= command ident ' + return AST(type='decl', + charOffset=args[0].charOffset, + attr=(args[0].attr, args[1].attr), + kids=[]) + + def p_decl_2(self, args): + ' decl ::= command ident arglist ' + return AST(type='decl', + charOffset=args[0].charOffset, + attr=(args[0].attr, args[1].attr), + kids=[args[2]]) + + +def flattenAST(ast): + class FlattenAST(GenericASTTraversal): + "Flatten an AST into a list of arguments." + + def __init__(self, ast): + GenericASTTraversal.__init__(self, ast) + self.postorder() + self.argList = ast.argList + + def n_defval(self, node): + node.argList = (node.attr[0], node.attr[1], node.charOffset) + + def n_defvals(self, node): + node.argList = [node.kids[0].argList] + if node.attr: + node.argList = node.argList + [node.attr] + node.kids[1].argList + + def n_arg(self, node): + if node.attr[0] == "optarg": + node.argList = node.attr + node.kids[0].argList + else: + node.argList = tuple(node.attr + [node.charOffset]) + + def n_arglist(self, node): + node.argList = [node.kids[0].argList] + if len(node.kids) == 2: + node.argList = node.argList + node.kids[1].argList + + def n_decl(self, node): + node.argList = [(node.attr[0], node.attr[1], node.charOffset)] + if node.kids != []: + node.argList = node.argList + node.kids[0].argList + + def default(self, node): + raise ParseError, \ + ('Internal error -- node type "%s" was unexpected' % node.type, + 1+node.charOffset) + + return FlattenAST(ast).argList + + +def checkArgList(argList): + "Raise an error if any problems are detected with the given argument list." + + def getFormals(sublist): + "Return the formal-parameter numbers in the order in which they appear." + if sublist == []: + return [] + head = sublist[0] + headval = [] + if head[0] == "argument": + headval = [(int(head[1][1:]), head[2])] + elif head[0][0] == "#": + headval = [(int(head[0][1:]), head[2])] + elif head[0] == "optarg": + headval = getFormals(head[2:]) + return headval + getFormals(sublist[1:]) + + # Ensure the formals appear in strict increasing order. + formals = getFormals(argList) + prevformal = 0 + for form, pos in formals: + if form != prevformal + 1: + raise ParseError, \ + ("Expected parameter %d but saw parameter %d" % (prevformal+1, form), 1+pos) + prevformal = form + + # Ensure that "*" appears at most once at the top level. + seenStar = False + for arg in argList: + if arg[0] == "rawtext" and arg[1] == "*": + if seenStar: + raise ParseError, \ + ("Only one star parameter is allowed", arg[2]) + seenStar = True + + # Ensure that no optional argument contains more than nine formals. + for arg in argList: + if arg[0] == "optarg": + optFormals = 0 + for oarg in arg[2:]: + if oarg[0][0] == "#": + optFormals += 1 + if optFormals > 9: + raise ParseError, \ + ("An optional argument can contain at most nine formals", + oarg[2]) + + # Ensure that "#" is used only where it's allowed. + for arg in argList: + if arg[0] in ["rawtext", "quoted"]: + hashidx = string.find(arg[1], "#") + if hashidx == 0 or (hashidx > 0 and arg[1][hashidx-1] != "\\"): + if arg[0] == "quoted": + hashidx += 1 + raise ParseError, \ + ('The "#" character cannot be used as a literal character unless escaped with "\\"', + arg[2] + hashidx) + elif arg[0] == "optarg": + for oarg in arg[2:]: + if oarg[0] in ["rawtext", "quoted"]: + hashidx = string.find(oarg[1], "#") + if hashidx == 0 or (hashidx > 0 and oarg[1][hashidx-1] != "\\"): + if oarg[0] == "quoted": + hashidx += 1 + raise ParseError, \ + ('The "#" character cannot be used as a literal character unless escaped with "\\"', + oarg[2] + hashidx) + + +class LaTeXgenerator(): + "Generate LaTeX code from a list of arguments." + + def __init__(self): + "Initialize all of LaTeXgenerator's instance variables." + self.argList = [] # List of arguments provided to generate() + self.topLevelName = "???" # Base macro name + self.haveStar = False # True=need to define \ifNAME@star + self.haveAt = False # True=need to use \makeatletter...\makeatother + self.numFormals = 0 # Total number of formal arguments + self.codeList = [] # List of lines of code to output + + def toRoman(self, num): + "Convert a decimal number to roman." + dec2rom = [("m", 1000), + ("cm", 900), + ("d", 500), + ("cd", 400), + ("c", 100), + ("xc", 90), + ("l", 50), + ("xl", 40), + ("x", 10), + ("ix", 9), + ("v", 5), + ("iv", 4), + ("i", 1)] + romanStr = "" + if num > 4000: + raise ParseError, ("Too many arguments", 0) + for rom, dec in dec2rom: + while num >= dec: + romanStr += rom + num -= dec + return romanStr + + def partitionArgList(self): + "Group arguments, one per macro to generate." + self.argGroups = [] + argIdx = 1 + + # Specially handle the first group because it's limited by + # \newcomand's semantics. + group = [] + if len(self.argList) == 1: + # No arguments whatsoever + self.argGroups.append(group) + return + arg = self.argList[argIdx] + if arg[0] == "optarg" and arg[1] == ("[", "]") and len(arg) == 3: + group.append(arg) + argIdx += 1 + while len(group) < 9 and argIdx < len(self.argList) and self.argList[argIdx][0] == "argument": + group.append(self.argList[argIdx]) + argIdx += 1 + self.argGroups.append(group) + + # Handle the remaining groups, each ending before an optional + # argument. + group = [] + numFormals = 0 + for arg in self.argList[argIdx:]: + if arg[0] == "rawtext": + # Treat "*" as an optional argument. + if arg[1] == "*": + if group != []: + self.argGroups.append(group) + group = [] + numFormals = 0 + group.append(arg) + elif arg[0] == "quoted": + group.append(arg) + elif arg[0] == "argument": + group.append(arg) + numFormals += 1 + if numFormals == 9: + if group != []: + self.argGroups.append(group) + group = [] + numFormals = 0 + elif arg[0] == "optarg": + # Note that we know from checkArgList() that there are + # no more than 10 formals specified within the + # optional argument. + if group != []: + self.argGroups.append(group) + group = [] + numFormals = 0 + optarg = arg[0:2] + for oarg in arg[2:]: + if oarg[0] in ["rawtext", "quoted"]: + optarg.append(oarg) + elif oarg[0][0] == "#": + numFormals += 1 + optarg.append(oarg) + else: + optarg.append(oarg) + group.append(optarg) + if group != []: + self.argGroups.append(group) + + def argsToString(self, argList, mode, argSubtract=0): + ''' + Produce a string version of a list of arguments. + mode is one of "define", "call", or "calldefault". + argSubtract is subtracted from each argument number. + ''' + if mode not in ["define", "call", "calldefault"]: + raise ParseError, ('Internal error (mode="%s")' % mode, argList[0][2]) + argStr = "" + findArgRE = re.compile('#(\d+)') + for arg in argList: + if arg[0] == "argument": + if mode == "define": + argStr += "#%d" % (int(arg[1][1:]) - argSubtract) + else: + argStr += "{#%d}" % (int(arg[1][1:]) - argSubtract) + elif arg[0] == "rawtext": + argStr += arg[1] + elif arg[0] == "quoted": + argStr += arg[1][1:-1] + elif arg[0] == "optarg": + argStr += arg[1][0] + for oarg in arg[2:]: + if oarg[0][0] == "#": + if mode == "define": + argStr += "#%d" % (int(oarg[0][1:]) - argSubtract) + elif mode == "call": + argStr += "{#%d}" % (int(oarg[0][1:]) - argSubtract) + else: + if self.numFormals > 9: + argStr += findArgRE.sub(lambda a: "\\"+self.topLevelName+"@arg@"+self.toRoman(int(a.group(0)[1:])), + oarg[1]) + else: + argStr += oarg[1] + elif oarg[0] == "quoted": + argStr += oarg[1][1:-1] + elif oarg[0] == "rawtext": + argStr += oarg[1] + else: + raise ParseError, ('Internal error ("%s")' % oarg[0], + oarg[2]) + argStr += arg[1][1] + else: + raise ParseError, ('Internal error ("%s")' % arg[0], arg[2]) + return argStr + + def callMacro(self, macroNum): + "Return an array of strings suitable for calling macro macroNum." + if macroNum >= len(self.argGroups): + # No more macros. + return [] + macroName = "\\%s@%s" % (self.topLevelName, self.toRoman(macroNum)) + nextArg = self.argGroups[macroNum][0] + callSeq = [] + if self.numFormals > 9: + # More than nine formal parameters + if nextArg[0] == "optarg": + callSeq.append(" \\@ifnextchar%s{%s}{%s%s}%%" % \ + (nextArg[1][0], macroName, macroName, + self.argsToString([nextArg], mode="calldefault"))) + elif nextArg[0] == "rawtext" and nextArg[1] == "*": + callSeq.append(" \\@ifstar{\\%s@startrue%s*}{\\%s@starfalse%s*}%%" % \ + (self.topLevelName, macroName, + self.topLevelName, macroName)) + else: + callSeq.append(" %s" % macroName) + else: + # Nine or fewer formal parameters + argStr = "" + for g in range(0, macroNum): + argStr += self.argsToString(self.argGroups[g], mode="call") + if nextArg[0] == "optarg": + callSeq.append(" \\@ifnextchar%s{%s%s}{%s%s%s}%%" % \ + (nextArg[1][0], + macroName, argStr, macroName, argStr, + self.argsToString([nextArg], mode="calldefault"))) + elif nextArg[0] == "rawtext" and nextArg[1] == "*": + callSeq.append(" \\@ifstar{\\%s@startrue%s%s*}{\\%s@starfalse%s%s*}%%" % \ + (self.topLevelName, macroName, argStr, + self.topLevelName, macroName, argStr)) + else: + callSeq.append(" %s%s%%" % (macroName, argStr)) + return callSeq + + def putCodeHere(self): + 'Return an array of strings representing "Put code here".' + code = [] + if self.haveStar: + code.extend([" \\if%s@star" % self.topLevelName, + ' % Put code for the "*" case here.', + " \\else", + ' % Put code for the non-"*" case here.', + " \\fi", + " %% Put code common to both cases here (and/or above the \\if%s@star)." % self.topLevelName]) + else: + code.append(" % Put your code here.") + if self.numFormals == 0: + return code + if self.numFormals > 9: + firstArgName = "\\%s@arg@i" % self.topLevelName + lastArgName = "\\%s@arg@%s" % (self.topLevelName, self.toRoman(self.numFormals)) + else: + firstArgName = "#1" + lastArgName = "#%d" % self.numFormals + if self.numFormals == 1: + code.append(" %% You can refer to the argument as %s." % firstArgName) + elif self.numFormals == 2: + code.append(" %% You can refer to the arguments as %s and %s." % (firstArgName, lastArgName)) + else: + code.append(" %% You can refer to the arguments as %s through %s." % (firstArgName, lastArgName)) + return code + + def produceTopLevel(self): + "Generate the code for the top-level macro definition." + # Generate the macro definition. + defStr = "\\newcommand{\\%s}" % self.topLevelName + argList = self.argGroups[0] + if argList != []: + defStr += "[%d]" % len(argList) + firstArg = argList[0] + if firstArg[0] == "optarg": + defVal = firstArg[2][1][1:-1] + if string.find(defVal, "]") != -1: + defVal = "{%s}" % defVal + defStr += "[%s]" % defVal + defStr += "{%" + self.codeList.append(defStr) + + # Generate the macro body. + if len(self.argGroups) == 1: + # Single macro definition. + self.codeList.extend(self.putCodeHere()) + else: + # More macros are forthcoming. + if self.numFormals > 9: + # More than nine formal parameters + for f in range(1, len(argList)+1): + self.codeList.append(" \\def\\%s@arg@%s{#%d}%%" % (self.topLevelName, self.toRoman(f), f)) + self.codeList.extend(self.callMacro(1)) + self.codeList.append("}") + + def produceRemainingMacros(self): + "Generate code for all macros except the first." + formalsSoFar = len(self.argGroups[0]) + for groupNum in range(1, len(self.argGroups)): + # Generate the macro header. + self.codeList.append("") + argList = self.argGroups[groupNum] + defStr = "\\def\\%s@%s" % (self.topLevelName, self.toRoman(groupNum)) + if self.numFormals > 9: + defStr += self.argsToString(argList, mode="define", argSubtract=formalsSoFar) + else: + for g in range (0, groupNum+1): + defStr += self.argsToString(self.argGroups[g], mode="define") + self.codeList.append(defStr + "{%") + + # Generate the macro body. + if self.numFormals > 9: + # More than nine formal parameters + for arg in argList: + if arg[0] == "argument": + formalNum = int(arg[1][1:]) + self.codeList.append(" \\def\\%s@arg@%s{#%d}%%" % \ + (self.topLevelName, + self.toRoman(formalNum), + formalNum - formalsSoFar)) + elif arg[0] == "optarg": + for oarg in arg[2:]: + if oarg[0][0] == "#": + formalNum = int(oarg[0][1:]) + self.codeList.append(" \\def\\%s@arg@%s{#%d}%%" % \ + (self.topLevelName, + self.toRoman(formalNum), + formalNum - formalsSoFar)) + if groupNum == len(self.argGroups) - 1: + self.codeList.extend(self.putCodeHere()) + else: + self.codeList.extend(self.callMacro(groupNum + 1)) + else: + # Nine or fewer formal parameters. + if groupNum == len(self.argGroups) - 1: + self.codeList.extend(self.putCodeHere()) + else: + self.codeList.extend(self.callMacro(groupNum + 1)) + + # Generate the macro trailer. + self.codeList.append("}") + + # Increment the count of formals seen so far. + for arg in argList: + if arg[0] == "argument": + formalsSoFar += 1 + elif arg[0] == "optarg": + formalsSoFar += len(filter(lambda o: o[0][0] == "#", arg[2:])) + + def generate(self, argList): + "Generate LaTeX code from an argument list." + # Group arguments and identify characteristics that affect the output. + self.argList = argList + self.partitionArgList() + self.haveAt = len(self.argGroups) > 1 + self.haveStar = filter(lambda arg: arg[0]=="rawtext" and arg[1]=="*", self.argList) != [] + self.topLevelName = self.argList[0][1] + for arg in self.argList: + if arg[0] == "argument": + self.numFormals += 1 + elif arg[0] == "optarg": + for oarg in arg[2:]: + if oarg[0][0] == "#": + self.numFormals += 1 + + # Output LaTeX code. + if self.haveAt: + self.codeList.append("\\makeatletter") + if self.haveStar: + self.codeList.append("\\newif\\if%s@star" % self.topLevelName) + self.produceTopLevel() + self.produceRemainingMacros() + if self.haveAt: + self.codeList.append("\\makeatother") + for codeLine in self.codeList: + print codeLine + + +# The buck starts here. +if __name__ == '__main__': + import sys + import string + + def processLine(): + "Parses the current value of oneLine." + global oneLine + try: + sys.stdout.softspace = 0 # Cancel the %$#@! space. + oneLine = string.strip(oneLine) + if oneLine=="" or oneLine[0]=="%": + return + if not isStdin: + print prompt, oneLine + scanner = CmdScanner() + parser = CmdParser() + tokens = scanner.tokenize(oneLine) + ast = parser.parse(tokens) + argList = flattenAST(ast) + checkArgList(argList) + gen = LaTeXgenerator() + gen.generate(argList) + except ParseError,(message, pos): + sys.stderr.write((" "*(len(prompt)+pos)) + "^\n") + sys.stderr.write("%s: %s.\n" % (sys.argv[0], message)) + if isStdin: + print "" + + sys.setrecursionlimit(5000) + prompt = "% Prototype:" + if len(sys.argv) <= 1: + isStdin = 1 + print prompt + " ", + while 1: + oneLine = sys.stdin.readline() + if not oneLine: + break + processLine() + print prompt + " ", + else: + isStdin = 0 + oneLine = string.join(sys.argv[1:]) + processLine() diff --git a/Master/texmf-dist/doc/latex/newcommand/newcommand.tex b/Master/texmf-dist/doc/latex/newcommand/newcommand.tex new file mode 100644 index 00000000000..4f4265c2376 --- /dev/null +++ b/Master/texmf-dist/doc/latex/newcommand/newcommand.tex @@ -0,0 +1,742 @@ +\documentclass{ltxdoc} +\usepackage[T1]{fontenc} +\usepackage{tabularx} +\usepackage{syntax} +\usepackage{varioref} +\usepackage{color} +\usepackage{booktabs} +\usepackage{alltt} +\usepackage{textcomp} +\usepackage[bookmarksopen=true]{hyperref} + +% Define this document's metadata. +\def\ncfileversion{2.0} +\def\ncfiledate{2010/06/01} + +\title{The \textsf{newcommand.py} utility\thanks{\textsf{newcommand.py} + has version number \ncfileversion, last revised \ncfiledate.}} +\author{\href{mailto:scott+nc@pakin.org}{Scott Pakin} \\ + \href{mailto:scott+nc@pakin.org}{scott+nc@pakin.org}} +\date{\ncfiledate} +\hypersetup{% + pdftitle={The newcommand.py utility}, + pdfauthor={Scott Pakin <scott+nc@pakin.org>}, + pdfsubject={Creating user-defined macros with more flexible argument + processing}, + pdfkeywords={LaTeX macros, optional arguments, newcommand, parenthesized + arguments, starred commands, Python} +} + +% Help prevent weird line breaks in URLs +\def\UrlBreaks{} +\def\UrlBigBreaks{\do/} + +% Define some useful shortcuts. +\newcommand*{\ncpy}{\texttt{newcommand.py}} % The name of the script +\newcommand*{\usercmd}[1]{\textcolor{blue}{#1}} % User command entry +\newcommand{\prototype}[1]{{% % Prompt and user entry + \bigskip + \noindent + \ttfamily\small\raggedright + \hangafter=1\hangindent=4em + ~~~~\% Prototype:~\textcolor{blue}{#1}\strut\par + \vspace*{-0.5\baselineskip}% +}} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\begin{document} +\maketitle +\sloppy + +\begin{abstract} + \LaTeX's \cs{newcommand} is fairly limited in the way it processes + optional arguments, but the \TeX\ alternative, a batch of \cs{def}s + and \cs{futurelet}s, can be overwhelming to the casual \LaTeX\ user. + \ncpy\ is a Python program that automatically generates + \LaTeX\ macro definitions for macros that require more powerful + argument processing than \cs{newcommand} can handle. \ncpy\ is + intended for \LaTeX\ advanced beginners (i.e., those who know how to + use \cs{newcommand} but not internal \LaTeXe\ macros like + |\@ifnextchar|) and for more advanced users who want to save some + typing when defining complex macros. +\end{abstract} + +\section{Introduction} + +\LaTeX's \cs{newcommand} is a rather limited way to define new macros. +Only one argument can be designated as optional, it must be the first +argument, and it must appear within square brackets. Defining macros +that take multiple optional arguments or in which an optional argument +appears in the middle of the argument list is possible but well beyond +the capabilities of the casual \LaTeX\ user. It requires using +\TeX\ primitives such as \cs{def} and \cs{futurelet} and/or +\LaTeXe\ internal macros such as \cs{@ifnextchar}. + +\ncpy\ is a Python program that reads a specification of an argument +list and automatically produces \LaTeX\ code that processes the +arguments appropriately. \ncpy\ makes it easy to define +\LaTeX\ macros with more complex parameter parsing than is possible +with \cs{newcommand} alone. Note that you do need to have Python +installed on your system to run \ncpy. Python is freely available for +download from \url{http://www.python.org/}. + +To define a \LaTeX\ macro, one gives \ncpy\ a macro description +written in a simple specification language. The description +essentially lists the required and optional arguments and, for each +optional argument, the default value. The next section of this +document describes the syntax and provides some examples, but for now, +let's look at how one would define the most trivial macro possible, +one that takes no arguments. Enter the following at your operating +system's prompt: + +\begin{alltt} + \usercmd{newcommand.py "MACRO trivial"} +\end{alltt} + +\noindent +(Depending on your system, you may need to prefix that command with +``|python|''.) The program should output the following \LaTeX\ code +in response: + +\begin{verbatim} + % Prototype: MACRO trivial + \newcommand{\trivial}{% + % Put your code here. + } +\end{verbatim} + +\noindent +Alternatively, you can run \ncpy\ interactively, entering macro +descriptions at the ``|% Prototype:|'' prompt: + +\prototype{MACRO trivial} +\begin{verbatim} + \newcommand{\trivial}{% + % Put your code here. + } + % Prototype: +\end{verbatim} + +\noindent +Enter your operating system's end-of-file character (Ctrl-D in Unix or +Ctrl-Z in Windows) to exit the program. + +While you certainly don't need \ncpy\ to write macros that are as +trivial as \cs{trivial}, the previous discussion shows how to run the +program and the sort of output that you should expect. There will +always be a ``\texttt{Put your code here}'' comment indicating where +you should fill in the actual macro code. At that location, all of +the macro's parameters---both optional and required---will be defined +and can be referred to in the ordinary way: |#1|, |#2|, |#3|, etc. + + +\section{Usage} + +As we saw in the previous section, macros are defined by the word +``|MACRO|'' followed by the macro name, with no preceding backslash. +In this section we examine how to specify increasingly sophisticated +argument processing using \ncpy. + + +\subsection{Required arguments} + +Required arguments are entered as~|#1|, |#2|, |#3|, \dots, with no +surrounding braces: + +\prototype{MACRO required \#1 \#2 \#3 \#4 \#5} +\begin{verbatim} + \newcommand{\required}[5]{% + % Put your code here. + % You can refer to the arguments as #1 through #5. + } +\end{verbatim} + +Parameters must be numbered in monotonically increasing order, +starting with~|#1|. Incorrectly ordered parameters will produce an +error message: + +\prototype{MACRO required \#1 \#3 \#4} +\begin{verbatim} + ^ + newcommand.py: Expected parameter 2 but saw parameter 3. +\end{verbatim} + + +\subsection{Optional arguments} + +Optional arguments\label{par:optional-args} are written as either +``|OPT[|\meta{param}|=|\linebreak[1]|{|\meta{default}|}]|'' or +``|OPT(|\meta{param}|=|\linebreak[1]|{|\meta{default}|})|''. In the +former case, square brackets are used to offset the optional argument; +in the latter case, parentheses are used. \meta{param} is the +parameter number (|#1|, |#2|, |#3|, \dots), and \meta{default} is the +default value for that parameter. Note that curly braces are required +around \meta{default}. + +\prototype{MACRO optional OPT[\#1=\{maybe\}]} +\begin{verbatim} + \newcommand{\optional}[1][maybe]{% + % Put your code here. + % You can refer to the argument as #1. + } +\end{verbatim} + +Up to this point, the examples have been so simple that \ncpy\ is +overkill for entering them. We can now begin specifying constructs +that \LaTeX's \cs{newcommand} can't handle, such as a parenthesized +optional argument, an optional argument that doesn't appear at the +beginning of the argument list, and multiple optional arguments: + +\prototype{MACRO parenthesized OPT(\#1=\{abc\})} +\begin{verbatim} + \makeatletter + \newcommand{\parenthesized}{% + \@ifnextchar({\parenthesized@i}{\parenthesized@i({abc})}% + } + + \def\parenthesized@i(#1){% + % Put your code here. + % You can refer to the argument as #1. + } + \makeatother +\end{verbatim} + + +\prototype{MACRO nonbeginning \#1 OPT[\#2=\{abc\}]} +\begin{verbatim} + \makeatletter + \newcommand{\nonbeginning}[1]{% + \@ifnextchar[{\nonbeginning@i{#1}}{\nonbeginning@i{#1}[{abc}]}% + } + + \def\nonbeginning@i#1[#2]{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +\prototype{MACRO multiple OPT[\#1=\{abc\}] OPT[\#2=\{def\}]} +\begin{verbatim} + \makeatletter + \newcommand{\multiple}[1][abc]{% + \@ifnextchar[{\multiple@i[{#1}]}{\multiple@i[{#1}][{def}]}% + } + + \def\multiple@i[#1][#2]{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +The template for optional arguments that was +shown~\vpageref[above]{par:optional-args} stated that optional +arguments contain a ``\meta{param}|={|\meta{default}|}|'' +specification. In fact, optional arguments can contain +\emph{multiple} ``\meta{param}|={|\meta{default}|}|'' specifications, +as long as they are separated by literal text: + +\prototype{MACRO multiopt OPT(\#1=\{0\},\#2=\{0\})} +\begin{verbatim} + \makeatletter + \newcommand{\multiopt}{% + \@ifnextchar({\multiopt@i}{\multiopt@i({0},{0})}% + } + + \def\multiopt@i(#1,#2){% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +\noindent +In that example, \cs{multiopt} takes an optional parenthesized +argument. If omitted, it defaults to |(0,0)|. If provided, the +argument must be of the form ``|(|\meta{x}|,|\meta{y}|)|''. In either +case, the comma-separated values within the parentheses are parsed +into~|#1| and~|#2|. Contrast that with the following: + +\prototype{MACRO multiopt OPT(\#1=\{0,0\})} +\begin{verbatim} + \makeatletter + \newcommand{\multiopt}{% + \@ifnextchar({\multiopt@i}{\multiopt@i({0,0})}% + } + + \def\multiopt@i(#1){% + % Put your code here. + % You can refer to the argument as #1. + } + \makeatother +\end{verbatim} + +The optional argument still defaults to |(0,0)|, but |#1| receives +\emph{all} of the text that lies between the parentheses; +\cs{multiopt} does not parse it into two comma-separated values +in~|#1| and~|#2|, as it did in the previous example. + +\bigskip + +The \meta{default} text in an |OPT| term can reference any macro +parameter introduced before the |OPT|\@. Hence, the following defines +a macro that accepts a required argument followed by an optional +argument. The default value of the optional argument is the value +provided for the required argument: + +\prototype{MACRO paramdefault \#1 OPT[\#2=\{\#1\}]} +\begin{verbatim} + \makeatletter + \newcommand{\paramdefault}[1]{% + \@ifnextchar[{\paramdefault@i{#1}}{\paramdefault@i{#1}[{#1}]}% + } + + \def\paramdefault@i#1[#2]{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + + +\subsection{Literal text} + +In addition to required and optional parameters, it is also possible +to specify text that must appear literally in the macro call. Merely +specify it within curly braces: + +\prototype{MACRO textual \#1 \{ and \} \#2 \{.\}} +\begin{verbatim} + \makeatletter + \newcommand{\textual}[1]{% + \textual@i{#1}% + } + + \def\textual@i#1 and #2.{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +\noindent +A macro such as \cs{textual} can be called like this: + +\begin{verbatim} + \textual {Milk} and {cookies}. +\end{verbatim} + +\noindent +Actually, in that example, because both |Milk| and |cookies| are +delimited on the right by literal text, \TeX\ can figure out how to +split \cs{textual}'s argument into~|#1| and~|#2| even if the curly +braces are omitted: + +\begin{verbatim} + \textual Milk and cookies. +\end{verbatim} + +\subsection{Starred macros} + +The names of some \LaTeX\ macros can be followed by an optional +``|*|'' to indicate a variation on the normal processing. For +example, \cs{vspace}, which introduces a given amount of vertical +space, discards the space if it appears at the top of the page. +|\vspace*|, in contrast, typesets the space no matter where it +appears. \ncpy\ makes it easy for users to define their own starred +commands: + +\prototype{MACRO starred * \#1 \#2} +\begin{verbatim} + \makeatletter + \newif\ifstarred@star + \newcommand{\starred}{% + \@ifstar{\starred@startrue\starred@i*}{\starred@starfalse\starred@i*}% + } + + \def\starred@i*#1#2{% + \ifstarred@star + % Put code for the "*" case here. + \else + % Put code for the non-"*" case here. + \fi + % Put code common to both cases here (and/or above the \ifstarred@star). + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +\noindent +Note that unlike the generated code shown up to this point, the code +for starred macros includes \emph{multiple} placeholders for user +code. + +The ``|*|'' in a starred macro does not have to immediately follow the +macro name; it can appear anywhere in the macro specification. +However, \ncpy\ currently limits macros to at most one asterisk. + +Embedding an asterisk within curly braces causes it to be treated not +as an optional character but as (required) literal text. Contrast the +preceding example with the following one: + +\prototype{MACRO starred \{*\} \#1 \#2} +\begin{verbatim} + \makeatletter + \newcommand{\starred}{% + \starred@i% + } + + \def\starred@i*#1#2{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +The asterisk in that definition of \cs{starred} must be included in +every macro invocation or \TeX\ will abort with a ``\texttt{Use of + \string\starred@i doesn't match its definition}'' error. + + +\subsection{More than nine arguments} + +\TeX\ imposes a limit of nine arguments per macro. Internally, +``|#|'' is expected to be followed by exactly one digit, which means +that ``|#10|'' refers to argument~|#1| followed by the character~|0|. +Fortunately, it's rare that a macro needs more than nine arguments and +rarer still that those arguments are not better specified as a list of +\meta{key}|=|\meta{value} pairs, as supported by the \textsf{keyval} +package and many other \LaTeX\ packages. + +If large numbers of arguments are in fact necessary, \ncpy\ does let +you specify them. The trick that the generated code uses is to split +the macro into multiple macros, each of which takes nine or fewer +arguments and stores the value of each argument in a variable that can +later be accessed. Because digits are awkward to use in macro names, +\ncpy\ uses roman numerals to name arguments in the case of more than +nine arguments: |\|\meta{name}|@arg@i| for |#1|, +|\|\meta{name}|@arg@ii| for |#2|, |\|\meta{name}|@arg@iii| for |#3|, +|\|\meta{name}|@arg@iv| for |#4|, and so forth. The following example +takes 14 required arguments and one optional argument (which defaults +to the string ``|etc|''): + +\prototype{MACRO manyargs \#1 \#2 \#3 \#4 \#5 \#6 \#7 \#8 \#9 \#10 + \#11 \#12 \#13 \#14 OPT[\#15=\{etc\}]} +\begin{verbatim} + \makeatletter + \newcommand{\manyargs}[9]{% + \def\manyargs@arg@i{#1}% + \def\manyargs@arg@ii{#2}% + \def\manyargs@arg@iii{#3}% + \def\manyargs@arg@iv{#4}% + \def\manyargs@arg@v{#5}% + \def\manyargs@arg@vi{#6}% + \def\manyargs@arg@vii{#7}% + \def\manyargs@arg@viii{#8}% + \def\manyargs@arg@ix{#9}% + \manyargs@i + } + + \def\manyargs@i#1#2#3#4#5{% + \def\manyargs@arg@x{#1}% + \def\manyargs@arg@xi{#2}% + \def\manyargs@arg@xii{#3}% + \def\manyargs@arg@xiii{#4}% + \def\manyargs@arg@xiv{#5}% + \@ifnextchar[{\manyargs@ii}{\manyargs@ii[{etc}]}% + } + + \def\manyargs@ii[#1]{% + \def\manyargs@arg@xv{#1}% + % Put your code here. + % You can refer to the arguments as \manyargs@arg@i through \manyargs@arg@xv. + } + \makeatother +\end{verbatim} + +The current version of \ncpy\ is limited to 4000 arguments, which +should be more than enough for most purposes. + + +\subsection{Summary} + +A macro is defined in \ncpy\ with: + +\begin{center} + \texttt{MACRO} \meta{name} \meta{arguments} +\end{center} + +\noindent +in which \meta{name} is the name of the macro, and \meta{arguments} is +zero or more of the following: + +\begin{center} +\renewcommand{\arraystretch}{1.1} +\begin{tabularx}{\linewidth}{@{}lXl@{}} + \toprule + \multicolumn{1}{@{}c}{Argument} & + \multicolumn{1}{c}{Meaning} & + \multicolumn{1}{c@{}}{Example} \\ + \midrule + + |#|\meta{number} & Parameter (required) & |#1| \\ + \marg{text} & Literal text (required) & |{+}| \\ + |OPT[#|\meta{number}|=|\marg{text}|]| & Parameter (optional, with default) & + |OPT[#1={tbp}]| \\ + |OPT(#|\meta{number}|=|\marg{text}|)| & Same as the above, but with + parentheses instead of brackets & |OPT(#1={tbp})| \\ + |*| & Literal asterisk (optional) & |*| \\ + \bottomrule +\end{tabularx} +\end{center} + +Within an |OPT| argument, |#|\meta{number}|=|\marg{text} can be +repeated any number of times, as long as the various instances are +separated by literal text. + + +\section{Further examples} + +\subsection{Mimicking \LaTeX's \texttt{picture} environment} + +The \LaTeX\ |picture| environment takes two, parenthesized, +coordinate-pair arguments, the second pair being optional. Here's how +to define a macro that takes the same arguments as the |picture| +environment and parses them into $x_1$, $y_1$, $x_2$, and~$y_2$ +(i.e.,~|#1|--|#4|): + +\prototype{MACRO picturemacro \{(\}\#1\{,\}\#2\{)\} OPT(\#3=\{0\},\#4=\{0\})} +\begin{verbatim} + \makeatletter + \newcommand{\picturemacro}{% + \picturemacro@i% + } + + \def\picturemacro@i(#1,#2){% + \@ifnextchar({\picturemacro@ii({#1},{#2})}{\picturemacro@ii({#1},{#2})({0},{0})}% + } + + \def\picturemacro@ii(#1,#2)(#3,#4){% + % Put your code here. + % You can refer to the arguments as #1 through #4. + } + \makeatother +\end{verbatim} + +The first pair of parentheses and the comma are quoted because they +represent required, literal text. + + +\subsection{Mimicking \LaTeX's \texttt{\string\parbox} macro} + +\LaTeX's \cs{parbox} macro takes three optional arguments and two +required arguments. Furthermore, the third argument defaults to +whatever value was specified for the first argument. This is easy to +express in \LaTeX\ with the help of \ncpy: + +\prototype{MACRO parboxlike OPT[\#1=\{s\}] OPT[\#2=\{\string\relax\}] + OPT[\#3=\{\#1\}] \#4 \#5} +\begin{verbatim} + \makeatletter + \newcommand{\parboxlike}[1][s]{% + \@ifnextchar[{\parboxlike@i[{#1}]}{\parboxlike@i[{#1}][{\relax}]}% + } + + \def\parboxlike@i[#1][#2]{% + \@ifnextchar[{\parboxlike@ii[{#1}][{#2}]}{\parboxlike@ii[{#1}][{#2}][{#1}]}% + } + + \def\parboxlike@ii[#1][#2][#3]#4#5{% + % Put your code here. + % You can refer to the arguments as #1 through #5. + } + \makeatother +\end{verbatim} + + +\subsection{Dynamically changing argument formats} +\label{sec:dynamic-args} + +With a little cleverness, it is possible for a macro to accept one of +two completely different sets of arguments based on the values +provided for earlier arguments. For example, suppose we want to +define a macro, \cs{differentargs} that can be called as either + +\begin{verbatim} + \differentargs*[optarg]{reqarg} +\end{verbatim} + +\noindent +or + +\begin{verbatim} + \differentargs{reqarg}(optarg) +\end{verbatim} + +\noindent +That is, the presence of an asterisk determines whether +\cs{differentargs} should expect an optional argument in square +brackets followed by a required argument or to expect a required +argument followed by an optional argument in parentheses. + +The trick is to create two helper macros: one for the ``|*|'' case +(\cs{withstar}) and the other for the non-``|*|'' case +(\cs{withoutstar}). \cs{differentargs} can then invoke one of +\cs{withstar} or \cs{withoutstar} based on whether or not it sees an +asterisk. The following shows how to use \ncpy\ to define +\cs{differentargs}, \cs{withstar}, and \cs{withoutstar} and how to +edit \cs{differentargs} to invoke its helper macros: + +\prototype{MACRO differentargs *} +\begin{verbatim} + \makeatletter + \newif\ifdifferentargs@star + \newcommand{\differentargs}{% + \@ifstar{\differentargs@startrue\differentargs@i*} + {\differentargs@starfalse\differentargs@i*}% + } +\end{verbatim} +\begingroup +\small +\begin{alltt} + \cs{def}\cs{differentargs@i}*\{% + \cs{ifdifferentargs@star} + % Put code for the "*" case here. + \colorbox{yellow}{\cs{let}\cs{next}=\cs{withstar}} + \cs{else} + % Put code for the non-"*" case here. + \colorbox{yellow}{\cs{let}\cs{next}=\cs{withoutstar}} + \cs{fi} + % Put code common to both cases here (and/or above the \cs{ifdifferentargs@star}). + \colorbox{yellow}{\cs{next}} + \} + \cs{makeatother} +\end{alltt} +\endgroup + +\prototype{MACRO withstar OPT[\#1=\{starry\}] \#2} +\begin{verbatim} + \newcommand{\withstar}[2][starry]{% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } +\end{verbatim} + +\prototype{MACRO withoutstar \#1 OPT(\#2=\{dark\})} +\begin{verbatim} + \makeatletter + \newcommand{\withoutstar}[1]{% + \@ifnextchar({\withoutstar@i{#1}}{\withoutstar@i{#1}({dark})}% + } + + \def\withoutstar@i#1(#2){% + % Put your code here. + % You can refer to the arguments as #1 and #2. + } + \makeatother +\end{verbatim} + +Note that we edited \cs{differentargs@i} to let \cs{next} be +equivalent to either \cs{withstar} or \cs{withoutstar} based on +whether an asterisk was encountered. \cs{next} is evaluated outside +of the \cs{ifdifferentargs@star}\dots\linebreak[0]\cs{fi} control +structure. This rigmarole is necessary because directly calling +\cs{withstar} or \cs{withoutstar} would cause those macros to see +\cs{ifdifferentargs@star}'s \cs{else} or \cs{fi} as their first +argument when they ought to see the text following the +\cs{differentargs} call. + + +\section{Grammar} + +The following is the formal specification of \ncpy's grammar, written +in a more-or-less top-down manner. Literal values, shown in a +typewriter font, are case-sensitive. \meta{letter} refers to a letter +of the (English) alphabet. \meta{digit} refers to a digit. + +\setlength{\grammarindent}{7em} +\begin{grammar} +<decl> ::= \[[ "MACRO" <ident> <arglist> \]] + +<ident> ::= \[[ \begin{rep} <letter> \end{rep} \]] + +<arglist> ::= \[[ \begin{rep} \\ <arg> \end{rep} \]] + +<arg> ::= \[[ + \begin{stack} + <formal> \\ + <quoted> \\ + <optarg> \\ + "*" + \end{stack} + \]] + +<formal> ::= \[[ "#" \begin{rep} <digit> \end{rep} \]] + +<quoted> ::= \[[ "{" <rawtext> "}" \]] + +<rawtext> ::= \[[ \begin{rep} \tok{anything except a "{", "}", or "\#"} \end{rep} \]] + +<optarg> ::= \[[ "OPT" <delim> <defvals> <delim> \]] + +<delim> ::= \[[ \begin{stack} "[" \\ "]" \\ "(" \\ ")" \end{stack} \]] + +<defvals> ::= \[[ + \begin{rep} + <defval> \\ + \begin{stack} + <quoted> \\ + <rawtext> + \end{stack} + \end{rep} + \]] + +<defval> ::= \[[ <formal> "=" <quoted> \]] +\end{grammar} + + +\section{Acknowledgements} + +I'd like to say thank you to the following people: + +\begin{itemize} + \item John Aycock for writing the + \href{http://pages.cpsc.ucalgary.ca/~aycock/spark/}{Scanning, + Parsing, and Rewriting Kit (SPARK)}---the lexer and parser + underlying \ncpy---and making it freely available and + redistributable. + + \item Hendri Adriaens for pointing out a bug in the code generated + by \ncpy. Previously, bracketed text within a mandatory argument + could be mistaken for an optional argument. + + \item Tom Potts for reporting a spurious error message caused by the + processing of |OPT|. This bug has now been fixed. Tom Potts also + proposed the example used in Section~\ref{sec:dynamic-args} in + which the starred and unstarred versions of a macro take different + arguments. +\end{itemize} + + +\section{Copyright and license} + +Copyright~\copyright{} 2010, Scott Pakin + +\bigskip + +This package may be distributed and/or modified under the conditions +of the \LaTeX{} Project Public License, either version~1.3c of this +license or (at your option) any later version. The latest version of +this license is in: + +\begin{center} + \url{http://www.latex-project.org/lppl.txt} +\end{center} + +\noindent +and version~1.3c or later is part of all distributions of \LaTeX{} +version 2006/05/20 or later. + +\end{document} diff --git a/Master/texmf-dist/doc/latex/newcommand/spark.py b/Master/texmf-dist/doc/latex/newcommand/spark.py new file mode 100644 index 00000000000..ffe9b4bf7c1 --- /dev/null +++ b/Master/texmf-dist/doc/latex/newcommand/spark.py @@ -0,0 +1,566 @@ +# Copyright (c) 1998-2000 John Aycock +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__version__ = 'SPARK-0.6.1' + +import re +import sys +import string + +def _namelist(instance): + namelist, namedict, classlist = [], {}, [instance.__class__] + for c in classlist: + for b in c.__bases__: + classlist.append(b) + for name in dir(c): + if not namedict.has_key(name): + namelist.append(name) + namedict[name] = 1 + return namelist + +class GenericScanner: + def __init__(self): + pattern = self.reflect() + self.re = re.compile(pattern, re.VERBOSE) + + self.index2func = {} + for name, number in self.re.groupindex.items(): + self.index2func[number-1] = getattr(self, 't_' + name) + + def makeRE(self, name): + doc = getattr(self, name).__doc__ + rv = '(?P<%s>%s)' % (name[2:], doc) + return rv + + def reflect(self): + rv = [] + for name in _namelist(self): + if name[:2] == 't_' and name != 't_default': + rv.append(self.makeRE(name)) + + rv.append(self.makeRE('t_default')) + return string.join(rv, '|') + + def error(self, s, pos): + print "Lexical error at position %s" % pos + raise SystemExit + + def tokenize(self, s): + pos = 0 + n = len(s) + while pos < n: + m = self.re.match(s, pos) + if m is None: + self.error(s, pos) + + groups = m.groups() + for i in range(len(groups)): + if groups[i] and self.index2func.has_key(i): + self.index2func[i](groups[i]) + pos = m.end() + + def t_default(self, s): + r'( . | \n )+' + pass + +class GenericParser: + def __init__(self, start): + self.rules = {} + self.rule2func = {} + self.rule2name = {} + self.collectRules() + self.startRule = self.augment(start) + self.ruleschanged = 1 + + _START = 'START' + _EOF = 'EOF' + + # + # A hook for GenericASTBuilder and GenericASTMatcher. + # + def preprocess(self, rule, func): return rule, func + + def addRule(self, doc, func): + rules = string.split(doc) + + index = [] + for i in range(len(rules)): + if rules[i] == '::=': + index.append(i-1) + index.append(len(rules)) + + for i in range(len(index)-1): + lhs = rules[index[i]] + rhs = rules[index[i]+2:index[i+1]] + rule = (lhs, tuple(rhs)) + + rule, fn = self.preprocess(rule, func) + + if self.rules.has_key(lhs): + self.rules[lhs].append(rule) + else: + self.rules[lhs] = [ rule ] + self.rule2func[rule] = fn + self.rule2name[rule] = func.__name__[2:] + self.ruleschanged = 1 + + def collectRules(self): + for name in _namelist(self): + if name[:2] == 'p_': + func = getattr(self, name) + doc = func.__doc__ + self.addRule(doc, func) + + def augment(self, start): + # + # Tempting though it is, this isn't made into a call + # to self.addRule() because the start rule shouldn't + # be subject to preprocessing. + # + startRule = (self._START, ( start, self._EOF )) + self.rule2func[startRule] = lambda args: args[0] + self.rules[self._START] = [ startRule ] + self.rule2name[startRule] = '' + return startRule + + def makeFIRST(self): + union = {} + self.first = {} + + for rulelist in self.rules.values(): + for lhs, rhs in rulelist: + if not self.first.has_key(lhs): + self.first[lhs] = {} + + if len(rhs) == 0: + self.first[lhs][None] = 1 + continue + + sym = rhs[0] + if not self.rules.has_key(sym): + self.first[lhs][sym] = 1 + else: + union[(sym, lhs)] = 1 + changes = 1 + while changes: + changes = 0 + for src, dest in union.keys(): + destlen = len(self.first[dest]) + self.first[dest].update(self.first[src]) + if len(self.first[dest]) != destlen: + changes = 1 + + # + # An Earley parser, as per J. Earley, "An Efficient Context-Free + # Parsing Algorithm", CACM 13(2), pp. 94-102. Also J. C. Earley, + # "An Efficient Context-Free Parsing Algorithm", Ph.D. thesis, + # Carnegie-Mellon University, August 1968, p. 27. + # + + def typestring(self, token): + return None + + def error(self, token): + print "Syntax error at or near `%s' token" % token + raise SystemExit + + def parse(self, tokens): + tree = {} + tokens.append(self._EOF) + states = { 0: [ (self.startRule, 0, 0) ] } + + if self.ruleschanged: + self.makeFIRST() + + for i in xrange(len(tokens)): + states[i+1] = [] + + if states[i] == []: + break + self.buildState(tokens[i], states, i, tree) + + #_dump(tokens, states) + + if i < len(tokens)-1 or states[i+1] != [(self.startRule, 2, 0)]: + del tokens[-1] + self.error(tokens[i-1]) + rv = self.buildTree(tokens, tree, ((self.startRule, 2, 0), i+1)) + del tokens[-1] + return rv + + def buildState(self, token, states, i, tree): + needsCompletion = {} + state = states[i] + predicted = {} + + for item in state: + rule, pos, parent = item + lhs, rhs = rule + + # + # A -> a . (completer) + # + if pos == len(rhs): + if len(rhs) == 0: + needsCompletion[lhs] = (item, i) + + for pitem in states[parent]: + if pitem is item: + break + + prule, ppos, pparent = pitem + plhs, prhs = prule + + if prhs[ppos:ppos+1] == (lhs,): + new = (prule, + ppos+1, + pparent) + if new not in state: + state.append(new) + tree[(new, i)] = [(item, i)] + else: + tree[(new, i)].append((item, i)) + continue + + nextSym = rhs[pos] + + # + # A -> a . B (predictor) + # + if self.rules.has_key(nextSym): + # + # Work on completer step some more; for rules + # with empty RHS, the "parent state" is the + # current state we're adding Earley items to, + # so the Earley items the completer step needs + # may not all be present when it runs. + # + if needsCompletion.has_key(nextSym): + new = (rule, pos+1, parent) + olditem_i = needsCompletion[nextSym] + if new not in state: + state.append(new) + tree[(new, i)] = [olditem_i] + else: + tree[(new, i)].append(olditem_i) + + # + # Has this been predicted already? + # + if predicted.has_key(nextSym): + continue + predicted[nextSym] = 1 + + ttype = token is not self._EOF and \ + self.typestring(token) or \ + None + if ttype is not None: + # + # Even smarter predictor, when the + # token's type is known. The code is + # grungy, but runs pretty fast. Three + # cases are looked for: rules with + # empty RHS; first symbol on RHS is a + # terminal; first symbol on RHS is a + # nonterminal (and isn't nullable). + # + for prule in self.rules[nextSym]: + new = (prule, 0, i) + prhs = prule[1] + if len(prhs) == 0: + state.append(new) + continue + prhs0 = prhs[0] + if not self.rules.has_key(prhs0): + if prhs0 != ttype: + continue + else: + state.append(new) + continue + first = self.first[prhs0] + if not first.has_key(None) and \ + not first.has_key(ttype): + continue + state.append(new) + continue + + for prule in self.rules[nextSym]: + # + # Smarter predictor, as per Grune & + # Jacobs' _Parsing Techniques_. Not + # as good as FIRST sets though. + # + prhs = prule[1] + if len(prhs) > 0 and \ + not self.rules.has_key(prhs[0]) and \ + token != prhs[0]: + continue + state.append((prule, 0, i)) + + # + # A -> a . c (scanner) + # + elif token == nextSym: + #assert new not in states[i+1] + states[i+1].append((rule, pos+1, parent)) + + def buildTree(self, tokens, tree, root): + stack = [] + self.buildTree_r(stack, tokens, -1, tree, root) + return stack[0] + + def buildTree_r(self, stack, tokens, tokpos, tree, root): + (rule, pos, parent), state = root + + while pos > 0: + want = ((rule, pos, parent), state) + if not tree.has_key(want): + # + # Since pos > 0, it didn't come from closure, + # and if it isn't in tree[], then there must + # be a terminal symbol to the left of the dot. + # (It must be from a "scanner" step.) + # + pos = pos - 1 + state = state - 1 + stack.insert(0, tokens[tokpos]) + tokpos = tokpos - 1 + else: + # + # There's a NT to the left of the dot. + # Follow the tree pointer recursively (>1 + # tree pointers from it indicates ambiguity). + # Since the item must have come about from a + # "completer" step, the state where the item + # came from must be the parent state of the + # item the tree pointer points to. + # + children = tree[want] + if len(children) > 1: + child = self.ambiguity(children) + else: + child = children[0] + + tokpos = self.buildTree_r(stack, + tokens, tokpos, + tree, child) + pos = pos - 1 + (crule, cpos, cparent), cstate = child + state = cparent + + lhs, rhs = rule + result = self.rule2func[rule](stack[:len(rhs)]) + stack[:len(rhs)] = [result] + return tokpos + + def ambiguity(self, children): + # + # XXX - problem here and in collectRules() if the same + # rule appears in >1 method. But in that case the + # user probably gets what they deserve :-) Also + # undefined results if rules causing the ambiguity + # appear in the same method. + # + sortlist = [] + name2index = {} + for i in range(len(children)): + ((rule, pos, parent), index) = children[i] + lhs, rhs = rule + name = self.rule2name[rule] + sortlist.append((len(rhs), name)) + name2index[name] = i + sortlist.sort() + list = map(lambda (a,b): b, sortlist) + return children[name2index[self.resolve(list)]] + + def resolve(self, list): + # + # Resolve ambiguity in favor of the shortest RHS. + # Since we walk the tree from the top down, this + # should effectively resolve in favor of a "shift". + # + return list[0] + +# +# GenericASTBuilder automagically constructs a concrete/abstract syntax tree +# for a given input. The extra argument is a class (not an instance!) +# which supports the "__setslice__" and "__len__" methods. +# +# XXX - silently overrides any user code in methods. +# + +class GenericASTBuilder(GenericParser): + def __init__(self, AST, start): + GenericParser.__init__(self, start) + self.AST = AST + + def preprocess(self, rule, func): + rebind = lambda lhs, self=self: \ + lambda args, lhs=lhs, self=self: \ + self.buildASTNode(args, lhs) + lhs, rhs = rule + return rule, rebind(lhs) + + def buildASTNode(self, args, lhs): + children = [] + for arg in args: + if isinstance(arg, self.AST): + children.append(arg) + else: + children.append(self.terminal(arg)) + return self.nonterminal(lhs, children) + + def terminal(self, token): return token + + def nonterminal(self, type, args): + rv = self.AST(type) + rv[:len(args)] = args + return rv + +# +# GenericASTTraversal is a Visitor pattern according to Design Patterns. For +# each node it attempts to invoke the method n_<node type>, falling +# back onto the default() method if the n_* can't be found. The preorder +# traversal also looks for an exit hook named n_<node type>_exit (no default +# routine is called if it's not found). To prematurely halt traversal +# of a subtree, call the prune() method -- this only makes sense for a +# preorder traversal. Node type is determined via the typestring() method. +# + +class GenericASTTraversalPruningException: + pass + +class GenericASTTraversal: + def __init__(self, ast): + self.ast = ast + + def typestring(self, node): + return node.type + + def prune(self): + raise GenericASTTraversalPruningException + + def preorder(self, node=None): + if node is None: + node = self.ast + + try: + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + except GenericASTTraversalPruningException: + return + + for kid in node: + self.preorder(kid) + + name = name + '_exit' + if hasattr(self, name): + func = getattr(self, name) + func(node) + + def postorder(self, node=None): + if node is None: + node = self.ast + + for kid in node: + self.postorder(kid) + + name = 'n_' + self.typestring(node) + if hasattr(self, name): + func = getattr(self, name) + func(node) + else: + self.default(node) + + + def default(self, node): + pass + +# +# GenericASTMatcher. AST nodes must have "__getitem__" and "__cmp__" +# implemented. +# +# XXX - makes assumptions about how GenericParser walks the parse tree. +# + +class GenericASTMatcher(GenericParser): + def __init__(self, start, ast): + GenericParser.__init__(self, start) + self.ast = ast + + def preprocess(self, rule, func): + rebind = lambda func, self=self: \ + lambda args, func=func, self=self: \ + self.foundMatch(args, func) + lhs, rhs = rule + rhslist = list(rhs) + rhslist.reverse() + + return (lhs, tuple(rhslist)), rebind(func) + + def foundMatch(self, args, func): + func(args[-1]) + return args[-1] + + def match_r(self, node): + self.input.insert(0, node) + children = 0 + + for child in node: + if children == 0: + self.input.insert(0, '(') + children = children + 1 + self.match_r(child) + + if children > 0: + self.input.insert(0, ')') + + def match(self, ast=None): + if ast is None: + ast = self.ast + self.input = [] + + self.match_r(ast) + self.parse(self.input) + + def resolve(self, list): + # + # Resolve ambiguity in favor of the longest RHS. + # + return list[-1] + +def _dump(tokens, states): + for i in range(len(states)): + print 'state', i + for (lhs, rhs), pos, parent in states[i]: + print '\t', lhs, '::=', + print string.join(rhs[:pos]), + print '.', + print string.join(rhs[pos:]), + print ',', parent + if i < len(tokens): + print + print 'token', str(tokens[i]) + print |