• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nhartland / forma / 23412288202

22 Mar 2026 08:54PM UTC coverage: 98.169% (-0.04%) from 98.213%
23412288202

push

github

web-flow
Merge pull request #32 from nhartland/mkdocs

MKdocs rather than Ldoc for documentation generation

9 of 12 new or added lines in 4 files covered. (75.0%)

36 existing lines in 4 files now uncovered.

2091 of 2130 relevant lines covered (98.17%)

8571.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.91
/forma/multipattern.lua
1
--- A class containing a collection of `pattern` objects.
2
--- Many pattern operations generate a set of patterns. This
3
--- class aims to provide a convenient collection with some
4
--- common methods for handling them.
5
---@class forma.multipattern
6
---@field components forma.pattern[] the list of sub-patterns
7
local multipattern = {}
1✔
8

9

10
-- Multipattern indexing
11
-- For enabling syntax sugar multipattern:method
12
-- This retains the ability to index by number.
13
multipattern.__index = function(mp, key)
14
    if type(key) == "number" then
1,523✔
15
        return mp.components[key]
108✔
16
    else
17
        return multipattern[key]
1,415✔
18
    end
19
end
20

21

22
--- Create a new multipattern from a list of patterns.
23
---
24
--- ```lua
25
--- local mp = multipattern.new()
26
--- local mp2 = multipattern.new({p1, p2, p3})
27
--- ```
28
---@param components? forma.pattern[] an array of pattern objects
29
---@return forma.multipattern mp a new multipattern containing those patterns
30
function multipattern.new(components)
1✔
31
    local mp = {
60✔
32
        components = components or {}
60✔
33
    }
34
    mp = setmetatable(mp, multipattern)
60✔
35
    return mp
60✔
36
end
37

38
--- Clone the multipattern.
39
---
40
---@param mp forma.multipattern multipattern to clone
41
---@return forma.multipattern clone the cloned multipattern
42
function multipattern.clone(mp)
1✔
43
    local components = {}
1✔
44
    for i, p in ipairs(mp.components) do
1,265✔
45
        components[i] = p:clone()
1,264✔
46
    end
47
    return multipattern.new(components)
1✔
48
end
49

50
--- Merge multipatterns.
51
---
52
---@param ... forma.multipattern a table of multipatterns or a list of multipattern arguments
53
---@return forma.multipattern mp a new multipattern consisting of the set of all input components
54
function multipattern.merge(...)
1✔
55
    local multipatterns = { ... }
1✔
56
    if #multipatterns == 1 then
1✔
UNCOV
57
        if type(multipatterns[1]) == 'table' then
×
UNCOV
58
            multipatterns = multipatterns[1]
×
59
        end
60
    end
61
    if #multipatterns == 1 then
1✔
UNCOV
62
        return multipatterns[1]
×
63
    end
64
    local total = multipattern.new()
1✔
65
    for _, v in ipairs(multipatterns) do
3✔
66
        assert(getmetatable(v) == multipattern, "multipattern.merge requires multipatterns as arguments")
2✔
67
        for _, p in ipairs(v.components) do
5✔
68
            total:insert(p)
3✔
69
        end
70
    end
71
    return total
1✔
72
end
73

74
--- Insert a pattern into the multipattern.
75
---
76
---@param mp forma.multipattern multipattern to be operated upon
77
---@param ip forma.pattern the new pattern to insert
78
---@return forma.multipattern mp the multipattern
79
function multipattern.insert(mp, ip)
1✔
80
    assert(getmetatable(mp) == multipattern, "multipattern.insert requires a multipattern as the first argument")
1,351✔
81
    table.insert(mp.components, ip)
1,351✔
82
    return mp
1,351✔
83
end
84

85
--- Count the number of components in a multipattern.
86
---
87
---@param mp forma.multipattern the multipattern to count
88
---@return integer count the number of components
89
function multipattern.n_components(mp)
1✔
90
    assert(getmetatable(mp) == multipattern, "multipattern.n_components requires a multipattern as the first argument")
37✔
91
    return #mp.components
37✔
92
end
93

94
--- Map a function over all patterns in this multipattern.
95
--- Calls `fn(pattern, index)` for each sub-pattern, returning a new multipattern
96
--- of their results.
97
---
98
--- ```lua
99
--- local bigger = mp:map(function(p) return p:enlarge(2) end)
100
--- ```
101
---@param mp forma.multipattern the multipattern upon which to map the function
102
---@param fn fun(p: forma.pattern, i: integer): forma.pattern a function taking (pattern, index) and returning a new pattern
103
---@return forma.multipattern result a new multipattern of the mapped results
104
function multipattern.map(mp, fn)
1✔
105
    assert(getmetatable(mp) == multipattern, "multipattern.map requires a multipattern as an argument")
1✔
106
    local new_components = {}
1✔
107
    for i, pat in ipairs(mp.components) do
3✔
108
        new_components[i] = fn(pat, i)
2✔
109
    end
110
    return multipattern.new(new_components)
1✔
111
end
112

113
--- Filter out sub-patterns according to a predicate.
114
--- Keeps only those patterns for which `predicate(pattern) == true`.
115
---
116
--- ```lua
117
--- local bigSegs = mp:filter(function(p) return p:size() >= 10 end)
118
--- ```
119
---@param mp forma.multipattern the multipattern upon which to filter
120
---@param fn fun(p: forma.pattern): boolean a predicate function
121
---@return forma.multipattern result a new multipattern containing only the sub-patterns passing the test
122
function multipattern.filter(mp, fn)
1✔
123
    assert(getmetatable(mp) == multipattern, "multipattern.filter requires a multipattern as an argument")
14✔
124
    local new_components = {}
14✔
125
    for _, pat in ipairs(mp.components) do
75✔
126
        if fn(pat) then
61✔
127
            new_components[#new_components + 1] = pat
15✔
128
        end
129
    end
130
    return multipattern.new(new_components)
14✔
131
end
132

133
--- Apply a named method to each pattern, returning a new multipattern.
134
--- This is an alternative to `:map(...)` for calling an *existing* pattern method
135
--- by name on all sub-patterns. You may also supply arguments to that method.
136
---
137
--- Note that when used with a method that generates multipatterns (e.g. `connected_components`),
138
--- the results will be 'flattened' into a single multipattern.
139
---
140
--- ```lua
141
--- local translated = mp:apply("translate", 10, 5)
142
--- -- calls p:translate(10,5) on each pattern p
143
--- ```
144
---@param mp forma.multipattern the multipattern upon which to apply the method
145
---@param method string the name of a function in `pattern`
146
---@param ... any additional arguments to pass to that method
147
---@return forma.multipattern result a new multipattern of the method's results
148
function multipattern.apply(mp, method, ...)
1✔
149
    assert(getmetatable(mp) == multipattern, "multipattern.apply requires a multipattern as an argument")
2✔
150
    local pattern_mt = require('forma.pattern')
2✔
151
    local new_components = {}
2✔
152
    for _, pat in ipairs(mp.components) do
7✔
153
        local m = pat[method]
5✔
154
        assert(type(m) == "function", "No method named '" .. tostring(method) .. "' on pattern")
5✔
155
        local return_value = m(pat, ...)
5✔
156
        if getmetatable(return_value) == multipattern then
5✔
157
            for _, v in ipairs(return_value.components) do
9✔
158
                table.insert(new_components, v)
6✔
159
            end
160
        elseif getmetatable(return_value) == pattern_mt then
2✔
161
            table.insert(new_components, return_value)
2✔
162
        else
UNCOV
163
            assert(false, "Method must return a pattern or multipattern")
×
164
        end
165
    end
166
    return multipattern.new(new_components)
2✔
167
end
168

169
--- Union all sub-patterns into a single pattern.
170
--- Folds over the sub-patterns with the union (`+`) operator,
171
--- returning a single `pattern`.
172
---
173
--- ```lua
174
--- local combined = mp:union_all()
175
--- ```
176
---@param mp forma.multipattern the multipattern to union over
177
---@return forma.pattern pattern a single pattern combining all sub-patterns
178
function multipattern.union_all(mp)
1✔
179
    -- Require here to avoid circular dependency.
180
    local pattern = require('forma.pattern')
9✔
181
    if mp:n_components() == 0 then
9✔
UNCOV
182
        return pattern.new()
×
183
    else
184
        return pattern.union(mp.components)
9✔
185
    end
186
end
187

188
--- Print a multipattern.
189
--- Prints a multipattern to `io.output`. If provided, a table of subpattern labels
190
--- can be used, with one entry per subpattern.
191
---
192
---@param mp forma.multipattern the multipattern to be drawn
193
---@param chars? string[] the characters to be printed for each subpattern
194
---@param domain? forma.pattern the domain in which to print
195
function multipattern.print(mp, chars, domain)
1✔
UNCOV
196
    assert(getmetatable(mp) == multipattern, "multipattern.print requires a multipattern as a first argument")
×
UNCOV
197
    domain = domain or mp:union_all()
×
UNCOV
198
    assert(domain:size() > 0, "multipattern.print: domain must have at least one cell")
×
199

UNCOV
200
    local n = mp:n_components()
×
201
    -- If no dictionary is supplied generate a new one (starting from '0')
UNCOV
202
    if chars == nil then
×
UNCOV
203
        local start_char = 47
×
UNCOV
204
        assert(n < (200 - start_char), "multipattern.print: too many components")
×
UNCOV
205
        chars = {}
×
UNCOV
206
        for i = 1, n, 1 do
×
UNCOV
207
            table.insert(chars, string.char(i + start_char))
×
208
        end
209
    end
UNCOV
210
    assert(n == #chars,
×
UNCOV
211
        "multipattern.print: there must be as many character table entries as components")
×
212
    -- Print out the segments to a map
UNCOV
213
    for i = domain.min.y, domain.max.y, 1 do
×
UNCOV
214
        local row = {}
×
UNCOV
215
        for j = domain.min.x, domain.max.x, 1 do
×
UNCOV
216
            local token = ' '
×
UNCOV
217
            for k, v in ipairs(mp.components) do
×
UNCOV
218
                if v:has_cell(j, i) then token = chars[k] end
×
219
            end
UNCOV
220
            row[#row + 1] = token
×
221
        end
UNCOV
222
        io.write(table.concat(row) .. '\n')
×
223
    end
224
end
225

226
return multipattern
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc