implement more code functionalities
This commit is contained in:
@@ -35,4 +35,39 @@ for struct in structs {
|
||||
}
|
||||
```
|
||||
|
||||
The [openrpc/docgen](../openrpc/docgen/) module demonstrates a good use case, where codemodels are serialized into JSON schema's, to generate an OpenRPC description document from a client in v.
|
||||
The [openrpc/docgen](../openrpc/docgen/) module demonstrates a good use case, where codemodels are serialized into JSON schema's, to generate an OpenRPC description document from a client in v.## V Language Utilities
|
||||
|
||||
The `vlang_utils.v` file provides a set of utility functions for working with V language files and code. These utilities are useful for:
|
||||
|
||||
1. **File Operations**
|
||||
- `list_v_files(dir string) ![]string` - Lists all V files in a directory, excluding generated files
|
||||
- `get_module_dir(mod string) string` - Converts a V module path to a directory path
|
||||
|
||||
2. **Code Inspection and Analysis**
|
||||
- `get_function_from_file(file_path string, function_name string) !string` - Extracts a function definition from a file
|
||||
- `get_function_from_module(module_path string, function_name string) !string` - Searches for a function across all files in a module
|
||||
- `get_type_from_module(module_path string, type_name string) !string` - Searches for a type definition across all files in a module
|
||||
|
||||
3. **V Language Tools**
|
||||
- `vtest(fullpath string) !string` - Runs V tests on files or directories
|
||||
- `vvet(fullpath string) !string` - Runs V vet on files or directories
|
||||
|
||||
### Example Usage
|
||||
|
||||
```v
|
||||
// Find and extract a function definition
|
||||
function_def := code.get_function_from_module('/path/to/module', 'my_function') or {
|
||||
eprintln('Could not find function: ${err}')
|
||||
return
|
||||
}
|
||||
println(function_def)
|
||||
|
||||
// Run tests on a directory
|
||||
test_results := code.vtest('/path/to/module') or {
|
||||
eprintln('Tests failed: ${err}')
|
||||
return
|
||||
}
|
||||
println(test_results)
|
||||
```
|
||||
|
||||
These utilities are particularly useful when working with code generation, static analysis, or when building developer tools that need to inspect V code.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module code
|
||||
|
||||
import log
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import os
|
||||
@@ -8,7 +9,6 @@ pub interface IFile {
|
||||
write(string, WriteOptions) !
|
||||
write_str(WriteOptions) !string
|
||||
name string
|
||||
write(string, WriteOptions) !
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
@@ -132,6 +132,7 @@ pub fn (code VFile) write_str(options WriteOptions) !string {
|
||||
}
|
||||
|
||||
pub fn (file VFile) get_function(name string) ?Function {
|
||||
log.error('Looking for function ${name} in file ${file.name}')
|
||||
functions := file.items.filter(it is Function).map(it as Function)
|
||||
target_lst := functions.filter(it.name == name)
|
||||
|
||||
@@ -161,3 +162,120 @@ pub fn (file VFile) functions() []Function {
|
||||
pub fn (file VFile) structs() []Struct {
|
||||
return file.items.filter(it is Struct).map(it as Struct)
|
||||
}
|
||||
|
||||
// parse_vfile parses V code into a VFile struct
|
||||
// It extracts the module name, imports, constants, structs, and functions
|
||||
pub fn parse_vfile(code string) !VFile {
|
||||
mut vfile := VFile{
|
||||
content: code
|
||||
}
|
||||
|
||||
lines := code.split_into_lines()
|
||||
|
||||
// Extract module name
|
||||
for line in lines {
|
||||
trimmed := line.trim_space()
|
||||
if trimmed.starts_with('module ') {
|
||||
vfile.mod = trimmed.trim_string_left('module ').trim_space()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Extract imports
|
||||
for line in lines {
|
||||
trimmed := line.trim_space()
|
||||
if trimmed.starts_with('import ') {
|
||||
import_obj := parse_import(trimmed)
|
||||
vfile.imports << import_obj
|
||||
}
|
||||
}
|
||||
|
||||
// Extract constants
|
||||
vfile.consts = parse_consts(code) or { []Const{} }
|
||||
|
||||
// Split code into chunks for parsing structs and functions
|
||||
mut chunks := []string{}
|
||||
mut current_chunk := ''
|
||||
mut brace_count := 0
|
||||
mut in_struct_or_fn := false
|
||||
mut comment_block := []string{}
|
||||
|
||||
for line in lines {
|
||||
trimmed := line.trim_space()
|
||||
|
||||
// Collect comments
|
||||
if trimmed.starts_with('//') && !in_struct_or_fn {
|
||||
comment_block << line
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for struct or function start
|
||||
if (trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ') ||
|
||||
trimmed.starts_with('fn ') || trimmed.starts_with('pub fn ')) && !in_struct_or_fn {
|
||||
in_struct_or_fn = true
|
||||
current_chunk = comment_block.join('\n')
|
||||
if current_chunk != '' {
|
||||
current_chunk += '\n'
|
||||
}
|
||||
current_chunk += line
|
||||
comment_block = []string{}
|
||||
|
||||
if line.contains('{') {
|
||||
brace_count += line.count('{')
|
||||
}
|
||||
if line.contains('}') {
|
||||
brace_count -= line.count('}')
|
||||
}
|
||||
|
||||
if brace_count == 0 {
|
||||
// Single line definition
|
||||
chunks << current_chunk
|
||||
current_chunk = ''
|
||||
in_struct_or_fn = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Add line to current chunk if we're inside a struct or function
|
||||
if in_struct_or_fn {
|
||||
current_chunk += '\n' + line
|
||||
|
||||
if line.contains('{') {
|
||||
brace_count += line.count('{')
|
||||
}
|
||||
if line.contains('}') {
|
||||
brace_count -= line.count('}')
|
||||
}
|
||||
|
||||
// Check if we've reached the end of the struct or function
|
||||
if brace_count == 0 {
|
||||
chunks << current_chunk
|
||||
current_chunk = ''
|
||||
in_struct_or_fn = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse each chunk and add to items
|
||||
for chunk in chunks {
|
||||
trimmed := chunk.trim_space()
|
||||
|
||||
if trimmed.contains('struct ') || trimmed.contains('pub struct ') {
|
||||
// Parse struct
|
||||
struct_obj := parse_struct(chunk) or {
|
||||
// Skip invalid structs
|
||||
continue
|
||||
}
|
||||
vfile.items << struct_obj
|
||||
} else if trimmed.contains('fn ') || trimmed.contains('pub fn ') {
|
||||
// Parse function
|
||||
fn_obj := parse_function(chunk) or {
|
||||
// Skip invalid functions
|
||||
continue
|
||||
}
|
||||
vfile.items << fn_obj
|
||||
}
|
||||
}
|
||||
|
||||
return vfile
|
||||
}
|
||||
|
||||
87
lib/core/code/model_file_test.v
Normal file
87
lib/core/code/model_file_test.v
Normal file
@@ -0,0 +1,87 @@
|
||||
module code
|
||||
|
||||
fn test_parse_vfile() {
|
||||
code := '
|
||||
module test
|
||||
|
||||
import os
|
||||
import strings
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
const (
|
||||
VERSION = \'1.0.0\'
|
||||
DEBUG = true
|
||||
)
|
||||
|
||||
pub struct Person {
|
||||
pub mut:
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
// greet returns a greeting message
|
||||
pub fn (p Person) greet() string {
|
||||
return \'Hello, my name is \${p.name} and I am \${p.age} years old\'
|
||||
}
|
||||
|
||||
// create_person creates a new Person instance
|
||||
pub fn create_person(name string, age int) Person {
|
||||
return Person{
|
||||
name: name
|
||||
age: age
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
vfile := parse_vfile(code) or {
|
||||
assert false, 'Failed to parse VFile: ${err}'
|
||||
return
|
||||
}
|
||||
|
||||
// Test module name
|
||||
assert vfile.mod == 'test'
|
||||
|
||||
// Test imports
|
||||
assert vfile.imports.len == 3
|
||||
assert vfile.imports[0].mod == 'os'
|
||||
assert vfile.imports[1].mod == 'strings'
|
||||
assert vfile.imports[2].mod == 'freeflowuniverse.herolib.core.texttools'
|
||||
|
||||
// Test constants
|
||||
assert vfile.consts.len == 2
|
||||
assert vfile.consts[0].name == 'VERSION'
|
||||
assert vfile.consts[0].value == '\'1.0.0\''
|
||||
assert vfile.consts[1].name == 'DEBUG'
|
||||
assert vfile.consts[1].value == 'true'
|
||||
|
||||
// Test structs
|
||||
structs := vfile.structs()
|
||||
assert structs.len == 1
|
||||
assert structs[0].name == 'Person'
|
||||
assert structs[0].is_pub == true
|
||||
assert structs[0].fields.len == 2
|
||||
assert structs[0].fields[0].name == 'name'
|
||||
assert structs[0].fields[0].typ.vgen() == 'string'
|
||||
assert structs[0].fields[1].name == 'age'
|
||||
assert structs[0].fields[1].typ.vgen() == 'int'
|
||||
|
||||
// Test functions
|
||||
functions := vfile.functions()
|
||||
assert functions.len == 2
|
||||
|
||||
// Test method
|
||||
assert functions[0].name == 'greet'
|
||||
assert functions[0].is_pub == true
|
||||
assert functions[0].receiver.typ.vgen() == 'Person'
|
||||
assert functions[0].result.typ.vgen() == 'string'
|
||||
|
||||
// Test standalone function
|
||||
assert functions[1].name == 'create_person'
|
||||
assert functions[1].is_pub == true
|
||||
assert functions[1].params.len == 2
|
||||
assert functions[1].params[0].name == 'name'
|
||||
assert functions[1].params[0].typ.vgen() == 'string'
|
||||
assert functions[1].params[1].name == 'age'
|
||||
assert functions[1].params[1].typ.vgen() == 'int'
|
||||
assert functions[1].result.typ.vgen() == 'Person'
|
||||
}
|
||||
@@ -43,35 +43,28 @@ pub fn (mod Module) write(path string, options WriteOptions) ! {
|
||||
}
|
||||
|
||||
for file in mod.files {
|
||||
console.print_debug('mod file write ${file.name}')
|
||||
file.write(module_dir.path, options)!
|
||||
}
|
||||
|
||||
for folder in mod.folders {
|
||||
console.print_debug('mod folder write ${folder.name}')
|
||||
folder.write('${path}/${mod.name}', options)!
|
||||
}
|
||||
|
||||
for mod_ in mod.modules {
|
||||
console.print_debug('mod write ${mod_.name}')
|
||||
mod_.write('${path}/${mod.name}', options)!
|
||||
}
|
||||
|
||||
if options.format {
|
||||
console.print_debug('format ${module_dir.path}')
|
||||
os.execute('v fmt -w ${module_dir.path}')
|
||||
}
|
||||
if options.compile {
|
||||
console.print_debug('compile shared ${module_dir.path}')
|
||||
os.execute_opt('${pre} -shared ${module_dir.path}') or { log.fatal(err.msg()) }
|
||||
}
|
||||
if options.test {
|
||||
console.print_debug('test ${module_dir.path}')
|
||||
os.execute_opt('${pre} test ${module_dir.path}') or { log.fatal(err.msg()) }
|
||||
}
|
||||
if options.document {
|
||||
docs_path := '${path}/${mod.name}/docs'
|
||||
console.print_debug('document ${module_dir.path}')
|
||||
os.execute('v doc -f html -o ${docs_path} ${module_dir.path}')
|
||||
}
|
||||
|
||||
@@ -82,7 +75,6 @@ pub fn (mod Module) write(path string, options WriteOptions) ! {
|
||||
pub fn (mod Module) write_str() !string {
|
||||
mut out := ''
|
||||
for file in mod.files {
|
||||
console.print_debug("mod file write ${file.name}")
|
||||
out += file.write_str()!
|
||||
}
|
||||
|
||||
|
||||
345
lib/core/code/vlang_utils.v
Normal file
345
lib/core/code/vlang_utils.v
Normal file
@@ -0,0 +1,345 @@
|
||||
module code
|
||||
|
||||
import freeflowuniverse.herolib.mcp
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
import os
|
||||
import log
|
||||
|
||||
// ===== FILE AND DIRECTORY OPERATIONS =====
|
||||
|
||||
// list_v_files returns all .v files in a directory (non-recursive), excluding generated files ending with _.v
|
||||
// ARGS:
|
||||
// dir string - directory path to search
|
||||
// RETURNS:
|
||||
// []string - list of absolute paths to V files
|
||||
pub fn list_v_files(dir string) ![]string {
|
||||
files := os.ls(dir) or { return error('Error listing directory: ${err}') }
|
||||
|
||||
mut v_files := []string{}
|
||||
for file in files {
|
||||
if file.ends_with('.v') && !file.ends_with('_.v') {
|
||||
filepath := os.join_path(dir, file)
|
||||
v_files << filepath
|
||||
}
|
||||
}
|
||||
|
||||
return v_files
|
||||
}
|
||||
|
||||
// get_module_dir converts a V module path to a directory path
|
||||
// ARGS:
|
||||
// mod string - module name (e.g., 'freeflowuniverse.herolib.mcp')
|
||||
// RETURNS:
|
||||
// string - absolute path to the module directory
|
||||
pub fn get_module_dir(mod string) string {
|
||||
module_parts := mod.trim_string_left('freeflowuniverse.herolib').split('.')
|
||||
return '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/${module_parts.join('/')}'
|
||||
}
|
||||
|
||||
// ===== CODE PARSING UTILITIES =====
|
||||
|
||||
// find_closing_brace finds the position of the closing brace that matches an opening brace
|
||||
// ARGS:
|
||||
// content string - the string to search in
|
||||
// start_i int - the position after the opening brace
|
||||
// RETURNS:
|
||||
// ?int - position of the matching closing brace, or none if not found
|
||||
fn find_closing_brace(content string, start_i int) ?int {
|
||||
mut brace_count := 1
|
||||
for i := start_i; i < content.len; i++ {
|
||||
if content[i] == `{` {
|
||||
brace_count++
|
||||
} else if content[i] == `}` {
|
||||
brace_count--
|
||||
if brace_count == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// get_function_from_file parses a V file and extracts a specific function block including its comments
|
||||
// ARGS:
|
||||
// file_path string - path to the V file
|
||||
// function_name string - name of the function to extract
|
||||
// RETURNS:
|
||||
// string - the function block including comments, or error if not found
|
||||
// pub fn get_function_from_file(file_path string, function_name string) !string {
|
||||
// log.error('Looking for function ${function_name} in file ${file_path}')
|
||||
// content := os.read_file(file_path) or {
|
||||
// return error('Failed to read file: ${file_path}: ${err}')
|
||||
// }
|
||||
|
||||
// vfile := parse_vfile(content) or {
|
||||
// return error('Failed to parse V file: ${file_path}: ${err}')
|
||||
// }
|
||||
|
||||
// for fn in vfile.functions() {
|
||||
// if fn.name == function_name {
|
||||
// return fn.code
|
||||
// }
|
||||
// }
|
||||
|
||||
// return error('Function ${function_name} not found in ${file_path}')
|
||||
// }
|
||||
|
||||
// lines := content.split_into_lines()
|
||||
// mut result := []string{}
|
||||
// mut in_function := false
|
||||
// mut brace_count := 0
|
||||
// mut comment_block := []string{}
|
||||
|
||||
// for i, line in lines {
|
||||
// trimmed := line.trim_space()
|
||||
|
||||
// // Collect comments that might be above the function
|
||||
// if trimmed.starts_with('//') {
|
||||
// if !in_function {
|
||||
// comment_block << line
|
||||
// } else if brace_count > 0 {
|
||||
// result << line
|
||||
// }
|
||||
// continue
|
||||
// }
|
||||
|
||||
// // Check if we found the function
|
||||
// if !in_function && (trimmed.starts_with('fn ${function_name}(')
|
||||
// || trimmed.starts_with('pub fn ${function_name}(')) {
|
||||
// in_function = true
|
||||
// // Add collected comments
|
||||
// result << comment_block
|
||||
// comment_block = []
|
||||
// result << line
|
||||
// if line.contains('{') {
|
||||
// brace_count++
|
||||
// }
|
||||
// continue
|
||||
// }
|
||||
|
||||
// // If we're inside the function, keep track of braces
|
||||
// if in_function {
|
||||
// result << line
|
||||
|
||||
// for c in line {
|
||||
// if c == `{` {
|
||||
// brace_count++
|
||||
// } else if c == `}` {
|
||||
// brace_count--
|
||||
// }
|
||||
// }
|
||||
|
||||
// // If brace_count is 0, we've reached the end of the function
|
||||
// if brace_count == 0 && trimmed.contains('}') {
|
||||
// return result.join('\n')
|
||||
// }
|
||||
// } else {
|
||||
// // Reset comment block if we pass a blank line
|
||||
// if trimmed == '' {
|
||||
// comment_block = []
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !in_function {
|
||||
// return error('Function "${function_name}" not found in ${file_path}')
|
||||
// }
|
||||
|
||||
// return result.join('\n')
|
||||
// }
|
||||
|
||||
// get_function_from_module searches for a function in all V files within a module
|
||||
// ARGS:
|
||||
// module_path string - path to the module directory
|
||||
// function_name string - name of the function to find
|
||||
// RETURNS:
|
||||
// string - the function definition if found, or error if not found
|
||||
pub fn get_function_from_module(module_path string, function_name string) !Function {
|
||||
v_files := list_v_files(module_path) or {
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
log.error('Found ${v_files} V files in ${module_path}')
|
||||
for v_file in v_files {
|
||||
// Read the file content
|
||||
content := os.read_file(v_file) or {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse the file
|
||||
vfile := parse_vfile(content) or {
|
||||
continue
|
||||
}
|
||||
|
||||
// Look for the function
|
||||
if fn_obj := vfile.get_function(function_name) {
|
||||
return fn_obj
|
||||
}
|
||||
}
|
||||
|
||||
return error('function ${function_name} not found in module ${module_path}')
|
||||
}
|
||||
|
||||
// get_type_from_module searches for a type definition in all V files within a module
|
||||
// ARGS:
|
||||
// module_path string - path to the module directory
|
||||
// type_name string - name of the type to find
|
||||
// RETURNS:
|
||||
// string - the type definition if found, or error if not found
|
||||
pub fn get_type_from_module(module_path string, type_name string) !string {
|
||||
println('Looking for type ${type_name} in module ${module_path}')
|
||||
v_files := list_v_files(module_path) or {
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
for v_file in v_files {
|
||||
println('Checking file: ${v_file}')
|
||||
content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
|
||||
|
||||
// Look for both regular and pub struct declarations
|
||||
mut type_str := 'struct ${type_name} {'
|
||||
mut i := content.index(type_str) or { -1 }
|
||||
mut is_pub := false
|
||||
|
||||
if i == -1 {
|
||||
// Try with pub struct
|
||||
type_str = 'pub struct ${type_name} {'
|
||||
i = content.index(type_str) or { -1 }
|
||||
is_pub = true
|
||||
}
|
||||
|
||||
if i == -1 {
|
||||
type_import := content.split_into_lines().filter(it.contains('import')
|
||||
&& it.contains(type_name))
|
||||
if type_import.len > 0 {
|
||||
log.debug('debugzoooo')
|
||||
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
|
||||
return get_type_from_module(get_module_dir(mod), type_name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
println('Found type ${type_name} in ${v_file} at position ${i}')
|
||||
|
||||
// Find the start of the struct definition including comments
|
||||
mut comment_start := i
|
||||
mut line_start := i
|
||||
|
||||
// Find the start of the line containing the struct definition
|
||||
for j := i; j >= 0; j-- {
|
||||
if j == 0 || content[j - 1] == `\n` {
|
||||
line_start = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find the start of the comment block (if any)
|
||||
for j := line_start - 1; j >= 0; j-- {
|
||||
if j == 0 {
|
||||
comment_start = 0
|
||||
break
|
||||
}
|
||||
|
||||
// If we hit a blank line or a non-comment line, stop
|
||||
if content[j] == `\n` {
|
||||
if j > 0 && j < content.len - 1 {
|
||||
// Check if the next line starts with a comment
|
||||
next_line_start := j + 1
|
||||
if next_line_start < content.len && content[next_line_start] != `/` {
|
||||
comment_start = j + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the end of the struct definition
|
||||
closing_i := find_closing_brace(content, i + type_str.len) or {
|
||||
return error('could not find where declaration for type ${type_name} ends')
|
||||
}
|
||||
|
||||
// Get the full struct definition including the struct declaration line
|
||||
full_struct := content.substr(line_start, closing_i + 1)
|
||||
println('Found struct definition:\n${full_struct}')
|
||||
|
||||
// Return the full struct definition
|
||||
return full_struct
|
||||
}
|
||||
|
||||
return error('type ${type_name} not found in module ${module_path}')
|
||||
}
|
||||
|
||||
// ===== V LANGUAGE TOOLS =====
|
||||
|
||||
// vtest runs v test on the specified file or directory
|
||||
// ARGS:
|
||||
// fullpath string - path to the file or directory to test
|
||||
// RETURNS:
|
||||
// string - test results output, or error if test fails
|
||||
pub fn vtest(fullpath string) !string {
|
||||
logger.info('test ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
if os.is_dir(fullpath) {
|
||||
mut results := ''
|
||||
for item in list_v_files(fullpath)! {
|
||||
results += vtest(item)!
|
||||
results += '\n-----------------------\n'
|
||||
}
|
||||
return results
|
||||
} else {
|
||||
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Test completed for ${fullpath}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
// vet_file runs v vet on a single file
|
||||
// ARGS:
|
||||
// file string - path to the file to vet
|
||||
// RETURNS:
|
||||
// string - vet results output, or error if vet fails
|
||||
fn vet_file(file string) !string {
|
||||
cmd := 'v vet -v -w ${file}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Vet completed for ${file}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
|
||||
// vvet runs v vet on the specified file or directory
|
||||
// ARGS:
|
||||
// fullpath string - path to the file or directory to vet
|
||||
// RETURNS:
|
||||
// string - vet results output, or error if vet fails
|
||||
pub fn vvet(fullpath string) !string {
|
||||
logger.info('vet ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
|
||||
if os.is_dir(fullpath) {
|
||||
mut results := ''
|
||||
files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') }
|
||||
for file in files {
|
||||
results += vet_file(file) or {
|
||||
logger.error('Failed to vet ${file}: ${err}')
|
||||
return error('Failed to vet ${file}: ${err}')
|
||||
}
|
||||
results += '\n-----------------------\n'
|
||||
}
|
||||
return results
|
||||
} else {
|
||||
return vet_file(fullpath)
|
||||
}
|
||||
}
|
||||
284
lib/mcp/vcode/vlang.v
Normal file
284
lib/mcp/vcode/vlang.v
Normal file
@@ -0,0 +1,284 @@
|
||||
module vcode
|
||||
|
||||
import freeflowuniverse.herolib.mcp
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
import os
|
||||
import log
|
||||
|
||||
fn get_module_dir(mod string) string {
|
||||
module_parts := mod.trim_string_left('freeflowuniverse.herolib').split('.')
|
||||
return '${os.home_dir()}/code/github/freeflowuniverse/herolib/lib/${module_parts.join('/')}'
|
||||
}
|
||||
|
||||
// given a module path and a type name, returns the type definition of that type within that module
|
||||
// for instance: get_type_from_module('lib/mcp/developer/vlang.v', 'Developer') might return struct Developer {...}
|
||||
fn get_type_from_module(module_path string, type_name string) !string {
|
||||
println('Looking for type ${type_name} in module ${module_path}')
|
||||
v_files := list_v_files(module_path) or {
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
for v_file in v_files {
|
||||
println('Checking file: ${v_file}')
|
||||
content := os.read_file(v_file) or { return error('Failed to read file ${v_file}: ${err}') }
|
||||
|
||||
// Look for both regular and pub struct declarations
|
||||
mut type_str := 'struct ${type_name} {'
|
||||
mut i := content.index(type_str) or { -1 }
|
||||
mut is_pub := false
|
||||
|
||||
if i == -1 {
|
||||
// Try with pub struct
|
||||
type_str = 'pub struct ${type_name} {'
|
||||
i = content.index(type_str) or { -1 }
|
||||
is_pub = true
|
||||
}
|
||||
|
||||
if i == -1 {
|
||||
type_import := content.split_into_lines().filter(it.contains('import')
|
||||
&& it.contains(type_name))
|
||||
if type_import.len > 0 {
|
||||
log.debug('debugzoooo')
|
||||
mod := type_import[0].trim_space().trim_string_left('import ').all_before(' ')
|
||||
return get_type_from_module(get_module_dir(mod), type_name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
println('Found type ${type_name} in ${v_file} at position ${i}')
|
||||
|
||||
// Find the start of the struct definition including comments
|
||||
mut comment_start := i
|
||||
mut line_start := i
|
||||
|
||||
// Find the start of the line containing the struct definition
|
||||
for j := i; j >= 0; j-- {
|
||||
if j == 0 || content[j - 1] == `\n` {
|
||||
line_start = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find the start of the comment block (if any)
|
||||
for j := line_start - 1; j >= 0; j-- {
|
||||
if j == 0 {
|
||||
comment_start = 0
|
||||
break
|
||||
}
|
||||
|
||||
// If we hit a blank line or a non-comment line, stop
|
||||
if content[j] == `\n` {
|
||||
if j > 0 && j < content.len - 1 {
|
||||
// Check if the next line starts with a comment
|
||||
next_line_start := j + 1
|
||||
if next_line_start < content.len && content[next_line_start] != `/` {
|
||||
comment_start = j + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the end of the struct definition
|
||||
closing_i := find_closing_brace(content, i + type_str.len) or {
|
||||
return error('could not find where declaration for type ${type_name} ends')
|
||||
}
|
||||
|
||||
// Get the full struct definition including the struct declaration line
|
||||
full_struct := content.substr(line_start, closing_i + 1)
|
||||
println('Found struct definition:\n${full_struct}')
|
||||
|
||||
// Return the full struct definition
|
||||
return full_struct
|
||||
}
|
||||
|
||||
return error('type ${type_name} not found in module ${module_path}')
|
||||
}
|
||||
|
||||
// given a module path and a function name, returns the function definition of that function within that module
|
||||
// for instance: get_function_from_module('lib/mcp/developer/vlang.v', 'develop') might return fn develop(...) {...}
|
||||
fn get_function_from_module(module_path string, function_name string) !string {
|
||||
v_files := list_v_files(module_path) or {
|
||||
return error('Failed to list V files in ${module_path}: ${err}')
|
||||
}
|
||||
|
||||
println('Found ${v_files.len} V files in ${module_path}')
|
||||
for v_file in v_files {
|
||||
println('Checking file: ${v_file}')
|
||||
result := get_function_from_file(v_file, function_name) or {
|
||||
println('Function not found in ${v_file}: ${err}')
|
||||
continue
|
||||
}
|
||||
println('Found function ${function_name} in ${v_file}')
|
||||
return result
|
||||
}
|
||||
|
||||
return error('function ${function_name} not found in module ${module_path}')
|
||||
}
|
||||
|
||||
fn find_closing_brace(content string, start_i int) ?int {
|
||||
mut brace_count := 1
|
||||
for i := start_i; i < content.len; i++ {
|
||||
if content[i] == `{` {
|
||||
brace_count++
|
||||
} else if content[i] == `}` {
|
||||
brace_count--
|
||||
if brace_count == 0 {
|
||||
return i
|
||||
}
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
// get_function_from_file parses a V file and extracts a specific function block including its comments
|
||||
// ARGS:
|
||||
// file_path string - path to the V file
|
||||
// function_name string - name of the function to extract
|
||||
// RETURNS: string - the function block including comments, or empty string if not found
|
||||
fn get_function_from_file(file_path string, function_name string) !string {
|
||||
content := os.read_file(file_path) or {
|
||||
return error('Failed to read file: ${file_path}: ${err}')
|
||||
}
|
||||
|
||||
lines := content.split_into_lines()
|
||||
mut result := []string{}
|
||||
mut in_function := false
|
||||
mut brace_count := 0
|
||||
mut comment_block := []string{}
|
||||
|
||||
for i, line in lines {
|
||||
trimmed := line.trim_space()
|
||||
|
||||
// Collect comments that might be above the function
|
||||
if trimmed.starts_with('//') {
|
||||
if !in_function {
|
||||
comment_block << line
|
||||
} else if brace_count > 0 {
|
||||
result << line
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if we found the function
|
||||
if !in_function && (trimmed.starts_with('fn ${function_name}(')
|
||||
|| trimmed.starts_with('pub fn ${function_name}(')) {
|
||||
in_function = true
|
||||
// Add collected comments
|
||||
result << comment_block
|
||||
comment_block = []
|
||||
result << line
|
||||
if line.contains('{') {
|
||||
brace_count++
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// If we're inside the function, keep track of braces
|
||||
if in_function {
|
||||
result << line
|
||||
|
||||
for c in line {
|
||||
if c == `{` {
|
||||
brace_count++
|
||||
} else if c == `}` {
|
||||
brace_count--
|
||||
}
|
||||
}
|
||||
|
||||
// If brace_count is 0, we've reached the end of the function
|
||||
if brace_count == 0 && trimmed.contains('}') {
|
||||
return result.join('\n')
|
||||
}
|
||||
} else {
|
||||
// Reset comment block if we pass a blank line
|
||||
if trimmed == '' {
|
||||
comment_block = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !in_function {
|
||||
return error('Function "${function_name}" not found in ${file_path}')
|
||||
}
|
||||
|
||||
return result.join('\n')
|
||||
}
|
||||
|
||||
// list_v_files returns all .v files in a directory (non-recursive), excluding generated files ending with _.v
|
||||
fn list_v_files(dir string) ![]string {
|
||||
files := os.ls(dir) or { return error('Error listing directory: ${err}') }
|
||||
|
||||
mut v_files := []string{}
|
||||
for file in files {
|
||||
if file.ends_with('.v') && !file.ends_with('_.v') {
|
||||
filepath := os.join_path(dir, file)
|
||||
v_files << filepath
|
||||
}
|
||||
}
|
||||
|
||||
return v_files
|
||||
}
|
||||
|
||||
// test runs v test on the specified file or directory
|
||||
pub fn vtest(fullpath string) !string {
|
||||
logger.info('test ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
if os.is_dir(fullpath) {
|
||||
mut results := ''
|
||||
for item in list_v_files(fullpath)! {
|
||||
results += vtest(item)!
|
||||
results += '\n-----------------------\n'
|
||||
}
|
||||
return results
|
||||
} else {
|
||||
cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc test ${fullpath}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Test failed for ${fullpath} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Test completed for ${fullpath}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
}
|
||||
|
||||
// vvet runs v vet on the specified file or directory
|
||||
pub fn vvet(fullpath string) !string {
|
||||
logger.info('vet ${fullpath}')
|
||||
if !os.exists(fullpath) {
|
||||
return error('File or directory does not exist: ${fullpath}')
|
||||
}
|
||||
|
||||
if os.is_dir(fullpath) {
|
||||
mut results := ''
|
||||
files := list_v_files(fullpath) or { return error('Error listing V files: ${err}') }
|
||||
for file in files {
|
||||
results += vet_file(file) or {
|
||||
logger.error('Failed to vet ${file}: ${err}')
|
||||
return error('Failed to vet ${file}: ${err}')
|
||||
}
|
||||
results += '\n-----------------------\n'
|
||||
}
|
||||
return results
|
||||
} else {
|
||||
return vet_file(fullpath)
|
||||
}
|
||||
}
|
||||
|
||||
// vet_file runs v vet on a single file
|
||||
fn vet_file(file string) !string {
|
||||
cmd := 'v vet -v -w ${file}'
|
||||
logger.debug('Executing command: ${cmd}')
|
||||
result := os.execute(cmd)
|
||||
if result.exit_code != 0 {
|
||||
return error('Vet failed for ${file} with exit code ${result.exit_code}\n${result.output}')
|
||||
} else {
|
||||
logger.info('Vet completed for ${file}')
|
||||
}
|
||||
return 'Command: ${cmd}\nExit code: ${result.exit_code}\nOutput:\n${result.output}'
|
||||
}
|
||||
|
||||
// cmd := 'v -gc none -stats -enable-globals -show-c-output -keepc -n -w -cg -o /tmp/tester.c -g -cc tcc ${fullpath}'
|
||||
Reference in New Issue
Block a user