Initial commit: Klammertext source distribution
Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
147
sks/table/sequences.py
Normal file
147
sks/table/sequences.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import sys
|
||||
import re
|
||||
|
||||
syntax_description = """
|
||||
|
||||
A "sequence" is an integer (the "index") followed by an optional description of one
|
||||
or more sequence subsets. A subset is defined by a series of subset
|
||||
descriptions, separated by a comma. A subset description is either an integer,
|
||||
two integers separated by a hypen to indicate a range, or an integer followed
|
||||
only by a hyphen, which will include all the following elements of the sequence to
|
||||
the end. No spaces are allowed in a sequence. All indices are zero-origin.
|
||||
|
||||
Sequence examples for an index of "3":
|
||||
3
|
||||
3(1)
|
||||
3(0-4)
|
||||
3(1-4,6-9)
|
||||
3(5-)
|
||||
|
||||
Note that some sequence subsets must include two numbers, for example, border
|
||||
lines in a table.
|
||||
|
||||
""".strip()
|
||||
|
||||
|
||||
class Sequence:
|
||||
def __init__(self, count, maxval, spec):
|
||||
def parse_range(match):
|
||||
start, hyphen, end = match.groups()
|
||||
if hyphen is None and end is None:
|
||||
end = start
|
||||
elif end is None:
|
||||
end = maxval
|
||||
return [int(start), int(end)]
|
||||
self.maxval = maxval
|
||||
self.all = True
|
||||
subset_pat = "[-\\d,]+"
|
||||
sequence_rgx = re.compile(fr"(\d+)(\({subset_pat}\))*")
|
||||
match = sequence_rgx.match(spec)
|
||||
self.spec = spec
|
||||
if match and match.group(0) == spec:
|
||||
range_rgx = re.compile(r"(\d+)(-)?(\d+)?")
|
||||
self.index = int(match.group(1))
|
||||
if (match.group(2)):
|
||||
self.subsets = match.group(2).strip("()").split(",")
|
||||
matches = [range_rgx.match(e) for e in self.subsets]
|
||||
self.ranges = [parse_range(e) if e else None for e in matches]
|
||||
self.all = False
|
||||
else:
|
||||
self.ranges = [[0, self.maxval]]
|
||||
else:
|
||||
print(f'The sequence specification "{spec}" is incorrect.\n\n{syntax_description}\n')
|
||||
sys.exit(1)
|
||||
|
||||
def __str__(self):
|
||||
#subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||||
subsets = "all" if False else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges])
|
||||
return f"{self.index}[{subsets}]"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def has(self, i):
|
||||
for start, end in self.ranges:
|
||||
if i >= start and i <= end:
|
||||
return True
|
||||
return False
|
||||
|
||||
def items(self, invert=False):
|
||||
result = []
|
||||
for start,end in self.ranges:
|
||||
for e in range(start, end+1):
|
||||
result.append((e, self.index) if invert else (self.index, e))
|
||||
return result
|
||||
|
||||
class Sequences:
|
||||
def __init__(self, count, maxval, sequence_specs):
|
||||
self.count = count
|
||||
self.maxval = maxval
|
||||
self.sequences = {}
|
||||
match_all = re.compile(r"\*(.*)").match(sequence_specs)
|
||||
match_some = re.compile(r"\[(\d+)-(\d+)\](.*)").match(sequence_specs)
|
||||
spec_list = []
|
||||
if match_all:
|
||||
subseq = match_all.group(1)
|
||||
for i in range(count):
|
||||
spec_list.append(f"{i}{subseq}")
|
||||
elif match_some:
|
||||
start = int(match_some.group(1))
|
||||
end = int(match_some.group(2))
|
||||
subseq = match_some.group(3)
|
||||
for i in range(start, end+1):
|
||||
spec_list.append(f"{i}{subseq}")
|
||||
else:
|
||||
spec_list = sequence_specs.split()
|
||||
for specs in spec_list:
|
||||
for spec in self.parse_spec(specs):
|
||||
self.sequences[spec.index] = spec
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.sequences.get(index)
|
||||
|
||||
def has(self, index, subseq_index):
|
||||
return self[index].has(subseq_index) if self[index] else None
|
||||
|
||||
def parse_spec(self, spec):
|
||||
named_spec = { "last" : [str(self.count-1)],
|
||||
"outer" : ["0", str(self.count-1)],
|
||||
"inner" : [str(e) for e in range(1, self.count - 1)],
|
||||
"all" : [str(e) for e in range(0, self.count)]
|
||||
}.get(spec)
|
||||
if named_spec is None:
|
||||
return [Sequence(self.count, self.maxval, spec)]
|
||||
else:
|
||||
return [Sequence(self.count, self.maxval, e) for e in named_spec]
|
||||
|
||||
def items(self, invert=False):
|
||||
result = []
|
||||
for seq in self.sequences:
|
||||
result += self.sequences[seq].items(invert)
|
||||
return set(result)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pprint
|
||||
for s in [
|
||||
Sequence(10, "0"),
|
||||
Sequence(10, "1"),
|
||||
Sequence(10, "2(2)"),
|
||||
Sequence(10, "3(2-)"),
|
||||
Sequence(10, "4(2-4)"),
|
||||
Sequence(10, "5(2-4)"),
|
||||
Sequence(10, "6(2-4,6)"),
|
||||
Sequence(10, "7(2,6-8)"),
|
||||
Sequence(10, "8(2,7-8,9-11,13-14)")]:
|
||||
print(s.spec, "->", s)
|
||||
|
||||
print("Sequences")
|
||||
S = Sequences(10, "0")
|
||||
for name in "top bottom head outer inner all 1(2-3)".split():
|
||||
print(name, "->", S.parse_spec(name))
|
||||
|
||||
print("Instantiate:")
|
||||
s = Sequences(10, "3(1-2,4-5) 4(8-9)")
|
||||
|
||||
#print(s.items(True))
|
||||
print(s)
|
||||
print(s.has(3,1))
|
||||
Reference in New Issue
Block a user