summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/penlight/penlightextras.lua
blob: f612bd3370f9f16dda0299121d7d49c1d272b089 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
-- requires penlight
local pl = _G['penlight'] or _G['pl'] -- penlight for this namespace is pl
local bind = bind or pl.func.bind

function help_wrt(s1, s2) -- helpful printing, makes it easy to debug, s1 is object, s2 is note
    local wrt = wrt or texio.write_nl
    local wrt = wrt or print
    s2 = s2 or ''
    wrt('\nvvvvv '..s2)
    if type(s1) == 'table' then
        wrt(pl.pretty.write(s1))
    else
        wrt(tostring(s1))
    end
    wrt('^^^^^\n')
end


function prt_array2d(t)
    for _, r in ipairs(t) do
        local s = ''
        for _, v in ipairs(r) do
            s = s.. tostring(v)..', '
        end
        print(s)
    end
end

-- -- -- -- --


-- -- -- --  functions below are helpers for arrays and 2d

local function compare_elements(a, b, op, ele)
    op = op or pl.oper.gt
    ele = ele or 1
    return op(a[ele], b[ele])
end

local function comp_2ele_func(op, ele) -- make a 2 element comparison function,
    --sort with function on element nnum
    return bind(compare_elements, _1, _2, op, ele)
end


-- -- -- --

local function check_index(ij, rc) -- converts array index to positive value if negative
    if type(ij) ~= 'number' then
        return 1
    else
        if ij < 0 then
            ij = rc + ij + 1
        elseif ij > rc then
            ij = rc
        end
        return ij
    end
end
local function check_slice(M, i1, j1, i2, j2) -- ensure a slice is valid; i.e. all positive numbers
    r, c = pl.array2d.size(M)
    i1 = check_index(i1, r)
    i2 = check_index(i2, r)
    j1 = check_index(j1, c)
    j2 = check_index(j2, c)
    return i1, j1, i2, j2
end

local function check_func(func)  -- check if a function is a PE, if so, make it a function
    if type(func) ~= 'function' then
        __func = I(func)
    end
    return __func
end


-- -- -- -- -- -- --
-- -- -- --  functions below extend the array2d module


local function map_slice1(func, L, i1, i2) -- map a function to a slice of an array, can use PlcExpr
    i2 = i2 or i1
    local len = #L
    i1 = check_index(i1, len)
    i2 = check_index(i2, len)
    func = check_func(func)
    for i in pl.seq.range(i1,i2) do
            L[i] = func(L[i])
        end
   return L
end

local function map_slice2(func, M, i1, j1, i2, j2) -- map a function to a slice of a Matrix
    i1, j1, i2, j2 = check_slice(M, i1, j1, i2, j2)
    --for i,j in array2d.iter(M, true, i1, j1, i2, j2) do  --todo this did not work, penlight may have fixed this
    func = check_func(func)
    for i in pl.seq.range(i1,i2) do
        for j in pl.seq.range(j1,j2) do
            M[i][j] = func(M[i][j])
        end
    end
   return M
end

local function map_columns(func, M, j1, j2) -- map function to columns of matrix
    j2 = j2 or j1
    return map_slice2(func, M, 1, j1, -1, j2)
end

local function map_rows(func, M, i1, i2) -- map function to rows of matrix
    i2 = i2 or i1
    return map_slice2(func, M, i1, 1, i2, -1)
end


-- -- -- -- -- -- -- --

function sortOP(M, op, ele) -- sort a 2d array based on operator criteria, ele is column, ie sort on which element
       M_new = {}
        for row in pl.seq.sort(M, comp_2ele_func(op, ele)) do
            M_new[#M_new+1] = row
        end
        return M_new
end

local function like(M1, v)
    v = v or 0
    r, c = pl.array2d.size(M1)
    return pl.array2d.new(r,c,v)
end

function from_table(t) -- turns a labelled table to a 2d, label-free array
    t_new = {}
    for k, v in pairs(t) do
        if type(v) == 'table' then
            t_new_row = {k}
            for _, v_ in ipairs(v) do
                 t_new_row[#t_new_row+1] =  v_
            end
            t_new[#t_new+1] = t_new_row
        else
            t_new[#t_new+1] = {k, v}
        end
    end
    return t_new
end

function toTeX(M, EL) --puts & between columns, can choose to end line with \\ if EL is true (end-line)
    EL = EL or false
    if EL then EL = '\\\\' else EL = '' end
    return pl.array2d.reduce2(_1..EL.._2, _1..'&'.._2, M)..EL
end

-- -- -- -- -- -- --

-- add to array2d module
pl.array2d['map_columns'] = map_columns
pl.array2d['map_rows'] = map_rows
pl.array2d['map_slice2'] = map_slice2
pl.array2d['map_slice1'] = map_slice1
pl.array2d['from_table'] = from_table
pl.array2d['toTeX'] = toTeX
pl.array2d['sortOP'] = sortOP
pl.array2d['like'] = like




-- -- -- -- -- -- -- -- -- -- -- --  functions below extend the operator module

local function strgt(a,b) return tostring(a) > tostring(b) end
local function strlt(a,b) return tostring(a) < tostring(b) end
pl.operator['strgt'] = strgt
pl.operator['strlt'] = strlt





local function gextract(s, pat) --extract a pattern from string, returns both
    local s_extr = ''
    local s_rem = s
    for e in s:gmatch(pat) do
        s_extr = s_extr..e
        s_rem = s_rem:gsub(e,'')
    end
    return s_extr, s_rem
end

local function gfirst(s, t) -- get the first pattern found from a table of pattern
    for _, pat in pairs(t) do
        if string.find(s, pat) then
            return pat
        end
    end
end

local function appif(S, W, B, O) --append W ord to S tring if B oolean true, otherwise O ther
    --append Word to String
    if B then --if b is true
        S = S .. W
    else --consider Other word
        O = O or ''
        S = S .. O
    end
    return S
end

local mt = getmetatable("") -- register functions with str
mt.__index["gextract"] = gextract
mt.__index["gfirst"] = gfirst
mt.__index["app_if"] = appif


function mod(a, b) -- math modulo, return remainder only
    return a - (math.floor(a/b)*b)
end

function mod2(a) -- math modulo 2
    return mod(a,2)
end

function hasval(x)  -- if something has value
    -- todo check for Lists, other penlight objects
    if (x == nil) or (x == false) or (x == 0) or (x == '') then
        return false
    elseif type(x) == 'table' then
        if #x == 0 then
            return false
        else
            return true
        end
    end
    return true
end




-- testing here
____zzz__ = [[
function hasStrKey(T)
    --checks if a Table contains a string-type key
    local hasaStrKey = false
    for k, v in pairs(T) do  --look through table pairs
        if type(k) == "string" then  --if any string found, return true
            hasaStrKey = true
            break
        end
    end
    return hasaStrKey
end


function any()
    --todo go through table or list of args and return true of anything is something
end

function all()
    --todo go through table or list of args and return true of anything is something
end


    --elseif x == {} then
    --    return false

n = nil
b = false
z = 0
e = ''
t = {}

--s = 'a'
--n = 1


if is(nil) then
    print('TRUE')
else
    print('FALSE')
end

--print({}=={})
--print(0.0==0)


]]











-- Some simple and helpful LaTeX functions
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

--Generic LuaLaTeX utilities for print commands or environments
-- http://lua-users.org/wiki/SplitJoin -- todo read me!!
__SKIP_TEX__ = __SKIP_TEX__ or false --if declared true before here, it will use regular print functions
--                                       (for troubleshooting with texlua)

-- xparse defaults
_xTrue = '\\BooleanTrue '
_xFalse = '\\BooleanFalse '
_xNoValue = '-NoValue-'


if not __SKIP_TEX__ then
    prt = tex.sprint     --can print lists, but will NOT put new line between them
    prtn = tex.print      --can print lists and will put new line. C-function not in Lua. think P rint R return
    wrt = texio.write
    wrtn = texio.write_nl
else
    prt = io.write
    prtn = print --print with new line
    wrt = io.write
    wrtn = io.write_nl
end

function prtl(str) -- prints a literal string to latex, adds new line between them
    for line in str:gmatch"[^\n]*" do  -- gets all characters up to a new line
        prtn(line)
    end
end



_NumBkts = 0
function reset_bkt_cnt(n)
     n = n or 0
    _NumBkts = n
end

function add_bkt_cnt(n)
     n = n or 1
    _NumBkts = _NumBkts + n
end

function close_bkt_cnt()
    local s = ('}'):rep(_NumBkts)
    reset_bkt_cnt()
    return s
end








--definition helpers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- todo add this

local function defcmd_nest(cs) -- for option if you'd like your commands under  a parent ex. \csparent{var}
    tex.print('\\gdef\\'..cs..'#1{\\csname '..var..'--#1--\\endcsname}')
end


local function defcmd(cs, val, nargs)
    if (nargs == nil) or (args == 0) then
        token.set_macro(cs, tostring(val), 'global')
    else
        local args = '#1'
        tex.print('\\gdef\\'..cs..args..'{'..val..'}')
        -- todo https://tex.stackexchange.com/questions/57551/create-a-capitalized-macro-token-using-csname
        --    \expandafter\gdef\csname Two\endcsname#1#2{1:#1, two:#2} --todo do it like this
    end
end


local function prvcmd(cs, val) -- provide command via lua
   if token.is_defined(cs) then
        tex.print('\\PackageWarning{YAMLvars}{Variable '..cs..' already defined, could not declare}{}')
    else
        defcmd(cs, val)
    end
end


local function newcmd(cs, val) -- provide command via lua
   if token.is_defined(cs) then
        tex.print('\\PackageError{luadefs}{Command '..cs..' already defined}{}')
    else
        defcmd(cs, val)
    end
end

local function renewcmd(cs, val) -- provide command via lua
   if token.is_defined(cs) then
        defcmd(cs, val)
    else
        tex.print('\\PackageError{luadefs}{Command '..cs..' already defined}{}')
    end
end

local function deccmd(cs, def)
    if def == nil then
        prvcmd(cs, '\\PackageError{luadefs}{Command "'..cs..'" was declared and used but, not set}{}')
    else
        prvcmd(cs, def)
    end
end


-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --