m-escrito.lua /size: 217 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['m-escrito'] = {
2    version   = 1.001,
3    comment   = "companion to m-escrito.mkiv",
4    author    = "Taco Hoekwater (BitText) and Hans Hagen (PRAGMA-ADE)",
5    license   = "see below and context related readme files"
6}
7
8-- This file is derived from Taco's escrito interpreter. Because the project was
9-- more or less stopped, after some chatting we decided to preserve the result
10-- and make it useable in ConTeXt. Hans went over all code, fixed a couple of
11-- things, messed other things, made the code more efficient, wrapped all in
12-- some helpers. So, a diff between the original and this file is depressingly
13-- large. This means that you shouldn't bother Taco with the side effects (better
14-- or worse) that result from this.
15
16-- Fonts need some work and I will do that when needed. I might cook up something
17-- similar to what we do with MetaFun. First I need to run into a use case. After
18-- all, this whole exercise is just that: getting an idea of what processing PS
19-- code involves.
20
21-- Here is the usual copyright blabla:
22--
23-- Copyright 2010 Taco Hoekwater <taco@luatex.org>. All rights reserved.
24--
25-- Redistribution and use in source and binary forms, with or without modification,
26-- are permitted provided that the following conditions are met:
27--
28-- 1. Redistributions of source code must retain the above copyright notice, this
29--    list of conditions and the following disclaimer.
30--
31-- 2. Redistributions in binary form must reproduce the above copyright notice, this
32--    list of conditions and the following disclaimer in the documentation and/or
33--    other materials provided with the distribution.
34--
35-- THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND ANY EXPRESS OR
36-- IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
37-- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
38-- SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
39-- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
40-- OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
41-- INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42-- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
43-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
44-- DAMAGE.
45
46-- We use a couple of do..ends later on because this rather large file has too many
47-- locals otherwise. Possible optimizations are using insert/remove and getting rid
48-- of the VM calls (in direct mode they are no-ops anyway). We can also share some
49-- more code here and there.
50
51-- Notes:
52--
53-- -- all modules are checked / adapted to lmtx but how about this one ... i noticed
54--    that a file in the test suite failed
55--
56-- -- the idea was to use this for the m4all eps files but we swichted the format
57--    there; nevertheless i patched a littl but it's still not ok (cold winter work)
58--
59-- -- for instance some ppor mans fancy shading doesn't show up (not that efficient
60--    either so ...)
61--
62-- -- let's see what the new fast ps->pdf lib from artifact brings ... makes more
63--    sense in the perspective of ps 2 and 3 .. but there is some sentiment involved
64--
65-- -- room for implification (like no integer / real distinction needed)
66--
67-- -- so for now this is not part of the mkiv/lmtx code split (then also go Lua 5.4)
68
69local type, unpack, tonumber, tostring, next = type, unpack, tonumber, tostring, next
70
71local format     = string.format
72local gmatch     = string.gmatch
73local match      = string.match
74local sub        = string.sub
75local char       = string.char
76local byte       = string.byte
77
78local insert     = table.insert
79local remove     = table.remove
80local concat     = table.concat
81local reverse    = table.reverse
82
83local abs        = math.abs
84local ceil       = math.ceil
85local floor      = math.floor
86local sin        = math.sin
87local cos        = math.cos
88local rad        = math.rad
89local sqrt       = math.sqrt
90local atan2      = math.atan2
91local tan        = math.tan
92local deg        = math.deg
93local pow        = math.pow
94local log        = math.log
95local log10      = math.log10
96local random     = math.random
97local setranseed = math.randomseed
98
99local bitand     = bit32.band     -- when lmtx: Lua 5.4
100local bitor      = bit32.bor
101local bitxor     = bit32.bxor
102local bitrshift  = bit32.rshift
103local bitlshift  = bit32.lshift
104
105local lpegmatch  = lpeg.match
106local Ct, Cc, Cs, Cp, C, R, S, P, V = lpeg.Ct, lpeg.Cc, lpeg.Cs, lpeg.Cp, lpeg.C, lpeg.R, lpeg.S, lpeg.P, lpeg.V
107
108local formatters        = string.formatters
109local setmetatableindex = table.setmetatableindex
110
111-- Namespace
112
113-- HH: Here we assume just one session. If needed we can support more (just a matter
114-- of push/pop) but it makes the code more complex and less efficient too.
115
116escrito = { }
117
118----- escrito      = escrito
119local initializers = { }
120local devices      = { }
121local specials
122
123local DEBUG     = false -- these will become trackers if needed
124local INITDEBUG = false -- these will become trackers if needed
125local MAX_INT   = 0x7FFFFFFF -- we could have slightly larger ints because lua internally uses doubles
126
127initializers[#initializers+1] = function(reset)
128    if reset then
129        specials = nil
130    else
131        specials = { }
132    end
133end
134
135local devicename
136local device
137
138-- "boundingbox",
139-- "randomseed",
140
141-- Composite objects
142--
143-- Arrays, dicts and  strings are stored in VM. To do this, VM is an integer-indexed table. This appears
144-- a bit silly in lua because we are actually just emulating a C implementation detail (pointers) but it
145-- is documented behavior. There is also supposed to be a VM stack, but I will worry about that when it
146-- becomes time to implement save/restore. (TH)
147
148local VM -- todo: just a hash
149
150initializers[#initializers+1] = function()
151    VM = { }
152end
153
154local directvm = false -- true (but then we ned to patch more VM[..]
155
156local add_VM, get_VM
157
158if directvm then -- if ok then we remove the functions
159
160    add_VM = function(a)
161        return a
162    end
163    get_VM = function(i)
164        return i
165    end
166
167else
168
169    add_VM = function(a)
170        local n = #VM + 1
171        VM[n] = a
172        return n
173    end
174
175    get_VM = function(i)
176        return VM[i]
177    end
178
179end
180
181-- Execution stack
182
183local execstack
184local execstackptr
185local do_exec
186local next_object
187local stopped
188
189initializers[#initializers+1] = function()
190    execstack    = { }
191    execstackptr = 0
192    stopped      = false
193end
194
195local function pop_execstack()
196    if execstackptr > 0 then
197        local value  = execstack[execstackptr]
198        execstackptr = execstackptr - 1
199        return value
200    else
201        return nil -- stackunderflow
202    end
203end
204
205local function push_execstack(v)
206    execstackptr = execstackptr + 1
207    execstack[execstackptr] = v
208end
209
210-- Operand stack
211--
212-- Most operand and exec stack entries are four-item arrays:
213--
214-- [1] = "[integer|real|boolean|name|mark|null|save|font]"  (a postscript interpreter type)
215-- [2] = "[unlimited|read-only|execute-only|noaccess]"
216-- [3] = "[executable|literal]" (exec attribute)
217-- [4] = value (a VM index inthe case of names)
218--
219-- But there are some exceptions.
220--
221-- Dictionaries save the access attribute inside the value
222--
223-- [1] = "dict"
224-- [2] = irrelevant
225-- [3] = "[executable|literal]"
226-- [4] = value (a VM index)
227--
228-- Operators have a fifth item:
229--
230-- [1] = "operator"
231-- [2] = "[unlimited|read-only|execute-only|noaccess]"
232-- [3] = "[executable|literal]"
233-- [4] = value
234-- [5] = identifier (the operator name)
235--
236-- Strings and files have a fifth and a sixth item, the fifth of which is
237-- only relevant if the exec attribute is 'executable':
238--
239-- [1] = "[string|file]"
240-- [2] = "[unlimited|read-only|execute-only|noaccess]"
241-- [3] = "[executable|literal]"
242-- [4] = value  (a VM index) (for input files, this holds the whole file)
243-- [5] = exec-index
244-- [6] = length
245-- [7] = iomode (for files only)
246-- [8] = filehandle (for files only)
247--
248-- Arrays also have a seven items, the fifth is only relevant if
249-- the exec attribute is 'executable', and the seventh is used to differentiate
250-- between direct and indirect interpreter views of the object.
251--
252-- [1] = "array"
253-- [2] = "[unlimited|read-only|execute-only|noaccess]"
254-- [3] = "[executable|literal]"
255-- [4] = value (a VM index)
256-- [5] = exec-index
257-- [6] = length (a VM index)
258-- [7] = "[d|i]" (direct vs. indirect)
259--
260-- The exec stack also has an object with [1] == ".stopped", which is used
261-- for "stopped" execution contexts
262
263local opstack
264local opstackptr
265
266local b_true  = { 'boolean', 'unlimited', 'literal', true  }
267local b_false = { 'boolean', 'unlimited', 'literal', false }
268
269initializers[#initializers+1] = function()
270    opstack    = { }
271    opstackptr = 0
272end
273
274local function pop_opstack()
275    if opstackptr > 0 then
276        local value = opstack[opstackptr]
277        opstackptr  = opstackptr - 1
278        return value
279    else
280        return nil -- stackunderflow
281    end
282end
283
284local function push_opstack(v)
285    opstackptr = opstackptr + 1
286    opstack[opstackptr] = v
287end
288
289local function check_opstack(n)
290    return opstackptr >= n
291end
292
293local function get_opstack()
294    if opstackptr > 0 then
295        return opstack[opstackptr]
296    else
297        return nil -- stackunderflow
298    end
299end
300
301-- In case of error, the interpreter has to restore the opstack
302
303local function copy_opstack()
304    local t = { }
305    for n=1,opstackptr do
306        local sn = opstack[n]
307        t[n] = { unpack(sn) }
308    end
309    return t
310end
311
312local function set_opstack(new)
313   opstackptr = #new
314   opstack    = new
315end
316
317-- Dict stack
318
319local dictstack
320local dictstackptr
321
322initializers[#initializers+1] = function()
323    dictstack    = { }
324    dictstackptr = 0
325end
326
327-- this finds a name in the current dictionary stack
328
329local function lookup(name)
330    for n=dictstackptr,1,-1 do
331        local found = get_VM(dictstack[n])
332        if found then
333            local dict = found.dict
334            if dict then
335                local d = dict[name]
336                if d then
337                    return d, n
338                end
339            end
340        end
341    end
342    return nil
343end
344
345-- Graphics state stack
346
347-- device backends are easier if gsstate items use bare data instead of
348-- ps objects, much as possible
349
350-- todo: just use one color array
351
352local gsstate
353
354initializers[#initializers+1] = function(reset)
355    if reset then
356        gsstate = nil
357    else
358        gsstate = {
359            matrix      = { 1, 0, 0, 1, 0, 0 },
360            color       = {
361                gray = 0,
362                hsb  = { },
363                rgb  = { },
364                cmyk = { },
365                type = "gray"
366            },
367            position    = { }, -- actual x and y undefined
368            path        = { },
369            clip        = { },
370            font        = nil,
371            linewidth   = 1,
372            linecap     = 0,
373            linejoin    = 0,
374            screen      = nil, -- by default, we don't use a screen, which matches "1 0 {pop}"
375            transfer    = nil, -- by default, we don't have a transfer function, which matches "{}"
376            flatness    = 0,
377            miterlimit  = 10,
378            dashpattern = { },
379            dashoffset  = 0,
380        }
381    end
382end
383
384local function copy_gsstate()
385    local old      = gsstate
386    local position = old.position
387    local matrix   = old.matrix
388    local color    = old.color
389    local rgb      = color.rgb
390    local cmyk     = color.cmyk
391    local hsb      = color.hsb
392    return {
393        matrix      = { matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6] },
394        color       = {
395            type = color.type,
396            gray = color.gray,
397            hsb  = { hsb[1], hsb[2], hsb[3] },
398            rgb  = { rgb[1], rgb[2], rgb[3] },
399            cmyk = { cmyk[1], cmyk[2], cmyk[3], cmyk[4] },
400        },
401        position    = { position[1], position[2] },
402        path        = { unpack (old.path) },
403        clip        = { unpack (old.clip) },
404        font        = old.font,
405        linewidth   = old.linewidth,
406        linecap     = old.linecap,
407        linejoin    = old.linejoin,
408        screen      = old.screen,
409        transfer    = nil,
410        flatness    = old.flatness,
411        miterlimit  = old.miterlimit,
412        dashpattern = { },
413        dashoffset  = 0,
414    }
415end
416
417-- gsstack entries are of the form
418-- [1] "[save|gsave]"
419-- [2] {gsstate}
420
421local gsstack
422local gsstackptr
423
424initializers[#initializers+1] = function(reset)
425    if reset then
426        gsstack    = nil
427        gsstackptr = nil
428    else
429        gsstack    = { }
430        gsstackptr = 0
431    end
432end
433
434local function push_gsstack(v)
435    gsstackptr = gsstackptr + 1
436    gsstack[gsstackptr] = v
437end
438
439local function pop_gsstack()
440    if gsstackptr > 0 then
441        local v = gsstack[gsstackptr]
442        gsstackptr = gsstackptr - 1
443        return v
444   end
445end
446
447-- Currentpage
448
449local currentpage
450
451initializers[#initializers+1] = function(reset)
452    if reset then
453        currentpage = nil
454    else
455        currentpage = { }
456    end
457end
458
459-- Errordict
460
461-- The standard errordict entry. The rest of these dictionaries will be filled
462-- in the new() function.
463
464local errordict
465local dicterror
466
467-- find an error handler
468
469local function lookup_error(name)
470    local dict = get_VM(errordict).dict
471    return dict and dict[name]
472end
473
474-- error handling and reporting
475
476local report = logs.reporter("escrito")
477
478local function ps_error(a)
479    -- can have print hook
480    return false, a
481end
482
483-- Most entries in systemdict are operators, and the operators each have their own
484-- implementation function. These functions are grouped by category cf. the summary
485-- in the Adobe PostScript reference manual, the creation of the systemdict entries
486-- is alphabetical.
487--
488-- In the summary at the start of the operator sections, the first character means:
489--
490-- "-" => todo
491-- "+" => done
492-- "*" => partial
493-- "^" => see elsewhere
494
495local operators = { }
496
497-- Operand stack manipulation operators
498--
499-- +pop +exch +dup +copy +index +roll +clear +count +mark +cleartomark +counttomark
500
501function operators.pop()
502    local a = pop_opstack()
503    if not a then
504        return ps_error('stackunderflow')
505    end
506    return true
507end
508
509function operators.exch()
510    if opstackptr < 2 then
511        return ps_error('stackunderflow')
512    end
513    local prv = opstackptr - 1
514    opstack[opstackptr], opstack[prv] = opstack[prv], opstack[opstackptr]
515    return true
516end
517
518function operators.dup()
519    if opstackptr < 1 then
520        return ps_error('stackunderflow')
521    end
522    local nxt = opstackptr + 1
523    opstack[nxt] = opstack[opstackptr]
524    opstackptr = nxt
525    return true
526end
527
528function operators.copy()
529    local a = pop_opstack()
530    if not a then
531        return ps_error('stackunderflow')
532    end
533    local ta = a[1]
534    if ta == 'integer' then
535        local va = a[4]
536        if va < 0 then
537            return ps_error('typecheck')
538        end
539        local thestack = opstackptr
540        if va > thestack then
541            return ps_error('stackunderflow')
542        end
543        -- use for loop
544        local n = thestack - va + 1
545        while n <= thestack do
546            local b = opstack[n]
547            local tb = b[1]
548            if tb == 'array' or tb == 'string' or tb == 'dict' or tb == 'font' then
549                b = { tb, b[2], b[3], add_VM(get_VM(b[4])), b[5], b[6], b[7] }
550            end
551            push_opstack(b)
552            n = n + 1
553        end
554    elseif ta == 'dict' then
555        local b = a
556        local a = pop_opstack()
557        if not a then
558            return ps_error('stackunderflow')
559        end
560        if a[1] ~= 'dict' then
561            return ps_error('typecheck')
562        end
563        local thedict    = get_VM(b[4])
564        local tobecopied = get_VM(a[4])
565        if thedict.maxsize < tobecopied.size then
566            return ps_error('rangecheck')
567        end
568        if thedict.size ~= 0 then
569            return ps_error('typecheck')
570        end
571        local access = thedict.access
572        if access == 'read-only' or access == 'noaccess' then
573            return ps_error('invalidaccess')
574        end
575        local dict = { }
576        for k, v in next, tobecopied.dict do
577            dict[k] = v -- fixed, was thedict[a], must be thedict.dict
578        end
579        thedict.access = tobecopied.access
580        thedict.size   = tobecopied.size
581        thedict.dict   = dict
582        b = { b[1], b[2], b[3], add_VM(thedict) }
583        push_opstack(b)
584    elseif ta == 'array' then
585        local b = a
586        local a = pop_opstack()
587        if not a then
588            return ps_error('stackunderflow')
589        end
590        if a[1] ~= 'array' then
591            return ps_error('typecheck')
592        end
593        if b[6] < a[6] then
594            return ps_error('rangecheck')
595        end
596        local access = b[2]
597        if access == 'read-only' or access == 'noaccess' then
598            return ps_error('invalidaccess')
599        end
600        local array      = { }
601        local thearray   = get_VM(b[4])
602        local tobecopied = get_VM(a[4])
603        for k, v in next, tobecopied do
604            array[k] = v
605        end
606        b = { b[1], b[2], b[3], add_VM(array), a[5], a[6], a[7] } -- fixed, was thearray
607        push_opstack(b)
608   elseif ta == 'string' then
609        local b = a
610        local a = pop_opstack()
611        if not a then
612            return ps_error('stackunderflow')
613        end
614        if a[1] ~= 'string' then
615            return ps_error('typecheck')
616        end
617        if b[6] < a[6] then
618            return ps_error('rangecheck')
619        end
620        local access = b[2]
621        if access == 'read-only' or access == 'noaccess' then
622            return ps_error('invalidaccess')
623        end
624        local thestring = get_VM(b[4])
625        local repl      = get_VM(a[4])
626        VM[b[4]] = repl .. sub(thestring,#repl+1,-1)
627        b = { b[1], b[2], b[3], add_VM(repl), a[5], b[6] }
628        push_opstack(b)
629    else
630        return ps_error('typecheck')
631    end
632    return true
633end
634
635function operators.index()
636    local a = pop_opstack()
637    if not a then
638        return ps_error('stackunderflow')
639    end
640    local ta = a[1]
641    if not ta == 'integer' then
642        return ps_error('typecheck')
643    end
644    local n = a[4]
645    if n < 0 then
646        return ps_error('rangecheck')
647    end
648    if n >= opstackptr then
649        return ps_error('stackunderflow')
650    end
651    push_opstack(opstack[opstackptr-n])
652    return true
653end
654
655function operators.roll()
656    local b = pop_opstack()
657    local a = pop_opstack()
658    if not a then
659        return ps_error('stackunderflow')
660    end
661    if b[1] ~= 'integer' then
662        return ps_error('typecheck')
663    end
664    if a[1] ~= 'integer' then
665        return ps_error('typecheck')
666    end
667    local stackcount = a[4]
668    if stackcount < 0 then
669        return ps_error('rangecheck')
670    end
671    if stackcount > opstackptr then
672        return ps_error('stackunderflow')
673    end
674    local rollcount = b[4]
675    if rollcount == 0 then
676        return true
677    end
678    if rollcount > 0 then
679        -- can be simplified
680        while rollcount > 0 do
681            local oldtop = opstack[opstackptr]
682            local n = 0
683            while n < stackcount do
684                opstack[opstackptr-n] = opstack[opstackptr-n-1]
685                n = n + 1
686            end
687            opstack[opstackptr-(stackcount-1)] = oldtop
688            rollcount = rollcount - 1
689        end
690    else
691        -- can be simplified
692        while rollcount < 0 do
693            local oldbot = opstack[opstackptr-stackcount+1]
694            local n = stackcount - 1
695            while n > 0 do
696                opstack[opstackptr-n] = opstack[opstackptr-n+1]
697                n = n - 1
698            end
699            opstack[opstackptr] = oldbot
700            rollcount = rollcount + 1
701        end
702    end
703    return true
704end
705
706function operators.clear()
707    opstack    = { } -- or just keep it
708    opstackptr = 0
709    return true
710end
711
712function operators.count()
713    push_opstack { 'integer', 'unlimited', 'literal', opstackptr }
714    return true
715end
716
717function operators.mark()
718    push_opstack { 'mark', 'unlimited', 'literal', null }
719end
720
721operators.beginarray = operators.mark
722
723function operators.cleartomark()
724    while opstackptr > 0 do
725        local val = pop_opstack()
726        if not val then
727            return ps_error('unmatchedmark')
728        end
729        if val[1] == 'mark' then
730            return true
731        end
732    end
733    return ps_error('unmatchedmark')
734end
735
736function operators.counttomark()
737    local v = 0
738    for n=opstackptr,1,-1 do
739        if opstack[n][1] == 'mark' then
740            push_opstack { 'integer', 'unlimited', 'literal', v }
741            return true
742        end
743        v = v + 1
744    end
745    return ps_error('unmatchedmark')
746end
747
748-- Arithmetic and math operators
749--
750-- +add +div +idiv +mod +mul +sub +abs +neg +ceiling +floor +round +truncate +sqrt +atan +cos
751-- +sin +exp +ln +log +rand +srand +rrand
752
753function operators.add()
754    local b = pop_opstack()
755    local a = pop_opstack()
756    if not a then
757        return ps_error('stackunderflow')
758    end
759    local ta, tb = a[1], b[1]
760    if not (tb == 'real' or tb == 'integer') then
761        return ps_error('typecheck')
762    end
763    if not (ta == 'real' or ta == 'integer') then
764        return ps_error('typecheck')
765    end
766    local c = a[4] + b[4]
767    push_opstack {
768        (ta == 'real' or tb == 'real' or c > MAX_INT) and "real" or "integer",
769        'unlimited', 'literal', c
770    }
771    return true
772end
773
774function operators.sub()
775    local b = pop_opstack()
776    local a = pop_opstack()
777    if not a then
778        return ps_error('stackunderflow')
779    end
780    local ta, tb = a[1], b[1]
781    if not (tb == 'real' or tb == 'integer') then
782        return ps_error('typecheck')
783    end
784    if not (ta == 'real' or ta == 'integer') then
785        return ps_error('typecheck')
786    end
787    local c = a[4] - b[4]
788    push_opstack {
789        (ta == 'real' or tb == 'real' or c > MAX_INT) and "real" or "integer",
790        'unlimited', 'literal', c
791    }
792    return true
793end
794
795function operators.div()
796    local b = pop_opstack()
797    local a = pop_opstack()
798    if not a then
799        return ps_error('stackunderflow')
800    end
801    local ta, tb = a[1], b[1]
802    if not (tb == 'real' or tb == 'integer') then
803        return ps_error('typecheck')
804    end
805    if not (ta == 'real' or ta == 'integer') then
806        return ps_error('typecheck')
807    end
808    local va, vb = a[4], b[4]
809    if vb == 0 then
810        return ps_error('undefinedresult')
811    end
812    push_opstack { 'real', 'unlimited', 'literal', va / vb }
813    return true
814end
815
816function operators.idiv()
817    local b = pop_opstack()
818    local a = pop_opstack()
819    if not a then
820        return ps_error('stackunderflow')
821    end
822    local ta, tb = a[1], b[1]
823    if tb ~= 'integer' then
824        return ps_error('typecheck')
825    end
826    if ta ~= 'integer' then
827        return ps_error('typecheck')
828    end
829    local va, vb = a[4], b[4]
830    if vb == 0 then
831        return ps_error('undefinedresult')
832    end
833    push_opstack { 'integer', 'unlimited', 'literal', floor(va / vb) }
834    return true
835end
836
837function operators.mod()
838    local b = pop_opstack()
839    local a = pop_opstack()
840    if not a then
841        return ps_error('stackunderflow')
842    end
843    local ta, tb = a[1], b[1]
844    if tb ~= 'integer' then
845        return ps_error('typecheck')
846    end
847    if ta ~= 'integer' then
848        return ps_error('typecheck')
849    end
850    local va, vb = a[4], b[4]
851    if vb == 0 then
852        return ps_error('undefinedresult')
853    end
854    local neg = false
855    local v
856    if va < 0 then
857        v   = -va
858        neg = true
859    else
860        v = va
861    end
862    local c = v % abs(vb)
863    if neg then
864        c = -c
865    end
866    push_opstack { 'integer', 'unlimited', 'literal', c }
867    return true
868end
869
870function operators.mul()
871    local b = pop_opstack()
872    local a = pop_opstack()
873    if not a then
874        return ps_error('stackunderflow')
875    end
876    local ta, tb = a[1], b[1]
877    if not (tb == 'real' or tb == 'integer') then
878        return ps_error('typecheck')
879    end
880    if not (ta == 'real' or ta == 'integer') then
881        return ps_error('typecheck')
882    end
883    local c = a[4] * b[4]
884    push_opstack {
885        (ta == 'real' or tb == 'real' or abs(c) > MAX_INT) and 'real' or 'integer',
886        'unlimited', 'literal', c
887    }
888    return true
889end
890
891function operators.abs()
892    local a = pop_opstack()
893    if not a then
894        return ps_error('stackunderflow')
895    end
896    local ta = a[1]
897    if not (ta == 'real' or ta == 'integer') then
898        return ps_error('typecheck')
899    end
900    local v = a[4]
901    local c = abs(v)
902    push_opstack {
903        (ta == 'real' or v == -(MAX_INT+1)) and 'real' or 'integer', -- hm, v or c
904        'unlimited', 'literal', c
905    }
906    return true
907end
908
909function operators.neg()
910    local a = pop_opstack()
911    if not a then
912        return ps_error('stackunderflow')
913    end
914    local ta = a[1]
915    if not (ta == 'real' or ta == 'integer') then
916        return ps_error('typecheck')
917    end
918    local v = a[4]
919    push_opstack {
920        (ta == 'real' or v == -(MAX_INT+1)) and 'real' or 'integer',
921        'unlimited', 'literal', -v
922    }
923    return true
924end
925
926function operators.ceiling()
927    local a = pop_opstack()
928    if not a then
929        return ps_error('stackunderflow')
930    end
931    local ta = a[1]
932    if not (ta == 'real' or ta == 'integer') then
933        return ps_error('typecheck')
934    end
935    local c = ceil(a[4])
936    push_opstack { ta, 'unlimited', 'literal', c }
937    return true
938end
939
940function operators.floor()
941    local a = pop_opstack()
942    if not a then
943        return ps_error('stackunderflow')
944    end
945    local ta = a[1]
946    if not (ta == 'real' or ta == 'integer') then
947        return ps_error('typecheck')
948    end
949    local c = floor(a[4])
950    push_opstack { ta, 'unlimited', 'literal', c }
951    return true
952end
953
954function operators.round()
955    local a = pop_opstack()
956    if not a then
957        return ps_error('stackunderflow')
958    end
959    local ta = a[1]
960    if not (ta == 'real' or ta == 'integer') then
961        return ps_error('typecheck')
962    end
963    local c = floor(a[4]+0.5)
964    push_opstack { ta, 'unlimited', 'literal', c }
965    return true
966end
967
968function operators.truncate()
969    local a = pop_opstack()
970    if not a then
971        return ps_error('stackunderflow')
972    end
973    local ta = a[1]
974    if not (ta == 'real' or ta == 'integer') then
975        return ps_error('typecheck')
976    end
977    local v = a[4]
978    local c =v < 0 and -floor(-v) or floor(v)
979    push_opstack { ta, 'unlimited', 'literal', c }
980    return true
981end
982
983function operators.sqrt()
984    local a = pop_opstack()
985    if not a then
986        return ps_error('stackunderflow')
987    end
988    local ta = a[1]
989    if not (ta == 'real' or ta == 'integer') then
990        return ps_error('typecheck')
991    end
992    local v = a[4]
993    if v < 0 then
994        return ps_error('rangecheck')
995    end
996    local c = sqrt(v)
997    push_opstack { 'real', 'unlimited', 'literal', c }
998    return true
999end
1000
1001function operators.atan()
1002    local b = pop_opstack()
1003    local a = pop_opstack()
1004    if not a then
1005        return ps_error('stackunderflow')
1006    end
1007    local ta, tb = a[1], b[1]
1008    if not (tb == 'real' or tb == 'integer') then
1009        return ps_error('typecheck')
1010    end
1011    if not (ta == 'real' or ta == 'integer') then
1012        return ps_error('typecheck')
1013    end
1014    local va, vb = a[4], b[4]
1015    if va == 0 and vb == 0 then
1016        return ps_error('undefinedresult')
1017    end
1018    local c = deg(atan2(rad(va),rad(vb)))
1019    if c < 0 then
1020        c = c + 360
1021    end
1022    push_opstack { 'real', 'unlimited', 'literal', c }
1023    return true
1024end
1025
1026function operators.sin()
1027    local a = pop_opstack()
1028    if not a then
1029        return ps_error('stackunderflow')
1030    end
1031    local ta = a[1]
1032    if not (ta == 'real' or ta == 'integer') then
1033        return ps_error('typecheck')
1034    end
1035    local c = sin(rad(a[4]))
1036    -- this is because double calculation introduces a small error
1037    if abs(c) < 1.0e-16 then
1038        c = 0
1039    end
1040    push_opstack { 'real', 'unlimited', 'literal', c }
1041    return true
1042end
1043
1044function operators.cos()
1045    local a = pop_opstack()
1046    if not a then
1047        return ps_error('stackunderflow')
1048    end
1049    local ta = a[1]
1050    if not (ta == 'real' or ta == 'integer') then
1051        return ps_error('typecheck')
1052    end
1053    local c = cos(rad(a[4]))
1054    -- this is because double calculation introduces a small error
1055    if abs(c) < 1.0e-16 then
1056        c = 0
1057    end
1058    push_opstack { 'real', 'unlimited', 'literal', c }
1059    return true
1060end
1061
1062function operators.exp()
1063    local b = pop_opstack()
1064    local a = pop_opstack()
1065    if not a then
1066        return ps_error('stackunderflow')
1067    end
1068    local ta, tb = a[1], b[1]
1069    if not (ta == 'real' or ta == 'integer') then
1070        return ps_error('typecheck')
1071    end
1072    if not (tb == 'real' or tb == 'integer') then
1073        return ps_error('typecheck')
1074    end
1075    local va, vb = a[4], b[4]
1076    if va < 0 and floor(vb) ~= vb then
1077        return ps_error('undefinedresult')
1078    end
1079    local c = pow(va,vb)
1080    push_opstack { 'real', 'unlimited', 'literal', c }
1081    return true
1082end
1083
1084function operators.ln()
1085    local a = pop_opstack()
1086    if not a then
1087        return ps_error('stackunderflow')
1088    end
1089    local ta = a[1]
1090    if not (ta == 'real' or ta == 'integer') then
1091        return ps_error('typecheck')
1092    end
1093    local v = a[4]
1094    if v <= 0 then
1095        return ps_error('undefinedresult')
1096    end
1097    local c = log(v)
1098    push_opstack { 'real', 'unlimited', 'literal', c }
1099    return true
1100end
1101
1102function operators.log()
1103    local a = pop_opstack()
1104    if not a then
1105        return ps_error('stackunderflow')
1106    end
1107    local ta = a[1]
1108    if not (ta == 'real' or ta == 'integer') then
1109        return ps_error('typecheck')
1110    end
1111    local v = a[4]
1112    if v <= 0 then
1113        return ps_error('undefinedresult')
1114    end
1115    local c = log10(v)
1116    push_opstack { 'real', 'unlimited', 'literal', c }
1117    return true
1118end
1119
1120escrito.randomseed = os.time()
1121
1122-- this interval is one off, but that'll do
1123
1124function operators.rand()
1125    local c = random(MAX_INT) - 1
1126    push_opstack { 'integer', 'unlimited', 'literal', c }
1127    return true
1128end
1129
1130function operators.srand()
1131    local a = pop_opstack()
1132    if not a then
1133        return ps_error('stackunderflow')
1134    end
1135    local ta = a[1]
1136    if ta ~= 'integer' then
1137        return ps_error('typecheck')
1138    end
1139    escrito.randomseed = a[4]
1140    setranseed(escrito.randomseed)
1141    return true
1142end
1143
1144function operators.rrand()
1145    push_opstack { 'integer', 'unlimited', 'literal', escrito.randomseed }
1146    return true
1147end
1148
1149-- Array operators
1150--
1151-- +array ^[ +] +length +get +put +getinterval +putinterval +aload +astore ^copy +forall
1152
1153function operators.array()
1154    local a = pop_opstack()
1155    if not a then
1156        return ps_error('stackunderflow')
1157    end
1158    local t = a[1]
1159    local v = a[4]
1160    if t ~= 'integer' then
1161        return ps_error('typecheck')
1162    end
1163    if v < 0 then
1164        return ps_error('rangecheck')
1165    end
1166    local array = { }
1167    for i=1,v do
1168        array[n] = { 'null', 'unlimited', 'literal', true } -- todo: share this one
1169    end
1170    push_opstack { 'array', 'unlimited', 'literal', add_VM(array), 0, v, 'd'}
1171end
1172
1173function operators.endarray()
1174    local n = opstackptr
1175    while n > 0 do
1176        if opstack[n][1] == 'mark' then
1177            break
1178        end
1179        n = n - 1
1180    end
1181    if n == 0 then
1182        return ps_error('unmatchedmark')
1183    end
1184    local top = opstackptr
1185    local i = opstackptr - n
1186    local array = { }
1187    while i > 0 do
1188        array[i] = pop_opstack()
1189        i = i - 1
1190    end
1191    pop_opstack() -- pop the mark
1192    push_opstack { 'array', 'unlimited', 'literal', add_VM(array), #array, #array, 'd' }
1193end
1194
1195function operators.length()
1196    local a = pop_opstack()
1197    if not a then
1198        return ps_error('stackunderflow')
1199    end
1200    local access = a[2]
1201    if access == "noaccess" or access == "executeonly" then
1202        return ps_error('invalidaccess')
1203    end
1204    local ta = a[1]
1205    local va = a[4]
1206    if ta == "dict" or ta == "font" then
1207        va = get_VM(va).size
1208    elseif ta == "array" or ta == "string" then
1209        va = get_VM(va)
1210        va = #va
1211    else
1212        return ps_error('typecheck')
1213    end
1214    push_opstack { 'integer', 'unlimited', 'literal', va }
1215    return true
1216end
1217
1218function operators.get()
1219    local b = pop_opstack()
1220    local a = pop_opstack()
1221    if not a then
1222        return ps_error('stackunderflow')
1223    end
1224    local access = a[2]
1225    if access == "noaccess" or access == "execute-only" then
1226        return ps_error('invalidaccess')
1227    end
1228    local ta = a[1]
1229    local va = a[4]
1230    if ta == "dict" then
1231        local dict = get_VM(va)
1232        local key = b
1233        local tb = b[1]
1234        local vb = b[4]
1235        if tb == "string" or tb == "name" then
1236            key = get_VM(vb)
1237        end
1238        local ddk = dict.dict[key]
1239        if ddk then
1240            push_opstack(ddk)
1241        else
1242            return ps_error('undefined')
1243        end
1244    elseif ta == "array" then
1245        local tb = b[1]
1246        local vb = b[4]
1247        if tb ~= 'integer' then
1248            return ps_error('typecheck')
1249        end
1250        if vb < 0 or vb >= a[6] then
1251            return ps_error('rangecheck')
1252        end
1253        local array = get_VM(va)
1254        local index = vb + 1
1255        push_opstack(array[index])
1256   elseif ta == "string" then
1257        local tb = b[1]
1258        local vb = b[4]
1259        if tb ~= 'integer' then
1260            return ps_error('typecheck')
1261        end
1262        if vb < 0 or vb >= a[6] then
1263            return ps_error('rangecheck')
1264        end
1265        local thestring = get_VM(va)
1266        local index = vb + 1
1267        local c = sub(thestring,index,index)
1268        push_opstack { 'integer', 'unlimited', 'literal', byte(c) }
1269    else
1270        return ps_error('typecheck')
1271    end
1272    return true
1273end
1274
1275function operators.put()
1276    local c = pop_opstack()
1277    local b = pop_opstack()
1278    local a = pop_opstack()
1279    if not a then
1280        return ps_error('stackunderflow')
1281    end
1282    local ta = a[1]
1283    if ta == "dict" then
1284        local dict = get_VM(a[4])
1285        if dict.access ~= 'unlimited' then
1286            return ps_error('invalidaccess')
1287        end
1288        local key = b
1289        local bt = b[1]
1290        if bt == "string" or bt == "name" then
1291            key = get_VM(b[4])
1292        end
1293        local dd  = dict.dict
1294        local ds  = dict.size
1295        local ddk = dd[key]
1296        if not ddk and (ds == dict.maxsize) then
1297            return ps_error('dictfull')
1298        end
1299        if c[1] == 'array' then
1300            c[7] = 'i'
1301        end
1302        if not ddk then
1303            dict.size = ds + 1
1304        end
1305        dd[key] = c
1306    elseif ta == "array" then
1307        if a[2] ~= 'unlimited' then
1308            return ps_error('invalidaccess')
1309        end
1310        if b[1] ~= 'integer' then
1311            return ps_error('typecheck')
1312        end
1313        local va, vb = a[4], b[4]
1314        if vb < 0 or vb >= a[6] then
1315            return ps_error('rangecheck')
1316        end
1317        local vm = VM[va]
1318        local vi = bv + 1
1319        if vm[vi][1] == 'null' then
1320            a[5] = a[5] + 1
1321        end
1322        vm[vi] = c
1323    elseif ta == "string" then
1324        if a[2] ~= 'unlimited' then
1325            return ps_error('invalidaccess')
1326        end
1327        if b[1] ~= 'integer' then
1328            return ps_error('typecheck')
1329        end
1330        if c[1] ~= 'integer' then
1331            return ps_error('typecheck')
1332        end
1333        local va, vb, vc = a[4], b[4], c[4]
1334        if vb < 0 or vb >= a[6] then
1335            return ps_error('rangecheck')
1336        end
1337        if vc < 0 or vc > 255 then
1338            return ps_error('rangecheck')
1339        end
1340        local thestring = get_VM(va)
1341        VM[va] = sub(thestring,1,vb) .. char(vc) .. sub(thestring,vb+2)
1342    else
1343        return ps_error('typecheck')
1344    end
1345    return true
1346end
1347
1348function operators.getinterval()
1349    local c = pop_opstack()
1350    local b = pop_opstack()
1351    local a = pop_opstack()
1352    if not a then
1353        return ps_error('stackunderflow')
1354    end
1355    local ta, tb, tc = a[1], b[1], c[1]
1356    local aa, ab, ac = a[2], b[2], c[2]
1357    local va, vb, vc = a[4], b[4], c[4]
1358    if ta ~= "array" and ta ~= 'string' then
1359        return ps_error('typecheck')
1360    end
1361    if tb ~= 'integer' or tc ~= 'integer' then
1362        return ps_error('typecheck')
1363    end
1364    if aa == "execute-only" or aa == 'noaccess' then
1365        return ps_error('invalidaccess')
1366    end
1367    if vb < 0 or vc < 0 or vb + vc >= a[6] then
1368        return ps_error('rangecheck')
1369    end
1370    -- vb : start
1371    -- vc : number
1372    if ta == 'array' then
1373        local array    = get_VM(va)
1374        local subarray = { }
1375        local index    = 1
1376        while index <= vc do
1377            subarray[index] = array[index+vb]
1378            index = index + 1
1379        end
1380        push_opstack { 'array', aa, a[3], add_VM(subarray), vc, vc, 'd' }
1381    else
1382        local thestring = get_VM(va)
1383        local newstring = sub(thestring,vb+1,vb+vc)
1384        push_opstack { 'string', aa, a[3], add_VM(newstring), vc, vc }
1385    end
1386    return true
1387end
1388
1389function operators.putinterval()
1390    local c = pop_opstack()
1391    local b = pop_opstack()
1392    local a = pop_opstack()
1393    if not a then
1394        return ps_error('stackunderflow')
1395    end
1396    local ta, tb, tc = a[1], b[1], c[1]
1397    local aa, ab, ac = a[2], b[2], c[2]
1398    local va, vb, vc = a[4], b[4], c[4]
1399    if ta ~= "array" and ta ~= 'string' then
1400        return ps_error('typecheck')
1401    end
1402    if tc ~= "array" and tc ~= 'string' then
1403        return ps_error('typecheck')
1404    end
1405    if ta ~= tc then
1406        return ps_error('typecheck')
1407    end
1408    if aa ~= "unlimited" then
1409        return ps_error('invalidaccess')
1410    end
1411    if tb ~= 'integer' then
1412        return ps_error('typecheck')
1413    end
1414    if vb < 0 or vb + c[6] >= a[6] then
1415        return ps_error('rangecheck')
1416    end
1417    if ta == 'array' then
1418        local newarr = get_VM(vc)
1419        local oldarr = get_VM(va)
1420        local index = 1
1421        local lastindex = c[6]
1422        local step = a[5]
1423        while index <= lastindex do
1424            if oldarr[vb+index][1] == 'null' then
1425                a[5] = a[5] + 1 -- needs checking, a[5] not used
1426             -- step = step + 1
1427            end
1428            oldarr[vb+index] = newarr[index]
1429            index = index + 1
1430        end
1431    else
1432        local thestring = get_VM(va)
1433        VM[va] = sub(thestring,1,vb) .. get_VM(vc) .. sub(thestring,vb+c[6]+1)
1434    end
1435    return true
1436end
1437
1438function operators.aload()
1439    local a = pop_opstack()
1440    if not a then
1441        return ps_error('stackunderflow')
1442    end
1443    local ta, aa, va = a[1], a[2], a[4]
1444    if ta ~= "array" then
1445       return ps_error('typecheck')
1446    end
1447    if aa == "execute-only" or aa == 'noaccess' then
1448       return ps_error('invalidaccess')
1449    end
1450    local array = get_VM(va)
1451    for i=1,#array do
1452       push_opstack(array[i])
1453    end
1454    push_opstack(a)
1455    return true
1456end
1457
1458function operators.astore()
1459    local a = pop_opstack()
1460    if not a then
1461        return ps_error('stackunderflow')
1462    end
1463    local ta, aa, va = a[1], a[2], a[4]
1464    if ta ~= "array" then
1465        return ps_error('typecheck')
1466    end
1467    if aa == "execute-only" or aa == 'noaccess' then
1468        return ps_error('invalidaccess')
1469    end
1470    local array = get_VM(va)
1471    local count = a[6]
1472    for i=1,count do
1473        local v = pop_opstack()
1474        if not v then
1475            return ps_error('stackunderflow')
1476        end
1477        array[i] = v
1478    end
1479    a[5] = a[5] + count
1480    push_opstack(a)
1481    return true
1482end
1483
1484function operators.forall()
1485    local b = pop_opstack()
1486    local a = pop_opstack()
1487    if not a then
1488        return ps_error('stackunderflow')
1489    end
1490    local ta, aa, va = a[1], a[2], a[4]
1491    local tb, ab, vb = b[1], b[2], b[4]
1492    if not tb == "array" and b[3] == 'executable' then
1493        return ps_error('typecheck')
1494    end
1495    if tb == 'noaccess' then
1496        return ps_error('invalidaccess')
1497    end
1498    if not (ta == "array" or ta == 'dict' or ta == 'string' or ta == "font") then
1499        return ps_error('typecheck')
1500    end
1501    if aa == "execute-only" or aa == 'noaccess' then
1502        return ps_error('invalidaccess')
1503    end
1504    push_execstack { '.exit', 'unlimited', 'literal', false }
1505    local curstack = execstackptr
1506    if ta == 'array' then
1507        if a[6] == 0 then
1508            return true
1509        end
1510        b[7] = 'i'
1511        local thearray = get_VM(va)
1512        for i=1,#thearray do
1513            if stopped then
1514                stopped = false
1515                return false
1516            end
1517            push_opstack(thearray[i])
1518            b[5] = 1
1519            push_execstack(b)
1520            while curstack <= execstackptr do
1521                do_exec()
1522            end
1523        end
1524        local entry = execstack[execstackptr]
1525        if entry[1] == '.exit' and antry[4] == true then
1526            pop_execstack()
1527            return true
1528        end
1529    elseif ta == 'dict' or ta == 'font' then
1530        local thedict = get_VM(va)
1531        if thedict.size == 0 then
1532            return true
1533        end
1534        b[7] = 'i'
1535        local thedict = get_VM(va)
1536        for k, v in next, thedict.dict do
1537            if stopped then
1538                stopped = false
1539                return false
1540            end
1541            if type(k) == "string" then
1542                push_opstack { 'name', 'unlimited', 'literal', add_VM(k) }
1543            else
1544                push_opstack(k)
1545            end
1546            push_opstack(v)
1547            b[5] = 1
1548            push_execstack(b)
1549            while curstack < execstackptr do
1550                do_exec()
1551            end
1552            local entry = execstack[execstackptr]
1553            if entry[1] == '.exit' and antry[4] == true then
1554                pop_execstack()
1555                return true
1556            end
1557        end
1558    else -- string
1559        if a[6] == 0 then
1560            return true
1561        end
1562        b[7] = 'i'
1563        local thestring = get_VM(va)
1564        for v in gmatch(thestring,".") do -- we can use string.bytes
1565            if stopped then
1566                stopped = false
1567                return false
1568            end
1569            push_opstack { 'integer', 'unlimited', 'literal', byte(v) }
1570            b[5] = 1
1571            push_execstack(b)
1572            while curstack < execstackptr do
1573                do_exec()
1574            end
1575            local entry = execstack[execstackptr]
1576            if entry[1] == '.exit' and antry[4] == true then
1577                pop_execstack()
1578                return true;
1579            end
1580        end
1581    end
1582    return true
1583end
1584
1585-- Dictionary operators
1586--
1587-- +dict ^length +maxlength +begin +end +def +load +store ^get ^put +known +where ^copy
1588-- ^forall ^errordict ^systemdict ^userdict +currentdict +countdictstack +dictstack
1589
1590function operators.dict()
1591    local a = pop_opstack()
1592    if not a then
1593        return ps_error('stackunderflow')
1594    end
1595    if not a[1] == 'integer' then
1596        return ps_error('typecheck')
1597    end
1598    local s = a[4]
1599    if s < 0 then
1600        return ps_error('rangecheck')
1601    end
1602    if s == 0 then -- level 2 feature
1603        s = MAX_INT
1604    end
1605    push_opstack {
1606        'dict',
1607        'unlimited',
1608        'literal',
1609        add_VM {
1610            access  = 'unlimited',
1611            size    = 0,
1612            maxsize = s,
1613            dict    = { },
1614        }
1615    }
1616end
1617
1618function operators.maxlength()
1619    local a = pop_opstack()
1620    if not a then
1621        return ps_error('stackunderflow')
1622    end
1623    local ta, aa, va = a[1], a[2], a[4]
1624    if ta ~= 'dict' then
1625        return ps_error('typecheck')
1626    end
1627    if aa == 'execute-only' or aa == 'noaccess' then
1628        return ps_error('invalidaccess')
1629    end
1630    local thedict = get_VM(va)
1631    push_opstack { 'integer', 'unlimited', 'literal', thedict.maxsize }
1632end
1633
1634function operators.begin()
1635    local a = pop_opstack()
1636    if not a then
1637        return ps_error('stackunderflow')
1638    end
1639    if a[1] ~= 'dict' then
1640        return ps_error('typecheck')
1641    end
1642    dictstackptr = dictstackptr + 1
1643    dictstack[dictstackptr] = a[4]
1644end
1645
1646operators["end"] = function()
1647    if dictstackptr < 3 then
1648        return ps_error('dictstackunderflow')
1649    end
1650    dictstack[dictstackptr] = nil
1651    dictstackptr = dictstackptr - 1
1652end
1653
1654function operators.def()
1655    local b = pop_opstack()
1656    local a = pop_opstack()
1657    if not a then
1658        return ps_error('stackunderflow')
1659    end
1660    if not (a[1] == 'name' and a[3] == 'literal') then
1661        return ps_error('typecheck')
1662    end
1663    if b[1] == 'array' then
1664        b[7] = 'i'
1665    end
1666    local thedict = get_VM(dictstack[dictstackptr])
1667    if not thedict.dict[get_VM(a[4])] then
1668        if thedict.size == thedict.maxsize then
1669         -- return ps_error('dictfull') -- level 1 only
1670        end
1671        thedict.size = thedict.size + 1
1672    end
1673    thedict.dict[get_VM(a[4])] = b
1674    return true
1675end
1676
1677-- unclear: the book says this operator can return typecheck
1678
1679function operators.load()
1680    local a = pop_opstack()
1681    if not a then
1682        return ps_error('stackunderflow')
1683    end
1684    local aa = a[2]
1685    if aa == 'noaccess' or aa == 'execute-only' then
1686        return ps_error('invalidaccess')
1687    end
1688    local v = lookup(get_VM(a[4]))
1689    if not v then
1690        return ps_error('undefined')
1691    end
1692    push_opstack(v)
1693end
1694
1695function operators.store()
1696    local b = pop_opstack()
1697    local a = pop_opstack()
1698    if not a then
1699        return ps_error('stackunderflow')
1700    end
1701    if not (a[1] == 'name' and a[3] == 'literal') then
1702        return ps_error('typecheck')
1703    end
1704    if b[7] == 'array' then
1705        b[7] = 'i'
1706    end
1707    local val, dictloc = lookup(a[4])
1708    if val then
1709        local thedict = get_VM(dictstack[dictloc])
1710        if thedict.access == 'execute-only' or thedict.access == 'noaccess' then
1711            return ps_error('invalidaccess')
1712        end
1713        thedict.dict[a[4]] = b
1714    else
1715        local thedict = get_VM(dictstack[dictstackptr])
1716        local access  = thedict.access
1717        local size    = thedict.size
1718        if access == 'execute-only' or access == 'noaccess' then
1719            return ps_error('invalidaccess')
1720        end
1721        if size == thedict.maxsize then
1722            return ps_error('dictfull')
1723        end
1724        thedict.size = size + 1
1725        thedict.dict[a[4]] = b
1726    end
1727    return true
1728end
1729
1730function operators.known()
1731    local b = pop_opstack()
1732    local a = pop_opstack()
1733    if not a then
1734        return ps_error('stackunderflow')
1735    end
1736    local ta, aa, va = a[1], a[2], a[4]
1737    local tb, vb = b[1], b[4]
1738    if ta ~= 'dict' then
1739        return ps_error('typecheck')
1740    end
1741    if not (tb == 'name' or tb == 'operator') then
1742        return ps_error('typecheck')
1743    end
1744    if aa == 'noaccess' or aa == 'execute-only' then
1745        return ps_error('invalidaccess')
1746    end
1747    local thedict = get_VM(va)
1748    push_opstack {'boolean', 'unlimited', 'literal', thedict.dict[vb] and true or false }
1749    return true
1750end
1751
1752function operators.where()
1753    local a = pop_opstack()
1754    if not a then
1755        return ps_error('stackunderflow')
1756    end
1757    if not (a[1] == 'name' and a[3] == 'literal') then
1758        return ps_error('typecheck')
1759    end
1760    local val, dictloc = lookup(get_VM(a[4]))
1761    local thedict = dictloc and get_VM(dictstack[dictloc]) -- fixed
1762    if val then
1763        if thedict.access == 'execute-only' or thedict.access == 'noaccess' then
1764            return ps_error('invalidaccess')
1765        end
1766        push_opstack {'dict', 'unlimited', 'literal', dictstack[dictloc]}
1767        push_opstack {'boolean', 'unlimited', 'literal', true}
1768    else
1769        push_opstack {'boolean', 'unlimited', 'literal', false}
1770    end
1771    return true
1772end
1773
1774function operators.currentdict()
1775    push_opstack { 'dict', 'unlimited', 'literal', dictstack[dictstackptr] }
1776    return true
1777end
1778
1779function operators.countdictstack()
1780    push_opstack { 'integer', 'unlimited', 'literal', dictstackptr }
1781    return true
1782end
1783
1784function operators.dictstack()
1785    local a = pop_opstack()
1786    if not a then
1787        return ps_error('stackunderflow')
1788    end
1789    if not a[1] == 'array' then
1790        return ps_error('typecheck')
1791    end
1792    if not a[2] == 'unlimited' then
1793        return ps_error('invalidaccess')
1794    end
1795    if a[6] < dictstackptr then
1796        return ps_error('rangecheck')
1797    end
1798    local thearray     = get_VM(a[4])
1799    local subarray     = { }
1800    for i=1,dictstackptr do
1801        thearray[n] = { 'dict', 'unlimited', 'literal', dictstack[i] }
1802        subarray[n] = thearray[i]
1803    end
1804    a[5] = a[5] + dictstackptr
1805    push_opstack { 'array', 'unlimited', 'literal', add_VM(subarray), dictstackptr, dictstackptr, '' }
1806    return true
1807end
1808
1809-- String operators
1810--
1811-- +string ^length ^get ^put ^getinterval ^putinterval ^copy ^forall +anchorsearch +search
1812-- +token
1813
1814function operators.string()
1815    local a = pop_opstack()
1816    if not a then
1817        return ps_error('stackunderflow')
1818    end
1819    local ta, va = a[1], a[4]
1820    if ta ~= 'integer' then
1821        return ps_error('typecheck')
1822    end
1823    if va < 0 then
1824        return ps_error('rangecheck')
1825    end
1826    push_opstack { 'string', 'unlimited', 'literal', add_VM(''), 1, va }
1827end
1828
1829function operators.anchorsearch()
1830    local b = pop_opstack()
1831    local a = pop_opstack()
1832    if not a then
1833        return ps_error('stackunderflow')
1834    end
1835    local ta, aa, va = a[1], a[2], a[4]
1836    local tb, ab, vb = b[1], b[2], b[4]
1837    if not ta ~= 'string' then
1838        return ps_error('typecheck')
1839    end
1840    if tb ~= 'string' then
1841        return ps_error('typecheck')
1842    end
1843    if aa == 'noaccess' or aa == 'execute-only' then
1844        return ps_error('invalidaccess')
1845    end
1846    if ab == 'noaccess' or ab == 'execute-only' then
1847        return ps_error('invalidaccess')
1848    end
1849    local thestring = get_VM(va)
1850    local thesearch = get_VM(vb)
1851    local prefix    = sub(thestring,1,#thesearch)
1852    if prefix == thesearch then
1853        if aa == 'read-only' then
1854            return ps_error('invalidaccess')
1855        end
1856        local post = sub(thestring,#thesearch+1)
1857        push_opstack { 'string',  'unlimited', 'literal', add_VM(post), 1, #post }
1858        push_opstack { 'string',  'unlimited', 'literal', add_VM(prefix), 1, #prefix }
1859        push_opstack (b_true)
1860    else
1861        push_opstack(a)
1862        push_opstack (b_false)
1863    end
1864    return true
1865end
1866
1867function operators.search()
1868    local b = pop_opstack()
1869    local a = pop_opstack()
1870    if not a then
1871        return ps_error('stackunderflow')
1872    end
1873    local ta, aa, va = a[1], a[2], a[4]
1874    local tb, ab, vb = b[1], b[2], b[4]
1875    if not ta ~= 'string' then
1876        return ps_error('typecheck')
1877    end
1878    if tb ~= 'string' then
1879        return ps_error('typecheck')
1880    end
1881    if aa == 'noaccess' or aa == 'execute-only' then
1882        return ps_error('invalidaccess')
1883    end
1884    if ab == 'noaccess' or ab == 'execute-only' then
1885        return ps_error('invalidaccess')
1886    end
1887    local thestring = get_VM(a[4])
1888    local thesearch = get_VM(b[4])
1889    -- hm, can't this be done easier?
1890    local n = 1
1891    local match
1892    while n + #thesearch-1 <= #thestring do
1893        match = sub(thestring,n,n+#thesearch-1)
1894        if match == thesearch then
1895            break
1896        end
1897        n = n + 1
1898    end
1899    if match == thesearch then
1900        if aa == 'read-only' then
1901            return ps_error('invalidaccess')
1902        end
1903        local prefix = sub(thestring,1,n-1)
1904        local post   = sub(thestring,#thesearch+n)
1905        push_opstack { 'string',  'unlimited', 'literal', add_VM(post), 1, #post }
1906        push_opstack { 'string',  'unlimited', 'literal', add_VM(thesearch), 1, #thesearch }
1907        push_opstack { 'string',  'unlimited', 'literal', add_VM(prefix), 1, #prefix }
1908        push_opstack (b_true)
1909    else
1910        push_opstack(a)
1911        push_opstack(b_false)
1912    end
1913    return true
1914end
1915
1916function operators.token()
1917    local a = pop_opstack()
1918    if not a then
1919        return ps_error('stackunderflow')
1920    end
1921    local ta, aa, va = a[1], a[2], a[4]
1922    if not (ta == 'string' or ta == 'file') then
1923        return ps_error('typecheck')
1924    end
1925    if aa ~= 'unlimited' then
1926        return ps_error('invalidaccess')
1927    end
1928    -- some fiddling with the tokenization process is needed
1929    if ta == 'string' then
1930        local top = execstackptr
1931        push_execstack { '.token', 'unlimited', 'literal', false }
1932        push_execstack {  a[1], a[2], 'executable', va, 1, a[6] }
1933        local v, err = next_object()
1934        if not v then
1935            pop_execstack()
1936            pop_execstack()
1937            push_opstack(b_false)
1938        else
1939            local q = pop_execstack()
1940            if execstack[execstackptr][1] == '.token' then
1941                pop_execstack()
1942            end
1943            local tq, vq = q[1], q[4]
1944            if tq == 'string' and vq ~= va then
1945                push_execstack(q)
1946            end
1947            local thestring, substring
1948            if vq ~= va  then
1949                thestring = ""
1950                substring = ""
1951            else
1952                thestring = get_VM(vq)
1953                substring = sub(thestring,q[5] or 0)
1954            end
1955            push_opstack { ta, aa, a[3], add_VM(substring), 1, #substring}
1956            push_opstack(v)
1957            push_opstack(b_true)
1958        end
1959    else -- file
1960        if a[7] ~= 'r' then
1961            return ps_error('invalidaccess')
1962        end
1963        push_execstack { '.token', 'unlimited', 'literal', false }
1964        push_execstack { 'file',   'unlimited', 'executable', va, a[5], a[6], a[7], a[8] }
1965        local v, err = next_object()
1966        if not v then
1967            pop_execstack()
1968            pop_execstack()
1969            push_opstack(b_false)
1970        else
1971            local q = pop_execstack() -- the file
1972            a[5] = q[5]
1973            if execstack[execstackptr][1] == '.token' then
1974                pop_execstack()
1975            end
1976            push_opstack(v)
1977            push_opstack(b_true)
1978        end
1979    end
1980    return true
1981end
1982
1983-- Relational, boolean and bitwise operators
1984--
1985-- +eq +ne +ge +gt +le +lt +and +not +or +xor ^true ^false +bitshift
1986
1987local function both()
1988    local b = pop_opstack()
1989    local a = pop_opstack()
1990    if not a then
1991        return ps_error('stackunderflow')
1992    end
1993    local ta, aa = a[1], a[2]
1994    local tb, ab = b[1], b[2]
1995    if aa == 'noaccess' or aa == 'execute-only' then
1996        return ps_error('invalidaccess')
1997    end
1998    if ab == 'noaccess' or ab == 'execute-only' then
1999        return ps_error('invalidaccess')
2000    end
2001    if (ta == 'dict' and tb == 'dict') or (ta == 'array' and tb =='array') then
2002        return true, a[4], b[4]
2003    elseif ((ta == 'string' or ta == 'name') and (tb == 'string' or tb == 'name' )) then
2004        local astr = get_VM(a[4])
2005        local bstr = get_VM(b[4])
2006        return true, astr, bstr
2007    elseif ((ta == 'integer' or ta == 'real') and (tb == 'integer' or tb == 'real')) or (ta == tb) then
2008        return true, a[4], b[4]
2009    else
2010        return ps_error('typecheck')
2011    end
2012    return true
2013end
2014
2015function operators.eq()
2016    local ok, a, b = both()
2017    if ok then
2018        push_opstack(a == b and b_true or b_false)
2019        return true
2020    else
2021        return a
2022    end
2023end
2024
2025function operators.ne()
2026    local ok, a, b = both()
2027    if ok then
2028        push_opstack(a ~= b and b_true or b_false)
2029        return true
2030    else
2031        return a
2032    end
2033end
2034
2035local function both()
2036    local b = pop_opstack()
2037    local a = pop_opstack()
2038    if not a then
2039        return ps_error('stackunderflow')
2040    end
2041    local aa, ab = a[2], b[2]
2042    if aa == 'noaccess' or aa == 'execute-only' then
2043        return ps_error('invalidaccess')
2044    end
2045    if ab == 'noaccess' or ab == 'execute-only' then
2046        return ps_error('invalidaccess')
2047    end
2048    local ta, tb = a[1], b[1]
2049    local va, vb = a[4], b[4]
2050    if (ta == 'real' or ta == 'integer') and (tb == 'real' or tb == 'integer') then
2051        return true, va, vb
2052    elseif ta == 'string' and tb == 'string' then
2053        local va = get_VM(va)
2054        local vb = get_VM(vb)
2055        return true, va, vb
2056    else
2057        return ps_error('typecheck')
2058    end
2059end
2060
2061function operators.ge()
2062    local ok, a, b = both()
2063    if ok then
2064        push_opstack(a >= b and b_true or b_false)
2065        return true
2066    else
2067        return a
2068    end
2069end
2070
2071function operators.gt()
2072    local ok, a, b = both()
2073    if ok then
2074        push_opstack(a > b and b_true or b_false)
2075        return true
2076    else
2077        return a
2078    end
2079end
2080
2081function operators.le()
2082    local ok, a, b = both()
2083    if ok then
2084        push_opstack(a <= b and b_true or b_false)
2085        return true
2086    else
2087        return a
2088    end
2089end
2090
2091function operators.lt()
2092    local ok, a, b = both()
2093    if ok then
2094        push_opstack(a < b and b_true or b_false)
2095        return true
2096    else
2097        return a
2098    end
2099end
2100
2101local function both()
2102    local b = pop_opstack()
2103    local a = pop_opstack()
2104    if not a then
2105        return ps_error('stackunderflow')
2106    end
2107    local aa, ab = a[2], b[2]
2108    if aa == 'noaccess' or aa == 'execute-only' then
2109        return ps_error('invalidaccess')
2110    end
2111    if ab == 'noaccess' or ab == 'execute-only' then
2112        return ps_error('invalidaccess')
2113    end
2114    local ta, tb = a[1], b[1]
2115    local va, vb = a[4], b[4]
2116    if ta == 'boolean' and tb == 'boolean' then
2117        return ta, va, vb
2118    elseif ta == 'integer' and tb == 'integer' then
2119        return ta, va, vb
2120    else
2121        return ps_error('typecheck')
2122    end
2123end
2124
2125operators["and"]= function()
2126    local ok, a, b = both()
2127    if ok == 'boolean' then
2128        push_opstack((a[1] and b[1]) and b_true or b_false)
2129        return true
2130    elseif ok == 'integer' then
2131        push_opstack { 'integer', 'unlimited', 'literal', bitand(a[1],b[1]) }
2132        return true
2133    else
2134        return a
2135    end
2136end
2137
2138operators["or"] = function()
2139    local ok, a, b = both()
2140    if ok == 'boolean' then
2141        push_opstack((a[1] or b[1]) and b_true or b_false)
2142        return true
2143    elseif ok == 'integer' then
2144        push_opstack {'integer', 'unlimited', 'literal', bitor(a[1],b[1]) }
2145        return true
2146    else
2147        return a
2148    end
2149end
2150
2151function operators.xor()
2152    local ok, a, b = both()
2153    if ok == 'boolean' then
2154        push_opstack ((a[1] ~= b[1]) and b_true or b_false) -- hm, unequal ?
2155        return true
2156    elseif ok == 'integer' then
2157        push_opstack {'integer', 'unlimited', 'literal', bitxor(a[1],b[1]) }
2158        return true
2159    else
2160        return a
2161    end
2162end
2163
2164operators["not"] = function()
2165    local a = pop_opstack()
2166    if not a then
2167        return ps_error('stackunderflow')
2168    end
2169    local aa = a[2]
2170    local ta = a[1]
2171    if aa == 'noaccess' or aa == 'execute-only' then
2172        return ps_error('invalidaccess')
2173    end
2174    if ta == 'boolean' then
2175        push_opstack ((not a[4]) and b_true or b_false)
2176    elseif ta == 'integer' then
2177        push_opstack { 'integer', 'unlimited', 'literal', -a[4] - 1 }
2178    else
2179        return ps_error('typecheck')
2180    end
2181    return true
2182end
2183
2184function operators.bitshift()
2185    local b = pop_opstack()
2186    local a = pop_opstack()
2187    if not a then
2188        return ps_error('stackunderflow')
2189    end
2190    local aa, ab = a[2], b[2]
2191    local ta, tb = a[1], b[1]
2192    local va, vb = a[4], b[4]
2193    if aa == 'noaccess' or aa == 'execute-only' then
2194        return ps_error('invalidaccess')
2195    end
2196    if ab == 'noaccess' or ab == 'execute-only' then
2197        return ps_error('invalidaccess')
2198    end
2199    if not (ta == 'integer' and tb == 'integer') then
2200        return ps_error('typecheck')
2201    end
2202    push_opstack { 'integer', 'unlimited', 'literal', bitrshift(va,vb < 0 and -vb or vb) }
2203    return true
2204end
2205
2206-- Control operators
2207--
2208-- +exec +if +ifelse +for +repeat +loop +exit +stop +stopped +countexecstack +execstack
2209-- +quit +start
2210
2211function operators.exec()
2212    local a = pop_opstack()
2213    if not a then
2214        return ps_error('stackunderflow')
2215    end
2216    if a[1] == 'array' then
2217        a[7] = 'i'
2218        a[5] = 1
2219    end
2220    push_execstack(a)
2221    return true
2222end
2223
2224operators["if"] = function()
2225    local b = pop_opstack()
2226    local a = pop_opstack()
2227    if not a then
2228        return ps_error('stackunderflow')
2229    end
2230    if a[1] ~= 'boolean' then
2231        return ps_error('typecheck')
2232    end
2233    if b[1] ~= 'array' then
2234        return ps_error('typecheck')
2235    end
2236    if a[4] == true then
2237        b[7] = 'i'
2238        b[5] = 1
2239        push_execstack(b)
2240    end
2241    return true
2242end
2243
2244function operators.ifelse()
2245    local c = pop_opstack()
2246    local b = pop_opstack()
2247    local a = pop_opstack()
2248    if not a then
2249        return ps_error('stackunderflow')
2250    end
2251    if a[1] ~= 'boolean' then
2252        return ps_error('typecheck')
2253    end
2254    if b[1] ~= 'array' then
2255        return ps_error('typecheck')
2256    end
2257    if c[1] ~= 'array' then
2258        return ps_error('typecheck')
2259    end
2260    if a[4] == true then
2261        b[5] = 1
2262        b[7] = 'i'
2263        push_execstack(b)
2264    else
2265        c[5] = 1
2266        c[7] = 'i'
2267        push_execstack(c)
2268    end
2269    return true
2270end
2271
2272operators["for"] = function()
2273    local d = pop_opstack()
2274    local c = pop_opstack()
2275    local b = pop_opstack()
2276    local a = pop_opstack()
2277    local ta, tb, tc, td = a[1], b[1], c[1], d[1]
2278    if not a then
2279        return ps_error('stackunderflow')
2280    end
2281    if not (ta == 'integer' or ta == 'real') then
2282        return ps_error('typecheck')
2283    end
2284    if not (tb == 'integer' or tb == 'real') then
2285        return ps_error('typecheck')
2286    end
2287    if not (tc == 'integer' or tc == 'real') then
2288        return ps_error('typecheck')
2289    end
2290    if not (td == 'array' and d[3] == 'executable') then
2291        return ps_error('typecheck')
2292    end
2293    local initial   = a[4]
2294    local increment = b[4]
2295    local limit     = c[4]
2296    if initial == limit then
2297        return true
2298    end
2299    push_execstack { '.exit', 'unlimited', 'literal', false }
2300    local curstack  = execstackptr
2301    local tokentype = (a[1] == 'real' or b[1] == 'real' or c[1] == 'real') and 'real' or 'integer'
2302    d[7] = 'i'
2303    local first, last
2304    if increment >= 0 then
2305        first, last = initial, limit
2306    else
2307        first, last = limit, limit
2308    end
2309    for control=first,last,increment do
2310        if stopped then
2311            stopped = false
2312            return false
2313        end
2314        push_opstack { tokentype, 'unlimited', 'literal', control }
2315        d[5] = 1
2316        push_execstack(d)
2317        while curstack < execstackptr do
2318            do_exec()
2319        end
2320        local entry = execstack[execstackptr]
2321        if entry[1] == '.exit' and entry[4] == true then
2322            pop_execstack()
2323            return true;
2324        end
2325    end
2326    return true
2327end
2328
2329operators["repeat"] = function()
2330    local b = pop_opstack()
2331    local a = pop_opstack()
2332    if not a then
2333        return ps_error('stackunderflow')
2334    end
2335    if a[1] ~= 'integer' then
2336        return ps_error('typecheck')
2337    end
2338    if a[4] < 0 then
2339        return ps_error('rangecheck')
2340    end
2341    if not (b[1] == 'array' and b[3] == 'executable') then
2342        return ps_error('typecheck')
2343    end
2344    local limit = a[4]
2345    if limit == 0 then
2346        return true
2347    end
2348    push_execstack { '.exit', 'unlimited', 'literal', false }
2349    local curstack = execstackptr
2350    b[7] = 'i'
2351    local control = 0
2352    while control < limit do
2353        if stopped then
2354            stopped = false
2355            return false
2356        end
2357        b[5] = 1
2358        push_execstack(b)
2359        while curstack < execstackptr do
2360            do_exec()
2361        end
2362        local entry = execstack[execstackptr]
2363        if entry[1] == '.exit' and entry[4] == true then
2364            pop_execstack()
2365            return true;
2366        end
2367        control = control + 1
2368    end
2369    return true
2370end
2371
2372function operators.loop()
2373    local a = pop_opstack()
2374    if not a then
2375        return ps_error('stackunderflow')
2376    end
2377    if not (a[1] == 'array'  and a[3] == 'executable') then
2378        return ps_error('typecheck')
2379    end
2380    push_execstack { '.exit', 'unlimited', 'literal', false }
2381    local curstack = execstackptr
2382    a[7] = 'i'
2383    while true do
2384        if stopped then
2385            stopped = false
2386            return false
2387        end
2388        a[5] = 1
2389        push_execstack(a)
2390        while curstack < execstackptr do
2391            do_exec()
2392        end
2393        if execstackptr > 0 then
2394            local entry = execstack[execstackptr]
2395            if entry[1] == '.exit' and entry[4] == true then
2396                pop_execstack()
2397                return true
2398            end
2399        end
2400    end
2401    return true
2402end
2403
2404function operators.exit()
2405    local v = pop_execstack()
2406    while v do
2407        local tv = val[1]
2408        if tv == '.exit' then
2409            push_execstack { '.exit', 'unlimited', 'literal', true }
2410            return true
2411        elseif tv == '.stopped' or tv == '.run' then
2412            push_execstack(v)
2413            return ps_error('invalidexit')
2414        end
2415        v = pop_execstack()
2416    end
2417    report("exit without context, quitting")
2418    push_execstack { 'operator', 'unlimited', 'executable', operators.quit, "quit" }
2419    return true
2420end
2421
2422function operators.stop()
2423    local v = pop_execstack()
2424    while v do
2425        if val[1] == '.stopped' then
2426            stopped = true
2427            push_opstack { 'boolean', 'unlimited', 'executable', true }
2428            return true
2429        end
2430        v = pop_execstack()
2431    end
2432    report("stop without context, quitting")
2433    push_execstack { 'operator', 'unlimited', 'executable', operators.quit, "quit" }
2434    return true
2435end
2436
2437function operators.stopped()
2438    local a = pop_opstack()
2439    if not a then
2440        return ps_error('stackunderflow')
2441    end
2442    -- push a special token on the exec stack (handled by next_object):
2443    push_execstack { '.stopped', 'unlimited', 'literal', false }
2444    a[3] = 'executable'
2445    if a[1] == 'array' then
2446        a[7] = 'i'
2447        a[5] = 1
2448    end
2449    push_execstack(a)
2450    return true
2451end
2452
2453function operators.countexecstack()
2454    push_opstack { 'integer', 'unlimited', 'literal', execstackptr }
2455    return true
2456end
2457
2458function operators.execstack()
2459    local a = pop_opstack()
2460    if not a then
2461        return ps_error('stackunderflow')
2462    end
2463    if not a[1] == 'array' then
2464        return ps_error('typecheck')
2465    end
2466    if not a[2] == 'unlimited' then
2467        return ps_error('invalidaccess')
2468    end
2469    if a[6] < execstackptr then
2470        return ps_error('rangecheck')
2471    end
2472    local thearray     = get_VM(a[4])
2473    local subarray     = { }
2474    for n=1,execstackptr do
2475     -- thearray[n] = execstack[n]
2476     -- subarray[n] = thearray[n]
2477        local v = execstack[n]
2478        thearray[n] = v
2479        subarray[n] = v
2480        a[5] = a[5] + 1
2481    end
2482    push_opstack { 'array', 'unlimited', 'literal', add_VM(subarray), execstackptr, execstackptr, "" }
2483    return true
2484end
2485
2486-- clearing the execstack does the trick,
2487-- todo: leave open files to be handled by the lua interpreter, for now
2488
2489function operators.quit()
2490    while execstackptr >= 0 do -- todo: for loop / slot 0?
2491        execstack[execstackptr] = nil
2492        execstackptr = execstackptr - 1
2493    end
2494    return true
2495end
2496
2497-- does nothing, for now
2498
2499function operators.start()
2500    return true
2501end
2502
2503-- Type, attribute and conversion operators
2504--
2505-- +type +cvlit +cvx +xcheck +executeonly +noaccess +readonly +rcheck +wcheck +cvi
2506-- +cvn +cvr +cvrs +cvs
2507
2508function operators.type()
2509    local a = pop_opstack()
2510    if not a then
2511        return ps_error('stackunderflow')
2512    end
2513    push_opstack { "name", "unlimited", "executable", add_VM(a[1] .. "type") }
2514    return true
2515end
2516
2517function operators.cvlit() -- no need to push/pop
2518    local a = get_opstack()
2519    if not a then
2520        return ps_error('stackunderflow')
2521    end
2522    a[3] = 'literal'
2523    return true
2524end
2525
2526function operators.cvx()
2527    local a = get_opstack()
2528    if not a then
2529        return ps_error('stackunderflow')
2530    end
2531    a[3] = 'executable'
2532    return true
2533end
2534
2535function operators.xcheck()
2536    local a = pop_opstack()
2537    if not a then
2538        return ps_error('stackunderflow')
2539    end
2540    push_opstack((a[3] == 'executable') and b_true or b_false)
2541    return true
2542end
2543
2544function operators.executeonly()
2545    local a = pop_opstack() -- get no push
2546    if not a then
2547        return ps_error('stackunderflow')
2548    end
2549    local ta = a[1]
2550    if ta == 'string' or ta == 'file' or ta == 'array' then
2551        if a[2] == 'noaccess' then
2552            return ps_error('invalidaccess')
2553        end
2554        a[2] = 'execute-only'
2555    else
2556        return ps_error('typecheck')
2557    end
2558    push_opstack(a)
2559    return true
2560end
2561
2562function operators.noaccess()
2563    local a = pop_opstack()
2564    if not a then
2565        return ps_error('stackunderflow')
2566    end
2567    local ta = a[1]
2568    if ta == 'string' or ta == 'file' or ta == 'array' then
2569        if a[2] == 'noaccess' then
2570            return ps_error('invalidaccess')
2571        end
2572        a[2] = 'noaccess'
2573    elseif ta == "dict" then
2574        local thedict = get_VM(a[4])
2575        if thedict.access == 'noaccess' then
2576            return ps_error('invalidaccess')
2577        end
2578        thedict.access = 'noaccess'
2579    else
2580        return ps_error('typecheck')
2581    end
2582    push_opstack(a)
2583    return true
2584end
2585
2586function operators.readonly()
2587    local a = pop_opstack()
2588    if not a then
2589        return ps_error('stackunderflow')
2590    end
2591    local ta = a[1]
2592    if ta == 'string' or ta == 'file' or ta == 'array' then
2593        local aa = a[2]
2594        if aa == 'noaccess' or aa == 'execute-only' then
2595            return ps_error('invalidaccess')
2596        end
2597        a[2] = 'read-only'
2598    elseif ta == 'dict' then
2599        local thedict = get_VM(a[4])
2600        local access  = thedict.access
2601        if access == 'noaccess' or access == 'execute-only' then
2602            return ps_error('invalidaccess')
2603        end
2604        thedict.access = 'read-only'
2605    else
2606        return ps_error('typecheck')
2607    end
2608    push_opstack(a)
2609    return true
2610end
2611
2612function operators.rcheck()
2613    local a = pop_opstack()
2614    if not a then
2615        return ps_error('stackunderflow')
2616    end
2617    local ta = a[1]
2618    local aa
2619    if ta == 'string' or ta == 'file' or ta == 'array' then
2620        aa = a[2]
2621    elseif ta == 'dict' then
2622        aa = get_VM(a[4]).access
2623    else
2624        return ps_error('typecheck')
2625    end
2626    push_opstack((aa == 'unlimited' or aa == 'read-only') and p_true or p_false)
2627    return true
2628end
2629
2630function operators.wcheck()
2631    local a = pop_opstack()
2632    if not a then
2633        return ps_error('stackunderflow')
2634    end
2635    local ta = a[1]
2636    local aa
2637    if ta == 'string' or ta == 'file' or ta == 'array' then
2638        aa = a[2]
2639    elseif ta == 'dict' then
2640        local thedict = get_VM(a[4])
2641        aa = thedict.access
2642    else
2643        return ps_error('typecheck')
2644    end
2645    push_opstack((aa == 'unlimited') and p_true or p_false)
2646    return true
2647end
2648
2649function operators.cvi()
2650    local a = pop_opstack()
2651    if not a then
2652        return ps_error('stackunderflow')
2653    end
2654    local ta = a[1]
2655    if ta == 'string' then
2656        push_opstack(a)
2657        local ret, err = operators.token()
2658        if not ret then
2659            return ret, err
2660        end
2661        local b = pop_opstack()
2662        if b[4] == false then
2663            return ps_error('syntaxerror')
2664        end
2665        a = pop_opstack()
2666        pop_opstack() -- get rid of the postmatch string remains
2667        ta = a[1]
2668    end
2669    local aa = a[2]
2670    if not (aa == 'unlimited' or aa == 'read-only') then
2671        return ps_error('invalidaccess')
2672    end
2673    if ta == 'integer' then
2674        push_opstack(a)
2675    elseif ta == 'real' then
2676        local va = a[4]
2677        local c = va < 0 and -floor(-va) or floor(ava)
2678        if abs(c) > MAX_INT then
2679            return ps_error('rangecheck')
2680        end
2681        push_opstack { 'integer', 'unlimited', 'literal', c }
2682    else
2683        return ps_error('typecheck')
2684    end
2685    return true
2686end
2687
2688function operators.cvn()
2689    local a = pop_opstack()
2690    if not a then
2691        return ps_error('stackunderflow')
2692    end
2693    local ta, aa = a[1], a[2]
2694    local ta = a[1]
2695    if ta ~= 'string' then
2696        return ps_error('typecheck')
2697    end
2698    if aa == 'execute-only' or aa == 'noaccess' then
2699        return ps_error('invalidaccess')
2700    end
2701    push_opstack { 'name', aa, a[3], add_VM(get_VM(a[4])) }
2702    return true
2703end
2704
2705function operators.cvr()
2706    local a = pop_opstack()
2707    if not a then
2708        return ps_error('stackunderflow')
2709    end
2710    local ta = a[1]
2711    if ta == 'string' then
2712        push_opstack(a)
2713        local ret, err = operators.token()
2714        if not ret then
2715            return ret, err
2716        end
2717        local b = pop_opstack()
2718        if b[4] == false then
2719            return ps_error('syntaxerror')
2720        end
2721        a = pop_opstack()
2722        pop_opstack() -- get rid of the postmatch string remains
2723        ta = a[1]
2724    end
2725    local aa = a[2]
2726    if not (aa == 'unlimited' or aa == 'read-only') then
2727        return ps_error('invalidaccess')
2728    end
2729    if ta == 'integer' then
2730        push_opstack { 'real', 'unlimited', 'literal', a[4] }
2731    elseif ta == 'real' then
2732        push_opstack(a)
2733    else
2734        return ps_error('typecheck')
2735    end
2736    return true
2737end
2738
2739do
2740
2741    local byte0 = byte('0')
2742    local byteA = byte('A') - 10
2743
2744    function operators.cvrs()
2745        local c = pop_opstack()
2746        local b = pop_opstack()
2747        local a = pop_opstack()
2748        if not a then
2749            return ps_error('stackunderflow')
2750        end
2751        local ta, tb, tc = a[1], b[1], c[1]
2752        if not (ta == 'integer' or ta == 'real') then
2753            return ps_error('typecheck')
2754        end
2755        if not tb == 'integer' then
2756            return ps_error('typecheck')
2757        end
2758        if not tc == 'string' then
2759            return ps_error('typecheck')
2760        end
2761        if not c[2] == 'unlimited' then
2762            return ps_error('invalidaccess')
2763        end
2764        local va, vb, vc = a[4], b[4], c[4]
2765        if (vb < 2 or vb > 36) then
2766            return ps_error('rangecheck')
2767        end
2768        if ta == 'real' then
2769            push_opstack(a)
2770            local ret, err = operators.cvi()
2771            if ret then
2772                return ret, err
2773            end
2774            a = pop_opstack()
2775        end
2776        -- todo: use an lpeg
2777        local decimal = va
2778        local str     = { }
2779        local n       = 0
2780        while decimal > 0 do
2781            local digit = decimal % vb
2782            n = n + 1
2783            str[n] = digit < 10 and char(digit+byte0) or char(digit+byteA)
2784            decimal = floor(decimal/vb)
2785        end
2786        if n > c[6] then
2787            return ps_error('rangecheck')
2788        end
2789        str = concat(reverse(str))
2790        local thestring = get_VM(vc)
2791        VM[va] = str .. sub(thestring,n+1,-1)
2792        push_opstack { c[1], c[2], c[3], add_VM(repl), n, n }
2793        return true
2794    end
2795
2796end
2797
2798function operators.cvs()
2799    local b = pop_opstack()
2800    local a = pop_opstack()
2801    if not 4 then
2802        return ps_error('stackunderflow')
2803    end
2804    local ta, tb = a[1], b[1]
2805    local ab = b[2]
2806    if not tb == 'string' then
2807        return ps_error('typecheck')
2808    end
2809    if not ab == 'unlimited' then
2810        return ps_error('invalidaccess')
2811    end
2812    local va, vb = a[4], b[4]
2813    if ta == 'real' then
2814        if floor(va) == va then
2815            va = tostring(va) .. '.0'
2816        else
2817            va = tostring(va)
2818        end
2819    elseif ta == 'integer' then
2820        va = tostring(va)
2821    elseif ta == 'string' or ta == 'name' then
2822        va = get_VM(va)
2823    elseif ta == 'operator' then
2824        va = a[5]
2825    elseif ta == 'boolean' then
2826        va = tostring(va)
2827    else
2828        va = "--nostringval--"
2829    end
2830    local n = #va
2831    if n > b[6] then
2832        return ps_error('rangecheck')
2833    end
2834    local thestring = get_VM(vb)
2835    VM[vb] = va .. sub(thestring,n+1,-1)
2836    push_opstack { tb, ab, b[3], add_VM(va), n, n }
2837    return true
2838end
2839
2840-- File operators
2841--
2842-- +file +closefile +read +write +writestring +readhexstring +writehexstring +readline ^token
2843-- +bytesavailable +flush +flushfile +resetfile +status +run +currentfile +print ^= ^stack
2844-- +== ^pstack ^prompt +echo
2845
2846function operators.file()
2847    local b = pop_opstack()
2848    local a = pop_opstack()
2849    if not a then
2850        return ps_error('stackunderflow')
2851    end
2852    if b[1] ~= 'string' then
2853        return ps_error('typecheck')
2854    end
2855    if a[1] ~= 'string' then
2856        return ps_error('typecheck')
2857    end
2858    local fmode = get_VM(b[4])
2859    local fname = get_VM(a[4])
2860    -- only accept (r), (w) and (a)
2861    if fmode ~= "r" and fmode ~= "w" and fmode ~= "a"  then
2862        return ps_error('typecheck')
2863    end
2864    if fname == "%stdin" then
2865        -- can only read from stdin
2866        if fmode ~= "r" then
2867            return ps_error('invalidfileaccess')
2868        end
2869        push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stdin }
2870    elseif fname == "%stdout" then
2871        -- can't read from stdout i.e. can only append, in fact, but lets ignore that
2872        if fmode == "r" then
2873            return ps_error('invalidfileaccess')
2874        end
2875        push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stdout }
2876    elseif fname == "%stderr" then
2877        -- cant read from stderr i.e. can only append, in fact, but lets ignore that
2878        if fmode == "r" then
2879            return ps_error('invalidfileaccess')
2880        end
2881        push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, io.stderr }
2882    elseif fname == "%statementedit" or fname == "%lineedit"then
2883        return ps_error('invalidfileaccess')
2884    else
2885      -- so it is a normal file
2886        local myfile, error = io.open(fname,fmode)
2887        if not myfile then
2888            return ps_error('undefinedfilename')
2889        end
2890        if fmode == 'r' then
2891            l = myfile:read("*a")
2892            if not l then
2893                return ps_error('invalidfileaccess')
2894            end
2895            -- myfile:close() -- do not close here, easier later on
2896            push_opstack { 'file', 'unlimited', 'literal', add_VM(l), 1, #l, fmode, myfile}
2897        else
2898            push_opstack { 'file', 'unlimited', 'literal', 0, 0, 0, fmode, myfile}
2899        end
2900    end
2901    return true
2902end
2903
2904function operators.read()
2905    local a = pop_opstack()
2906    if not a then
2907        return ps_error('stackunderflow')
2908    end
2909    if a[1] ~= 'file' then
2910        return ps_error('typecheck')
2911    end
2912    if a[7] ~= 'r' then
2913        return ps_error('invalidaccess')
2914    end
2915    local b
2916    local v = a[4]
2917    local f = a[8]
2918    if v > 0 then
2919        local thestr = get_VM(v)
2920        local n = a[5]
2921        if n < a[6] then
2922            byte = sub(thestr,n,n+1)
2923         -- a[5] = n + 1
2924        end
2925    else -- %stdin
2926        b = f:read(1)
2927    end
2928    if b then
2929        push_opstack { 'integer', 'unlimited', 'literal', byte(b) }
2930        push_opstack (p_true)
2931    else
2932        f:close()
2933        push_opstack (p_false)
2934    end
2935    return true
2936end
2937
2938function operators.write()
2939    local b = pop_opstack()
2940    local a = pop_opstack()
2941    if not a then
2942        return ps_error('stackunderflow')
2943    end
2944    if b[1] ~= 'integer' then
2945        return ps_error('typecheck')
2946    end
2947    if a[1] ~= 'file' then
2948        return ps_error('typecheck')
2949    end
2950    if a[7] == 'r' then
2951        return ps_error('ioerror')
2952    end
2953    a[8]:write(char(b[4] % 256))
2954    return true
2955end
2956
2957function operators.writestring()
2958    local b = pop_opstack()
2959    local a = pop_opstack()
2960    if not a then
2961        return ps_error('stackunderflow')
2962    end
2963    if b[1] ~= 'string' then
2964        return ps_error('typecheck')
2965    end
2966    if a[1] ~= 'file' then
2967        return ps_error('typecheck')
2968    end
2969    if a[7] == 'r' then
2970        return ps_error('ioerror')
2971    end
2972    a[8]:write(get_VM(b[4]))
2973    return true
2974end
2975
2976function operators.writehexstring()
2977    local b = pop_opstack()
2978    local a = pop_opstack()
2979    if not a then
2980        return ps_error('stackunderflow')
2981    end
2982    if b[1] ~= 'string' then
2983        return ps_error('typecheck')
2984    end
2985    if a[1] ~= 'file' then
2986        return ps_error('typecheck')
2987    end
2988    if a[7] == 'r' then
2989        return ps_error('ioerror')
2990    end
2991    local f = a[8]
2992    local s = get_VM(b[4])
2993    for w in gmatch(s,".") do
2994        f:write(format("%x",byte(w))) -- we have a table for that somewhere
2995    end
2996   return true
2997end
2998
2999do
3000
3001    local function get_string_line(a)
3002        local str    = get_VM(a[4])
3003        local start  = a[5]
3004        local theend = a[6]
3005        if start == theend then
3006            return nil
3007        end
3008        str = match(str,"[\n\r]*([^\n\r]*)",start)
3009        a[5] = a[5] + #str + 1 -- ?
3010        return str
3011    end
3012
3013    local function get_hexstring_line (a,b)
3014        local thestring = get_VM(a[4])
3015        local start, theend = a[5], a[6]
3016        if start == theend then
3017            return nil
3018        end
3019        local prefix, result, n = nil, { }, 0
3020        local nmax = b[6]
3021        while start < theend do
3022            local b = sub(thestring,start,start)
3023            if not b then
3024                break
3025            end
3026            local hexbyte = tonumber(b,16)
3027            if not hexbyte then
3028                -- skip
3029            elseif prefix then
3030                n = n + 1
3031                result[n] = char(prefix*16+hexbyte)
3032                if n == nmax then
3033                    break
3034                else
3035                    prefix = nil
3036                end
3037            else
3038                prefix = hexbyte
3039            end
3040            start = start + 1
3041        end
3042        a[5] = start + 1 -- ?
3043        return concat(result)
3044    end
3045
3046    function operators.readline()
3047        local b = pop_opstack()
3048        local a = pop_opstack()
3049        if not a then
3050            return ps_error('stackunderflow')
3051        end
3052        if a[1] ~= 'file' then
3053            return ps_error('typecheck')
3054        end
3055        if a[7] ~= 'r' then
3056            return ps_error('invalidaccess')
3057        end
3058        local va = a[4]
3059        if va > 0 then
3060            va = get_string_line(a)
3061        else
3062            va = a[8]:read('*l')
3063        end
3064        if not va then
3065            push_opstack { 'string', 'unlimited', 'literal', add_VM(''), 0, 0 }
3066            push_opstack (p_false)
3067        else
3068            local n = #va
3069            if n > b[6] then
3070                return ps_error('rangecheck')
3071            end
3072            local thestring = get_VM(b[4])
3073            VM[b[4]] = va .. sub(thestring,#va+1, -1)
3074            push_opstack { 'string', 'unlimited', 'literal', add_VM(va), n, n }
3075            push_opstack (p_true)
3076        end
3077        return true
3078    end
3079
3080    function operators.readhexstring()
3081        local b = pop_opstack()
3082        local a = pop_opstack()
3083        if not a then
3084            return ps_error('stackunderflow')
3085        end
3086        local ta = a[1]
3087        if not (ta == 'string' or ta == 'file') then
3088            return ps_error('typecheck')
3089        end
3090        local thefile = a[8]
3091        local va = a[4]
3092        if va > 0 then
3093            va = get_hexstring_line (a,b)
3094        else
3095            local prefix, result, n = nil, { }, 0
3096            -- todo: read #va bytes and lpeg
3097            while true do
3098                local b = thefile:read(1)
3099                if not b then
3100                    break
3101                end
3102                local hexbyte = tonumber(b,16)
3103                local nmax = b[6]
3104                if not hexbyte then
3105                    -- skip
3106                elseif prefix then
3107                    n = n + 1
3108                    result[n] = char(prefix*16+hexbyte)
3109                    if n == nmax then
3110                        break
3111                    else
3112                        prefix = nil
3113                    end
3114                else
3115                    prefix = hexbyte
3116                end
3117            end
3118            va = concat(result)
3119        end
3120        local thestring = get_VM(b[4])
3121        local n = #va
3122        VM[b[4]] = repl .. sub(thestring,n+1,-1)
3123        push_opstack { b[1], b[2], b[3], add_VM(va), n, n }
3124        push_opstack ((n == b[6]) and p_true or p_false)
3125        return true
3126    end
3127
3128end
3129
3130function operators.flush()
3131    io.flush()
3132    return true
3133end
3134
3135function operators.bytesavailable()
3136    local a = pop_opstack()
3137    if not a then
3138        return ps_error('stackunderflow')
3139    end
3140    if a[1] ~= 'file' then
3141        return ps_error('typecheck')
3142    end
3143    if a[7] ~= 'r' then
3144        return ps_error('typecheck')
3145    end
3146    local waiting = (a[4] > 0) and (a[6] - a[5] + 1) or -1
3147    push_opstack { "integer", "unlimited", "literal", waiting }
3148    return true
3149end
3150
3151-- this does not really do anything useful
3152
3153function operators.resetfile()
3154    local a = pop_opstack()
3155    if not a then
3156        return ps_error('stackunderflow')
3157    end
3158    if a[1] ~= 'file' then
3159        return ps_error('typecheck')
3160    end
3161    return true
3162end
3163
3164function operators.flushfile()
3165    local a = pop_opstack()
3166    if not a then
3167        return ps_error('stackunderflow')
3168    end
3169    if a[1] ~= 'file' then
3170        return ps_error('typecheck')
3171    end
3172    if a[4] > 0 then
3173        a[5] = a[6]
3174    else
3175        a[8]:flush()
3176    end
3177    return true
3178end
3179
3180function operators.closefile()
3181    local a = pop_opstack()
3182    if not a then
3183        return ps_error('stackunderflow')
3184    end
3185    if a[1] ~= 'file' then
3186        return ps_error('typecheck')
3187    end
3188    if a[7] == 'r' then
3189        a[5] = a[6]
3190    else
3191        push_opstack(a)
3192        operators.flushfile()
3193    end
3194    a[8]:close()
3195    return true
3196end
3197
3198function operators.status()
3199    local a = pop_opstack()
3200    if not a then
3201        return ps_error('stackunderflow')
3202    end
3203    if a[1] ~= 'file' then
3204        return ps_error('typecheck')
3205    end
3206    local state = io.type(a[8])
3207    push_opstack { "boolean", 'unlimited', 'literal', not state or state == "closed file" }
3208    return true
3209end
3210
3211function operators.run()
3212    push_opstack { "string", "unlimited", "literal", add_VM("r"), 1, 1 }
3213    local ret, err = operators.file()
3214    if not ret then
3215        return ret, err
3216    end
3217    ret, err = operators.cvx()
3218    if not ret then
3219        return ret, err
3220    end
3221    local a = pop_opstack() -- an executable file
3222    push_execstack { ".run", "unlimited", "literal", false } -- constant
3223    local curstack = execstackptr
3224    local thefile  = a[8]
3225    push_execstack(a)
3226    while curstack < execstackptr do
3227        do_exec()
3228    end
3229    local state = io.type(thefile)
3230    if not state or state == "closed file" then
3231        -- okay
3232    else
3233        thefile:close()
3234    end
3235    if execstackptr > 0 then
3236        local entry = execstack[execstackptr]
3237        if entry[1] == '.run' and entry[4] == true then
3238            pop_execstack()
3239        end
3240    end
3241    return true
3242end
3243
3244function operators.currentfile()
3245    local n = execstackptr
3246    while n >= 0 do
3247        local entry = execstack[n]
3248        if entry[1] == 'file' and entry[7] == 'r' then
3249            push_opstack(entry)
3250            return true
3251        end
3252        n = n - 1
3253    end
3254    push_opstack { 'file', 'unlimited', 'executable', add_VM(''), 0, 0, 'r', stdin }
3255    return true
3256end
3257
3258function operators.print()
3259    local a = pop_opstack()
3260    if not a then return
3261        ps_error('stackunderflow')
3262    end
3263    if a[1] ~= 'string' then
3264        return ps_error('typecheck')
3265    end
3266    report(get_VM(a[4]))
3267end
3268
3269-- '=' is also defined as a procedure below;
3270--
3271-- it is actually supposed to do this: "equaldict begin dup type exec end"
3272-- where each of the entries in equaldict handles one type only, but this
3273-- works just as well
3274
3275do
3276
3277    local pattern = Cs(
3278        Cc("(")
3279      * (
3280            P("\n") / "\\n"
3281          + P("\r") / "\\r"
3282          + P("(")  / "\\("
3283          + P(")")  / "\\)"
3284          + P("\\") / "\\\\"
3285          + P("\b") / "\\b"
3286          + P("\t") / "\\t"
3287          + P("\f") / "\\f"
3288          + R("\000\032","\127\255") / tonumber / formatters["\\%03o"]
3289          + P(1)
3290        )^0
3291      * Cc(")")
3292    )
3293
3294    -- print(lpegmatch(pattern,[[h(a\nn)s]]))
3295
3296    local function do_operator_equal(a)
3297        local ta, va = a[1], a[4]
3298        if ta == 'real' then
3299            if floor(va) == va then
3300                return tostring(va .. '.0')
3301            else
3302                return tostring(va)
3303            end
3304        elseif ta == 'integer' then
3305            return tostring(va)
3306        elseif ta == 'string' then
3307            return lpegmatch(pattern,get_VM(va))
3308        elseif ta == 'boolean' then
3309            return tostring(va)
3310        elseif ta == 'operator' then
3311            return '--' .. a[5] .. '--'
3312        elseif ta == 'name' then
3313            if a[3] == 'literal' then
3314                return '/' .. get_VM(va)
3315            else
3316                return get_VM(va)
3317            end
3318        elseif ta == 'array' then
3319            va = get_VM(va)
3320            local isexec = a[3] == 'executable'
3321            local result = { isexec and "{" or "[" }
3322            local n      = 1
3323            for i=1,#va do
3324                n = n + 1
3325                result[n] = do_operator_equal(va[i])
3326            end
3327            result[n+1] = isexec and "}" or "]"
3328            return concat(result," ")
3329        elseif ta == 'null' then
3330            return 'null'
3331        elseif ta == 'dict' then
3332            return '-dicttype-'
3333        elseif ta == 'save' then
3334            return '-savetype-'
3335        elseif ta == 'mark' then
3336            return '-marktype-'
3337        elseif ta == 'file' then
3338            return '-filetype-'
3339        elseif ta == 'font' then
3340            return '-fonttype-'
3341        end
3342    end
3343
3344    function operators.equal()
3345        local a = pop_opstack()
3346        if not a then
3347            return ps_error('stackunderflow')
3348        end
3349        report(do_operator_equal(a))
3350        return true
3351    end
3352
3353end
3354
3355local function commonstack(seperator)
3356    for n=1,opstackptr do
3357        push_opstack { 'string', 'unlimited', 'literal', add_VM(seperator), 1 ,1 }
3358        push_opstack(opstack[n])
3359        push_execstack { 'operator','unlimited','executable', operators.print, 'print' }
3360        push_execstack { 'operator','unlimited','executable', operators.equal, '==' }
3361    end
3362    return true
3363end
3364
3365function operators.pstack()
3366    return commonstack("\n")
3367end
3368
3369function operators.stack()
3370    return commonstack(" ")
3371end
3372
3373-- this does not really do anything useful
3374
3375function operators.echo()
3376    local a = pop_opstack()
3377    if not a then
3378        return ps_error('stackunderflow')
3379    end
3380    if a[1] ~= 'boolean' then
3381        return ps_error('typecheck')
3382    end
3383    return true
3384end
3385
3386-- Virtual memory operators
3387--
3388-- +save +restore +vmstatus
3389
3390-- to be checked: we do a one-level shallow copy now, not sure if that
3391-- is good enough yet
3392
3393local savelevel = 0
3394
3395initializers[#initializers+1] = function(reset)
3396    savelevel = 0
3397end
3398
3399function operators.save()
3400    local saved_VM = { }
3401--     for k1, v1 in next, VM do
3402    for k1 = 1, #VM do
3403        local v1 = VM[k1]
3404        if type(v1) == "table" then
3405            local t1 = { }
3406            saved_VM[k1] = t1
3407--             for k2, v2 in next, v1 do
3408            for k2=1,#v1 do
3409                local v2 = v1[k2]
3410                if type(v2) == "table" then
3411                    local t2 = { }
3412                    t1[k2] = t2
3413--                     for k3, v3 in next, v2 do
3414                    for k3=1,#v2 do
3415                        local v3 = v2[k3]
3416                        t2[k3] = v3
3417                    end
3418                else
3419                    t1[k2] = v2
3420                end
3421            end
3422        else
3423            saved_VM[k1] = v1
3424        end
3425    end
3426    push_gsstack { 'save', copy_gsstate() }
3427    savelevel = savelevel + 1
3428    push_opstack { 'save', 'unlimited', 'executable', add_VM(saved_VM) }
3429end
3430
3431function operators.save()
3432    local saved_VM = table.copy(VM)
3433    push_gsstack { 'save', copy_gsstate() }
3434    savelevel = savelevel + 1
3435    push_opstack { 'save', 'unlimited', 'executable', add_VM(saved_VM) }
3436end
3437
3438do
3439
3440    local function validstack(stack,index,saved_VM)
3441        -- loop over pstack, execstack, and dictstack to make sure
3442        -- there are no entries with VM_id > #saved_VM
3443        for i=index,1,-1 do
3444            local v = stack[i]
3445            if type(v) == "table" then
3446                local tv = v[1]
3447                if tv == "save" or tv == "string" or tv == "array" or tv == "dict" or tv == "name" or tv == "file" then
3448                    -- todo: check on %stdin/%stdout, but should be ok
3449                    if v[4] > #saved_VM then
3450                        return false
3451                    end
3452                end
3453            end
3454        end
3455        return true
3456    end
3457
3458    function operators.restore()
3459        local a = pop_opstack()
3460        if not a then
3461            return ps_error('stackunderflow')
3462        end
3463        if a[1] ~= 'save' then
3464            return ps_error('typecheck')
3465        end
3466        if a[4] == 0 or savelevel == 0 then
3467            return ps_error('invalidrestore')
3468        end
3469        local saved_VM = get_VM(a[4])
3470        if directvm then
3471        else
3472            if not validstack(execstack,execstackptr,saved_VM) then
3473                return ps_error('invalidrestore')
3474            end
3475            if not validstack(dictstack,dictstackptr,saved_VM) then
3476                return ps_error('invalidrestore')
3477            end
3478            if not validstack(opstack,opstackptr,saved_VM) then
3479                return ps_error('invalidrestore')
3480            end
3481        end
3482        while gsstackptr > 0 do
3483            local g = gsstack[gsstackptr]
3484            gsstackptr = gsstackptr - 1
3485            if g[1] == "save"  then
3486                gsstate = g[2]
3487                return
3488            end
3489        end
3490        a[4] = 0 -- invalidate save object
3491        savelevel = savelevel - 1
3492        VM = saved_VM
3493    end
3494
3495end
3496
3497function operators.vmstatus()
3498    local n = 0 -- #VM * 100
3499    push_opstack { 'integer', 'unlimited', 'literal', savelevel }
3500    push_opstack { 'integer', 'unlimited', 'literal', n }
3501    push_opstack { 'integer', 'unlimited', 'literal', n }
3502    return true
3503end
3504
3505-- Miscellaneous operators
3506--
3507-- +bind +null +usertime +version
3508
3509-- the reference manual says bind only ERRORS on typecheck
3510
3511local function bind()
3512    local a = pop_opstack()
3513    if not a then
3514        return true -- ps_error('stackunderflow')
3515    end
3516    if not a[1] == 'array' then
3517        return ps_error('typecheck')
3518    end
3519    local proc = get_VM(a[4])
3520    for i=1,#proc do
3521        local v = proc[i]
3522        local t = v[1]
3523        if t == 'name' then
3524            if v[3] == 'executable' then
3525                local op = lookup(get_VM(v[4]))
3526                if op and op[1] == 'operator' then
3527                    proc[i] = op
3528                end
3529            end
3530        elseif t == 'array' then
3531            if v[2] == 'unlimited' then
3532                push_opstack(v)
3533                bind() -- recurse
3534                pop_opstack()
3535                proc[i][2] = 'read-only'
3536            end
3537        end
3538    end
3539    push_opstack(a)
3540end
3541
3542operators.bind = bind
3543
3544function operators.null()
3545    push_opstack { 'null', 'unlimited', 'literal' }
3546    return true
3547end
3548
3549function operators.usertime()
3550    push_opstack { 'integer', 'unlimited', 'literal', floor(os.clock() * 1000) }
3551    return true
3552end
3553
3554function operators.version()
3555    push_opstack { 'string', 'unlimited', 'literal', add_VM('23.0') }
3556    return true
3557end
3558
3559-- Graphics state operators
3560--
3561-- +gsave +grestore +grestoreall +initgraphics +setlinewidth +currentlinewidth +setlinecap +currentlinecap
3562-- +setlinejoin +currentlinejoin +setmiterlimit +currentmiterlimit +setdash +currentdash +setflat +currentflat
3563-- +setgray +currentgray +sethsbcolor +currenthsbcolor +setrgbcolor +setcmykcolor +currentrgbcolor +setscreen
3564-- +currentscreen +settransfer +currenttransfer
3565
3566function operators.gsave()
3567    push_gsstack { 'gsave', copy_gsstate() }
3568    currentpage[#currentpage+1] = {
3569        type      = 'gsave',
3570    }
3571    return true
3572end
3573
3574function operators.grestore()
3575    if gsstackptr > 0 then
3576        local g = gsstack[gsstackptr]
3577        if g[1] == "gsave" then
3578            gsstackptr = gsstackptr - 1
3579            gsstate = g[2]
3580        end
3581    end
3582    currentpage[#currentpage+1] = {
3583        type      = 'grestore',
3584    }
3585    return true
3586end
3587
3588function operators.grestoreall() -- needs checking
3589    for i=gsstackptr,1,-1 do
3590        local g = gsstack[i]
3591        if g[1] == "save"  then
3592            gsstate    = g[2]
3593            gsstackptr = i
3594            return true
3595        end
3596    end
3597    gsstackptr = 0
3598    return true
3599end
3600
3601function operators.initgraphics()
3602    local newstate       = copy_gsstate() -- hm
3603    newstate.matrix      = { 1, 0, 0, 1, 0, 0 }
3604    newstate.color       = { gray = 0, hsb = { }, rgb = { }, cmyk = { }, type = "gray" }
3605    newstate.position    = { } -- actual x and y undefined
3606    newstate.path        = { }
3607    newstate.linewidth   = 1
3608    newstate.linecap     = 0
3609    newstate.linejoin    = 0
3610    newstate.miterlimit  = 10
3611    newstate.dashpattern = { }
3612    newstate.dashoffset  = 0
3613    gsstate = newstate
3614    device.initgraphics()
3615    operators.initclip()
3616    return true
3617end
3618
3619function operators.setlinewidth()
3620    local a = pop_opstack()
3621    if not a then
3622        return ps_error('stackunderflow')
3623    end
3624    local t = a[1]
3625    if not (t == 'integer' or t == 'real') then
3626        return ps_error('typecheck')
3627    end
3628    gsstate.linewidth = a[4]
3629    return true
3630end
3631
3632function operators.currentlinewidth()
3633    local w = gsstate.linewidth
3634    push_opstack {
3635        (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer',
3636        'unlimited',
3637        'literal',
3638        w,
3639    }
3640    return true
3641end
3642
3643function operators.setlinecap()
3644    local a = pop_opstack()
3645    if not a then
3646        return ps_error('stackunderflow')
3647    end
3648    if a[1] ~= 'integer' then
3649        return ps_error('typecheck')
3650    end
3651    local c =  a[4]
3652    if c > 2 or c < 0 then
3653        return ps_error('rangecheck')
3654    end
3655    gsstate.linecap = c
3656    return true
3657end
3658
3659function operators.currentlinecap()
3660    push_opstack { 'integer', 'unlimited', 'literal', gsstate.linecap }
3661    return true
3662end
3663
3664function operators.setlinejoin()
3665    local a = pop_opstack()
3666    if not a then
3667        return ps_error('stackunderflow')
3668    end
3669    if a[1] ~= 'integer' then
3670        return ps_error('typecheck')
3671    end
3672    local j = a[4]
3673    if j > 2 or j < 0 then
3674        return ps_error('rangecheck')
3675    end
3676    gsstate.linejoin = j
3677    return true
3678end
3679
3680function operators.currentlinejoin()
3681   push_opstack { 'integer', 'unlimited', 'literal', gsstate.linejoin }
3682   return true
3683end
3684
3685function operators.setmiterlimit()
3686    local a = pop_opstack()
3687    if not a then
3688        return ps_error('stackunderflow')
3689    end
3690    local t = a[1]
3691    if not (t == 'integer' or t == 'real') then
3692        return ps_error('typecheck')
3693    end
3694    local m = a[4]
3695    if m < 1 then
3696        return ps_error('rangecheck')
3697    end
3698    gsstate.miterlimit = m
3699    return true
3700end
3701
3702function operators.currentmiterlimit()
3703    local w = gsstate.miterlimit
3704    push_opstack {
3705        (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer',
3706        'unlimited',
3707        'literal',
3708        w
3709    }
3710    return true
3711end
3712
3713function operators.setdash()
3714    local b = pop_opstack()
3715    local a = pop_opstack()
3716    if not a then
3717        return ps_error('stackunderflow')
3718    end
3719    local ta, tb = a[1], b[1]
3720    if ta ~= 'array' then
3721        return ps_error('typecheck')
3722    end
3723    if not (tb == 'integer' or tb == 'real') then
3724        return ps_error('typecheck')
3725    end
3726    local pattern  = { }
3727    local total    = 0
3728    local thearray = get_VM(a[4])
3729    for i=1,#thearray do
3730        local a = thearray[i]
3731        local ta, va = a[1], a[4]
3732        if ta ~= "integer" then
3733            return ps_error('typecheck')
3734        end
3735        if va < 0 then
3736            return ps_error('limitcheck')
3737        end
3738        total = total + va
3739        pattern[#pattern+1] = va
3740    end
3741    if #pattern > 0 and total == 0 then
3742        return ps_error('limitcheck')
3743    end
3744    gsstate.dashpattern = pattern
3745    gsstate.dashoffset  = b[4]
3746    return true
3747end
3748
3749function operators.currentdash()
3750    local thearray = gsstate.dashpattern
3751    local pattern  = { }
3752    for i=1,#thearray do
3753        pattern[i] = { 'integer', 'unlimited', 'literal', thearray[i] }
3754    end
3755    push_opstack { 'array', 'unlimited', 'literal', add_VM(pattern), #pattern, #pattern }
3756    local w = gsstate.dashoffset
3757    push_opstack {
3758        (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w
3759    }
3760    return true
3761end
3762
3763function operators.setflat()
3764    local a = pop_opstack()
3765    if not a then
3766        return ps_error('stackunderflow')
3767    end
3768    local ta, va = a[1], a[4]
3769    if not (ta == 'integer' or ta == 'real') then
3770        return ps_error('typecheck')
3771    end
3772    gsstate.flatness = va
3773    return true
3774end
3775
3776function operators.currentflat()
3777    local w = gsstate.flatness
3778    push_opstack {
3779        (abs(w) > MAX_INT or floor(w) ~= w) and 'real' or 'integer', 'unlimited', 'literal', w
3780    }
3781    return true
3782end
3783
3784-- Color conversion functions
3785--
3786-- normally, level one colors are based on hsb, but for our backend it is better to
3787-- stick with the original request when possible
3788
3789do
3790
3791    local function rgb_to_gray (r, g, b)
3792        return 0.30 * r + 0.59 * g + 0.11 * b
3793    end
3794
3795    local function cmyk_to_gray (c, m, y, k)
3796        return 0.30 * (1.0 - min(1.0,c+k)) + 0.59 * (1.0 - min(1.0,m+k)) + 0.11 * (1.0 - min(1.0,y+k))
3797    end
3798
3799    local function cmyk_to_rgb (c, m, y, k)
3800        return 1.0 - min(1.0,c+k), 1.0 - min(1.0,m+k), 1.0 - min(1.0,y+k)
3801    end
3802
3803    local function rgb_to_hsv(r, g, b)
3804        local offset, maximum, other_1, other_2
3805        if r >= g and r >= b then
3806            offset, maximum, other_1, other_2 = 0, r, g, b
3807        elseif g >= r and g >= b then
3808            offset, maximum, other_1, other_2 = 2, g, b, r
3809        else
3810            offset, maximum, other_1, other_2 = 4, b, r, g
3811        end
3812        if maximum == 0 then
3813            return 0, 0, 0
3814        end
3815        local minimum = other_1 < other_2 and other_1 or other_2
3816        if maximum == minimum then
3817            return 0, 0, maximum
3818        end
3819        local delta = maximum - minimum
3820        return (offset + (other_1-other_2)/delta)/6, delta/maximum, maximum
3821     end
3822
3823    local function gray_to_hsv (col)
3824        return 0, 0, col
3825    end
3826
3827    local function gray_to_rgb (col)
3828        return 1-col, 1-col, 1-col
3829    end
3830
3831    local function gray_to_cmyk (col)
3832        return 0, 0, 0, col
3833    end
3834
3835    local function hsv_to_rgb(h,s,v)
3836        local hi = floor(h * 6.0) % 6
3837        local f =  (h * 6) - floor(h * 6)
3838        local p = v * (1 - s)
3839        local q = v * (1 - f * s)
3840        local t = v * (1 - (1 - f) * s)
3841        if hi == 0 then
3842            return v, t, p
3843        elseif hi == 1 then
3844            return q, v, p
3845        elseif hi == 2 then
3846            return p, v, t
3847        elseif hi == 3 then
3848            return p, q, v
3849        elseif hi == 4 then
3850            return t, p, v
3851        elseif hi == 5 then
3852            return v, p, q
3853        end
3854    end
3855
3856    local function hsv_to_gray(h,s,v)
3857        return rgb_to_gray(hsv_to_rgb(h,s,v))
3858    end
3859
3860    -- color operators
3861
3862    function operators.setgray()
3863        local g = pop_opstack()
3864        if not g then
3865            return ps_error('stackunderflow')
3866        end
3867        local gt = g[1]
3868        if not (gt == 'integer' or gt == 'real') then
3869            return ps_error('typecheck')
3870        end
3871        local gv = g[4]
3872        local color = gsstate.color
3873        color.type = "gray"
3874        color.gray = (gv < 0 and 0) or (gv > 1 and 1) or gv
3875        return true
3876    end
3877
3878    function operators.currentgray()
3879        local color = gsstate.color
3880        local t = color.type
3881        local s
3882        if t == "gray" then
3883            s = color.gray
3884        elseif t == "rgb" then
3885            local col = color.rgb
3886            s = rgb_to_gray(col[1],col[2],col[3])
3887        elseif t == "cmyk" then
3888            local col = cmyk
3889            s = cmyk_to_gray(col[1],col[2],col[3],col[4])
3890        else
3891            local col = color.hsb
3892            s = hsv_to_gray(col[1],col[2],col[3])
3893        end
3894        push_opstack { (s == 0 or s == 1) and 'integer' or 'real', 'unlimited', 'literal', s }
3895        return true
3896    end
3897
3898    function operators.sethsbcolor()
3899        local b = pop_opstack()
3900        local s = pop_opstack()
3901        local h = pop_opstack()
3902        if not h then
3903            return ps_error('stackunderflow')
3904        end
3905        local ht, st, bt = h[1], s[1], b[1]
3906        if not (ht == 'integer' or ht == 'real') then
3907            return ps_error('typecheck')
3908        end
3909        if not (st == 'integer' or st == 'real') then
3910            return ps_error('typecheck')
3911        end
3912        if not (bt == 'integer' or bt == 'real') then
3913            return ps_error('typecheck')
3914        end
3915        local hv, sv, bv = h[4], s[4], b[4]
3916        local color = gsstate.color
3917        color.type = "hsb"
3918        color.hsb  = {
3919           (hv < 0 and 0) or (hv > 1 and 1) or hv,
3920           (sv < 0 and 0) or (sv > 1 and 1) or sv,
3921           (bv < 0 and 0) or (bv > 1 and 1) or bv,
3922        }
3923        return true
3924    end
3925
3926    function operators.currenthsbcolor()
3927        local color = gsstate.color
3928        local t = color.type
3929        local h, s, b
3930        if t == "gray" then
3931            h, s, b = gray_to_hsv(color.gray)
3932        elseif t == "rgb" then
3933            local col = color.rgb
3934            h, s, b = rgb_to_hsv(col[1],col[2],col[3])
3935        elseif t == "cmyk" then
3936            local col = color.cmyk
3937            h, s, b = cmyk_to_hsv(col[1],col[2],col[3],col[4])
3938        else
3939            local col = color.hsb
3940            h, s, b = col[1], col[2], col[3]
3941        end
3942        push_opstack { (h == 0 or h == 1) and 'integer' or 'real', 'unlimited', 'literal', h }
3943        push_opstack { (s == 0 or s == 1) and 'integer' or 'real', 'unlimited', 'literal', s }
3944        push_opstack { (b == 0 or b == 1) and 'integer' or 'real', 'unlimited', 'literal', b }
3945        return true
3946    end
3947
3948    function operators.setrgbcolor()
3949        local b = pop_opstack()
3950        local g = pop_opstack()
3951        local r = pop_opstack()
3952        if not r then
3953            return ps_error('stackunderflow')
3954        end
3955        local rt, gt, bt = r[1], g[1], b[1]
3956        if not (rt == 'integer' or rt == 'real') then
3957            return ps_error('typecheck')
3958        end
3959        if not (gt == 'integer' or gt == 'real') then
3960            return ps_error('typecheck')
3961        end
3962        if not (bt == 'integer' or bt == 'real') then
3963            return ps_error('typecheck')
3964        end
3965        local rv, gv, bv = r[4], g[4], b[4]
3966        local color = gsstate.color
3967        color.type = "rgb"
3968        color.rgb  = {
3969            (rv < 0 and 0) or (rv > 1 and 1) or rv,
3970            (gv < 0 and 0) or (gv > 1 and 1) or gv,
3971            (bv < 0 and 0) or (bv > 1 and 1) or bv,
3972        }
3973        return true
3974    end
3975
3976    function operators.currentrgbcolor()
3977        local color = gsstate.color
3978        local t = color.type
3979        local r, g, b
3980        if t == "gray" then
3981            r, g, b = gray_to_rgb(color.gray)
3982        elseif t == "rgb" then
3983            local col = color.rgb
3984            r, g, b = col[1], col[2], col[3]
3985        elseif t == "cmyk" then
3986            r, g, b = cmyk_to_rgb(color.cmyk)
3987        else
3988            local col = color.hsb
3989            r, g, b = hsv_to_rgb(col[1], col[2], col[3])
3990        end
3991        push_opstack { (r == 0 or r == 1) and "integer" or "real", 'unlimited', 'literal', r }
3992        push_opstack { (g == 0 or g == 1) and "integer" or "real", 'unlimited', 'literal', g }
3993        push_opstack { (b == 0 or b == 1) and "integer" or "real", 'unlimited', 'literal', b }
3994        return true
3995    end
3996
3997    function operators.setcmykcolor()
3998        local k = pop_opstack()
3999        local y = pop_opstack()
4000        local m = pop_opstack()
4001        local c = pop_opstack()
4002        if not c then
4003            return ps_error('stackunderflow')
4004        end
4005        local ct, mt, yt, kt = c[1], m[1], y[1], k[1]
4006        if not (ct == 'integer' or ct == 'real') then
4007            return ps_error('typecheck')
4008        end
4009        if not (mt == 'integer' or mt == 'real') then
4010            return ps_error('typecheck')
4011        end
4012        if not (yt == 'integer' or yt == 'real') then
4013            return ps_error('typecheck')
4014        end
4015        if not (kt == 'integer' or kt == 'real') then
4016            return ps_error('typecheck')
4017        end
4018        local cv, mv, yv, kv = c[4], m[4], y[4], k[4]
4019        local color = gsstate.color
4020        color.type = "cmyk"
4021        color.cmyk = {
4022            (cv < 0 and 0) or (cv > 1 and 1) or cv,
4023            (mv < 0 and 0) or (mv > 1 and 1) or mv,
4024            (yv < 0 and 0) or (yv > 1 and 1) or yv,
4025            (kv < 0 and 0) or (kv > 1 and 1) or kv,
4026        }
4027        return true
4028    end
4029
4030    function operators.currentcmykcolor()
4031        local color = gsstate.color
4032        local t = color.type
4033        local c, m, y, k
4034        if t == "gray" then
4035            c, m, y, k = gray_to_cmyk(color.gray)
4036        elseif t == "rgb" then
4037            c, m, y, k = rgb_to_cmyk(color.rgb)
4038        elseif t == "cmyk" then
4039            local col = color.cmyk
4040            c, m, y, k = col[1], col[2], col[3], col[4]
4041        else
4042            local col = color.hsb
4043            c, m, y, k = hsv_to_cmyk(col[1], col[2], col[3])
4044        end
4045        push_opstack { (c == 0 or c == 1) and "integer" or "real", 'unlimited', 'literal', c }
4046        push_opstack { (m == 0 or m == 1) and "integer" or "real", 'unlimited', 'literal', m }
4047        push_opstack { (y == 0 or y == 1) and "integer" or "real", 'unlimited', 'literal', y }
4048        push_opstack { (k == 0 or k == 1) and "integer" or "real", 'unlimited', 'literal', k }
4049        return true
4050    end
4051
4052end
4053
4054function operators.setscreen()
4055    local c = pop_opstack()
4056    local b = pop_opstack()
4057    local a = pop_opstack()
4058    if not a then
4059        return ps_error('stackunderflow')
4060    end
4061    local ta, tb, tc, ac = a[1], b[1], c[1], c[3]
4062    if not (tc == 'array' and ac == 'executable') then
4063        return ps_error('typecheck')
4064    end
4065    if not (tb == 'real' or tb == 'integer') then
4066        return ps_error('typecheck')
4067    end
4068    if not (ta == 'real' or ta == 'integer') then
4069        return ps_error('typecheck')
4070    end
4071    local va, vb, vc = a[4], b[4], c[4]
4072    if vb < 0 or vb > 360 then
4073        return ps_error('rangecheck')
4074    end
4075    if va < 0 then
4076        return ps_error('rangecheck')
4077    end
4078    gsstate.screen = { va, vb, vc }
4079    return true
4080end
4081
4082function operators.currentscreen()
4083    local w
4084    if not gsstate.screen then
4085        local popper = { 'operator', 'unlimited', 'executable', operators.pop, 'pop' }
4086        push_opstack { 'integer', 'unlimited', 'literal', 1 }
4087        push_opstack { 'integer', 'unlimited', 'literal', 0 }
4088        push_opstack { 'array',   'unlimited', 'executable', add_VM{ popper }, 1, 1, 'd' }
4089    else
4090        local w1 = gsstate.screen[1]
4091        local w2 = gsstate.screen[2]
4092        local w3 = gsstate.screen[3]
4093        push_opstack {
4094            (abs(w) > MAX_INT or floor(w1) ~= w1) and 'real' or 'integer', 'unlimited', 'literal', w1
4095        }
4096        push_opstack {
4097            (abs(w) > MAX_INT or floor(w2) ~= w2) and 'real' or 'integer', 'unlimited', 'literal', w2
4098        }
4099        local thearray = get_VM(w3)
4100        push_opstack { 'array', 'unlimited', 'executable', w3, 1, #thearray, 'd' } -- w3 or thearray ?
4101    end
4102    return true
4103end
4104
4105function operators.settransfer()
4106    local a = pop_opstack()
4107    if not a then
4108        return ps_error('stackunderflow')
4109    end
4110    if not (a[1] == 'array' and a[3] == 'executable') then
4111        return ps_error('typecheck')
4112    end
4113    local va = a[4]
4114    if va < 0 then
4115        return ps_error('rangecheck')
4116    end
4117    gsstate.transfer = va
4118    return true
4119end
4120
4121function operators.currenttransfer()
4122    local transfer = gsstate.transfer
4123    if not transfer then
4124        push_opstack { 'array', 'unlimited', 'executable', add_VM{ }, 0, 0, 'd'}
4125    else
4126        local thearray = get_VM(transfer)
4127        push_opstack { 'array', 'unlimited', 'executable', transfer, 1, #thearray, 'd' }
4128    end
4129    return true
4130end
4131
4132-- Coordinate system and matrix operators
4133--
4134-- +matrix +initmatrix +identmatrix +defaultmatrix +currentmatrix +setmatrix +translate
4135-- +scale +rotate +concat +concatmatrix +transform +dtransform +itransform +idtransform
4136-- +invertmatrix
4137
4138-- are these changed in place or not? if not then we can share
4139
4140function operators.matrix()
4141    local matrix = {
4142        {'real', 'unlimited', 'literal', 1},
4143        {'real', 'unlimited', 'literal', 0},
4144        {'real', 'unlimited', 'literal', 0},
4145        {'real', 'unlimited', 'literal', 1},
4146        {'real', 'unlimited', 'literal', 0},
4147        {'real', 'unlimited', 'literal', 0},
4148    }
4149    push_opstack { 'array', 'unlimited', 'literal', add_VM(matrix), 6, 6 }
4150    return true
4151end
4152
4153function operators.initmatrix()
4154    gsstate.matrix = { 1, 0, 0, 1, 0, 0 }
4155    return true
4156end
4157
4158function operators.identmatrix()
4159    local a = pop_opstack()
4160    if not a then return
4161        ps_error('stackunderflow')
4162    end
4163    if a[1] ~= 'array' then
4164        return ps_error('typecheck')
4165    end
4166    if a[6] < 6 then
4167        return ps_error('rangecheck')
4168    end
4169    local m = VM[a[4]] -- or can we replace the numbers
4170    m[1] = { 'real', 'unlimited', 'literal', 1 }
4171    m[2] = { 'real', 'unlimited', 'literal', 0 }
4172    m[3] = { 'real', 'unlimited', 'literal', 0 }
4173    m[4] = { 'real', 'unlimited', 'literal', 1 }
4174    m[5] = { 'real', 'unlimited', 'literal', 0 }
4175    m[6] = { 'real', 'unlimited', 'literal', 0 }
4176    a[5] = 6
4177    push_opstack(a)
4178    return true
4179end
4180
4181operators.defaultmatrix = operators.identmatrix
4182
4183function operators.currentmatrix()
4184    local a = pop_opstack()
4185    if not a then
4186        return ps_error('stackunderflow')
4187    end
4188    if a[1] ~= 'array' then
4189        return ps_error('typecheck')
4190    end
4191    if a[6] < 6 then
4192        return ps_error('rangecheck')
4193    end
4194    local thearray = get_VM(a[4])
4195    local matrix = gsstate.matrix
4196    for i=1,6 do
4197        thearray[i] = {'real', 'unlimited', 'literal', matrix[i]}
4198    end
4199    push_opstack { 'array', 'unlimited', 'literal', a[4], 6, 6 }
4200    return true
4201end
4202
4203function operators.setmatrix()
4204    local a = pop_opstack()
4205    if not a then
4206        return ps_error('stackunderflow')
4207    end
4208    if a[1] ~= 'array' then
4209        return ps_error('typecheck')
4210    end
4211    if a[6] ~= 6 then
4212        return ps_error('rangecheck')
4213    end
4214    local thearray = get_VM(a[4])
4215    local matrix   = gsstate.matrix
4216    for i=1,#thearray do
4217        local a = thearray[i]
4218        local ta, tv = a[1], a[4]
4219        if not (ta == 'real' or ta == 'integer') then
4220            return ps_error('typecheck')
4221        end
4222        if i > 6 then
4223            return ps_error('rangecheck')
4224        end
4225        matrix[i] = tv
4226    end
4227    return true
4228end
4229
4230local function do_transform(matrix,a,b)
4231    local x = matrix[1] * a + matrix[3] * b + matrix[5]
4232    local y = matrix[2] * a + matrix[4] * b + matrix[6]
4233    return x, y
4234end
4235
4236local function do_itransform(matrix,a,b)
4237    local m1 = matrix[1]
4238    local m4 = matrix[4]
4239    if m1 == 0 or m4 == 0 then
4240        return nil
4241    end
4242    local x = (a - matrix[5] - matrix[3] * b) / m1
4243    local y = (b - matrix[6] - matrix[2] * a) / m4
4244    return x, y
4245end
4246
4247local function do_concat (a,b)
4248    local a1, a2, a3, a4, a5, a6 = a[1], a[2], a[3], a[4], a[5], a[6]
4249    local b1, b2, b3, b4, b5, b6 = b[1], b[2], b[3], b[4], b[5], b[6]
4250    local c1 = a1 * b1 + a2 * b3
4251    local c2 = a1 * b2 + a2 * b4
4252    local c3 = a1 * b3 + a3 * b4
4253    local c4 = a3 * b2 + a4 * b4
4254    local c5 = a5 * b1 + a6 * b3 + b5
4255    local c6 = a5 * b2 + a6 * b4 + b6
4256    -- this is because double calculation introduces a small error
4257    return {
4258        abs(c1) < 1.0e-16 and 0 or c1,
4259        abs(c2) < 1.0e-16 and 0 or c2,
4260        abs(c3) < 1.0e-16 and 0 or c3,
4261        abs(c4) < 1.0e-16 and 0 or c4,
4262        abs(c5) < 1.0e-16 and 0 or c5,
4263        abs(c6) < 1.0e-16 and 0 or c6,
4264    }
4265end
4266
4267local function do_inverse (a)
4268    local a1, a2, a3, a4, a5, a6 = a[1], a[2], a[3], a[4], a[5], a[6]
4269    local det = a1 * a4 - a3 * a2
4270    if det == 0 then
4271        return nil
4272    end
4273    local c1 =  a4 / det
4274    local c3 = -a3 / det
4275    local c2 = -a2 / det
4276    local c4 =  a1 / det
4277    local c5 = (a3 * a6 - a5 * a4) / det
4278    local c6 = (a5 * a2 - a1 * a6) / det
4279    return {
4280        abs(c1) < 1.0e-16 and 0 or c1,
4281        abs(c2) < 1.0e-16 and 0 or c2,
4282        abs(c3) < 1.0e-16 and 0 or c3,
4283        abs(c4) < 1.0e-16 and 0 or c4,
4284        abs(c5) < 1.0e-16 and 0 or c5,
4285        abs(c6) < 1.0e-16 and 0 or c6,
4286    }
4287end
4288
4289function operators.translate()
4290    local a = pop_opstack()
4291    if not a then
4292        return ps_error('stackunderflow')
4293    end
4294    if a[1] == 'array' then
4295        if a[6] ~= 6 then
4296            return ps_error('typecheck')
4297        end
4298        local tf = a
4299        local a = pop_opstack()
4300        local b = pop_opstack()
4301        if not b then
4302            return ps_error('stackunderflow')
4303        end
4304        local ta, tb = a[1], b[1]
4305        if not (ta == 'real' or ta == 'integer') then
4306            return ps_error('typecheck')
4307        end
4308        if not (tb == 'real' or tb == 'integer') then
4309            return ps_error('typecheck')
4310        end
4311        local m   = VM[tf[4]]
4312        local old = { m[1][4], m[2][4], m[3][4], m[4][4], m[5][4], m[6][4] }
4313        local c   = do_concat(old,{1,0,0,1,b[4],a[4]})
4314        for i=1,6 do
4315            m[i] = { 'real', 'unlimited', 'literal', c[i] }
4316        end
4317        tf[5] = 6
4318        push_opstack(tf)
4319    else
4320        local b = pop_opstack()
4321        local ta = a[1]
4322        local tb = b[1]
4323        if not (ta == 'real' or ta == 'integer') then
4324            return ps_error('typecheck')
4325        end
4326        if not (tb == 'real' or tb == 'integer') then
4327            return ps_error('typecheck')
4328        end
4329        gsstate.matrix = do_concat(gsstate.matrix,{1,0,0,1,b[4],a[4]})
4330    end
4331    return true
4332end
4333
4334function operators.scale()
4335    local a = pop_opstack()
4336    if not a then
4337        return ps_error('stackunderflow')
4338    end
4339    local ta = a[1]
4340    if ta == 'array' then
4341        local tf = a
4342        if a[6] ~= 6 then
4343            return ps_error('typecheck')
4344        end
4345        local a = pop_opstack()
4346        local b = pop_opstack()
4347        if not b then
4348            return ps_error('stackunderflow')
4349        end
4350        local ta, tb = a[1], b[1]
4351        if not (ta == 'real' or ta == 'integer') then
4352            return ps_error('typecheck')
4353        end
4354        if not (tb == 'real' or tb == 'integer') then
4355            return ps_error('typecheck')
4356        end
4357        local v = VM[tf[4]]
4358        local c = do_concat (
4359            { v[1][4], v[2][4], v[3][4], v[4][4], v[5][4], v[6][4] },
4360            { b[4], 0, 0, a[4], 0, 0 }
4361        )
4362        for i=1,6 do
4363            v[i] = { 'real', 'unlimited', 'literal', c[i] }
4364        end
4365        tf[5] = 6
4366        push_opstack(tf)
4367    else
4368        local b = pop_opstack()
4369        if not b then
4370            return ps_error('stackunderflow')
4371        end
4372        local ta, tb = a[1], b[1]
4373        if not (ta == 'real' or ta == 'integer') then
4374            return ps_error('typecheck')
4375        end
4376        if not (tb == 'real' or tb == 'integer') then
4377            return ps_error('typecheck')
4378        end
4379        gsstate.matrix = do_concat(gsstate.matrix, { b[4], 0, 0, a[4], 0, 0 })
4380    end
4381    return true
4382end
4383
4384function operators.concat()
4385    local a = pop_opstack()
4386    if not a then
4387        return ps_error('stackunderflow')
4388    end
4389    if a[1] ~= "array" then
4390        return ps_error('typecheck')
4391    end
4392    if a[6] ~= 6 then
4393        return ps_error('typecheck')
4394    end
4395    local thearray = get_VM(a[4])
4396    local l = { }
4397    for i=1,#thearray do
4398        local v = thearray[i]
4399        local t = v[1]
4400        if not (t == 'real' or t == 'integer') then
4401            return ps_error('typecheck')
4402        end
4403        l[i] = v[4]
4404    end
4405    gsstate.matrix = do_concat(gsstate.matrix,l)
4406    return true
4407end
4408
4409function operators.concatmatrix()
4410    local tf = pop_opstack()
4411    local b  = pop_opstack()
4412    local a  = pop_opstack()
4413    if not a then
4414        return ps_error('stackunderflow')
4415    end
4416    if tf[1] ~= "array" then return ps_error('typecheck') end
4417    if b [1] ~= "array" then return ps_error('typecheck') end
4418    if a [1] ~= "array" then return ps_error('typecheck') end
4419    if tf[6] ~= 6       then return ps_error('typecheck') end
4420    if b [6] ~= 6       then return ps_error('typecheck') end
4421    if a [6] ~= 6       then return ps_error('typecheck') end
4422    local al = { }
4423    local thearray = get_VM(a[4])
4424    for i=1,#thearray do
4425        local v = thearray[i]
4426        local tv = v[1]
4427        if not (tv == 'real' or tv == 'integer') then
4428            return ps_error('typecheck')
4429        end
4430        al[i] = v[4]
4431    end
4432    local bl = { }
4433    local thearray = get_VM(b[4])
4434    for i=1,#thearray do
4435        local v = thearray[i]
4436        local tv = v[1]
4437        if not (tv == 'real' or tv == 'integer') then
4438            return ps_error('typecheck')
4439        end
4440        bl[i] = v[4]
4441    end
4442    local c = do_concat(al, bl)
4443    local m = VM[tf[4]]
4444    for i=1,6 do
4445        m[i] = { 'real', 'unlimited', 'literal', c[i] }
4446    end
4447    tf[5] = 6
4448    push_opstack(tf)
4449    return true
4450end
4451
4452function operators.rotate()
4453    local a = pop_opstack()
4454    if not a then
4455        return ps_error('stackunderflow')
4456    end
4457    local ta = a[1]
4458    if ta == 'array' then
4459        local tf
4460        if a[6] ~= 6 then
4461            return ps_error('typecheck')
4462        end
4463        tf = a
4464        a = pop_opstack()
4465        if not a then
4466            return ps_error('stackunderflow')
4467        end
4468        if not (a[1] == 'real' or a[1] == 'integer') then
4469            return ps_error('typecheck')
4470        end
4471        local m   = VM[tf[4]]
4472        local old = { m[1][4], m[2][4], m[3][4], m[4][4], m[5][4], m[6][4] }
4473        local av  = a[4]
4474        local c   = do_concat (old, {cos(rad(av)),sin(rad(av)),-sin(rad(av)),cos(rad(av)), 0, 0})
4475        for i=1,6 do
4476            m[i] = { 'real', 'unlimited', 'literal', c[i] }
4477        end
4478        push_opstack(tf)
4479    elseif ta == 'real' or ta == 'integer' then
4480        local av = a[4]
4481        gsstate.matrix = do_concat(gsstate.matrix,{cos(rad(av)),sin(rad(av)),-sin(rad(av)),cos(rad(av)),0,0})
4482    else
4483        return ps_error('typecheck')
4484    end
4485    return true
4486end
4487
4488function operators.transform()
4489    local a = pop_opstack()
4490    local b = pop_opstack()
4491    if not b then
4492        ps_error('stackunderflow')
4493    end
4494    local tf
4495    if a[1] == 'array' then
4496        if a[6] ~= 6 then
4497            return ps_error('typecheck')
4498        end
4499        local thearray = get_VM(a[4])
4500        tf = { }
4501        for i=1,#thearray do
4502            local v  = thearray[i]
4503            local v1 = v[1]
4504            if not (v1 == 'real' or v1 == 'integer') then
4505                return ps_error('typecheck')
4506            end
4507            tf[i] = v[4]
4508        end
4509        a = pop_opstack()
4510        if not a then
4511            return ps_error('stackunderflow')
4512        end
4513    else
4514        tf = gsstate.matrix
4515    end
4516    local a1 = a[1]
4517    local b1 = b[1]
4518    if not (a1 == 'real' or a1 == 'integer') then
4519        return ps_error('typecheck')
4520    end
4521    if not (b1 == 'real' or b1 == 'integer') then
4522        return ps_error('typecheck')
4523    end
4524    local x, y = do_transform(tf,b[4],a[4]);
4525    push_opstack { 'real', 'unlimited', 'literal', x }
4526    push_opstack { 'real', 'unlimited', 'literal', y }
4527    return true
4528end
4529
4530local function commontransform()
4531    local a = pop_opstack()
4532    if not a then
4533        return ps_error('stackunderflow')
4534    end
4535    local tf
4536    if a[1] == 'array' then
4537        if a[6] ~= 6 then
4538            return ps_error('typecheck')
4539        end
4540        tf = { }
4541        local thearray = get_VM(a[4])
4542        for i=1,#thearray do
4543            local v = thearray[i]
4544            local tv = v[1]
4545            if not (tv == 'real' or tv == 'integer') then
4546                return ps_error('typecheck')
4547            end
4548            tf[i] = v[4]
4549        end
4550        a = pop_opstack()
4551        if not a then
4552            return ps_error('stackunderflow')
4553        end
4554    else
4555        tf = gsstate.matrix
4556    end
4557    local b = pop_opstack()
4558    if not b then
4559        return ps_error('stackunderflow')
4560    end
4561    local ta = a[1]
4562    local tb = b[1]
4563    if not (ta == 'real' or ta == 'integer') then
4564        return ps_error('typecheck')
4565    end
4566    if not (tb == 'real' or tb == 'integer') then
4567        return ps_error('typecheck')
4568    end
4569    return true, tf, a, b
4570end
4571
4572function operators.dtransform()
4573    local ok, tf, a, b = commontransform()
4574    if ok then
4575        local x, y = do_transform({tf[1],tf[2],tf[3],tf[4],0,0},b[4],a[4])
4576        if not x then
4577            return ps_error('undefinedresult')
4578        end
4579        push_opstack { 'real', 'unlimited', 'literal', x }
4580        push_opstack { 'real', 'unlimited', 'literal', y }
4581        return true
4582    else
4583        return false, tf
4584    end
4585end
4586
4587function operators.itransform()
4588    local ok, tf, a, b = commontransform()
4589    if ok then
4590        local x, y = do_itransform(tf,b[4],a[4])
4591        if not x then
4592            return ps_error('undefinedresult')
4593        end
4594        push_opstack { 'real', 'unlimited', 'literal', x }
4595        push_opstack { 'real', 'unlimited', 'literal', y }
4596        return true
4597    else
4598        return false, tf
4599    end
4600end
4601
4602function operators.idtransform()
4603    local ok, tf, a, b = commontransform()
4604    if ok then
4605        local x,y = do_itransform({tf[1],tf[2],tf[3],tf[4],0,0},b[4],a[4]);
4606        if not x then
4607            return ps_error('undefinedresult')
4608        end
4609        push_opstack { 'real', 'unlimited', 'literal', x }
4610        push_opstack { 'real', 'unlimited', 'literal', y }
4611        return true
4612    else
4613        return false, tf
4614    end
4615end
4616
4617function operators.invertmatrix()
4618    local tf = pop_opstack()
4619    if not tf then
4620        return ps_error('stackunderflow')
4621    end
4622    if tf[1] ~= "array" then
4623        return ps_error('typecheck')
4624    end
4625    if tf[6] ~= 6 then
4626        return ps_error('typecheck')
4627    end
4628    local a = pop_opstack()
4629    if not a then
4630        return ps_error('stackunderflow')
4631    end
4632    if a[1] ~= "array" then
4633        return ps_error('typecheck')
4634    end
4635    if a[6] ~= 6 then
4636        return ps_error('typecheck')
4637    end
4638    local al = { }
4639    local thearray = get_VM(a[4])
4640    for i=1,#thearray do
4641        local v = thearray[i]
4642        local tv = v[1]
4643        if not (tv == 'real' or tv == 'integer') then
4644            return ps_error('typecheck')
4645        end
4646        al[i] = v[4]
4647    end
4648    local c = do_inverse(al)
4649    if not c then
4650        return ps_error('undefinedresult')
4651    end
4652    local m = VM[tf[4]]
4653    for i=1,6 do
4654        m[i] = { 'real', 'unlimited', 'literal', c[i] }
4655    end
4656    tf[5] = 6
4657    push_opstack(tf)
4658    return true
4659end
4660
4661-- Path construction operators
4662--
4663-- +newpath +currentpoint +moveto +rmoveto +lineto +rlineto +arc +arcn +arcto +curveto +rcurveto
4664-- +closepath +flattenpath -reversepath -strokepath -charpath +clippath -pathbbox -pathforall
4665-- +initclip *clip *eoclip
4666
4667function operators.newpath()
4668    gsstate.path     = { }
4669    gsstate.position = { }
4670    return true
4671end
4672
4673function operators.currentpoint()
4674    local position = gsstate.position
4675    if #position == 0 then
4676        return ps_error('nocurrentpoint')
4677    end
4678    local x, y = do_itransform(gsstate.matrix, position[1], position[2])
4679    if not x then
4680        return ps_error('undefinedresult')
4681    end
4682    push_opstack { 'real', 'unlimited', 'literal', x }
4683    push_opstack { 'real', 'unlimited', 'literal', y }
4684end
4685
4686function operators.moveto()
4687    local b = pop_opstack()
4688    local a = pop_opstack()
4689    if not a then
4690        return ps_error('stackunderflow')
4691    end
4692    local b1 = b[1]
4693    local a1 = a[1]
4694    if not (b1 == 'real' or b1 == 'integer') then
4695        return ps_error('typecheck')
4696    end
4697    if not (a1 == 'real' or a1 == 'integer') then
4698        return ps_error('typecheck')
4699    end
4700    local path    = gsstate.path
4701    local length  = #path
4702    local x, y = do_transform(gsstate.matrix, a[4], b[4])
4703    if length > 0 and path[length][1] == "moveto" then
4704        -- replace last moveto
4705    else
4706        length = length + 1
4707    end
4708    path[length] = { "moveto", x, y }
4709    gsstate.position = { x, y }
4710    return true
4711end
4712
4713function operators.rmoveto()
4714    local b = pop_opstack()
4715    local a = pop_opstack()
4716    if not a then
4717        return ps_error('stackunderflow')
4718    end
4719    local bt = b[1]
4720    local at = a[1]
4721    if not (bt == 'real' or bt == 'integer') then
4722        return ps_error('typecheck')
4723    end
4724    if not (at == 'real' or at == 'integer') then
4725        return ps_error('typecheck')
4726    end
4727    local position = gsstate.position
4728    local path     = gsstate.path
4729    local length   = #path
4730    if #position == 0 then
4731        return ps_error('nocurrentpoint')
4732    end
4733    local x, y = do_transform(gsstate.matrix, a[4], b[4])
4734    x = position[1] + x
4735    y = position[2] + y
4736    position[1] = x
4737    position[2] = y
4738    if length > 0 and path[length][1] == "moveto" then
4739        -- replace last moveto
4740    else
4741        length = length + 1
4742    end
4743    path[length] = { "moveto", x, y }
4744    return true
4745end
4746
4747function operators.lineto()
4748    local b = pop_opstack()
4749    local a = pop_opstack()
4750    if not a then
4751        return ps_error('stackunderflow')
4752    end
4753    local at = a[1]
4754    local bt = b[1]
4755    if not (bt == 'real' or bt == 'integer') then
4756        return ps_error('typecheck')
4757    end
4758    if not (at == 'real' or at == 'integer') then
4759        return ps_error('typecheck')
4760    end
4761    local position = gsstate.position
4762    local path     = gsstate.path
4763    local length   = #path
4764    if #position == 0 then
4765        return ps_error('nocurrentpoint')
4766    end
4767    local x, y = do_transform(gsstate.matrix, a[4], b[4])
4768    gsstate.position = { x, y }
4769    path[length+1] = { "lineto", x, y }
4770    return true
4771end
4772
4773function operators.rlineto()
4774    local b = pop_opstack()
4775    local a = pop_opstack()
4776    if not a then
4777        return ps_error('stackunderflow')
4778    end
4779    local at = a[1]
4780    local bt = b[1]
4781    if not (bt == 'real' or bt == 'integer') then
4782        return ps_error('typecheck')
4783    end
4784    if not (at == 'real' or at == 'integer') then
4785        return ps_error('typecheck')
4786    end
4787    local position = gsstate.position
4788    local path     = gsstate.path
4789    local length   = #path
4790    if #position == 0 then
4791        return ps_error('nocurrentpoint')
4792    end
4793    local x, y = do_transform(gsstate.matrix, a[4], b[4])
4794    x = position[1] + x
4795    y = position[2] + y
4796    position[1] = x
4797    position[2] = y
4798    path[length+1] = { "lineto", x, y }
4799    return true
4800end
4801
4802local function arc_to_curve (x, y, r, aa, theta)
4803    local th = rad(theta/2.0)
4804    local x0 = cos(th)
4805    local y0 = sin(th)
4806    local x1 = (4.0-x0)/3.0
4807    local y1 = ((1.0-x0)*(3.0-x0))/(3.0*y0)  -- y0 != 0...
4808    local x2 =  x1
4809    local y2 = -y1
4810 -- local x3 =  x0
4811 -- local y3 = -y0
4812
4813    local bezAng  = rad(aa) + th
4814    local cBezAng = cos(bezAng)
4815    local sBezAng = sin(bezAng)
4816
4817    local rx0 = (cBezAng * x0) - (sBezAng * y0)
4818    local ry0 = (sBezAng * x0) + (cBezAng * y0)
4819    local rx1 = (cBezAng * x1) - (sBezAng * y1)
4820    local ry1 = (sBezAng * x1) + (cBezAng * y1)
4821    local rx2 = (cBezAng * x2) - (sBezAng * y2)
4822    local ry2 = (sBezAng * x2) + (cBezAng * y2)
4823 -- local rx3 = (cBezAng * x3) - (sBezAng * y3)
4824 -- local ry3 = (sBezAng * x3) + (cBezAng * y3)
4825
4826    local px0 = x + r*rx0
4827    local py0 = y + r*ry0
4828    local px1 = x + r*rx1
4829    local py1 = y + r*ry1
4830    local px2 = x + r*rx2
4831    local py2 = y + r*ry2
4832 -- local px3 = x + r*rx3
4833 -- local py3 = y + r*ry3
4834
4835    return px2, py2, px1, py1, px0, py0 -- no px3, py3
4836end
4837
4838local function arc_start(x,y,r,aa)
4839    local x3 = 1
4840    local y3 = 0
4841    local bezAng  = rad(aa)
4842    local cBezAng = cos(bezAng)
4843    local sBezAng = sin(bezAng)
4844    local rx3 = (cBezAng * x3) - (sBezAng * y3)
4845    local ry3 = (sBezAng * x3) + (cBezAng * y3)
4846    local px3 = x + r*rx3
4847    local py3 = y + r*ry3
4848    return px3, py3
4849end
4850
4851local function do_arc(matrix,path,x,y,r,aa,ab)
4852    local endx, endy
4853    local segments = floor((ab-aa+44.999999999)/45)
4854    if segments == 0 then
4855        return do_transform(gsstate.matrix, x,y)
4856    end
4857    local theta = (ab-aa) / segments
4858    while segments>0 do
4859        local x1, y1, x2, y2, x3, y3  = arc_to_curve(x,y,r,aa,theta)
4860        local px2, py2 = do_transform(matrix,x2,y2)
4861        local px1, py1 = do_transform(matrix,x1,y1)
4862        endx, endy = do_transform(matrix, x3,y3)
4863        path[#path+1] = { "curveto", px1, py1, px2, py2, endx, endy }
4864        segments = segments - 1
4865        aa = aa + theta
4866    end
4867    return endx, endy
4868end
4869
4870local function do_arcn(matrix,path,x,y,r,aa,ab)
4871    local endx, endy
4872    local segments = floor((aa-ab+44.999999999)/45)
4873    if segments == 0 then
4874        return do_transform(matrix, x,y)
4875    end
4876    local theta = (aa-ab) / segments
4877    while segments > 0 do
4878        local x1, y1, x2, y2, x3, y3 = arc_to_curve(x,y,r,aa,-theta)
4879        local px1, py1 = do_transform(matrix,x1,y1)
4880        local px2, py2 = do_transform(matrix,x2,y2)
4881        endx, endy = do_transform(matrix,x3,y3)
4882        path[#path+1] = { "curveto", px1 , py1 , px2 , py2 , endx , endy  }
4883        segments = segments - 1
4884        aa = aa - theta
4885    end
4886    return endx, endy
4887end
4888
4889local function commonarc(action)
4890    local e = pop_opstack()
4891    local d = pop_opstack()
4892    local c = pop_opstack()
4893    local b = pop_opstack()
4894    local a = pop_opstack()
4895    if not a then
4896        return ps_error('stackunderflow')
4897    end
4898    local ta, tb, tc, td, te = a[1], b[1], c[1], d[1], e[1]
4899    if not (ta == 'real' or ta == 'integer') then return ps_error('typecheck') end
4900    if not (tb == 'real' or tb == 'integer') then return ps_error('typecheck') end
4901    if not (tc == 'real' or tc == 'integer') then return ps_error('typecheck') end
4902    if not (td == 'real' or td == 'integer') then return ps_error('typecheck') end
4903    if not (te == 'real' or te == 'integer') then return ps_error('typecheck') end
4904    local position = gsstate.position
4905    local path     = gsstate.path
4906    local matrix   = gsstate.matrix
4907    local vd = d[4]
4908    local ve = e[4]
4909    if vd < 0 or ve < 0 or vd > 360 or ve > 360 or (vd-ve) <= 0 then
4910        return ps_error('limitcheck')
4911    end
4912    local r = c[4]
4913    if r == 0 then
4914        ps_error('limitcheck')
4915    end
4916    local x = a[4]
4917    local y = b[4]
4918    local x0, y0 = arc_start(x,y,r,vd) -- find starting points
4919    local startx, starty = do_transform(matrix,x0,y0)
4920    path[#path+1] = { #position == 2 and "lineto" or "moveto", startx, starty }
4921    position[1], position[2] = action(matrix,path,x,y,r,vd,ve)
4922    return true
4923end
4924
4925function operators.arc()
4926    commonarc(do_arc)
4927end
4928
4929function operators.arcn()
4930    commonarc(do_arcn)
4931end
4932
4933local function vlength (a,b)
4934    return sqrt(a^2+b^2)
4935end
4936
4937local function vscal_ (a,b,c)
4938    return a*b, a*c
4939end
4940
4941-- this is of_the_way
4942
4943local function between (dist, pa, pb)
4944    local pa1, pa2 = pa[1], pa[2]
4945    local pb1, pb2 = pb[1], pb[2]
4946    return {
4947        pa1 + dist * (pb1 - pa1),
4948        pa2 + dist * (pb2 - pa2),
4949    }
4950end
4951
4952local function sign (a)
4953    return a < 0 and -1 or 1
4954end
4955
4956local function do_arcto(x,y,r) -- todo: check with original
4957    local h  = gsstate.position
4958    local tx1, tx2, ty1, ty2
4959    local c1, c2
4960    local x1, x2 = x[1], x[2]
4961    local y1, y2 = y[1], y[2]
4962    local h1, h2 = h[1], h[2]
4963    local ux, uy = x1 - h1, x2 - h2
4964    local vx, vy = y1 - x1, y2 - x2
4965    local lx, ly = vlength(ux,uy), vlength(vx,vy)
4966    local sx, sy = ux*vy - uy*vx, ux*vx + uy*vy
4967    if sx == 0 and sy == 0 then
4968        sx = r
4969        sy = 0
4970    else
4971        sx = r
4972        sy = atan2(sx,sy)
4973    end
4974    local a_arcto = sx*tan(abs(sy)/2)
4975    if sx*sy*lx*ly == 0 then
4976        tx1 = x1
4977        tx2 = x2
4978        ty1 = x1
4979        ty2 = x2
4980        c1  = x1
4981        c2  = x2
4982    else
4983        local tx = between(a_arcto/lx,x,h)
4984        local ty = between(a_arcto/ly,x,y)
4985        local cc, dd = vscal_(sign(sy)*sx/lx,-uy,ux)
4986        tx1 = tx[1]
4987        tx2 = tx[2]
4988        ty1 = ty[1]
4989        ty2 = ty[2]
4990        c1  = tx1 + cc
4991        c2  = tx2 + dd
4992    end
4993    -- now tx is the starting point, ty is the endpoint,
4994    -- c is the center of the circle. find the two angles
4995    local anga = deg(atan2(tx2-c2,tx1-c1)) -- todo, -90 is wrong
4996    local angb = deg(atan2(ty2-c2,ty1-c1)) -- todo, -90 is wrong
4997    return c1, c2, r, anga, angb, tx1, tx2, ty1, ty2
4998end
4999
5000function operators.arcto()
5001    local e = pop_opstack()
5002    local d = pop_opstack()
5003    local c = pop_opstack()
5004    local b = pop_opstack()
5005    local a = pop_opstack()
5006    if not a then
5007        return ps_error('stackunderflow')
5008    end
5009    local ta, tb, tc, td, te = a[1], b[2], c[1], d[1], e[1]
5010    if not (ta == 'real' or ta == 'integer') then
5011        return ps_error('typecheck')
5012    end
5013    if not (tb == 'real' or tb == 'integer') then
5014        return ps_error('typecheck')
5015    end
5016    if not (tc == 'real' or tc == 'integer') then
5017        return ps_error('typecheck')
5018    end
5019    if not (td == 'real' or td == 'integer') then
5020        return ps_error('typecheck')
5021    end
5022    if not (te == 'real' or te == 'integer') then
5023        return ps_error('typecheck')
5024    end
5025    local x1, y1, x2, y2, r = a[4], b[4], c[4], d[4], e[4]
5026    local position = gsstate.position
5027    local path     = gsstate.path
5028    if #position == 0 then
5029        return ps_error('nocurrentpoint')
5030    end
5031    local x, y, r, anga, angb, tx1, tx2, ty1, ty2 = do_arcto({x1,y1},{x2, y2},r)
5032    local vx, vy = do_transform(gsstate.matrix,tx1,tx2)
5033    path[#path+1] = { "lineto", vx, vy }
5034    if anga == angb then
5035        -- do nothing
5036    elseif anga > angb then
5037        position[1], position[2] = do_arcn(x,y,r,anga,angb)
5038    else
5039        position[1], position[2] = do_arc (x,y,r,anga,angb)
5040    end
5041    push_opstack { 'real', 'unlimited', 'literal', tx1 }
5042    push_opstack { 'real', 'unlimited', 'literal', tx2 }
5043    push_opstack { 'real', 'unlimited', 'literal', ty1 }
5044    push_opstack { 'real', 'unlimited', 'literal', ty2 }
5045end
5046
5047function operators.curveto()
5048    local f = pop_opstack()
5049    local e = pop_opstack()
5050    local d = pop_opstack()
5051    local c = pop_opstack()
5052    local b = pop_opstack()
5053    local a = pop_opstack()
5054    if not a then
5055        return ps_error('stackunderflow')
5056    end
5057    local f1 = f[1] if not (f1 == 'real' or f1 == 'integer') then return ps_error('typecheck') end
5058    local e1 = e[1] if not (e1 == 'real' or e1 == 'integer') then return ps_error('typecheck') end
5059    local d1 = d[1] if not (d1 == 'real' or d1 == 'integer') then return ps_error('typecheck') end
5060    local c1 = c[1] if not (c1 == 'real' or c1 == 'integer') then return ps_error('typecheck') end
5061    local b1 = b[1] if not (b1 == 'real' or b1 == 'integer') then return ps_error('typecheck') end
5062    local a1 = a[1] if not (a1 == 'real' or a1 == 'integer') then return ps_error('typecheck') end
5063    --
5064    if #gsstate.position == 0 then
5065        return ps_error('nocurrentpoint')
5066    end
5067    --
5068    local matrix = gsstate.matrix
5069    local x, y   = do_transform(matrix, e[4], f[4])
5070    local ax, ay = do_transform(matrix, a[4], b[4])
5071    local bx, by = do_transform(matrix, c[4], d[4])
5072    gsstate.position = { x, y }
5073    --
5074    local path = gsstate.path
5075    path[#path+1] = { "curveto", ax, ay, bx, by, x, y }
5076    return true
5077end
5078
5079function operators.rcurveto()
5080    local f = pop_opstack()
5081    local e = pop_opstack()
5082    local d = pop_opstack()
5083    local c = pop_opstack()
5084    local b = pop_opstack()
5085    local a = pop_opstack()
5086    if not a then
5087        return ps_error('stackunderflow')
5088    end
5089    local ft if not (ft == 'real' or ft == 'integer') then return ps_error('typecheck') end
5090    local et if not (et == 'real' or et == 'integer') then return ps_error('typecheck') end
5091    local dt if not (dt == 'real' or dt == 'integer') then return ps_error('typecheck') end
5092    local ct if not (ct == 'real' or ct == 'integer') then return ps_error('typecheck') end
5093    local bt if not (bt == 'real' or bt == 'integer') then return ps_error('typecheck') end
5094    local at if not (at == 'real' or at == 'integer') then return ps_error('typecheck') end
5095    local position = gsstate.position
5096    local path     = gsstate.path
5097    if #position == 0 then
5098        return ps_error('nocurrentpoint')
5099    end
5100    local matrix = gsstate.matrix
5101    local x,   y = do_transform(matrix, e[4], f[4])
5102    local ax, ay = do_transform(matrix, a[4], b[4])
5103    local bx, by = do_transform(matrix, c[4], d[4])
5104    local px = position[1] + x
5105    local py = position[2] + y
5106    path[#path+1] = {
5107        "curveto",
5108        position[1] + ax,
5109        position[2] + ay,
5110        position[1] + bx,
5111        position[2] + by,
5112        px,
5113        py
5114    }
5115    position[1] = px
5116    position[2] = py
5117    return true
5118end
5119
5120function operators.closepath()
5121    local path    = gsstate.path
5122    local length  = #path
5123    if length > 0 and path[length][1] ~= 'closepath' then
5124        local m = path[1]
5125        local a = m[2]
5126        local b = m[3]
5127        local x, y = do_transform(gsstate.matrix, a, b)
5128        gsstate.position = { x, y }
5129        path[length+1] = { "closepath", x, y }
5130    end
5131    return true
5132end
5133
5134-- finds a point on a bezier curve
5135-- P(x,y) = (1-t)^3*(x0,y0)+3*(1-t)^2*t*(x1,y1)+3*(1-t)*t^2*(x2,y2)+t^3*(x3,y3)
5136
5137local function bezier_at(t,x0,y0,x1,y1,x2,y2,x3,y3)
5138   local v = (1 - t)
5139   local x = (v^3)*x0 + 3*(v^2)*t*x1 + 3*v*(t^2)*x2 + (t^3)*x3
5140   local y = (v^3)*y0 + 3*(v^2)*t*y1 + 3*v*(t^2)*y2 + (t^3)*y3
5141   return x, y
5142end
5143
5144local delta = 10 -- 100
5145
5146local function good_enough (flatness,c,ct1,ct2,l)
5147    local c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y = c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8]
5148    local l0x, l0y, l1x, l1y = l[1], l[2], l[3], l[4]
5149    local t = 0
5150    while t < delta do
5151        local td = t/delta
5152        local bx, by = bezier_at(ct1+(ct2-ct1)*td,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y)
5153        local lx, ly = (1-td)*l0x + td*l1x, (1-td)*l0y + td*l1y
5154        local dist = vlength(bx-lx,by-ly)
5155        if dist > flatness then
5156            return false
5157        end
5158        t = t + 1
5159    end
5160    return true
5161end
5162
5163-- argument d is recursion depth, 10 levels should be enough to reach a conclusion
5164-- (and already generates over 1000 lineto's in the worst case)
5165
5166local function splitter (flatness,p,d,c,ct1,ct2,l)
5167    local c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y = c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8]
5168    d = d + 1
5169    local r = good_enough(flatness,c,ct1,ct1+ct2,l)
5170    if r or d > 10 then
5171        p[#p + 1] = { 'lineto', l[3], l[4] }
5172    else
5173        local ct22 = ct2/2
5174        local l2x, l2y = bezier_at(ct1+ct22,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y)
5175        local l1 = { l[1], l[2], l2x, l2y }
5176        local l2 = { l2x, l2y, l[3], l[4] }
5177        splitter(flatness,p,d,c,ct1,ct22,l1)
5178        splitter(flatness,p,d,c,ct1+ct22,ct22,l2)
5179    end
5180end
5181
5182local function flattencurve( homex, homey, curve, flatness)
5183    local p = { }
5184    local c6 = curve[6]
5185    local c7 = curve[7]
5186    local thecurve = { homex, homey, curve[2], curve[3], curve[4], curve[5], c6, c7 }
5187    local theline  = { homex, homey, c6, c7 }
5188    splitter(flatness, p, 0, thecurve, 0, 1, theline)
5189    return p
5190end
5191
5192local function do_flattenpath (p, flatness)
5193    local x, y
5194    local px = { }
5195    local nx = 0
5196    -- we don't care about differences less than a a permille of a point, ever
5197    if flatness < 0.001  then
5198        flatness = 0.001
5199    end
5200    if p then
5201        for i=1,#p do
5202            local v = p[i]
5203            local t = v[1]
5204            if t == "curveto" then
5205                local pxl = flattencurve(x,y,v,flatness)
5206                for i=1,#pxl do
5207                    nx = nx + 1 ; px[nx] = pxl[i]
5208                end
5209                x, y = v[6], v[7]
5210            elseif t == "lineto" or t == "moveto" then
5211                x, y = v[2], v[3]
5212                nx = nx + 1 ; px[nx] = v
5213            else
5214                nx = nx + 1 ; px[nx] = v
5215            end
5216        end
5217    end
5218    return px
5219end
5220
5221function operators.flattenpath()
5222    gsstate.path = do_flattenpath(gsstate.path,gsstate.flatness)
5223end
5224
5225function operators.clippath()
5226    gsstate.path = gsstate.clip
5227    return true
5228end
5229
5230function operators.initclip()
5231    device.initclip()
5232    return true
5233end
5234
5235function operators.eofill()
5236    local color    = gsstate.color
5237    local thecolor = color[color.type]
5238    if type(thecolor) == "table" then
5239        thecolor = { unpack(thecolor) }
5240    end
5241    currentpage[#currentpage+1] = {
5242        type      = 'eofill',
5243        path      = gsstate.path,
5244        colortype = color.type,
5245        color     = thecolor,
5246    }
5247    operators.newpath()
5248    return true
5249end
5250
5251-- todo: this only fixes the output, not the actual clipping path
5252-- in the gsstate !
5253
5254function operators.clip()
5255    currentpage[#currentpage+1] = {
5256        type = 'clip',
5257        path = gsstate.path,
5258    }
5259    return true
5260end
5261
5262-- todo: this only fixes the output, not the actual clipping path
5263-- in the gsstate !
5264
5265function operators.eoclip()
5266    currentpage[#currentpage+1] = {
5267        type = 'eoclip',
5268        path = gsstate.path,
5269    }
5270    return true
5271end
5272
5273-- Painting operators
5274--
5275-- +erasepage +fill +eofill +stroke -image -imagemask
5276
5277-- general graphics todo: transfer function, flatness
5278
5279function operators.erasepage()
5280    currentpage = { }
5281    return true
5282end
5283
5284function operators.stroke()
5285    local color       = gsstate.color
5286    local ctype       = color.type
5287    local thecolor    = color[ctype]
5288 -- if type(thecolor) == "table" then
5289 --     thecolor = { unpack(thecolor) }
5290 -- end
5291    currentpage[#currentpage+1] = {
5292        type        = 'stroke',
5293        path        = gsstate.path,
5294        colortype   = ctype,
5295        color       = thecolor,
5296        miterlimit  = gsstate.miterlimit,
5297        linewidth   = gsstate.linewidth,
5298        linecap     = gsstate.linecap,
5299        linejoin    = gsstate.linejoin,
5300     -- dashpattern = { unpack (gsstate.dashpattern) }, -- unpack? we don't manipulate
5301        dashpattern = gsstate.dashpattern,
5302        dashoffset  = gsstate.dashoffset
5303    }
5304    operators.newpath()
5305    return true
5306end
5307
5308function operators.fill()
5309    local color       = gsstate.color
5310    local ctype       = color.type
5311    local thecolor    = color[ctype]
5312 -- if type(thecolor) == "table" then
5313 --     thecolor = { unpack(thecolor) }
5314 -- end
5315    currentpage[#currentpage+1] = {
5316        type      = 'fill',
5317        path      = gsstate.path,
5318        colortype = ctype,
5319        color     = thecolor,
5320    }
5321    operators.newpath()
5322    return true
5323end
5324
5325-- Device setup and output operators
5326--
5327-- +showpage +copypage +banddevice +framedevice +nulldevice +renderbands
5328
5329-- will be replaced by the argument of 'new'
5330
5331-- this reports the bounding box of a page
5332
5333-- todo: linewidth for strokes
5334-- todo: clips
5335-- todo: strings (width&height)
5336
5337local calculatebox = false
5338
5339initializers[#initializers+1] = function()
5340    calculatebox = true
5341end
5342
5343local function boundingbox(page)
5344
5345    local bounding = specials.boundingbox
5346    if bounding and not calculatebox then
5347        return unpack(bounding)
5348    end
5349
5350    local minx, miny, maxx, maxy
5351    local startx, starty
5352    local linewidth
5353
5354    local function update_bbox (x,y)
5355        if not minx then
5356            minx = x
5357            miny = y
5358            maxx = x
5359            maxy = y
5360        end
5361        if linewidth then
5362            local xx = x + linewidth/2
5363            if xx > maxx then maxx = xx elseif xx < minx then minx = xx end
5364            local xx = x - linewidth/2
5365            if xx > maxx then maxx = xx elseif xx < minx then minx = xx end
5366            local yy = y + linewidth/2
5367            if yy > maxy then maxy = yy elseif yy < miny then miny = yy end
5368            local yy = y - linewidth/2
5369            if yy > maxy then maxy = yy elseif yy < miny then miny = yy end
5370        else
5371            if x > maxx then maxx = x elseif x < minx then minx = x end
5372            if y > maxy then maxy = y elseif y < miny then miny = y end
5373        end
5374        startx, starty = x, y
5375    end
5376
5377    for i=1,#page do
5378        local object = page[i]
5379        local p = do_flattenpath(object.path,0.5)
5380        linewidth = object.type == "stroke" and object.linewidth
5381        for i=1,#p do
5382            local segment = p[i]
5383            local type = segment[1]
5384            if type == "lineto" then
5385                if startx then
5386                    update_bbox(startx,starty)
5387                end
5388                update_bbox(segment[2],segment[3])
5389            elseif type == "curveto" then
5390                if startx then
5391                    update_bbox(startx,starty)
5392                end
5393                update_bbox(segment[6],segment[7])
5394            elseif type == "moveto" then
5395                startx, starty = segment[2], segment[3]
5396            end
5397        end
5398    end
5399    if minx then
5400        return minx, miny, maxx, maxy
5401    else
5402        return 0, 0, 0, 0
5403    end
5404end
5405
5406------------------------------------------------------------------
5407
5408local function boundingbox (page)
5409
5410    local bounding = specials.boundingbox
5411    if bounding and not calculatebox then
5412        return unpack(bounding)
5413    end
5414
5415    local minx, miny, maxx, maxy
5416    local startx, starty
5417    local linewidth
5418
5419    local function update_bbox (x,y)
5420        if not minx then
5421            minx = x
5422            miny = y
5423            maxx = x
5424            maxy = y
5425        end
5426        if linewidth then
5427            local xx = x + linewidth/2
5428            if xx > maxx then
5429                maxx = xx
5430            elseif xx < minx then
5431                minx = xx
5432            end
5433            local xx = x - linewidth/2
5434            if xx > maxx then
5435                maxx = xx
5436            elseif xx < minx then
5437                minx = xx
5438            end
5439            local yy = y + linewidth/2
5440            if yy > maxy then
5441                maxy = yy
5442            elseif yy < miny then
5443                miny = yy
5444            end
5445            local yy = y - linewidth/2
5446            if yy > maxy then
5447                maxy = yy
5448            elseif yy < miny then
5449                miny = yy
5450            end
5451        else
5452            if x > maxx then
5453                maxx = x
5454            elseif x < minx then
5455                minx = x
5456            end
5457            if y > maxy then
5458                maxy = y
5459            elseif y < miny then
5460                miny = y
5461            end
5462        end
5463        startx, starty = x, y
5464    end
5465
5466    local delta = 10 -- 100
5467
5468    local function good_enough (ct1,ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y)
5469        local t = 0
5470        while t < delta do
5471            local td = t/delta
5472            local bx, by = bezier_at(ct1+(ct2-ct1)*td,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y)
5473            local lx, ly = (1-td)*l0x + td*l1x, (1-td)*l0y + td*l1y
5474            local dist = sqrt((bx-lx)^2+(by-ly)^2) -- vlength(bx-lx,by-ly)
5475            if dist > 0.5 then
5476                return false
5477            end
5478            t = t + 1
5479        end
5480        return true
5481    end
5482
5483    local function splitter (d,ct1,ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y)
5484        d = d + 1
5485        local r = good_enough(ct1,ct1+ct2, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l1x, l1y)
5486        if r or d > 10 then
5487            if startx then
5488                update_bbox(l1x, l1y)
5489            end
5490        else
5491            local ct22 = ct2/2
5492            local l2x, l2y = bezier_at(ct1+ct22,c0x,c0y,c1x,c1y,c2x,c2y,c3x,c3y)
5493            splitter(d,ct1,     ct22, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l0x, l0y, l2x, l2y)
5494            splitter(d,ct1+ct22,ct22, c0x, c0y, c1x, c1y, c2x, c2y, c3x, c3y, l2x, l2y, l1x, l1y)
5495        end
5496    end
5497
5498    for i=1,#page do
5499        local object = page[i]
5500        local p = object.path
5501        if p then
5502            linewidth = object.type == "stroke" and object.linewidth
5503            for i=1,#p do
5504                local segment = p[i]
5505                local type = segment[1]
5506                if type == "lineto" then
5507                    if startx then
5508                        update_bbox(startx,starty)
5509                    end
5510                    update_bbox(segment[2],segment[3])
5511                elseif type == "curveto" then
5512                    local c6 = segment[6]
5513                    local c7 = segment[7]
5514                    splitter(0, 0, 1, startx, starty, segment[2], segment[3], segment[4], segment[5], c6, c7, startx, starty, c6, c7)
5515                elseif type == "moveto" then
5516                    startx, starty = segment[2], segment[3]
5517                end
5518            end
5519        end
5520    end
5521    if minx then
5522        return minx, miny, maxx, maxy
5523    else
5524        return 0, 0, 0, 0
5525    end
5526end
5527
5528------------------------------------------------------------------
5529
5530function operators.pathbbox()
5531    print("todo: pathbbox")
5532    push_opstack { "real", 'unlimited', 'literal', 0 }
5533    push_opstack { "real", 'unlimited', 'literal', 0 }
5534    push_opstack { "real", 'unlimited', 'literal', 1 }
5535    push_opstack { "real", 'unlimited', 'literal', 1 }
5536    return true
5537end
5538
5539------------------------------------------------------------------
5540
5541-- most time is spend in calculating the boundingbox
5542
5543-- NULL output
5544
5545devices.null = {
5546    initgraphics = function() gsstate.matrix = { 1, 0, 0, 1, 0, 0 } end,
5547    initclip     = function() gsstate.clip = { } end,
5548    showpage     = function() return "" end,
5549}
5550
5551-- PDF output
5552
5553local pdf = {
5554    initgraphics = function() gsstate.matrix = { 1, 0, 0, 1, 0, 0 } end,
5555    initclip     = function() gsstate.clip = { } end,
5556 -- startpage    = function(llc,lly,urx,ury) end,
5557 -- flushpage    = function() end,
5558 -- stoppage     = function() end,
5559}
5560
5561devices.pdf = pdf
5562
5563function pdf.showpage(page)
5564    --
5565    local startpage = pdf.startpage
5566    local stoppage  = pdf.stoppage
5567    local flushpage = pdf.flushpage
5568    local showfont  = pdf.showfont
5569    --
5570    if not flushpage then
5571        return
5572    end
5573    --
5574    if startpage then
5575        startpage(boundingbox(page))
5576    end
5577    --
5578    local t = { "q" }
5579    local n = 1
5580    local g_colortype   = "notacolor"
5581    local g_color       = ""
5582    local g_miterlimit  = -1
5583    local g_linejoin    = -1
5584    local g_linecap     = -1
5585    local g_linewidth   = -1
5586    local g_dashpattern = nil
5587    local g_dashoffset  = -1
5588    local flush = devices.pdf.flush
5589    for i=1,#page do
5590        local object = page[i]
5591        local path   = object.path
5592        local otyp   = object.type
5593        if otyp == "gsave" then
5594            n = n + 1 ; t[n] = "q"
5595         -- todo push / pop
5596g_colortype   = "notacolor"
5597g_color       = ""
5598g_miterlimit  = -1
5599g_linejoin    = -1
5600g_linecap     = -1
5601g_linewidth   = -1
5602g_dashpattern = nil
5603g_dashoffset  = -1
5604        elseif otyp == "grestore" then
5605g_colortype   = "notacolor"
5606g_color       = ""
5607g_miterlimit  = -1
5608g_linejoin    = -1
5609g_linecap     = -1
5610g_linewidth   = -1
5611g_dashpattern = nil
5612g_dashoffset  = -1
5613            n = n + 1 ; t[n] = "Q"
5614        else
5615            if otyp ~= "clip" and otyp ~= "eoclip" then
5616                local colortype = object.colortype
5617                local color     = object.color
5618                if colortype == "gray" then
5619                    local v = formatters["%f g %f G"](color,color)
5620                    if g_color ~= v then
5621                        g_colortype = "gray"
5622                        g_color     = v
5623                        n = n + 1 ; t[n] = v
5624                    end
5625                elseif colortype == "rgb" then
5626                    local r, g, b = color[1], color[2], color[3]
5627                    local v = formatters["%f %f %f rg %f %f %f RG"](r,g,b,r,g,b)
5628                    if g_color ~= v then
5629                        g_colortype = "rgb"
5630                        g_color     = v
5631                        n = n + 1 ; t[n] = v
5632                    end
5633                elseif colortype == "cmyk" then
5634                    local c, m, y, k = color[1], color[2], color[3], color[4]
5635                    local v = formatters["%f %f %f %f k %f %f %f %f K"](c,m,y,k,c,m,y,k)
5636                    if g_color ~= v then
5637                        g_colortype = "cmyk"
5638                        g_color     = v
5639                        n = n + 1 ; t[n] = v
5640                    end
5641                elseif colortype == "hsb" then
5642                    local r, g, b = hsv_to_rgb(color[1],color[2],color[3])
5643                    local v = formatters["%f %f %f rg %f %f %f RG"](r,g,b,r,g,b)
5644                    if g_color ~= v then
5645                        g_colortype = "rgb"
5646                        g_color     = v
5647                        n = n + 1 ; t[n] = v
5648                    end
5649                end
5650            end
5651            if otyp == "stroke" then
5652                local miterlimit = object.miterlimit
5653                if g_miterlimit ~= miterlimit then
5654                    g_miterlimit = miterlimit
5655                    n = n + 1 ; t[n] = formatters["%f M"](miterlimit)
5656                end
5657                local linejoin = object.linejoin
5658                if g_linejoin ~= linejoin then
5659                    g_linejoin = linejoin
5660                    n = n + 1 ; t[n] = formatters["%d j"](linejoin)
5661                end
5662                local linecap = object.linecap
5663                if g_linecap ~= linecap then
5664                    g_linecap = linecap
5665                    n = n + 1 ; t[n] = formatters["%d J"](linecap)
5666                end
5667                local linewidth = object.linewidth
5668                if g_linewidth ~= linewidth then
5669                    g_linewidth = linewidth
5670                    n = n + 1 ; t[n] = formatters["%f w"](linewidth)
5671                end
5672                local dashpattern = object.dashpattern
5673                local dashoffset  = object.dashoffset
5674                if g_dashpattern ~= dashpattern or g_dashoffset ~= dashoffset then
5675                    g_dashpattern = dashpattern
5676                    g_dashoffset  = dashoffset
5677                    local l = #dashpattern
5678                    if l == 0 then
5679                        n = n + 1 ; t[n] = "[] 0 d"
5680                    else
5681                        n = n + 1 ; t[n] = formatters["[% t] %d d"](dashpattern,dashoffset)
5682                    end
5683                end
5684            end
5685            if path then
5686                for i=1,#path do
5687                    local segment = path[i]
5688                    local styp    = segment[1]
5689                    if styp == "moveto" then
5690                        n = n + 1 ; t[n] = formatters["%f %f m"](segment[2],segment[3])
5691                    elseif styp == "lineto" then
5692                        n = n + 1 ; t[n] = formatters["%f %f l"](segment[2],segment[3])
5693                    elseif styp == "curveto" then
5694                        n = n + 1 ; t[n] = formatters["%f %f %f %f %f %f c"](segment[2],segment[3],segment[4],segment[5],segment[6],segment[7])
5695                    elseif styp == "closepath" then
5696                        n = n + 1 ; t[n] = "h"
5697                    else
5698                        report("unknown path segment type %a",styp)
5699                    end
5700                end
5701            end
5702            if otyp == "stroke" then
5703                n = n + 1 ; t[n] = "S"
5704            elseif otyp == "fill" then
5705                n = n + 1 ; t[n] = "f"
5706            elseif otyp == "eofill" then
5707                n = n + 1 ; t[n] = "f*"
5708            elseif otyp == "clip" then
5709                n = n + 1 ; t[n] = "W n"
5710            elseif otyp == "eoclip" then
5711                n = n + 1 ; t[n] = "W* n"
5712            elseif otyp == "show" then
5713                if showfont then
5714                    if n > 0 then
5715                        flushpage(concat(t,"\n"))
5716                        n = 0 ; t = { }
5717                    end
5718                    showfont(object)
5719                end
5720            else
5721                -- nothing to do
5722            end
5723        end
5724    end
5725    n = n + 1 ; t[n] = "Q"
5726    flushpage(concat(t,"\n"))
5727    --
5728    if startpage then
5729        stoppage()
5730    end
5731end
5732
5733function operators.showpage()
5734    local copies = lookup("#copies")
5735    if copies and copies[1] == 'integer' and copies[4] >= 1 then
5736        local amount = floor(copies[4])
5737        local render = device.showpage
5738        if render then
5739            for i=1,amount do
5740                render(currentpage)
5741            end
5742        end
5743    end
5744    operators.erasepage()
5745    operators.initgraphics()
5746    return true
5747end
5748
5749function operators.copypage()
5750    local render = device.showpage
5751    if render then
5752        render(currentpage)
5753    end
5754    return true
5755end
5756
5757function operators.banddevice()
5758    local d = pop_opstack()
5759    local c = pop_opstack()
5760    local b = pop_opstack()
5761    local a = pop_opstack()
5762    if not a then
5763        return ps_error('stackunderflow')
5764    end
5765    local ta, tb, tc, td = a[1], b[1], c[1], d[1]
5766    if not (ta == 'array' and a[5] == 6) then
5767        return ps_error('typecheck')
5768    end
5769    if not (td == 'array' and d[3] == 'executable') then
5770        return ps_error('typecheck')
5771    end
5772    if not (tb == 'real'  or tb == 'integer') then
5773        return ps_error('typecheck')
5774    end
5775    if not (tc == 'real'  or tc == 'integer') then
5776        return ps_error('typecheck')
5777    end
5778    local dev = device.banddevice
5779    if dev then
5780        dev(a,b,c,d)
5781    else
5782        return ps_error('undefined') -- fixed
5783    end
5784    return true
5785end
5786
5787function operators.renderbands()
5788    local a = pop_opstack()
5789    if not a then
5790        return ps_error('stackunderflow')
5791    end
5792    if not (a[1] == 'array' and a[3] == 'executable') then
5793        return ps_error('typecheck')
5794    end
5795    local dev = device.renderbands
5796    if dev then
5797        dev(d)
5798    else
5799        return ps_error('undefined')
5800    end
5801    return true
5802end
5803
5804function operators.framedevice()
5805    local d = pop_opstack()
5806    local c = pop_opstack()
5807    local b = pop_opstack()
5808    local a = pop_opstack()
5809    if not a then
5810        return ps_error('stackunderflow')
5811    end
5812    local ta, tb, tc, td = a[1], b[1], c[1], d[1]
5813    if not (ta == 'array' and a[5] == 6) then
5814        return ps_error('typecheck')
5815    end
5816    if not (tb == 'real' or tb == 'integer') then
5817        return ps_error('typecheck')
5818    end
5819    if not (tc == 'real' or tc == 'integer') then
5820        return ps_error('typecheck')
5821    end
5822    if not (td == 'array' and d[3] == 'executable') then
5823        return ps_error('typecheck')
5824    end
5825    local dev = device.framedevice
5826    if dev then
5827        dev(a,b,c,d)
5828    else
5829        return ps_error('undefined')
5830    end
5831    return true
5832end
5833
5834function operators.nulldevice()
5835    gsstate.device = "null"
5836    operators.initgraphics()
5837    return true
5838end
5839
5840-- Character and font operators
5841--
5842-- +definefont *findfont +scalefont +makefont +setfont +currentfont +show -ashow -widthshow
5843-- -awidthshow +kshow -stringwidth ^FontDirectory ^StandardEncoding
5844
5845-- Fonts are a bit special because it is needed to cooperate with the enclosing PDF document.
5846
5847local FontDirectory
5848
5849initializers[#initializers+1] = function(reset)
5850    if reset then
5851        FontDirectory = nil
5852    else
5853        FontDirectory = add_VM {
5854            access  = 'unlimited',
5855            size    = 0,
5856            maxsize = 5000,
5857            dict    = { },
5858        }
5859    end
5860end
5861
5862-- loading actual fonts is a worryingly slow exercise
5863
5864local fontmap
5865
5866initializers[#initializers+1] = function()
5867    if reset then
5868        fontmap = nil
5869    else
5870        fontmap = {
5871            ['Courier-Bold']          = 'NimbusMonL-Bold.ps',
5872            ['Courier-BoldOblique']   = 'NimbusMonL-BoldObli.ps',
5873            ['Courier']               = 'NimbusMonL-Regu.ps',
5874            ['Courier-Oblique']       = 'NimbusMonL-ReguObli.ps',
5875            ['Times-Bold']            = 'NimbusRomNo9L-Medi.ps',
5876            ['Times-BoldItalic']      = 'NimbusRomNo9L-MediItal.ps',
5877            ['Times-Roman']           = 'NimbusRomNo9L-Regu.ps',
5878            ['Times-Italic']          = 'NimbusRomNo9L-ReguItal.ps',
5879            ['Helvetica-Bold']        = 'NimbusSanL-Bold.ps',
5880            ['Helvetica-BoldOblique'] = 'NimbusSanL-BoldItal.ps',
5881            ['Helvetica']             = 'NimbusSanL-Regu.ps',
5882            ['Helvetica-Oblique']     = 'NimbusSanL-ReguItal.ps',
5883            ['Symbol']                = 'StandardSymL.ps',
5884        }
5885    end
5886end
5887
5888-- this can be overwritten by the user
5889
5890local function findfont(fontname)
5891    return fontmap[fontname]
5892end
5893
5894-- tests required keys in a font dict
5895
5896local function checkfont(f)
5897    -- FontMatrix
5898    local matrix = f['FontMatrix']
5899    if not matrix or matrix[1] ~= 'array' or matrix[5] ~= 6 then
5900        return false
5901    end
5902    local thearray = get_VM(matrix[4])
5903    for i=1,#thearray do
5904        local v = thearray[i]
5905        local tv = v[1]
5906        if not (tv == 'real' or tv == 'integer') then
5907            return false
5908        end
5909    end
5910    -- FontType
5911    local ftype = f['FontType']
5912    if not ftype or ftype[1] ~= 'integer' then
5913        return false
5914    end
5915    -- FontBBox
5916    local bbox = f['FontBBox']
5917    -- do not test [5] here, because it can be '1' (executable array)
5918    if not bbox or bbox[1] ~= 'array' or bbox[6] ~= 4 then
5919        return false
5920    end
5921    local thearray = get_VM(bbox[4])
5922    for i=1,#thearray do
5923        local v = thearray[i]
5924        local tv = v[1]
5925        if not (tv == 'real' or tv == 'integer') then
5926            return false
5927        end
5928    end
5929    -- Encoding
5930    local bbox = f['Encoding']
5931    if not bbox or bbox[1] ~= 'array' or bbox[5] ~= 256 then
5932        return false
5933    end
5934    local thearray = get_VM(bbox[4])
5935    for i=1,#thearray do
5936        local v = thearray[i]
5937        local tv = v[1]
5938        if tv[1] ~= 'name' then
5939            return false
5940        end
5941    end
5942    return true
5943end
5944
5945-- objects of type font as essentially the same as objects of type dict
5946
5947function operators.definefont()
5948    local b = pop_opstack()
5949    local a = pop_opstack()
5950    if not a then
5951        return ps_error('stackunderflow')
5952    end
5953    if b[1] ~= 'dict' then
5954        return ps_error('typecheck')
5955    end
5956    -- force keys to be names
5957    if a[1] ~= 'name' then
5958        return ps_error('typecheck')
5959    end
5960    local fontdict = get_VM(b[4])
5961    if not checkfont(fontdict.dict) then
5962        return ps_error('invalidfont')
5963    end
5964    -- check that a FID will fit
5965    if fontdict.size == fontdict.maxsize then
5966        return ps_error('invalidfont')
5967    end
5968    fontdict.dict['FID'] = {'font', 'executable', 'literal', b[4]}
5969    fontdict.size = fontdict.size + 1
5970    fontdict.access = 'read-only'
5971    local dict = get_VM(FontDirectory)
5972    local key  = get_VM(a[4])
5973    if not dict.dict[key] and dict.size == dict.maxsize then
5974        -- return ps_error('dictfull') -- level 1 only
5975    end
5976    if not dict.dict[key] then
5977        dict.size = dict.size + 1
5978    end
5979    dict.dict[key] = fontdict.dict['FID']
5980    push_opstack(b)
5981    return true
5982end
5983
5984function operators.findfont()
5985    local a = pop_opstack()
5986    if not a then
5987        return ps_error('stackunderflow')
5988    end
5989    if a[1] ~= 'name' then
5990        return ps_error('typecheck')
5991    end
5992    local fontdict = get_VM(FontDirectory)
5993    local key      = get_VM(a[4])
5994    local dict     = dict.dict
5995    if not dict[key] then
5996        fname = findfont(key)
5997        if not fname then
5998            return ps_error('invalidfont')
5999        end
6000        local oldfontkeys = { }
6001        for k, v in next, dict do
6002            oldfontkeys[i] = 1
6003        end
6004        report("loading font file %a",fname)
6005        local theopstack = opstackptr
6006        local run = formatters['/eexec {pop} def (%s) run'](fname)
6007        push_execstack { '.stopped', 'unlimited', 'literal', false }
6008        local curstack = execstackptr
6009        push_execstack { 'string', 'unlimited', 'executable', add_VM(run), 1, #run }
6010        while curstack < execstackptr do
6011            do_exec()
6012        end
6013        if execstack[execstackptr][1] == '.stopped' then
6014            pop_execstack()
6015        end
6016        opstackptr = theopstack
6017        local fkey, ftab
6018        for k, v in next, dict do
6019            if not oldfontkeys[k] then
6020                -- this is the new dict
6021                fkey = k
6022                ftab = v
6023                break
6024            end
6025        end
6026        if not fkey then
6027            return ps_error('invalidfont')
6028        end
6029        dict[key] = ftab -- set up the user requested name as well
6030    end
6031    push_opstack(dict[key])
6032    return true
6033end
6034
6035local function pushscaledcopy(fontdict,matrix)
6036    local olddict  = fontdict.dict
6037    if not checkfont(olddict) then
6038        return ps_error('invalidfont')
6039    end
6040    local newdict = { }
6041    local oldsize = fontdict.size
6042    local newfontdict = {
6043        dict    = newdict,
6044        access  = 'read-only',
6045        size    = oldsize,
6046        maxsize = oldsize,
6047    }
6048    for k, v in next, olddict do
6049        if k == "FontMatrix" then
6050            local oldmatrix = get_VM(v[4])
6051            local old = {
6052                oldmatrix[1][4],
6053                oldmatrix[2][4],
6054                oldmatrix[3][4],
6055                oldmatrix[4][4],
6056                oldmatrix[5][4],
6057                oldmatrix[6][4],
6058            }
6059            local c = do_concat(old,matrix)
6060            local new = {
6061                { 'real', 'unlimited', 'literal', c[1] },
6062                { 'real', 'unlimited', 'literal', c[2] },
6063                { 'real', 'unlimited', 'literal', c[3] },
6064                { 'real', 'unlimited', 'literal', c[4] },
6065                { 'real', 'unlimited', 'literal', c[5] },
6066                { 'real', 'unlimited', 'literal', c[6] }
6067             }
6068            newdict[k] = { 'array', 'unlimited', 'literal', add_VM(new), 6, 6 }
6069        elseif k == "FID" then
6070            -- updated later
6071        else
6072            newfontdict.dict[k] = v
6073        end
6074    end
6075    local f = add_VM(newfontdict)
6076    newdict['FID'] = { 'font', 'read-only', 'literal', f }
6077    push_opstack { 'font', 'read-only', 'literal', f } -- share ?
6078    return true
6079end
6080
6081function operators.scalefont()
6082    local s = pop_opstack()
6083    local b = pop_opstack()
6084    if not b then
6085        return ps_error('stackunderflow')
6086    end
6087    if b[1] ~= 'font' then
6088        return ps_error('typecheck')
6089    end
6090    if not (s[1] == 'integer' or s[1] == 'real') then
6091        return ps_error('typecheck')
6092    end
6093    local scals    = s[4]
6094    local matrix   = { scale, 0, 0, scale, 0, 0 }
6095    local fontdict = get_VM(b[4])
6096    return pushscaledcopy(fontdict,matrix)
6097end
6098
6099function operators.makefont()
6100    local s = pop_opstack()
6101    local b = pop_opstack()
6102    if not b then
6103        return ps_error('stackunderflow')
6104    end
6105    if b[1] ~= 'font' then
6106        return ps_error('typecheck')
6107    end
6108    if s[1] ~= 'array' then
6109        return ps_error('typecheck')
6110    end
6111    if s[6] ~= 6 then
6112        return ps_error('rangecheck')
6113    end
6114    local matrix = { }
6115    local array  = get_VM(s[4])
6116    for i=1,#array do
6117        local v = array[i]
6118        local tv = v[1]
6119        if not (tv == 'real' or tv == 'integer') then
6120            return ps_error('typecheck')
6121        end
6122        matrix[i] = v[4]
6123    end
6124    local fontdict = get_VM(b[4])
6125    pushscaledcopy(fontdict,matrix)
6126    return true
6127end
6128
6129function operators.setfont()
6130    local b = pop_opstack()
6131    if not b then
6132        return ps_error('stackunderflow')
6133    end
6134    if b[1] ~= 'font' then
6135        return ps_error('typecheck')
6136    end
6137    gsstate.font = b[4]
6138    return true
6139end
6140
6141-- todo: the invalidfont error is temporary. 'start' should set up at least one font in
6142-- FontDirectory and assing it as the current font
6143
6144function operators.currentfont()
6145    if not gsstate.font then
6146        return ps_error('invalidfont')
6147    end
6148    push_opstack {'font', 'read-only', 'literal', gsstate.font }
6149    return true
6150end
6151
6152function do_show(fontdict,s)
6153    local stringmatrix   = { }
6154    local truematrix     = { }
6155    local stringencoding = { }
6156    --
6157    local dict           = fontdict.dict
6158    local fontname       = get_VM(dict['FontName'][4])
6159    local fontmatrix     = get_VM(dict['FontMatrix'][4])
6160    local encoding       = get_VM(dict['Encoding'][4])
6161    local matrix         = gsstate.matrix
6162    local position       = gsstate.position
6163    local color          = gsstate.color
6164    local colortype      = color.type
6165    local colordata      = color[colortype]
6166    --
6167    if fontmatrix then
6168        for i=1,#fontmatrix do
6169            stringmatrix[i] = fontmatrix[i][4]
6170        end
6171    end
6172    if matrix then
6173        for i=1,#matrix do
6174            truematrix[i] = matrix[i]
6175        end
6176    end
6177    if encoding then
6178        for i=1,#m do
6179            stringencoding[i] = get_VM(e[i][4])
6180        end
6181    end
6182    if type(colordata) == "table" then
6183        colordata = { unpack(colordata) } -- copy
6184    end
6185    currentpage[#currentpage+1] = {
6186      type       = 'show',
6187      string     = s,
6188      fontname   = fontname,
6189      adjust     = nil,
6190      x          = position[1],
6191      y          = position[2],
6192      encoding   = stringencoding,
6193      fontmatrix = stringmatrix,
6194      matrix     = truematrix,
6195      colortype  = colortype,
6196      color      = colordata,
6197   }
6198   -- todo: update currentpoint, needing 'stringwidth'
6199end
6200
6201function operators.show()
6202    local s = pop_opstack()
6203    if not s then
6204        return ps_error('stackunderflow')
6205    end
6206    if s[1] ~= 'string' then
6207        return ps_error('typecheck')
6208    end
6209    if #gsstate.position == 0 then
6210        return ps_error('nocurrentpoint')
6211    end
6212    if not gsstate.font then
6213        return ps_error('invalidfont')
6214    end
6215    local fontdict = get_VM(gsstate.font)
6216    if fontdict.access == "noaccess" then
6217        return ps_error('invalidaccess')
6218    end
6219    if not checkfont(fontdict.dict) then
6220        return ps_error('invalidfont')
6221    end
6222    do_show(fontdict,get_VM(s[4]))
6223end
6224
6225
6226function operators.kshow()
6227    local a = pop_opstack()
6228    local b = pop_opstack()
6229    if not a then
6230        return ps_error('stackunderflow')
6231    end
6232    if b[1] ~= "array" and b[3] == 'executable' then
6233        return ps_error('typecheck')
6234    end
6235    if b[2] == 'noaccess' then
6236        return ps_error('invalidaccess')
6237    end
6238    if not a[1] == 'string' then
6239        return ps_error('typecheck')
6240    end
6241    if a[2] == "execute-only" or a[2] == 'noaccess' then
6242        return ps_error('invalidaccess')
6243    end
6244    local fontdict = get_VM(gsstate.font)
6245    if fontdict.access == "noaccess" then
6246        return ps_error('invalidaccess')
6247    end
6248    if #gsstate.position == 0 then
6249        return ps_error('nocurrentpoint')
6250    end
6251    -- ok, that were the errors
6252    push_execstack { '.exit', 'unlimited', 'literal', false }
6253    local curstack = execstackptr
6254    if a[6] == 0 then
6255        return true
6256    end
6257    b[7] = 'i'
6258    local thestring = get_VM(a[4])
6259    local v = sub(thestring,1,1)
6260    thestring = sub(thestring,2,-1)
6261    do_show(fontdict,v)
6262    for w in gmatch(thestring,".") do
6263        if stopped then
6264            stopped = false
6265            return false
6266        end
6267        push_opstack { 'integer', 'unlimited', 'literal', byte(v) }
6268        push_opstack { 'integer', 'unlimited', 'literal', byte(w) }
6269        b[5] = 1
6270        push_execstack(b)
6271        while curstack < execstackptr do
6272            do_exec()
6273        end
6274        local entry = execstack[execstackptr]
6275        if entry[1] == '.exit' and entry[4] == true then
6276            pop_execstack()
6277            return true
6278        end
6279        do_show(fontdict,w)
6280        v = w
6281    end
6282    return true
6283end
6284
6285local the_standardencoding = {
6286    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6287    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6288    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6289    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6290    '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand',
6291    'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma',
6292    'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
6293    'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less',
6294    'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F',
6295    'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
6296    'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash',
6297    'bracketright', 'asciicircum', 'underscore', 'quoteleft', 'a', 'b',
6298    'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
6299    'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar',
6300    'braceright', 'asciitilde', '.notdef', '.notdef', '.notdef',
6301    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6302    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6303    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6304    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6305    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6306    '.notdef', 'exclamdown', 'cent', 'sterling', 'fraction', 'yen',
6307    'florin', 'section', 'currency', 'quotesingle', 'quotedblleft',
6308    'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl',
6309    '.notdef', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '.notdef',
6310    'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase',
6311    'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', '.notdef',
6312    'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron',
6313    'breve', 'dotaccent', 'dieresis', '.notdef', 'ring', 'cedilla',
6314    '.notdef', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '.notdef',
6315    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6316    '.notdef', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6317    '.notdef', '.notdef', '.notdef', 'AE', '.notdef', 'ordfeminine',
6318    '.notdef', '.notdef', '.notdef', '.notdef', 'Lslash', 'Oslash', 'OE',
6319    'ordmasculine', '.notdef', '.notdef', '.notdef', '.notdef', '.notdef',
6320    'ae', '.notdef', '.notdef', '.notdef', 'dotlessi', '.notdef',
6321    '.notdef', 'lslash', 'oslash', 'oe', 'germandbls', '.notdef',
6322    '.notdef', '.notdef', '.notdef'
6323}
6324
6325local function standardencoding()
6326    local a = { }
6327    for i=1,#the_standardencoding do
6328        a[i] = { 'name', 'unlimited', 'literal', add_VM(the_standardencoding[i]) }
6329    end
6330    return a
6331end
6332
6333-- Font cache operators
6334--
6335-- -cachestatus -setcachedevice -setcharwidth -setcachelimit
6336
6337-- userdict (initially empty)
6338
6339local systemdict
6340local userdict
6341
6342initializers[#initializers+1] = function(reset)
6343    if reset then
6344        systemdict = nil
6345    else
6346        dictstackptr = dictstackptr + 1
6347        dictstack[dictstackptr] = add_VM {
6348            access  = 'unlimited',
6349            maxsize = MAX_INTEGER,
6350            size    = 0,
6351            dict    = { },
6352        }
6353        if directvm then
6354            systemdict = dictstack[dictstackptr]
6355        else
6356            systemdict = dictstackptr
6357        end
6358    end
6359end
6360
6361initializers[#initializers+1] = function(reset)
6362    if reset then
6363        userdict = nil
6364    else
6365        dictstackptr = dictstackptr + 1
6366        dictstack[dictstackptr] = add_VM {
6367            access  = 'unlimited',
6368            maxsize = MAX_INTEGER,
6369            size    = 0,
6370            dict    = { },
6371        }
6372        if directvm then
6373            userdict = dictstack[dictstackptr]
6374        else
6375            userdict = dictstackptr
6376        end
6377    end
6378end
6379
6380initializers[#initializers+1] = function(reset)
6381    if reset then
6382        -- already done
6383    else
6384        local dict = {
6385            ['$error']            = { 'dict',     'unlimited', 'literal',    dicterror },
6386            ['[']                 = { 'operator', 'unlimited', 'executable', operators.beginarray, '[' },
6387            [']']                 = { 'operator', 'unlimited', 'executable', operators.endarray, ']' },
6388         -- ['=']                 = { 'operator', 'unlimited', 'executable', operators.EQ, '=' },
6389            ['==']                = { 'operator', 'unlimited', 'executable', operators.equal, '==' },
6390            ['abs']               = { 'operator', 'unlimited', 'executable', operators.abs, 'abs' },
6391            ['add']               = { 'operator', 'unlimited', 'executable', operators.add, 'add' },
6392            ['aload']             = { 'operator', 'unlimited', 'executable', operators.aload, 'aload' },
6393            ['anchorsearch']      = { 'operator', 'unlimited', 'executable', operators.anchorsearch, 'anchorsearch' },
6394            ['and']               = { 'operator', 'unlimited', 'executable', operators["and"], 'and' },
6395            ['arc']               = { 'operator', 'unlimited', 'executable', operators.arc, 'arc' },
6396            ['arcn']              = { 'operator', 'unlimited', 'executable', operators.arcn, 'arcn' },
6397            ['arcto']             = { 'operator', 'unlimited', 'executable', operators.arcto, 'arcto' },
6398            ['array']             = { 'operator', 'unlimited', 'executable', operators.array, 'array' },
6399            ['astore']            = { 'operator', 'unlimited', 'executable', operators.astore, 'astore' },
6400            ['atan']              = { 'operator', 'unlimited', 'executable', operators.atan, 'atan' },
6401            ['banddevice']        = { 'operator', 'unlimited', 'executable', operators.banddevice, 'banddevice' },
6402            ['bind']              = { 'operator', 'unlimited', 'executable', operators.bind, 'bind' },
6403            ['bitshift']          = { 'operator', 'unlimited', 'executable', operators.bitshift, 'bitshift' },
6404            ['begin']             = { 'operator', 'unlimited', 'executable', operators.begin, 'begin' },
6405            ['bytesavailable']    = { 'operator', 'unlimited', 'executable', operators.bytesavailable, 'bytesavailable' },
6406            ['ceiling']           = { 'operator', 'unlimited', 'executable', operators.ceiling, 'ceiling' },
6407            ['clear']             = { 'operator', 'unlimited', 'executable', operators.clear, 'clear' },
6408            ['cleartomark']       = { 'operator', 'unlimited', 'executable', operators.cleartomark, 'cleartomark' },
6409            ['clip']              = { 'operator', 'unlimited', 'executable', operators.clip, 'clip' },
6410            ['clippath']          = { 'operator', 'unlimited', 'executable', operators.clippath, 'clippath' },
6411            ['pathbbox']          = { 'operator', 'unlimited', 'executable', operators.pathbbox, 'pathbbox' },
6412            ['closefile']         = { 'operator', 'unlimited', 'executable', operators.closefile, 'closefile' },
6413            ['closepath']         = { 'operator', 'unlimited', 'executable', operators.closepath, 'closepath' },
6414            ['concat']            = { 'operator', 'unlimited', 'executable', operators.concat, 'concat' },
6415            ['concatmatrix']      = { 'operator', 'unlimited', 'executable', operators.concatmatrix, 'concatmatrix' },
6416            ['copy']              = { 'operator', 'unlimited', 'executable', operators.copy, 'copy' },
6417            ['copypage']          = { 'operator', 'unlimited', 'executable', operators.copypage, 'copypage' },
6418            ['cos']               = { 'operator', 'unlimited', 'executable', operators.cos, 'cos' },
6419            ['count']             = { 'operator', 'unlimited', 'executable', operators.count, 'count' },
6420            ['countdictstack']    = { 'operator', 'unlimited', 'executable', operators.countdictstack, 'countdictstack' },
6421            ['countexecstack']    = { 'operator', 'unlimited', 'executable', operators.countexecstack, 'countexecstack' },
6422            ['counttomark']       = { 'operator', 'unlimited', 'executable', operators.counttomark, 'counttomark' },
6423            ['currentdash']       = { 'operator', 'unlimited', 'executable', operators.currentdash, 'currentdash' },
6424            ['currentdict']       = { 'operator', 'unlimited', 'executable', operators.currentdict, 'currentdict' },
6425            ['currentfile']       = { 'operator', 'unlimited', 'executable', operators.currentfile, 'currentfile' },
6426            ['currentflat']       = { 'operator', 'unlimited', 'executable', operators.currentflat, 'currentflat' },
6427            ['currentfont']       = { 'operator', 'unlimited', 'executable', operators.currentfont, 'currentfont' },
6428            ['currentgray']       = { 'operator', 'unlimited', 'executable', operators.currentgray, 'currentgray' },
6429            ['currenthsbcolor']   = { 'operator', 'unlimited', 'executable', operators.currenthsbcolor, 'currenthsbcolor' },
6430            ['currentlinecap']    = { 'operator', 'unlimited', 'executable', operators.currentlinecap, 'currentlinecap' },
6431            ['currentlinejoin']   = { 'operator', 'unlimited', 'executable', operators.currentlinejoin, 'currentlinejoin' },
6432            ['currentlinewidth']  = { 'operator', 'unlimited', 'executable', operators.currentlinewidth, 'currentlinewidth' },
6433            ['currentmatrix']     = { 'operator', 'unlimited', 'executable', operators.currentmatrix,  'currentmatrix' },
6434            ['currentmiterlimit'] = { 'operator', 'unlimited', 'executable', operators.currentmiterlimit,  'currentmiterlimit' },
6435            ['currentpoint']      = { 'operator', 'unlimited', 'executable', operators.currentpoint, 'currentpoint' },
6436            ['currentrgbcolor']   = { 'operator', 'unlimited', 'executable', operators.currentrgbcolor, 'currentrgbcolor' },
6437            ['currentcmykcolor']  = { 'operator', 'unlimited', 'executable', operators.currentcmykcolor, 'currentcmykcolor' },
6438            ['currentscreen']     = { 'operator', 'unlimited', 'executable', operators.currentscreen, 'currentscreen' },
6439            ['currenttransfer']   = { 'operator', 'unlimited', 'executable', operators.currenttransfer, 'currenttransfer' },
6440            ['curveto']           = { 'operator', 'unlimited', 'executable', operators.curveto, 'curveto' },
6441            ['cvi']               = { 'operator', 'unlimited', 'executable', operators.cvi, 'cvi' },
6442            ['cvlit']             = { 'operator', 'unlimited', 'executable', operators.cvlit, 'cvlit' },
6443            ['cvn']               = { 'operator', 'unlimited', 'executable', operators.cvn, 'cvn' },
6444            ['cvr']               = { 'operator', 'unlimited', 'executable', operators.cvr, 'cvr' },
6445            ['cvrs']              = { 'operator', 'unlimited', 'executable', operators.cvrs, 'cvrs' },
6446            ['cvs']               = { 'operator', 'unlimited', 'executable', operators.cvs, 'cvs' },
6447            ['cvx']               = { 'operator', 'unlimited', 'executable', operators.cvx, 'cvx' },
6448            ['def']               = { 'operator', 'unlimited', 'executable', operators.def, 'def' },
6449            ['definefont']        = { 'operator', 'unlimited', 'executable', operators.definefont, 'definefont' },
6450            ['dict']              = { 'operator', 'unlimited', 'executable', operators.dict, 'dict' },
6451            ['dictstack']         = { 'operator', 'unlimited', 'executable', operators.dictstack, 'dictstack' },
6452            ['div']               = { 'operator', 'unlimited', 'executable', operators.div, 'div' },
6453            ['dtransform']        = { 'operator', 'unlimited', 'executable', operators.dtransform, 'dtransform' },
6454            ['dup']               = { 'operator', 'unlimited', 'executable', operators.dup, 'dup' },
6455            ['echo']              = { 'operator', 'unlimited', 'executable', operators.echo, 'echo' },
6456            ['end']               = { 'operator', 'unlimited', 'executable', operators["end"], 'end' },
6457            ['eoclip']            = { 'operator', 'unlimited', 'executable', operators.eoclip, 'eoclip' },
6458            ['eofill']            = { 'operator', 'unlimited', 'executable', operators.eofill, 'eofill' },
6459            ['eq']                = { 'operator', 'unlimited', 'executable', operators.eq, 'eq' },
6460            ['errordict']         = { 'dict',     'unlimited', 'literal',    errordict },
6461            ['exch']              = { 'operator', 'unlimited', 'executable', operators.exch, 'exch' },
6462            ['exec']              = { 'operator', 'unlimited', 'executable', operators.exec, 'exec' },
6463            ['execstack']         = { 'operator', 'unlimited', 'executable', operators.execstack, 'execstack' },
6464            ['executeonly']       = { 'operator', 'unlimited', 'executable', operators.executeonly, 'executeonly' },
6465            ['exit']              = { 'operator', 'unlimited', 'executable', operators.exit, 'exit' },
6466            ['exp']               = { 'operator', 'unlimited', 'executable', operators.exp, 'exp' },
6467            ['false']             = { 'boolean',  'unlimited', 'literal',    false },
6468            ['file']              = { 'operator', 'unlimited', 'executable', operators.file, 'file' },
6469            ['fill']              = { 'operator', 'unlimited', 'executable', operators.fill, 'fill' },
6470            ['findfont']          = { 'operator', 'unlimited', 'executable', operators.findfont, 'findfont' },
6471            ['FontDirectory']     = { 'dict',     'unlimited', 'literal',    escrito['FontDirectory'] },
6472            ['flattenpath']       = { 'operator', 'unlimited', 'executable', operators.flattenpath, 'flattenpath' },
6473            ['floor']             = { 'operator', 'unlimited', 'executable', operators.floor, 'floor' },
6474            ['flush']             = { 'operator', 'unlimited', 'executable', operators.flush, 'flush' },
6475            ['flushfile']         = { 'operator', 'unlimited', 'executable', operators.flushfile, 'flushfile' },
6476            ['for']               = { 'operator', 'unlimited', 'executable', operators["for"], 'for' },
6477            ['forall']            = { 'operator', 'unlimited', 'executable', operators.forall, 'forall' },
6478            ['framedevice']       = { 'operator', 'unlimited', 'executable', operators.framedevice, 'framedevice' },
6479            ['ge']                = { 'operator', 'unlimited', 'executable', operators.ge, 'ge' },
6480            ['get']               = { 'operator', 'unlimited', 'executable', operators.get, 'get' },
6481            ['getinterval']       = { 'operator', 'unlimited', 'executable', operators.getinterval, 'getinterval' },
6482            ['grestore']          = { 'operator', 'unlimited', 'executable', operators.grestore, 'grestore' },
6483            ['grestoreall']       = { 'operator', 'unlimited', 'executable', operators.grestoreall, 'grestoreall' },
6484            ['gsave']             = { 'operator', 'unlimited', 'executable', operators.gsave, 'gsave' },
6485            ['gt']                = { 'operator', 'unlimited', 'executable', operators.gt, 'gt' },
6486            ['identmatrix']       = { 'operator', 'unlimited', 'executable', operators.identmatrix, 'identmatrix' },
6487            ['idiv']              = { 'operator', 'unlimited', 'executable', operators.idiv, 'idiv' },
6488            ['if']                = { 'operator', 'unlimited', 'executable', operators["if"], 'if' },
6489            ['ifelse']            = { 'operator', 'unlimited', 'executable', operators.ifelse, 'ifelse' },
6490            ['index']             = { 'operator', 'unlimited', 'executable', operators.index, 'index' },
6491            ['initclip']          = { 'operator', 'unlimited', 'executable', operators.initclip, 'initclip' },
6492            ['initgraphics']      = { 'operator', 'unlimited', 'executable', operators.initgraphics, 'initgraphics' },
6493            ['initmatrix']        = { 'operator', 'unlimited', 'executable', operators.initmatrix, 'initmatrix' },
6494            ['invertmatrix']      = { 'operator', 'unlimited', 'executable', operators.invertmatrix, 'invertmatrix' },
6495            ['idtransform']       = { 'operator', 'unlimited', 'executable', operators.idtransform, 'idtransform' },
6496            ['itransform']        = { 'operator', 'unlimited', 'executable', operators.itransform, 'itransform' },
6497            ['known']             = { 'operator', 'unlimited', 'executable', operators.known, 'known' },
6498            ['kshow']             = { 'operator', 'unlimited', 'executable', operators.kshow, 'kshow' },
6499            ['le']                = { 'operator', 'unlimited', 'executable', operators.le, 'le' },
6500            ['length']            = { 'operator', 'unlimited', 'executable', operators.length, 'length' },
6501            ['lineto']            = { 'operator', 'unlimited', 'executable', operators.lineto, 'lineto' },
6502            ['ln']                = { 'operator', 'unlimited', 'executable', operators.ln, 'ln' },
6503            ['load']              = { 'operator', 'unlimited', 'executable', operators.load, 'load' },
6504            ['log']               = { 'operator', 'unlimited', 'executable', operators.log, 'log' },
6505            ['loop']              = { 'operator', 'unlimited', 'executable', operators.loop, 'loop' },
6506            ['lt']                = { 'operator', 'unlimited', 'executable', operators.lt, 'lt' },
6507            ['makefont']          = { 'operator', 'unlimited', 'executable', operators.makefont, 'makefont' },
6508            ['mark']              = { 'operator', 'unlimited', 'executable', operators.mark, 'mark' },
6509            ['matrix']            = { 'operator', 'unlimited', 'executable', operators.matrix, 'matrix' },
6510            ['maxlength']         = { 'operator', 'unlimited', 'executable', operators.maxlength, 'maxlength' },
6511            ['mod']               = { 'operator', 'unlimited', 'executable', operators.mod, 'mod' },
6512            ['moveto']            = { 'operator', 'unlimited', 'executable', operators.moveto, 'moveto' },
6513            ['mul']               = { 'operator', 'unlimited', 'executable', operators.mul, 'mul' },
6514            ['ne']                = { 'operator', 'unlimited', 'executable', operators.ne, 'ne' },
6515            ['neg']               = { 'operator', 'unlimited', 'executable', operators.neg, 'neg' },
6516            ['newpath']           = { 'operator', 'unlimited', 'executable', operators.newpath, 'newpath' },
6517            ['noaccess']          = { 'operator', 'unlimited', 'executable', operators.noaccess, 'noaccess' },
6518            ['not']               = { 'operator', 'unlimited', 'executable', operators["not"], 'not' },
6519            ['null']              = { 'operator', 'unlimited', 'executable', operators.null, 'null' },
6520            ['or']                = { 'operator', 'unlimited', 'executable', operators["or"], 'or' },
6521            ['pop']               = { 'operator', 'unlimited', 'executable', operators.pop, 'pop' },
6522            ['print']             = { 'operator', 'unlimited', 'executable', operators.print, 'print' },
6523            ['pstack']            = { 'operator', 'unlimited', 'executable', operators.pstack, 'pstack' },
6524            ['put']               = { 'operator', 'unlimited', 'executable', operators.put, 'put' },
6525            ['putinterval']       = { 'operator', 'unlimited', 'executable', operators.putinterval, 'putinterval' },
6526            ['quit']              = { 'operator', 'unlimited', 'executable', operators.quit, 'quit' },
6527            ['rand']              = { 'operator', 'unlimited', 'executable', operators.rand, 'rand' },
6528            ['rcheck']            = { 'operator', 'unlimited', 'executable', operators.rcheck, 'rcheck' },
6529            ['rcurveto']          = { 'operator', 'unlimited', 'executable', operators.rcurveto, 'rcurveto' },
6530            ['read']              = { 'operator', 'unlimited', 'executable', operators.read, 'read' },
6531            ['readhexstring']     = { 'operator', 'unlimited', 'executable', operators.readhexstring, 'readhexstring' },
6532            ['readline']          = { 'operator', 'unlimited', 'executable', operators.readline, 'readline' },
6533            ['readonly']          = { 'operator', 'unlimited', 'executable', operators.readonly, 'readonly' },
6534            ['renderbands']       = { 'operator', 'unlimited', 'executable', operators.renderbands, 'renderbands' },
6535            ['repeat']            = { 'operator', 'unlimited', 'executable', operators["repeat"], 'repeat' },
6536            ['resetfile']         = { 'operator', 'unlimited', 'executable', operators.resetfile, 'resetfile' },
6537            ['restore']           = { 'operator', 'unlimited', 'executable', operators.restore, 'restore' },
6538            ['rlineto']           = { 'operator', 'unlimited', 'executable', operators.rlineto, 'rlineto' },
6539            ['rmoveto']           = { 'operator', 'unlimited', 'executable', operators.rmoveto, 'rmoveto' },
6540            ['roll']              = { 'operator', 'unlimited', 'executable', operators.roll, 'roll' },
6541            ['rotate']            = { 'operator', 'unlimited', 'executable', operators.rotate, 'rotate' },
6542            ['round']             = { 'operator', 'unlimited', 'executable', operators.round, 'round' },
6543            ['rrand']             = { 'operator', 'unlimited', 'executable', operators.rrand, 'rrand' },
6544            ['run']               = { 'operator', 'unlimited', 'executable', operators.run, 'run' },
6545            ['save']              = { 'operator', 'unlimited', 'executable', operators.save, 'save' },
6546            ['scale']             = { 'operator', 'unlimited', 'executable', operators.scale, 'scale' },
6547            ['scalefont']         = { 'operator', 'unlimited', 'executable', operators.scalefont, 'scalefont' },
6548            ['search']            = { 'operator', 'unlimited', 'executable', operators.search, 'search' },
6549            ['setdash']           = { 'operator', 'unlimited', 'executable', operators.setdash,  'setdash' },
6550            ['setflat']           = { 'operator', 'unlimited', 'executable', operators.setflat,  'setflat' },
6551            ['setfont']           = { 'operator', 'unlimited', 'executable', operators.setfont,  'setfont' },
6552            ['setgray']           = { 'operator', 'unlimited', 'executable', operators.setgray,  'setgray' },
6553            ['sethsbcolor']       = { 'operator', 'unlimited', 'executable', operators.sethsbcolor,  'sethsbcolor' },
6554            ['setlinecap']        = { 'operator', 'unlimited', 'executable', operators.setlinecap,  'setlinecap' },
6555            ['setlinejoin']       = { 'operator', 'unlimited', 'executable', operators.setlinejoin,  'setlinejoin' },
6556            ['setlinewidth']      = { 'operator', 'unlimited', 'executable', operators.setlinewidth,  'setlinewidth' },
6557            ['setmatrix']         = { 'operator', 'unlimited', 'executable', operators.setmatrix,  'setmatrix' },
6558            ['setmiterlimit']     = { 'operator', 'unlimited', 'executable', operators.setmiterlimit,  'setmiterlimit' },
6559            ['setrgbcolor']       = { 'operator', 'unlimited', 'executable', operators.setrgbcolor,  'setrgbcolor' },
6560            ['setcmykcolor']      = { 'operator', 'unlimited', 'executable', operators.setcmykcolor,  'setcmykcolor' },
6561            ['setscreen']         = { 'operator', 'unlimited', 'executable', operators.setscreen,  'setscreen' },
6562            ['settransfer']       = { 'operator', 'unlimited', 'executable', operators.settransfer,  'settransfer' },
6563            ['show']              = { 'operator', 'unlimited', 'executable', operators.show, 'show' },
6564            ['showpage']          = { 'operator', 'unlimited', 'executable', operators.showpage, 'showpage' },
6565            ['sin']               = { 'operator', 'unlimited', 'executable', operators.sin, 'sin' },
6566            ['sqrt']              = { 'operator', 'unlimited', 'executable', operators.sqrt, 'sqrt' },
6567            ['srand']             = { 'operator', 'unlimited', 'executable', operators.srand, 'srand' },
6568            ['stack']             = { 'operator', 'unlimited', 'executable', operators.stack, 'stack' },
6569            ['start']             = { 'operator', 'unlimited', 'executable', operators.start, 'start' },
6570            ['StandardEncoding']  = { 'array',    'unlimited', 'literal',    add_VM(standardencoding()), 256, 256 },
6571            ['status']            = { 'operator', 'unlimited', 'executable', operators.status, 'status' },
6572            ['stop']              = { 'operator', 'unlimited', 'executable', operators.stop, 'stop' },
6573            ['stopped']           = { 'operator', 'unlimited', 'executable', operators.stopped, 'stopped' },
6574            ['store']             = { 'operator', 'unlimited', 'executable', operators.store, 'store' },
6575            ['string']            = { 'operator', 'unlimited', 'executable', operators.string, 'string' },
6576            ['stroke']            = { 'operator', 'unlimited', 'executable', operators.stroke, 'stroke' },
6577            ['sub']               = { 'operator', 'unlimited', 'executable', operators.sub, 'sub' },
6578            ['systemdict']        = { 'dict',     'unlimited', 'literal',    systemdict },
6579            ['token']             = { 'operator', 'unlimited', 'executable', operators.token, 'token' },
6580            ['translate']         = { 'operator', 'unlimited', 'executable', operators.translate, 'translate' },
6581            ['transform']         = { 'operator', 'unlimited', 'executable', operators.transform, 'transform' },
6582            ['true']              = { 'boolean',  'unlimited', 'literal',    true },
6583            ['truncate']          = { 'operator', 'unlimited', 'executable', operators.truncate, 'truncate' },
6584            ['type']              = { 'operator', 'unlimited', 'executable', operators.type, 'type' },
6585            ['userdict']          = { 'dict',     'unlimited', 'literal',    userdict },
6586            ['usertime']          = { 'operator', 'unlimited', 'executable', operators.usertime, 'usertime' },
6587            ['version']           = { 'operator', 'unlimited', 'executable', operators.version, 'version' },
6588            ['vmstatus']          = { 'operator', 'unlimited', 'executable', operators.vmstatus, 'vmstatus' },
6589            ['wcheck']            = { 'operator', 'unlimited', 'executable', operators.wcheck, 'wcheck' },
6590            ['where']             = { 'operator', 'unlimited', 'executable', operators.where, 'where' },
6591            ['write']             = { 'operator', 'unlimited', 'executable', operators.write, 'write' },
6592            ['writehexstring']    = { 'operator', 'unlimited', 'executable', operators.writehexstring, 'writehexstring' },
6593            ['writestring']       = { 'operator', 'unlimited', 'executable', operators.writestring, 'writestring' },
6594            ['xcheck']            = { 'operator', 'unlimited', 'executable', operators.xcheck, 'xcheck' },
6595            ['xor']               = { 'operator', 'unlimited', 'executable', operators.xor, 'xor' },
6596        }
6597        if directvm then
6598            systemdict.dict = dict
6599        else
6600            VM[dictstack[systemdict]].dict = dict
6601        end
6602    end
6603end
6604
6605initializers[#initializers+1] = function(reset)
6606    if reset then
6607        dicterror = nil
6608        errordict = nil
6609    else
6610        dicterror = add_VM {
6611            access  = 'unlimited',
6612            size    = 1,
6613            maxsize = 40,
6614            dict    = {
6615                newerror = p_false
6616            },
6617        }
6618        --
6619        errordict = add_VM {
6620            access  = 'unlimited',
6621            size    = 0,
6622            maxsize = 40,
6623            dict    = { },
6624        }
6625        --
6626        local d
6627        if directvm then
6628            d = systemdict.dict
6629        else
6630            d = VM[dictstack[systemdict]].dict
6631        end
6632        -- still needed ?
6633        d['errordict']  = { 'dict', 'unlimited', 'literal', errordict }
6634        d['systemdict'] = { 'dict', 'unlimited', 'literal', systemdict }
6635        d['userdict']   = { 'dict', 'unlimited', 'literal', userdict }
6636        d['$error']     = { 'dict', 'unlimited', 'literal', dicterror }
6637    end
6638end
6639
6640-- What follows is the main interpreter, with the tokenizer first
6641
6642-- procedure scanning stack for the tokenizer
6643
6644local procstack
6645local procstackptr
6646
6647initializers[#initializers+1] = function(reset)
6648    if reset then
6649        procstack    = nil
6650        procstackptr = nil
6651    else
6652        procstack    = { }
6653        procstackptr = 0
6654    end
6655end
6656
6657-- lpeg parser for tokenization
6658
6659do
6660
6661    local function push(v)
6662        if procstackptr > 0 then
6663            local top = procstack[procstackptr]
6664            if top then
6665                top[#top+1] = v
6666            else
6667                procstack[procstackptr] = { v }
6668            end
6669            return false
6670        else
6671            push_execstack(v)
6672            return true
6673        end
6674    end
6675
6676    local function start()
6677        procstackptr = procstackptr + 1
6678        return true
6679    end
6680
6681    local function stop()
6682        local v = procstack[procstackptr]
6683        procstack[procstackptr] = { }
6684        procstackptr = procstackptr - 1
6685        if push {'array', 'unlimited', 'executable', add_VM(v), 1, #v, 'd' } then
6686            return true
6687        end
6688    end
6689
6690    local function hexify(a)
6691        return char(tonumber(a,16))
6692    end
6693
6694    local function octify(a)
6695        return char(tonumber(a,8))
6696    end
6697
6698    local function radixed(base,value)
6699        base = tonumber(base)
6700        if base > 36 or base < 2 then
6701            return nil
6702        end
6703        value = tonumber(value,base)
6704        if not value then
6705            return "error", false
6706        elseif value > MAX_INT then
6707            return "integer", value
6708        else
6709            return "real", value
6710        end
6711    end
6712
6713    local space      = S(' ')
6714    local spacing    = S(' \t\r\n\f')
6715    local sign       = S('+-')^-1
6716    local digit      = R('09')
6717    local period     = P('.')
6718    local letters    = R('!~') - S('[]<>{}()%/')
6719    local hexdigit   = R('09','af','AF')
6720    local radixdigit = R('09','az','AZ')
6721
6722    local p_integer  = (sign * digit^1 * #(1-letters)) / tonumber
6723    local p_real     = ((sign * digit^0 * period * digit^0 + period * digit^1) * (S('eE') * sign * digit^1)^-1 * #(1-letters)) / tonumber
6724    local p_literal  = Cs(P("/")/"" * letters^1 * letters^0)
6725    local p_symbol   = C(letters^1 * letters^0)
6726    ----- p_radixed  = C(digit^1) * P("#") * C(radixdigit^1) * #(1-letters)  / radixed-- weird #() here
6727    local p_radixed  = C(digit^1) * P("#") * C(radixdigit^1) / radixed
6728    local p_unhexed  = P("<") * Cs(((C(hexdigit*hexdigit) * Cc(16))/tonumber/char+spacing/"")^0) * P(">")
6729    local p_comment  = P('%') * (1 - S('\r\n'))^0 * Cc(true)
6730    local p_bounding = P('%%BoundingBox:') * Ct((space^0 * p_integer)^4) * (1 - S('\r\n'))^0
6731    local p_lbrace   = C("{")
6732    local p_rbrace   = C("}")
6733    local p_lbracket = C("[")
6734    local p_rbracket = C("]")
6735    local p_finish   = Cc(false)
6736
6737    local p_string   =
6738        P("(")
6739      * Cs( P {
6740            (
6741                (1 - S("()\\"))^1
6742              + P("\\")/"" * (
6743                    (C(digit *digit * digit) * Cc(8)) / tonumber / char
6744                  + P("n") / "\n" + P("r") / "\r" + P("t") / "\t"
6745                  + P("b") / "\b" + P("f") / "\f" + P("\\") / "\\"
6746                  + 1
6747                )
6748              + P("(") * V(1) * P(")")
6749            )^0
6750        })
6751    * P(")")
6752
6753    -- inspect(lpegmatch(p_radixed,"10#123"))
6754    -- inspect(lpegmatch(p_unhexed,"<A2B3  C3>"))
6755    -- inspect(lpegmatch(p_string,[[(foo(bar \124\125 \( bar\n bar\\bar))]]))
6756
6757    local p_unhexed     = Cc('string')   * p_unhexed
6758    local p_string      = Cc('string')   * p_string
6759    local p_array_start = Cc('name')     * p_lbracket
6760    local p_array_stop  = Cc('name')     * p_rbracket
6761    local p_exec_start  = Cc('start')    * p_lbrace
6762    local p_exec_stop   = Cc('stop')     * p_rbrace
6763    local p_integer     = Cc('integer')  * p_integer
6764    local p_real        = Cc('real')     * p_real
6765    local p_radixed     =                  p_radixed
6766    local p_symbol      = Cc('name')     * p_symbol
6767    local p_literal     = Cc('literal')  * p_literal
6768    local p_comment     = Cc('comment')  * p_comment
6769    local p_bounding    = Cc('bounding') * p_bounding
6770    local p_finish      = Cc("eof")      * p_finish
6771    local p_whitespace  = spacing^0
6772
6773    local tokens =  p_whitespace
6774                 * (
6775                    p_bounding
6776                  + p_comment
6777                  + p_string
6778                  + p_unhexed
6779                  + p_array_start
6780                  + p_array_stop
6781                  + p_exec_start
6782                  + p_exec_stop
6783                  + p_real
6784                  + p_radixed
6785                  + p_integer
6786                  + p_literal
6787                  + p_symbol
6788                  + p_finish
6789                )^-1
6790                * Cp()
6791
6792    -- we can do push etc in the lpeg but the call is not faster than the check
6793    -- and this stays closer to the original
6794
6795    local function tokenize()
6796        local object    = execstack[execstackptr]
6797        local sequence  = object[4]
6798        local position  = object[5]
6799        local length    = object[6]
6800        local tokentype = nil
6801        local value     = nil
6802        while position < length do
6803            tokentype, value, position = lpegmatch(tokens,get_VM(sequence),position)
6804            if not position then
6805                return false
6806            elseif position >= length then
6807                pop_execstack()
6808            else
6809                object[5] = position
6810            end
6811            if not value then
6812                if tokentype == "eof" then
6813                 -- pop_execstack()
6814                    return true
6815                else
6816                    return false -- handle_error('syntaxerror')
6817                end
6818            elseif tokentype == 'integer' or tokentype == 'real' then
6819                if push { tokentype, 'unlimited', 'literal', value } then
6820                    return true
6821                end
6822            elseif tokentype == 'name' then
6823                if push { 'name', 'unlimited', 'executable', add_VM(value) } then
6824                    return true
6825                end
6826            elseif tokentype == 'literal' then
6827                if push { 'name', 'unlimited', 'literal', add_VM(value) } then
6828                    return true
6829                end
6830            elseif tokentype == 'string' then
6831                if push { 'string', 'unlimited', 'literal', add_VM(value), 1, #value } then
6832                    return true
6833                end
6834            elseif tokentype == 'start' then
6835                if start() then
6836                    -- stay
6837                end
6838            elseif tokentype == 'stop' then
6839                if stop() then
6840                    return true
6841                end
6842            elseif tokentype == 'bounding' then
6843                specials.boundingbox = value
6844            else
6845                -- comment
6846            end
6847        end
6848        return position >= length
6849    end
6850
6851    -- the exec stack can contain a limited amount of interesting item types
6852    -- to be handled by next_object:
6853    -- executable arrays (procedures)
6854    -- executable strings
6855    -- executable files
6856
6857    next_object = function()
6858        if execstackptr == 0 then
6859            return nil
6860        end
6861        local object = execstack[execstackptr]
6862        if not object then
6863            return nil
6864        end
6865        local otyp = object[1]
6866        local exec = object[3] == 'executable'
6867        if not exec then
6868            return pop_execstack()
6869        elseif otyp == 'array' then
6870            if object[7] == 'd' then
6871                return pop_execstack()
6872            else
6873                local proc = get_VM(object[4])
6874                local o = object[5]
6875                local val = proc[o]
6876                if o >= #proc then
6877                    object[5] = 1
6878                    pop_execstack()
6879                else
6880                    object[5] = o + 1
6881                end
6882                return val
6883            end
6884        elseif otyp == 'string' then
6885            if not tokenize() then
6886                report("tokenizer failed on string")
6887                return nil
6888            else
6889                return next_object() -- recurse
6890            end
6891        elseif otyp == 'file' then
6892            if object[4] == 0 then
6893                report('sorry, interactive mode is not supported')
6894            end
6895            if not tokenize() then
6896                report("tokenizer failed on file")
6897                return nil
6898            else
6899                return next_object() -- recurse
6900            end
6901        else
6902            return pop_execstack()
6903        end
6904    end
6905
6906-- The main execution control function
6907
6908    local detail = false -- much faster
6909
6910    local report_exec = logs.reporter("escrito","exec")
6911
6912    do_exec = function() -- already a local
6913        local ret
6914        local savedopstack = detail and copy_opstack()
6915        local object = next_object()
6916        if not object then
6917            return false
6918        end
6919        local otyp = object[1]
6920        if false then -- debugging
6921            if otyp == 'operator' then
6922                report_exec("%s %s %s",otyp,object[3],object[5])
6923            elseif otyp == 'dict' then
6924                local d = get_VM(object[4])
6925                report_exec("%s %s <%s:%s>",otyp,object[3],d.size or '',d.maxsize or '')
6926            elseif otyp == 'array' or otyp == 'file' or otyp == 'save' then
6927                report_exec("%s <%s:%s>",object[3],object[5] or '',object[6] or '')
6928            elseif otyp == 'string' or otyp == 'name' then
6929                report_exec("%s %s %s",otyp,object[3],get_VM(object[4]))
6930            else
6931                report_exec("%s %s %s",otyp,object[3],tostring(object[4]))
6932            end
6933        end
6934        if otyp == 'real' or otyp == 'integer' or otyp == 'boolean' or otyp == 'mark' or otyp == 'save' or otyp == 'font' then
6935            push_opstack(object)
6936        elseif otyp == '.stopped' then
6937            -- when .stopped is seen here, stop was never called
6938            push_opstack { 'boolean', 'unlimited', 'executable', false}
6939        elseif otyp == '.exit' then
6940            -- when .exit is seen here, exit was never called
6941        elseif otyp == 'array' then
6942            if object[2] == 'noaccess' then
6943                escrito.errorname = 'noaccess'
6944            else
6945                push_opstack(object)
6946            end
6947        elseif otyp == 'string' then
6948            if object[2] == 'noaccess' then
6949                escrito.errorname = 'noaccess'
6950            else
6951                push_opstack(object)
6952            end
6953        elseif otyp == 'dict' then
6954            local dict = get_VM(object[4])
6955            if dict.access == 'noaccess' then
6956                escrito.errorname = 'noaccess'
6957            else
6958                push_opstack(object)
6959            end
6960        elseif otyp == 'file' then
6961            if object[2] == 'noaccess' then
6962                errorname = 'noaccess'
6963            else
6964                push_opstack(object)
6965            end
6966        elseif otyp == 'null' then
6967            push_opstack(object)
6968        elseif otyp == 'operator' then
6969            if object[3]=='executable' then
6970                ret, escrito.errorname = object[4]()
6971            else
6972                push_opstack(object)
6973            end
6974        elseif otyp == 'save' then
6975          -- todo
6976        elseif otyp == 'name' then
6977            if object[3] == 'executable' then
6978                local v = lookup(get_VM(object[4]))
6979                if not v then
6980                    if escrito.errorname then
6981                        -- doesn't work, needs thinking
6982                        error ("recursive error detected inside '" .. escrito.errorname .. "'")
6983                    end
6984                    escrito.errorname = 'undefined'
6985                else
6986                    if DEBUG then
6987                        local vt = v[1]
6988                        if vt == 'operator' then
6989                            print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. v[5])
6990                        elseif vt == 'dict' or vt == 'array' or vt == 'file' or vt == 'save'  then
6991                            print ('exec2: ' .. vt .. ' ' .. v[3] .. ' <'.. (v[5] or '') .. '>')
6992                        elseif vt == 'string' or vt == 'name' then
6993                            print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. get_VM(v[4]))
6994                        else
6995                            print ('exec2: ' .. vt .. ' ' .. v[3] .. ' '.. tostring(v[4]))
6996                        end
6997                    end
6998                    push_execstack(v)
6999                end
7000            else
7001                push_opstack(object)
7002            end
7003        elseif otyp == 'null' then
7004            -- do nothing
7005        elseif otyp == 'array' then
7006            push_opstack(object)
7007        end
7008
7009        if escrito.errorname then
7010            if savedopstack then
7011                local v = lookup_error(escrito.errorname)
7012                if not v then
7013                    print("unknown error handler for '" .. escrito.errorname .. "', quitting")
7014                    return false
7015                else
7016                    set_opstack(savedopstack)
7017                    push_opstack { otyp, object[2], "literal", object[4], object[5], object[6], object[7] }
7018                    push_opstack { 'string','unlimited','literal',add_VM(escrito.errorname), 1 }
7019                    push_execstack(v)
7020                end
7021                escrito.errorname = nil
7022            else
7023                print("error '" .. escrito.errorname .. "', quitting")
7024             -- os.exit()
7025            end
7026        end
7027
7028        return true
7029    end
7030
7031end
7032
7033do
7034
7035    -- some of the errors will never actually happen
7036
7037    local errornames = {
7038        "dictfull", "dictstackoverflow", "dictstackunderflow", "execstackoverflow",
7039        "interrupt", "invalidaccess", "invalidexit", "invalidfileaccess", "invalidfont", "invalidrestore",
7040        "ioerror", "limitcheck", "nocurrentpoint", "rangecheck", "stackoverflow", "stackunderflow",
7041        "syntaxerror", "timeout", "typecheck", "undefined", "undefinedfilename", "undefinedresult",
7042        "unmatchedmark", "unregistered", "VMerror"
7043    }
7044
7045    local generic_error_proc = [[{
7046        $error /newerror true put
7047        $error exch /errorname exch put
7048        $error exch /command exch put
7049        count array astore $error /ostack 3 -1 roll put
7050        $error /dstack countdictstack array dictstack put
7051        countexecstack array execstack aload pop pop count array astore $error /estack 3 -1 roll put
7052        stop
7053    } bind ]]
7054
7055    local generic_handleerror_proc = [[{
7056        $error begin
7057            /newerror false def
7058            (%%[ Error: ) print
7059            errorname print
7060            (; OffendingCommand: ) print
7061            command ==
7062            ( ]%%\n) print flush
7063        end
7064    }]]
7065
7066    local enabled
7067
7068    local function interpret(data)
7069        if enabled then
7070            push_opstack { 'file', 'unlimited', 'executable', add_VM(data), 1, #data, 'r', stdin }
7071            push_execstack { 'operator', 'unlimited', 'executable', operators.stopped, 'stopped' }
7072            while true do
7073                if not do_exec() then
7074                    local v = pop_opstack()
7075                    if v and v[4] == true then
7076                        local proc = {
7077                            { 'name',     'unlimited', 'executable', add_VM('errordict') }, -- hm, errordict
7078                            { 'name',     'unlimited', 'literal',    add_VM('handleerror') },
7079                            { 'operator', 'unlimited', 'executable', operators.get,  'get' },
7080                            { 'operator', 'unlimited', 'executable', operators.exec, 'exec' },
7081                        }
7082                        push_execstack { 'array', 'unlimited', 'executable', add_VM(proc), 1, #proc, 'i' }
7083                    else
7084                        return
7085                    end
7086                end
7087            end
7088        end
7089    end
7090
7091    local function close()
7092        for i=1,#initializers do
7093            initializers[i](true)
7094        end
7095        enabled = false
7096    end
7097
7098    local function open(options)
7099        enabled = true
7100        local starttime = os.clock()
7101        local stoptime  = nil
7102        for i=1,#initializers do
7103            initializers[i]()
7104        end
7105        if type(options) == "table" then
7106            devicename   = options.device or "pdf"
7107            findfont     = options.findfont   or findfont
7108            randomseed   = options.randomseed or randomseed -- todo
7109            calculatebox = options.calculatebox
7110        else
7111            devicename = "pdf"
7112        end
7113        device = devices[devicename] or devices.pdf
7114        operators.initgraphics()
7115        for i=1,#errornames do
7116            interpret(formatters["errordict /%s %s put"](errornames[i],generic_error_proc), INITDEBUG)
7117        end
7118        -- set up the error handler
7119        interpret("systemdict /= { 20 string cvs print } bind put", INITDEBUG)
7120        interpret("systemdict /prompt { (PS>) print flush } bind put", INITDEBUG)
7121        interpret(format("errordict /handleerror %s bind put", generic_handleerror_proc), INITDEBUG)
7122        interpret("systemdict /handleerror {errordict /handleerror get exec } bind put", INITDEBUG)
7123        -- user dict initializations
7124        interpret(format("/quit { stop } bind def"), INITDEBUG)
7125        interpret(format("userdict /#copies 1 put"), INITDEBUG)
7126        local job = {
7127            runtime     = 0,
7128            interpret   = interpret,
7129            boundingbox = boundingbox,
7130            close       = function()
7131                close()
7132                local runtime = os.clock() - starttime
7133                job.runtime = runtime
7134                return runtime
7135            end,
7136        }
7137        return job
7138    end
7139
7140    escrito.open = open
7141
7142    if context then
7143
7144        function escrito.convert(options)
7145            if type(options) == "table" then
7146                local data = options.data
7147                if not data or data == "" then
7148                    local buffer   = options.buffer
7149                    local filename = options.filename -- needs escaping
7150                    if buffer and buffer ~= "" then
7151                        data = buffers.getcontent(buffer)
7152                    elseif filename and filename ~= "" then
7153                        data = io.loaddata(filename) -- use resolver
7154                    end
7155                end
7156                if data and data ~= "" then
7157                    local e = open(options)
7158-- print(data)
7159                    e.interpret(data)
7160                    return e.close()
7161                end
7162            end
7163            return 0
7164        end
7165
7166    end
7167
7168    escrito.devices = devices
7169
7170end
7171
7172return escrito
7173