18 Fangle is a tool for fangled literate programming. Newfangled is defined as New and often needlessly novel by TheFreeDictionary.com.
19 In this case, fangled means yet another not-so-new1. but improved. ^1 method for literate programming.
20 Literate Programming has a long history starting with the great Donald Knuth himself, whose literate programming tools seem to make use of as many escape sequences for semantic markup as TeX (also by Donald Knuth).
21 Norman Ramsey wrote the Noweb set of tools (notangle, noweave and noroots) and helpfully reduced the amount of magic character sequences to pretty much just <<, >> and @, and in doing so brought the wonders of literate programming within my reach.
22 While using the L Y X editor for LaTeX editing I had various troubles with the noweb tools, some of which were my fault, some of which were noweb's fault and some of which were L Y X's fault.
23 Noweb generally brought literate programming to the masses through removing some of the complexity of the original literate programming, but this would be of no advantage to me if the L Y X / LaTeX combination brought more complications in their place.
24 Fangle was thus born (originally called Newfangle) as an awk replacement for notangle, adding some important features, like better integration with L Y X and LaTeX (and later TeXmacs), multiple output format conversions, and fixing notangle bugs like indentation when using -L for line numbers.
25 Significantly, fangle is just one program which replaces various programs in Noweb. Noweave is done away with and implemented directly as LaTeX macros, and noroots is implemented as a function of the untangler fangle.
26 Fangle is written in awk for portability reasons, awk being available for most platforms. A Python version2. hasn't anyone implemented awk in python yet? ^2 was considered for the benefit of L Y X but a scheme version for TeXmacs will probably materialise first; as TeXmacs macro capabilities help make edit-time and format-time rendering of fangle chunks simple enough for my weak brain.
27 As an extension to many literate-programming styles, Fangle permits code chunks to take parameters and thus operate somewhat like C pre-processor macros, or like C++ templates. Name parameters (or even local variables in the callers scope) are anticipated, as parameterized chunks — useful though they are — are hard to comprehend in the literate document.
29 Fangle is licensed under the GPL 3 (or later).
30 This doesn't mean that sources generated by fangle must be licensed under the GPL 3.
31 This doesn't mean that you can't use or distribute fangle with sources of an incompatible license, but it means you must make the source of fangle available too.
32 As fangle is currently written in awk, an interpreted language, this should not be too hard.
34 4a <gpl3-copyright[1](
\v), lang=text> ≡
35 ________________________________________________________________________
36 1 | fangle - fully featured notangle replacement in awk
38 3 | Copyright (C) 2009-2010 Sam Liddicott <sam@liddicott.com>
40 5 | This program is free software: you can redistribute it and/or modify
41 6 | it under the terms of the GNU General Public License as published by
42 7 | the Free Software Foundation, either version 3 of the License, or
43 8 | (at your option) any later version.
45 10 | This program is distributed in the hope that it will be useful,
46 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of
47 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
48 13 | GNU General Public License for more details.
50 15 | You should have received a copy of the GNU General Public License
51 16 | along with this program. If not, see <http://www.gnu.org/licenses/>.
52 |________________________________________________________________________
59 1 Introduction to Literate Programming 11
62 2.2 Extracting roots 13
63 2.3 Formatting the document 13
64 3 Using Fangle with L^ A T_ E X 15
65 4 Using Fangle with L Y X 17
66 4.1 Installing the L Y X module 17
67 4.2 Obtaining a decent mono font 17
71 4.3 Formatting your Lyx document 18
72 4.3.1 Customising the listing appearance 18
73 4.3.2 Global customisations 18
74 4.4 Configuring the build script 19
76 5 Using Fangle with T_ E X_( M A CS) 21
77 6 Fangle with Makefiles 23
78 6.1 A word about makefiles formats 23
79 6.2 Extracting Sources 23
80 6.2.1 Converting from L Y X to L^ A T_ E X 24
81 6.2.2 Converting from T_ E X_( M A CS) 24
82 6.3 Extracting Program Source 25
83 6.4 Extracting Source Files 25
84 6.5 Extracting Documentation 27
85 6.5.1 Formatting T_ E X 27
86 6.5.1.1 Running pdflatex 27
87 6.5.2 Formatting T_ E X_( M A CS) 28
88 6.5.3 Building the Documentation as a Whole 28
90 6.7 Boot-strapping the extraction 29
91 6.8 Incorporating Makefile.inc into existing projects 30
94 7 Fangle awk source code 33
96 7.2 Catching errors 34
97 8 T_ E X_( M A CS) args 35
98 9 L^ A T_ E X and lstlistings 37
99 9.1 Additional lstlstings parameters 37
100 9.2 Parsing chunk arguments 39
101 9.3 Expanding parameters in the text 40
102 10 Language Modes & Quoting 43
104 10.1.1 Modes to keep code together 43
105 10.1.2 Modes affect included chunks 43
106 10.2 Language Mode Definitions 44
109 10.2.3 Parentheses, Braces and Brackets 47
110 10.2.4 Customizing Standard Modes 47
116 10.4 A non-recursive mode tracker 50
117 10.4.1 Constructor 50
120 10.4.3.1 One happy chunk 54
122 10.5 Escaping and Quoting 54
123 11 Recognizing Chunks 57
125 11.1.1 T_ E X_( M A CS) 57
126 11.1.2 lstlistings 58
128 11.2.1 T_ E X_( M A CS) 59
131 11.3.1 lstlistings 60
133 11.4 Chunk contents 61
134 11.4.1 lstlistings 62
135 12 Processing Options 65
136 13 Generating the Output 67
137 13.1 Assembling the Chunks 68
138 13.1.1 Chunk Parts 68
141 16 Fangle LaTeX source code 79
142 16.1 fangle module 79
143 16.1.1 The Chunk style 79
144 16.1.2 The chunkref style 80
146 16.2.1 The chunk command 81
147 16.2.1.1 Chunk parameters 82
148 16.2.2 The noweb styled caption 82
149 16.2.3 The chunk counter 82
150 16.2.4 Cross references 85
152 17 Extracting fangle 87
153 17.1 Extracting from Lyx 87
154 17.2 Extracting documentation 87
155 17.3 Extracting from the command line 88
158 18 Chunk Parameters 91
159 19 Compile-log-lyx 91
161 Chapter 1Introduction to Literate Programming
162 Todo: Should really follow on from a part-0 explanation of what literate programming is.
163 Chapter 2Running Fangle
164 Fangle is a replacement for noweb, which consists of notangle, noroots and noweave.
165 Like notangle and noroots, fangle can read multiple named files, or from stdin.
167 The -r option causes fangle to behave like noroots.
168 fangle -r filename.tex
169 will print out the fangle roots of a tex file.
170 Unlike the noroots command, the printed roots are not enclosed in angle brackets e.g. <<name>>, unless at least one of the roots is defined using the notangle notation <<name>>=.
171 Also, unlike noroots, it prints out all roots --- not just those that are not used elsewhere. I find that a root not being used doesn't make it particularly top level — and so-called top level roots could also be included in another root as well.
172 My convention is that top level roots to be extracted begin with ./ and have the form of a filename.
173 Makefile.inc, discussed in 6, can automatically extract all such sources prefixed with ./
175 notangle's -R and -L options are supported.
176 If you are using L Y X or LaTeX, the standard way to extract a file would be:
177 fangle -R./Makefile.inc fangle.tex > ./Makefile.inc
178 If you are using TeXmacs, the standard way to extract a file would similarly be:
179 fangle -R./Makefile.inc fangle.txt > ./Makefile.inc
180 TeXmacs users would obtain the text file with a verbatim export from TeXmacs which can be done on the command line with texmacs -s -c fangle.tm fangle.txt -q
181 Unlike the noroots command, the -L option to generate C pre-preocessor #file style line-number directives,does not break indenting of the generated file..
182 Also, thanks to mode tracking (described in 10) the -L option does not interrupt (and break) multi-line C macros either.
183 This does mean that sometimes the compiler might calculate the source line wrongly when generating error messages in such cases, but there isn't any other way around if multi-line macros include other chunks.
184 Future releases will include a mapping file so that line/character references from the C compiler can be converted to the correct part of the source document.
185 2.3 Formatting the document
186 The noweave replacement built into the editing and formatting environment for TeXmacs, L Y X (which uses LaTeX), and even for raw LaTeX.
187 Use of fangle with TeXmacs, L Y X and LaTeX are explained the the next few chapters.
188 Chapter 3Using Fangle with LaTeX
189 Because the noweave replacement is impemented in LaTeX, there is no processing stage required before running the LaTeX command. Of course, LaTeX may need running two or more times, so that the code chunk references can be fully calculated.
190 The formatting is managed by a set of macros shown in 16, and can be included with:
191 \usepackage{fangle.sty}
192 Norman Ramsay's origial noweb.sty package is currently required as it is used for formatting the code chunk captions.
193 The listings.sty package is required, and is used for formatting the code chunks and syntax highlighting.
194 The xargs.sty package is also required, and makes writing LaTeX macro so much more pleasant.
195 To do: Add examples of use of Macros
197 Chapter 4Using Fangle with L Y X
198 L Y X uses the same LaTeX macros shown in 16 as part of a L Y X module file fangle.module, which automatically includes the macros in the document pre-amble provided that the fangle L Y X module is used in the document.
199 4.1 Installing the L Y X module
200 Copy fangle.module to your L Y X layouts directory, which for unix users will be ~/.lyx/layouts
201 In order to make the new literate styles availalble, you will need to reconfigure L Y X by clicking Tools->Reconfigure, and then re-start L Y X.
202 4.2 Obtaining a decent mono font
203 The syntax high-lighting features of lstlistings makes use of bold; however a mono-space tt font is used to typeset the listings. Obtaining a bold tt font can be impossibly difficult and amazingly easy. I spent many hours at it, following complicated instructions from those who had spend many hours over it, and was finally delivered the simple solution on the lyx mailing list.
205 The simple way was to add this to my preamble:
207 \renewcommand{\ttdefault}{txtt}
210 The next simplest way was to use ams poor-mans-bold, by adding this to the pre-amble:
212 %\renewcommand{\ttdefault}{txtt}
213 %somehow make \pmb be the command for bold, forgot how, sorry, above line not work
214 It works, but looks wretched on the dvi viewer.
216 The lstlistings documention suggests using Luximono.
217 Luximono was installed according to the instructions in Ubuntu Forums thread 11591811. http://ubuntuforums.org/showthread.php?t=1159181 ^1 with tips from miknight2. http://miknight.blogspot.com/2005/11/how-to-install-luxi-mono-font-in.html ^2 stating that sudo updmap --enable MixedMap ul9.map is required. It looks fine in PDF and PS view but still looks rotten in dvi view.
218 4.3 Formatting your Lyx document
219 It is not necessary to base your literate document on any of the original L Y X literate classes; so select a regular class for your document type.
220 Add the new module Fangle Literate Listings and also Logical Markup which is very useful.
221 In the drop-down style listbox you should notice a new style defined, called Chunk.
222 When you wish to insert a literate chunk, you enter it's plain name in the Chunk style, instead of the old noweb method that uses <<name>>= type tags. In the line (or paragraph) following the chunk name, you insert a listing with: Insert->Program Listing.
223 Inside the white listing box you can type (or paste using shift+ctrl+V) your listing. There is no need to use ctrl+enter at the end of lines as with some older L Y X literate techniques --- just press enter as normal.
224 4.3.1 Customising the listing appearance
225 The code is formatted using the lstlistings package. The chunk style doesn't just define the chunk name, but can also define any other chunk options supported by the lstlistings package \lstset command. In fact, what you type in the chunk style is raw latex. If you want to set the chunk language without having to right-click the listing, just add ,lanuage=C after the chunk name. (Currently the language will affect all subsequent listings, so you may need to specify ,language= quite a lot).
226 To do: so fix the bug
228 Of course you can do this by editing the listings box advanced properties by right-clicking on the listings box, but that takes longer, and you can't see at-a-glance what the advanced settings are while editing the document; also advanced settings apply only to that box --- the chunk settings apply through the rest of the document3. It ought to apply only to subsequent chunks of the same name. I'll fix that later ^3.
229 To do: So make sure they only apply to chunks of that name
231 4.3.2 Global customisations
232 As lstlistings is used to set the code chunks, it's \lstset command can be used in the pre-amble to set some document wide settings.
233 If your source has many words with long sequences of capital letters, then columns=fullflexible may be a good idea, or the capital letters will get crowded. (I think lstlistings ought to use a slightly smaller font for captial letters so that they still fit).
234 The font family \ttfamily looks more normal for code, but has no bold (an alternate typewriter font is used).
235 With \ttfamily, I must also specify columns=fullflexible or the wrong letter spacing is used.
236 In my LaTeX pre-amble I usually specialise my code format with:
238 19a <document-preamble[1](
\v), lang=tex> ≡
239 ________________________________________________________________________
241 2 | numbers=left, stepnumber=1, numbersep=5pt,
242 3 | breaklines=false,
243 4 | basicstyle=\footnotesize\ttfamily,
244 5 | numberstyle=\tiny,
246 7 | columns=fullflexible,
247 8 | numberfirstline=true
249 |________________________________________________________________________
253 4.4 Configuring the build script
254 You can invoke code extraction and building from the L Y X menu option Document->Build Program.
255 First, make sure you don't have a conversion defined for Lyx->Program
256 From the menu Tools->Preferences, add a conversion from Latex(Plain)->Program as:
257 set -x ; fangle -Rlyx-build $$i |
258 env LYX_b=$$b LYX_i=$$i LYX_o=$$o LYX_p=$$p LYX_r=$$r bash
259 (But don't cut-n-paste it from this document or you may be be pasting a multi-line string which will break your lyx preferences file).
260 I hope that one day, L Y X will set these into the environment when calling the build script.
261 You may also want to consider adding options to this conversion...
262 parselog=/usr/share/lyx/scripts/listerrors
263 ...but if you do you will lose your stderr4. There is some bash plumbing to get a copy of stderr but this footnote is too small ^4.
264 Now, a shell script chunk called lyx-build will be extracted and run whenever you choose the Document->Build Program menu item.
265 This document was originally managed using L Y X and lyx-build script for this document is shown here for historical reference.
266 lyx -e latex fangle.lyx && \
267 fangle fangle.lyx > ./autoboot
268 This looks simple enough, but as mentioned, fangle has to be had from somewhere before it can be extracted.
270 When the lyx-build chunk is executed, the current directory will be a temporary directory, and LYX_SOURCE will refer to the tex file in this temporary directory. This is unfortunate as our makefile wants to run from the project directory where the Lyx file is kept.
271 We can extract the project directory from $$r, and derive the probable Lyx filename from the noweb file that Lyx generated.
273 19b <lyx-build-helper[1](
\v), lang=sh> ≡ 87c⊳
274 ________________________________________________________________________
275 1 | PROJECT_DIR="$LYX_r"
276 2 | LYX_SRC="$PROJECT_DIR/${LYX_i%.tex}.lyx"
278 4 | TEX_SRC="$TEX_DIR/$LYX_i"
279 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
280 And then we can define a lyx-build fragment similar to the autoboot fragment
282 20a <lyx-build[1](
\v), lang=sh> ≡ 87a⊳
283 ________________________________________________________________________
285 2 | «lyx-build-helper 19b»
286 3 | cd $PROJECT_DIR || exit 1
288 5 | #/usr/bin/fangle -filter ./notanglefix-filter \
289 6 | # -R./Makefile.inc "../../noweb-lyx/noweb-lyx3.lyx" \
290 7 | # | sed '/NOWEB_SOURCE=/s/=.*/=samba4-dfs.lyx/' \
291 8 | # > ./Makefile.inc
293 10 | #make -f ./Makefile.inc fangle_sources
294 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
296 Chapter 5Using Fangle with TeXmacs
297 To do: Write this chapter
299 Chapter 6Fangle with Makefiles
300 Here we describe a Makefile.inc that you can include in your own Makefiles, or glue as a recursive make to other projects.
301 Makefile.inc will cope with extracting all the other source files from this or any specified literate document and keeping them up to date.
302 It may also be included by a Makefile or Makefile.am defined in a literate document to automatically deal with the extraction of source files and documents during normal builds.
303 Thus, if Makefile.inc is included into a main project makefile it add rules for the source files, capable of extracting the source files from the literate document.
304 6.1 A word about makefiles formats
305 Whitespace formatting is very important in a Makefile. The first character of each action line must be a TAB.
306 target: pre-requisite
309 This requires that the literate programming environment have the ability to represent a TAB character in a way that fangle will generate an actual TAB character.
310 We also adopt a convention that code chunks whose names beginning with ./ should always be automatically extracted from the document. Code chunks whose names do not begin with ./ are for internal reference. Such chunks may be extracted directly, but will not be automatically extracted by this Makefile.
311 6.2 Extracting Sources
312 Our makefile has two parts; variables must be defined before the targets that use them.
313 As we progress through this chapter, explaining concepts, we will be adding lines to <Makefile.inc-vars 23b> and <Makefile.inc-targets 24b> which are included in <./Makefile.inc 23a> below.
315 23a <./Makefile.inc[1](
\v), lang=make> ≡
316 ________________________________________________________________________
317 1 | «Makefile.inc-vars 23b»
318 2 | «Makefile.inc-targets 24b»
319 |________________________________________________________________________
322 We first define a placeholder for LITERATE_SOURCE to hold the name of this document. This will normally be passed on the command line.
324 23b <Makefile.inc-vars[1](
\v), lang=> ≡ 24a⊳
325 ________________________________________________________________________
327 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
328 Fangle cannot process L Y X or TeXmacs documents directly, so the first stage is to convert these to more suitable text based formats1. L Y X and TeXmacs formats are text-based, but not suitable for fangle ^1.
329 6.2.1 Converting from L Y X to LaTeX
330 The first stage will always be to convert the L Y X file to a LaTeX file. Fangle must run on a TeX file because the L Y X command server-goto-file-line2. The Lyx command server-goto-file-line is used to position the Lyx cursor at the compiler errors. ^2 requries that the line number provided be a line of the TeX file and always maps this the line in the L Y X docment. We use server-goto-file-line when moving the cursor to error lines during compile failures.
331 The command lyx -e literate fangle.lyx will produce fangle.tex, a TeX file; so we define a make target to be the same as the L Y X file but with the .tex extension.
332 The EXTRA_DIST is for automake support so that the TeX files will automaticaly be distributed with the source, to help those who don't have L Y X installed.
334 24a <Makefile.inc-vars[2](
\v) ⇑23b, lang=> +≡ ⊲23b 24c▿
335 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
336 2 | TEX_SOURCE=$(LYX_SOURCE:.lyx=.tex)
337 3 | EXTRA_DIST+=$(TEX_SOURCE)
338 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
339 We then specify that the TeX source is to be generated from the L Y X source.
341 24b <Makefile.inc-targets[1](
\v), lang=> ≡ 24d▿
342 ________________________________________________________________________
343 1 | $(TEX_SOURCE): $(LYX_SOURCE)
346 4 | ↦rm -f -- $(TEX_SOURCE)
348 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
349 6.2.2 Converting from TeXmacs
350 Fangle cannot process TeXmacs files directly3. but this is planned when TeXmacs uses xml as it's native format ^3, but must first convert them to text files.
351 The command texmacs -c fangle.tm fangle.txt -q will produce fangle.txt, a text file; so we define a make target to be the same as the TeXmacs file but with the .txt extension.
352 The EXTRA_DIST is for automake support so that the TeX files will automaticaly be distributed with the source, to help those who don't have L Y X installed.
354 24c <Makefile.inc-vars[3](
\v) ⇑23b, lang=> +≡ ▵24a 25a⊳
355 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
356 4 | TXT_SOURCE=$(LITERATE_SOURCE:.tm=.txt)
357 5 | EXTRA_DIST+=$(TXT_SOURCE)
358 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
359 To do: Add loop around each $< so multiple targets can be specified
362 24d <Makefile.inc-targets[2](
\v) ⇑24b, lang=> +≡ ▵24b 25c⊳
363 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
364 6 | $(TXT_SOURCE): $(LITERATE_SOURCE)
365 7 | ↦texmacs -c $< $(TXT_SOURCE) -q
367 9 | ↦rm -f -- $(TXT_SOURCE)
368 10 | clean: clean_txt
369 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
370 6.3 Extracting Program Source
371 The program source is extracted using fangle, which is designed to operate on text or a LaTeX documents4. LaTeX documents are just slightly special text documents ^4.
373 25a <Makefile.inc-vars[4](
\v) ⇑23b, lang=> +≡ ⊲24c 25b▿
374 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
375 6 | FANGLE_SOURCE=$(TEX_SOURCE) $(TXT_SOURCE)
376 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
377 The literate document can result in any number of source files, but not all of these will be changed each time the document is updated. We certainly don't want to update the timestamps of these files and cause the whole source tree to be recompiled just because the literate explanation was revised. We use CPIF from the Noweb tools to avoid updating the file if the content has not changed, but should probably write our own.
378 However, if a source file is not updated, then the fangle file will always have a newer time-stamp and the makefile would always re-attempt to extact a newer source file which would be a waste of time.
379 Because of this, we use a stamp file which is always updated each time the sources are fully extracted from the LaTeX document. If the stamp file is newer than the document, then we can avoid an attempt to re-extract any of the sources. Because this stamp file is only updated when extraction is complete, it is safe for the user to interrupt the build-process mid-extraction.
380 We use echo rather than touch to update the stamp file beause the touch command does not work very well over an sshfsmount that I was using.
382 25b <Makefile.inc-vars[5](
\v) ⇑23b, lang=> +≡ ▵25a 26a⊳
383 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
384 7 | FANGLE_SOURCE_STAMP=$(FANGLE_SOURCE).stamp
385 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
387 25c <Makefile.inc-targets[3](
\v) ⇑24b, lang=> +≡ ⊲24d 26b⊳
388 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
389 11 | $(FANGLE_SOURCE_STAMP): $(FANGLE_SOURCE) \
390 12 | ↦ $(FANGLE_SOURCES) ; \
391 13 | ↦echo -n > $(FANGLE_SOURCE_STAMP)
393 15 | ↦rm -f $(FANGLE_SOURCE_STAMP)
394 16 | clean: clean_stamp
395 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
396 6.4 Extracting Source Files
397 We compute FANGLE_SOURCES to hold the names of all the source files defined in the document. We compute this only once, by means of := in assignent. The sed deletes the any << and >> which may surround the roots names (for compatibility with Noweb's noroots command).
398 As we use chunk names beginning with ./ to denote top level fragments that should be extracted, we filter out all fragments that do not begin with ./
399 Note 1. FANGLE_PREFIX is set to ./ by default, but whatever it may be overridden to, the prefix is replaced by a literal ./ before extraction so that files will be extracted in the current directory whatever the prefix. This helps namespace or sub-project prefixes like documents: for chunks like documents:docbook/intro.xml
400 To do: This doesn't work though, because it loses the full name and doesn't know what to extact!
403 26a <Makefile.inc-vars[6](
\v) ⇑23b, lang=> +≡ ⊲25b 26e▿
404 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
405 8 | FANGLE_PREFIX:=\.\/
406 9 | FANGLE_SOURCES:=$(shell \
407 10 | fangle -r $(FANGLE_SOURCE) |\
408 11 | sed -e 's/^[<][<]//;s/[>][>]$$//;/^$(FANGLE_PREFIX)/!d' \
409 12 | -e 's/^$(FANGLE_PREFIX)/\.\//' )
410 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
411 The target below, echo_fangle_sources is a helpful debugging target and shows the names of the files that would be extracted.
413 26b <Makefile.inc-targets[4](
\v) ⇑24b, lang=> +≡ ⊲25c 26c▿
414 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
415 17 | .PHONY: echo_fangle_sources
416 18 | echo_fangle_sources: ; @echo $(FANGLE_SOURCES)
417 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
418 We define a convenient target called fangle_sources so that make -f fangle_sources will re-extract the source if the literate document has been updated.
420 26c <Makefile.inc-targets[5](
\v) ⇑24b, lang=> +≡ ▵26b 26d▿
421 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
422 19 | .PHONY: fangle_sources
423 20 | fangle_sources: $(FANGLE_SOURCE_STAMP)
424 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
425 And also a convenient target to remove extracted sources.
427 26d <Makefile.inc-targets[6](
\v) ⇑24b, lang=> +≡ ▵26c 27d⊳
428 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
429 21 | .PHONY: clean_fangle_sources
430 22 | clean_fangle_sources: ; \
431 23 | rm -f -- $(FANGLE_SOURCE_STAMP) $(FANGLE_SOURCES)
432 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
433 We now look at the extraction of the source files.
434 This makefile macro if_extension takes 4 arguments: the filename $(1), some extensions to match $(2) and a shell command to return if the filename does match the exensions $(3), and a shell command to return if it does not match the extensions $(4).
436 26e <Makefile.inc-vars[7](
\v) ⇑23b, lang=> +≡ ▵26a 26f▿
437 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
438 13 | if_extension=$(if $(findstring $(suffix $(1)),$(2)),$(3),$(4))
439 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
440 For some source files like C files, we want to output the line number and filename of the original LaTeX document from which the source came5. I plan to replace this option with a separate mapping file so as not to pollute the generated source, and also to allow a code pretty-printing reformatter like indent be able to re-format the file and adjust for changes through comparing the character streams. ^5.
441 To make this easier we define the file extensions for which we want to do this.
443 26f <Makefile.inc-vars[8](
\v) ⇑23b, lang=> +≡ ▵26e 26g▿
444 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
445 14 | C_EXTENSIONS=.c .h
446 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
447 We can then use the if_extensions macro to define a macro which expands out to the -L option if fangle is being invoked in a C source file, so that C compile errors will refer to the line number in the TeX document.
449 26g <Makefile.inc-vars[9](
\v) ⇑23b, lang=> +≡ ▵26f 27a⊳
450 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
452 16 | nf_line=-L -T$(TABS)
453 17 | fangle=fangle $(call if_extension,$(2),$(C_EXTENSIONS),$(nf_line)) -R"$(2)" $(1)
454 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
455 We can use a similar trick to define an indent macro which takes just the filename as an argument and can return a pipeline stage calling the indent command. Indent can be turned off with make fangle_sources indent=
457 27a <Makefile.inc-vars[10](
\v) ⇑23b, lang=> +≡ ⊲26g 27b▿
458 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
459 18 | indent_options=-npro -kr -i8 -ts8 -sob -l80 -ss -ncs
460 19 | indent=$(call if_extension,$(1),$(C_EXTENSIONS), | indent $(indent_options))
461 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
462 We now define the pattern for extracting a file. The files are written using noweb's cpif so that the file timestamp will not be touched if the contents haven't changed. This avoids the need to rebuild the entire project because of a typographical change in the documentation, or if none or a few C source files have changed.
464 27b <Makefile.inc-vars[11](
\v) ⇑23b, lang=> +≡ ▵27a 27c▿
465 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
466 20 | fangle_extract=@mkdir -p $(dir $(1)) && \
467 21 | $(call fangle,$(2),$(1)) > "$(1).tmp" && \
468 22 | cat "$(1).tmp" $(indent) | cpif "$(1)" \
469 23 | && rm -- "$(1).tmp" || \
470 24 | (echo error newfangling $(1) from $(2) ; exit 1)
471 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
472 We define a target which will extract or update all sources. To do this we first defined a makefile template that can do this for any source file in the LaTeX document.
474 27c <Makefile.inc-vars[12](
\v) ⇑23b, lang=> +≡ ▵27b 28a⊳
475 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
476 25 | define FANGLE_template
478 27 | ↦$$(call fangle_extract,$(1),$(2))
479 28 | FANGLE_TARGETS+=$(1)
481 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
482 We then enumerate the discovered FANGLE_SOURCES to generate a makefile rule for each one using the makefile template we defined above.
484 27d <Makefile.inc-targets[7](
\v) ⇑24b, lang=> +≡ ⊲26d 27e▿
485 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
486 24 | $(foreach source,$(FANGLE_SOURCES),\
487 25 | $(eval $(call FANGLE_template,$(source),$(FANGLE_SOURCE))) \
489 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
490 These will all be built with FANGLE_SOURCE_STAMP.
491 We also remove the generated sources on a make distclean.
493 27e <Makefile.inc-targets[8](
\v) ⇑24b, lang=> +≡ ▵27d 28b⊳
494 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
495 27 | _distclean: clean_fangle_sources
496 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
497 6.5 Extracting Documentation
498 We then identify the intermediate stages of the documentation and their build and clean targets.
500 6.5.1.1 Running pdflatex
501 We produce a pdf file from the tex file.
503 28a <Makefile.inc-vars[13](
\v) ⇑23b, lang=> +≡ ⊲27c 28c▿
504 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
505 30 | FANGLE_PDF=$(TEX_SOURCE:.tex=.pdf)
506 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
507 We run pdflatex twice to be sure that the contents and aux files are up to date. We certainly are required to run pdflatex at least twice if these files do not exist.
509 28b <Makefile.inc-targets[9](
\v) ⇑24b, lang=> +≡ ⊲27e 28d▿
510 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
511 28 | $(FANGLE_PDF): $(TEX_SOURCE)
512 29 | ↦pdflatex $< && pdflatex $<
515 32 | ↦rm -f -- $(FANGLE_PDF) $(TEX_SOURCE:.tex=.toc) \
516 33 | ↦ $(TEX_SOURCE:.tex=.log) $(TEX_SOURCE:.tex=.aux)
517 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
518 6.5.2 Formatting TeXmacs
519 TeXmacs can produce a PDF file directly.
521 28c <Makefile.inc-vars[14](
\v) ⇑23b, lang=> +≡ ▵28a 28e▿
522 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
523 31 | FANGLE_PDF=$(TEX_SOURCE:.tm=.pdf)
524 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
525 To do: Outputting the PDF may not be enough to update the links and page references. I think
526 we need to update twice, generate a pdf, update twice mode and generate a new PDF.
527 Basically the PDF export of TeXmacs is pretty rotten and doesn't work properly from the CLI
530 28d <Makefile.inc-targets[10](
\v) ⇑24b, lang=> +≡ ▵28b 28f▿
531 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
532 34 | $(FANGLE_PDF): $(TEXMACS_SOURCE)
533 35 | ↦texmacs -c $(TEXMACS_SOURCE) $< -q
536 38 | ↦rm -f -- $(FANGLE_PDF)
537 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
538 6.5.3 Building the Documentation as a Whole
539 Currently we only build pdf as a final format, but FANGLE_DOCS may later hold other output formats.
541 28e <Makefile.inc-vars[15](
\v) ⇑23b, lang=> +≡ ▵28c
542 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
543 32 | FANGLE_DOCS=$(FANGLE_PDF)
544 |________________________________________________________________________
547 We also define fangle_docs as a convenient phony target.
549 28f <Makefile.inc-targets[11](
\v) ⇑24b, lang=> +≡ ▵28d 28g▿
550 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
551 39 | .PHONY: fangle_docs
552 40 | fangle_docs: $(FANGLE_DOCS)
553 41 | docs: fangle_docs
554 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
555 And define a convenient clean_fangle_docs which we add to the regular clean target
557 28g <Makefile.inc-targets[12](
\v) ⇑24b, lang=> +≡ ▵28f
558 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
559 42 | .PHONEY: clean_fangle_docs
560 43 | clean_fangle_docs: clean_tex clean_pdf
561 44 | clean: clean_fangle_docs
563 46 | distclean_fangle_docs: clean_tex clean_fangle_docs
564 47 | distclean: clean distclean_fangle_docs
565 |________________________________________________________________________
569 If Makefile.inc is included into Makefile, then extracted files can be updated with this command:
572 make -f Makefile.inc fangle_sources
573 6.7 Boot-strapping the extraction
574 As well as having the makefile extract or update the source files as part of it's operation, it also seems convenient to have the makefile re-extracted itself from this document.
575 It would also be convenient to have the code that extracts the makefile from this document to also be part of this document, however we have to start somewhere and this unfortunately requires us to type at least a few words by hand to start things off.
576 Therefore we will have a minimal root fragment, which, when extracted, can cope with extracting the rest of the source. This shell script fragment can do that. It's name is * — out of regard for Noweb, but when extracted might better be called autoupdate.
580 29a <*[1](
\v), lang=sh> ≡
581 ________________________________________________________________________
584 3 | MAKE_SRC="${1:-${NW_LYX:-../../noweb-lyx/noweb-lyx3.lyx}}"
585 4 | MAKE_SRC=‘dirname "$MAKE_SRC"‘/‘basename "$MAKE_SRC" .lyx‘
586 5 | NOWEB_SRC="${2:-${NOWEB_SRC:-$MAKE_SRC.lyx}}"
587 6 | lyx -e latex $MAKE_SRC
589 8 | fangle -R./Makefile.inc ${MAKE_SRC}.tex \
590 9 | | sed "/FANGLE_SOURCE=/s/^/#/;T;aNOWEB_SOURCE=$FANGLE_SRC" \
591 10 | | cpif ./Makefile.inc
593 12 | make -f ./Makefile.inc fangle_sources
594 |________________________________________________________________________
597 The general Makefile can be invoked with ./autoboot and can also be included into any automake file to automatically re-generate the source files.
598 The autoboot can be extracted with this command:
599 lyx -e latex fangle.lyx && \
600 fangle fangle.lyx > ./autoboot
601 This looks simple enough, but as mentioned, fangle has to be had from somewhere before it can be extracted.
602 On a unix system this will extract fangle.module and the fangle awk script, and run some basic tests.
603 To do: cross-ref to test chapter when it is a chapter all on its own
605 6.8 Incorporating Makefile.inc into existing projects
606 If you are writing a literate module of an existing non-literate program you may find it easier to use a slight recursive make instead of directly including Makefile.inc in the projects makefile.
607 This way there is less chance of definitions in Makefile.inc interfering with definitions in the main makefile, or with definitions in other Makefile.inc from other literate modules of the same project.
608 To do this we add some glue to the project makefile that invokes Makefile.inc in the right way. The glue works by adding a .PHONY target to call the recursive make, and adding this target as an additional pre-requisite to the existing targets.
609 Example Sub-module of existing system
610 In this example, we are building module.so as a literate module of a larger project.
611 We will show the sort glue that can be inserted into the projects Makefile — or more likely — a regular Makefile included in or invoked by the projects Makefile.
613 30a <makefile-glue[1](
\v), lang=> ≡ 30b▿
614 ________________________________________________________________________
615 1 | module_srcdir=modules/module
616 2 | MODULE_SOURCE=module.tm
617 3 | MODULE_STAMP=$(MODULE_SOURCE).stamp
618 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
619 The existing build system may already have a build target for module.o, but we just add another pre-requisite to that. In this case we use module.tm.stamp as a pre-requisite, the stamp file's modified time indicating when all sources were extracted6. If the projects build system does not know how to build the module from the extracted sources, then just add build actions here as normal. ^6.
621 30b <makefile-glue[2](
\v) ⇑30a, lang=make> +≡ ▵30a 30c▿
622 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
623 4 | $(module_srcdir)/module.o: $(module_srcdir)/$(MODULE_STAMP)
624 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
625 The target for this new pre-requisite will be generated by a recursive make using Makefile.inc which will make sure that the source is up to date, before it is built by the main projects makefile.
627 30c <makefile-glue[3](
\v) ⇑30a, lang=> +≡ ▵30b 30d▿
628 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
629 5 | $(module_srcdir)/$(MODULE_STAMP): $(module_srcdir)/$(MODULE_SOURCE)
630 6 | ↦$(MAKE) -C $(module_srcdir) -f Makefile.inc fangle_sources LITERATE_SOURCE=$(MODULE_SOURCE)
631 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
632 We can do similar glue for the docs, clean and distclean targets. In this example the main prject was using a double colon for these targets, so we must use the same in our glue.
634 30d <makefile-glue[4](
\v) ⇑30a, lang=> +≡ ▵30c
635 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
636 7 | docs:: docs_module
637 8 | .PHONY: docs_module
639 10 | ↦$(MAKE) -C $(module_srcdir) -f Makefile.inc docs LITERATE_SOURCE=$(MODULE_SOURCE)
641 12 | clean:: clean_module
642 13 | .PHONEY: clean_module
644 15 | ↦$(MAKE) -C $(module_srcdir) -f Makefile.inc clean LITERATE_SOURCE=$(MODULE_SOURCE)
646 17 | distclean:: distclean_module
647 18 | .PHONY: distclean_module
648 19 | distclean_module:
649 20 | ↦$(MAKE) -C $(module_srcdir) -f Makefile.inc distclean LITERATE_SOURCE=$(MODULE_SOURCE)
650 |________________________________________________________________________
653 We could do similarly for install targets to install the generated docs.
655 Chapter 7Fangle awk source code
656 We use the copyright notice from chapter 2.
658 33a <./fangle[1](
\v), lang=awk> ≡ 33b▿
659 ________________________________________________________________________
660 1 | #! /usr/bin/awk -f
661 2 | # «gpl3-copyright 4a»
662 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
663 We also use code from Arnold Robbins public domain getopt (1993 revision) defined in 77a, and naturally want to attribute this appropriately.
665 33b <./fangle[2](
\v) ⇑33a, lang=> +≡ ▵33a 33c▿
666 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
667 3 | # NOTE: Arnold Robbins public domain getopt for awk is also used:
668 4 | «getopt.awk-header 75a»
669 5 | «getopt.awk-getopt() 75c»
671 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
672 And include the following chunks (which are explained further on) to make up the program:
674 33c <./fangle[3](
\v) ⇑33a, lang=> +≡ ▵33b 38a⊳
675 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
676 7 | «helper-functions 34d»
677 8 | «mode-tracker 54a»
678 9 | «parse_chunk_args 40a»
679 10 | «chunk-storage-functions 73b»
680 11 | «output_chunk_names() 67d»
681 12 | «output_chunks() 67e»
682 13 | «write_chunk() 68a»
683 14 | «expand_chunk_args() 40b»
686 17 | «recognize-chunk 57a»
688 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
690 The portable way to erase an array in awk is to split the empty string, so we define a fangle macro that can split an array, like this:
692 33d <awk-delete-array[1](ARRAY
\v\v), lang=awk> ≡
693 ________________________________________________________________________
694 1 | split("", ${ARRAY});
695 |________________________________________________________________________
698 For debugging it is sometimes convenient to be able to dump the contents of an array to stderr, and so this macro is also useful.
700 33e <dump-array[1](ARRAY
\v\v), lang=awk> ≡
701 ________________________________________________________________________
702 1 | print "\nDump: ${ARRAY}\n--------\n" > "/dev/stderr";
703 2 | for (_x in ${ARRAY}) {
704 3 | print _x "=" ${ARRAY}[_x] "\n" > "/dev/stderr";
706 5 | print "========\n" > "/dev/stderr";
707 |________________________________________________________________________
711 Fatal errors are issued with the error function:
713 34a <error()[1](
\v), lang=awk> ≡ 34b▿
714 ________________________________________________________________________
715 1 | function error(message)
717 3 | print "ERROR: " FILENAME ":" FNR " " message > "/dev/stderr";
720 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
721 and likewise for non-fatal warnings:
723 34b <error()[2](
\v) ⇑34a, lang=awk> +≡ ▵34a 34c▿
724 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
725 6 | function warning(message)
727 8 | print "WARNING: " FILENAME ":" FNR " " message > "/dev/stderr";
730 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
731 and debug output too:
733 34c <error()[3](
\v) ⇑34a, lang=awk> +≡ ▵34b
734 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
735 11 | function debug_log(message)
737 13 | print "DEBUG: " FILENAME ":" FNR " " message > "/dev/stderr";
739 |________________________________________________________________________
742 To do: append=helper-functions
745 34d <helper-functions[1](
\v), lang=> ≡
746 ________________________________________________________________________
748 |________________________________________________________________________
751 Chapter 8TeXmacs args
752 TeXmacs functions with arguments1. or function declarations with parameters ^1 appear like this:
753 blah((I came, I saw, I conquered)<wide-overbrace>^(argument 1)(^K, )<wide-overbrace>^(sep.)(and then went home asd)<wide-overbrace>^(argument 3)(^K))<wide-overbrace>^(term.)_arguments
754 Arguments commence after the opening parenthesis. The first argument runs up till the next ^K.
755 If the following character is a , then another argument follows. If the next character after the , is a space character, then it is also eaten. The fangle stylesheet emits ^K,space as separators, but the fangle untangler will forgive a missing space.
756 If the following character is ) then this is a terminator and there are no more arguments.
758 35a <constants[1](
\v), lang=> ≡ 73a⊳
759 ________________________________________________________________________
760 1 | ARG_SEPARATOR=sprintf("%c", 11);
761 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
762 To process the text in this fashion, we split the string on ^K
765 35b <get_chunk_args[1](
\v), lang=> ≡
766 ________________________________________________________________________
767 1 | function get_texmacs_chunk_args(text, args, a, done) {
768 2 | split(text, args, ARG_SEPARATOR);
771 5 | for (a=1; (a in args); a++) if (a>1) {
772 6 | if (args[a] == "" || substr(args[a], 1, 1) == ")") done=1;
778 12 | if (substr(args[a], 1, 2) == ", ") args[a]=substr(args[a], 3);
779 13 | else if (substr(args[a], 1, 1) == ",") args[a]=substr(args[a], 2);
782 |________________________________________________________________________
785 Chapter 9LaTeX and lstlistings
786 To do: Split LyX and TeXmacs parts
788 For L Y X and LaTeX, the lstlistings package is used to format the lines of code chunks. You may recal from chapter XXX that arguments to a chunk definition are pure LaTeX code. This means that fangle needs to be able to parse LaTeX a little.
789 LaTeX arguments to lstlistings macros are a comma seperated list of key-value pairs, and values containing commas are enclosed in { braces } (which is to be expected for LaTeX).
790 A sample expressions is:
791 name=thomas, params={a, b}, something, something-else
792 but we see that this is just a simpler form of this expression:
793 name=freddie, foo={bar=baz, quux={quirk, a=fleeg}}, etc
794 We may consider that we need a function that can parse such LaTeX expressions and assign the values to an AWK associated array, perhaps using a recursive parser into a multi-dimensional hash1. as AWK doesn't have nested-hash support ^1, resulting in:
799 a[foo, quux, a] fleeg
802 Yet, also, on reflection it seems that sometimes such nesting is not desirable, as the braces are also used to delimit values that contain commas --- we may consider that
803 name={williamson, freddie}
804 should assign williamson, freddie to name.
805 In fact we are not so interested in the detail so as to be bothered by this, which turns out to be a good thing for two reasons. Firstly TeX has a malleable parser with no strict syntax, and secondly whether or not williamson and freddie should count as two items will be context dependant anyway.
806 We need to parse this latex for only one reason; which is that we are extending lstlistings to add some additional arguments which will be used to express chunk parameters and other chunk options.
807 9.1 Additional lstlstings parameters
808 Further on we define a \Chunk LaTeX macro whose arguments will consist of a the chunk name, optionally followed by a comma and then a comma separated list of arguments. In fact we will just need to prefix name= to the arguments to in order to create valid lstlistings arguments.
809 There will be other arguments supported too;
810 params.As an extension to many literate-programming styles, fangle permits code chunks to take parameters and thus operate somewhat like C pre-processor macros, or like C++ templates. Chunk parameters are declared with a chunk argument called params, which holds a semi-colon separated list of parameters, like this:
811 achunk,language=C,params=name;address
812 addto.a named chunk that this chunk is to be included into. This saves the effort of having to declare another listing of the named chunk merely to include this one.
813 Function get_chunk_args() will accept two paramters, text being the text to parse, and values being an array to receive the parsed values as described above. The optional parameter path is used during recursion to build up the multi-dimensional array path.
815 38a <./fangle[4](
\v) ⇑33a, lang=> +≡ ⊲33c
816 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
817 19 | «get_chunk_args() 38b»
818 |________________________________________________________________________
822 38b <get_chunk_args()[1](
\v), lang=> ≡ 38c▿
823 ________________________________________________________________________
824 1 | function get_tex_chunk_args(text, values,
825 2 | # optional parameters
826 3 | path, # hierarchical precursors
829 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
830 The strategy is to parse the name, and then look for a value. If the value begins with a brace {, then we recurse and consume as much of the text as necessary, returning the remaining text when we encounter a leading close-brace }. This being the strategy --- and executed in a loop --- we realise that we must first look for the closing brace (perhaps preceded by white space) in order to terminate the recursion, and returning remaining text.
832 38c <get_chunk_args()[2](
\v) ⇑38b, lang=> +≡ ▵38b
833 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
835 7 | split("", values);
836 8 | while(length(text)) {
837 9 | if (match(text, "^ *}(.*)", a)) {
840 12 | «parse-chunk-args 38d»
844 |________________________________________________________________________
847 We can see that the text could be inspected with this regex:
849 38d <parse-chunk-args[1](
\v), lang=> ≡ 39a⊳
850 ________________________________________________________________________
851 1 | if (! match(text, " *([^,=]*[^,= ]) *(([,=]) *(([^,}]*) *,* *(.*))|)$", a)) {
854 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
855 and that a will have the following values:
858 2 =freddie, foo={bar=baz, quux={quirk, a=fleeg}}, etc
860 4 freddie, foo={bar=baz, quux={quirk, a=fleeg}}, etc
862 6 , foo={bar=baz, quux={quirk, a=fleeg}}, etc
864 a[3] will be either = or , and signify whether the option named in a[1] has a value or not (respectively).
865 If the option does have a value, then if the expression substr(a[4],1,1) returns a brace { it will signify that we need to recurse:
867 39a <parse-chunk-args[2](
\v) ⇑38d, lang=> +≡ ⊲38d
868 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
870 5 | if (a[3] == "=") {
871 6 | if (substr(a[4],1,1) == "{") {
872 7 | text = get_tex_chunk_args(substr(a[4],2), values, path name SUBSEP);
874 9 | values[path name]=a[5];
878 13 | values[path name]="";
881 |________________________________________________________________________
884 We can test this function like this:
886 39b <gca-test.awk[1](
\v), lang=> ≡
887 ________________________________________________________________________
888 1 | «get_chunk_args() 38b»
892 5 | print get_tex_chunk_args("name=freddie, foo={bar=baz, quux={quirk, a=fleeg}}, etc", a);
894 7 | print "a[" b "] => " a[b];
897 |________________________________________________________________________
900 which should give this output:
902 39c <gca-test.awk-results[1](
\v), lang=> ≡
903 ________________________________________________________________________
904 1 | a[foo.quux.quirk] =>
905 2 | a[foo.quux.a] => fleeg
906 3 | a[foo.bar] => baz
908 5 | a[name] => freddie
909 |________________________________________________________________________
912 9.2 Parsing chunk arguments
913 Arguments to paramterized chunks are expressed in round brackets as a comma separated list of optional arguments. For example, a chunk that is defined with:
914 \Chunk{achunk, params=name ; address}
916 \chunkref{achunk}(John Jones, jones@example.com)
917 An argument list may be as simple as in \chunkref{pull}(thing, otherthing) or as complex as:
918 \chunkref{pull}(things[x, y], get_other_things(a, "(all)"))
919 --- which for all it's commas and quotes and parenthesis represents only two parameters: things[x, y] and get_other_things(a, "(all)").
920 If we simply split parameter list on commas, then the comma in things[x,y] would split into two seperate arguments: things[x and y]--- neither of which make sense on their own.
921 One way to prevent this would be by refusing to split text between matching delimiters, such as [, ], (, ), {, } and most likely also ", " and ', '. Of course this also makes it impossible to pass such mis-matched code fragments as parameters, but I think that it would be hard for readers to cope with authors who would pass such code unbalanced fragments as chunk parameters2. I know that I couldn't cope with users doing such things, and although the GPL3 license prevents me from actually forbidding anyone from trying, if they want it to work they'll have to write the code themselves and not expect any support from me. ^2.
922 Unfortunately, the full set of matching delimiters may vary from language to language. In certain C++ template contexts, < and > would count as delimiters, and yet in other contexts they would not.
923 This puts me in the unfortunate position of having to parse-somewhat all programming languages without knowing what they are!
924 However, if this universal mode-tracking is possible, then parsing the arguments would be trivial. Such a mode tracker is described in chapter 10 and used here with simplicity.
926 40a <parse_chunk_args[1](
\v), lang=> ≡
927 ________________________________________________________________________
928 1 | function parse_chunk_args(language, text, values, mode,
930 3 | c, context, rest)
932 5 | «new-mode-tracker
\v(context
\v, language
\v, mode
\v) 50b»
933 6 | rest = mode_tracker(context, text, values);
935 8 | for(c=1; c <= context[0, "values"]; c++) {
936 9 | values[c] = context[0, "values", c];
940 |________________________________________________________________________
943 9.3 Expanding parameters in the text
944 Within the body of the chunk, the parameters are referred to with: ${name} and ${address}. There is a strong case that a LaTeX style notation should be used, like \param{name} which would be expressed in the listing as =<\param{name}> and be rendered as ${name}. Such notation would make me go blind, but I do intend to adopt it.
945 We therefore need a function expand_chunk_args which will take a block of text, a list of permitted parameters, and the arguments which must substitute for the parameters.
946 Here we split the text on ${ which means that all parts except the first will begin with a parameter name which will be terminated by }. The split function will consume the literal ${ in each case.
948 40b <expand_chunk_args()[1](
\v), lang=> ≡
949 ________________________________________________________________________
950 1 | function expand_chunk_args(text, params, args,
951 2 | p, text_array, next_text, v, t, l)
953 4 | if (split(text, text_array, "\\${")) {
954 5 | «substitute-chunk-args 41a»
959 |________________________________________________________________________
962 First, we produce an associative array of substitution values indexed by parameter names. This will serve as a cache, allowing us to look up the replacement values as we extract each name.
964 41a <substitute-chunk-args[1](
\v), lang=> ≡ 41b▿
965 ________________________________________________________________________
966 1 | for(p in params) {
967 2 | v[params[p]]=args[p];
969 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
970 We accumulate substituted text in the variable text. As the first part of the split function is the part before the delimiter --- which is ${ in our case --- this part will never contain a parameter reference, so we assign this directly to the result kept in $text.
972 41b <substitute-chunk-args[2](
\v) ⇑41a, lang=> +≡ ▵41a 41c▿
973 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
974 4 | text=text_array[1];
975 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
976 We then iterate over the remaining values in the array3. I don't know why I think that it will enumerate the array in order, but it seems to work ^3
977 To do: fix or prove it
978 , and substitute each reference for it's argument.
980 41c <substitute-chunk-args[3](
\v) ⇑41a, lang=> +≡ ▵41b
981 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
982 5 | for(t=2; t in text_array; t++) {
983 6 | «substitute-chunk-arg 41d»
985 |________________________________________________________________________
988 After the split on ${ a valid parameter reference will consist of valid parameter name terminated by a close-brace }. A valid character name begins with the underscore or a letter, and may contain letters, digits or underscores.
989 A valid looking reference that is not actually the name of a parameter will be and not substituted. This is good because there is nothing to substitute anyway, and it avoids clashes when writing code for languages where ${...} is a valid construct --- such constructs will not be interfered with unless the parameter name also matches.
991 41d <substitute-chunk-arg[1](
\v), lang=> ≡
992 ________________________________________________________________________
993 1 | if (match(text_array[t], "^([a-zA-Z_][a-zA-Z0-9_]*)}", l) &&
996 4 | text = text v[l[1]] substr(text_array[t], length(l[1])+2);
998 6 | text = text "${" text_array[t];
1000 |________________________________________________________________________
1003 Chapter 10Language Modes & Quoting
1005 lstlistings and fangle both recognize source languages, and perform some basic parsing. lstlistings can detect strings and comments within a language definition and perform suitable rendering, such as italics for comments, and visible-spaces within strings.
1006 Fangle similarly can recognize strings, and comments, etc, within a language, so that any chunks included with \chunkref can be suitably escape or quoted.
1007 10.1.1 Modes to keep code together
1008 As an example, in the C language there are a few parse modes, affecting the interpretation of characters.
1009 One parse mode is the strings mode. The string mode is commenced by an un-escaped quotation mark " and terminated by the same. Within the string mode, only one additional mode can be commenced, it is the backslash mode \, which is always terminated after the folloing character.
1010 Another mode is [ which is terminated by a ] (unless it occurs in a string).
1011 Consider this fragment of C code:
1013 things([x, y])<wide-overbrace>^(1. [ mode), get_other_things((a, "(all)"_(3. " mode)))<wide-overbrace>^(2. ( mode)
1015 Mode nesting prevents the close parenthesis in the quoted string (part 3) from terminating the parenthesis mode (part 2).
1016 Each language has a set of modes, the default mode being the null mode. Each mode can lead to other modes.
1017 10.1.2 Modes affect included chunks
1018 For instance, consider this chunk with language=perl:
1020 43a <example-perl[1](
\v), lang=perl> ≡
1021 ________________________________________________________________________
1022 print "hello world $0\n";
1023 |________________________________________________________________________
1026 If it were included in a chunk with language=sh, like this:
1028 43b <example-sh[1](
\v), lang=sh> ≡
1029 ________________________________________________________________________
1030 perl -e "«example-perl 43a»"
1031 |________________________________________________________________________
1034 fangle would want to generate output like this:
1035 perl -e "print \"hello world \$0\\n\";"
1036 See that the double quote ", back-slash \ and $ have been quoted with a back-slash to protect them from shell interpretation.
1037 If that were then included in a chunk with language=make, like this:
1039 44a <example-makefile[1](
\v), lang=make> ≡
1040 ________________________________________________________________________
1042 2 | «example-sh 43b»
1043 |________________________________________________________________________
1046 We would need the output to look like this --- note the $$:
1048 perl -e "print \"hello world \$$0\\n\";"
1049 In order to make this work, we need to define a mode-tracker supporting each language, that can detect the various quoting modes, and provide a transformation that must be applied to any included text so that included text will be interpreted correctly after any interpolation that it may be subject to at run-time.
1050 For example, the sed transformation for text to be inserted into shell double-quoted strings would be something like:
1051 s/\\/\\\\/g;s/$/\\$/g;s/"/\\"/g;
1052 which protects \ $ ".
1053 To do: I don't think this example is true
1054 The mode tracker must also track nested mode-changes, as in this sh example.
1055 echo "hello ‘id ...‘"
1057 Any characters inserted at the point marked ↑ would need to be escaped, including ‘ | * among others. First it would need escaping for the back-ticks ‘, and then for the double-quotes ".
1059 Escaping need not occur if the format and mode of the included chunk matches that of the including chunk.
1060 As each chunk is output a new mode tracker for that language is initialized in it's normal state. As text is output for that chunk the output mode is tracked. When a new chunk is included, a transformation appropriate to that mode is selected and pushed onto a stack of transformations. Any text to be output is first passed through this stack of transformations.
1061 It remains to consider if the chunk-include function should return it's generated text so that the caller can apply any transformations (and formatting), or if it should apply the stack of transformations itself.
1062 Note that the transformed text should have the property of not being able to change the mode in the current chunk.
1063 To do: Note chunk parameters should probably also be transformed
1065 10.2 Language Mode Definitions
1066 All modes are stored in a single multi-dimensional hash. The first index is the language, and the second index is the mode-identifier. The third indexes are terminators, and optionally, submodes, and delimiters.
1067 A useful set of mode definitions for a nameless general C-type language is shown here. (Don't be confused by the double backslash escaping needed in awk. One set of escaping is for the string, and the second set of escaping is for the regex).
1068 To do: TODO: Add =<\mode{}> command which will allow us to signify that a string is
1069 regex and thus fangle will quote it for us.
1071 Submodes are entered by the characters " ' { ( [ /*
1073 45a <common-mode-definitions[1](language
\v\v), lang=> ≡ 45b▿
1074 ________________________________________________________________________
1075 1 | modes[${language}, "", "submodes"]="\\\\|\"|'|{|\\(|\\[";
1076 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1077 In the default mode, a comma surrounded by un-important white space is a delimiter of language items1. whatever a language item might be ^1.
1079 45b <common-mode-definitions[2](language
\v\v) ⇑45a, lang=> +≡ ▵45a 45d▿
1080 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1081 2 | modes[${language}, "", "delimiters"]=" *, *";
1082 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1083 and should pass this test:
1084 To do: Why do the tests run in ?(? mode and not ?? mode
1087 45c <test:mode-definitions[1](
\v), lang=> ≡ 46g⊳
1088 ________________________________________________________________________
1089 1 | parse_chunk_args("c-like", "1,2,3", a, "");
1090 2 | if (a[1] != "1") e++;
1091 3 | if (a[2] != "2") e++;
1092 4 | if (a[3] != "3") e++;
1093 5 | if (length(a) != 3) e++;
1094 6 | «pca-test.awk:summary 54c»
1096 8 | parse_chunk_args("c-like", "joe, red", a, "");
1097 9 | if (a[1] != "joe") e++;
1098 10 | if (a[2] != "red") e++;
1099 11 | if (length(a) != 2) e++;
1100 12 | «pca-test.awk:summary 54c»
1102 14 | parse_chunk_args("c-like", "${colour}", a, "");
1103 15 | if (a[1] != "${colour}") e++;
1104 16 | if (length(a) != 1) e++;
1105 17 | «pca-test.awk:summary 54c»
1106 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1107 Nested modes are identified by a backslash, a double or single quote, various bracket styles or a /* comment.
1108 For each of these sub-modes modes we must also identify at a mode terminator, and any sub-modes or delimiters that may be entered2. Because we are using the sub-mode characters as the mode identifier it means we can't currently have a mode character dependant on it's context; i.e. { can't behave differently when it is inside [. ^2.
1110 The backslash mode has no submodes or delimiters, and is terminated by any character. Note that we are not so much interested in evaluating or interpolating content as we are in delineating content. It is no matter that a double backslash (\\) may represent a single backslash while a backslash-newline may represent white space, but it does matter that the newline in a backslash newline should not be able to terminate a C pre-processor statement; and so the newline will be consumed by the backslash however it is to be interpreted.
1112 45d <common-mode-definitions[3](language
\v\v) ⇑45a, lang=> +≡ ▵45b 46f⊳
1113 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1114 3 | modes[${language}, "\\", "terminators"]=".";
1115 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1117 Common languages support two kinds of strings quoting, double quotes and single quotes.
1118 In a string we have one special mode, which is the backslash. This may escape an embedded quote and prevent us thinking that it should terminate the string.
1120 46a <mode:common-string[1](language
\v, quote
\v\v), lang=> ≡ 46b▿
1121 ________________________________________________________________________
1122 1 | modes[${language}, ${quote}, "submodes"]="\\\\";
1123 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1124 Otherwise, the string will be terminated by the same character that commenced it.
1126 46b <mode:common-string[2](language
\v, quote
\v\v) ⇑46a, lang=> +≡ ▵46a 46c▿
1127 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1128 2 | modes[${language}, ${quote}, "terminators"]=${quote};
1129 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1130 In C type languages, certain escape sequences exist in strings. We need to define mechanism to enclode any chunks included in this mode using those escape sequences. These are expressed in two parts, s meaning search, and r meaning replace.
1131 The first substitution is to replace a backslash with a double backslash. We do this first as other substitutions may introduce a backslash which we would not then want to escape again here.
1132 Note: Backslashes need double-escaping in the search pattern but not in the replacement string, hence we are replacing a literal \ with a literal \\.
1134 46c <mode:common-string[3](language
\v, quote
\v\v) ⇑46a, lang=> +≡ ▵46b 46d▿
1135 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1136 3 | escapes[${language}, ${quote}, ++escapes[${language}, ${quote}], "s"]="\\\\";
1137 4 | escapes[${language}, ${quote}, escapes[${language}, ${quote}], "r"]="\\\\";
1138 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1139 If the quote character occurs in the text, it should be preceded by a backslash, otherwise it would terminate the string unexpectedly.
1141 46d <mode:common-string[4](language
\v, quote
\v\v) ⇑46a, lang=> +≡ ▵46c 46e▿
1142 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1143 5 | escapes[${language}, ${quote}, ++escapes[${language}, ${quote}], "s"]=${quote};
1144 6 | escapes[${language}, ${quote}, escapes[${language}, ${quote}], "r"]="\\" ${quote};
1145 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1146 Any newlines in the string, must be replaced by \n.
1148 46e <mode:common-string[5](language
\v, quote
\v\v) ⇑46a, lang=> +≡ ▵46d
1149 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1150 7 | escapes[${language}, ${quote}, ++escapes[${language}, ${quote}], "s"]="\n";
1151 8 | escapes[${language}, ${quote}, escapes[${language}, ${quote}], "r"]="\\n";
1152 |________________________________________________________________________
1155 For the common modes, we define this string handling for double and single quotes.
1157 46f <common-mode-definitions[4](language
\v\v) ⇑45a, lang=> +≡ ⊲45d 47b⊳
1158 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1159 4 | «mode:common-string
\v(${language}
\v, "\""
\v) 46a»
1160 5 | «mode:common-string
\v(${language}
\v, "'"
\v) 46a»
1161 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1162 Working strings should pass this test:
1164 46g <test:mode-definitions[2](
\v) ⇑45c, lang=> +≡ ⊲45c 49d⊳
1165 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1166 18 | parse_chunk_args("c-like", "say \"I said, \\\"Hello, how are you\\\".\", for me", a, "");
1167 19 | if (a[1] != "say \"I said, \\\"Hello, how are you\\\".\"") e++;
1168 20 | if (a[2] != "for me") e++;
1169 21 | if (length(a) != 2) e++;
1170 22 | «pca-test.awk:summary 54c»
1171 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1172 10.2.3 Parentheses, Braces and Brackets
1173 Where quotes are closed by the same character, parentheses, brackets and braces are closed by an alternate character.
1175 47a <mode:common-brackets[1](language
\v, open
\v, close
\v\v), lang=> ≡
1176 ________________________________________________________________________
1177 1 | modes[${language}, ${open}, "submodes" ]="\\\\|\"|{|\\(|\\[|'|/\\*";
1178 2 | modes[${language}, ${open}, "delimiters"]=" *, *";
1179 3 | modes[${language}, ${open}, "terminators"]=${close};
1180 |________________________________________________________________________
1183 Note that the open is NOT a regex but the close token IS.
1184 To do: When we can quote regex we won't have to put the slashes in here
1187 47b <common-mode-definitions[5](language
\v\v) ⇑45a, lang=> +≡ ⊲46f
1188 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1189 6 | «mode:common-brackets
\v(${language}
\v, "{"
\v, "}"
\v) 47a»
1190 7 | «mode:common-brackets
\v(${language}
\v, "["
\v, "\\]"
\v) 47a»
1191 8 | «mode:common-brackets
\v(${language}
\v, "("
\v, "\\)"
\v) 47a»
1192 |________________________________________________________________________
1195 10.2.4 Customizing Standard Modes
1197 47c <mode:add-submode[1](language
\v, mode
\v, submode
\v\v), lang=> ≡
1198 ________________________________________________________________________
1199 1 | modes[${language}, ${mode}, "submodes"] = modes[${language}, ${mode}, "submodes"] "|" ${submode};
1200 |________________________________________________________________________
1204 47d <mode:add-escapes[1](language
\v, mode
\v, search
\v, replace
\v\v), lang=> ≡
1205 ________________________________________________________________________
1206 1 | escapes[${language}, ${mode}, ++escapes[${language}, ${mode}], "s"]=${search};
1207 2 | escapes[${language}, ${mode}, escapes[${language}, ${mode}], "r"]=${replace};
1208 |________________________________________________________________________
1213 We can define /* comment */ style comments and //comment style comments to be added to any language:
1215 47e <mode:multi-line-comments[1](language
\v\v), lang=> ≡
1216 ________________________________________________________________________
1217 1 | «mode:add-submode
\v(${language}
\v, ""
\v, "/\\*"
\v) 47c»
1218 2 | modes[${language}, "/*", "terminators"]="\\*/";
1219 |________________________________________________________________________
1223 47f <mode:single-line-slash-comments[1](language
\v\v), lang=> ≡
1224 ________________________________________________________________________
1225 1 | «mode:add-submode
\v(${language}
\v, ""
\v, "//"
\v) 47c»
1226 2 | modes[${language}, "//", "terminators"]="\n";
1227 3 | «mode:add-escapes
\v(${language}
\v, "//"
\v, "\n"
\v, "\n//"
\v) 47d»
1228 |________________________________________________________________________
1231 We can also define # comment style comments (as used in awk and shell scripts) in a similar manner.
1232 To do: I'm having to use # for hash and ¯extbackslash{} for and have hacky work-arounds in the parser for now
1235 47g <mode:add-hash-comments[1](language
\v\v), lang=> ≡
1236 ________________________________________________________________________
1237 1 | «mode:add-submode
\v(${language}
\v, ""
\v, "#"
\v) 47c»
1238 2 | modes[${language}, "#", "terminators"]="\n";
1239 3 | «mode:add-escapes
\v(${language}
\v, "#"
\v, "\n"
\v, "\n#"
\v) 47d»
1240 |________________________________________________________________________
1243 In C, the # denotes pre-processor directives which can be multi-line
1245 48a <mode:add-hash-defines[1](language
\v\v), lang=> ≡
1246 ________________________________________________________________________
1247 1 | «mode:add-submode
\v(${language}
\v, ""
\v, "#"
\v) 47c»
1248 2 | modes[${language}, "#", "submodes" ]="\\\\";
1249 3 | modes[${language}, "#", "terminators"]="\n";
1250 4 | «mode:add-escapes
\v(${language}
\v, "#"
\v, "\n"
\v, "\\\\\n"
\v) 47d»
1251 |________________________________________________________________________
1255 48b <mode:quote-dollar-escape[1](language
\v, quote
\v\v), lang=> ≡
1256 ________________________________________________________________________
1257 1 | escapes[${language}, ${quote}, ++escapes[${language}, ${quote}], "s"]="\\$";
1258 2 | escapes[${language}, ${quote}, escapes[${language}, ${quote}], "r"]="\\$";
1259 |________________________________________________________________________
1262 We can add these definitions to various languages
1264 48c <mode-definitions[1](
\v), lang=> ≡ 49b⊳
1265 ________________________________________________________________________
1266 1 | «common-mode-definitions
\v("c-like"
\v) 45a»
1268 3 | «common-mode-definitions
\v("c"
\v) 45a»
1269 4 | «mode:multi-line-comments
\v("c"
\v) 47e»
1270 5 | «mode:single-line-slash-comments
\v("c"
\v) 47f»
1271 6 | «mode:add-hash-defines
\v("c"
\v) 48a»
1273 8 | «common-mode-definitions
\v("awk"
\v) 45a»
1274 9 | «mode:add-hash-comments
\v("awk"
\v) 47g»
1275 10 | «mode:add-naked-regex
\v("awk"
\v) 49a»
1276 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1277 The awk definitions should allow a comment block like this:
1279 48d <test:comment-quote[1](
\v), lang=awk> ≡
1280 ________________________________________________________________________
1281 1 | # Comment: «test:comment-text 48e»
1282 |________________________________________________________________________
1286 48e <test:comment-text[1](
\v), lang=> ≡
1287 ________________________________________________________________________
1288 1 | Now is the time for
1289 2 | the quick brown fox to bring lemonade
1291 |________________________________________________________________________
1294 to come out like this:
1296 48f <test:comment-quote:result[1](
\v), lang=> ≡
1297 ________________________________________________________________________
1298 1 | # Comment: Now is the time for
1299 2 | #the quick brown fox to bring lemonade
1301 |________________________________________________________________________
1304 The C definition for such a block should have it come out like this:
1306 48g <test:comment-quote:C-result[1](
\v), lang=> ≡
1307 ________________________________________________________________________
1308 1 | # Comment: Now is the time for\
1309 2 | the quick brown fox to bring lemonade\
1311 |________________________________________________________________________
1315 This pattern is incomplete, but meant to detect naked regular expressions in awk and perl; e.g. /.*$/, however required capabilities are not present.
1316 Current it only detects regexes anchored with ^ as used in fangle.
1317 For full regex support, modes need to be named not after their starting character, but some other more fully qualified name.
1319 49a <mode:add-naked-regex[1](language
\v\v), lang=> ≡
1320 ________________________________________________________________________
1321 1 | «mode:add-submode
\v(${language}
\v, ""
\v, "/\\^"
\v) 47c»
1322 2 | modes[${language}, "/^", "terminators"]="/";
1323 |________________________________________________________________________
1328 49b <mode-definitions[2](
\v) ⇑48c, lang=> +≡ ⊲48c 49c▿
1329 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1330 11 | «common-mode-definitions
\v("perl"
\v) 45a»
1331 12 | «mode:multi-line-comments
\v("perl"
\v) 47e»
1332 13 | «mode:add-hash-comments
\v("perl"
\v) 47g»
1333 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1334 Still need to add add s/, submode /, terminate both with //. This is likely to be impossible as perl regexes can contain perl.
1337 49c <mode-definitions[3](
\v) ⇑48c, lang=awk> +≡ ▵49b
1338 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1339 14 | «common-mode-definitions
\v("sh"
\v) 45a»
1340 15 | «mode:add-hash-comments
\v("sh"
\v) 47g»
1341 16 | «mode:quote-dollar-escape
\v("sh"
\v, "\""
\v) 48b»
1342 |________________________________________________________________________
1346 Also, the parser must return any spare text at the end that has not been processed due to a mode terminator being found.
1348 49d <test:mode-definitions[3](
\v) ⇑45c, lang=> +≡ ⊲46g 49e▿
1349 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1350 23 | rest = parse_chunk_args("c-like", "1, 2, 3) spare", a, "(");
1351 24 | if (a[1] != 1) e++;
1352 25 | if (a[2] != 2) e++;
1353 26 | if (a[3] != 3) e++;
1354 27 | if (length(a) != 3) e++;
1355 28 | if (rest != " spare") e++;
1356 29 | «pca-test.awk:summary 54c»
1357 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1358 We must also be able to parse the example given earlier.
1360 49e <test:mode-definitions[4](
\v) ⇑45c, lang=> +≡ ▵49d
1361 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1362 30 | parse_chunk_args("c-like", "things[x, y], get_other_things(a, \"(all)\"), 99", a, "(");
1363 31 | if (a[1] != "things[x, y]") e++;
1364 32 | if (a[2] != "get_other_things(a, \"(all)\")") e++;
1365 33 | if (a[3] != "99") e++;
1366 34 | if (length(a) != 3) e++;
1367 35 | «pca-test.awk:summary 54c»
1368 |________________________________________________________________________
1371 10.4 A non-recursive mode tracker
1373 The mode tracker holds its state in a stack based on a numerically indexed hash. This function, when passed an empty hash, will intialize it.
1375 50a <new_mode_tracker()[1](
\v), lang=> ≡
1376 ________________________________________________________________________
1377 1 | function new_mode_tracker(context, language, mode) {
1378 2 | context[""] = 0;
1379 3 | context[0, "language"] = language;
1380 4 | context[0, "mode"] = mode;
1382 |________________________________________________________________________
1385 Because awk functions cannot return an array, we must create the array first and pass it in, so we have a fangle macro to do this:
1387 50b <new-mode-tracker[1](context
\v, language
\v, mode
\v\v), lang=awk> ≡
1388 ________________________________________________________________________
1389 1 | «awk-delete-array
\v(context
\v) 33d»
1390 2 | new_mode_tracker(${context}, ${language}, ${mode});
1391 |________________________________________________________________________
1395 And for tracking modes, we dispatch to a mode-tracker action based on the current language
1397 50c <mode_tracker[1](
\v), lang=awk> ≡ 50d▿
1398 ________________________________________________________________________
1399 1 | function push_mode_tracker(context, language, mode,
1403 5 | if (! ("" in context)) {
1404 6 | «new-mode-tracker
\v(context
\v, language
\v, mode
\v) 50b»
1406 8 | top = context[""];
1407 9 | if (context[top, "language"] == language && mode=="") mode = context[top, "mode"];
1409 11 | context[top, "language"] = language;
1410 12 | context[top, "mode"] = mode;
1411 13 | context[""] = top;
1414 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1416 50d <mode_tracker[2](
\v) ⇑50c, lang=> +≡ ▵50c 50e▿
1417 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1418 16 | function dump_mode_tracker(context,
1421 19 | for(c=0; c <= context[""]; c++) {
1422 20 | printf(" %2d %s:%s\n", c, context[c, "language"], context[c, "mode"]) > "/dev/stderr";
1423 21 | for(d=1; ( (c, "values", d) in context); d++) {
1424 22 | printf(" %2d %s\n", d, context[c, "values", d]) > "/dev/stderr";
1428 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1430 50e <mode_tracker[3](
\v) ⇑50c, lang=> +≡ ▵50d 55a⊳
1431 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1432 26 | function finalize_mode_tracker(context)
1434 28 | if ( ("" in context) && context[""] != 0) return 0;
1437 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1438 This implies that any chunk must be syntactically whole; for instance, this is fine:
1440 51a <test:whole-chunk[1](
\v), lang=> ≡
1441 ________________________________________________________________________
1443 2 | «test:say-hello 51b»
1445 |________________________________________________________________________
1449 51b <test:say-hello[1](
\v), lang=> ≡
1450 ________________________________________________________________________
1452 |________________________________________________________________________
1455 But this is not fine; the chunk <test:hidden-else 51d> is not properly cromulent.
1457 51c <test:partial-chunk[1](
\v), lang=> ≡
1458 ________________________________________________________________________
1460 2 | «test:hidden-else 51d»
1462 |________________________________________________________________________
1466 51d <test:hidden-else[1](
\v), lang=> ≡
1467 ________________________________________________________________________
1468 1 | print "I'm fine";
1470 3 | print "I'm not";
1471 |________________________________________________________________________
1474 These tests will check for correct behaviour:
1476 51e <test:cromulence[1](
\v), lang=> ≡
1477 ________________________________________________________________________
1478 1 | echo Cromulence test
1479 2 | passtest $FANGLE -Rtest:whole-chunk $TEX_SRC &>/dev/null || ( echo "Whole chunk failed" && exit 1 )
1480 3 | failtest $FANGLE -Rtest:partial-chunk $TEX_SRC &>/dev/null || ( echo "Partial chunk failed" && exit 1 )
1481 |________________________________________________________________________
1485 We must avoid recursion as a language construct because we intend to employ mode-tracking to track language mode of emitted code, and the code is emitted from a function which is itself recursive, so instead we implement psuedo-recursion using our own stack based on a hash.
1487 51f <mode_tracker()[1](
\v), lang=awk> ≡ 51g▿
1488 ________________________________________________________________________
1489 1 | function mode_tracker(context, text, values,
1490 2 | # optional parameters
1492 4 | mode, submodes, language,
1493 5 | cindex, c, a, part, item, name, result, new_values, new_mode,
1494 6 | delimiters, terminators)
1496 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1497 We could be re-commencing with a valid context, so we need to setup the state according to the last context.
1499 51g <mode_tracker()[2](
\v) ⇑51f, lang=> +≡ ▵51f 52c⊳
1500 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1501 8 | cindex = context[""] + 0;
1502 9 | mode = context[cindex, "mode"];
1503 10 | language = context[cindex, "language" ];
1504 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1505 First we construct a single large regex combining the possible sub-modes for the current mode along with the terminators for the current mode.
1507 52a <parse_chunk_args-reset-modes[1](
\v), lang=> ≡ 52b▿
1508 ________________________________________________________________________
1509 1 | submodes=modes[language, mode, "submodes"];
1511 3 | if ((language, mode, "delimiters") in modes) {
1512 4 | delimiters = modes[language, mode, "delimiters"];
1513 5 | if (length(submodes)>0) submodes = submodes "|";
1514 6 | submodes=submodes delimiters;
1515 7 | } else delimiters="";
1516 8 | if ((language, mode, "terminators") in modes) {
1517 9 | terminators = modes[language, mode, "terminators"];
1518 10 | if (length(submodes)>0) submodes = submodes "|";
1519 11 | submodes=submodes terminators;
1520 12 | } else terminators="";
1521 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1522 If we don't find anything to match on --- probably because the language is not supported --- then we return the entire text without matching anything.
1524 52b <parse_chunk_args-reset-modes[2](
\v) ⇑52a, lang=> +≡ ▵52a
1525 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1526 13 | if (! length(submodes)) return text;
1527 |________________________________________________________________________
1531 52c <mode_tracker()[3](
\v) ⇑51f, lang=> +≡ ⊲51g 52d▿
1532 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1533 11 | «parse_chunk_args-reset-modes 52a»
1534 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1535 We then iterate the text (until there is none left) looking for sub-modes or terminators in the regex.
1537 52d <mode_tracker()[4](
\v) ⇑51f, lang=> +≡ ▵52c 52e▿
1538 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1539 12 | while((cindex >= 0) && length(text)) {
1540 13 | if (match(text, "(" submodes ")", a)) {
1541 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1542 A bug that creeps in regularly during development is bad regexes of zero length which result in an infinite loop (as no text is consumed), so I catch that right away with this test.
1544 52e <mode_tracker()[5](
\v) ⇑51f, lang=> +≡ ▵52d 52f▿
1545 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1546 14 | if (RLENGTH<1) {
1547 15 | error(sprintf("Internal error, matched zero length submode, should be impossible - likely regex computation error\n" \
1548 16 | "Language=%s\nmode=%s\nmatch=%s\n", language, mode, submodes));
1550 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1551 part is defined as the text up to the sub-mode or terminator, and this is appended to item --- which is the current text being gathered. If a mode has a delimiter, then item is reset each time a delimiter is found.
1552 ("hello_item, there_item")<wide-overbrace>^item, (he said.)<wide-overbrace>^item
1554 52f <mode_tracker()[6](
\v) ⇑51f, lang=> +≡ ▵52e 52g▿
1555 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1556 18 | part = substr(text, 1, RSTART -1);
1557 19 | item = item part;
1558 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1559 We must now determine what was matched. If it was a terminator, then we must restore the previous mode.
1561 52g <mode_tracker()[7](
\v) ⇑51f, lang=> +≡ ▵52f 53a⊳
1562 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1563 20 | if (match(a[1], "^" terminators "$")) {
1564 21 | #printf("%2d EXIT MODE [%s] by [%s] [%s]\n", cindex, mode, a[1], text) > "/dev/stderr"
1565 22 | context[cindex, "values", ++context[cindex, "values"]] = item;
1566 23 | delete context[cindex];
1567 24 | context[""] = --cindex;
1568 25 | if (cindex>=0) {
1569 26 | mode = context[cindex, "mode"];
1570 27 | language = context[cindex, "language"];
1571 28 | «parse_chunk_args-reset-modes 52a»
1573 30 | item = item a[1];
1574 31 | text = substr(text, 1 + length(part) + length(a[1]));
1576 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1577 If a delimiter was matched, then we must store the current item in the parsed values array, and reset the item.
1579 53a <mode_tracker()[8](
\v) ⇑51f, lang=> +≡ ⊲52g 53b▿
1580 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1581 33 | else if (match(a[1], "^" delimiters "$")) {
1582 34 | if (cindex==0) {
1583 35 | context[cindex, "values", ++context[cindex, "values"]] = item;
1586 38 | item = item a[1];
1588 40 | text = substr(text, 1 + length(part) + length(a[1]));
1590 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1591 otherwise, if a new submode is detected (all submodes have terminators), we must create a nested parse context until we find the terminator for this mode.
1593 53b <mode_tracker()[9](
\v) ⇑51f, lang=> +≡ ▵53a 53c▿
1594 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1595 42 | else if ((language, a[1], "terminators") in modes) {
1596 43 | #check if new_mode is defined
1597 44 | item = item a[1];
1598 45 | #printf("%2d ENTER MODE [%s] in [%s]\n", cindex, a[1], text) > "/dev/stderr"
1599 46 | text = substr(text, 1 + length(part) + length(a[1]));
1600 47 | context[""] = ++cindex;
1601 48 | context[cindex, "mode"] = a[1];
1602 49 | context[cindex, "language"] = language;
1604 51 | «parse_chunk_args-reset-modes 52a»
1606 53 | error(sprintf("Submode '%s' set unknown mode in text: %s\nLanguage %s Mode %s\n", a[1], text, language, mode));
1607 54 | text = substr(text, 1 + length(part) + length(a[1]));
1610 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1611 In the final case, we parsed to the end of the string. If the string was entire, then we should have no nested mode context, but if the string was just a fragment we may have a mode context which must be preserved for the next fragment. Todo: Consideration ought to be given if sub-mode strings are split over two fragments.
1613 53c <mode_tracker()[10](
\v) ⇑51f, lang=> +≡ ▵53b
1614 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1616 58 | context[cindex, "values", ++context[cindex, "values"]] = item text;
1622 64 | context["item"] = item;
1624 66 | if (length(item)) context[cindex, "values", ++context[cindex, "values"]] = item;
1627 |________________________________________________________________________
1630 10.4.3.1 One happy chunk
1631 All the mode tracker chunks are referred to here:
1633 54a <mode-tracker[1](
\v), lang=> ≡
1634 ________________________________________________________________________
1635 1 | «new_mode_tracker() 50a»
1636 2 | «mode_tracker() 51f»
1637 |________________________________________________________________________
1641 We can test this function like this:
1643 54b <pca-test.awk[1](
\v), lang=awk> ≡
1644 ________________________________________________________________________
1646 2 | «mode-tracker 54a»
1647 3 | «parse_chunk_args() ?»
1650 6 | «mode-definitions 48c»
1652 8 | «test:mode-definitions 45c»
1654 |________________________________________________________________________
1658 54c <pca-test.awk:summary[1](
\v), lang=awk> ≡
1659 ________________________________________________________________________
1661 2 | printf "Failed " e
1663 4 | print "a[" b "] => " a[b];
1670 |________________________________________________________________________
1673 which should give this output:
1675 54d <pca-test.awk-results[1](
\v), lang=> ≡
1676 ________________________________________________________________________
1677 1 | a[foo.quux.quirk] =>
1678 2 | a[foo.quux.a] => fleeg
1679 3 | a[foo.bar] => baz
1681 5 | a[name] => freddie
1682 |________________________________________________________________________
1685 10.5 Escaping and Quoting
1686 For the time being and to get around TeXmacs inability to export a TAB character, the right arrow ↦ whose UTF-8 sequence is ...
1689 Another special character is used, the left-arrow ↤ with UTF-8 sequence 0xE2 0x86 0xA4 is used to strip any preceding white space as a way of un-tabbing and removing indent that has been applied — this is important for bash here documents, and the like. It's a filthy hack.
1690 To do: remove the hack
1693 55a <mode_tracker[4](
\v) ⇑50c, lang=> +≡ ⊲50e 55b▿
1694 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1696 31 | function untab(text) {
1697 32 | gsub("[[:space:]]*\xE2\x86\xA4","", text);
1700 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1701 Each nested mode can optionally define a set of transforms to be applied to any text that is included from another language.
1702 This code can perform transforms
1704 55b <mode_tracker[5](
\v) ⇑50c, lang=awk> +≡ ▵55a 55c▿
1705 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1706 35 | function transform_escape(s, r, text,
1712 41 | for(c=1; c <= max && (c in s); c++) {
1713 42 | gsub(s[c], r[c], text);
1717 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1718 This function must append from index c onwards, and escape transforms from the supplied context, and return c + number of new transforms.
1720 55c <mode_tracker[6](
\v) ⇑50c, lang=awk> +≡ ▵55b
1721 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1722 46 | function mode_escaper(context, s, r, src,
1725 49 | for(c = context[""]; c >= 0; c--) {
1726 50 | if ( (context[c, "language"], context[c, "mode"]) in escapes) {
1727 51 | cpl = escapes[context[c, "language"], context[c, "mode"]];
1728 52 | for (cp = 1; cp <= cpl; cp ++) {
1730 54 | s[src] = escapes[context[c, "language"], context[c, "mode"], cp, "s"];
1731 55 | r[src] = escapes[context[c, "language"], context[c, "mode"], cp, "r"];
1737 61 | function dump_escaper(c, s, r, cc) {
1738 62 | for(cc=1; cc<=c; cc++) {
1739 63 | printf("%2d s[%s] r[%s]\n", cc, s[cc], r[cc]) > "/dev/stderr"
1742 |________________________________________________________________________
1746 55d <test:escapes[1](
\v), lang=sh> ≡
1747 ________________________________________________________________________
1748 1 | echo escapes test
1749 2 | passtest $FANGLE -Rtest:comment-quote $TEX_SRC &>/dev/null || ( echo "Comment-quote failed" && exit 1 )
1750 |________________________________________________________________________
1753 Chapter 11Recognizing Chunks
1754 Fangle recognizes noweb chunks, but as we also want better LaTeX integration we will recognize any of these:
1755 • notangle chunks matching the pattern ^<<.*?>>=
1756 • chunks beginning with \begin{lstlistings}, possibly with \Chunk{...} on the previous line
1757 • an older form I have used, beginning with \begin{Chunk}[options] --- also more suitable for plain LaTeX users1. Is there such a thing as plain LaTeX? ^1.
1759 The variable chunking is used to signify that we are processing a code chunk and not document. In such a state, input lines will be assigned to the current chunk; otherwise they are ignored.
1761 We don't handle TeXmacs files natively yet, but rather instead emit unicode character sequences to mark up the text-export file which we do process.
1762 These hacks detect the unicode character sequences and retro-fit in the old TeX parsing.
1763 We convert ↦ into a tab character.
1765 57a <recognize-chunk[1](
\v), lang=> ≡ 57b▿
1766 ________________________________________________________________________
1769 2 | # gsub("\n*$","");
1770 3 | # gsub("\n", " ");
1773 6 | /\xE2\x86\xA6/ {
1774 7 | gsub("\\xE2\\x86\\xA6", "\x09");
1776 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1777 TeXmacs back-tick handling is obscure, and a cut-n-paste back-tick from a shell window comes out as a unicode sequence2. that won't export to html, except as a NULL character (literal 0x00) ^2 that is fixed-up here.
1779 57b <recognize-chunk[2](
\v) ⇑57a, lang=> +≡ ▵57a 58a⊳
1780 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1782 10 | /\xE2\x80\x98/ {
1783 11 | gsub("\\xE2\\x80\\x98", "‘");
1785 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1786 In the TeXmacs output, the start of a chunk will appear like this:
1787 5b<example-chunk^K[1](arg1,^K arg2^K^K), lang=C> ≡
1788 We detect the the start of a TeXmacs chunk by detecting the ≡ symbol which occurs near the end of the line. We obtain the chunk name, the chunk parameters, and the chunk language.
1790 58a <recognize-chunk[3](
\v) ⇑57a, lang=> +≡ ⊲57b 58b▿
1791 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1793 14 | /\xE2\x89\xA1/ {
1794 15 | if (match($0, "^ *([^[ ]* |)<([^[ ]*)\\[[0-9]*\\][(](.*)[)].*, lang=([^ ]*)>", line)) {
1795 16 | next_chunk_name=line[2];
1796 17 | get_texmacs_chunk_args(line[3], next_chunk_params);
1797 18 | gsub(ARG_SEPARATOR ",? ?", ";", line[3]);
1798 19 | params = "params=" line[3];
1799 20 | if ((line[4])) {
1800 21 | params = params ",language=" line[4]
1802 23 | get_tex_chunk_args(params, next_chunk_opts);
1803 24 | new_chunk(next_chunk_name, next_chunk_opts, next_chunk_params);
1804 25 | texmacs_chunking = 1;
1806 27 | warning(sprintf("Unexpected chunk match: %s\n", $_))
1810 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1812 Our current scheme is to recognize the new lstlisting chunks, but these may be preceded by a \Chunk command which in L Y X is a more convenient way to pass the chunk name to the \begin{lstlistings} command, and a more visible way to specify other lstset settings.
1813 The arguments to the \Chunk command are a name, and then a comma-seperated list of key-value pairs after the manner of \lstset. (In fact within the LaTeX \Chunk macro (section 16.2.1) the text name= is prefixed to the argument which is then literally passed to \lstset).
1815 58b <recognize-chunk[4](
\v) ⇑57a, lang=awk> +≡ ▵58a 58c▿
1816 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1818 32 | if (match($0, "^\\\\Chunk{ *([^ ,}]*),?(.*)}", line)) {
1819 33 | next_chunk_name = line[1];
1820 34 | get_tex_chunk_args(line[2], next_chunk_opts);
1824 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1825 We also make a basic attempt to parse the name out of the \lstlistings[name=chunk-name] text, otherwise we fall back to the name found in the previous chunk command. This attempt is very basic and doesn't support commas or spaces or square brackets as part of the chunkname. We also recognize \begin{Chunk} which is convenient for some users3. but not yet supported in the LaTeX macros ^3.
1827 58c <recognize-chunk[5](
\v) ⇑57a, lang=> +≡ ▵58b 59a⊳
1828 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1829 38 | /^\\begin{lstlisting}|^\\begin{Chunk}/ {
1830 39 | if (match($0, "}.*[[,] *name= *{? *([^], }]*)", line)) {
1831 40 | new_chunk(line[1]);
1833 42 | new_chunk(next_chunk_name, next_chunk_opts);
1838 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1841 A chunk body in TeXmacs ends with |________... if it is the final chunklet of a chunk, or if there are further chunklets it ends with |\/\/\/... which is a depiction of a jagged line of torn paper.
1843 59a <recognize-chunk[6](
\v) ⇑57a, lang=> +≡ ⊲58c 59b▿
1844 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1845 47 | /^ *\|____________*/ && texmacs_chunking {
1846 48 | active_chunk="";
1847 49 | texmacs_chunking=0;
1850 52 | /^ *\|\/\\/ && texmacs_chunking {
1851 53 | texmacs_chunking=0;
1853 55 | active_chunk="";
1855 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1856 It has been observed that not every line of output when a TeXmacs chunk is active is a line of chunk. This may no longer be true, but we set a variable texmacs_chunk if the current line is a chunk line.
1857 Initially we set this to zero...
1859 59b <recognize-chunk[7](
\v) ⇑57a, lang=> +≡ ▵59a 59c▿
1860 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1861 57 | texmacs_chunk=0;
1862 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1863 ...and then we look to see if the current line is a chunk line.
1864 TeXmacs lines look like this: 3 | main() { so we detect the lines by leading white space, digits, more whiter space and a vertical bar followed by at least once space.
1865 If we find such a line, we remove this line-header and set texmacs_chunk=1 as well as chunking=1
1867 59c <recognize-chunk[8](
\v) ⇑57a, lang=> +≡ ▵59b 59d▿
1868 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1869 58 | /^ *[1-9][0-9]* *\| / {
1870 59 | if (texmacs_chunking) {
1872 61 | texmacs_chunk=1;
1873 62 | gsub("^ *[1-9][0-9]* *\\| ", "")
1876 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1877 When TeXmacs chunking, lines that commence with \/ or __ are not chunk content but visual framing, and are skipped.
1879 59d <recognize-chunk[9](
\v) ⇑57a, lang=> +≡ ▵59c 60a⊳
1880 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1881 65 | /^ *\.\/\\/ && texmacs_chunking {
1884 68 | /^ *__*$/ && texmacs_chunking {
1887 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1888 Any other line when TeXmacs chunking is considered to be a line-wrapped line.
1890 60a <recognize-chunk[10](
\v) ⇑57a, lang=> +≡ ⊲59d 60b▿
1891 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1892 71 | texmacs_chunking {
1893 72 | if (! texmacs_chunk) {
1894 73 | # must be a texmacs continued line
1896 75 | texmacs_chunk=1;
1899 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1900 This final chunklet seems bogus and probably stops L Y X working.
1902 60b <recognize-chunk[11](
\v) ⇑57a, lang=> +≡ ▵60a 60c▿
1903 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1904 78 | ! texmacs_chunk {
1905 79 | # texmacs_chunking=0;
1908 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1910 We recognize notangle style chunks too:
1912 60c <recognize-chunk[12](
\v) ⇑57a, lang=awk> +≡ ▵60b 60d▿
1913 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1914 82 | /^[<]<.*[>]>=/ {
1915 83 | if (match($0, "^[<]<(.*)[>]>= *$", line)) {
1917 85 | notangle_mode=1;
1918 86 | new_chunk(line[1]);
1922 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1924 Likewise, we need to recognize when a chunk ends.
1926 The e in [e]nd{lislisting} is surrounded by square brackets so that when this document is processed, this chunk doesn't terminate early when the lstlistings package recognizes it's own end-string!4. This doesn't make sense as the regex is anchored with ^, which this line does not begin with! ^4
1928 60d <recognize-chunk[13](
\v) ⇑57a, lang=> +≡ ▵60c 61a⊳
1929 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1930 90 | /^\\[e]nd{lstlisting}|^\\[e]nd{Chunk}/ {
1932 92 | active_chunk="";
1935 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1938 61a <recognize-chunk[14](
\v) ⇑57a, lang=> +≡ ⊲60d 61b▿
1939 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1942 97 | active_chunk="";
1944 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1945 All other recognizers are only of effect if we are chunking; there's no point in looking at lines if they aren't part of a chunk, so we just ignore them as efficiently as we can.
1947 61b <recognize-chunk[15](
\v) ⇑57a, lang=> +≡ ▵61a 61c▿
1948 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1949 99 | ! chunking { next; }
1950 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1952 Chunk contents are any lines read while chunking is true. Some chunk contents are special in that they refer to other chunks, and will be replaced by the contents of these chunks when the file is generated.
1953 We add the output record separator ORS to the line now, because we will set ORS to the empty string when we generate the output5. So that we can partial print lines using print instead of printf.
1954 To do: This does't make sense
1957 61c <recognize-chunk[16](
\v) ⇑57a, lang=> +≡ ▵61b
1958 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
1959 100 | length(active_chunk) {
1960 101 | «process-chunk-tabs 61e»
1961 102 | «process-chunk 62b»
1963 |________________________________________________________________________
1966 If a chunk just consisted of plain text, we could handle the chunk like this:
1968 61d <process-chunk-simple[1](
\v), lang=> ≡
1969 ________________________________________________________________________
1970 1 | chunk_line(active_chunk, $0 ORS);
1971 |________________________________________________________________________
1974 but in fact a chunk can include references to other chunks. Chunk includes are traditionally written as <<chunk-name>> but we support other variations, some of which are more suitable for particular editing systems.
1975 However, we also process tabs at this point. A tab at input can be replaced by a number of spaces defined by the tabs variable, set by the -T option. Of course this is poor tab behaviour, we should probably have the option to use proper counted tab-stops and process this on output.
1977 61e <process-chunk-tabs[1](
\v), lang=> ≡
1978 ________________________________________________________________________
1979 1 | if (length(tabs)) {
1980 2 | gsub("\t", tabs);
1982 |________________________________________________________________________
1986 If \lstset{escapeinside={=<}{>}} is set, then we can use <chunk-name ?> in listings. The sequence =< was chosen because:
1987 1.it is a better mnemonic than <<chunk-name>> in that the = sign signifies equivalence or substitutability.
1988 2.and because =< is not valid in C or any language I can think of.
1989 3.and also because lstlistings doesn't like >> as an end delimiter for the texcl escape, so we must make do with a single > which is better complemented by =< than by <<.
1990 Unfortunately the =<...> that we use re-enters a LaTeX parsing mode in which some characters are special, e.g. # \ and so these cause trouble if used in arguments to \chunkref. At some point I must fix the LaTeX command \chunkref so that it can accept these literally, but until then, when writing chunkref argumemts that need these characters, I must use the forms \textbackslash{} and \#; so I also define a hacky chunk delatex to be used further on whose purpose it is to remove these from any arguments parsed by fangle.
1992 62a <delatex[1](text
\v\v), lang=> ≡
1993 ________________________________________________________________________
1995 2 | gsub("\\\\#", "#", ${text});
1996 3 | gsub("\\\\textbackslash{}", "\\", ${text});
1997 4 | gsub("\\\\\\^", "^", ${text});
1998 |________________________________________________________________________
2001 As each chunk line may contain more than one chunk include, we will split out chunk includes in an iterative fashion6. Contrary to our use of split when substituting parameters in chapter ? ^6.
2002 First, as long as the chunk contains a \chunkref command we take as much as we can up to the first \chunkref command.
2003 TeXmacs text output uses ⟨...⟩ which comes out as unicode sequences 0xC2 0xAB ... 0xC2 0xBB. Modern awk will interpret [^\xC2\xBB] as a single unicode character if LANG is set correctly to the sub-type UTF-8, e.g. LANG=en_GB.UTF-8, otherwise [^\xC2\xBB] will be treated as a two character negated match — but this should not interfere with the function.
2005 62b <process-chunk[1](
\v), lang=> ≡ 62c▿
2006 ________________________________________________________________________
2009 3 | while(match(chunk,"(\xC2\xAB)([^\xC2\xBB]*) [^\xC2\xBB]*\xC2\xBB", line) ||
2011 5 | "([=]<\\\\chunkref{([^}>]*)}(\\(.*\\)|)>|<<([a-zA-Z_][-a-zA-Z0-9_]*)>>)",
2014 8 | chunklet = substr(chunk, 1, RSTART - 1);
2015 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2016 We keep track of the indent count, by counting the number of literal characters found. We can then preserve this indent on each output line when multi-line chunks are expanded.
2017 We then process this first part literal text, and set the chunk which is still to be processed to be the text after the \chunkref command, which we will process next as we continue around the loop.
2019 62c <process-chunk[2](
\v) ⇑62b, lang=> +≡ ▵62b 63a⊳
2020 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2021 9 | indent += length(chunklet);
2022 10 | chunk_line(active_chunk, chunklet);
2023 11 | chunk = substr(chunk, RSTART + RLENGTH);
2024 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2025 We then consider the type of chunk command we have found, whether it is the fangle style command beginning with =< the older notangle style beginning with <<.
2026 Fangle chunks may have parameters contained within square brackets. These will be matched in line[3] and are considered at this stage of processing to be part of the name of the chunk to be included.
2028 63a <process-chunk[3](
\v) ⇑62b, lang=> +≡ ⊲62c 63b▿
2029 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2030 12 | if (substr(line[1], 1, 1) == "=") {
2031 13 | # chunk name up to }
2032 14 | «delatex
\v(line[3]
\v) 62a»
2033 15 | chunk_include(active_chunk, line[2] line[3], indent);
2034 16 | } else if (substr(line[1], 1, 1) == "<") {
2035 17 | chunk_include(active_chunk, line[4], indent);
2036 18 | } else if (line[1] == "\xC2\xAB") {
2037 19 | chunk_include(active_chunk, line[2], indent);
2039 21 | error("Unknown chunk fragment: " line[1]);
2041 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2042 The loop will continue until there are no more chunkref statements in the text, at which point we process the final part of the chunk.
2044 63b <process-chunk[4](
\v) ⇑62b, lang=> +≡ ▵63a 63c▿
2045 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2047 24 | chunk_line(active_chunk, chunk);
2048 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2049 We add the newline character as a chunklet on it's own, to make it easier to detect new lines and thus manage indentation when processing the output.
2051 63c <process-chunk[5](
\v) ⇑62b, lang=> +≡ ▵63b
2052 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2053 25 | chunk_line(active_chunk, "\n");
2054 |________________________________________________________________________
2057 We will also permit a chunk-part number to follow in square brackets, so that <chunk-name[1] ?> will refer to the first part only. This can make it easy to include a C function prototype in a header file, if the first part of the chunk is just the function prototype without the trailing semi-colon. The header file would include the prototype with the trailing semi-colon, like this:
2059 This is handled in section 13.1.1
2060 We should perhaps introduce a notion of language specific chunk options; so that perhaps we could specify:
2061 =<\chunkref{chunk-name[function-declaration]}
2062 which applies a transform function-declaration to the chunk --- which in this case would extract a function prototype from a function.
2065 Chapter 12Processing Options
2066 At the start, first we set the default options.
2068 65a <default-options[1](
\v), lang=> ≡
2069 ________________________________________________________________________
2072 3 | notangle_mode=0;
2075 |________________________________________________________________________
2078 Then we use getopt the standard way, and null out ARGV afterwards in the normal AWK fashion.
2080 65b <read-options[1](
\v), lang=> ≡
2081 ________________________________________________________________________
2082 1 | Optind = 1 # skip ARGV[0]
2083 2 | while(getopt(ARGC, ARGV, "R:LdT:hr")!=-1) {
2084 3 | «handle-options 65c»
2086 5 | for (i=1; i<Optind; i++) { ARGV[i]=""; }
2087 |________________________________________________________________________
2090 This is how we handle our options:
2092 65c <handle-options[1](
\v), lang=> ≡
2093 ________________________________________________________________________
2094 1 | if (Optopt == "R") root = Optarg;
2095 2 | else if (Optopt == "r") root="";
2096 3 | else if (Optopt == "L") linenos = 1;
2097 4 | else if (Optopt == "d") debug = 1;
2098 5 | else if (Optopt == "T") tabs = indent_string(Optarg+0);
2099 6 | else if (Optopt == "h") help();
2100 7 | else if (Optopt == "?") help();
2101 |________________________________________________________________________
2104 We do all of this at the beginning of the program
2106 65d <begin[1](
\v), lang=> ≡
2107 ________________________________________________________________________
2110 3 | «mode-definitions 48c»
2111 4 | «default-options 65a»
2113 6 | «read-options 65b»
2115 |________________________________________________________________________
2118 And have a simple help function
2120 65e <help()[1](
\v), lang=> ≡
2121 ________________________________________________________________________
2122 1 | function help() {
2124 3 | print " fangle [-L] -R<rootname> [source.tex ...]"
2125 4 | print " fangle -r [source.tex ...]"
2126 5 | print " If the filename, source.tex is not specified then stdin is used"
2128 7 | print "-L causes the C statement: #line <lineno> \"filename\"" to be issued"
2129 8 | print "-R causes the named root to be written to stdout"
2130 9 | print "-r lists all roots in the file (even those used elsewhere)"
2133 |________________________________________________________________________
2136 Chapter 13Generating the Output
2137 We generate output by calling output_chunk, or listing the chunk names.
2139 67a <generate-output[1](
\v), lang=> ≡
2140 ________________________________________________________________________
2141 1 | if (length(root)) output_chunk(root);
2142 2 | else output_chunk_names();
2143 |________________________________________________________________________
2146 We also have some other output debugging:
2148 67b <debug-output[1](
\v), lang=> ≡
2149 ________________________________________________________________________
2151 2 | print "------ chunk names "
2152 3 | output_chunk_names();
2153 4 | print "====== chunks"
2154 5 | output_chunks();
2155 6 | print "++++++ debug"
2156 7 | for (a in chunks) {
2157 8 | print a "=" chunks[a];
2160 |________________________________________________________________________
2163 We do both of these at the end. We also set ORS="" because each chunklet is not necessarily a complete line, and we already added ORS to each input line in section 11.4.
2165 67c <end[1](
\v), lang=> ≡
2166 ________________________________________________________________________
2168 2 | «debug-output 67b»
2170 4 | «generate-output 67a»
2172 |________________________________________________________________________
2175 We write chunk names like this. If we seem to be running in notangle compatibility mode, then we enclose the name like this <<name>> the same way notangle does:
2177 67d <output_chunk_names()[1](
\v), lang=> ≡
2178 ________________________________________________________________________
2179 1 | function output_chunk_names( c, prefix, suffix)
2181 3 | if (notangle_mode) {
2185 7 | for (c in chunk_names) {
2186 8 | print prefix c suffix "\n";
2189 |________________________________________________________________________
2192 This function would write out all chunks
2194 67e <output_chunks()[1](
\v), lang=> ≡
2195 ________________________________________________________________________
2196 1 | function output_chunks( a)
2198 3 | for (a in chunk_names) {
2199 4 | output_chunk(a);
2203 8 | function output_chunk(chunk) {
2205 10 | lineno_needed = linenos;
2207 12 | write_chunk(chunk);
2210 |________________________________________________________________________
2213 13.1 Assembling the Chunks
2214 chunk_path holds a string consisting of the names of all the chunks that resulted in this chunk being output. It should probably also contain the source line numbers at which each inclusion also occured.
2215 We first initialize the mode tracker for this chunk.
2217 68a <write_chunk()[1](
\v), lang=> ≡ 68b▿
2218 ________________________________________________________________________
2219 1 | function write_chunk(chunk_name) {
2220 2 | «awk-delete-array
\v(context
\v) 33d»
2221 3 | return write_chunk_r(chunk_name, context);
2224 6 | function write_chunk_r(chunk_name, context, indent, tail,
2226 8 | chunk_path, chunk_args,
2227 9 | s, r, src, new_src,
2229 11 | chunk_params, part, max_part, part_line, frag, max_frag, text,
2230 12 | chunklet, only_part, call_chunk_args, new_context)
2232 14 | if (debug) debug_log("write_chunk_r(" chunk_name ")");
2233 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2235 As mentioned in section ?, a chunk name may contain a part specifier in square brackets, limiting the parts that should be emitted.
2237 68b <write_chunk()[2](
\v) ⇑68a, lang=> +≡ ▵68a 68c▿
2238 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2239 15 | if (match(chunk_name, "^(.*)\\[([0-9]*)\\]$", chunk_name_parts)) {
2240 16 | chunk_name = chunk_name_parts[1];
2241 17 | only_part = chunk_name_parts[2];
2243 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2244 We then create a mode tracker
2246 68c <write_chunk()[3](
\v) ⇑68a, lang=> +≡ ▵68b 69a⊳
2247 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2248 19 | «new-mode-tracker
\v(context
\v, chunks[chunk_name, "language"]
\v, ""
\v) 50b»
2249 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2250 We extract into chunk_params the names of the parameters that this chunk accepts, whose values were (optionally) passed in chunk_args.
2252 69a <write_chunk()[4](
\v) ⇑68a, lang=> +≡ ⊲68c 69b▿
2253 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2254 20 | split(chunks[chunk_name, "params"], chunk_params, " *; *");
2255 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2256 To assemble a chunk, we write out each part.
2258 69b <write_chunk()[5](
\v) ⇑68a, lang=> +≡ ▵69a
2259 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2260 21 | if (! (chunk_name in chunk_names)) {
2261 22 | error(sprintf(_"The root module <<%s>> was not defined.\nUsed by: %s",\
2262 23 | chunk_name, chunk_path));
2265 26 | max_part = chunks[chunk_name, "part"];
2266 27 | for(part = 1; part <= max_part; part++) {
2267 28 | if (! only_part || part == only_part) {
2268 29 | «write-part 69c»
2271 32 | if (! finalize_mode_tracker(context)) {
2272 33 | dump_mode_tracker(context);
2273 34 | error(sprintf(_"Module %s did not close context properly.\nUsed by: %s\n", chunk_name, chunk_path));
2276 |________________________________________________________________________
2279 A part can either be a chunklet of lines, or an include of another chunk.
2280 Chunks may also have parameters, specified in LaTeX style with braces after the chunk name --- looking like this in the document: chunkname{param1, param2}. Arguments are passed in square brackets: \chunkref{chunkname}[arg1, arg2].
2281 Before we process each part, we check that the source position hasn't changed unexpectedly, so that we can know if we need to output a new file-line directive.
2283 69c <write-part[1](
\v), lang=> ≡
2284 ________________________________________________________________________
2285 1 | «check-source-jump 71d»
2287 3 | chunklet = chunks[chunk_name, "part", part];
2288 4 | if (chunks[chunk_name, "part", part, "type"] == part_type_chunk) {
2289 5 | «write-included-chunk 69d»
2290 6 | } else if (chunklet SUBSEP "line" in chunks) {
2291 7 | «write-chunklets 70a»
2293 9 | # empty last chunklet
2295 |________________________________________________________________________
2298 To write an included chunk, we must detect any optional chunk arguments in parenthesis. Then we recurse calling write_chunk().
2300 69d <write-included-chunk[1](
\v), lang=> ≡
2301 ________________________________________________________________________
2302 1 | if (match(chunklet, "^([^\\[\\(]*)\\((.*)\\)$", chunklet_parts)) {
2303 2 | chunklet = chunklet_parts[1];
2305 4 | gsub(sprintf("%c",11), "", chunklet);
2306 5 | gsub(sprintf("%c",11), "", chunklet_parts[2]);
2307 6 | parse_chunk_args("c-like", chunklet_parts[2], call_chunk_args, "(");
2308 7 | for (c in call_chunk_args) {
2309 8 | call_chunk_args[c] = expand_chunk_args(call_chunk_args[c], chunk_params, chunk_args);
2312 11 | split("", call_chunk_args);
2314 13 | # update the transforms arrays
2315 14 | new_src = mode_escaper(context, s, r, src);
2316 15 | «awk-delete-array
\v(new_context
\v) 33d»
2317 16 | write_chunk_r(chunklet, new_context,
2318 17 | chunks[chunk_name, "part", part, "indent"] indent,
2319 18 | chunks[chunk_name, "part", part, "tail"],
2320 19 | chunk_path "\n " chunk_name,
2321 20 | call_chunk_args,
2322 21 | s, r, new_src);
2323 |________________________________________________________________________
2326 Before we output a chunklet of lines, we first emit the file and line number if we have one, and if it is safe to do so.
2327 Chunklets are generally broken up by includes, so the start of a chunklet is a good place to do this. Then we output each line of the chunklet.
2328 When it is not safe, such as in the middle of a multi-line macro definition, lineno_suppressed is set to true, and in such a case we note that we want to emit the line statement when it is next safe.
2330 70a <write-chunklets[1](
\v), lang=> ≡ 70b▿
2331 ________________________________________________________________________
2332 1 | max_frag = chunks[chunklet, "line"];
2333 2 | for(frag = 1; frag <= max_frag; frag++) {
2334 3 | «write-file-line 71c»
2335 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2336 We then extract the chunklet text and expand any arguments.
2338 70b <write-chunklets[2](
\v) ⇑70a, lang=> +≡ ▵70a 70c▿
2339 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2341 5 | text = chunks[chunklet, frag];
2343 7 | /* check params */
2344 8 | text = expand_chunk_args(text, chunk_params, chunk_args);
2345 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2346 If the text is a single newline (which we keep separate - see 6) then we increment the line number. In the case where this is the last line of a chunk and it is not a top-level chunk we replace the newline with an empty string --- because the chunk that included this chunk will have the newline at the end of the line that included this chunk.
2347 We also note by newline = 1 that we have started a new line, so that indentation can be managed with the following piece of text.
2349 70c <write-chunklets[3](
\v) ⇑70a, lang=> +≡ ▵70b 70d▿
2350 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2352 10 | if (text == "\n") {
2354 12 | if (part == max_part && frag == max_frag && length(chunk_path)) {
2360 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2361 If this text does not represent a newline, but we see that we are the first piece of text on a newline, then we prefix our text with the current indent.
2362 Note 1. newline is a global output-state variable, but the indent is not.
2364 70d <write-chunklets[4](
\v) ⇑70a, lang=> +≡ ▵70c 71a⊳
2365 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2366 18 | } else if (length(text) || length(tail)) {
2367 19 | if (newline) text = indent text;
2371 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2372 Tail will soon no longer be relevant once mode-detection is in place.
2374 71a <write-chunklets[5](
\v) ⇑70a, lang=> +≡ ⊲70d 71b▿
2375 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2376 23 | text = text tail;
2377 24 | mode_tracker(context, text);
2378 25 | print untab(transform_escape(s, r, text, src));
2379 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2380 If a line ends in a backslash --- suggesting continuation --- then we supress outputting file-line as it would probably break the continued lines.
2382 71b <write-chunklets[6](
\v) ⇑70a, lang=> +≡ ▵71a
2383 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2385 27 | lineno_suppressed = substr(lastline, length(lastline)) == "\\";
2388 |________________________________________________________________________
2391 Of course there is no point in actually outputting the source filename and line number (file-line) if they don't say anything new! We only need to emit them if they aren't what is expected, or if we we not able to emit one when they had changed.
2393 71c <write-file-line[1](
\v), lang=> ≡
2394 ________________________________________________________________________
2395 1 | if (newline && lineno_needed && ! lineno_suppressed) {
2396 2 | filename = a_filename;
2397 3 | lineno = a_lineno;
2398 4 | print "#line " lineno " \"" filename "\"\n"
2399 5 | lineno_needed = 0;
2401 |________________________________________________________________________
2404 We check if a new file-line is needed by checking if the source line matches what we (or a compiler) would expect.
2406 71d <check-source-jump[1](
\v), lang=> ≡
2407 ________________________________________________________________________
2408 1 | if (linenos && (chunk_name SUBSEP "part" SUBSEP part SUBSEP "FILENAME" in chunks)) {
2409 2 | a_filename = chunks[chunk_name, "part", part, "FILENAME"];
2410 3 | a_lineno = chunks[chunk_name, "part", part, "LINENO"];
2411 4 | if (a_filename != filename || a_lineno != lineno) {
2412 5 | lineno_needed++;
2415 |________________________________________________________________________
2418 Chapter 14Storing Chunks
2419 Awk has pretty limited data structures, so we will use two main hashes. Uninterrupted sequences of a chunk will be stored in chunklets and the chunklets used in a chunk will be stored in chunks.
2421 73a <constants[2](
\v) ⇑35a, lang=> +≡ ⊲35a
2422 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2423 2 | part_type_chunk=1;
2425 |________________________________________________________________________
2428 The params mentioned are not chunk parameters for parameterized chunks, as mentioned in 9.2, but the lstlistings style parameters used in the \Chunk command1. The params parameter is used to hold the parameters for parameterized chunks ^1.
2430 73b <chunk-storage-functions[1](
\v), lang=> ≡ 73c▿
2431 ________________________________________________________________________
2432 1 | function new_chunk(chunk_name, opts, args,
2436 5 | # HACK WHILE WE CHANGE TO ( ) for PARAM CHUNKS
2437 6 | gsub("\\(\\)$", "", chunk_name);
2438 7 | if (! (chunk_name in chunk_names)) {
2439 8 | if (debug) print "New chunk " chunk_name;
2440 9 | chunk_names[chunk_name];
2441 10 | for (p in opts) {
2442 11 | chunks[chunk_name, p] = opts[p];
2443 12 | if (debug) print "chunks[" chunk_name "," p "] = " opts[p];
2445 14 | for (p in args) {
2446 15 | chunks[chunk_name, "params", p] = args[p];
2448 17 | if ("append" in opts) {
2449 18 | append=opts["append"];
2450 19 | if (! (append in chunk_names)) {
2451 20 | warning("Chunk " chunk_name " is appended to chunk " append " which is not defined yet");
2452 21 | new_chunk(append);
2454 23 | chunk_include(append, chunk_name);
2455 24 | chunk_line(append, ORS);
2458 27 | active_chunk = chunk_name;
2459 28 | prime_chunk(chunk_name);
2461 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2463 73c <chunk-storage-functions[2](
\v) ⇑73b, lang=> +≡ ▵73b 74a⊳
2464 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2466 31 | function prime_chunk(chunk_name)
2468 33 | chunks[chunk_name, "part", ++chunks[chunk_name, "part"] ] = \
2469 34 | chunk_name SUBSEP "chunklet" SUBSEP "" ++chunks[chunk_name, "chunklet"];
2470 35 | chunks[chunk_name, "part", chunks[chunk_name, "part"], "FILENAME"] = FILENAME;
2471 36 | chunks[chunk_name, "part", chunks[chunk_name, "part"], "LINENO"] = FNR + 1;
2474 39 | function chunk_line(chunk_name, line){
2475 40 | chunks[chunk_name, "chunklet", chunks[chunk_name, "chunklet"],
2476 41 | ++chunks[chunk_name, "chunklet", chunks[chunk_name, "chunklet"], "line"] ] = line;
2479 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2480 Chunk include represents a chunkref statement, and stores the requirement to include another chunk. The parameter indent represents the quanity of literal text characters that preceded this chunkref statement and therefore by how much additional lines of the included chunk should be indented.
2482 74a <chunk-storage-functions[3](
\v) ⇑73b, lang=> +≡ ⊲73c 74b▿
2483 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2484 44 | function chunk_include(chunk_name, chunk_ref, indent, tail)
2486 46 | chunks[chunk_name, "part", ++chunks[chunk_name, "part"] ] = chunk_ref;
2487 47 | chunks[chunk_name, "part", chunks[chunk_name, "part"], "type" ] = part_type_chunk;
2488 48 | chunks[chunk_name, "part", chunks[chunk_name, "part"], "indent" ] = indent_string(indent);
2489 49 | chunks[chunk_name, "part", chunks[chunk_name, "part"], "tail" ] = tail;
2490 50 | prime_chunk(chunk_name);
2493 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2494 The indent is calculated by indent_string, which may in future convert some spaces into tab characters. This function works by generating a printf padded format string, like %22s for an indent of 22, and then printing an empty string using that format.
2496 74b <chunk-storage-functions[4](
\v) ⇑73b, lang=> +≡ ▵74a
2497 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2498 53 | function indent_string(indent) {
2499 54 | return sprintf("%" indent "s", "");
2501 |________________________________________________________________________
2505 I use Arnold Robbins public domain getopt (1993 revision). This is probably the same one that is covered in chapter 12 of âĂIJEdition 3 of GAWK: Effective AWK Programming: A User's Guide for GNU AwkâĂİ but as that is licensed under the GNU Free Documentation License, Version 1.3, which conflicts with the GPL3, I can't use it from there (or it's accompanying explanations), so I do my best to explain how it works here.
2506 The getopt.awk header is:
2508 75a <getopt.awk-header[1](
\v), lang=> ≡
2509 ________________________________________________________________________
2510 1 | # getopt.awk --- do C library getopt(3) function in awk
2512 3 | # Arnold Robbins, arnold@skeeve.com, Public Domain
2514 5 | # Initial version: March, 1991
2515 6 | # Revised: May, 1993
2517 |________________________________________________________________________
2520 The provided explanation is:
2522 75b <getopt.awk-notes[1](
\v), lang=> ≡
2523 ________________________________________________________________________
2524 1 | # External variables:
2525 2 | # Optind -- index in ARGV of first nonoption argument
2526 3 | # Optarg -- string value of argument to current option
2527 4 | # Opterr -- if nonzero, print our own diagnostic
2528 5 | # Optopt -- current option letter
2531 8 | # -1 at end of options
2532 9 | # ? for unrecognized option
2533 10 | # <c> a character representing the current option
2535 12 | # Private Data:
2536 13 | # _opti -- index in multi-flag option, e.g., -abc
2538 |________________________________________________________________________
2541 The function follows. The final two parameters, thisopt and i are local variables and not parameters --- as indicated by the multiple spaces preceding them. Awk doesn't care, the multiple spaces are a convention to help us humans.
2543 75c <getopt.awk-getopt()[1](
\v), lang=> ≡ 76a⊳
2544 ________________________________________________________________________
2545 1 | function getopt(argc, argv, options, thisopt, i)
2547 3 | if (length(options) == 0) # no options given
2549 5 | if (argv[Optind] == "--") { # all done
2553 9 | } else if (argv[Optind] !~ /^-[^: \t\n\f\r\v\b]/) {
2557 13 | if (_opti == 0)
2559 15 | thisopt = substr(argv[Optind], _opti, 1)
2560 16 | Optopt = thisopt
2561 17 | i = index(options, thisopt)
2564 20 | printf("%c -- invalid option\n",
2565 21 | thisopt) > "/dev/stderr"
2566 22 | if (_opti >= length(argv[Optind])) {
2573 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2574 At this point, the option has been found and we need to know if it takes any arguments.
2576 76a <getopt.awk-getopt()[2](
\v) ⇑75c, lang=> +≡ ⊲75c
2577 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2578 29 | if (substr(options, i + 1, 1) == ":") {
2579 30 | # get option argument
2580 31 | if (length(substr(argv[Optind], _opti + 1)) > 0)
2581 32 | Optarg = substr(argv[Optind], _opti + 1)
2583 34 | Optarg = argv[++Optind]
2587 38 | if (_opti == 0 || _opti >= length(argv[Optind])) {
2594 |________________________________________________________________________
2597 A test program is built in, too
2599 76b <getopt.awk-begin[1](
\v), lang=> ≡
2600 ________________________________________________________________________
2602 2 | Opterr = 1 # default is to diagnose
2603 3 | Optind = 1 # skip ARGV[0]
2605 5 | if (_getopt_test) {
2606 6 | while ((_go_c = getopt(ARGC, ARGV, "ab:cd")) != -1)
2607 7 | printf("c = <%c>, optarg = <%s>\n",
2609 9 | printf("non-option arguments:\n")
2610 10 | for (; Optind < ARGC; Optind++)
2611 11 | printf("\tARGV[%d] = <%s>\n",
2612 12 | Optind, ARGV[Optind])
2615 |________________________________________________________________________
2618 The entire getopt.awk is made out of these chunks in order
2620 76c <getopt.awk[1](
\v), lang=> ≡
2621 ________________________________________________________________________
2622 1 | «getopt.awk-header 75a»
2624 3 | «getopt.awk-notes 75b»
2625 4 | «getopt.awk-getopt() 75c»
2626 5 | «getopt.awk-begin 76b»
2627 |________________________________________________________________________
2630 Although we only want the header and function:
2632 77a <getopt[1](
\v), lang=> ≡
2633 ________________________________________________________________________
2634 1 | # try: locate getopt.awk for the full original file
2635 2 | # as part of your standard awk installation
2636 3 | «getopt.awk-header 75a»
2638 5 | «getopt.awk-getopt() 75c»
2639 |________________________________________________________________________
2642 Chapter 16Fangle LaTeX source code
2644 Here we define a L Y X .module file that makes it convenient to use L Y X for writing such literate programs.
2645 This file ./fangle.module can be installed in your personal .lyx/layouts folder. You will need to Tools Reconfigure so that L Y X notices it. It adds a new format Chunk, which should precede every listing and contain the chunk name.
2647 79a <./fangle.module[1](
\v), lang=lyx-module> ≡
2648 ________________________________________________________________________
2649 1 | #\DeclareLyXModule{Fangle Literate Listings}
2650 2 | #DescriptionBegin
2651 3 | # Fangle literate listings allow one to write
2652 4 | # literate programs after the fashion of noweb, but without having
2653 5 | # to use noweave to generate the documentation. Instead the listings
2654 6 | # package is extended in conjunction with the noweb package to implement
2655 7 | # to code formating directly as latex.
2656 8 | # The fangle awk script
2659 11 | «gpl3-copyright.hashed 79b»
2664 16 | «./fangle.sty 80d»
2667 19 | «chunkstyle 80a»
2670 |________________________________________________________________________
2673 Because L Y X modules are not yet a language supported by fangle or lstlistings, we resort to this fake awk chunk below in order to have each line of the GPL3 license commence with a #
2675 79b <gpl3-copyright.hashed[1](
\v), lang=awk> ≡
2676 ________________________________________________________________________
2677 1 | #«gpl3-copyright 4a»
2679 |________________________________________________________________________
2682 16.1.1 The Chunk style
2683 The purpose of the chunk style is to make it easier for L Y X users to provide the name to lstlistings. Normally this requires right-clicking on the listing, choosing settings, advanced, and then typing name=chunk-name. This has the further disadvantage that the name (and other options) are not generally visible during document editing.
2684 The chunk style is defined as a LaTeX command, so that all text on the same line is passed to the LaTeX command Chunk. This makes it easy to parse using fangle, and easy to pass these options on to the listings package. The first word in a chunk section should be the chunk name, and will have name= prepended to it. Any other words are accepted arguments to lstset.
2685 We set PassThru to 1 because the user is actually entering raw latex.
2687 80a <chunkstyle[1](
\v), lang=> ≡ 80b▿
2688 ________________________________________________________________________
2690 2 | LatexType Command
2692 4 | Margin First_Dynamic
2693 5 | LeftMargin Chunk:xxx
2695 7 | LabelType Static
2696 8 | LabelString "Chunk:"
2700 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2701 To make the label very visible we choose a larger font coloured red.
2703 80b <chunkstyle[2](
\v) ⇑80a, lang=> +≡ ▵80a
2704 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2713 |________________________________________________________________________
2716 16.1.2 The chunkref style
2717 We also define the Chunkref style which can be used to express cross references to chunks.
2719 80c <chunkref[1](
\v), lang=> ≡
2720 ________________________________________________________________________
2721 1 | InsetLayout Chunkref
2722 2 | LyxType charstyle
2723 3 | LatexType Command
2724 4 | LatexName chunkref
2731 |________________________________________________________________________
2735 We require the listings, noweb and xargs packages. As noweb defines it's own \code environment, we re-define the one that L Y X logical markup module expects here.
2737 80d <./fangle.sty[1](
\v), lang=tex> ≡ 81a⊳
2738 ________________________________________________________________________
2739 1 | \usepackage{listings}%
2740 2 | \usepackage{noweb}%
2741 3 | \usepackage{xargs}%
2742 4 | \renewcommand{\code}[1]{\texttt{#1}}%
2743 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2744 We also define a CChunk macro, for use as: \begin{CChunk} which will need renaming to \begin{Chunk} when I can do this without clashing with \Chunk.
2746 81a <./fangle.sty[2](
\v) ⇑80d, lang=> +≡ ⊲80d 81b▿
2747 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2748 5 | \lstnewenvironment{Chunk}{\relax}{\relax}%
2749 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2750 We also define a suitable \lstset of parameters that suit the literate programming style after the fashion of noweave.
2752 81b <./fangle.sty[3](
\v) ⇑80d, lang=> +≡ ▵81a 81c▿
2753 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2754 6 | \lstset{numbers=left, stepnumber=5, numbersep=5pt,
2755 7 | breaklines=false,basicstyle=\ttfamily,
2756 8 | numberstyle=\tiny, language=C}%
2757 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2758 We also define a notangle-like mechanism for escaping to LaTeX from the listing, and by which we can refer to other listings. We declare the =<...> sequence to contain LaTeX code, and include another like this chunk: <chunkname ?>. However, because =<...> is already defined to contain LaTeX code for this document --- this is a fangle document after all --- the code fragment below effectively contains the LaTeX code: }{. To avoid problems with document generation, I had to declare an lstlistings property: escapeinside={} for this listing only; which in L Y X was done by right-clicking the listings inset, choosing settings->advanced. Therefore =< isn't interpreted literally here, in a listing when the escape sequence is already defined as shown... we need to somehow escape this representation...
2760 81c <./fangle.sty[4](
\v) ⇑80d, lang=> +≡ ▵81b 81d▿
2761 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2762 9 | \lstset{escapeinside={=<}{>}}%
2763 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2764 Although our macros will contain the @ symbol, they will be included in a \makeatletter section by L Y X; however we keep the commented out \makeatletter as a reminder. The listings package likes to centre the titles, but noweb titles are specially formatted and must be left aligned. The simplest way to do this turned out to be by removing the definition of \lst@maketitle. This may interact badly if other listings want a regular title or caption. We remember the old maketitle in case we need it.
2766 81d <./fangle.sty[5](
\v) ⇑80d, lang=> +≡ ▵81c 81e▿
2767 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2769 11 | %somehow re-defining maketitle gives us a left-aligned title
2770 12 | %which is extactly what our specially formatted title needs!
2771 13 | \global\let\fangle@lst@maketitle\lst@maketitle%
2772 14 | \global\def\lst@maketitle{}%
2773 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2774 16.2.1 The chunk command
2775 Our chunk command accepts one argument, and calls \ltset. Although \ltset will note the name, this is erased when the next \lstlisting starts, so we make a note of this in \lst@chunkname and restore in in lstlistings Init hook.
2777 81e <./fangle.sty[6](
\v) ⇑80d, lang=> +≡ ▵81d 82a⊳
2778 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2780 16 | \lstset{title={\fanglecaption},name=#1}%
2781 17 | \global\edef\lst@chunkname{\lst@intname}%
2783 19 | \def\lst@chunkname{\empty}%
2784 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2785 16.2.1.1 Chunk parameters
2786 Fangle permits parameterized chunks, and requires the paramters to be specified as listings options. The fangle script uses this, and although we don't do anything with these in the LaTeX code right now, we need to stop the listings package complaining.
2788 82a <./fangle.sty[7](
\v) ⇑80d, lang=> +≡ ⊲81e 82b▿
2789 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2790 20 | \lst@Key{params}\relax{\def\fangle@chunk@params{#1}}%
2791 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2792 As it is common to define a chunk which then needs appending to another chunk, and annoying to have to declare a single line chunk to manage the include, we support an append= option.
2794 82b <./fangle.sty[8](
\v) ⇑80d, lang=> +≡ ▵82a 82c▿
2795 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2796 21 | \lst@Key{append}\relax{\def\fangle@chunk@append{#1}}%
2797 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2798 16.2.2 The noweb styled caption
2799 We define a public macro \fanglecaption which can be set as a regular title. By means of \protect, It expands to \fangle@caption at the appopriate time when the caption is emitted.
2801 82c <./fangle.sty[9](
\v) ⇑80d, lang=> +≡ ▵82b 82d▿
2802 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2803 \def\fanglecaption{\protect\fangle@caption}%
2804 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2805 22c ⟨some-chunk 19b⟩≡+ ⊲22b 24d⊳
2807 In this example, the current chunk is 22c, and therefore the third chunk on page 22.
2808 It's name is some-chunk.
2809 The first chunk with this name (19b) occurs as the second chunk on page 19.
2810 The previous chunk (22d) with the same name is the second chunk on page 22.
2811 The next chunk (24d) is the fourth chunk on page 24.
2813 Figure 1. Noweb Heading
2815 The general noweb output format compactly identifies the current chunk, and references to the first chunk, and the previous and next chunks that have the same name.
2816 This means that we need to keep a counter for each chunk-name, that we use to count chunks of the same name.
2817 16.2.3 The chunk counter
2818 It would be natural to have a counter for each chunk name, but TeX would soon run out of counters1. ...soon did run out of counters and so I had to re-write the LaTeX macros to share a counter as described here. ^1, so we have one counter which we save at the end of a chunk and restore at the beginning of a chunk.
2820 82d <./fangle.sty[10](
\v) ⇑80d, lang=> +≡ ▵82c 83c⊳
2821 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2822 22 | \newcounter{fangle@chunkcounter}%
2823 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2824 We construct the name of this variable to store the counter to be the text lst-chunk- prefixed onto the chunks own name, and store it in \chunkcount.
2825 We save the counter like this:
2827 83a <save-counter[1](
\v), lang=> ≡
2828 ________________________________________________________________________
2829 \global\expandafter\edef\csname \chunkcount\endcsname{\arabic{fangle@chunkcounter}}%
2830 |________________________________________________________________________
2833 and restore the counter like this:
2835 83b <restore-counter[1](
\v), lang=> ≡
2836 ________________________________________________________________________
2837 \setcounter{fangle@chunkcounter}{\csname \chunkcount\endcsname}%
2838 |________________________________________________________________________
2841 If there does not already exist a variable whose name is stored in \chunkcount, then we know we are the first chunk with this name, and then define a counter.
2842 Although chunks of the same name share a common counter, they must still be distinguished. We use is the internal name of the listing, suffixed by the counter value. So the first chunk might be something-1 and the second chunk be something-2, etc.
2843 We also calculate the name of the previous chunk if we can (before we increment the chunk counter). If this is the first chunk of that name, then \prevchunkname is set to \relax which the noweb package will interpret as not existing.
2845 83c <./fangle.sty[11](
\v) ⇑80d, lang=> +≡ ⊲82d 83d▿
2846 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2847 23 | \def\fangle@caption{%
2848 24 | \edef\chunkcount{lst-chunk-\lst@intname}%
2849 25 | \@ifundefined{\chunkcount}{%
2850 26 | \expandafter\gdef\csname \chunkcount\endcsname{0}%
2851 27 | \setcounter{fangle@chunkcounter}{\csname \chunkcount\endcsname}%
2852 28 | \let\prevchunkname\relax%
2854 30 | \setcounter{fangle@chunkcounter}{\csname \chunkcount\endcsname}%
2855 31 | \edef\prevchunkname{\lst@intname-\arabic{fangle@chunkcounter}}%
2857 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2858 After incrementing the chunk counter, we then define the name of this chunk, as well as the name of the first chunk.
2860 83d <./fangle.sty[12](
\v) ⇑80d, lang=> +≡ ▵83c 83e▿
2861 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2862 33 | \addtocounter{fangle@chunkcounter}{1}%
2863 34 | \global\expandafter\edef\csname \chunkcount\endcsname{\arabic{fangle@chunkcounter}}%
2864 35 | \edef\chunkname{\lst@intname-\arabic{fangle@chunkcounter}}%
2865 36 | \edef\firstchunkname{\lst@intname-1}%
2866 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2867 We now need to calculate the name of the next chunk. We do this by temporarily skipping the counter on by one; however there may not actually be another chunk with this name! We detect this by also defining a label for each chunk based on the chunkname. If there is a next chunkname then it will define a label with that name. As labels are persistent, we can at least tell the second time LaTeX is run. If we don't find such a defined label then we define \nextchunkname to \relax.
2869 83e <./fangle.sty[13](
\v) ⇑80d, lang=> +≡ ▵83d 84a⊳
2870 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2871 37 | \addtocounter{fangle@chunkcounter}{1}%
2872 38 | \edef\nextchunkname{\lst@intname-\arabic{fangle@chunkcounter}}%
2873 39 | \@ifundefined{r@label-\nextchunkname}{\let\nextchunkname\relax}{}%
2874 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2875 The noweb package requires that we define a \sublabel for every chunk, with a unique name, which is then used to print out it's navigation hints.
2876 We also define a regular label for this chunk, as was mentioned above when we calculated \nextchunkname. This requires LaTeX to be run at least twice after new chunk sections are added --- but noweb requried that anyway.
2878 84a <./fangle.sty[14](
\v) ⇑80d, lang=> +≡ ⊲83e 84b▿
2879 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2880 40 | \sublabel{\chunkname}%
2881 41 | % define this label for every chunk instance, so we
2882 42 | % can tell when we are the last chunk of this name
2883 43 | \label{label-\chunkname}%
2884 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2885 We also try and add the chunk to the list of listings, but I'm afraid we don't do very well. We want each chunk name listing once, with all of it's references.
2887 84b <./fangle.sty[15](
\v) ⇑80d, lang=> +≡ ▵84a 84c▿
2888 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2889 44 | \addcontentsline{lol}{lstlisting}{\lst@name~[\protect\subpageref{\chunkname}]}%
2890 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2891 We then call the noweb output macros in the same way that noweave generates them, except that we don't need to call \nwstartdeflinemarkup or \nwenddeflinemarkup — and if we do, it messes up the output somewhat.
2893 84c <./fangle.sty[16](
\v) ⇑80d, lang=> +≡ ▵84b 84d▿
2894 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2898 48 | \subpageref{\chunkname}%
2905 55 | \nwtagstyle{}\/%
2906 56 | \@ifundefined{fangle@chunk@params}{}{%
2907 57 | (\fangle@chunk@params)%
2909 59 | [\csname \chunkcount\endcsname]~%
2910 60 | \subpageref{\firstchunkname}%
2912 62 | \@ifundefined{fangle@chunk@append}{}{%
2913 63 | \ifx{}\fangle@chunk@append{x}\else%
2914 64 | ,~add~to~\fangle@chunk@append%
2917 67 | \global\def\fangle@chunk@append{}%
2918 68 | \lstset{append=x}%
2921 71 | \ifx\relax\prevchunkname\endmoddef\else\plusendmoddef\fi%
2922 72 | % \nwstartdeflinemarkup%
2923 73 | \nwprevnextdefs{\prevchunkname}{\nextchunkname}%
2924 74 | % \nwenddeflinemarkup%
2926 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2927 Originally this was developed as a listings aspect, in the Init hook, but it was found easier to affect the title without using a hook — \lst@AddToHookExe{PreSet} is still required to set the listings name to the name passed to the \Chunk command, though.
2929 84d <./fangle.sty[17](
\v) ⇑80d, lang=> +≡ ▵84c 85a⊳
2930 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2931 76 | %\lst@BeginAspect{fangle}
2932 77 | %\lst@Key{fangle}{true}[t]{\lstKV@SetIf{#1}{true}}
2933 78 | \lst@AddToHookExe{PreSet}{\global\let\lst@intname\lst@chunkname}
2934 79 | \lst@AddToHook{Init}{}%\fangle@caption}
2935 80 | %\lst@EndAspect
2936 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2937 16.2.4 Cross references
2938 We define the \chunkref command which makes it easy to generate visual references to different code chunks, e.g.
2941 \chunkref[3]{preamble}
2942 \chunkref{preamble}[arg1, arg2]
2944 Chunkref can also be used within a code chunk to include another code chunk. The third optional parameter to chunkref is a comma sepatarated list of arguments, which will replace defined parameters in the chunkref.
2945 Note 1. Darn it, if I have: =<\chunkref{new-mode-tracker}[{chunks[chunk_name, "language"]},{mode}]> the inner braces (inside [ ]) cause _ to signify subscript even though we have lst@ReplaceIn
2947 85a <./fangle.sty[18](
\v) ⇑80d, lang=> +≡ ⊲84d 86a⊳
2948 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2949 81 | \def\chunkref@args#1,{%
2951 83 | \lst@ReplaceIn\arg\lst@filenamerpl%
2953 85 | \@ifnextchar){\relax}{, \chunkref@args}%
2955 87 | \newcommand\chunkref[2][0]{%
2956 88 | \@ifnextchar({\chunkref@i{#1}{#2}}{\chunkref@i{#1}{#2}()}%
2958 90 | \def\chunkref@i#1#2(#3){%
2960 92 | \def\chunk{#2}%
2961 93 | \def\chunkno{#1}%
2962 94 | \def\chunkargs{#3}%
2963 95 | \ifx\chunkno\zero%
2964 96 | \def\chunkname{#2-1}%
2966 98 | \def\chunkname{#2-\chunkno}%
2968 100 | \let\lst@arg\chunk%
2969 101 | \lst@ReplaceIn\chunk\lst@filenamerpl%
2970 102 | \LA{%\moddef{%
2973 105 | \nwtagstyle{}\/%
2974 106 | \ifx\chunkno\zero%
2978 110 | \ifx\chunkargs\empty%
2980 112 | (\chunkref@args #3,)%
2982 114 | ~\subpageref{\chunkname}%
2985 117 | \RA%\endmoddef%
2987 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2990 86a <./fangle.sty[19](
\v) ⇑80d, lang=> +≡ ⊲85a
2991 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
2994 |________________________________________________________________________
2997 Chapter 17Extracting fangle
2998 17.1 Extracting from Lyx
2999 To extract from L Y X, you will need to configure L Y X as explained in section ?.
3000 And this lyx-build scrap will extract fangle for me.
3002 87a <lyx-build[2](
\v) ⇑20a, lang=sh> +≡ ⊲20a
3003 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3007 14 | «lyx-build-helper 19b»
3008 15 | cd $PROJECT_DIR || exit 1
3010 17 | /usr/local/bin/fangle -R./fangle $TEX_SRC > ./fangle
3011 18 | /usr/local/bin/fangle -R./fangle.module $TEX_SRC > ./fangle.module
3013 20 | «test:helpers 88b»
3014 21 | export FANGLE=./fangle
3015 22 | export TMP=${TMP:-/tmp}
3016 23 | «test:run-tests 87b»
3017 24 | # Now check that we can extract a fangle that also passes the tests!
3018 25 | $FANGLE -R./fangle $TEX_SRC > ./new-fangle
3019 26 | export FANGLE=./new-fangle
3020 27 | «test:run-tests 87b»
3021 |________________________________________________________________________
3025 87b <test:run-tests[1](
\v), lang=sh> ≡
3026 ________________________________________________________________________
3028 2 | $FANGLE -Rpca-test.awk $TEX_SRC | awk -f - || exit 1
3029 3 | «test:cromulence 51e»
3030 4 | «test:escapes 55d»
3031 5 | «test:chunk-params 91a»
3032 |________________________________________________________________________
3035 With a lyx-build-helper
3037 87c <lyx-build-helper[2](
\v) ⇑19b, lang=sh> +≡ ⊲19b
3038 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3039 5 | PROJECT_DIR="$LYX_r"
3040 6 | LYX_SRC="$PROJECT_DIR/${LYX_i%.tex}.lyx"
3041 7 | TEX_DIR="$LYX_p"
3042 8 | TEX_SRC="$TEX_DIR/$LYX_i"
3043 |________________________________________________________________________
3046 17.2 Extracting documentation
3048 87d <./gen-www[1](
\v), lang=> ≡
3049 ________________________________________________________________________
3050 1 | #python -m elyxer --css lyx.css $LYX_SRC | \
3051 2 | # iconv -c -f utf-8 -t ISO-8859-1//TRANSLIT | \
3052 3 | # sed 's/UTF-8"\(.\)>/ISO-8859-1"\1>/' > www/docs/fangle.html
3054 5 | python -m elyxer --css lyx.css --iso885915 --html --destdirectory www/docs/fangle.e \
3055 6 | fangle.lyx > www/docs/fangle.e/fangle.html
3057 8 | ( mkdir -p www/docs/fangle && cd www/docs/fangle && \
3058 9 | lyx -e latex ../../../fangle.lyx && \
3059 10 | htlatex ../../../fangle.tex "xhtml,fn-in" && \
3060 11 | sed -i -e 's/<!--l\. [0-9][0-9]* *-->//g' fangle.html
3063 14 | ( mkdir -p www/docs/literate && cd www/docs/literate && \
3064 15 | lyx -e latex ../../../literate.lyx && \
3065 16 | htlatex ../../../literate.tex "xhtml,fn-in" && \
3066 17 | sed -i -e 's/<!--l\. [0-9][0-9]* *-->$//g' literate.html
3068 |________________________________________________________________________
3071 17.3 Extracting from the command line
3072 First you will need the tex output, then you can extract:
3074 88a <lyx-build-manual[1](
\v), lang=sh> ≡
3075 ________________________________________________________________________
3076 1 | lyx -e latex fangle.lyx
3077 2 | fangle -R./fangle fangle.tex > ./fangle
3078 3 | fangle -R./fangle.module fangle.tex > ./fangle.module
3079 |________________________________________________________________________
3084 88b <test:helpers[1](
\v), lang=> ≡
3085 ________________________________________________________________________
3088 3 | then echo "Passed"
3089 4 | else echo "Failed"
3096 11 | then echo "Passed"
3097 12 | else echo "Failed"
3101 |________________________________________________________________________
3105 Chapter 18Chunk Parameters
3108 91a <test:lyx:chunk-params:sub[1](THING
\v, colour
\v\v), lang=> ≡
3109 ________________________________________________________________________
3110 1 | I see a ${THING},
3111 2 | a ${THING} of colour ${colour},
3112 3 | and looking closer =<\chunkref{test:lyx:chunk-params:sub:sub}(${colour)>
3113 |________________________________________________________________________
3117 91b <test:lyx:chunk-params:sub:sub[1](colour
\v\v), lang=> ≡
3118 ________________________________________________________________________
3119 1 | a funny shade of ${colour}
3120 |________________________________________________________________________
3124 91c <test:chunk-params:text[1](
\v), lang=> ≡ ?a⊳
3125 ________________________________________________________________________
3126 1 | What do you see? "=<\chunkref{test:lyx:chunk-params:sub}(joe, red)>"
3128 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3129 Should generate output:
3131 91d <test:lyx:chunk-params:result[1](
\v), lang=> ≡
3132 ________________________________________________________________________
3133 1 | What do you see? "I see a joe,
3134 2 | a joe of colour red,
3135 3 | and looking closer a funny shade of red"
3137 |________________________________________________________________________
3140 And this chunk will perform the test:
3142 91e <test:chunk-params[1](
\v), lang=> ≡ ?a⊳
3143 ________________________________________________________________________
3144 1 | $FANGLE -Rtest:lyx:chunk-params:result $TEX_SRC > $TMP/answer || exit 1
3145 2 | $FANGLE -Rtest:lyx:chunk-params:text $TEX_SRC > $TMP/result || exit 1
3146 3 | passtest diff $TMP/answer $TMP/result || (echo test:lyx:chunk-params:text failed ; exit 1)
3147 |/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3150 91f <test:chunk-params:sub[1](THING
\v, colour
\v\v), lang=> ≡
3151 ________________________________________________________________________
3152 1 | I see a ${THING}
3153 2 | a ${THING} of colour ${colour},
3154 3 | and looking closer «test:chunk-params:sub:sub
\v(${colour}
\v) 91c»
3155 |________________________________________________________________________
3159 91g <test:chunk-params:sub:sub[1](colour
\v\v), lang=> ≡
3160 ________________________________________________________________________
3161 1 | a funny shade of ${colour}
3162 |________________________________________________________________________
3166 92a <test:chunk-params:text[2](
\v) ⇑91a, lang=> +≡ ⊲91a
3167 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3168 3 | What do you see? "«test:chunk-params:sub
\v(joe
\v, red
\v) 91b»
3170 |________________________________________________________________________
3173 Should generate output:
3175 92b <test:chunk-params:result[1](
\v), lang=> ≡
3176 ________________________________________________________________________
3177 1 | What do you see? "I see a joe,
3178 2 | a joe of colour red,
3179 3 | and looking closer a funny shade of red"
3181 |________________________________________________________________________
3184 And this chunk will perform the test:
3186 92c <test:chunk-params[2](
\v) ⇑91a, lang=> +≡ ⊲91a
3187 ./\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
3188 4 | $FANGLE -Rtest:chunk-params:result $TEX_SRC > $TMP/answer || exit 1
3189 5 | $FANGLE -Rtest:chunk-params:text $TEX_SRC > $TMP/result || exit 1
3190 6 | passtest diff $TMP/answer $TMP/result || (echo test:chunk-params:text failed ; exit 1)
3191 |________________________________________________________________________
3194 Chapter 19Compile-log-lyx
3196 93a <Chunk:./compile-log-lyx[1](
\v), lang=sh> ≡
3197 ________________________________________________________________________
3199 2 | # can't use gtkdialog -i, cos it uses the "source" command which ubuntu sh doesn't have
3202 5 | errors="/tmp/compile.log.$$"
3203 6 | # if grep '^[^ ]*:\( In \|[0-9][0-9]*: [^ ]*:\)' > $errors
3204 7 | if grep '^[^ ]*(\([0-9][0-9]*\)) *: *\(error\|warning\)' > $errors
3206 9 | sed -i -e 's/^[^ ]*[/\\]\([^/\\]*\)(\([ 0-9][ 0-9]*\)) *: */\1:\2|\2|/' $errors
3207 10 | COMPILE_DIALOG='
3210 13 | <label>Compiler errors:</label>
3212 15 | <tree exported_column="0">
3213 16 | <variable>LINE</variable>
3214 17 | <height>400</height><width>800</width>
3215 18 | <label>File | Line | Message</label>
3216 19 | <action>'". $SELF ; "'lyxgoto $LINE</action>
3217 20 | <input>'"cat $errors"'</input>
3220 23 | <button><label>Build</label>
3221 24 | <action>lyxclient -c "LYXCMD:build-program" &</action>
3223 26 | <button ok></button>
3227 30 | export COMPILE_DIALOG
3228 31 | ( gtkdialog --program=COMPILE_DIALOG ; rm $errors ) &
3235 38 | file="${LINE%:*}"
3236 39 | line="${LINE##*:}"
3237 40 | extraline=‘cat $file | head -n $line | tac | sed '/^\\\\begin{lstlisting}/q' | wc -l‘
3238 41 | extraline=‘expr $extraline - 1‘
3239 42 | lyxclient -c "LYXCMD:command-sequence server-goto-file-row $file $line ; char-forward ; repeat $extraline paragraph-down ; paragraph-up-select"
3243 46 | if test -z "$COMPILE_DIALOG"
3246 |________________________________________________________________________