Hi Kurt,

On Fri Oct 18, 2024 at 11:23 PM CEST, T. Kurt Bond wrote:
> onf, I would certainly be interested in seeing your
> "env-local variables"!

my sincere apologies for the delay. I found a bug in the code and it
took me a while to find the time to fix it. I also haven't realized
that it depends on some of my other utility macros (for nicer argument
parsing etc.), so it also took me some time to get it rid of those
dependencies.

Anyway. The standalone version is attached as 'envar.tmac'. I have
included a tiny demo at the end: "relative vertical spacing" macros.
They allow one to set vertical spacing to say 1.5m and then adjust the
point size with the vertical spacing auto-adjusting itself to the new
value of 1.5m. The env-local variables ensure that these settings remain
separate for each environment and that they are correctly copied with
the environment, too. The attached file 'relvs.tr' shows it in action.

I am also attaching the (almost) original version with all the
dependencies as 'util.tmac' just in case you would find it nicer
to use (or read). Please let me know if you need help understanding
anything or if you find any bugs.

~ onf
.\" Copyright 2024 onf
.\"
.\" Permission to use, copy, modify, and/or distribute this software for any
.\" purpose with or without fee is hereby granted.
.\"
.\" This software is provided "AS IS" and any warranties, express or implied,
.\" are hereby DISCLAIMED. IN NO EVENT shall the copyright holder be liable
.\" for any damages arising IN ANY WAY out of the use of this software.
.
.
.\" assert(COND) -- print warning to stderr if COND evaluates false
.de assert
. ie \\$* .return
. backtrace
. tm warning: assertion failed: \\$*
..
.
.
.\" ===== ENV-LOCAL REGISTERS & STRINGS =====
.
.\" How to use this:
.\" 1. use @ev:set-str-local and @ev:set-reg-local to make given
.\"    string/register env-local (see their documentation for more)
.\" 2. use @ev and @evc instead of ev and evc, respectively
.
.\" Internal registers used:
.\"  ev:nreg            -- number of env-local registers
.\"  ev:nstr            -- number of env-local strings
.\"  ev:regNR.can-copy  -- whether N-th env-local register can be copied
.\"  ev:strNR.can-copy  -- whether N-th env-local string can be copied
.\"
.\" Internal strings used:
.\"  ev:regNR.name      -- name of N-th env-local register
.\"  ev:strNR.name      -- name of N-th env-local string
.\"  ev:regNR.setter    -- name of setter macro of given reg (empty if none)
.\"  ev:strNR.setter    -- name of setter macro of given string (empty if none)
.\"
.\" Public registers used:
.\"  ev:reg(ENV,NAME)   -- contents of given register in given environment
.\"
.\" Public strings used:
.\"  ev:str(ENV,NAME)   -- contents of given string in given environment
.
.nr ev:nreg 0
.nr ev:nstr 0
.
.\" @ev:set-str-local -- set given string as env-local, i.e. saved on exiting
.\"                      an environment and restored on entering it
.\" $1 = string name
.\" $2 = setter macro to be called with the new value as argument instead of
.\"      restoring the string directly (through .ds request)
.\" $3 = NO-COPY if string shouldn't be copied with @evc
.de @ev:set-str-local
. assert (\\n[.$] >= 1 & (\\n[.$] <= 3))
. ds ev:str\\n[ev:nstr].name \\$1
. ds ev:str\\n[ev:nstr].setter \\$2
. ie '\\$3'NO-COPY' .nr ev:str\\n[ev:nstr].can-copy 0
. el                .nr ev:str\\n[ev:nstr].can-copy 1
. nr ev:nstr +1
..
.
.\" @ev:set-reg-local -- set given register as env-local, i.e. saved on exiting
.\"                      an environment and restored on entering it
.\" $1 = register name
.\" $2 = setter macro to be called with the new value as argument instead of
.\"      restoring the register directly (through .nr & .af requests)
.\" $3 = NO-COPY if register shouldn't be copied with @evc
.de @ev:set-reg-local
. assert (\\n[.$] >= 1 & (\\n[.$] <= 3))
. ds ev:reg\\n[ev:nreg].name \\$1
. ds ev:reg\\n[ev:nreg].setter \\$2
. ie '\\$3'NO-COPY' .nr ev:reg\\n[ev:nreg].can-copy 0
. el                .nr ev:reg\\n[ev:nreg].can-copy 1
. nr ev:nreg +1
..
.
.de @ev:_restore_reg  \" $1 = env, $2 = reg index
. assert (\\n[.$] == 2)
.
. ds \\$0:name \\*[ev:reg\\$2.name]
. rr \\*[\\$0:name]
. nr \\$0:can-copy 0
. ie r ev:reg(\\$1,\\*[\\$0:name]) \{\
.   ie '\\$1'\\n[.ev]' .nr \\$0:can-copy 1         \" @ev
.   el .nr \\$0:can-copy \\n[ev:reg\\$2.can-copy]  \" @evc
. \}
.
. ie \\n[\\$0:can-copy] \{\
.   nr \\$0:val \\n[ev:reg(\\$1,\\*[\\$0:name])]
.   ds \\$0:setter \\*[ev:reg\\$2.setter]
.   ie !'\\*[\\$0:setter]'' .\\*[\\$0:setter]  \\n[\\$0:val]
.   el                      .nr \\*[\\$0:name] \\n[\\$0:val]
. \}
..
.
.de @ev:_restore_str  \" $1 = env, $2 = str index
. assert (\\n[.$] == 2)
.
. ds \\$0:name \\*[ev:str\\$2.name]
. rm \\*[\\$0:name]
. nr \\$0:can-copy 0
. ie d ev:str(\\$1,\\*[\\$0:name]) \{\
.   ie '\\$1'\\n[.ev]' .nr \\$0:can-copy 1         \" @ev
.   el .nr \\$0:can-copy \\n[ev:str\\$2.can-copy]  \" @evc
. \}
.
. ie \\n[\\$0:can-copy] \{\
.   ds \\$0:val "\\*[ev:str(\\$1,\\*[\\$0:name])]\"
.   ds \\$0:setter \\*[ev:str\\$2.setter]
.   ie !'\\*[\\$0:setter]'' .\\*[\\$0:setter]  "\\*[\\$0:val]"
.   el                      .ds \\*[\\$0:name] "\\*[\\$0:val]\"
. \}
..
.
.\" @ev -- switch environment as with ev + save & restore contents of env-locals
.de @ev
. assert (\\n[.$] <= 1)
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nreg]) \{\# save registers
.   ds \\$0:name \\*[ev:reg\\n[\\$0:i].name]
.   ie r \\*[\\$0:name] \{\
.     nr ev:reg(\\n[.ev],\\*[\\$0:name]) \\n[\\*[\\$0:name]]
.   \}
.   nr \\$0:i +1
. \}
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nstr]) \{\# save strings
.   ds \\$0:name \\*[ev:str\\n[\\$0:i].name]
.   ie d \\*[\\$0:name] \{\
.     ds ev:str(\\n[.ev],\\*[\\$0:name]) "\\*[\\*[\\$0:name]]\"
.   \}
.   nr \\$0:i +1
. \}
.
. ev \\$1
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nreg]) \{\# restore registers
.   @ev:_restore_reg \\n[.ev] \\n[\\$0:i]
.   nr \\$0:i +1
. \}
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nstr]) \{\# restore strings
.   @ev:_restore_str \\n[.ev] \\n[\\$0:i]
.   nr \\$0:i +1
. \}
..
.
.\" @evc -- copy environment as with evc + copy copiable env-locals
.de @evc
. assert (\\n[.$] == 1)
. evc \\$1
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nreg]) \{\# copy copiable registers
.   @ev:_restore_reg \\$1 \\n[\\$0:i]
.   nr \\$0:i +1
. \}
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[ev:nstr]) \{\# copy copiable strings
.   @ev:_restore_str \\$1 \\n[\\$0:i]
.   nr \\$0:i +1
. \}
..
.
.
.\" ===== RELATIVE VERTICAL SPACING =====
.
.\" Internal env-strings used:
.\"  %vs      -- the expression to be evaluated by .vs
.\"  %vs:prev -- previous value of %vs
.
.@ev:set-str-local %vs
.@ev:set-str-local %vs:prev
.
.de @vs
. ie (\\n[.$] == 0) .assert d %vs:prev
. ie (\\n[.$] == 0) .ds \\$0:new \\*[%vs:prev]
. el                .ds \\$0:new \\$*
.
. ie d %vs  .ds %vs:prev \\*[%vs]
. ds %vs \\*[\\$0:new]
. vs \\*[\\$0:new]
..
.
.de @ps
. ps \\$*
. ie d %vs  .vs \\*[%vs]
..
.\" Copyright 2024 onf
.\"
.\" Permission to use, copy, modify, and/or distribute this software for any
.\" purpose with or without fee is hereby granted.
.\"
.\" This software is provided "AS IS" and any warranties, express or implied,
.\" are hereby DISCLAIMED. IN NO EVENT shall the copyright holder be liable
.\" for any damages arising IN ANY WAY out of the use of this software.
.
.
.\" ===== EASIER DEBUGGING =====
.
.\" dbg:info(MSG) -- print info MSG to stderr
.de dbg:info
. tm info: \\$*
..
.
.\" dbg:warn(MSG) -- print warning MSG to stderr
.de dbg:warn
. backtrace
. tm warning: \\$*
..
.
.\" dbg:err(MSG) -- print error MSG to stderr
.de dbg:err
. backtrace
. tm error: \\$*
..
.
.\" dbg:fatal(MSG) -- print error MSG to stderr & exit
.de dbg:fatal
. backtrace
. ab fatal: \\$*
..
.
.\" dbg:assert(COND) -- print warning to stderr if COND evaluates false
.de dbg:assert
. ie !\\$* .dbg:warn assertion failed: \\$*
..
.
.
.\" ===== TEMPORARY VARIABLES =====
.
.\" How to use this:
.\"  1. add `.tmp:init \\$0` at the beginning of a macro
.\"  2. a) use tmp:init-reg to declare names of used temporary registers,
.\"        and tmp:init-str to declare names of used temporary strings
.\"  2. b) pass the string and register names as args to the -s and -r flags
.\"        of tmp:init, respectively; i.e. `.tmp:init \\$0 -s "_str1 _str2"
.\"  3. add `.tmp:finish \\$0` at the end of a macro and before any .return's
.
.\" Internal registers used:
.\"  tmp:lvl              -- index of topmost level (context)
.\"
.\"  tmp:nreg(LVL)        -- number of saved registers at given level
.\"  tmp:reg(LVL,N).val   -- previous value of given register (if defined)
.\"  tmp:reg(LVL,N).incr  -- previous auto-increment of given register (if def)
.\"
.\"  tmp:nstr(LVL)        -- number of saved strings at given level
.\"
.\" Internal strings used:
.\"  tmp:name(LVL)        -- name of given level (context)
.\"
.\"  tmp:reg(LVL,N).name  -- name of N-th saved register at given level
.\"  tmp:reg(LVL,N).fmt   -- previous format of given register (if defined)
.\"
.\"  tmp:str(LVL,N).name  -- name of N-th saved string at given level
.\"  tmp:str(LVL,N).val   -- previous value of given string (if defined)
.
.nr tmp:lvl -1
.
.de tmp:init  \" initialize new context; $1 = context name
.             \" flags: -r "REG1 REG2" - declare temporary registers
.             \"        -s "STR1 STR2" - declare temporary strings
. nr tmp:lvl +1
. nr tmp:nreg(\\n[tmp:lvl]) 0
. nr tmp:nstr(\\n[tmp:lvl]) 0
. ds tmp:name(\\n[tmp:lvl]) \\$1
. shift
. while (\\n[.$] > 0) \{\# gotta avoid cyclic dependency with getopt
.   ie !'\\$1'-r' .ie !'\\$1'-s' \{\
.     dbg:err unknown flag '\\$1'
.     shift
.     continue
.   \}
.
.   ie '\\$2'' .dbg:err missing argument to flag '\\$1'
.   el \{ .ie '\\$1'-r' .tmp:init-reg \\$2
.   el \{ .ie '\\$1'-s' .tmp:init-str \\$2
.   \}\}
.   shift 2
. \}
..
.
.de tmp:init-reg  \" declare registers $1..$N as temporary, save & reset to 0
. aln \\$0:nreg tmp:nreg(\\n[tmp:lvl])
. while (\\n[.$] > 0) \{\
.   ds \\$0:base tmp:reg(\\n[tmp:lvl],\\n[\\$0:nreg])
.   ds \\*[\\$0:base].name \\$1
.   ie r \\$1 \{\# save register if defined
.     ds \\*[\\$0:base].fmt \\g[\\$1]
.     af \\$1 1
.     nr \\*[\\$0:base].val \\n[\\$1]
.     nr \\*[\\$0:base].incr (\\n+[\\$1] - \\n-[\\$1])
.   \}
.   nr \\$1 0 +1
.   nr \\$0:nreg +1
.   shift
. \}
. rm \\$0:base
. rr \\$0:nreg
..
.
.de tmp:init-str  \" declare strings $1..$N as temporary, save & reset to empty
. aln \\$0:nstr tmp:nstr(\\n[tmp:lvl])
. while (\\n[.$] > 0) \{\
.   ds \\$0:base tmp:str(\\n[tmp:lvl],\\n[\\$0:nstr])
.   ds \\*[\\$0:base].name \\$1
.   ie d \\$1  .ds \\*[\\$0:base].val "\\*[\\$1]\"
.   ds \\$1
.   nr \\$0:nstr +1
.   shift
. \}
. rm \\$0:base
. rr \\$0:nstr
..
.
.de tmp:finish  \" cleanup current context, restore previous one; $1 = ctx name
. dbg:assert (\\n[.$] == 1)
. ds \\$0:name \\*[tmp:name(\\n[tmp:lvl])]
. rm tmp:name(\\n[tmp:lvl])
. ie !'\\*[\\$0:name]'\\$1' \{\
.   dbg:warn tmp: unbalanced init & finish calls; '\\*[\\$0:name]' not finished
. \}
.
. nr \\$0:nreg \\n[tmp:nreg(\\n[tmp:lvl])]
. nr \\$0:nstr \\n[tmp:nstr(\\n[tmp:lvl])]
. rr tmp:nreg(\\n[tmp:lvl])
. rr tmp:nstr(\\n[tmp:lvl])
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[\\$0:nreg]) \{\# for each register declared temp...
.   ds \\$0:base tmp:reg(\\n[tmp:lvl],\\n[\\$0:i])
.   ds \\$0:name \\*[\\*[\\$0:base].name]
.   rr \\*[\\$0:name]
.   ie r \\*[\\$0:base].val \{\# restore contents if saved
.     nr \\*[\\$0:name] \\n[\\*[\\$0:base].val] \\n[\\*[\\$0:base].incr]
.     af \\*[\\$0:name] \\*[\\*[\\$0:base].fmt]
.     rr \\*[\\$0:base].val
.     rr \\*[\\$0:base].incr
.     rm \\*[\\$0:base].fmt
.   \}
.   rm \\*[\\$0:base].name
.   nr \\$0:i +1
. \}
.
. nr \\$0:i 0
. while (\\n[\\$0:i] < \\n[\\$0:nstr]) \{\# for each string declared temp...
.   ds \\$0:base tmp:str(\\n[tmp:lvl],\\n[\\$0:i])
.   ds \\$0:name \\*[\\*[\\$0:base].name]
.   rm \\*[\\$0:name]
.   ie d \\*[\\$0:base].val \{\# restore contents if saved
.     ds \\*[\\$0:name] "\\*[\\*[\\$0:base].val]\"
.     rm \\*[\\$0:base].val
.   \}
.   rm \\*[\\$0:base].name
.   nr \\$0:i +1
. \}
.
. nr tmp:lvl -1
. rr \\$0:nreg
. rr \\$0:nstr
. rr \\$0:i
. rm \\$0:base
. rm \\$0:name
..
.
.
.\" ===== HELPERS =====
.
.\" test whether string $2 has prefix $1, save result in register has-prefix?
.de has-prefix
. dbg:assert (\\n[.$] == 2)
. tmp:init \\$0 -r "_len1 _len2" -s "_str"
.
. nr \\$0? 0
. length _len1 "\\$1\"
. length _len2 "\\$2\"
. ie (\\n[_len2] > 0) \{\
.   ds _str "\\$2\"
.   substring _str 0 (\\n[_len1] - 1 <? (\\n[_len2] - 1))
.   ie '\\*[_str]'\\$1' .nr \\$0? 1
. \}
.
. tmp:finish \\$0
..
.
.\" test whether string $2 has suffix $1, save result in register has-suffix?
.de has-suffix
. dbg:assert (\\n[.$] == 2)
. tmp:init \\$0 -r "_len1 _len2" -s "_str"
.
. nr \\$0? 0
. length _len1 "\\$1
. length _len2 "\\$2
. ie (\\n[_len2] > 0) \{\
.   ds _str  "\\$2\"
.   substring _str (-\\n[_len1] >? -\\n[_len2]) -1
.   ie '\\*[_str]'\\$1' .nr \\$0? 1
. \}
.
. tmp:finish \\$0
..
.
.
.\" ===== ARGUMENT PARSING =====
.
.\" How to use this:
.\"  1. call getopt:setup, passing in as arguments the names of desired flags
.\"     not prefixed by hyphen; if you want the flag to have an argument,
.\"     suffix its name by `:` or `=` if you want it to be validated as number
.\"  2. call .getopt:parse \\$@
.\"  3. call .shift \\n[getopt:nshift]
.\"  4. you can test for any flag by interpolating \\n[getopt:is-set(FLAG)], and
.\"     you can get its arg (if defined with arg) with \\*[getopt:get-arg(FLAG)]
.
.\" Internal registers used:
.\"  getopt:nflags                -- number of flags defined during setup
.\"  getopt:flagNR.has-arg        -- whether given flag expects an argument
.\"  getopt:flagNR.validate       -- whether to check if arg is valid number
.\"
.\" Internal strings used:
.\"  getopt:flagNR.name           -- given flag's name
.
.\" Public registers used:
.\"  getopt:nshift                -- how many args to shift to get rid of flags
.\"  getopt:is-set(NAME_OF_FLAG)  -- whether given flag was found during parsing
.\"
.\" Public strings used:
.\"  getopt:get-arg(NAME_OF_FLAG) -- argument passed to the given flag
.\"                                  (last occurence counts)
.
.de getopt:setup
. tmp:init \\$0 -r "_i" -s "_name"
.
. \" clean up after last getopt
. nr _i 0
. ie r getopt:nflags .while (\\n[_i] < \\n[getopt:nflags]) \{\
.   ds _name \\*[getopt:flag\\n[_i].name]
.   rr getopt:flag\\n[_i].has-arg
.   rr getopt:flag\\n[_i].validate
.   rm getopt:flag\\n[_i].name
.   rr getopt:is-set(\\*[_name])
.   rm getopt:get-arg(\\*[_name])
.   nr _i +1
. \}
. rr getopt:nshift
.
. nr _i 0
. while !'\\$1'' \{\
.   ds getopt:flag\\n[_i].name \\$1
.   nr getopt:flag\\n[_i].has-arg 0
.   nr getopt:flag\\n[_i].validate 0
.
.   has-suffix = \\$1
.   ie \\n[has-suffix?] .nr getopt:flag\\n[_i].validate 1
.   el .has-suffix : \\$1
.
.   ie \\n[has-suffix?] \{\
.     nr getopt:flag\\n[_i].has-arg 1
.     substring getopt:flag\\n[_i].name 0 -2
.   \}
.
.   nr _i +1
.   shift
. \}
. nr getopt:nflags \\n[_i]
.
. tmp:finish \\$0
..
.
.de getopt:parse
. tmp:init \\$0 -s "_name" -r "_i _hasarg _validate"
.
. \" initialize the "is-set" register for all defined flags to 0
. nr _i 0
. while (\\n[_i] < \\n[getopt:nflags]) \{\
.   nr getopt:is-set(\\*[getopt:flag\\n[_i].name]) 0
.   nr _i +1
. \}
.
. nr getopt:nshift 0
. has-prefix - "\\$1"
. while \\n[has-prefix?] \{\# loop over args
.   nr getopt:nshift +1
.   nr _i 0
.   while (\\n[_i] < \\n[getopt:nflags]) \{\# loop over pre-defined flags
.     ie '\\$1'--' .return  \" end of flags requested
.
.     ds _name \\*[getopt:flag\\n[_i].name]
.     nr _hasarg \\n[getopt:flag\\n[_i].has-arg]
.     nr _validate \\n[getopt:flag\\n[_i].validate]
.
.     ie '\\$1'-\\*[_name]' \{\
.       nr getopt:is-set(\\*[_name]) 1
.       ie \\n[_hasarg] \{\
.         has-prefix - "\\$2"
.         ie ((\\n[.$] == 1) : \\n[has-prefix?]) \{\
.           dbg:warn missing argument to flag \\$1
.         \}
.         el \{\
.           ie (\\n[_validate] & (\B'\\$2' == 0)) \{\
.             dbg:warn flag \\$1 expected numeric expression, got '\\$2'
.           \}
.           el .ds getopt:get-arg(\\*[_name]) "\\$2
.           nr getopt:nshift +1
.           shift
.         \}
.       \}
.       break
.     \}
.     nr _i +1
.   \}
.   ie (\\n[_i] == \\n[getopt:nflags]) .dbg:warn unknown flag '\\$1'
.   ie (\\n[.$] == 1) .break
.
.   shift
.   has-prefix - "\\$1"
. \}
.
. tmp:finish \\$0
..
.
.
.\" ===== ENV-LOCAL REGISTERS & STRINGS =====
.
.\" How to use this:
.\" 1. use @ev:set-str-local and @ev:set-reg-local to make given
.\"    string/register env-local (see their documentation for more)
.\" 2. use @ev and @evc instead of ev and evc, respectively
.
.\" Internal registers used:
.\"  ev:nreg            -- number of env-local registers
.\"  ev:nstr            -- number of env-local strings
.\"  ev:regNR.can-copy  -- whether N-th env-local register can be copied
.\"  ev:strNR.can-copy  -- whether N-th env-local string can be copied
.\"
.\" Internal strings used:
.\"  ev:regNR.name      -- name of N-th env-local register
.\"  ev:strNR.name      -- name of N-th env-local string
.\"  ev:regNR.setter    -- name of setter macro of given reg (empty if none)
.\"  ev:strNR.setter    -- name of setter macro of given string (empty if none)
.\"
.\" Public registers used:
.\"  ev:reg(ENV,NAME)   -- contents of given register in given environment
.\"
.\" Public strings used:
.\"  ev:str(ENV,NAME)   -- contents of given string in given environment
.
.nr ev:nreg 0
.nr ev:nstr 0
.
.\" @ev:set-str-local -- set given string as env-local, i.e. saved on exiting
.\"                      an environment and restored on entering it
.\"                   -- flags:
.\"                      -no-copy -- do not copy string contents with @evc
.\"                      -setter  -- define a setter macro which will be called
.\"                                  instead of restoring the string directly
.de @ev:set-str-local
. getopt:setup no-copy setter:
. getopt:parse \\$@
. shift \\n[getopt:nshift]
. dbg:assert (\\n[.$] == 1)
.
. ds ev:str\\n[ev:nstr].name \\$1
. ds ev:str\\n[ev:nstr].setter
. ie \\n[getopt:is-set(setter)] \{\
.   ds ev:str\\n[ev:nstr].setter \\*[getopt:get-arg(setter)]
. \}
. nr ev:str\\n[ev:nstr].can-copy (\\n[getopt:is-set(no-copy)] == 0)
. nr ev:nstr +1
..
.
.\" @ev:set-reg-local -- set given register as env-local, i.e. saved on exiting
.\"                      an environment and restored on entering it
.\"                   -- flags:
.\"                      -no-copy -- do not copy register contents with @evc
.\"                      -setter  -- define a setter macro which will be called
.\"                                  instead of restoring the register directly
.de @ev:set-reg-local
. getopt:setup no-copy setter:
. getopt:parse \\$@
. shift \\n[getopt:nshift]
. dbg:assert (\\n[.$] == 1)
.
. ds ev:reg\\n[ev:nreg].name \\$1
. ds ev:reg\\n[ev:nreg].setter
. ie \\n[getopt:is-set(setter)] \{\
.   ds ev:reg\\n[ev:nreg].setter \\*[getopt:get-arg(setter)]
. \}
. nr ev:reg\\n[ev:nreg].can-copy (\\n[getopt:is-set(no-copy)] == 0)
. nr ev:nreg +1
..
.
.de @ev:_restore_reg  \" $1 = env, $2 = reg index
. tmp:init \\$0 -s "_name _setter" -r "_val _can_copy"
. dbg:assert (\\n[.$] == 2)
.
. ds _name \\*[ev:reg\\$2.name]
. rr \\*[_name]
. nr _can_copy 0
. ie r ev:reg(\\$1,\\*[_name]) \{\
.   ie '\\$1'\\n[.ev]' .nr _can_copy 1         \" @ev
.   el .nr _can_copy \\n[ev:reg\\$2.can-copy]  \" @evc
. \}
.
. ie \\n[_can_copy] \{\
.   nr _val \\n[ev:reg(\\$1,\\*[_name])]
.   ds _setter \\*[ev:reg\\$2.setter]
.   ie !'\\*[_setter]'' .\\*[_setter]  \\n[_val]
.   el                  .nr \\*[_name] \\n[_val]
. \}
. tmp:finish \\$0
..
.
.de @ev:_restore_str  \" $1 = env, $2 = str index
. tmp:init \\$0 -s "_name _val _setter" -r "_can_copy"
. dbg:assert (\\n[.$] == 2)
.
. ds _name \\*[ev:str\\$2.name]
. rm \\*[_name]
. nr _can_copy 0
. ie d ev:str(\\$1,\\*[_name]) \{\
.   ie '\\$1'\\n[.ev]' .nr _can_copy 1         \" @ev
.   el .nr _can_copy \\n[ev:str\\$2.can-copy]  \" @evc
. \}
.
. ie \\n[_can_copy] \{\
.   ds _val "\\*[ev:str(\\$1,\\*[_name])]\"
.   ds _setter \\*[ev:str\\$2.setter]
.   ie !'\\*[_setter]'' .\\*[_setter]  "\\*[_val]"
.   el                  .ds \\*[_name] "\\*[_val]\"
. \}
. tmp:finish \\$0
..
.
.\" @ev -- switch environment as with ev + save & restore contents of env-locals
.de @ev
. tmp:init \\$0 -r "_i" -s "_name"
. dbg:assert (\\n[.$] <= 1)
. nr _i 0
. while (\\n[_i] < \\n[ev:nreg]) \{\# save registers
.   ds _name \\*[ev:reg\\n[_i].name]
.   ie r \\*[_name]  .nr ev:reg(\\n[.ev],\\*[_name]) \\n[\\*[_name]]
.   \}
.   nr _i +1
. \}
.
. nr _i 0
. while (\\n[_i] < \\n[ev:nstr]) \{\# save strings
.   ds _name \\*[ev:str\\n[_i].name]
.   ie d \\*[_name]  .ds ev:str(\\n[.ev],\\*[_name]) "\\*[\\*[_name]]\"
.   nr _i +1
. \}
.
. ev \\$1
.
. nr _i 0
. while (\\n[_i] < \\n[ev:nreg]) \{\# restore registers
.   @ev:_restore_reg \\n[.ev] \\n[_i]
.   nr _i +1
. \}
.
. nr _i 0
. while (\\n[_i] < \\n[ev:nstr]) \{\# restore strings
.   @ev:_restore_str \\n[.ev] \\n[_i]
.   nr _i +1
. \}
.
. tmp:finish \\$0
..
.
.\" @evc -- copy environment as with evc + copy copiable env-locals
.de @evc
. tmp:init \\$0 -r "_i"
. dbg:assert (\\n[.$] == 1)
. evc \\$1
. nr _i 0
. while (\\n[_i] < \\n[ev:nreg]) \{\# copy copiable registers
.   @ev:_restore_reg \\$1 \\n[_i]
.   nr _i +1
. \}
.
. nr _i 0
. while (\\n[_i] < \\n[ev:nstr]) \{\# copy copiable strings
.   @ev:_restore_str \\$1 \\n[_i]
.   nr _i +1
. \}
.
. tmp:finish \\$0
..
.
.
.\" ===== RELATIVE VERTICAL SPACING =====
.
.\" Internal env-strings used:
.\"  %vs      -- the expression to be evaluated by .vs
.\"  %vs:prev -- previous value of %vs
.
.@ev:set-str-local %vs
.@ev:set-str-local %vs:prev
.
.de @vs
. tmp:init \\$0 -s "_new"
.
. ie (\\n[.$] == 0) .dbg:assert d %vs:prev
. ie (\\n[.$] == 0) .ds _new \\*[%vs:prev]
. el                .ds _new \\$*
.
. ie d %vs  .ds %vs:prev \\*[%vs]
. ds %vs \\*[_new]
. vs \\*[_new]
.
. tmp:finish \\$0
..
.
.de @ps
. ps \\$*
. ie d %vs  .vs \\*[%vs]
..
.\" groff -b -P-pa5 -Tpdf relvs.tr > relvs.pdf
.so envar.tmac
.po 1c
.ll 12.8c
.pl 21c
.sp |1c-1v
.@vs 1.25m
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Curabitur gravida sapien dolor, et tempus neque luctus ultrices.
Curabitur ultrices lobortis feugiat.
Proin eget placerat purus.
Aliquam erat volutpat.
In vel pretium elit.
.br
.@vs 1.75m
Pellentesque lacinia sem pellentesque massa sollicitudin,
et scelerisque lectus viverra.
Ut ullamcorper ultricies lacus ut pellentesque.
Maecenas quis massa tempus, luctus erat a, pulvinar est.
Suspendisse semper mi sit amet orci elementum gravida.
Quisque fringilla neque ac velit lobortis ornare.
Ut luctus faucibus sapien, ut molestie purus posuere sed.
Sed ultricies, leo quis tincidunt aliquet, enim velit eleifend orci,
eget dictum quam purus eget ante.
.@vs \" 1.25m
.@ev 1
.@evc 0
.@ps +5
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Curabitur gravida sapien dolor, et tempus neque luctus ultrices.
Curabitur ultrices lobortis feugiat.
Proin eget placerat purus.
Aliquam erat volutpat.
In vel pretium elit.
.br
.@vs \" 1.75m
Pellentesque lacinia sem pellentesque massa sollicitudin,
et scelerisque lectus viverra.
Ut ullamcorper ultricies lacus ut pellentesque.
Maecenas quis massa tempus, luctus erat a, pulvinar est.
Suspendisse semper mi sit amet orci elementum gravida.
Quisque fringilla neque ac velit lobortis ornare.
Ut luctus faucibus sapien, ut molestie purus posuere sed.
Sed ultricies, leo quis tincidunt aliquet, enim velit eleifend orci,
eget dictum quam purus eget ante.

Reply via email to