about-metafun.tex /size: 22 Kb    last modification: 2023-12-21 09:43
1% language=us
2
3\startcomponent about-metafun
4
5\environment about-environment
6
7\startchapter[title={\LUA\ in \METAPOST}]
8
9% Hans Hagen, PRAGMA ADE, April 2014
10
11\startsection[title=Introduction]
12
13Already for some years I have been wondering how it would be if we could escape
14to \LUA\ inside \METAPOST, or in practice, in \MPLIB\ in \LUATEX. The idea is
15simple: embed \LUA\ code in a \METAPOST\ file that gets run as soon as it's seen.
16In case you wonder why \LUA\ code makes sense, imagine generating graphics using
17external data. The capabilities of \LUA\ to deal with that is more flexible and
18advanced than in \METAPOST. Of course we could generate a \METAPOST\ definition
19of a graphic from data but it often makes more sense to do the reverse. I finally
20found time and reason to look into this and in the following sections I will
21describe how it's done.
22
23\stopsection
24
25\startsection[title=The basics]
26
27The approach is comparable to \LUATEX's \type {\directlua}. That primitive can be
28used to execute \LUA\ code and in combination with \type {tex.print} we can pipe
29strings back into the \TEX\ input stream. A complication is that we have to be
30able to operate under different so called catcode regimes: the meaning of
31characters can differ per regime. We also have to deal with line endings in
32special ways as they relate to paragraphs and such. In \METAPOST\ we don't have
33that complication so getting back input into the \METAPOST\ input, we can do so
34with simple strings. For that a mechanism similar to \type {scantokens} can be
35used. That way we can return anything (including nothing) as long as \METAPOST\
36can interpret it and as long as it fulfils the expectations.
37
38\starttyping
39numeric n ; n := scantokens("123.456") ;
40\stoptyping
41
42A script is run as follows:
43
44\starttyping
45numeric n ; n := runscript("return '123.456'") ;
46\stoptyping
47
48This primitive doesn't have the word \type {lua} in its name so in principle any
49wrapper around the library can use it as a hook. In the case of \LUATEX\ the
50script language is of course \LUA. At the \METAPOST\ end we only expect a string.
51How that string is constructed is completely up to the \LUA\ script. In fact, the
52user is completely free to implement the runner any way she or he wants, like:
53
54\starttyping
55local function scriptrunner(code)
56    local f = loadstring(code)
57    if f then
58        return tostring(f())
59    else
60        return ""
61    end
62end
63\stoptyping
64
65This is hooked into an instance as follows:
66
67\starttyping
68local m = mplib.new {
69    ...
70    run_script = scriptrunner,
71    ...
72}
73\stoptyping
74
75Now, beware, this is not the \CONTEXT\ way. We provide print functions and other
76helpers, which we will explain in the next section.
77
78\stopsection
79
80\startsection[title=Helpers]
81
82After I got this feature up and running I played a bit with possible interfaces
83at the \CONTEXT\ (read: \METAFUN) end and ended up with a bit more advanced runner
84where no return value is used. The runner is wrapped in the \type {lua} macro.
85
86\startbuffer
87numeric n ; n := lua("mp.print(12.34567)") ;
88draw textext(n) xsized 4cm withcolor maincolor ;
89\stopbuffer
90
91\typebuffer
92
93This renders as:
94
95\startlinecorrection[blank]
96\processMPbuffer
97\stoplinecorrection
98
99In case you wonder how efficient calling \LUA\ is, don't worry: it's fast enough,
100especially if you consider suboptimal \LUA\ code and the fact that we switch
101between machineries.
102
103\startbuffer
104draw image (
105    lua("statistics.starttiming()") ;
106    for i=1 upto 10000 : draw
107        lua("mp.pair(math.random(-200,200),math.random(-50,50))") ;
108    endfor ;
109    setbounds currentpicture to fullsquare xyscaled (400,100) ;
110    lua("statistics.stoptiming()") ;
111    draw textext(lua("mp.print(statistics.elapsedtime())"))
112        ysized 50 ;
113) withcolor maincolor withpen pencircle scaled 1 ;
114\stopbuffer
115
116\typebuffer
117
118Here the line:
119
120\starttyping
121draw lua("mp.pair(math.random(-200,200),math.random(-50,50))") ;
122\stoptyping
123
124effectively becomes (for instance):
125
126\starttyping
127draw scantokens "(25,40)" ;
128\stoptyping
129
130which in turn becomes:
131
132\starttyping
133draw scantokens (25,40) ;
134\stoptyping
135
136The same happens with this:
137
138\starttyping
139draw textext(lua("mp.print(statistics.elapsedtime())")) ...
140\stoptyping
141
142This becomes for instance:
143
144\starttyping
145draw textext(scantokens "1.23") ...
146\stoptyping
147
148and therefore:
149
150\starttyping
151draw textext(1.23) ...
152\stoptyping
153
154We can use \type {mp.print} here because the \type {textext} macro can deal with
155numbers. The following also works:
156
157\starttyping
158draw textext(lua("mp.quoted(statistics.elapsedtime())")) ...
159\stoptyping
160
161Now we get (in \METAPOST\ speak):
162
163\starttyping
164draw textext(scantokens (ditto & "1.23" & ditto) ...
165\stoptyping
166
167Here \type {ditto} represents the double quotes that mark a string. Of course,
168because we pass the strings directly to \type {scantokens}, there are no outer
169quotes at all, but this is how it can be simulated. In the end we have:
170
171\starttyping
172draw textext("1.23") ...
173\stoptyping
174
175What print variant you use, \type {mp.print} or \type {mp.quoted}, depends on
176what the expected code is: an assignment to a numeric can best be a number or an
177expression resulting in a number.
178
179This graphic becomes:
180
181\startlinecorrection[blank]
182\processMPbuffer
183\stoplinecorrection
184
185The runtime on my current machine is some 0.25 seconds without and 0.12 seconds
186with caching. But to be honest, speed is not really a concern here as the amount
187of complex \METAPOST\ graphics can be neglected compared to extensive node list
188manipulation. Generating the graphic with \LUAJITTEX\ takes 15\% less time.
189\footnote {Processing a small 8 page document like this takes about one second,
190which includes loading a bunch of fonts.}
191
192\startbuffer
193numeric n ; n := lua("mp.print(1) mp.print('+') mp.print(2)") ;
194draw textext(n) xsized 1cm withcolor maincolor ;
195\stopbuffer
196
197The three print command accumulate their arguments:
198
199\typebuffer
200
201As expected we get:
202
203\startlinecorrection[blank]
204\processMPbuffer
205\stoplinecorrection
206
207\startbuffer
208numeric n ; n := lua("mp.print(1,'+',2)") ;
209draw textext(n) xsized 1cm withcolor maincolor ;
210\stopbuffer
211
212Equally valid is:
213
214\typebuffer
215
216This gives the same result:
217
218\startlinecorrection[blank]
219\processMPbuffer
220\stoplinecorrection
221
222Of course all kind of action can happen between the prints. It is also legal to
223have nothing returned as could be seen in the 10.000 dot example: there the timer
224related code returns nothing, so effectively we have \type {scantokens("")}.
225Another helper is \type {mp.quoted}, as in:
226
227\startbuffer
228draw
229    textext(lua("mp.quoted('@0.3f'," & decimal n & ")"))
230    withcolor maincolor ;
231\stopbuffer
232
233\typebuffer
234
235This typesets \processMPbuffer. Note the \type {@}. When no percent character is
236found in the format specifier, we assume that an \type {@} is used instead.
237
238\startbuffer
239\startluacode
240table.save("demo-data.lua",
241    {
242        { 1, 2 }, { 2, 4 }, { 3, 3 }, { 4, 2 },
243        { 5, 2 }, { 6, 3 }, { 7, 4 }, { 8, 1 },
244    }
245)
246\stopluacode
247\stopbuffer
248
249But, the real benefit of embedded \LUA\ is when we deal with data that is stored
250at the \LUA\ end. First we define a small dataset:
251
252\typebuffer
253
254\getbuffer
255
256There are several ways to deal with this table. I will show clumsy as well as
257better looking ways.
258
259\startbuffer
260lua("MP = { } MP.data = table.load('demo-data.lua')") ;
261numeric n ;
262lua("mp.print('n := ',\#MP.data)") ;
263for i=1 upto n :
264    drawdot
265        lua("mp.pair(MP.data[" & decimal i & "])") scaled cm
266        withpen pencircle scaled 2mm
267        withcolor maincolor ;
268endfor ;
269\stopbuffer
270
271\typebuffer
272
273Here we load a \LUA\ table and assign the size to a \METAPOST\ numeric. Next we
274loop over the table entries and draw the coordinates.
275
276\startlinecorrection[blank]
277\processMPbuffer
278\stoplinecorrection
279
280We will stepwise improve this code. In the previous examples we omitted wrapper
281code but here we show it:
282
283\startbuffer
284\startluacode
285    MP.data = table.load('demo-data.lua')
286    function MP.n()
287        mp.print(#MP.data)
288    end
289    function MP.dot(i)
290        mp.pair(MP.data[i])
291    end
292\stopluacode
293
294\startMPcode
295    numeric n ; n := lua("MP.n()") ;
296    for i=1 upto n :
297        drawdot
298            lua("MP.dot(" & decimal i & ")") scaled cm
299            withpen pencircle scaled 2mm
300            withcolor maincolor ;
301    endfor ;
302\stopMPcode
303\stopbuffer
304
305\typebuffer
306
307So, we create a few helpers in the \type {MP} table. This table is predefined so
308normally you don't need to define it. You may however decide to wipe it clean.
309
310\startlinecorrection[blank]
311\getbuffer
312\stoplinecorrection
313
314You can decide to hide the data:
315
316\startbuffer
317\startluacode
318    local data = { }
319    function MP.load(name)
320        data = table.load(name)
321    end
322    function MP.n()
323        mp.print(#data)
324    end
325    function MP.dot(i)
326        mp.pair(data[i])
327    end
328\stopluacode
329\stopbuffer
330
331\typebuffer \getbuffer
332
333It is possible to use less \LUA, for instance in:
334
335\startbuffer
336\startluacode
337    local data = { }
338    function MP.loaded(name)
339        data = table.load(name)
340        mp.print(#data)
341    end
342    function MP.dot(i)
343        mp.pair(data[i])
344    end
345\stopluacode
346
347\startMPcode
348    for i=1 upto lua("MP.loaded('demo-data.lua')") :
349        drawdot
350            lua("MP.dot(",i,")") scaled cm
351            withpen pencircle scaled 4mm
352            withcolor maincolor ;
353    endfor ;
354\stopMPcode
355\stopbuffer
356
357\typebuffer
358
359Here we also omit the \type {decimal} because the \type {lua} macro is clever
360enough to recognize it as a number.
361
362\startlinecorrection[blank]
363\getbuffer
364\stoplinecorrection
365
366By using some \METAPOST\ magic we can even go a step further in readability:
367
368\startbuffer
369\startMPcode{doublefun}
370    lua.MP.load("demo-data.lua") ;
371
372    for i=1 upto lua.MP.n() :
373        drawdot
374            lua.MP.dot(i) scaled cm
375            withpen pencircle scaled 4mm
376            withcolor maincolor ;
377    endfor ;
378
379    for i=1 upto MP.n() :
380        drawdot
381            MP.dot(i) scaled cm
382            withpen pencircle scaled 2mm
383            withcolor white ;
384    endfor ;
385\stopMPcode
386\stopbuffer
387
388\typebuffer
389
390Here we demonstrate that it also works well in \type {double} mode, which makes
391much sense when processing data from other sources. Note how we omit the
392type {lua} prefix: the \type {MP} macro will deal with that.
393
394\startlinecorrection[blank]
395\getbuffer
396\stoplinecorrection
397
398So in the end we can simplify the code that we started with to:
399
400\starttyping
401\startMPcode{doublefun}
402    for i=1 upto MP.loaded("demo-data.lua") :
403        drawdot
404            MP.dot(i) scaled cm
405            withpen pencircle scaled 2mm
406            withcolor maincolor ;
407    endfor ;
408\stopMPcode
409\stoptyping
410
411\stopsection
412
413\startsection[title=Access to variables]
414
415The question with such mechanisms is always: how far should we go. Although
416\METAPOST\ is a macro language, it has properties of procedural languages. It also
417has more introspective features at the user end. For instance, one can loop over
418the resulting picture and manipulate it. This means that we don't need full
419access to \METAPOST\ internals. However, it makes sense to provide access to
420basic variables: \type {numeric}, \type {string}, and \type {boolean}.
421
422\startbuffer
423draw textext(lua("mp.quoted('@0.15f',mp.get.numeric('pi')-math.pi)"))
424    ysized 1cm
425    withcolor maincolor ;
426\stopbuffer
427
428\typebuffer
429
430In double mode you will get zero printed but in scaled mode we definitely get a
431different results:
432
433\startlinecorrection[blank]
434\processMPbuffer
435\stoplinecorrection
436
437\startbuffer
438boolean b ; b := true ;
439draw textext(lua("mp.quoted(mp.get.boolean('b') and 'yes' or 'no')"))
440    ysized 1cm
441    withcolor maincolor ;
442\stopbuffer
443
444In the next example we use \type {mp.quoted} to make sure that indeed we pass a
445string. The \type {textext} macro can deal with numbers, but an unquoted \type
446{yes} or \type {no} is asking for problems.
447
448\typebuffer
449
450Especially when more text is involved it makes sense to predefine a helper in
451the \type {MP} namespace, if only because \METAPOST\ (currently) doesn't like
452newlines in the middle of a string, so a \type {lua} call has to be on one line.
453
454\startlinecorrection[blank]
455\processMPbuffer
456\stoplinecorrection
457
458Here is an example where \LUA\ does something that would be close to impossible,
459especially if more complex text is involved.
460
461% \enabletrackers[metapost.lua]
462
463\startbuffer
464string s ; s := "ΤΕΧ" ; % "τεχ"
465draw textext(lua("mp.quoted(characters.lower(mp.get.string('s')))"))
466    ysized 1cm
467    withcolor maincolor ;
468\stopbuffer
469
470\typebuffer
471
472As you can see here, the whole repertoire of helper functions can be used in
473a \METAFUN\ definition.
474
475\startlinecorrection[blank]
476\processMPbuffer
477\stoplinecorrection
478
479\stopsection
480
481\startsection[title=The library]
482
483In \CONTEXT\ we have a dedicated runner, but for the record we mention the
484low level constructor:
485
486\starttyping
487local m = mplib.new {
488    ...
489    script_runner = function(s) return loadstring(s)() end,
490    script_error  = function(s) print(s) end,
491    ...,
492}
493\stoptyping
494
495An instance (in this case \type {m}) has a few extra methods. Instead you can use
496the helpers in the library.
497
498\starttabulate[|l|l|]
499\HL
500\NC \type {m:get_numeric(name)}       \NC returns a numeric (double) \NC \NR
501\NC \type {m:get_boolean(name)}       \NC returns a boolean (\type {true} or \type {false}) \NC \NR
502\NC \type {m:get_string (name)}       \NC returns a string \NC \NR
503\HL
504\NC \type {mplib.get_numeric(m,name)} \NC returns a numeric (double) \NC \NR
505\NC \type {mplib.get_boolean(m,name)} \NC returns a boolean (\type {true} or \type {false}) \NC \NR
506\NC \type {mplib.get_string (m,name)} \NC returns a string \NC \NR
507\HL
508\stoptabulate
509
510In \CONTEXT\ the instances are hidden and wrapped in high level macros, so there
511you cannot use these commands.
512
513\stopsection
514
515\startsection[title=\CONTEXT\ helpers]
516
517The \type {mp} namespace provides the following helpers:
518
519\starttabulate[|l|l|]
520\HL
521\NC \type {print(...)}                \NC returns one or more values \NC \NR
522\NC \type {pair(x,y)}
523    \type {pair(t)}                   \NC returns a proper pair \NC \NR
524\NC \type {triplet(x,y,z)}
525    \type {triplet(t)}                \NC returns an \RGB\ color \NC \NR
526\NC \type {quadruple(w,x,y,z)}
527    \type {quadruple(t)}              \NC returns an \CMYK\ color \NC \NR
528\NC \type {format(fmt,...)}           \NC returns a formatted string \NC \NR
529\NC \type {quoted(fmt,...)}
530    \type {quoted(s)}                 \NC returns a (formatted) quoted string  \NC \NR
531\NC \type {path(t[,connect][,close])} \NC returns a connected (closed) path \NC \NR
532\HL
533\stoptabulate
534
535The \type {mp.get} namespace provides the following helpers:
536
537\starttabulate[|l|l|]
538\NC \type {numeric(name)}      \NC gets a numeric from \METAPOST \NC \NR
539\NC \type {boolean(name)}      \NC gets a boolean from \METAPOST \NC \NR
540\NC \type {string(name)}       \NC gets a string from \METAPOST \NC \NR
541\HL
542\stoptabulate
543
544\stopsection
545
546\startsection[title=Paths]
547
548% {\em This section will move to the metafun manual.} \blank
549
550In the meantime we got several questions on the \CONTEXT\ mailing list about turning
551coordinates into paths. Now imagine that we have this dataset:
552
553\startbuffer[dataset]
55410 20 20 20 -- sample 1
55530 40 40 60
55650 10
557
55810 10 20 30 % sample 2
55930 50 40 50
56050 20
561
56210 20 20 10 # sample 3
56330 40 40 20
56450 10
565\stopbuffer
566
567\typebuffer[dataset]
568
569In this case I have put the data in a buffer, so that it can be shown
570here, as well as used in a demo. Look how we can add comments. The
571following code converts this into a table with three subtables.
572
573\startbuffer
574\startluacode
575  MP.myset = mp.dataset(buffers.getcontent("dataset"))
576\stopluacode
577\stopbuffer
578
579\typebuffer \getbuffer
580
581We use the \type {MP} (user) namespace to store the table. Next we turn
582these subtables into paths:
583
584\startbuffer
585\startMPcode
586  for i=1 upto lua("mp.print(mp.n(MP.myset))") :
587    draw
588      lua("mp.path(MP.myset[" & decimal i & "])")
589      xysized (HSize,10ExHeight)
590      withpen pencircle scaled .25ExHeight
591      withcolor basiccolors[i]/2 ;
592  endfor ;
593\stopMPcode
594\stopbuffer
595
596\typebuffer
597
598This gives:
599
600\startlinecorrection[blank] \getbuffer \stoplinecorrection
601
602Instead we can fill the path, in which case we will also need to close it. The
603\type {true} argument deals with that:
604
605\startbuffer
606\startMPcode
607  for i=1 upto lua("mp.print(mp.n(MP.myset))") :
608    path p ; p :=
609      lua("mp.path(MP.myset[" & decimal i & "],true)")
610      xysized (HSize,10ExHeight) ;
611    fill p
612      withcolor basiccolors[i]/2
613      withtransparency (1,.5) ;
614  endfor ;
615\stopMPcode
616\stopbuffer
617
618\typebuffer
619
620We get:
621
622\startlinecorrection[blank] \getbuffer \stoplinecorrection
623
624\startbuffer
625\startMPcode
626  for i=1 upto lua("mp.print(mp.n(MP.myset))") :
627    path p ; p :=
628      lua("mp.path(MP.myset[" & decimal i & "])")
629      xysized (HSize,10ExHeight) ;
630    p :=
631      (xpart llcorner boundingbox p,0) --
632      p --
633      (xpart lrcorner boundingbox p,0) --
634      cycle ;
635    fill p
636      withcolor basiccolors[i]/2
637      withtransparency (1,.25) ;
638  endfor ;
639\stopMPcode
640\stopbuffer
641
642The following makes more sense:
643
644\typebuffer
645
646So this gives:
647
648\startlinecorrection[blank] \getbuffer \stoplinecorrection
649
650This (area) fill is so common, that we have a helper for it:
651
652\startbuffer
653\startMPcode
654  for i=1 upto lua("mp.size(MP.myset)") :
655    fill area
656      lua("mp.path(MP.myset[" & decimal i & "])")
657      xysized (HSize,5ExHeight)
658      withcolor basiccolors[i]/2
659      withtransparency (2,.25) ;
660  endfor ;
661\stopMPcode
662\stopbuffer
663
664\typebuffer
665
666So this gives:
667
668\startlinecorrection[blank] \getbuffer \stoplinecorrection
669
670This snippet of \METAPOST\ code still looks kind of horrible, so how can we make
671it look better? Here is an attempt. First we define a bit more \LUA:
672
673\startbuffer
674\startluacode
675local data = mp.dataset(buffers.getcontent("dataset"))
676
677MP.dataset = {
678  Line = function(n) mp.path(data[n]) end,
679  Size = function()  mp.size(data)    end,
680}
681\stopluacode
682\stopbuffer
683
684\typebuffer \getbuffer
685
686\startbuffer
687\startMPcode
688  for i=1 upto lua.MP.dataset.Size() :
689    path p ; p :=
690      lua.MP.dataset.Line(i)
691      xysized (HSize,20ExHeight) ;
692    draw
693      p
694      withpen pencircle scaled .25ExHeight
695      withcolor basiccolors[i]/2 ;
696    drawpoints
697      p
698      withpen pencircle scaled ExHeight
699      withcolor .5white ;
700  endfor ;
701\stopMPcode
702\stopbuffer
703
704We can now make the \METAPOST\ look more natural. Of course, this is possible
705because in \METAFUN\ the \type {lua} macro does some extra work.
706
707\typebuffer
708
709As expected, we get the desired result:
710
711\startlinecorrection[blank] \getbuffer \stoplinecorrection
712
713Once we start making things look nicer and more convenient, we quickly end up
714with helpers like those in the next example. First we save some demo data in
715files:
716
717\startbuffer
718\startluacode
719  io.savedata("foo.tmp","10 20 20 20 30 40 40 60 50 10")
720  io.savedata("bar.tmp","10 10 20 30 30 50 40 50 50 20")
721\stopluacode
722\stopbuffer
723
724\typebuffer \getbuffer
725
726We load the data in datasets:
727
728\startbuffer
729\startMPcode
730  lua.mp.datasets("load","foo","foo.tmp") ;
731  lua.mp.datasets("load","bar","bar.tmp") ;
732  fill area
733    lua.mp.datasets("foo","line")
734    xysized (HSize/2-EmWidth,10ExHeight)
735    withpen pencircle scaled .25ExHeight
736    withcolor green/2 ;
737  fill area
738    lua.mp.datasets("bar","line")
739    xysized (HSize/2-EmWidth,10ExHeight)
740    shifted (HSize/2+EmWidth,0)
741    withpen pencircle scaled .25ExHeight
742    withcolor red/2 ;
743\stopMPcode
744\stopbuffer
745
746\typebuffer
747
748Because the datasets are stored by name, we can use them without worrying about
749them being forgotten:
750
751\startlinecorrection[blank] \getbuffer \stoplinecorrection
752
753If no tag is given, the filename (without suffix) is used as a tag, so the
754following is valid:
755
756\starttyping
757\startMPcode
758  lua.mp.datasets("load","foo.tmp") ;
759  lua.mp.datasets("load","bar.tmp") ;
760\stopMPcode
761\stoptyping
762
763The following methods are defined for a dataset:
764
765\starttabulate[|l|pl|]
766\HL
767\NC \type {method} \NC usage \NC \NR
768\HL
769\NC \type {Size}   \NC the number of subsets in a dataset \NC \NR
770\NC \type {Line}   \NC the joined pairs in a dataset making a non|-|closed path \NC \NR
771\NC \type {Data}   \NC the table containing the data (in subsets, so there is always at least one subset) \NC \NR
772\HL
773\stoptabulate
774
775{\em Due to limitations in \METAPOST\ suffix handling the methods start with an
776uppercase character.}
777
778\stopsection
779
780\startsection[title=Remark]
781
782The features described here are currently still experimental but the interface
783will not change. There might be a few more accessors and for sure more \LUA\
784helpers will be provided. As usual I need some time to play with it before I make
785up my mind. It is also possible to optimize the \METAPOST||\LUA\ script call a
786bit, but I might do that later.
787
788When we played with this interface we ran into problems with loop variables
789and macro arguments. These are internally kind of anonymous. Take this:
790
791\starttyping
792for i=1 upto 100 : draw(i,i) endfor ;
793\stoptyping
794
795The \type {i} is not really a variable with name \type {i} but becomes an object
796(capsule) when the condition is scanned, and a reference to that object when the
797body is scanned. The body of the for loop gets expanded for each step, but at that
798time there is no longer a variable \type {i}. The same is true for variables in:
799
800\starttyping
801def foo(expr x, y, delta) = draw (x+delta,y+delta) enddef ;
802\stoptyping
803
804We are still trying to get this right with the \LUA\ interface. Interesting is
805that when we were exploring this, we ran into quite some cases where we could
806make \METAPOST\ abort due some memory or stack overflow. Some are just bugs in
807the new code (due to the new number model) while others come with the design of
808the system: border cases that never seem to happen in interactive use while the
809library use assumes no interaction in case of errors.
810
811In \CONTEXT\ there are more features and helpers than shown here but these are
812discussed in the \METAFUN\ manual.
813
814\stopsection
815
816\stopchapter
817
818\stopcomponent
819
820% \startMPcode{doublefun}
821%     numeric n ; n := 123.456 ;
822%     lua("print('>>>>>>>>>>>> number',mp.get.number('n'))") ;
823%     lua("print('>>>>>>>>>>>> number',mp.get.boolean('n'))") ;
824%     lua("print('>>>>>>>>>>>> number',mp.get.string('n'))") ;
825%     boolean b ; b := true ;
826%     lua("print('>>>>>>>>>>>> boolean',mp.get.number('b'))") ;
827%     lua("print('>>>>>>>>>>>> boolean',mp.get.boolean('b'))") ;
828%     lua("print('>>>>>>>>>>>> boolean',mp.get.string('b'))") ;
829%     string s ; s := "TEST" ;
830%     lua("print('>>>>>>>>>>>> string',mp.get.number('s'))") ;
831%     lua("print('>>>>>>>>>>>> string',mp.get.boolean('s'))") ;
832%     lua("print('>>>>>>>>>>>> string',mp.get.string('s'))") ;
833% \stopMPcode
834
835