108 lines
3.1 KiB
V
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
|
|
}
|