tools: improve the v reduce parser for functions, and add more loops to reduce more (#23694)

This commit is contained in:
Eliyaan (Nopana) 2025-02-12 09:40:51 +01:00 committed by GitHub
parent 694cac9443
commit 6ed56eef64
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -44,15 +44,14 @@ fn main() {
content := os.read_file(file_path)! content := os.read_file(file_path)!
assert string_reproduces(content, error_msg, command) assert string_reproduces(content, error_msg, command)
show_code_stats(content, label: 'Original code size') show_code_stats(content, label: 'Original code size')
mut tree := parse(content)
// start tests // start tests
tmp_code := create_code(tree) tmp_code := create_code(parse(content))
assert string_reproduces(tmp_code, error_msg, command) assert string_reproduces(tmp_code, error_msg, command)
show_code_stats(tmp_code, label: 'Code size without comments') show_code_stats(tmp_code, label: 'Code size without comments')
// reduce the code // reduce the code
reduce_scope(mut tree, error_msg, command, do_fmt) reduce_scope(content, error_msg, command, do_fmt)
} }
// Return true if the command ran on the file produces the pattern // Return true if the command ran on the file produces the pattern
@ -77,6 +76,7 @@ type Elem = string | Scope
@[heap] @[heap]
struct Scope { struct Scope {
mut: mut:
fn_scope bool // contains a function (string: signature{}, children: function body)
ignored bool // is the scope ignored when creating the file ignored bool // is the scope ignored when creating the file
tmp_ignored bool // used when testing if it can be ignored in the file tmp_ignored bool // used when testing if it can be ignored in the file
children []Elem // code blocks (strings & children scope children []Elem // code blocks (strings & children scope
@ -96,6 +96,24 @@ fn parse(file string) Scope { // The parser is surely incomplete for the V synta
for file[i] != `\n` { // comment -> skip until newline for file[i] != `\n` { // comment -> skip until newline
i++ i++
} }
} else if file[i] == `\n` && file[i - 1] == `\n` {
i++ // remove excess newlines
} else if file[i] == `\t` {
i++ // remove tabs for easier processing
} else if file[i] == `f` && file[i + 1] == `n` && file[i + 2] == ` ` && file[i - 1] or {
`\n`
} == `\n` {
top.children << current_string
// no increase in scope because not handled with {}
current_string = ''
top.children << &Scope{
fn_scope: true
}
stack << &(top.children[top.children.len - 1] as Scope)
current_string += file[i].ascii_str() // f
i++
current_string += file[i].ascii_str() // n
i++
} else if file[i] == `/` && file[i + 1] == `*` { } else if file[i] == `/` && file[i + 1] == `*` {
i++ i++
i++ i++
@ -142,12 +160,23 @@ fn parse(file string) Scope { // The parser is surely incomplete for the V synta
} else if file[i] == `}` { } else if file[i] == `}` {
scope_level -= 1 scope_level -= 1
assert scope_level >= 0, 'The scopes are not well detected ${stack[0]}' assert scope_level >= 0, 'The scopes are not well detected ${stack[0]}'
top.children << current_string if current_string != '' {
top.children << current_string
}
if stack.last().children == [] {
stack[stack.len - 2].children.delete(stack[stack.len - 2].children.len - 1) // delete the empty scope (the last children because top of the stack)
}
stack.pop() stack.pop()
top = stack[stack.len - 1] top = stack[stack.len - 1]
current_string = '' current_string = ''
current_string += file[i].ascii_str() // } current_string += file[i].ascii_str() // }
i++ i++
if scope_level == 0 && stack.len == 2 { // the function and the body scope
top.children << current_string
stack.pop()
top = stack[stack.len - 1]
current_string = ''
}
} else { } else {
current_string += file[i].ascii_str() current_string += file[i].ascii_str()
i++ i++
@ -157,7 +186,7 @@ fn parse(file string) Scope { // The parser is surely incomplete for the V synta
top = stack[stack.len - 1] top = stack[stack.len - 1]
top.children << current_string // last part of the file top.children << current_string // last part of the file
assert scope_level == 0, 'The scopes are not well detected' assert scope_level == 0, 'The scopes are not well detected'
assert stack.len == 1, 'The stack should only have the BODY scope' assert stack.len == 1, 'The stack should only have the body scope'
return *stack[0] return *stack[0]
} }
@ -183,101 +212,112 @@ fn create_code(sc Scope) string {
} }
// Reduces the code contained in the scope tree and writes the reduced code to `rpdc.v` // Reduces the code contained in the scope tree and writes the reduced code to `rpdc.v`
fn reduce_scope(mut sc Scope, error_msg string, command string, do_fmt bool) { fn reduce_scope(content string, error_msg string, command string, do_fmt bool) {
mut sc := parse('') // will get filled in the start of the loop
println('Cleaning the scopes') println('Cleaning the scopes')
mut modified_smth := true // was a modification successful in reducing the code in the last iteration mut text_code := content
for modified_smth { // as long as there are successful modifications mut outer_modified_smth := true
modified_smth = false for outer_modified_smth {
println('NEXT ITERATION, loop 1') sc = parse(text_code)
mut stack := []&Elem{} outer_modified_smth = false
for i in 0 .. sc.children.len { mut modified_smth := true // was a modification successful in reducing the code in the last iteration
stack << &sc.children[i] for modified_smth { // as long as there are successful modifications
} modified_smth = false
for stack.len > 0 { // traverse the tree and disable (ignore) scopes that are not needed for reproduction println('NEXT ITERATION, loop 1')
mut item := stack.pop() mut stack := []&Elem{}
if mut item is Scope { for i in 0 .. sc.children.len {
if !item.ignored { stack << &sc.children[i]
item.tmp_ignored = true // try to ignore it }
code := create_code(sc) for stack.len > 0 { // traverse the tree and disable (ignore) scopes that are not needed for reproduction
item.tmp_ignored = false // dont need it anymore mut item := stack.pop()
if string_reproduces(code, error_msg, command) { if mut item is Scope {
item.ignored = true if !item.ignored {
modified_smth = true item.tmp_ignored = true // try to ignore it
show_code_stats(code) code := create_code(sc)
} else { // if can remove it, no need to go though it's children item.tmp_ignored = false // dont need it anymore
for i in 0 .. item.children.len { if string_reproduces(code, error_msg, command) {
stack << &item.children[i] item.ignored = true
modified_smth = true
outer_modified_smth = true
show_code_stats(code)
} else { // if can remove it, no need to go though it's children
for i in 0 .. item.children.len {
stack << &item.children[i]
}
} }
} }
} }
} }
} }
}
println('Processing remaining lines') text_code = create_code(sc)
tmp_code := create_code(sc).split_into_lines() // dont forget to add back the \n
// Create the binary tree of the lines println('Processing remaining lines')
depth := int(math.log2(tmp_code.len)) + 1 split_code := text_code.split_into_lines() // dont forget to add back the \n
mut c := 0 // Create the binary tree of the lines
mut line_stack := []&Scope{} depth := int(math.log2(split_code.len)) + 1
line_stack << &Scope{} mut c := 0
for c < tmp_code.len { mut line_stack := []&Scope{}
l1 := line_stack.len line_stack << &Scope{}
if l1 <= depth { // or equal because of the first node for c < split_code.len {
if line_stack[l1 - 1].children.len < 2 { l1 := line_stack.len
line_stack[l1 - 1].children << &Scope{} if l1 <= depth { // or equal because of the first node
l2 := line_stack[l1 - 1].children.len if line_stack[l1 - 1].children.len < 2 {
line_stack << &(line_stack[l1 - 1].children[l2 - 1] as Scope) line_stack[l1 - 1].children << &Scope{}
l2 := line_stack[l1 - 1].children.len
line_stack << &(line_stack[l1 - 1].children[l2 - 1] as Scope)
} else {
line_stack.pop()
}
} else { } else {
line_stack.pop() if line_stack[l1 - 1].children.len != 0 { // if there is already a string
} line_stack.pop()
} else { } else {
if line_stack[l1 - 1].children.len != 0 { // if there is already a string line_stack[l1 - 1].children << split_code[c] + '\n' // the \n were removed by the split
line_stack.pop() c++
} else { line_stack.pop() // already a string
line_stack[l1 - 1].children << tmp_code[c] + '\n' // the \n were removed by the split }
c++
line_stack.pop() // already a string
} }
} }
}
// Traverse the tree and prune the useless lines / line groups for the reproduction // Traverse the tree and prune the useless lines / line groups for the reproduction
mut line_tree := *line_stack[0] mut line_tree := *line_stack[0]
assert string_reproduces(create_code(line_tree), error_msg, command) // should be the same assert string_reproduces(create_code(line_tree), error_msg, command) // should be the same
println('Pruning the lines/line groups') println('Pruning the lines/line groups')
modified_smth = true modified_smth = true
for modified_smth { for modified_smth {
modified_smth = false modified_smth = false
println('NEXT ITERATION, loop 2') println('NEXT ITERATION, loop 2')
mut stack := []&Elem{} mut stack := []&Elem{}
for i in 0 .. line_tree.children.len { for i in 0 .. line_tree.children.len {
stack << &line_tree.children[i] stack << &line_tree.children[i]
} }
for stack.len > 0 { // traverse the binary tree (of the lines) for stack.len > 0 { // traverse the binary tree (of the lines)
mut item := stack.pop() mut item := stack.pop()
if mut item is Scope { if mut item is Scope {
if !item.ignored { if !item.ignored {
item.tmp_ignored = true item.tmp_ignored = true
code := create_code(line_tree) code := create_code(line_tree)
item.tmp_ignored = false // dont need it anymore item.tmp_ignored = false // dont need it anymore
if string_reproduces(code, error_msg, command) { if string_reproduces(code, error_msg, command) {
item.ignored = true item.ignored = true
modified_smth = true modified_smth = true
show_code_stats(code) outer_modified_smth = true
} else { // if can remove it, can remove it's children show_code_stats(code)
for i in 0 .. item.children.len { } else { // if can remove it, can remove it's children
stack << &item.children[i] for i in 0 .. item.children.len {
stack << &item.children[i]
}
} }
} }
} }
} }
} }
text_code = create_code(line_tree)
} }
mre := create_code(line_tree) // final minimal reproductible example assert string_reproduces(text_code, error_msg, command)
assert string_reproduces(mre, error_msg, command) os.write_file('rpdc.v', text_code) or { panic(err) }
os.write_file('rpdc.v', mre) or { panic(err) }
if do_fmt { if do_fmt {
os.execute('v fmt -w rpdc.v') os.execute('v fmt -w rpdc.v')
final_content := os.read_file('rpdc.v') or { panic(err) } final_content := os.read_file('rpdc.v') or { panic(err) }