% % % \iffalse Meta-comment % %<*driver> \documentclass{tcldoc} \DeclareRobustCommand{\package}[1]{\textsf{#1}} \newcommand{\Alpha}{\textit{Alpha}} \newcommand{\AlphaTk}{\textit{Alphatk}} \PageIndex \setcounter{IndexColumns}{2} \CodelineNumbered \RecordChanges \IndexPrologue{\section*{Index}\markboth{Index}{Index}% Numbers written in italic refer to the page where the corresponding entry is described; numbers underlined refer to the definition; numbers in roman refer to the pages where the entry is used.} \expandafter\def \expandafter\GlossaryParms \expandafter{\GlossaryParms \hbadness=10000\emergencystretch=2cm} \begin{document} \DocInput{dtxload.dtx} \PrintChanges \PrintIndex \end{document} % % \fi % % \title{\package{dtxload} --- loading \Tcllogo\ code directly from % \texttt{.dtx} source files} % \author{Lars Hellstr\"om^^A % \thanks{E-mail: \texttt{Lars.Hellstrom@math.umu.se}}} % \date{December 20, 2000} % \maketitle % % \begin{abstract} % \package{dtxload} is a feature for the text editor \Alpha. It % implements a command for loading a piece of \Tcllogo\ code directly % from a \texttt{.dtx} file. % \end{abstract} % % \tableofcontents % % \section{Usage} % % \subsection{About \Alpha} % % About \Alpha\ itself, the \Alpha~v\,7.3 manual says the following: % \begin{quotation} % \Alpha\ is a very powerful, multi-purpose text editor. % \Alpha\ is multi-modal, which means it switches between different % modes depending on which type of document you are editing. Everybody % who has used Emacs knows about this concept. The idea with different % modes is to change \Alpha's behaviour depending on which kind of text % you're editing. For example if you're programming in C$^{++}$ or % editing a \LaTeX\ document you want different features. % % \Alpha\ is very customizable. Much of \Alpha's functionality is % implemented in `packages' or `extensions'. [\dots] % \Alpha\ uses Dr.~Ousterhout's \emph{Tool Command Language} (\Tcllogo) as % an extension language. Many of the functions bound to keystrokes, % as well as many of the functions in the menus, are written in \Tcllogo. % All the `packages' or `extensions' just mentioned are all written % in \Tcllogo. Anybody who wants can write new packages for \Alpha! % % Of course, you don't have to learn \Tcllogo\ to use \Alpha. You can % have lots of use of \Alpha\ without knowing anything about \Tcllogo. % But if you want to become a real power user, you should consider % trying it out. Then the possibilities of customizing \Alpha\ are % unlimited. % \end{quotation} % Originally \Alpha\ only existed on the \mbox{MacOS} platform, but % recently the program was rewritten in Tk.\footnote{Tk is an extension of % \Tcllogo\ which includes GUI support.} In that form (which is called % \AlphaTk), \Alpha\ can be run on any platform for which there exists % a port of Tk! % % \medskip % % There are also some \Alpha-related URLs which should be mentioned. % \Alpha's official home page is~\cite{AlphaHome}, but it is currently % rather out-of-date. More recent information can be found on % e.g.~\cite{DocScriptingAlpha}, which also has links to many other % \Alpha-related sites. The home page for \AlphaTk\ is~\cite{Alphatk}. % % % % \subsection{Installation} % % To install \package{dtxload} as an \Alpha\ feature, you first \LaTeX\ % the file \texttt{dtxload.ins}---this will generate a file % \texttt{dtxload.tcl}, which contains the \Tcllogo\ code for the % package in a form that \Alpha\ understands. Then follow the normal % procedure for installing an \Alpha\ package, i.e., open % \texttt{dtxload.tcl}\footnote{Which should be somewhere outside % \Alpha's directory subtree.} in \Alpha---this will trigger the % automatic installation dialog for the package and you proceed as % usual. % % % \subsection{Loading \Tcllogo\ code} % \label{Ssec:Loading} % % To load (evaluate) a piece of \Tcllogo\ code in a \texttt{.dtx} % document, simply select that code and press \marginpar{Loading code: % \textsf{Cmd--L}}Command--L. The commands are evaluated one by one and % the result of each command will be shown at the current log destination % (by default the status line, but that can be changed). In case one of % the commands return with an error, that error will be shown as well, % evaluation of further commands is cancelled, and the insertion point is % moved to the beginning of the erroneous command. Furthermore the mark % will be left at the end of the selection, so that the part of the % original selection which has not yet been successfully evaluated can % easily be reselected using the \textsf{Mark Hilite} command. % % Loading \Tcllogo\ code does not necessary mean all text in the % selection will be evaluated; only extractable code will be passed on % to the \Tcllogo\ interpreter. There are several factors which affect % whether a piece of text is extractable code or not, but the foremost % is that text on \package{docstrip} comment lines (i.e., lines which % begin by a `|%|' that is not immediately followed by a `|<|') is % \emph{not} extractable code. Secondly, if the selection contains a % \package{docstrip} guard line which marks the start of a module then % \package{dtxload} will ask you whether you want the code in that % module to be loaded. If you answer no, it naturally won't be. % % Thus far the factors which may restrict which text is extractable % code correspond exactly to what the \package{docstrip} % program~\cite{docstrip} might do when generating a file of extracted % code, but \package{dtxload} has two additional mechanisms which may % categorize text as ``not extractable code''. Both are however % controlled by preferences in the \Alpha\ \TeX\ mode, so they can be % switched off if you don't like them. % The first is the \describestring[var.][]{onlyLoadDtxFiles} % \textsf{Only Load Dtx Files} preference, which when on restricts % loading to files with suffix \texttt{.dtx}. For \TeX\ files with other % suffixes I suspect that Command--L is pressed more often by accident % than because there actually is some \Tcllogo\ code that should be % loaded \textellipsis\space The second mechanism is controlled by the % \describestring[var.][]{onlyLoadTclEnvs} \textsf{Only Load Tcl Envs} % preference, which when on adds the restriction that extractable code % must appear inside a \texttt{tcl} or \texttt{tcl*} environment % (defined by the \package{tcldoc} \LaTeX\ package~\cite{tcldoc}). This % is primarily useful when a \texttt{.dtx} file contains both \Tcllogo\ % code in \texttt{tcl} environments and code in some other language in % e.g.\ \texttt{macrocode} environments, since if the preference had been % off then \package{dtxload} would stop at the \package{docstrip} guard % that must appear at the beginning of each non-\Tcllogo\ code section % and ask whether the code should be included, whereas if it is on then % \package{dtxload} doesn't even have to ask whether the code in the % \texttt{macrocode} environments should be loaded. % % \medskip % % As an example of how the load \Tcllogo\ code command works, consider % the following code: % \DontCheckModules\iffalse %<*example> %< append a B % append a C %<*andD> append a D % expr $a string length $a % \end{tcl} % text text text. % \end{macrocode}\iffalse %EXAMPLE % % \fi \CheckModules % If lines 1--7 above are selected and Command--L is pressed, % \package{dtxload} will display the message ``\texttt{Tcl eval OK}'' on % the status line and the following information will have been added to % the log.\footnote{This shows the window log format, but there are % other formats. Furthermore the `\texttt{<}' and `\texttt{>}' will % actually be guillemets in \Alpha\ (but not in \AlphaTk).} % \begin{trivlist}\item\small % | set a A|\\ % |A|\\[\medskipamount] % | append a B|\\ % |AB|\\[\medskipamount] % | append a C|\\ % |ABC| % \end{trivlist} % The || lines show the commands that were evaluated and the % lines after these contain the respective results of these commands. % Before the |append a B| command is evaluated, \package{dtxload} will % stop and ask % \begin{quote} % \textsf{Should \textless andB\textgreater\ modules be included?} % \end{quote} % In the case above the answer given was `Yes'; if it had been `No' then % the |append a B| command would neither be logged nor evaluated, and % the result of the |append a C| command would have been |AC|. % % If one continues by selecting lines 9--12 and pressing Command--L % then \package{dtxload} will display the message ``\texttt{Tcl eval % error}'' on the status line, the insertion point will be moved to the % beginning of line 11, and the following information will be found in % the log: % \begin{trivlist}\item\small % | append a D|\\ % |ABCD|\\[\medskipamount] % | expr $a|\\ % |Error: syntax error in expression "ABCD"| % \end{trivlist} % If then line 11 is changed to |expr {$a}| and lines 11--12 are again % selected (e.g. using \textsf{Mark Hilite}) and loaded then % \package{dtxload} will log: % \begin{trivlist}\item\small % | expr {$a}|\\ % |ABCD|\\[\medskipamount] % | string length $a|\\ % |4| % \end{trivlist} % % Finally, if lines 3--12 are selected and loaded then these are the % questions \package{dtxload} will ask: % \begin{itemize} % \item \textsf{Should \textless andB\textgreater\ modules be included?} % (Possible answers are `Yes' and `No'.) % \item \textsf{Should \textless andD\textgreater\ modules be included?} % (Possible answers are `Yes' and `No'.) % \item \textsf{Module nesting error: \textless*andD\textgreater\ % module ended by \textless/andB\textgreater. For which % guards should the positions be pushed?} % (Possible answers are `None', `Start', `End', and `Both'.) % \end{itemize} % In the last case, \package{dtxload} has noticed that the guard % expressions that began (\textsf{andD}) and ended (\textsf{andB}) a % module are not equal (which is an error). Answering `Both' here will % push the positions of (the beginning of) both guard lines (8 and 10) onto % the bookmark stack so that they can easily be jumped to and corrected % later, but \package{dtxload} will continue evaluating the rest of the % selection after you answer. % % % \subsection{Selecting log destination} % % At startup, \package{dtxload} is logging results on the status line, % i.e., after a command has been evaluated it either displays the message % \begin{quote} % \texttt{Tcl eval OK:\ }\meta{result} % \end{quote} % or % \begin{quote} % \texttt{Tcl eval error:\ }\meta{error message} % \end{quote} % depending on whether the command returned an error or not. The % evaluation log can however be sent to a window instead. % % To set the destination for the log, one uses the procedure % \describestring[proc][dtxload]{logdest}|dtxload::logdest|. To set the % log destination to the window \word{win}, one gives the command % \begin{quote} % |dtxload::logdest window| \word{win} % \end{quote} % When the log destination is a window, the log shows the commands % that were evaluated as well as their result. If there is no window % named \word{win} then it is created. A slight % variation on this is the command % \begin{quote} % |dtxload::logdest shell| \word{win} % \end{quote} % which makes the \word{win} window a \Alpha\ \Tcllogo\ shell window if % it has to create it. When the log destination is a shell window, the % commands that are executed will be saved on the history list as well % as inserted in the window. % % A short form which may be convenient is % \begin{quote} % |dtxload::logdest here| % \end{quote} % which simply makes the current window the log destination. Finally, % the log destination is reset to the status line after the command % \begin{quote} % |dtxload::logdest status| % \end{quote} % Evaluation will also be logged on the status line if the log % destination window is closed. % % % \subsection{Other features and surprises} % % If nothing is selected when Command--L is pressed, \package{dtxload} % will continue as if all text was selected. % % Like \Alpha's \Tcllogo\ shell, \package{dtxload} can send the commands % to evaluate to a remote \Tcllogo\ interpreter rather than \Alpha's % built-in interpreter. In both cases the choice of target interpreter is % controlled by the global \Alpha\ variable % \describestring[var.][]{evaluateRemotely}|evaluateRemotely|. The % easiest way of changing this variable is via the \Tcllogo-tk submenu % of \Alpha's \Tcllogo\ menu. % % If the \textsf{Only Load Tcl Envs} preference is on and lines 4--6 in % the code example of Subsection~\ref{Ssec:Loading} are loaded then % \package{dtxload} will simply display the message % \begin{quote} % \texttt{No extractable code found.} % \end{quote} % The reason for this is that the first character of the selection is % a |%|; when the first character in the interval to load is a percent % character then \package{dtxload} will guess that this position is not % in a \texttt{tcl} or \texttt{tcl*} environment, and therefore it won't % try to extract any code until it has seen a comment line that contains % |\begin{tcl}| or |\begin{tcl*}|. You can always avoid making a % \package{docstrip} guard the first thing in the selection to load. % % % % \StopEventually{ % \begin{thebibliography}{9} % \bibitem{Alphatk} % Vince Darley: \textit{Alphatk} ({\small WWW} page), % \texttt{http}://\texttt{www.santafe.edu}\slash % \texttt{\~{}vince}\slash \texttt{alphatk}\slash % \texttt{about.html} % \bibitem{DocScriptingAlpha} % Donavan Hall: \textit{Alpha} ({\small WWW} page), % \texttt{http}://\texttt{magnet.fsu.edu}\slash % \texttt{\~{}hall}\slash\texttt{docscripting}\slash % \texttt{alpha}/. % \bibitem{tcldoc} % Lars Hellstr\"om: % \textit{The \package{tcldoc} package and class}, % \texttt{ftp}:/\slash\texttt{ftp.tex.ac.uk}\slash % \texttt{tex-archive}\slash \texttt{macros}\slash % \texttt{latex}\slash \texttt{contrib}\slash % \texttt{supported}\slash \texttt{tcldoc.dtx}. % \bibitem{AlphaHome} % Pete Keleher: \textit{Alpha Home Page} ({\small WWW} page), % \texttt{http}://\texttt{alpha.olm.net}/. % \bibitem{docstrip} % Frank Mittelbach, Denys Duchier, Johannes Braams, Marcin % Woli\'nski, and Mark Wooding: \textit{The \textsf{DocStrip} % program}, The \LaTeX3 Project; % \texttt{ftp}:/\slash\texttt{ftp.tex.ac.uk}\slash % \texttt{tex-archive}\slash \texttt{macros}\slash % \texttt{latex}\slash \texttt{base}\slash \texttt{docstrip.dtx}. % \end{thebibliography} % } % % \section{Implementation} % % % \subsection{Interface to \Alpha} % % The package begins with an |alpha::feature| command to declare itself % to \Alpha. As it is hardly useful outside the \texttt{TeX} mode, it % declares itself as being specific to that mode. % \begin{tcl} %<*tcl> alpha::feature dtxload 1.1 {TeX} { % \end{tcl} % % \begin{variable}{onlyLoadDtxFiles} % \begin{variable}{onlyLoadTclEnvs} % Next comes the initialization script for the feature, which % declares the two preference variables used by \package{dtxload}. % Both variables are flags. |onlyLoadDtxFiles| is |1| if loading of % code is restricted to files with suffix \texttt{.dtx} and |0| if % there is no restriction. |onlyLoadTclEnvs| is |1| if only code % surrounded by a \texttt{tcl} or \texttt{tcl*} environment is loaded % and |0| if there is no such restriction. Both preferences are by % default |1|. % \begin{tcl} # When set, the Load Tcl Code command will be disabled in # TeX files whose suffix is not dtx. newPref flag onlyLoadDtxFiles 1 TeX # When set, the Load Tcl Code command will only extract code # from code lines in tcl and tcl* environments. newPref flag onlyLoadTclEnvs 1 TeX % \end{tcl} % \end{variable}\end{variable} % \begin{variable}{loadTclCode} % The initialization script also declares a keybinding preference (to % the |dtxload::load| procedure), which is used for % \package{dtxload}'s only command. The default value is Command--L % and the binding is specific to the \texttt{TeX} mode. % \begin{tcl} # This is the key binding for dtxload's Load Tcl Code command. newPref binding loadTclCode modules be included?"]"}] } set dtxload::known_expressions($e) } % \end{tcl} % \end{proc} % % \begin{proc}{push_module} % The |dtxload::push_module| procedure pushes a block module onto the % module stack, but it does not modify the value of % |dtxload::module_included|. The syntax of |dtxload::push_module| is % \begin{quote} % |dtxload::push_module| \word{expression} \word{position} % \end{quote} % where \word{expression} is the expression string and % \word{position} is the position where the guard begins. % \begin{tcl} proc dtxload::push_module {e p} { global dtxload::module_stack lappend dtxload::module_stack [list $e $p] } % \end{tcl} % \end{proc} % % \begin{proc}{pop_module} % The |dtxload::pop_module| procedure pops a block module off the % module stack and checks that the modules are properly nested. Its % syntax is % \begin{quote} % |dtxload::pop_module| \word{expression} \word{position} % \end{quote} % where \word{expression} is the expression string of the module to % pop and \word{position} is the position where the (end of module) % guard begins. % % If the module nesting is incorrect then the user is presented with % a dialog with buttons for pushing the positions of the incorrect % modules. It is not an error if the module stack is empty, since it % is perfectly reasonable that the beginning of the code to evaluate % is in the middle of a module. % \begin{tcl} proc dtxload::pop_module {e p} { global dtxload::module_stack dtxload::module_included\ dtxload::next_module_idx set len [llength ${dtxload::module_stack}] if {$len==0} then {return} set L [lindex ${dtxload::module_stack} [expr {$len-1}]] if {[lindex $L 0] != $e} then { switch [buttonAlert "Module nesting error: <*[lindex $L 0]>\ module ended by . For which guards should the positions\ be pushed?" None Start End Both]\ { Start {dtxload::push_bookmarks [lindex $L 1]} End {dtxload::push_bookmarks $p} Both {dtxload::push_bookmarks [lindex $L 1] $p} } } set dtxload::module_stack\ [lreplace ${dtxload::module_stack} end end] if {$len>=${dtxload::next_module_idx}} then { incr dtxload::next_module_idx -1 } if {[llength $L]>2} then { set dtxload::module_included [lindex $L 2] } } % \end{tcl} % \end{proc} % % \begin{proc}{push_bookmarks} % The |dtxload::push_bookmarks| procedure pushes one or several % positions in the current window onto the bookmarks stack. The % arguments are the positions to push. % \begin{tcl} proc dtxload::push_bookmarks {args} { global markStack markName foreach pos $args { set name mark$markName incr markName createTMark $name $pos set fileName [win::Current] set markStack [linsert $markStack 0 [list $fileName $name]] } } % \end{tcl} % \end{proc} % % \begin{variable}{module_included} % \begin{variable}{in_tcl_env} % The |dtxload::module_included| variable keeps track of whether the % current position is in an included module. It is |1| if the current % position should be included and |0| if it shouldn't. % The |dtxload::in_tcl_env| variable is used for keeping track of % what markup context surrounds the currently considered line in the % file. It is set to |1| when a |\begin{tcl}| or |\begin{tcl*}| is % scanned, and set to |0| when a |\end{tcl}| or |\end{tcl*}| is scanned. % \end{variable}\end{variable} % % \begin{proc}{update_included} % Simply pushing a module onto the module stack does not update the % |dtxload:|\penalty\hyphenpenalty|:module_included| variable; that is % instead done by the |dtxload::update_included| procedure, which % should be called right before the value of |dtxload::module_included| % is used. The reason for this arrangement is that it avoids evaluating % guard expressions whose values do not matter. % % |dtxload::update_included| has no arguments. % \begin{tcl} proc dtxload::update_included {} { global dtxload::module_included dtxload::module_stack\ dtxload::next_module_idx while {${dtxload::module_included} &&\ ${dtxload::next_module_idx}<[llength ${dtxload::module_stack}]}\ { set L [lindex ${dtxload::module_stack} ${dtxload::next_module_idx}] lappend L ${dtxload::module_included} set dtxload::module_stack [lreplace ${dtxload::module_stack}\ ${dtxload::next_module_idx} ${dtxload::next_module_idx} $L] incr dtxload::next_module_idx set dtxload::module_included [dtxload::eval_guard [lindex $L 0]] } } % \end{tcl} % \end{proc} % % % \begin{proc}{extract_line} % The |dtxload::extract_line| procedure steps forward in the file one % line at a time until it has found a codeline that can be extracted or % it reaches the end of the part of the file from which code is to be % extracted. Its syntax is % \begin{quote} % |dtxload::extract_line| \word{startpos} \word{endpos} % \end{quote} % Here \word{startpos} is the position at which it should start to % look for an extractable code line, and \word{endpos} is the % position at which the search should stop if no such line is found. % \word{startpos} is assumed to be at the beginning of a line (for % identification of code, comment, and guard line purposes) but no % test for this is made. % % The return value of |dtxload::extract_line| is a list with the % structure % \begin{quote} % \word{nextpos} \word{line}\regopt\ \word{foundpos}\regopt % \end{quote} % \word{nextpos} is the first position in the file after the part that % was searched. \word{line} is the extracted codeline and % \word{foundpos} is the position at which it began, but these two items % are missing if there was no more codeline. % % The overall structure of |dtxload::extract_line| is a loop % which steps through the lines until it reaches the |endpos|. When a % line to extract is actually found the procedure returns from within % the loop using the |return| command. The local variable |pos| % stores the current position, and |pos2| stores the position of the % beginning of the next line. % \begin{tcl} proc dtxload::extract_line {startpos endpos} { global dtxload::in_tcl_env onlyLoadTclEnvs dtxload::module_included for {set pos $startpos} {[pos::compare {$pos<$endpos}]}\ {set pos $pos2} { % \end{tcl} % Inside the loop, the next line is extracted and stored in the local % variable |line|. Then |switch| is used for classifying the line and % taking appropriate actions. % \begin{tcl} set pos2 [nextLineStart $pos] if {$pos2>$endpos} then {set pos2 $endpos} set line [getText $pos $pos2] switch -regexp -- $line { % \end{tcl} % \changes{v\,1.0}{2000/10/15}{Corrected regular expressions; only % percents on the beginning of a line are significant. (LH)} % \changes{v\,1.1}{2000/12/03}{Guard lines will affect code extraction. % (LH)} % \changes{v\,1.1}{2000/12/18}{The code extraction procedures can % now handle \package{docstrip} verbatim mode guards, provided % they do not appear in a block of extractable code. (LH)} % Lines that begin with |%<<| are \package{docstrip} guard lines that % start verbatim mode. Extracting code from such lines is currently % not supported by \package{dtxload}, so if such a guard is ever % encountered the extraction is stopped, but \package{dtxload} can % skip verbatim sections from which it shouldn't extract code. % \begin{tcl} {^%<<} { if {!$onlyLoadTclEnvs || ${dtxload::in_tcl_env}} then { dtxload::update_included if {${dtxload::module_included}} then { alertnote "docstrip verbatim mode guard line\ encountered. Verbatim mode extraction is\ currently not supported, so I stop here." goto $pos return [list $endpos] } } % \end{tcl} % The part of the guard line which comes after the |%<<| is called an % end-tag. Verbatim mode ends with the next line which consists of a % percent character followed by this end-tag, and therefore all lines % up to and including that one are skipped. A technical matter here % is that spaces at the end of the line does not count (\TeX\ cannot % see them), but spaces inside the end-tag do count! % \begin{tcl} set endtag %[string trimright [string trimright\ [string range $line 3 end] \n\r] \ ] while {[string trimright [string trimright $line \n\r] \ ]\ != $endtag} { set pos $pos2 if {[pos::compare {$pos>=$endpos}]}\ then {return [list $pos]} set pos2 [nextLineStart $pos] if {$pos2>$endpos} then {set pos2 $endpos} set line [getText $pos $pos2] } } % \end{tcl} % Lines that begin with |%<*| or |%]+)>} $line foo modifier\ expression]}\ then { if {"[askyesno "Malformed guard line \"$line\"\ encountered. Push position?"]"=="yes"}\ % then {dtxload::push_bookmarks $pos} } elseif {$modifier=="*"} then { dtxload::push_module $expression $pos } else { dtxload::pop_module $expression $pos } } % \end{tcl} % % Other lines that begin with |%<| are also \package{docstrip} % guard lines, but in these cases the lines (i) contain some code % and (ii) the guard only affect the code on that line. There must be % a |>| somewhere on the line, and the part of the line after the |>| % is code that \package{docstrip} can extract. % \begin{tcl} {^%<} { if {![regexp {^%<(-|\+|)([^>]+)>(.*)$} $line\ foo modifier expression code]}\ then { if {"[askyesno "Malformed guard line \"$line\"\ encountered. Push position?"]"=="yes"}\ % then {dtxload::push_bookmarks $pos} } elseif {!$onlyLoadTclEnvs || ${dtxload::in_tcl_env}} then { dtxload::update_included if {${dtxload::module_included}} then { if {[dtxload::eval_guard $expression] !=\ ($modifier=="-")}\ then {return [list $pos2 $code $pos]} } } } % \end{tcl} % Remaining lines that begin with |%| are considered comment lines, % but they could technically be metacomment lines (begin with |%%|) or % a line that \package{docstrip} would process in verbatim mode. In % both these cases \package{docstrip} would copy the line, but as there % is little point in using these \package{docstrip} features for lines % that the \Tcllogo\ interpreter would miss, \package{dtxload} ignores % those subtleties. % \begin{tcl} {^%} { while {[regexp -indices {\\(begin|end) *{tcl\*?}} $line\ match type]} { set dtxload::in_tcl_env\ [expr {"[eval string range \$line $type]"=="begin"}] set line [string range $line\ [expr {[lindex $match 1]+1}] end] } } % \end{tcl} % Finally, all lines that do not begin with a |%| are code lines. % \begin{tcl} default { if {!$onlyLoadTclEnvs || ${dtxload::in_tcl_env}} then { dtxload::update_included if {${dtxload::module_included}}\ % then {return [list $pos2 $line $pos]} } } } } list $endpos } % \end{tcl} % \end{proc} % % % % \subsection{Evaluating the code} % % The code in this subsection is based on the \Alpha\ procedures % |Alpha::evaluate| and |tcltk::evaluate|, but |dtxload::evaluate| % tries to return a bit more information. % % \begin{proc}{evaluate} % The |dtxload::evaluate| procedure takes a script as argument and % evaluates it in the global variable context. Any errors that occur % are caught. The returned value is a list with the structure % \begin{quote} % \word{code} \word{message} % \begin{regexp}[\regopt]\word{errorInfo} % \word{errorCode}\end{regexp} % \end{quote} % \word{code} is the \Tcllogo\ return code, as returned by |catch| % when evaluating the script. \word{message} is the value returned by % the script; it is an error message if an error occurred. % \word{errorInfo} is the value that the global variable |errorInfo| % got after the script was evaluated and \word{errorCode} is the % corresponding value of |errorCode|. % % When the \Alpha\ global variable |evaluateRemotely| is |0| the % evaluation is carried out by \Alpha\ itself, but when % |evaluateRemotely| is |1| the script is instead sent to a separate % \Tcllogo\ shell for evaluation. The current implementation does not % return the values of the |errorInfo| and |errorCode| variables in % that case, which is why they may be absent from the return value of % |dtxload::evaluate|. % \changes{v\,1.0}{2000/10/17}{Moved \texttt{history} interaction to % \texttt{dtxload::shell\_log}. (LH)} % \changes{v\,1.1}{2000/12/05}{Added code for remote evaluation. (LH)} % \begin{tcl} proc dtxload::evaluate {t} { global errorInfo errorCode evaluateRemotely tcl_platform if {!$evaluateRemotely} then { if {[set code [catch {uplevel \#0 $t} msg]] == 1} then { % \end{tcl} % If there was an error, the last part of the value of |errorInfo| % will be due to the |uplevel| command. As that command was not % requested by the user, it would be confusing and it is therefore % best to remove it. % \begin{tcl} set L [split $errorInfo \n] set errorInfo [join [lrange $L 0 [expr {[llength $L] - 4}]] \n] } list $code $msg $errorInfo $errorCode } elseif {$tcl_platform(platform) == "macintosh"} then { % \end{tcl} % On the \mbox{MacOS} platform, the script is sent to a remote % \Tcllogo\ shell via AppleEvents. % \begin{tcl} global tclshSig app::ensureRunning $tclshSig list [catch { aebuild::result -t 30000 '${tclshSig}' misc dosc ----\ [aebuild::TEXT $t] } res]\ $res } else { % \end{tcl} % On the other platforms I don't really know what should be done. The % following code is based on that in |tcltk::evaluate|, but I haven't % been able to test it (help, please?). % \begin{tcl} global tclshInterp if {![info exists tclshInterp]} { if {[catch {tcltk::findTclshInterp}]} { return [list 1 "No shell selected"] } } if {$tcl_platform(platform) == "windows"} { if {[dde services Tk $tclshInterp] == ""} { alertnote "The remote shell has died, please select a new one." unset tclshInterp return [dtxload::evaluate $t] } list [catch {dde execute Tk $tclshInterp $t} res] $res } else { list [catch {send $tclshInterp $t} res] $res } } } % \end{tcl} % \end{proc} % % % \subsection{Logging} % % The code in this subsection is based on the \Alpha\ procedures % |Shel::carriageReturn| and |Alpha::evaluate|, but many details had to % be changed because, amongst other things, the window to which text is % written is not the frontmost one. % % \begin{proc}{message_log} % The |dtxload::message_log| procedure handles logging a script % evaluation on the status line. It takes one argument, which is the % script to evaluate, and returns a boolean value that specifies whether % an error occurred (|0| is no error, |1| is that there was an error). % % Since the status line is rather limited in size there isn't very % much that can be logged on it. |dtxload::message_log| simply displays % the result or error that occurred. % \begin{tcl} proc dtxload::message_log {script} { set res [dtxload::evaluate $script] if {[lindex $res 0]==1} then { message "Tcl eval error: [lindex $res 1]" expr 1 } else { if {[string length [lindex $res 1]] == 0} then { message "Tcl eval OK." } else { message "Tcl eval OK: [lindex $res 1]" } expr 0 } } % \end{tcl} % \end{proc} % % % \begin{proc}{window_log} % \changes{v\,1.1}{2000/12/05}{Renamed procedure (old name was % \texttt{dtxload:\discretionary{}{}{}:shell\_log}). (LH)} % \begin{variable}{log_window_name} % \changes{v\,1.1}{2000/12/05}{Renamed variable (old name was % \texttt{dtxload:\discretionary{}{}{}:shell\_window\_name}). (LH)} % The |dtxload::window_log| procedure handles logging a script % evaluation in a window, which must be open when this procedure is % called. It takes one argument, which is the script to evaluate, and % returns a boolean value that specifies whether an error occurred % (|0| is no error, |1| is that there was an error). % % When it is set the |dtxload::log_window_name| variable holds the % name of the window which is used in logging, and when it isn't set % this means that logging should be in the status line. Initially it % is unset. % \begin{tcl} if {[info exists dtxload::log_window_name]} then { unset dtxload::log_window_name } % \end{tcl} % \end{variable} % % If the log window has mode |Shel| then the |script| is recorded in % the history list, but since the shells have trouble handling history % items that contain more than one line, all newlines in the recorded % script will be replaced by semicolons. This \emph{can} change the % meaning of the script, but hopefully that happens so rarely that one % can live with it. % \changes{v\,1.0}{2000/11/01}{Scripts added to the history list are % converted to one-liners. (LH)} % \begin{tcl} proc dtxload::window_log {script} { upvar #0 dtxload::log_window_name win global win::Modes Shel::startPrompt Shel::endPrompt if {[set win::Modes($win)] == "Shel"} then { global Shel::histnum regsub -all \n [string trimright $script] " ; " text history add $text set Shel::histnum [history nextid] } % \end{tcl} % % Next the script is inserted into (at the end of) the log window. % The lines on which logged scripts are written are marked out using % a special \package{dtxload} prompt, which is constructed as % \begin{quote} % |"${Shel::startPrompt}dtxload${Shel::endPrompt} "| % \end{quote} % If the log window mode is |Shel| and the last thing in the window % is the current prompt for the window, then that prompt is replaced % by the \package{dtxload} prompt, otherwise a newline followed by a % \package{dtxload} prompt will be appended to the window. The result % of a logged script is put on the line after the last line of the % script. % \begin{tcl} set pos [maxPos -w $win] select -w $win $pos $pos set dtxprompt "${Shel::startPrompt}dtxload${Shel::endPrompt} " if {[set win::Modes($win)] != "Shel"} then { insertText -w $win \r$dtxprompt } else { set stdprompt [Alpha::Prompt] set pos2 [pos::math $pos - [string length $stdprompt]] if {[getText -w $win $pos2 $pos] != $stdprompt} then { insertText -w $win \r$dtxprompt } else { replaceText -w $win $pos2 $pos $dtxprompt refresh } } % \end{tcl} % There currently is a bug (it is fixed in \Alpha~8) in % |replaceText| which causes it to always draw in the current window. % The |refresh| restores the proper window contents. % \changes{v\,1.0}{2000/10/16}{\texttt{refresh} inserted to overcome % \texttt{replaceText} bug. (LH)} % % Inserting the actual script text is basically an |insertText|, but % before the script is inserted all newlines in it (except the last) % are replaced by newlines followed by a \package{dtxload} prompt. % Since |dtxload::load| converts all newlines to linefeeds, it is % sufficient to replace linefeeds. It seems that newlines in \Alpha\ % are always carriage returns (regardless of the window's ``platform'' % attribute), and I assume that the \package{dtxload} prompt doesn't % contain any regexp metacharacter. % \changes{v\,1.0}{2000/10/16}{Assuming newlines in the script to log % are linefeeds. (LH)} % \begin{tcl} regsub -all \n [string trimright $script] \r$dtxprompt text insertText -w $win "$text\r" % \end{tcl} % After the script has been logged, it is time to evaluate it and log % the result. % \begin{tcl} set res [dtxload::evaluate $script] if {[lindex $res 0]==1} then { insertText -w $win "Error: [lindex $res 1]\r" if {[llength $res]>2} then { global errorInfo set errorInfo [lindex $res 2] } message "Tcl eval error" } else { if {[string length [lindex $res 1]]>0}\ then {insertText -w $win "[lindex $res 1]\r"} message "Tcl eval OK" } % \end{tcl} % If the window has mode |Shel| then the window's current prompt is % inserted. It is necessary to call |Alpha::Prompt| again because % evaluating the script might have changed the prompt. Then the return % value is finally computed. % \begin{tcl} if {[set win::Modes($win)] == "Shel"}\ then {insertText -w $win [Alpha::Prompt]} expr {[lindex $res 0]==1} } % \end{tcl} % \end{proc} % % % % % \subsection{Putting it all together} % % \begin{proc}{load} % The |dtxload::load| procedure implements the \package{dtxload} user % command---it extracts and evaluates the code in the current % selection (if something is selected) or the entire front window (if % nothing is selected). The evaluation is logged a window or on the % status line depending on the current value of % |dtxload::log_window_name|. % % Code is extracted and evaluated one command (or as close as one gets) % at a time. In case an error occurs the loading is stopped and the % insertion point is moved to the beginning of the problematic script, % whereas the mark is put at the end of the code to load. This way a % simple \textsf{Mark Hilite} will select the part of the code that % remains to be loaded. % % The very first thing |dtxload::load| does is to check whether it is % allowed to load any code in this window. % \begin{tcl} proc dtxload::load {} { global onlyLoadDtxFiles if {$onlyLoadDtxFiles && ![string match *.dtx [win::CurrentTail]]}\ then { message "Not a .dtx file." return } % \end{tcl} % Then the interval from which code should be extracted is % determined. % \begin{tcl} set startpos [getPos] set endpos [selEnd] if {[pos::compare {$startpos == $endpos}]} then { set startpos [minPos] set endpos [maxPos] } % \end{tcl} % Next it is determined what logging method should be used. The name % of the procedure gets stored in the local variable |log|. % \begin{tcl} global dtxload::log_window_name if { [info exists dtxload::log_window_name] && [lsearch -exact [winNames] ${dtxload::log_window_name}]!=-1 } then {set log dtxload::window_log}\ else {set log dtxload::message_log} % \end{tcl} % After that a couple of extraction-related variables are initialized. % The idea behind how the |dtxload::in_tcl_env| variable is set is that % if one is loading a short piece of code inside a \texttt{tcl} % environment then the first line probably doesn't start with a |%|, % whereas if one is loading an entire file (and thus starts in % non-\texttt{tcl}-environment) the first character quite likely is a % |%|. % \begin{tcl} global dtxload::in_tcl_env dtxload::module_stack\ dtxload::next_module_idx dtxload::module_included set dtxload::in_tcl_env\ [expr {[getText $startpos [pos::math {$startpos+1}]] != "%"}] set dtxload::module_stack {} set dtxload::next_module_idx 0 set dtxload::module_included 1 % \end{tcl} % The last piece of initialization concerns the |script| local % variable, in which the extracted lines are collected until they % make up an entire command. The |escaped| variable is a boolean % which is true when the newline at the end of the last line of % |script| was escaped. The |spos| variable stores the position at % which the command currently being collected began, but it is % initialized to |none| here to help distinguishing the case that no % extractable code was found. % \begin{tcl} set script "" set escaped 0 set spos none while {[pos::compare $startpos < $endpos]} { set extract [dtxload::extract_line $startpos $endpos] if {[llength $extract]>1} then { % \end{tcl} % When a line of code has been extracted, it must be appended to % |script|. This isn't as easy as one might imagine, because \Tcllogo\ % code being |source|d (which is what loading must function as) is fed % through a few more conversion steps than code being |eval|ed. In % particular all escaped newlines are converted to spaces and all % other newlines are converted to linefeeds, so that must be done % explicitly. A newline is considered to be escaped if is is preceded % by an odd number of backslashes, and if it is then the last backslash % and the newline are replaced by a single space. If the last line of % |script| ended by an escaped newline then the script is considered % incomplete and more code will be collected before evaluation is % attempted. Evaluation of |script| is also conditioned by that |info\ % complete $script| returns |1|. % \changes{v\,1.0}{2000/10/16}{Converting nonescaped newlines to % linefeeds, since \Tcllogo\ doesn't consider carriage returns to % be command separators. (LH)} % \begin{tcl} if {[string length $script]==0} then { set spos [lindex $extract 2] } set line [lindex $extract 1] if {$escaped} then {set line [string trimleft $line]} set escaped [regsub -all\ "((^|\[^\\\\\])(\\\\\\\\)*)\\\\(\r|\n|\r\n)$" $line {\1 } line] if {!$escaped} then {regsub "\r\n?\$" $line \n line} append script $line if {!$escaped && [info complete $script]} then { if {[$log $script]} then { select $spos $endpos markHilite return } else { set script "" } } } elseif {$spos=="none"} then { message "No extractable code found." } set startpos [lindex $extract 0] } % \end{tcl} % If the |script| variable isn't empty, then try to evaluate whatever % is in it before returning. If not even that causes an error the % |dtxload:|\penalty\hyphenpenalty|:known_expressions| array is unset % so that one can use other options for the next piece of code loaded. % \begin{tcl} if {[string length $script]>0 && [$log $script]} then { select $spos $endpos markHilite } else { global dtxload::known_expressions if {[info exists dtxload::known_expressions]}\ then {unset dtxload::known_expressions} } } % \end{tcl} % \end{proc} % % % \subsection{Setting the log destination} % % \begin{proc}{logdest} % \changes{v\,1.0}{2000/10/16}{Procedure added. (LH)} % \changes{v\,1.1}{2000/12/07}{Procedure renamed from % \texttt{dtxload::redirect} and the syntax was changed. (LH)} % The log destination is set using the |dtxload::logdest| procedure. % The general syntax for this procedure is % \begin{quote} % |dtxload::logdest| \word{keyword} \word{window name}\regopt % \end{quote} % where the \word{keyword} (mainly) specifies the type of log % destination and \word{window name} is the name of the target % window (ignored for some \word{keyword}s). % \begin{tcl} proc dtxload::logdest {type {win "dtxload log"}} { global dtxload::log_window_name switch -exact -- $type { % \end{tcl} % The first form of |dtxload::logdest| has the syntax % \begin{quote} % |dtxload::logdest status| % \end{quote} % It makes the status line the log destination. % \begin{tcl} status { if {[info exists dtxload::log_window_name]}\ then {unset dtxload::log_window_name} } % \end{tcl} % The second form has the syntax % \begin{quote} % |dtxload::logdest here| % \end{quote} % It makes the current window the log destination, regardless of what % may already be in it or which mode it has. % \begin{tcl} here {set dtxload::log_window_name [win::CurrentTail]} % \end{tcl} % The third form has the syntax % \begin{quote} % |dtxload::logdest window| \word{window name} % \end{quote} % It sets the log destination to the window named \word{window name}, % regardless of what may already be in it or which mode it has. If % the window does not already exist it is created. % \begin{tcl} window { if {[lsearch -exact [winNames] $win] == -1} then { new -n $win -shell 1 -text "This is a dtxload log window.\r" } set dtxload::log_window_name $win } % \end{tcl} % The fourth and final form has the syntax % \begin{quote} % |dtxload::logdest shell| \word{window name} % \end{quote} % It calls |Shel::start| to open a \Tcllogo\ shell window and sets the % log destination to that window. % \begin{tcl} shell { Shel::start "Alpha" $win\ "This is a dtxload log window (and Alpha Tcl shell).\r" set dtxload::log_window_name $win } } } % % \end{tcl} % \end{proc} % % % \Finale % \endinput