code module fixes
This commit is contained in:
@@ -6,6 +6,7 @@ import os
|
||||
|
||||
pub interface IFile {
|
||||
write(string, WriteOptions) !
|
||||
write_str(WriteOptions) !string
|
||||
name string
|
||||
}
|
||||
|
||||
@@ -24,6 +25,10 @@ pub fn (f File) write(path string, params WriteOptions) ! {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (f File) write_str(params WriteOptions) !string {
|
||||
return f.content
|
||||
}
|
||||
|
||||
pub fn (f File) typescript(path string, params WriteOptions) ! {
|
||||
if params.format {
|
||||
os.execute('npx prettier --write ${path}')
|
||||
@@ -99,6 +104,31 @@ pub fn (code VFile) write(path string, options WriteOptions) ! {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (code VFile) write_str(options WriteOptions) !string {
|
||||
imports_str := code.imports.map(it.vgen()).join_lines()
|
||||
|
||||
code_str := if code.content != '' {
|
||||
code.content
|
||||
} else {
|
||||
vgen(code.items)
|
||||
}
|
||||
|
||||
consts_str := if code.consts.len > 1 {
|
||||
stmts := code.consts.map('${it.name} = ${it.value}')
|
||||
'\nconst(\n${stmts.join('\n')}\n)\n'
|
||||
} else if code.consts.len == 1 {
|
||||
'\nconst ${code.consts[0].name} = ${code.consts[0].value}\n'
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
mod_stmt := if code.mod == '' {''} else {
|
||||
'module ${code.mod}'
|
||||
}
|
||||
|
||||
return '${mod_stmt}\n${imports_str}\n${consts_str}${code_str}'
|
||||
}
|
||||
|
||||
pub fn (file VFile) get_function(name string) ?Function {
|
||||
functions := file.items.filter(it is Function).map(it as Function)
|
||||
target_lst := functions.filter(it.name == name)
|
||||
|
||||
@@ -79,7 +79,26 @@ pub fn new_function(code string) !Function {
|
||||
}
|
||||
|
||||
pub fn parse_function(code_ string) !Function {
|
||||
mut code := code_.trim_space()
|
||||
// Extract comments and actual function code
|
||||
mut lines := code_.split_into_lines()
|
||||
mut comment_lines := []string{}
|
||||
mut function_lines := []string{}
|
||||
mut in_function := false
|
||||
|
||||
for line in lines {
|
||||
trimmed := line.trim_space()
|
||||
if !in_function && trimmed.starts_with('//') {
|
||||
comment_lines << trimmed.trim_string_left('//').trim_space()
|
||||
} else if !in_function && (trimmed.starts_with('pub fn') || trimmed.starts_with('fn')) {
|
||||
in_function = true
|
||||
function_lines << line
|
||||
} else if in_function {
|
||||
function_lines << line
|
||||
}
|
||||
}
|
||||
|
||||
// Process the function code
|
||||
mut code := function_lines.join('\n').trim_space()
|
||||
is_pub := code.starts_with('pub ')
|
||||
if is_pub {
|
||||
code = code.trim_string_left('pub ').trim_space()
|
||||
@@ -109,16 +128,33 @@ pub fn parse_function(code_ string) !Function {
|
||||
} else {
|
||||
[]Param{}
|
||||
}
|
||||
// Extract the result type, handling the ! for result types
|
||||
mut result_type := code.all_after(')').all_before('{').replace(' ', '')
|
||||
mut has_return := false
|
||||
|
||||
// Check if the result type contains !
|
||||
if result_type.contains('!') {
|
||||
has_return = true
|
||||
result_type = result_type.replace('!', '')
|
||||
}
|
||||
|
||||
result := new_param(
|
||||
v: code.all_after(')').all_before('{').replace(' ', '')
|
||||
v: result_type
|
||||
)!
|
||||
|
||||
body := if code.contains('{') { code.all_after('{').all_before_last('}') } else { '' }
|
||||
|
||||
// Process the comments into a description
|
||||
description := comment_lines.join('\n')
|
||||
|
||||
return Function{
|
||||
name: name
|
||||
receiver: receiver
|
||||
params: params
|
||||
result: result
|
||||
body: body
|
||||
description: description
|
||||
is_pub: is_pub
|
||||
has_return: has_return
|
||||
}
|
||||
}
|
||||
95
lib/core/code/model_function_test.v
Normal file
95
lib/core/code/model_function_test.v
Normal file
@@ -0,0 +1,95 @@
|
||||
module code
|
||||
|
||||
fn test_parse_function_with_comments() {
|
||||
// Test function string with comments
|
||||
function_str := '// test_function is a simple function for testing the MCP tool code generation
|
||||
// It takes a config and returns a result
|
||||
pub fn test_function(config TestConfig) !TestResult {
|
||||
// This is just a mock implementation for testing purposes
|
||||
if config.name == \'\' {
|
||||
return error(\'Name cannot be empty\')
|
||||
}
|
||||
|
||||
return TestResult{
|
||||
success: config.enabled
|
||||
message: \'Test completed for \${config.name}\'
|
||||
code: if config.enabled { 0 } else { 1 }
|
||||
}
|
||||
}'
|
||||
|
||||
// Parse the function
|
||||
function := parse_function(function_str) or {
|
||||
assert false, 'Failed to parse function: ${err}'
|
||||
Function{}
|
||||
}
|
||||
|
||||
// Verify the parsed function properties
|
||||
assert function.name == 'test_function'
|
||||
assert function.is_pub == true
|
||||
assert function.params.len == 1
|
||||
assert function.params[0].name == 'config'
|
||||
assert function.params[0].typ.symbol() == 'TestConfig'
|
||||
assert function.result.typ.symbol() == 'TestResult'
|
||||
|
||||
// Verify that the comments were correctly parsed into the description
|
||||
expected_description := 'test_function is a simple function for testing the MCP tool code generation
|
||||
It takes a config and returns a result'
|
||||
assert function.description == expected_description
|
||||
|
||||
println('test_parse_function_with_comments passed')
|
||||
}
|
||||
|
||||
fn test_parse_function_without_comments() {
|
||||
// Test function string without comments
|
||||
function_str := 'fn simple_function(name string, count int) string {
|
||||
return \'\${name} count: \${count}\'
|
||||
}'
|
||||
|
||||
// Parse the function
|
||||
function := parse_function(function_str) or {
|
||||
assert false, 'Failed to parse function: ${err}'
|
||||
Function{}
|
||||
}
|
||||
|
||||
// Verify the parsed function properties
|
||||
assert function.name == 'simple_function'
|
||||
assert function.is_pub == false
|
||||
assert function.params.len == 2
|
||||
assert function.params[0].name == 'name'
|
||||
assert function.params[0].typ.symbol() == 'string'
|
||||
assert function.params[1].name == 'count'
|
||||
assert function.params[1].typ.symbol() == 'int'
|
||||
assert function.result.typ.symbol() == 'string'
|
||||
|
||||
// Verify that there is no description
|
||||
assert function.description == ''
|
||||
|
||||
println('test_parse_function_without_comments passed')
|
||||
}
|
||||
|
||||
fn test_parse_function_with_receiver() {
|
||||
// Test function with a receiver
|
||||
function_str := 'pub fn (d &Developer) create_tool(name string) !Tool {
|
||||
return Tool{
|
||||
name: name
|
||||
}
|
||||
}'
|
||||
|
||||
// Parse the function
|
||||
function := parse_function(function_str) or {
|
||||
assert false, 'Failed to parse function: ${err}'
|
||||
Function{}
|
||||
}
|
||||
|
||||
// Verify the parsed function properties
|
||||
assert function.name == 'create_tool'
|
||||
assert function.is_pub == true
|
||||
assert function.receiver.name == 'd'
|
||||
assert function.receiver.typ.symbol() == '&Developer'
|
||||
assert function.params.len == 1
|
||||
assert function.params[0].name == 'name'
|
||||
assert function.params[0].typ.symbol() == 'string'
|
||||
assert function.result.typ.symbol() == 'Tool'
|
||||
|
||||
println('test_parse_function_with_receiver passed')
|
||||
}
|
||||
@@ -81,3 +81,13 @@ pub fn (mod Module) write(path string, options WriteOptions) ! {
|
||||
mut mod_file := pathlib.get_file(path: '${module_dir.path}/v.mod')!
|
||||
mod_file.write($tmpl('templates/v.mod.template'))!
|
||||
}
|
||||
|
||||
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()!
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
@@ -3,6 +3,7 @@ module code
|
||||
import log
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import strings
|
||||
|
||||
pub struct Struct {
|
||||
pub mut:
|
||||
@@ -51,6 +52,108 @@ pub fn (struct_ Struct) vgen() string {
|
||||
return struct_str
|
||||
}
|
||||
|
||||
// parse_struct parses a struct definition string and returns a Struct object
|
||||
// The input string should include the struct definition including any preceding comments
|
||||
pub fn parse_struct(code_ string) !Struct {
|
||||
// Extract comments and actual struct code
|
||||
mut lines := code_.split_into_lines()
|
||||
mut comment_lines := []string{}
|
||||
mut struct_lines := []string{}
|
||||
mut in_struct := false
|
||||
mut struct_name := ''
|
||||
mut is_pub := false
|
||||
|
||||
for line in lines {
|
||||
trimmed := line.trim_space()
|
||||
if !in_struct && trimmed.starts_with('//') {
|
||||
comment_lines << trimmed.trim_string_left('//').trim_space()
|
||||
} else if !in_struct && (trimmed.starts_with('struct ') || trimmed.starts_with('pub struct ')) {
|
||||
in_struct = true
|
||||
struct_lines << line
|
||||
|
||||
// Extract struct name
|
||||
is_pub = trimmed.starts_with('pub ')
|
||||
mut name_part := if is_pub {
|
||||
trimmed.trim_string_left('pub struct ').trim_space()
|
||||
} else {
|
||||
trimmed.trim_string_left('struct ').trim_space()
|
||||
}
|
||||
|
||||
// Handle generics in struct name
|
||||
if name_part.contains('<') {
|
||||
struct_name = name_part.all_before('<').trim_space()
|
||||
} else if name_part.contains('{') {
|
||||
struct_name = name_part.all_before('{').trim_space()
|
||||
} else {
|
||||
struct_name = name_part
|
||||
}
|
||||
} else if in_struct {
|
||||
struct_lines << line
|
||||
|
||||
// Check if we've reached the end of the struct
|
||||
if trimmed.starts_with('}') {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if struct_name == '' {
|
||||
return error('Invalid struct format: could not extract struct name')
|
||||
}
|
||||
|
||||
// Process the struct fields
|
||||
mut fields := []StructField{}
|
||||
mut current_section := ''
|
||||
|
||||
for i := 1; i < struct_lines.len - 1; i++ { // Skip the first and last lines (struct declaration and closing brace)
|
||||
line := struct_lines[i].trim_space()
|
||||
|
||||
// Skip empty lines and comments
|
||||
if line == '' || line.starts_with('//') {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for section markers (pub:, mut:, pub mut:)
|
||||
if line.ends_with(':') {
|
||||
current_section = line
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse field
|
||||
parts := line.split_any(' \t')
|
||||
if parts.len < 2 {
|
||||
continue // Skip invalid lines
|
||||
}
|
||||
|
||||
field_name := parts[0]
|
||||
field_type_str := parts[1..].join(' ')
|
||||
|
||||
// Parse the type string into a Type object
|
||||
field_type := parse_type(field_type_str)
|
||||
|
||||
// Determine field visibility based on section
|
||||
is_pub_field := current_section.contains('pub')
|
||||
is_mut_field := current_section.contains('mut')
|
||||
|
||||
fields << StructField{
|
||||
name: field_name
|
||||
typ: field_type
|
||||
is_pub: is_pub_field
|
||||
is_mut: is_mut_field
|
||||
}
|
||||
}
|
||||
|
||||
// Process the comments into a description
|
||||
description := comment_lines.join('\n')
|
||||
|
||||
return Struct{
|
||||
name: struct_name
|
||||
description: description
|
||||
is_pub: is_pub
|
||||
fields: fields
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct Interface {
|
||||
pub mut:
|
||||
|
||||
73
lib/core/code/model_struct_test.v
Normal file
73
lib/core/code/model_struct_test.v
Normal file
@@ -0,0 +1,73 @@
|
||||
module code
|
||||
|
||||
fn test_parse_struct() {
|
||||
// Test case 1: struct with comments and pub fields
|
||||
struct_str := '// TestResult is a struct for test results
|
||||
// It contains information about test execution
|
||||
pub struct TestResult {
|
||||
pub:
|
||||
success bool
|
||||
message string
|
||||
code int
|
||||
}
|
||||
'
|
||||
result := parse_struct(struct_str) or {
|
||||
assert false, 'Failed to parse struct: ${err}'
|
||||
Struct{}
|
||||
}
|
||||
|
||||
assert result.name == 'TestResult'
|
||||
assert result.description == 'TestResult is a struct for test results
|
||||
It contains information about test execution'
|
||||
assert result.is_pub == true
|
||||
assert result.fields.len == 3
|
||||
|
||||
assert result.fields[0].name == 'success'
|
||||
assert result.fields[0].typ.symbol() == 'bool'
|
||||
assert result.fields[0].is_pub == true
|
||||
assert result.fields[0].is_mut == false
|
||||
|
||||
assert result.fields[1].name == 'message'
|
||||
assert result.fields[1].typ.symbol() == 'string'
|
||||
assert result.fields[1].is_pub == true
|
||||
assert result.fields[1].is_mut == false
|
||||
|
||||
assert result.fields[2].name == 'code'
|
||||
assert result.fields[2].typ.symbol() == 'int'
|
||||
assert result.fields[2].is_pub == true
|
||||
assert result.fields[2].is_mut == false
|
||||
|
||||
// Test case 2: struct without comments and with mixed visibility
|
||||
struct_str2 := 'struct SimpleStruct {
|
||||
pub:
|
||||
name string
|
||||
mut:
|
||||
count int
|
||||
active bool
|
||||
}
|
||||
'
|
||||
result2 := parse_struct(struct_str2) or {
|
||||
assert false, 'Failed to parse struct: ${err}'
|
||||
Struct{}
|
||||
}
|
||||
|
||||
assert result2.name == 'SimpleStruct'
|
||||
assert result2.description == ''
|
||||
assert result2.is_pub == false
|
||||
assert result2.fields.len == 3
|
||||
|
||||
assert result2.fields[0].name == 'name'
|
||||
assert result2.fields[0].typ.symbol() == 'string'
|
||||
assert result2.fields[0].is_pub == true
|
||||
assert result2.fields[0].is_mut == false
|
||||
|
||||
assert result2.fields[1].name == 'count'
|
||||
assert result2.fields[1].typ.symbol() == 'int'
|
||||
assert result2.fields[1].is_pub == false
|
||||
assert result2.fields[1].is_mut == true
|
||||
|
||||
assert result2.fields[2].name == 'active'
|
||||
assert result2.fields[2].typ.symbol() == 'bool'
|
||||
assert result2.fields[2].is_pub == false
|
||||
assert result2.fields[2].is_mut == true
|
||||
}
|
||||
@@ -82,28 +82,66 @@ pub fn type_from_symbol(symbol_ string) Type {
|
||||
return Object{symbol}
|
||||
}
|
||||
|
||||
pub fn (t Array) symbol() string {
|
||||
return '[]${t.typ.symbol()}'
|
||||
}
|
||||
|
||||
pub fn (t Object) symbol() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
pub fn (t Result) symbol() string {
|
||||
return '!${t.typ.symbol()}'
|
||||
}
|
||||
|
||||
pub fn (t Integer) symbol() string {
|
||||
mut str := ''
|
||||
if !t.signed {
|
||||
str += 'u'
|
||||
}
|
||||
if t.bytes != 0 {
|
||||
return '${str}${t.bytes}'
|
||||
} else {
|
||||
return '${str}int'
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (t Alias) symbol() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
pub fn (t String) symbol() string {
|
||||
return 'string'
|
||||
}
|
||||
|
||||
pub fn (t Boolean) symbol() string {
|
||||
return 'bool'
|
||||
}
|
||||
|
||||
pub fn (t Map) symbol() string {
|
||||
return 'map[string]${t.typ.symbol()}'
|
||||
}
|
||||
|
||||
pub fn (t Function) symbol() string {
|
||||
return 'fn ()'
|
||||
}
|
||||
|
||||
pub fn (t Void) symbol() string {
|
||||
return ''
|
||||
}
|
||||
|
||||
pub fn (t Type) symbol() string {
|
||||
return match t {
|
||||
Array { '[]${t.typ.symbol()}' }
|
||||
Object { t.name }
|
||||
Result { '!${t.typ.symbol()}'}
|
||||
Integer {
|
||||
mut str := ''
|
||||
if !t.signed {
|
||||
str += 'u'
|
||||
}
|
||||
if t.bytes != 0 {
|
||||
'${str}${t.bytes}'
|
||||
} else {
|
||||
'${str}int'
|
||||
}
|
||||
}
|
||||
Alias {t.name}
|
||||
String {'string'}
|
||||
Boolean {'bool'}
|
||||
Map{'map[string]${t.typ.symbol()}'}
|
||||
Function{'fn ()'}
|
||||
Void {''}
|
||||
Array { t.symbol() }
|
||||
Object { t.symbol() }
|
||||
Result { t.symbol() }
|
||||
Integer { t.symbol() }
|
||||
Alias { t.symbol() }
|
||||
String { t.symbol() }
|
||||
Boolean { t.symbol() }
|
||||
Map { t.symbol() }
|
||||
Function { t.symbol() }
|
||||
Void { t.symbol() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,4 +200,75 @@ pub fn (t Type) empty_value() string {
|
||||
Function {''}
|
||||
Void {''}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse_type parses a type string into a Type struct
|
||||
pub fn parse_type(type_str string) Type {
|
||||
println('Parsing type string: "${type_str}"')
|
||||
mut type_str_trimmed := type_str.trim_space()
|
||||
|
||||
// Handle struct definitions by extracting just the struct name
|
||||
if type_str_trimmed.contains('struct ') {
|
||||
lines := type_str_trimmed.split_into_lines()
|
||||
for line in lines {
|
||||
if line.contains('struct ') {
|
||||
mut struct_name := ''
|
||||
if line.contains('pub struct ') {
|
||||
struct_name = line.all_after('pub struct ').all_before('{')
|
||||
} else {
|
||||
struct_name = line.all_after('struct ').all_before('{')
|
||||
}
|
||||
struct_name = struct_name.trim_space()
|
||||
println('Extracted struct name: "${struct_name}"')
|
||||
return Object{struct_name}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for simple types first
|
||||
if type_str_trimmed == 'string' {
|
||||
return String{}
|
||||
} else if type_str_trimmed == 'bool' || type_str_trimmed == 'boolean' {
|
||||
return Boolean{}
|
||||
} else if type_str_trimmed == 'int' {
|
||||
return Integer{}
|
||||
} else if type_str_trimmed == 'u8' {
|
||||
return Integer{bytes: 8, signed: false}
|
||||
} else if type_str_trimmed == 'u16' {
|
||||
return Integer{bytes: 16, signed: false}
|
||||
} else if type_str_trimmed == 'u32' {
|
||||
return Integer{bytes: 32, signed: false}
|
||||
} else if type_str_trimmed == 'u64' {
|
||||
return Integer{bytes: 64, signed: false}
|
||||
} else if type_str_trimmed == 'i8' {
|
||||
return Integer{bytes: 8}
|
||||
} else if type_str_trimmed == 'i16' {
|
||||
return Integer{bytes: 16}
|
||||
} else if type_str_trimmed == 'i32' {
|
||||
return Integer{bytes: 32}
|
||||
} else if type_str_trimmed == 'i64' {
|
||||
return Integer{bytes: 64}
|
||||
}
|
||||
|
||||
// Check for array types
|
||||
if type_str_trimmed.starts_with('[]') {
|
||||
elem_type := type_str_trimmed.all_after('[]')
|
||||
return Array{parse_type(elem_type)}
|
||||
}
|
||||
|
||||
// Check for map types
|
||||
if type_str_trimmed.starts_with('map[') && type_str_trimmed.contains(']') {
|
||||
value_type := type_str_trimmed.all_after(']')
|
||||
return Map{parse_type(value_type)}
|
||||
}
|
||||
|
||||
// Check for result types
|
||||
if type_str_trimmed.starts_with('!') {
|
||||
result_type := type_str_trimmed.all_after('!')
|
||||
return Result{parse_type(result_type)}
|
||||
}
|
||||
|
||||
// If no other type matches, treat as an object/struct type
|
||||
println('Treating as object type: "${type_str_trimmed}"')
|
||||
return Object{type_str_trimmed}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user