<commit_before>class Math::Symbolic::Tree is rw;

has $.type;
has $.content;
has @.children;

# note only looks for first result
method contains (:$type, :$content) {
    die 'At least one of :type or :content is required' unless defined $type || defined $content;
    
    my $hit = True;
    $hit &&= ($type eq $.type) if defined $type;
    $hit &&= ($content eq $.content) if $hit && defined $content;
    
    return self if $hit;

    for @.children {
        return $_ if .contains(:$type, :$content);
    }

    return False;
}

method match (*%s) {
    for %s.keys {
        next if $_ eq 'children';
        my $criteria = %s{$_};
        my $value = self."$_"();
        return False unless $value ~~ $criteria;
    }

    if %s<children> :exists {
        my @child_criteria = %s<children>;
        for ^@child_criteria -> $child_i {
            my $child = @child_criteria[$child_i];
            #next unless $child ~~ Positional && $child.keys;
            next if $child ~~ Whatever;
            my %child_params = %$child;
            next unless %child_params;
            return False unless self.children[$child_i].match( |%child_params );
        }
    }

    return True;
}

method find (*%s) {
    return self if self.match(|%s);

    for @.children {
        my $result = $_.find(|%s);
        return $result if $result;
    }

    return;
}

method find_all (*%s) {
    my @results;
    @results.push: self if self.match(|%s);

    for @.children {
        my @child_results = $_.find_all(|%s);
        @results.push: @child_results;
    }

    return @results;
}

method count () {
    [+] @.children».count, 1
}

method Str () {
    return '' unless defined self;

    given $.type {
        when 'operation' {
            my $op = $.content;
            my $assoc = $op.function.associative;
            my $syn = $op.syntax // die "Error: No syntax to stringify for '{$op.name}' operations";
            my @args = @.children».Str;
            my $prec = $syn.precedence;
            if defined $prec {
                for ^@args -> $child_i {
                    my $child = @.children[$child_i];
                    if $child.type eq 'operation' {
                        my $this_op = $child.content;
                        my $this_syn = $this_op.syntax // die "Error: No syntax to stringify for '{$.content.name}' operations";
                        my $this_prec = $this_syn.precedence;
                        if defined($prec) && defined($this_prec) and (
                            $this_prec > $prec or
                            $child_i > 0 && ($this_prec == $prec) && (!$assoc or $this_op !=== $op)
                        ) {
                            # () here is a hard-coded hack and should be looked up as the first defined non-function circumfix
                            @args[$child_i] = '(' ~ @args[$child_i] ~ ')';
                        }
                    }
                }
            }
            return $syn.make_str(@args);
        }
        when 'relation' {
            return @.children».Str.join: $.content.Str;
        }
        when 'symbol' | 'value' {
            return $.content.Str;
        }
        default {
            die "Error: Can't stringify nodes of type '$.type'";
        }
    }
}

method Numeric () {
    +self.Str;
    CATCH { die "Cannot convert '{self}' to a number: a single constant is required; eliminate any remaining variables with .evaluate() first." }
}

method clone () {
    self.new( :$.type, :$.content, :children(@.children».clone) );
}


<commit_msg>Just a little revision for clarity<commit_after>class Math::Symbolic::Tree is rw;

has $.type;
has $.content;
has @.children;

# note only looks for first result
method contains (:$type, :$content) {
    die 'At least one of :type or :content is required' unless defined $type || defined $content;
    
    my $hit = True;
    $hit &&= ($type eq $.type) if defined $type;
    $hit &&= ($content eq $.content) if $hit && defined $content;
    
    return self if $hit;

    for @.children {
        return $_ if .contains(:$type, :$content);
    }

    return False;
}

method match (*%s) {
    for %s.keys {
        next if $_ eq 'children';
        my $criteria = %s{$_};
        my $value = self."$_"();
        return False unless $value ~~ $criteria;
    }

    if %s<children> :exists {
        my @child_criteria = %s<children>;
        for ^@child_criteria -> $child_i {
            my $child_criteria = @child_criteria[$child_i];
            #next unless $child ~~ Positional && $child.keys;
            next if $child_criteria ~~ Whatever;
            my %child_params = %$child_criteria;
            next unless %child_params;
            my $child = self.children[$child_i];
            return False unless $child.match( |%child_params );
        }
    }

    return True;
}

method find (*%s) {
    return self if self.match(|%s);

    for @.children {
        my $result = $_.find(|%s);
        return $result if $result;
    }

    return;
}

method find_all (*%s) {
    my @results;
    @results.push: self if self.match(|%s);

    for @.children {
        my @child_results = $_.find_all(|%s);
        @results.push: @child_results;
    }

    return @results;
}

method count () {
    [+] @.children».count, 1
}

method Str () {
    return '' unless defined self;

    given $.type {
        when 'operation' {
            my $op = $.content;
            my $assoc = $op.function.associative;
            my $syn = $op.syntax // die "Error: No syntax to stringify for '{$op.name}' operations";
            my @args = @.children».Str;
            my $prec = $syn.precedence;
            if defined $prec {
                for ^@args -> $child_i {
                    my $child = @.children[$child_i];
                    if $child.type eq 'operation' {
                        my $this_op = $child.content;
                        my $this_syn = $this_op.syntax // die "Error: No syntax to stringify for '{$.content.name}' operations";
                        my $this_prec = $this_syn.precedence;
                        if defined($prec) && defined($this_prec) and (
                            $this_prec > $prec or
                            $child_i > 0 && ($this_prec == $prec) && (!$assoc or $this_op !=== $op)
                        ) {
                            # () here is a hard-coded hack and should be looked up as the first defined non-function circumfix
                            @args[$child_i] = '(' ~ @args[$child_i] ~ ')';
                        }
                    }
                }
            }
            return $syn.make_str(@args);
        }
        when 'relation' {
            return @.children».Str.join: $.content.Str;
        }
        when 'symbol' | 'value' {
            return $.content.Str;
        }
        default {
            die "Error: Can't stringify nodes of type '$.type'";
        }
    }
}

method Numeric () {
    +self.Str;
    CATCH { die "Cannot convert '{self}' to a number: a single constant is required; eliminate any remaining variables with .evaluate() first." }
}

method clone () {
    self.new( :$.type, :$.content, :children(@.children».clone) );
}



{'fraction_non_alphanumeric': 0.13996112191058038, 'fraction_numerical': 0.0005554012774229381, 'mean_word_length': 1.6816827997021593, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1044812', 'n_tokens_mistral': 2168, 'n_tokens_neox': 2114, 'n_words': 638}