1if not modules then modules = { } end modules ['mlib-run'] = {
2 version = 1.001,
3 comment = "companion to mlib-ctx.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files",
7}
8
9
10
11
12
13
14
15
16local type, tostring, tonumber, next = type, tostring, tonumber, next
17local find, striplines = string.find, utilities.strings.striplines
18local concat, insert, remove = table.concat, table.insert, table.remove
19
20local emptystring = string.is_empty
21
22local trace_graphics = false trackers.register("metapost.graphics", function(v) trace_graphics = v end)
23local trace_tracingall = false trackers.register("metapost.tracingall", function(v) trace_tracingall = v end)
24
25local report_metapost = logs.reporter("metapost")
26local texerrormessage = logs.texerrormessage
27
28local starttiming = statistics.starttiming
29local stoptiming = statistics.stoptiming
30
31local formatters = string.formatters
32
33local mplib = mplib
34metapost = metapost or { }
35local metapost = metapost
36
37metapost.showlog = false
38metapost.lastlog = ""
39metapost.texerrors = false
40metapost.exectime = metapost.exectime or { }
41metapost.nofruns = 0
42
43local mpxformats = { }
44local nofformats = 0
45local mpxpreambles = { }
46local mpxterminals = { }
47local mpxextradata = { }
48
49
50
51
52
53
54
55local function flatten(source,target)
56 for i=1,#source do
57 local d = source[i]
58 if type(d) == "table" then
59 flatten(d,target)
60 elseif d and d ~= "" then
61 target[#target+1] = d
62 end
63 end
64 return target
65end
66
67local function prepareddata(data)
68 if data and data ~= "" then
69 if type(data) == "table" then
70 data = flatten(data,{ })
71 data = #data > 1 and concat(data,"\n") or data[1]
72 end
73 return data
74 end
75end
76
77local function executempx(mpx,data)
78 local terminal = mpxterminals[mpx]
79 if terminal then
80 terminal.writer(data)
81 data = ""
82 elseif type(data) == "table" then
83 data = prepareddata(data,collapse)
84 end
85 metapost.nofruns = metapost.nofruns + 1
86 return mpx:execute(data)
87end
88
89directives.register("mplib.texerrors", function(v) metapost.texerrors = v end)
90trackers.register ("metapost.showlog", function(v) metapost.showlog = v end)
91
92function metapost.resetlastlog()
93 metapost.lastlog = ""
94end
95
96local new_instance = mplib.new
97local find_file = mplib.finder
98
99function metapost.reporterror(result)
100 if not result then
101 report_metapost("error: no result object returned")
102 return true
103 elseif result.status == 0 then
104 return false
105 elseif mplib.realtimelogging then
106 return false
107 else
108 local t = result.term
109 local e = result.error
110 local l = result.log
111 local report = metapost.texerrors and texerrormessage or report_metapost
112 if t and t ~= "" then
113 report("mp error: %s",striplines(t))
114 end
115 if e == "" or e == "no-error" then
116 e = nil
117 end
118 if e then
119 report("mp error: %s",striplines(e))
120 end
121 if not t and not e and l then
122 metapost.lastlog = metapost.lastlog .. "\n" .. l
123 report_metapost("log: %s",l)
124 else
125 report_metapost("error: unknown, no error, terminal or log messages")
126 end
127 return true
128 end
129end
130
131local f_preamble = formatters [ [[
132 boolean mplib ; mplib := true ;
133 let dump = endinput ;
134 input "%s" ;
135 randomseed:=%s;
136]] ]
137
138local methods = {
139 double = "double",
140 scaled = "scaled",
141
142 binary = "double",
143 decimal = "decimal",
144 default = "scaled",
145}
146
147function metapost.runscript(code)
148 return ""
149end
150
151function metapost.scripterror(str)
152 report_metapost("script error: %s",str)
153end
154
155
156
157local seed = nil
158
159function metapost.load(name,method)
160 starttiming(mplib)
161 if not seed then
162 seed = job.getrandomseed()
163 if seed <= 1 then
164 seed = seed % 1000
165 elseif seed > 4095 then
166 seed = seed % 4096
167 end
168 end
169 method = method and methods[method] or "scaled"
170 local mpx, terminal = new_instance {
171 ini_version = true,
172 math_mode = method,
173 run_script = metapost.runscript,
174 script_error = metapost.scripterror,
175 make_text = metapost.maketext,
176 extensions = 1,
177
178 utf8_mode = true,
179 text_mode = true,
180 }
181 report_metapost("initializing number mode %a",method)
182 local result
183 if not mpx then
184 result = { status = 99, error = "out of memory"}
185 else
186 mpxterminals[mpx] = terminal
187
188 metapost.pushscriptrunner(mpx)
189 result = executempx(mpx,f_preamble(file.addsuffix(name,"mp"),seed))
190 metapost.popscriptrunner()
191 end
192 stoptiming(mplib)
193 metapost.reporterror(result)
194 return mpx, result
195end
196
197function metapost.checkformat(mpsinput,method)
198 local mpsinput = mpsinput or "metafun"
199 local foundfile = ""
200 if file.suffix(mpsinput) ~= "" then
201 foundfile = find_file(mpsinput) or ""
202 end
203
204
205
206 if CONTEXTLMTXMODE > 0 and foundfile == "" then
207 foundfile = find_file(file.replacesuffix(mpsinput,"mpxl")) or ""
208 end
209 if foundfile == "" then
210 foundfile = find_file(file.replacesuffix(mpsinput,"mpiv")) or ""
211 end
212 if foundfile == "" then
213 foundfile = find_file(file.replacesuffix(mpsinput,"mp")) or ""
214 end
215 if foundfile == "" then
216 report_metapost("loading %a fails, format not found",mpsinput)
217 else
218 report_metapost("loading %a as %a using method %a",mpsinput,foundfile,method or "default")
219 local mpx, result = metapost.load(foundfile,method)
220 if mpx then
221 return mpx
222 else
223 report_metapost("error in loading %a",mpsinput)
224 metapost.reporterror(result)
225 end
226 end
227end
228
229function metapost.unload(mpx)
230 starttiming(mplib)
231 if mpx then
232 mpx:finish()
233 end
234 stoptiming(mplib)
235end
236
237metapost.defaultformat = "metafun"
238metapost.defaultinstance = "metafun"
239metapost.defaultmethod = "default"
240
241function metapost.getextradata(mpx)
242 return mpxextradata[mpx]
243end
244
245function metapost.pushformat(specification,f,m)
246 if type(specification) ~= "table" then
247 specification = {
248 instance = specification,
249 format = f,
250 method = m,
251 }
252 end
253 local instance = specification.instance
254 local format = specification.format
255 local method = specification.method
256 local definitions = specification.definitions
257 local extensions = specification.extensions
258 local preamble = nil
259 if not instance or instance == "" then
260 instance = metapost.defaultinstance
261 specification.instance = instance
262 end
263 if not format or format == "" then
264 format = metapost.defaultformat
265 specification.format = format
266 end
267 if not method or method == "" then
268 method = metapost.defaultmethod
269 specification.method = method
270 end
271 if definitions and definitions ~= "" then
272 preamble = definitions
273 end
274 if extensions and extensions ~= "" then
275 if preamble then
276 preamble = preamble .. "\n" .. extensions
277 else
278 preamble = extensions
279 end
280 end
281 nofformats = nofformats + 1
282 local usedinstance = instance .. ":" .. nofformats
283 local mpx = mpxformats [usedinstance]
284 local mpp = mpxpreambles[instance] or ""
285
286 if preamble then
287 preamble = prepareddata(preamble)
288 mpp = mpp .. "\n" .. preamble
289 mpxpreambles[instance] = mpp
290 end
291 if not mpx then
292 report_metapost("initializing instance %a using format %a and method %a",usedinstance,format,method)
293 mpx = metapost.checkformat(format,method)
294 mpxformats [usedinstance] = mpx
295 mpxextradata[mpx] = { }
296 if mpp ~= "" then
297 preamble = mpp
298 end
299 end
300 if preamble then
301 executempx(mpx,preamble)
302 end
303 specification.mpx = mpx
304 return mpx
305end
306
307
308
309
310
311
312
313function metapost.popformat()
314 nofformats = nofformats - 1
315end
316
317function metapost.reset(mpx)
318 if not mpx then
319
320 elseif type(mpx) == "string" then
321 if mpxformats[mpx] then
322 local instance = mpxformats[mpx]
323 instance:finish()
324 mpxterminals[mpx] = nil
325 mpxextradata[mpx] = nil
326 mpxformats [mpx] = nil
327 end
328 else
329 for name, instance in next, mpxformats do
330 if instance == mpx then
331 mpx:finish()
332 mpxterminals[mpx] = nil
333 mpxextradata[mpx] = nil
334 mpxformats [mpx] = nil
335 break
336 end
337 end
338 end
339end
340
341local mp_tra = { }
342local mp_tag = 0
343
344
345
346do
347
348 local stack, top = { }, nil
349
350 function metapost.setvariable(k,v)
351 if top then
352 top[k] = v
353 else
354 metapost.variables[k] = v
355 end
356 end
357
358 function metapost.pushvariable(k)
359 local t = { }
360 if top then
361 insert(stack,top)
362 top[k] = t
363 else
364 metapost.variables[k] = t
365 end
366 top = t
367 end
368
369 function metapost.popvariable()
370 top = remove(stack)
371 end
372
373 local stack = { }
374
375 function metapost.pushvariables()
376 insert(stack,metapost.variables)
377 metapost.variables = { }
378 end
379
380 function metapost.popvariables()
381 metapost.variables = remove(stack) or metapost.variables
382 end
383
384end
385
386
387if not metapost.process then
388
389 function metapost.process(specification)
390 metapost.run(specification)
391 end
392
393end
394
395
396
397
398
399
400
401
402
403
404
405local function makebeginbanner(specification)
406 return formatters["%% begin graphic: n=%s\n\n"](metapost.n)
407end
408
409local function makeendbanner(specification)
410 return "\n% end graphic\n\n"
411end
412
413function metapost.run(specification)
414 local mpx = specification.mpx
415 local data = specification.data
416 local converted = false
417 local result = { }
418 local mpxdone = type(mpx) == "string"
419 if mpxdone then
420 mpx = metapost.pushformat { instance = mpx, format = mpx }
421 end
422 if mpx and data then
423 local tra = nil
424 starttiming(metapost)
425 metapost.variables = { }
426 metapost.pushscriptrunner(mpx)
427 if trace_graphics then
428 tra = mp_tra[mpx]
429 if not tra then
430 mp_tag = mp_tag + 1
431 local jobname = tex.jobname
432 tra = {
433 inp = io.open(formatters["%s-mplib-run-%03i.mp"] (jobname,mp_tag),"w"),
434 log = io.open(formatters["%s-mplib-run-%03i.log"](jobname,mp_tag),"w"),
435 }
436 mp_tra[mpx] = tra
437 end
438 local banner = makebeginbanner(specification)
439 tra.inp:write(banner)
440 tra.log:write(banner)
441 end
442 local function process(d,i)
443 if d then
444 if trace_graphics then
445 if i then
446 tra.inp:write(formatters["\n%% begin snippet %s\n"](i))
447 end
448 if type(d) == "table" then
449 for i=1,#d do
450 tra.inp:write(d[i])
451 end
452 else
453 tra.inp:write(d)
454 end
455 if i then
456 tra.inp:write(formatters["\n%% end snippet %s\n"](i))
457 end
458 end
459 starttiming(metapost.exectime)
460 result = executempx(mpx,d)
461 stoptiming(metapost.exectime)
462 if trace_graphics and result then
463 local str = result.log or result.error
464 if str and str ~= "" then
465 tra.log:write(str)
466 end
467 end
468 if not metapost.reporterror(result) then
469 if metapost.showlog then
470
471 local str = result.term ~= "" and result.term or "no terminal output"
472 if not emptystring(str) then
473 metapost.lastlog = metapost.lastlog .. "\n" .. str
474 report_metapost("log: %s",str)
475 end
476 end
477 if result.fig then
478 converted = metapost.convert(specification,result)
479 end
480 end
481 elseif i then
482 report_metapost("error: invalid graphic component %s",i)
483 else
484 report_metapost("error: invalid graphic")
485 end
486 end
487
488
489 if type(data) == "table" then
490 if trace_tracingall then
491 executempx(mpx,"tracingall;")
492 end
493 process(data)
494
495
496
497 else
498 if trace_tracingall then
499 data = "tracingall;" .. data
500 end
501 process(data)
502 end
503 if trace_graphics then
504 local banner = makeendbanner(specification)
505 tra.inp:write(banner)
506 tra.log:write(banner)
507 end
508 stoptiming(metapost)
509 metapost.popscriptrunner(mpx)
510 end
511 if mpxdone then
512 metapost.popformat()
513 end
514 return converted, result
515end
516
517if not metapost.convert then
518
519 function metapost.convert()
520 report_metapost('warning: no converter set')
521 end
522
523end
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588function metapost.directrun(formatname,filename,outputformat,astable,mpdata)
589 report_metapost("producing postscript and svg is no longer supported")
590end
591
592do
593
594 local result = { }
595 local width = 0
596 local height = 0
597 local depth = 0
598 local bbox = { 0, 0, 0, 0 }
599
600 local flusher = {
601 startfigure = function(n,llx,lly,urx,ury)
602 result = { }
603 width = urx - llx
604 height = ury
605 depth = -lly
606 bbox = { llx, lly, urx, ury }
607 end,
608 flushfigure = function(t)
609 local r = #result
610 for i=1,#t do
611 r = r + 1
612 result[r] = t[i]
613 end
614 end,
615 stopfigure = function()
616 end,
617 }
618
619
620
621 function metapost.simple(instance,code,useextensions,dontwrap)
622
623 local mpx = metapost.pushformat {
624 instance = instance or "simplefun",
625 format = "metafun",
626 method = "double",
627 }
628 metapost.process {
629 mpx = mpx,
630 flusher = flusher,
631 askedfig = 1,
632 useplugins = useextensions,
633 data = dontwrap and { code } or { "beginfig(1);", code, "endfig;" },
634 incontext = false,
635 }
636 metapost.popformat()
637 if result then
638 local stream = concat(result," ")
639 result = { }
640 return stream, width, height, depth, bbox
641 else
642 return "", 0, 0, 0, { 0, 0, 0, 0 }
643 end
644 end
645
646end
647
648local getstatistics = mplib.getstatistics or mplib.statistics
649
650function metapost.getstatistics(memonly)
651 if memonly then
652 local n, m = 0, 0
653 for name, mpx in next, mpxformats do
654 n = n + 1
655 m = m + getstatistics(mpx).memory
656 end
657 return n, m
658 else
659 local t = { }
660 for name, mpx in next, mpxformats do
661 t[name] = getstatistics(mpx)
662 end
663 return t
664 end
665end
666 |