diff options
author | Karl Berry <karl@freefriends.org> | 2011-12-03 23:36:05 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2011-12-03 23:36:05 +0000 |
commit | f884cf55d378c5815215caa25f5fb7ff1d8f83cd (patch) | |
tree | 62ee34c74798cbac6b6fbe0448713249097c08a9 /Master/texmf-dist | |
parent | e57737e5e749b3cdff002d8ca4c32c865ec5e03e (diff) |
interpreter 1.1 (3dec11)
git-svn-id: svn://tug.org/texlive/trunk@24740 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist')
-rw-r--r-- | Master/texmf-dist/doc/luatex/interpreter/README | 9 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/interpreter/i-doc.lua | 9 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.pdf | bin | 72802 -> 102479 bytes | |||
-rw-r--r-- | Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.tex | 103 | ||||
-rw-r--r-- | Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.txt | 426 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/interpreter/interpreter.lua | 368 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/interpreter/interpreter.sty | 4 | ||||
-rw-r--r-- | Master/texmf-dist/tex/luatex/interpreter/interpreter.tex | 13 |
8 files changed, 685 insertions, 247 deletions
diff --git a/Master/texmf-dist/doc/luatex/interpreter/README b/Master/texmf-dist/doc/luatex/interpreter/README index c62c78604ae..c91f26a1d59 100644 --- a/Master/texmf-dist/doc/luatex/interpreter/README +++ b/Master/texmf-dist/doc/luatex/interpreter/README @@ -2,12 +2,15 @@ This is the README file for the Interpreter package. Author: Paul Isambert. E-mail: zappathustra AT free DOT fr Comments and suggestions are welcome. -Date: July 2011. -Version: 1.0. +Date: December 2011. +Version: 1.1. Interpreter preprocesses input files on the fly (no external program) and manipulates input lines, e.g. to turn some markup into proper TeX syntax. -LuaTeX is required. +Interpreter doesn't work with ConTeXt. + +LuaTeX is required, as is, since version 1.1, the Lua side of the +Gates package (i.e. gates.lua). Relevant information can be found in interpreter-doc.pdf or interpreter-doc.txt (source of the doc readable in a text editor). diff --git a/Master/texmf-dist/doc/luatex/interpreter/i-doc.lua b/Master/texmf-dist/doc/luatex/interpreter/i-doc.lua index b8b3c106a46..12bc214eda8 100644 --- a/Master/texmf-dist/doc/luatex/interpreter/i-doc.lua +++ b/Master/texmf-dist/doc/luatex/interpreter/i-doc.lua @@ -122,7 +122,8 @@ add_pattern{ add_pattern{ pattern = "_..._", replace = [[\ital{%1}]], - class = 4 } + class = 4 +} --[[ I use double quotes as protectors; they are replaced with a "\verb" command at @@ -188,7 +189,7 @@ class is 3). Of course, the first ten space characters are removed. --]] local function verbatim (buffer) for n, l in ipairs(buffer) do - buffer[n] = gsub(l, "%s%s%s%s%s%s%s%s%s%s","") + buffer[n] = gsub(l, "%s%s%s%s%s%s%s%s%s%s","", 1) end table.insert(buffer, 1, [[\verbatim]]) if gsub(buffer[#buffer], @@ -244,10 +245,10 @@ add_pattern{ call = autoverbatim, class = 1 } -local function test () +local function remove_comment () return "" end add_pattern{ pattern = nomagic"%^--[[", - replace = test, + replace = remove_comment } diff --git a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.pdf b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.pdf Binary files differindex 8e70e34b7fd..ea76a6c65cd 100644 --- a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.pdf +++ b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.pdf diff --git a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.tex b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.tex index 8d78ad02963..8a1ab383a81 100644 --- a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.tex +++ b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.tex @@ -1,36 +1,20 @@ % This is the master file producing interpreter-doc.pdf. The version of the % documentation readable in a text editor is interpreter-doc.txt (input below). % -% Paul Isambert - zappathustra AT free DOT fr - July 2011 +% Paul Isambert - zappathustra AT free DOT fr - December 2011 + \input pitex -% Output stuff. -\gates remove {output_headers}{output_shipout} -\gates remove {output_postship}{output_shipout} -\gates remove {output_precheck}{output} -\gates close {output_shipout}{output} - -\newif\ifright -\newbox\leftbox -\gates def {twocol}{% - \ifright - \global\rightfalse - \setbox\outputbox=\hbox{\box\leftbox\kern24pt\box\outputbox}% - \gates ajar {output_shipout}{output}% - \else - \global\righttrue - \global\setbox\leftbox=\box\outputbox - \fi - } -\gates add {twocol}[before output_shipout]{output} +\overfullrule=0pt +\OutputRoutine remove {headers}{shipout} \setparameter page : - hsize = 20pc + hsize = 25pc left = 60pt - width = "\dimexpr 144pt + 40pc\relax" - lines = 45 - height = 24cm + width = "\dimexpr 25pc + 120pt\relax" + lines = 35 + height = 20cm \setparameter section : font = \bf @@ -40,36 +24,37 @@ beforeskip = 1 \setparameter navigator : - open = true title = "Interpreter documentation" author = "Paul Isambert" mode = outlines -\setparameter font : - command = \mainfont - name = "Chaparral Pro" - bold = Semibold - big = 18pt - -\setparameter font : - command = \codefont - name = "Lucida Console" - bold = none - size = 8pt - features = "space = .6, -tlig, -trep, -liga" +\setfont\mainfont: + name = "Chaparral Pro" + bold = Semibold + big = 18pt +\setfont\codefont: + name = "Lucida Console" + slant = 15 + bold italic = none + size = 8pt + features = "-tlig, -trep, space = mono" \parfillskip=0pt plus 1fill \def\describe#1#2#3{% - \iflines3{\vskip\baselineskip}{\breakpage}% + \unless\ifdim\lastskip=\baselineskip + \vskip\baselineskip + \fi + \needspace{2\baselineskip}% \noindent\color{.8 0 0}{% {\outline{#3}{\directlua{% local t = string.gsub("\luaescapestring{#1}", "[ (].*", "") tex.print(t)}}% \codefont#1}% - \reverse\iffemptystring{#2} - {\kern1em \hfil\penalty0\hbox{\ital{(#2)}}}}% - \par\removenextindent} + \reverse\iffemptystring{#2} + {\kern1em \hfil\penalty0\hbox{\ital{(#2)}}}}% + \par + \removenextindent} \newverbatim\source{} {\vskip\baselineskip @@ -80,19 +65,51 @@ \pdfcolorstack0 pop \vskip\baselineskip} +% The verbatim facilities in PiTeX aren't gated yet, so I must rely on +% this horrible hack! +\directlua{ +function do_verbatim (name, exec) + if exec then + tex.print(pitex.verbatims[name]) + else + for n, l in ipairs(pitex.verbatims[name]) do + if n == \string#pitex.verbatims[name] then + tex.print("\noexpand\\penalty\noexpand\\widowpenalty") + end + tex.print(pitex.verbatims[name].regime, l) + if n == 1 and \string#pitex.verbatims[name] > 2 then + tex.print("\noexpand\\penalty\noexpand\\widowpenalty") + end + end + end +end +} + \def\arg#1{{\codefont\char"2039 #1\char"203A}} +\pdfdef\ital#1{#1} +\pdfdef\verb`#1`{#1} + +% Not optimal, but hey, with all the "intepreter.core.classes" stuff... +\hyphenation{li-nes cla-sses} \input interpreter % Title \vbox to 3\baselineskip{ \hbox to \hsize{\big Interpreter\hfil\normalsize Paul Isambert} -\hbox to \hsize{v.1.0, July 2011 \hfil \tcode{zappathustra AT free DOT fr}} +\hbox to \hsize{v.1.1, December 2011 \hfil \tcode{zappathustra AT free DOT fr}} \vfil } + + + % Bulk of the doc. \interpretfile{doc}{interpreter-doc.txt} \vskip0pt plus 1filll -\noindent\ital{Typeset with Lua\TeX\ 0.71 in Chaparral Pro and Lucida Console} +\noindent +\bgroup\it +Typeset with Lua\TeX\ 0.71 in Chaparral Pro and Lucida Console +... nonetheless this documentation looks dull, I don't know why. +\egroup \bye diff --git a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.txt b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.txt index 5bece3bac7a..0156c3e07d8 100644 --- a/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.txt +++ b/Master/texmf-dist/doc/luatex/interpreter/interpreter-doc.txt @@ -1,19 +1,23 @@ TABLE OF CONTENTS - (Make a search on the tag in the right - column to jump to the associated section. - Vim users can simply type * on the tag; - Emacs users do that with C-s C-w (I think); - Other editors: I don't know!) -======================================= - Introduction intro_tag - Input files input_tag - Paragraphs paragraphs_tag - Declaring patterns patterns_tag - Classes classes_tag - Protecting input protect_tag - Technical stuff technical_tag - An example: i-doc.lua example_tag -======================================= + (Make a search on the tag in the right column to jump + to the associated section. + Vim users can simply type * on the tag; + Emacs users do that with C-s C-w (I think); + Other editors: I don't know!) +=========================================================== + Introduction intro_tag + Input files input_tag + Paragraphs paragraphs_tag + Declaring patterns patterns_tag + Classes classes_tag + Protecting input protect_tag + Technical stuff technical_tag + An example: i-doc.lua example_tag + The Gates in Interpreter gates_tag + The interpreter table interpreter_tag + The interpreter.core.tools table interpreter_tools_tag + The interpreter.core.reader table interpreter_reader_tag +=========================================================== @@ -41,6 +45,14 @@ modified lines to TeX you can also translate the original lines into, say, HTML, and write them to an external file, thus creating both PDF and HTML output at once. +Interpreter has been rewritten with the Gates package (actually, only +the Lua side) in version 1.1. That hasn't changed anything to its default +behavior, but now it can also be customized quite deeply, since its +code is a collection of small chunks with names that can be externally +controlled and/or augmented. See the Gates documentation for further +information. The last sections of this documentation describe the gates +in Interpreter. + ================================== input_tag @@ -224,7 +236,7 @@ precisely, the process is as follows: Interpreter searches the entire paragraph for the first pattern in class~1, then for the second pattern in the same class, then for the third, etc., then when there is no pattern left in class~1 it does the same with class~2, up to class~_n_, where _n_ is the -hightest class number such that there exists a class _n - 1_ (in other words, +highest class number such that there exists a class _n - 1_ (in other words, classes should be numbered consecutively). Finally, the same goes for the patterns in class~0 (which always exists, even if it contains no pattern). @@ -381,3 +393,385 @@ does its job; actually you won't learn much anyway. ================================== \interpretfile{doc}{i-doc.lua} + + + +================================== gates_tag +=== The Gates in Interpreter ===== +================================== + +Interpreter is written with the Gates package (only the Lua side, +actually). It means that it can be hacked down to the core. Here I'll +simply list the gates involved; you should read the Gates +documentation to learn how to use them. + +There are three gates families: "interpreter", associated with the main +"interpreter" table, contains the user interface; "interpreter_tools", +associated with "interpreter.core.tools" table, contains internal +functions; finally "interpreter_reader", associated with the +"interpreter.core.reader" table, contains the main functions used to +read the file. + +Whenever I mention a conditional or a loop, I mean the local conditionals +and loops, relative to the l-gate where the gate appears. Also, the +syntax indicates the arguments a gate uses, not all the arguments that +are passed to it (which are simply what the previous gate has returned). + +As an example of customizing Interpreter with Gates, you could very +well add a bit of code which does something to all lines. Inserting a +small gate, say "everyline", after "check_direct" in "aggregate_lines" +below would do the trick, e.g.: + + + function interpreter.core.reader.everyline (file, line) + line = dosomething(line) + return file, line + end + interpreter.core.reader.add( + "everyline", "aggregate_lines", "after check_direct") + interpreter.core.reader.conditional( + "everyline", "aggregate_lines", function (f, l) return l end) + +(Note that it is important to check that the line really exist, +because one might have hit the end of the file; hence the conditional, +as with others gates in "aggregate_lines"). + + + + +================================== interpreter_tag +=== The "interpreter" table ======== +================================== + +All the user functions in "interpreter" are simple m-gates, so they can +be treated as ordinary functions, except "interpreter.add_pattern", +which is an l-gate containing, built as: + + add_pattern + . ensure_class + . apply_nomagic + . insert-pattern + . . do_insert + . . sort_class + +> ensure_class (<pattern>) [m-gate] + Creates the class of <pattern> if necessary, and set it as the metatable + for <pattern>. Classes themselves are kept in the "interpreter.core.classes" + table. The gate return <pattern> and the class number. + +> apply_nomagic (<pattern>, <class>) [m-gate] + Transforms the "pattern" entry in <pattern> with "intepreter.nomagic"; + tied to a conditional that returns <pattern>'s "nomagic" entry (so + the gate is executed only if "nomagic" is true); autoreturns both + arguments. + +> insert_pattern (<pattern>, <class>) [l-gate] + An autoreturning l-gate containing the following two gates. + +> do_insert (<pattern>, <class>) [m-gate] + Adds <pattern> to <class> (i.e. "interpreter.core.classes[<class>]"). + +> sort_class (<pattern>, <class>) [m-gate] + Sorts <class> with function "interpreter.core.tools.sort". This gate + can be skipped to apply the patterns in the order in which they were + declared. + + + +============================================= interpreter_tools_tag +=== The interpreter.core.tools table ======== +============================================= + +All the functions in the "interpreter.core.tools" all are simple m-gates. + +> sort (<patt1>, <patt2>) [m-gate] + Returns true if the pattern in "patt1" is longer than the one in + "patt2", or if they have the same length and the first ranks before + the second with respect to alphabetical order. The gate is used in + the "interpreter.sort_class" m-gate. + +> xsub (<string>, <index>, <pattern>, <replacement>) [m-gate] + Returns <string> with <pattern> replaced with <replacement>, but only + once, and only after <index>. + +> protector (<string>, <index>) [m-gate] + Checks whether <index> in <string> isn't between characters declared + with "interpreter.protector". If that is the case, the function returns + "nil" and the index of the second protector. Otherwise, it returns + <index>. + +> get_index (<string>, <pattern>, <index>) [m-gate] + Checks whether <pattern> occurs in <string>, starting at <index>. If + it does, but if <index>-1 is "interpreter.escape", calls itself with + <index>+1. Otherwise, calls "interpreter.core.tools.protector" to + check whether <index> is in a protected part of the string. If so, + calls itself with <right>+1 instead of <index>, where <right> is the + second return value of of "interpreter.core.tools.protector", i.e. + it searches again after the right protector. If <index> is found, + end of story, returns <index>, otherwise returns nothing. + +The "interpreter.core.tools" table also contains +"magic_characters", a table with an entry for each magic character in Lua except +`"."' and `"%"'; the values to those entries are the same characters +prefixed with `"%"'. The table is used by "interpreter.nomagic" to spot +and replace magic characters, with the dot and the percent sign dealt +with independantly. + + + +============================================= interpreter_reader_tag +=== The interpreter.core.reader table ======= +============================================= + +Interpreter works by hooking in the "open_read_file" callback; the +function registered there is the "interpreter.core.reader.input" +l-gate, built as follows: + + input + . unregister + . . set_unregister + . . use_unregister + . open_file + . set_reader + +> unregister (<filename>) [l-gate] + Contains the following two m-gates; <filename> is received from + "input", which itself receives it from the callback, i.e. that's the + file that's being input (the second argument to "\interpretfile"). + It is also automatically returned. + +> set_unregister () [m-gate] + Sets the function to remove "input" from the callback, namely + "interpreter.unregister"; the gate is called only if gate + "interpreter.unregister" doesn't already exits. If "luatexbase" is + detected, the functions there are used; otherwise, "callback.register" + is used with "nil" as the second argument. + +> use_unregister () [m-gate] + Calls "interpreter.unregister()". (You don't want the next input file + to be processed with Interpreter by default, that's why you remove + the callback function; not that the current one is nonetheless + processed with the current file, of course.) + +> open_file (<filename>) [m-gate] + Returns "io.open(<filename>)". + +> set_reader (<file>) [m-gate] + Returns a table with a "reader" entry containing a function whose + definition is + + function () + return interpreter.core.reader.read_file(f) + end + + That's the convention for the "open_read_file" callback: it should + return such a table, and the function will be called each time a + line is required from the input file. + +So most of the work is done by "interpreter.core.reader.read_file", +which is why it is so heavy; it receives a file handle: + + read_file + . make_paragraph + . . aggregate_lines + . . . read_line + . . . check_direct + . . . insert_line + . . apply_classes + . . . initialize + . . . pass_class + . . . . pass_pattern + . . . . . pass_line + . . . . . . check_index + . . . . . . . call + . . . . . . . replace + . . . . . . . protect + . . . . . . increment_line + . . . . . increment_pattern + . . . . increment_class + . . . apply_class0 + . . . . initialize0 + . . . . pass_pattern + . . . . . pass_line + . . . . . . check_index + . . . . . . . call + . . . . . . . replace + . . . . . . . protect + . . . . . . increment_line + . . . . . increment_pattern + . . . unprotect + . . . . undo_protected + . . . . unprotect_lines + . . . remove_escape + . return_line + +> make_paragraph (<file>) [l-gate] + The big l-gate that contains everything that follows, barring + "return_line". It is called if and only if the "interpreter.core.lines" + table is empty; that table is where lines of a paragraph are stored, + and it is emptied by "return_line". + +> aggregate_lines (<file>, <line>) [l-gate] + The main l-gate that reads line from <file> and stores them in + "interpreter.core.lines". It loops until <line> is "nil" or equivalent + to "interpreter.paragraph". (Of course, <line> is "nil" on the first + iteration, but the "loopuntil" conditional is evaluated after that + first iteration, during which the last subgate "insert_line" will + probably returns a line.) + +> read_line (<file>) [m-gate] + Reads the next line from <file>, and returns <file> and that line. + +> check_direct (<file>, <line>) [m-gate] + If <line> begins with "interpreter.direct", removes it and use + "loadstring" on the resulting string. Returns <file> and <line>, the + latter set to an empty string is the previous operation applied. The + gate depends on a conditional: <line> should be non-"nil" (of + course), and "interpreter.direct" should be defined. + +> insert_line (<file>, <line>) [m-gate] + Adds <line> to "interpreter.core.lines". Automatically returns the + two arguments (and if <line> isn't "nil" or equivalent to + "interpreter.paragraph", it will be executed again). + +> apply_classes () [l-gate] + The l-gate that applies transformations to the lines, once the paragraph + has been gathered, with the gates that follow. For each class, it + will apply each pattern on each line. It depends on a conditional: + "interpreter.core.lines" shouldn't be empty, and "interpreter.active" + should be true. + +> initialize () [m-gate] + Returns "1, 1, 1, 1"; it simply starts the following loop. + +> pass_class (<class>) [l-gate] + An l-gate called as long as "interpreter.core.reader.protected" isn't + a boolean value (see "unprotect" below) and <class> isn't larger than + "#interpreter.core.classes"; see "increment_class" below, which returns + "<class>+1". In other words, "pass_class" will be repeated for each + class. + +> pass_pattern (<class>, <pattern>) [l-gate] + Same as "pass_class", except it iterates on the patterns in "class": + it is executed as long as "interpreter.core.reader.protected" isn't + a boolean and "<pattern>" isn't larger than "#interpreter.core.classes[<class>]". + See "increment_pattern" below. In other words, it will be repeated + for each pattern in <class>. + +> pass_line (<class>, <pattern>, <line>), [l-gate] + Here we go again, except we check, besides protection, that <line> + isn't larger than "#interpreter.core.lines". See "increment_line" + below. + +> check_index (<class>, <pattern>, <line>, <index>) [l-gate] + This time we check that "interpreter.core.classes[<class>][<pattern>].pattern" + occurs in "interpreter.core.lines[<line>]", starting at <index>. + Protection is checked too, as is the type of "interpreter.core.lines[<line>]"; + indeed, individual lines are protected by turning them into tables + (with a single entry, the line itself), see "protect" below. The + occurrence of the pattern in the line is checked with + "interpreter.core.tools.get_index". + +> call (<class>, <pattern>, <line>, <index>) [m-gate] + This is executed if and only if the pattern table (i.e. + "interpreter.core.classes[<class>][<pattern>]") has a "call" entry. + The index where the pattern occurs is retrieved with + "interpreter.core.tools.get_index" and stored in <newindex>, and then + the "call" entry is executed as: + + call (interpreter.core.lines, + <line>, <newindex>, <pattern table>) + + where <pattern table> is defined as above; if call returns one value, + it is assigned to <index>; if it returns two, they are assigned to + <line> and <index>. The gate returns the four arguments. + +> replace (<class>, <pattern>, <line>, <index>) [m-gate] + This is executed only if the pattern table, as defined above, has a + "replace" field and no "call" field; it applies "interpreter.core.tools.xsub" + as: + + xsub (interpreter.core.lines[<line>], + <newindex>, <pattern table>.pattern, + <pattern table>.replace) + + where <newindex> is defined as in "call". The return value of "xsub" + is assigned to "interpreter.core.lines[<line>]". The gate returns + its four argument, with <index> incremented by the value of the + "offset" entry of the pattern, if any. + +> protect () [m-gate] + The "interpreter.protect()" function can either protect the whole + paragraph (when no argument is passed) or a single line (when a number + is passed). In the first case, "interpreter.core.tools.protected" + takes the value "true", which is checked in various gates above. In + the second case, "interpreter.core.tools.protected" is a table with + each index indicating a line to be protected. This gate implements + the protection in that case: for each entry in the table, it protects + the line with the same index in "interpreter.core.lines" by transforming + it into a table (with a single entry, the string representing the + original line); the type of the line is checked in "check_index" + above. This gate is executed if and only if "interpreter.core.tools.protected" + is a table. The four arguments returned by "call" or "replace" are + automatically returned, so that "check_index" is reexecuted if its + "loop" conditional evaluates to "true". + +> increment_line (<class>, <pattern>, <line>, <index>) [m-gate] + Returns <class>, <pattern>, <line> + 1, <index>, so "pass_line" is + reexecuted if its "loop" conditional evaluates to "true". + +> increment_pattern (<class>, <pattern>, <line>, <index>) [m-gate] + Returns <class>, <pattern> + 1, <line>, <index>, so "pass_pattern" + is reexecuted if its "loop" conditional evaluates to "true". + +> increment_class (<class>, <pattern>, <line>, <index>) [m-gate] + Returns <class> + 1, <pattern>, <line>, <index>, so "pass_class" is + reexecuted if its "loop" conditional evaluates to "true". + +> apply_class0 () [l-gate] + The l-gate that does roughly the same job as "apply_classes", but + with class 0. It is called if and only if "interpreter.core.reader.protected" + isn't a boolean. + +> initialize0 () [m-gate] + The same as "initialize", except it returns "0, 1, 1, 1", since + we're interested in class 0. + +> pass_pattern (<class>, <pattern>) [l-gate] + The same as above. (Note that this version of the gate doesn't occur + in the same l-gate as the previous one, so its local "loop" is also + different.) + +There is one important difference between "apply_class" and "apply_class0": +the latter has "pass_pattern" as its immediate subgate, without +"pass_class" as an intermediary l-gate. That makes sense, since we don't +want to loop on all the classes here (as that was done in "apply_class"), +but we're only interested in class 0. Apart from that, the "pass_pattern" +gate is really the same in both, so all the subgates of "pass_pattern" +are also reapplied here; I won't describe them again. + +> unprotect () [l-gate] + Now all the patterns in all the classes have been applied to the + entire paragraph, and protection must be removed. This l-gate + contains the following two gates. + +> undo_protected () [m-gate] + Simply sets "interpreter.core.tools.protected" to "nil" so it is + ready for the next paragraph. + +> unprotect_lines () [m-gate] + Restore all the lines in "interpreter.core.lines" as simple strings. + +> remove_escape () [m-gate] + If "interpreter.escape" is defined, removes all its occurrences in + each line of the paragraph. + +This is the end of the big "make_paragraph" l-gate. It won't be called +again until the paragraph has been fully passed to TeX, i.e. when +"interpreter.core.lines" is empty. + +> return_line () [m-gate] + Pops the first line from "interpreter.core.lines" and returns it. + Since this is the very last subgate of "read_file", the line is + passed to TeX. + diff --git a/Master/texmf-dist/tex/luatex/interpreter/interpreter.lua b/Master/texmf-dist/tex/luatex/interpreter/interpreter.lua index 3c2d3391eec..2a87d979195 100644 --- a/Master/texmf-dist/tex/luatex/interpreter/interpreter.lua +++ b/Master/texmf-dist/tex/luatex/interpreter/interpreter.lua @@ -1,28 +1,38 @@ -- This is the main Lua file for the Interpreter package. -- Further information in interpreter-doc.pdf or interpreter-doc.txt. --- Paul Isambert - zappathustra AT free DOT fr - July 2011 +-- Paul Isambert - zappathustra AT free DOT fr - December 2011 +-- +-- Beware, this is written with Gates. Please read the Gates doc if +-- you want to understand something. local find, gsub, match, sub = string.find, string.gsub, string.match, string.sub -local insert, sort = table.insert, table.sort +local insert, sort, remove = table.insert, table.sort, table.remove +local io_open = io.open +require("gates.lua") --- Utility function sorting patterns by length (alphabetically if they are of --- equal length). -local function string_order (a, b) - local a, b = a.pattern, b.pattern - return #a == #b and a < b or #a > #b -end +interpreter = gates.new("interpreter") -interpreter = { -- *** interpreter.active *** -- Following paragraphs (as defined by interpreter.paragraph) are interpreted -- iff this is not set to false. - active = true, +interpreter.active = true -- *** interpreter.default_class *** -- Sets the default class for patterns which are added without specifying the -- class. Default 1. - default_class = 1 - } -local _classes = {} +interpreter.default_class = 1 + +interpreter.core = { + classes = {}, -- The classes of patterns. + lines = {}, -- The lines of the paragraph. + reader = gates.new("interpreter_reader"), -- The main processing functions. + tools = gates.new("interpreter_tools")} -- Auxiliary functions. + +-- Utility function sorting patterns by length (alphabetically if they are of +-- equal length). +function interpreter.core.tools.sort (a, b) + local a, b = a.pattern, b.pattern + return #a == #b and a < b or #a > #b +end -- *** interpreter.add_pattern (table) *** -- Creates pattern <table>, which can contain the following entries: @@ -47,29 +57,41 @@ local _classes = {} -- always applied last). If <class> is not given, the -- default_class number is used. Classes must be numbered -- consecutively. -function interpreter.add_pattern (tb) - local class = tb.class or interpreter.default_class - interpreter.set_class(class, {}) - setmetatable(tb, _classes[class].meta) - if tb.nomagic then - tb.pattern = interpreter.nomagic(tb.pattern) - end - insert(_classes[class], tb) - sort(_classes[class], string_order) - return tb -end +interpreter.list{"add_pattern", + {"ensure_class", + function (tb) + local class = tb.class or interpreter.default_class + interpreter.set_class(class, {}) + setmetatable(tb, interpreter.core.classes[class].meta) + return tb, class + end}, + {"apply_nomagic", conditional = function (tb) return tb.nomagic end, + autoreturn = true, + function (tb, class) + tb.pattern = interpreter.nomagic(tb.pattern) + end}, + {"insert_pattern", autoreturn = true, + {"do_insert", autoreturn = true, + function (tb, class) + insert(interpreter.core.classes[class], tb) + end}, + {"sort_class", autoreturn = true, + function (tb, class) + sort(interpreter.core.classes[class], interpreter.core.tools.sort) + end}}} -- *** interpreter.set_class (number, table) *** -- Sets default values (of the table normally specified in add_pattern) for -- patterns of class <number>; patterns added to this class can still specify -- different values, which will override defaults. In other words, this is a -- metatable for patterns (which are tables) of that class. -function interpreter.set_class(num, tb) - _classes[num] = _classes[num] or { meta = { __index = function (_, k) return _classes[num].meta[k] end } } - for a, b in pairs(tb) do - _classes[num].meta[a] = b - end - return _classes[num] +function interpreter.set_class (num, tb) + interpreter.core.classes[num] = interpreter.core.classes[num] or + { meta = { __index = function (_, k) return interpreter.core.classes[num].meta[k] end } } + for a, b in pairs(tb) do + interpreter.core.classes[num].meta[a] = b + end + return interpreter.core.classes[num] end -- Class 0 must exist since it is always used at the end of the paragraph. @@ -78,7 +100,7 @@ interpreter.set_class(0, {}) -- *** interpreter.nomagic (string) *** -- Turns a normal string into a string with magic characters escaped, so it -- can be used as a pattern. -local magic_characters = { +interpreter.core.tools.magic_characters = { ["^"] = "%^", ["$"] = "%$", ["("] = "%(", @@ -92,11 +114,9 @@ local magic_characters = { ["-"] = "%-", ["?"] = "%?", } ---function interpreter.nomagic (str) --- return gsub(str, ".", magic_characters) ---end function interpreter.nomagic (str) local i, s = 1, "" + local magic_characters = interpreter.core.tools.magic_characters while i <= #str do local c, c2, c3 = sub(str, i, i), sub(str, i + 1, i + 1), sub(str, i + 2, i + 2) i = i + 1 @@ -114,28 +134,28 @@ function interpreter.nomagic (str) end return s end + -- *** interpreter.protect ([spec]) *** -- Protects a set of lines in a paragraph; a protected line won't be -- interpreted. If <spec> is a number, this protects line <spec> in the current -- paragraph; if <spec> is true, this protects the entire current paragraph. Of -- course, patterns that were applied to the line(s) or paragraph before -- protection happened aren't undone. -local _protected function interpreter.protect (num) if type(num) == "number" then - if type(_protected) ~= "boolean" then - _protected = _protected or {} - _protected[num] = true + if type(interpreter.core.reader.protected) ~= "boolean" then + interpreter.core.reader.protected = interpreter.core.reader.protected or {} + interpreter.core.reader.protected[num] = true end else - _protected = true + interpreter.core.reader.protected = true end end -- Utility function making a replacement in a string but only from a certain -- position and only once. We can't let gsub unrestricted, because some -- part(s) of the string might be protected. -local function xsub (str, num, patt, rep) +function interpreter.core.tools.xsub (str, num, patt, rep) return sub(str, 1, num-1) .. gsub(sub(str, num), patt, rep, 1) end @@ -160,7 +180,7 @@ end -- interpreter.escape. local P, Cf, Cg, Cp, Ct, V = lpeg.P, lpeg.Cf, lpeg.Cg, lpeg.Cp, lpeg.Ct, lpeg.V local _grammar -local function _protector (str, index) +function interpreter.core.tools.protector (str, index) local protections = Cf(Ct("") * Cg{ _grammar + 1 * V(1) }^1, rawset) protections = protections:match(str) if protections then @@ -196,93 +216,21 @@ end -- "this won't be *processed*" will be passed to TeX (note that the escape -- character has disappeared). -local function get_index (str, patt, index) +function interpreter.core.tools.get_index (str, patt, index) index = find(str, patt, index) if index then if sub(str, index-1, index-1) == interpreter.escape then - return get_index(str, patt, index + 1) + return interpreter.core.tools.get_index(str, patt, index + 1) elseif _grammar then local right - index, right = _protector(str, index, patt) - return index or get_index(str, patt, right + 1) + index, right = interpreter.core.tools.protector(str, index, patt) + return index or interpreter.core.tools.get_index(str, patt, right + 1) else return index end end end --- The main function that applies all the patterns in a <class> on a given --- paragraph. -local function process_buffer (buffer, class) - for _, tb in ipairs(class) do - local num, idx = 1, 1 - while num <= #buffer do - local line = type(buffer[num]) == "string" and buffer[num] - if line then - local index = get_index(line, tb.pattern, idx) - if index then - if tb.call then - local l, o = tb.call(buffer, num, index, tb) - if o then - num, idx = l, o - elseif l then - idx = l - end - elseif tb.replace then - buffer[num] = xsub(line, index, tb.pattern, tb.replace) - idx = index + (tb.offset or 0) - end - -- If there was a function applied, protection might have been - -- enforced. Acts accordingly. - if _protected then - if type(_protected) == "table" then - for n, _ in pairs(_protected) do - if type(buffer[n]) == "string" then - buffer[n] = {buffer[n]} - end - end - if _protected[num] then - num = num + 1 - end - else - break - end - end - else - num = num + 1 - idx = 1 - end - else - num = num + 1 - idx = 1 - end - end - end -end - --- The functions that iterates the previous one for each class. Note that --- class 0 is processed last. -local function read_buffer (buffer) - for _, class in ipairs(_classes) do - if type(_protected) ~= "boolean" then - process_buffer(buffer, class) - end - end - if type(_protected) ~= "boolean" then - process_buffer(buffer, _classes[0]) - end - _protected = nil - -- Changes protected lines (tables) back to strings. - for n, _ in ipairs(buffer) do - if type(buffer[n]) == "table" then - buffer[n] = buffer[n][1] - end - end - return buffer -end - -local lines = { } - -- *** interpreter.paragraph *** -- The pattern that defines a line acting as a paragraph boundary, -- prompting Interpreter to process the lines gathered up to now. Default is a @@ -302,69 +250,143 @@ interpreter.direct = "%%%%I%s+" -- At last, the function to be registered in open_read_file, defining the -- function that reads a file. -function interpreter.read_file (fname) - -- *** interpreter.unregister () *** - -- The function used to remove read_file from the "open_read_file" callback. - -- Uses callback.register by default, or luatexbase.remove_from_callback if - -- detected. - if not interpreter.unregister then - if luatexbase and luatexbase.remove_from_callback then - function interpreter.unregister () - luatexbase.remove_from_callback("open_read_file", "interpreter") - end - else - function interpreter.unregister () - callback.register("open_read_file", nil) - end - end - end - interpreter.unregister() - local f, line = io.open(fname), true - local read_line = function () - - -- If no lines is left in store, we gather a new paragraph. - if #lines == 0 then - local line = f:read () - while line do - -- Checks for direct code. - if interpreter.direct and match(line, "^" .. interpreter.direct) then - loadstring(gsub(line, "^" .. interpreter.direct, ""))() - line = "" - end - table.insert(lines, line) - -- Checks for paragraph boundary, which breaks line gathering. - if gsub(line, interpreter.paragraph, "") == "" then - break - end - line = f:read () - end - - -- Pass the paragraph (a table of strings) to the bug functions above, - -- if interpreter is active; then remove escape characters, if any. - if interpreter.active then - lines = read_buffer (lines) - if interpreter.escape then - for num, line in ipairs(lines) do - lines[num] = gsub(line, interpreter.escape, "") - end - end - end - end - return lines and table.remove (lines, 1) - end - return {reader = read_line} -end +interpreter.core.reader.list{"read_file", + {"make_paragraph", conditional = function () return #interpreter.core.lines == 0 end, + {"aggregate_lines", loopuntil = function (_, line) return not line or gsub(line, "^" .. interpreter.paragraph .. "$", "") == "" end, + {"read_line", + function (f) + return f, f:read() + end}, + {"check_direct", conditional = function (_, line) return line and interpreter.direct end, + function (f, line) + if match(line, "^" .. interpreter.direct) then + loadstring(gsub(line, "^" .. interpreter.direct, ""))() + line = "" + end + return f, line + end}, + {"insert_line", conditional = function (_, line) return line end, + autoreturn = true, + function (f, line) + insert(interpreter.core.lines, line) + end}}, + {"apply_classes", conditional = function () return #interpreter.core.lines > 0 and interpreter.active end, + {"initialize", + function () + return 1, 1, 1, 1 + end}, + {"pass_class", loop = function (c) return type(interpreter.core.reader.protected) ~= "boolean" + and c <= #interpreter.core.classes end, + {"pass_pattern", loop = function (c, p) return type(interpreter.core.reader.protected) ~= "boolean" + and p <= #interpreter.core.classes[c] end, + {"pass_line", loop = function (c, p, l) return type(interpreter.core.reader.protected) ~= "boolean" + and l <= #interpreter.core.lines end, + {"check_index", loop = function (c, p, l, i) + return (type(interpreter.core.lines[l]) == "string" and type(interpreter.core.reader.protected) ~= "boolean") + and interpreter.core.tools.get_index(interpreter.core.lines[l], interpreter.core.classes[c][p].pattern, i) + end, + {"call", conditional = function (c, p) return interpreter.core.classes[c][p].call end, + function(c, p, l, i) + local line, pattern = interpreter.core.lines[l], interpreter.core.classes[c][p] + local index = interpreter.core.tools.get_index(line, pattern.pattern, i) + local L, O = pattern.call(interpreter.core.lines, l, index, pattern) + if O then + l, i = L, O + elseif L then + i = L + end + return c, p, l, i + end}, + {"replace", conditional = function (c, p) return not interpreter.core.classes[c][p].call + and interpreter.core.classes[c][p].replace end, + function(c, p, l, i) + local line, pattern = interpreter.core.lines[l], interpreter.core.classes[c][p] + local index = interpreter.core.tools.get_index(line, pattern.pattern, i) + interpreter.core.lines[l] = interpreter.core.tools.xsub(line, index, pattern.pattern, pattern.replace) + i = index + (pattern.offset or 0) + return c, p, l, i + end}, + {"protect", conditional = function () return type(interpreter.core.reader.protected) == "table" end, + autoreturn = true, + function () + for n, _ in pairs(interpreter.core.reader.protected) do + if type(interpreter.core.lines[n]) == "string" then + interpreter.core.lines[n] = {interpreter.core.lines[n]} + end + end + end}}, + {"increment_line", function (c, p, l, i) return c, p, l+1, 1 end}}, + {"increment_pattern", function (c, p, l) return c, p+1, 1, 1 end}}, + {"increment_class", function (c) return c+1, 1, 1, 1 end}}, + {"apply_class0", conditional = function () return type(interpreter.core.reader.protected) ~= "boolean" end, + {"initialize0", + function () + return 0, 1, 1, 1 + end}, + {"pass_pattern", loop = function (c, p) return type(interpreter.core.reader.protected) ~= "boolean" + and p <= #interpreter.core.classes[c] end}}, + {"unprotect", + {"undo_protected", + function () + interpreter.core.reader.protected = nil + end}, + {"unprotect_lines", + function () + for n, l in ipairs(interpreter.core.lines) do + if type(l) == "table" then + interpreter.core.lines[n] = l[1] + end + end + end}}, + {"remove_escape", conditional = function () return interpreter.escape end, + function () + for num, line in ipairs(interpreter.core.lines) do + interpreter.core.lines[num] = gsub(line, interpreter.escape, "") + end + end}}}, + {"return_line", + function () + return remove(interpreter.core.lines, 1) + end}} + +interpreter.core.reader.list{"input", + -- *** interpreter.unregister () *** + -- The function used to remove read_file from the "open_read_file" callback. + -- Uses callback.register by default, or luatexbase.remove_from_callback if + -- detected. + {"unregister", autoreturn = true, + {"set_unregister", conditional = function () return interpreter.type"unregister" == 0 end, + function () + if luatexbase and luatexbase.remove_from_callback then + function interpreter.unregister () + luatexbase.remove_from_callback("open_read_file", "interpreter") + end + else + function interpreter.unregister () + callback.register("open_read_file", nil) + end + end + end}, + {"use_unregister", + function () -- You can't use the `unregister' gate directly, because it isn't created yet. + interpreter.unregister() + end}}, + {"open_file", + function (fname) + return io_open(fname) + end}, + {"set_reader", + function (f) + return {reader = function () return interpreter.core.reader.read_file(f) end} + end}} --- *** interpreter.reset () *** --- Resets to default values; mostly to be used when calling a new --- interpretation with \interpretfile. function interpreter.reset () interpreter.active = true interpreter.default_class = 1 - _classes = {} + interpreter.core.classes = {} interpreter.set_class(0, {}) _grammar = nil interpreter.escape = nil diff --git a/Master/texmf-dist/tex/luatex/interpreter/interpreter.sty b/Master/texmf-dist/tex/luatex/interpreter/interpreter.sty index 9ffcad52f1c..94f94443ad9 100644 --- a/Master/texmf-dist/tex/luatex/interpreter/interpreter.sty +++ b/Master/texmf-dist/tex/luatex/interpreter/interpreter.sty @@ -5,10 +5,10 @@ % Author: Paul Isambert. % E-mail: zappathustra AT free DOT fr % Comments and suggestions are welcome. -% Date: July 2011. +% Date: December 2011. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{interpreter}[2011/08/07 v.1.0 Preprocessing input files on the fly.] +\ProvidesPackage{interpreter}[2011/12/03 v.1.1 Preprocessing input files on the fly.] % Needed to prevent LaTeX check input, which would call open_read_file and % thus remove Interpreter's main function (which is removed as soon as it is % used). diff --git a/Master/texmf-dist/tex/luatex/interpreter/interpreter.tex b/Master/texmf-dist/tex/luatex/interpreter/interpreter.tex index 634c1ef826f..3f58083dd72 100644 --- a/Master/texmf-dist/tex/luatex/interpreter/interpreter.tex +++ b/Master/texmf-dist/tex/luatex/interpreter/interpreter.tex @@ -1,9 +1,10 @@ % This is the main TeX file for the Interpreter package. % Further information in interpreter-doc.pdf or interpreter-doc.txt. % -% Paul Isambert - zappathustra AT free DOT fr - July 2011 -\csname Oh no, I won't be loaded twice!\endcsname -\expandafter\let\csname Oh no, I won't be loaded twice!\endcsname\endinput +% Paul Isambert - zappathustra AT free DOT fr - December 2011 +% +\csname Oh no, Interpreter won't be loaded twice!\endcsname +\expandafter\let\csname Oh no, Interpreter won't be loaded twice!\endcsname\endinput \directlua{require("interpreter.lua")} \def\interpretergobble#1{} \unless\ifdefined\interpreterinput @@ -15,7 +16,7 @@ if f then interpreter.reset() dofile(f) - if not interpreter.register then + if interpreter.type"register" == 0 then if luatexbase and luatexbase.add_to_callback then function interpreter.register (f) luatexbase.add_to_callback("open_read_file", f, "interpreter") @@ -26,11 +27,11 @@ end end end - interpreter.register(interpreter.read_file) + interpreter.register(interpreter.core.reader.input) tex.print([[\noexpand\interpreterinput]]) else tex.error("I can't find file `i-#1.lua'. I won't input file `#2'") - tex.print([[\noexpand\igobble]]) + tex.print([[\noexpand\interpretergobble]]) end }{#2}% } |