This commit is contained in:
2025-11-23 07:18:45 +01:00
parent 0a25fc95b5
commit 01639853ce
9 changed files with 218 additions and 208 deletions

View File

@@ -1,6 +1,7 @@
module code
pub struct Const {
pub mut:
name string
value string
}

View File

@@ -2,17 +2,6 @@ module codeparser
import incubaid.herolib.core.code
import incubaid.herolib.core.pathlib
// import incubaid.herolib.ui.console
// import os
@[params]
pub struct ParserOptions {
pub:
path string @[required]
recursive bool = true
exclude_patterns []string
include_patterns []string = ['*.v']
}
// ParseError represents an error that occurred while parsing a file
pub struct ParseError {
@@ -54,57 +43,80 @@ pub mut:
parse_errors []ParseError
}
// new creates a CodeParser and scans the given root directory
@[params]
pub fn new(args ParserOptions) !CodeParser {
mut parser := CodeParser{
root_dir: args.path
options: args
parsed_files: map[string]ParsedFile{}
modules: map[string][]string{}
// scan_directory recursively walks the directory and identifies all V files
// Files are stored but not parsed until parse() is called
fn (mut parser CodeParser) scan_directory() ! {
mut root := pathlib.get_dir(path: parser.root_dir, create: false)!
if !root.exists() {
return error('root directory does not exist: ${parser.root_dir}')
}
parser.scan_directory()!
return parser
}
// Accessor properties for backward compatibility
pub fn (parser CodeParser) files() map[string]code.VFile {
mut result := map[string]code.VFile{}
for _, parsed_file in parser.parsed_files {
result[parsed_file.path] = parsed_file.vfile
// Use pathlib's recursive listing capability
mut items := root.list(recursive: parser.options.recursive)!
for item in items.paths {
// Skip non-V files
if !item.path.ends_with('.v') {
continue
}
// Skip generated files (ending with _.v)
if item.path.ends_with('_.v') {
continue
}
// Check exclude patterns
should_skip := parser.options.exclude_patterns.any(item.path.contains(it))
if should_skip {
continue
}
// Store file path for lazy parsing
parsed_file := ParsedFile{
path: item.path
module_name: ''
vfile: code.VFile{}
}
parser.parsed_files[item.path] = parsed_file
}
return result
}
pub fn (parser CodeParser) errors() []ParseError {
return parser.parse_errors
// parse processes all V files that were scanned and parses them
pub fn (mut parser CodeParser) parse() ! {
for file_path, _ in parser.parsed_files {
if parser.parsed_files[file_path].vfile.mod == '' {
// Only parse if not already parsed
parser.parse_file(file_path)!
}
}
}
// parse_file parses a single V file and adds it to the index (public wrapper)
pub fn (mut parser CodeParser) parse_file(file_path string) {
// parse_file parses a single V file and adds it to the index
pub fn (mut parser CodeParser) parse_file(file_path string) ! {
mut file := pathlib.get_file(path: file_path) or {
parser.parse_errors << ParseError{
file_path: file_path
error: err.msg()
error: 'Failed to access file: ${err.msg()}'
}
return
return error('Failed to access file: ${err.msg()}')
}
content := file.read() or {
parser.parse_errors << ParseError{
file_path: file_path
error: err.msg()
error: 'Failed to read file: ${err.msg()}'
}
return
return error('Failed to read file: ${err.msg()}')
}
// Parse the V file
vfile := code.parse_vfile(content) or {
parser.parse_errors << ParseError{
file_path: file_path
error: err.msg()
error: 'Parse error: ${err.msg()}'
}
return
return error('Parse error: ${err.msg()}')
}
parsed_file := ParsedFile{
@@ -119,27 +131,8 @@ pub fn (mut parser CodeParser) parse_file(file_path string) {
if vfile.mod !in parser.modules {
parser.modules[vfile.mod] = []string{}
}
parser.modules[vfile.mod] << file_path
}
// parse processes all V files that were scanned
pub fn (mut parser CodeParser) parse() ! {
for file_path, _ in parser.parsed_files {
parser.parse_file(file_path)
}
}
// get_module_stats calculates statistics for a module
pub fn (parser CodeParser) get_module_stats(module string) ModuleStats {
// TODO: Fix this function
return ModuleStats{}
}
// error adds a new parsing error to the list
fn (mut parser CodeParser) error(file_path string, msg string) {
parser.parse_errors << ParseError{
file_path: file_path
error: msg
if file_path !in parser.modules[vfile.mod] {
parser.modules[vfile.mod] << file_path
}
}

View File

@@ -3,40 +3,24 @@ module codeparser
import incubaid.herolib.core.pathlib
import incubaid.herolib.core.code
// scan_directory recursively walks the directory and parses all V files using pathlib
fn (mut parser CodeParser) scan_directory() ! {
mut root := pathlib.get_dir(path: parser.root_dir, create: false)!
if !root.exists() {
return error('root directory does not exist: ${parser.root_dir}')
}
// Use pathlib's recursive listing capability
mut items := root.list(recursive: parser.options.recursive)!
for item in items.paths {
// Skip non-V files
if !item.path.ends_with('.v') {
continue
}
// Skip generated files
if item.path.ends_with('_.v') {
continue
}
// Check exclude patterns
should_skip := parser.options.exclude_patterns.any(item.path.contains(it))
if should_skip {
continue
}
// Store file path for later parsing
parsed_file := ParsedFile{
path: item.path
module_name: ''
vfile: code.VFile{}
}
parser.parsed_files[item.path] = parsed_file
}
@[params]
pub struct ParserOptions {
pub:
path string @[required]
recursive bool = true
exclude_patterns []string
include_patterns []string = ['*.v']
}
// new creates a CodeParser and scans the given root directory
pub fn new(args ParserOptions) !CodeParser {
mut parser := CodeParser{
root_dir: args.path
options: args
parsed_files: map[string]ParsedFile{}
modules: map[string][]string{}
parse_errors: []ParseError{}
}
parser.scan_directory()!
return parser
}

View File

@@ -1,26 +1,29 @@
module codeparser
import incubaid.herolib.core.code
import regex
@[params]
pub struct FilterOptions {
pub:
module_ string
name_regex string
module_name string
name_filter string // just partial match
is_public bool
has_receiver bool
}
// structs returns a filtered list of all structs found in the parsed files
pub fn (p CodeParser) structs(options FilterOptions) []code.Struct {
pub fn (parser CodeParser) structs(options FilterOptions) []code.Struct {
mut result := []code.Struct{}
for _, file in p.parsed_files {
if options.module_ != '' && file.module_name != options.module_ {
for _, file in parser.parsed_files {
if options.module_name != '' && file.module_name != options.module_name {
continue
}
for struct_ in file.vfile.structs() {
if options.name_regex != '' && !struct_.name.match_regex(options.name_regex) {
continue
if options.name_filter.len > 0 {
if !struct_.name.contains(options.name_filter) {
continue
}
}
if options.is_public && !struct_.is_pub {
continue
@@ -32,20 +35,22 @@ pub fn (p CodeParser) structs(options FilterOptions) []code.Struct {
}
// functions returns a filtered list of all functions found in the parsed files
pub fn (p CodeParser) functions(options FilterOptions) []code.Function {
pub fn (parser CodeParser) functions(options FilterOptions) []code.Function {
mut result := []code.Function{}
for _, file in p.parsed_files {
if options.module_ != '' && file.module_name != options.module_ {
for _, file in parser.parsed_files {
if options.module_name != '' && file.module_name != options.module_name {
continue
}
for func in file.vfile.functions() {
if options.name_regex != '' && !func.name.match_regex(options.name_regex) {
continue
if options.name_filter.len > 0 {
if !func.name.contains(options.name_filter) {
continue
}
}
if options.is_public && !func.is_pub {
continue
}
if options.has_receiver && func.receiver.typ.name == '' {
if options.has_receiver && func.receiver.typ.symbol() == '' {
continue
}
result << func
@@ -53,3 +58,27 @@ pub fn (p CodeParser) functions(options FilterOptions) []code.Function {
}
return result
}
// filter_public_structs returns all public structs
pub fn (parser CodeParser) filter_public_structs(module_name string) []code.Struct {
return parser.structs(
module_name: module_name
is_public: true
)
}
// filter_public_functions returns all public functions
pub fn (parser CodeParser) filter_public_functions(module_name string) []code.Function {
return parser.functions(
module_name: module_name
is_public: true
)
}
// filter_methods returns all functions with receivers (methods)
pub fn (parser CodeParser) filter_methods(module_name string) []code.Function {
return parser.functions(
module_name: module_name
has_receiver: true
)
}

View File

@@ -2,89 +2,92 @@ module codeparser
import incubaid.herolib.core.code
@[params]
pub struct FinderOptions {
pub:
name string @[required]
struct_name string // only useful for methods on structs
module_name string
}
// find_struct searches for a struct by name
pub fn (parser CodeParser) find_struct(name: string, module: string = '') !code.Struct {
pub fn (parser CodeParser) find_struct(args FinderOptions) !code.Struct {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if args.module_name != '' && parsed_file.module_name != args.module_name {
continue
}
structs := parsed_file.vfile.structs()
for struct_ in structs {
if struct_.name == name {
if struct_.name == args.name {
return struct_
}
}
}
return error('struct \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
module_suffix := if args.module_name != '' { ' in module \'${args.module_name}\'' } else { '' }
return error('struct \'${args.name}\' not found${module_suffix}')
}
// find_function searches for a function by name
pub fn (parser CodeParser) find_function(name: string, module: string = '') !code.Function {
pub fn (parser CodeParser) find_function(args FinderOptions) !code.Function {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if args.module_name != '' && parsed_file.module_name != args.module_name {
continue
}
if func := parsed_file.vfile.get_function(name) {
if func := parsed_file.vfile.get_function(args.name) {
return func
}
}
return error('function \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
module_suffix := if args.module_name != '' { ' in module \'${args.module_name}\'' } else { '' }
return error('function \'${args.name}\' not found${module_suffix}')
}
// find_interface searches for an interface by name
pub fn (parser CodeParser) find_interface(name: string, module: string = '') !code.Interface {
pub fn (parser CodeParser) find_interface(args FinderOptions) !code.Interface {
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if args.module_name != '' && parsed_file.module_name != args.module_name {
continue
}
for item in parsed_file.vfile.items {
if item is code.Interface {
iface := item as code.Interface
if iface.name == name {
if iface.name == args.name {
return iface
}
}
}
}
return error('interface \'${name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
module_suffix := if args.module_name != '' { ' in module \'${args.module_name}\'' } else { '' }
return error('interface \'${args.name}\' not found${module_suffix}')
}
// find_method searches for a method on a struct
pub fn (parser CodeParser) find_method(struct_name: string, method_name: string, module: string = '') !code.Function {
methods := parser.list_methods_on_struct(struct_name, module)
pub fn (parser CodeParser) find_method(args FinderOptions) !code.Function {
methods := parser.list_methods_on_struct(args.struct_name, args.module_name)
for method in methods {
if method.name == method_name {
if method.name == args.name {
return method
}
}
return error('method \'${method_name}\' on struct \'${struct_name}\' not found${if module != '' { ' in module \'${module}\'' } else { '' }}')
module_suffix := if args.module_name != '' { ' in module \'${args.module_name}\'' } else { '' }
return error('method \'${args.name}\' on struct \'${args.struct_name}\' not found${module_suffix}')
}
// find_module searches for a module by name
pub fn (parser CodeParser) find_module(module_name: string) !ParsedModule {
pub fn (parser CodeParser) find_module(module_name string) !ParsedModule {
if module_name !in parser.modules {
return error('module \'${module_name}\' not found')
}
file_paths := parser.modules[module_name]
mut stats := ModuleStats{}
for file_path in file_paths {
if parsed_file := parser.parsed_files[file_path] {
stats.file_count++
stats.struct_count += parsed_file.vfile.structs().len
stats.function_count += parsed_file.vfile.functions().len
stats.const_count += parsed_file.vfile.consts.len
}
}
stats := parser.get_module_stats(module_name)
return ParsedModule{
name: module_name
@@ -94,7 +97,7 @@ pub fn (parser CodeParser) find_module(module_name: string) !ParsedModule {
}
// find_file retrieves parsed file information
pub fn (parser CodeParser) find_file(path: string) !ParsedFile {
pub fn (parser CodeParser) find_file(path string) !ParsedFile {
if path !in parser.parsed_files {
return error('file \'${path}\' not found in parsed files')
}
@@ -103,12 +106,12 @@ pub fn (parser CodeParser) find_file(path: string) !ParsedFile {
}
// find_structs_with_method finds all structs that have a specific method
pub fn (parser CodeParser) find_structs_with_method(method_name: string, module: string = '') []string {
pub fn (parser CodeParser) find_structs_with_method(args FinderOptions) []string {
mut struct_names := []string{}
functions := parser.list_functions(module)
functions := parser.list_functions(args.module_name)
for func in functions {
if func.name == method_name && func.receiver.name != '' {
if func.name == args.name && func.receiver.name != '' {
struct_type := func.receiver.typ.symbol()
if struct_type !in struct_names {
struct_names << struct_type
@@ -120,15 +123,15 @@ pub fn (parser CodeParser) find_structs_with_method(method_name: string, module:
}
// find_callers finds all functions that call a specific function (basic text matching)
pub fn (parser CodeParser) find_callers(function_name: string, module: string = '') []code.Function {
pub fn (parser CodeParser) find_callers(args FinderOptions) []code.Function {
mut callers := []code.Function{}
functions := parser.list_functions(module)
functions := parser.list_functions(args.module_name)
for func in functions {
if func.body.contains(function_name) {
if func.body.contains(args.name) {
callers << func
}
}
return callers
}
}

View File

@@ -2,26 +2,28 @@ module codeparser
import incubaid.herolib.core.code
// list_modules returns a list of all parsed module names
pub fn (parser CodeParser) list_modules() []string {
return parser.modules.keys()
}
// get_module_stats returns statistics for a given module
// get_module_stats calculates statistics for a module
pub fn (parser CodeParser) get_module_stats(module_name string) ModuleStats {
mut stats := ModuleStats{}
if file_paths := parser.modules[module_name] {
stats.file_count = file_paths.len
for file_path in file_paths {
if parsed_file := parser.parsed_files[file_path] {
vfile := parsed_file.vfile
stats.struct_count += vfile.structs().len
stats.function_count += vfile.functions().len
stats.const_count += vfile.consts.len
stats.interface_count += vfile.interfaces().len
file_paths := parser.modules[module_name] or { []string{} }
for file_path in file_paths {
if parsed_file := parser.parsed_files[file_path] {
stats.file_count++
stats.struct_count += parsed_file.vfile.structs().len
stats.function_count += parsed_file.vfile.functions().len
for item in parsed_file.vfile.items {
if item is code.Interface {
stats.interface_count++
}
}
stats.const_count += parsed_file.vfile.consts.len
}
}
return stats
}
@@ -77,11 +79,11 @@ pub fn (p CodeParser) all_enums() []code.Enum {
return all
}
// all_interfaces returns all interfaces from all parsed files
pub fn (p CodeParser) all_interfaces() []code.Interface {
mut all := []code.Interface{}
for _, file in p.parsed_files {
all << file.vfile.interfaces()
}
return all
}
// // all_interfaces returns all interfaces from all parsed files
// pub fn (p CodeParser) all_interfaces() []code.Interface {
// mut all := []code.Interface{}
// for _, file in p.parsed_files {
// all << file.vfile.interfaces()
// }
// return all
// }

View File

@@ -5,18 +5,18 @@ import incubaid.herolib.core.code
// JSON export structures
pub struct CodeParserJSON {
pub:
pub mut:
root_dir string
modules map[string]ModuleJSON
summary SummaryJSON
}
pub struct ModuleJSON {
pub:
name string
files map[string]FileJSON
stats ModuleStats
imports []string
pub mut:
name string
files map[string]FileJSON
stats ModuleStats
imports []string
}
pub struct FileJSON {
@@ -61,7 +61,7 @@ pub:
}
pub struct SummaryJSON {
pub:
pub mut:
total_files int
total_modules int
total_structs int
@@ -70,23 +70,23 @@ pub:
}
// to_json exports the complete code structure to JSON
//
//
// Args:
// module - optional module filter (if empty, exports all modules)
// module_name - optional module filter (if empty, exports all modules)
// Returns:
// JSON string representation
pub fn (parser CodeParser) to_json(module: string = '') !string {
pub fn (parser CodeParser) to_json(module_name string) !string {
mut result := CodeParserJSON{
root_dir: parser.root_dir
modules: map[string]ModuleJSON{}
summary: SummaryJSON{}
}
modules_to_process := if module != '' {
if module in parser.modules {
[module]
modules_to_process := if module_name != '' {
if module_name in parser.modules {
[module_name]
} else {
return error('module \'${module}\' not found')
return error('module \'${module_name}\' not found')
}
} else {
parser.list_modules()
@@ -180,13 +180,11 @@ pub fn (parser CodeParser) to_json(module: string = '') !string {
result.summary.total_modules++
}
result.summary.total_files = result.modules.values().map(it.stats.file_count).sum()
// mut total_files := 0
// for module in result.modules.values() {
// total_files += module.stats.file_count
// }
// result.summary.total_files = total_files
return json.encode(result)
return json.encode_pretty(result)
}
// to_json_pretty exports to pretty-printed JSON
pub fn (parser CodeParser) to_json_pretty(module: string = '') !string {
json_str := parser.to_json(module)!
return json.encode_pretty(json.decode(map[string]interface{}, json_str)!)
}

View File

@@ -2,28 +2,27 @@ module codeparser
import incubaid.herolib.core.code
// list_modules returns all module names found in the codebase
// list_modules returns a list of all parsed module names
pub fn (parser CodeParser) list_modules() []string {
return parser.modules.keys()
}
// list_files returns all parsed file paths
pub fn (parser CodeParser) list_files() []string {
return parser.parsed_files.keys()
}
// list_files_in_module returns all file paths in a specific module
pub fn (parser CodeParser) list_files_in_module(module: string) []string {
return parser.modules[module] or { []string{} }
pub fn (parser CodeParser) list_files_in_module(module_name string) []string {
return parser.modules[module_name] or { []string{} }
}
// list_structs returns all structs in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_structs(module: string = '') []code.Struct {
pub fn (parser CodeParser) list_structs(module_name string) []code.Struct {
mut structs := []code.Struct{}
for _, parsed_file in parser.parsed_files {
// Skip if module filter is provided and doesn't match
if module != '' && parsed_file.module_name != module {
if module_name != '' && parsed_file.module_name != module_name {
continue
}
@@ -35,11 +34,11 @@ pub fn (parser CodeParser) list_structs(module: string = '') []code.Struct {
}
// list_functions returns all functions in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_functions(module: string = '') []code.Function {
pub fn (parser CodeParser) list_functions(module_name string) []code.Function {
mut functions := []code.Function{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if module_name != '' && parsed_file.module_name != module_name {
continue
}
@@ -51,11 +50,11 @@ pub fn (parser CodeParser) list_functions(module: string = '') []code.Function {
}
// list_interfaces returns all interfaces in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_interfaces(module: string = '') []code.Interface {
pub fn (parser CodeParser) list_interfaces(module_name string) []code.Interface {
mut interfaces := []code.Interface{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if module_name != '' && parsed_file.module_name != module_name {
continue
}
@@ -71,13 +70,14 @@ pub fn (parser CodeParser) list_interfaces(module: string = '') []code.Interface
}
// list_methods_on_struct returns all methods (receiver functions) for a struct
pub fn (parser CodeParser) list_methods_on_struct(struct_name: string, module: string = '') []code.Function {
pub fn (parser CodeParser) list_methods_on_struct(struct_name string, module_name string) []code.Function {
mut methods := []code.Function{}
functions := parser.list_functions(module)
functions := parser.list_functions(module_name)
for func in functions {
// Check if function has a receiver of the matching type
if func.receiver.typ.symbol().contains(struct_name) {
receiver_type := func.receiver.typ.symbol()
if receiver_type.contains(struct_name) {
methods << func
}
}
@@ -86,11 +86,11 @@ pub fn (parser CodeParser) list_methods_on_struct(struct_name: string, module: s
}
// list_imports returns all unique imports used in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_imports(module: string = '') []code.Import {
pub fn (parser CodeParser) list_imports(module_name string) []code.Import {
mut imports := map[string]code.Import{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if module_name != '' && parsed_file.module_name != module_name {
continue
}
@@ -103,11 +103,11 @@ pub fn (parser CodeParser) list_imports(module: string = '') []code.Import {
}
// list_constants returns all constants in the codebase (optionally filtered by module)
pub fn (parser CodeParser) list_constants(module: string = '') []code.Const {
pub fn (parser CodeParser) list_constants(module_name string) []code.Const {
mut consts := []code.Const{}
for _, parsed_file in parser.parsed_files {
if module != '' && parsed_file.module_name != module {
if module_name != '' && parsed_file.module_name != module_name {
continue
}
@@ -115,4 +115,4 @@ pub fn (parser CodeParser) list_constants(module: string = '') []code.Const {
}
return consts
}
}