Files
herolib/lib/data/encoderhero/decoder.v
2025-10-29 07:53:34 +04:00

108 lines
3.1 KiB
V

module encoderhero
import incubaid.herolib.data.paramsparser
import incubaid.herolib.data.ourtime
import incubaid.herolib.core.texttools
pub struct Decoder[T] {
pub mut:
object T
data string
}
pub fn decode[T](data string) !T {
return decode_struct[T](T{}, data)
}
// decode_struct decodes a heroscript string into a struct T
// Only supports single-level structs (no nested structs or arrays of structs)
fn decode_struct[T](_ T, data string) !T {
mut typ := T{}
$if T is $struct {
obj_name := texttools.snake_case(T.name.all_after_last('.'))
obj_name2 := texttools.name_fix(T.name.all_after_last('.'))
// Define possible action name formats to try
action_names_to_try := [
'define.${obj_name}',
'configure.${obj_name}',
'${obj_name}.define',
'${obj_name}.configure',
'define.${obj_name2}',
'configure.${obj_name2}',
'${obj_name2}.define',
'${obj_name2}.configure',
]
mut found_action_name := ''
mut actions := []string{}
mut action_str := '' // Declare action_str here
mut params_str := '' // Declare params_str here
// Find the action line
actions_split := data.split('!!')
for name_format in action_names_to_try {
actions = actions_split.filter(it.contains(name_format))
if actions.len > 0 {
found_action_name = name_format
break
}
}
if found_action_name == '' {
return error('Data does not contain expected action format for obj:${obj_name} or obj:${obj_name2}\nData: ${data}')
}
if actions.len > 1 {
return error('Multiple actions found for ${found_action_name}. Only single-level structs supported.')
}
action_str = actions[0]
params_str = action_str.all_after(found_action_name).trim_space()
params := paramsparser.parse(params_str) or {
return error('Could not parse params: ${params_str}\n${err}')
}
// Decode all fields (paramsparser.decode handles embedded structs)
typ = params.decode[T](typ)!
// Validate no nested structs or struct arrays in the decoded type
$for field in T.fields {
if !should_skip_field_decode(field.attrs) {
$if field.is_struct {
// Embedded structs (capitalized) are OK
// Non-embedded structs are not supported
if !field.name[0].is_capital() {
$if field.typ !is ourtime.OurTime {
return error('Nested structs not supported. Field: ${field.name}')
}
}
} $else $if field.is_array {
// Arrays of basic types are OK, arrays of structs are not
// This is validated at encode time, so just a safety check
}
}
}
} $else {
return error("The type `${T.name}` can't be decoded. Only structs are supported.")
}
return typ
}
// Helper function to check if field should be skipped during decode
fn should_skip_field_decode(attrs []string) bool {
for attr in attrs {
attr_clean := attr.to_lower().replace(' ', '').replace('\t', '')
if attr_clean == 'skip' || attr_clean.starts_with('skip;') || attr_clean.ends_with(';skip')
|| attr_clean.contains(';skip;') || attr_clean == 'skipdecode'
|| attr_clean.starts_with('skipdecode;') || attr_clean.ends_with(';skipdecode')
|| attr_clean.contains(';skipdecode;') {
return true
}
}
return false
}