Hi Matthew,

On Tue Oct 15, 2024 at 8:13 PM CEST, Matthew Polk wrote:
> I'm currently getting impression that everything is global in groff,
> except maybe when talking about environment, then it's bound to that
> environment. But is anything assuming the environment doesn't change
> is local?
>
> I ask because reading the heirloom-doctools manual, which is a very
> conservative implementation (Minus a couple of groff extensions
> implemented) of troff, states that the requests '.lds' and '.lnr'
> create a register such as a number or string bound to only the macro
> it was created in and erased when the macro has finished releasing the
> memory. But searching through the groff manual, I do not right now see
> anything like .lds or .lnr, so is there no intention behind such a
> thing? [...]

I would disagree that Heirloom troff is a "very conservative
implementation". Although it has less extensions than groff,
it does implement some things which aren't implemented by groff,
such as the local variables which you mention above. So to answer
your question, no, as far as I am aware there is no such feature
in groff.

When writing my own macros for groff, I found the only reliable way to
store temporary data in macros was for each macro to have its own
pseudo-namespace, as in \\$0:var. Unfortunately this somewhat decreases
readability:
  (\\n[var1] - \\n[var2] + \\n[var3])
vs
  (\\n[\\$0:var1] - \\n[\\$0:var2] + \\n[\\$0:var3])

As a result, I developed macros for creating local variables to avoid
this. It's a bit fragile internally, but as long as you don't modify
the internals, it works alright. I attach the relevant macros and
some examples in case you would find this useful. (I also implemented
"env-local variables"... I can share that too if you are interested.)

~ 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.
.
.
.\" ===== 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 fatal 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) \{\# process args
.   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
. 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 contents 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  \" reset to 0
.   nr \\$0:nreg +1
.   shift
. \}
. rm \\$0:base
. rr \\$0:nreg
..
.
.de tmp:init-str  \" declare strings $1..$N as temporary
. aln \\$0:nstr tmp:nstr(\\n[tmp:lvl])
. while (\\n[.$] > 0) \{\# save strings' contents and reset them to empty
.   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 temp register...
.   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 register 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 temp string...
.   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 string 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
..
.
.
.\" ===== EXAMPLE 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
..
.
.\" test whether string $1 is prefixed by dash, save result in register is-flag?
.de is-flag
. dbg:assert (\\n[.$] == 1)
. tmp:init \\$0 -r "_has_prefix"
. ie r has-prefix?  .nr _has_prefix \\n[has-prefix?]
. has-prefix - "\\$1"
. nr is-flag? \\n[has-prefix?]
. ie r _has_prefix  .nr has-prefix? \\n[_has_prefix]
. tmp:finish \\$0
..

Reply via email to