def _(*reps: RowRepeat) -> Knittable: if not all( map(lambda item: len(set(map(type, item))) == 1, _padded_zip(*map(attrgetter("rows"), reps)))): # Unroll all row repeats if we see a row and a row repeat side-by-side. # This is conservative, but repetitive output can be fixed up by # _roll_repeated_rows. # # noinspection PyTypeChecker rows = list( starmap( _merge_across, _padded_zip(*map(attrgetter("rows"), map(partial(_flatten, unroll=True), reps))))) return RowRepeat(rows=rows, times=NaturalLit.of(1), consumes=rows[0].consumes, produces=rows[-1].produces, sources=list(_flat_map(attrgetter("sources"), reps))) # Find the smallest number of rows that all row repeats can be expanded to. num_rows = _lcm(*map(lambda rep: sum(map(count_rows, rep.rows)), reps)) def expand(rep: RowRepeat) -> Iterator[Node]: times = min(rep.times.value, num_rows // sum(map(count_rows, rep.rows))) return _repeat_rows(rep.rows, times) rows = list(starmap(_merge_across, _padded_zip(*map(expand, reps)))) return RowRepeat(rows=rows, times=NaturalLit.of( ceil(max(map(count_rows, reps)) / num_rows)), consumes=rows[0].consumes, produces=rows[-1].produces, sources=list(_flat_map(attrgetter("sources"), reps)))
def _(rep: FixedStitchRepeat, unroll: bool = False) -> Node: # Cases where the only node a fixed stitch repeat contains is another fixed # stitch repeat should be flattened by multiplying the repeat times # together. if (rep.times.value != 1 and len(rep.stitches) == 1 and isinstance(rep.stitches[0], FixedStitchRepeat)): first = rep.stitches[0] assert isinstance(first, FixedStitchRepeat) # noinspection PyTypeChecker return ast_map( replace(rep, stitches=first.stitches, times=NaturalLit.of(first.times.value * rep.times.value), consumes=first.consumes * rep.times.value, produces=first.produces * rep.times.value), partial(_flatten, unroll=unroll)) else: stitches = [] # noinspection PyTypeChecker for stitch in map(partial(_flatten, unroll=unroll), rep.stitches): if (isinstance(stitch, FixedStitchRepeat) and stitch.times.value == 1): # Un-nest fixed stitch repeats that only repeat once. stitches.extend(stitch.stitches) else: stitches.append(stitch) return replace(rep, stitches=stitches)
def _(expanding: KnitScriptParser.ExpandingStitchRepeatContext) -> Node: return ExpandingStitchRepeat( stitches=list(map(build_ast, _get_stitches(expanding))), to_last=(build_ast(expanding.toLast) if expanding.toLast else NaturalLit.of(0)), consumes=None, produces=None, sources=[_get_source(expanding)])
def _normalize_row_repeat(rep: RowRepeat) -> RowRepeat: # Make sure every row repeat has an even number of rows inside of it, so # the knitter doesn't have to reverse rows in their head every other # iteration. if not (rep.times.value > 1 and _has_explicit_sides(rep) and sum(map(count_rows, rep.rows)) % 2 != 0): return rep twice = replace(rep, rows=list(_repeat_rows(rep.rows, 2)), times=NaturalLit.of(rep.times.value // 2)) if rep.times.value % 2 == 0: return twice else: return RowRepeat(rows=[twice, *rep.rows], times=NaturalLit.of(1), consumes=rep.consumes, produces=rep.produces, sources=rep.sources)
def _(expanding: ExpandingStitchRepeat, n: int) -> Node: # noinspection PyTypeChecker return replace( expanding, stitches=list( map(partial(_increase_expanding_repeats, n=n), expanding.stitches)), to_last=NaturalLit.of(expanding.to_last.value + n), )
def _(row: Row, times: int) -> Node: return replace(row, stitches=[ FixedStitchRepeat(stitches=row.stitches, times=NaturalLit.of(times), consumes=row.consumes * times, produces=row.produces * times, sources=row.sources) ], consumes=row.consumes * times, produces=row.produces * times)
def fill(pattern: Pattern, width: int, height: int) -> Node: """ Repeats a pattern horizontally and vertically to fill a box. :param pattern: the pattern to repeat :param width: the width of the box (number of stitches) :param height: the height of the box (number of rows) :return: the repeated pattern """ pattern = infer_counts(pattern) assert isinstance(pattern, Pattern) stitches = max(map(attrgetter("consumes"), pattern.rows)) rows = count_rows(pattern) if (width % stitches != 0 or width < stitches or height % rows != 0 or height < rows): raise InterpretError( f"{stitches}×{rows} pattern does not fit evenly into " + f"{width}×{height} fill box", pattern) n = width // stitches m = height // rows return replace(pattern, rows=[ RowRepeat(rows=[ FixedBlockRepeat(block=Block( patterns=[pattern], consumes=pattern.consumes, produces=pattern.produces, sources=pattern.sources), times=NaturalLit.of(n), consumes=pattern.consumes * n, produces=pattern.produces * n, sources=pattern.sources) ], times=NaturalLit.of(m), consumes=pattern.consumes * n, produces=pattern.produces * n, sources=pattern.sources) ], consumes=pattern.consumes * n, produces=pattern.produces * n)
def _(rep: RowRepeat, unroll: bool = False) -> Node: flattened_rows = [] # noinspection PyTypeChecker for row in map(partial(_flatten, unroll=unroll), rep.rows): if isinstance(row, RowRepeat) and (unroll or row.times.value <= 1): flattened_rows.extend(_repeat_rows(row.rows, row.times.value)) elif isinstance(row, Pattern): flattened_rows.extend(row.rows) else: flattened_rows.append(row) if (rep.times.value > 1 and len(flattened_rows) == 1 and isinstance(flattened_rows[0], RowRepeat)): # Cases where the only node a row repeat contains is another row # repeat should be flattened by multiplying the repeat times together. return _normalize_row_repeat( replace(rep, rows=flattened_rows[0].rows, times=NaturalLit.of(rep.times.value * flattened_rows[0].times.value))) return _normalize_row_repeat(replace(rep, rows=flattened_rows))
def roll(rows): if not rows: return [] for size in range(1, len(rows) // 2 + 1): sections = _chunks(rows, size) first = next(sections) # noinspection PyTypeChecker times = len( list(takewhile(partial(_eq_ignore_sides, first), sections))) if times > 0: rolled = RowRepeat(rows=first, times=NaturalLit.of(times + 1), consumes=first[0].consumes, produces=first[-1].produces, sources=list( _flat_map(attrgetter("sources"), first))) # Only roll up if the total number of rows is greater than some # threshold to avoid creating lots of little row repeats. if count_rows(rolled) >= 4: return [rolled, *roll(rows[size * (times + 1):])] return [rows[0], *roll(rows[1:])]
def combine(acc, node): try: current_stitch, current_times = get_stitch(node) last_stitch, last_times = get_stitch(acc[-1]) except (IndexError, TypeError, ValueError): return acc + [node] if current_stitch != last_stitch: return acc + [node] times = current_times + last_times return acc[:-1] + [ FixedStitchRepeat(stitches=[ StitchLit(value=current_stitch, consumes=current_stitch.consumes, produces=current_stitch.produces, sources=acc[-1].sources + node.sources) ], times=NaturalLit.of(times), consumes=current_stitch.consumes * times, produces=current_stitch.produces * times, sources=acc[-1].sources + node.sources) ]
def _(pattern: Pattern, times: int = 1) -> Node: return RowRepeat(rows=pattern.rows, times=NaturalLit.of(times), consumes=pattern.consumes, produces=pattern.produces, sources=pattern.sources)
def _(rep: ExpandingStitchRepeat) -> Node: return FixedStitchRepeat(stitches=rep.stitches, times=NaturalLit.of(1), consumes=None, produces=None, sources=rep.sources)
def _(row: Row) -> Node: return FixedStitchRepeat(stitches=row.stitches, times=NaturalLit.of(1), consumes=row.consumes, produces=row.produces, sources=row.sources)
def _(rep: ExpandingStitchRepeat, before: int) -> Node: fixed = _reverse(to_fixed_repeat(rep), before) assert isinstance(fixed, FixedStitchRepeat) return replace(rep, stitches=fixed.stitches, to_last=NaturalLit.of(before))