diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 3059457..faec6e0 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -33,6 +33,7 @@ "gosec", "Infof", "Infoln", + "keyvalue", "Naur", "productionstring", "pyactr", diff --git a/actr/actr.go b/actr/actr.go index 008b72c..439df1f 100644 --- a/actr/actr.go +++ b/actr/actr.go @@ -5,8 +5,9 @@ package actr import ( "github.com/asmaloney/gactar/actr/buffer" "github.com/asmaloney/gactar/actr/modules" - "github.com/asmaloney/gactar/actr/params" + "github.com/asmaloney/gactar/util/container" + "github.com/asmaloney/gactar/util/keyvalue" ) type Options struct { @@ -250,13 +251,13 @@ func (model Model) LookupBuffer(bufferName string) buffer.Interface { return nil } -func (model *Model) SetParam(param *params.Param) (err error) { +func (model *Model) SetParam(param *keyvalue.KeyValue) (err error) { value := param.Value switch param.Key { case "log_level": if (value.Str == nil) || !ValidLogLevel(*value.Str) { - return params.ErrInvalidOption{Expected: ACTRLoggingLevels} + return modules.ErrInvalidValue{Expected: ACTRLoggingLevels} } model.LogLevel = ACTRLogLevel(*value.Str) @@ -271,7 +272,7 @@ func (model *Model) SetParam(param *params.Param) (err error) { case "random_seed": if value.Number == nil { - return params.ErrInvalidType{ExpectedType: params.Number} + return keyvalue.ErrInvalidType{ExpectedType: keyvalue.Number} } seed := uint32(*value.Number) @@ -279,7 +280,7 @@ func (model *Model) SetParam(param *params.Param) (err error) { model.RandomSeed = &seed default: - return params.ErrUnrecognizedParam + return modules.ErrUnrecognizedOption{Option: param.Key} } return diff --git a/actr/modules/declarative_memory.go b/actr/modules/declarative_memory.go index ad8b1c5..af3c614 100644 --- a/actr/modules/declarative_memory.go +++ b/actr/modules/declarative_memory.go @@ -7,7 +7,8 @@ import ( "golang.org/x/exp/slices" "github.com/asmaloney/gactar/actr/buffer" - "github.com/asmaloney/gactar/actr/params" + + "github.com/asmaloney/gactar/util/keyvalue" ) type RetrievalTimeParams struct { @@ -209,7 +210,7 @@ func (d DeclarativeMemory) BufferName() string { } // SetParam is called to set our module's parameter from the parameter in the code ("param") -func (d *DeclarativeMemory) SetParam(param *params.Param) (err error) { +func (d *DeclarativeMemory) SetParam(param *keyvalue.KeyValue) (err error) { err = d.ValidateParam(param) if err != nil { return diff --git a/actr/modules/extra_buffers.go b/actr/modules/extra_buffers.go index a758ccd..9339e5f 100644 --- a/actr/modules/extra_buffers.go +++ b/actr/modules/extra_buffers.go @@ -2,7 +2,8 @@ package modules import ( "github.com/asmaloney/gactar/actr/buffer" - "github.com/asmaloney/gactar/actr/params" + + "github.com/asmaloney/gactar/util/keyvalue" ) // ExtraBuffers module is used to declare one or more extra goal-style buffers in the model. @@ -23,7 +24,7 @@ func NewExtraBuffers() *ExtraBuffers { } // SetParam is called to set our module's parameter from the parameter in the code ("param") -func (eb *ExtraBuffers) SetParam(param *params.Param) (err error) { +func (eb *ExtraBuffers) SetParam(param *keyvalue.KeyValue) (err error) { newBuffer := goalBuffer{ buffer.Buffer{ Name: param.Key, diff --git a/actr/modules/goal.go b/actr/modules/goal.go index 6977f8b..bd6eb0c 100644 --- a/actr/modules/goal.go +++ b/actr/modules/goal.go @@ -2,7 +2,8 @@ package modules import ( "github.com/asmaloney/gactar/actr/buffer" - "github.com/asmaloney/gactar/actr/params" + + "github.com/asmaloney/gactar/util/keyvalue" ) // Goal is a module which provides the ACT-R "goal" buffer. @@ -49,7 +50,7 @@ func NewGoal() *Goal { } // SetParam is called to set our module's parameter from the parameter in the code ("param") -func (g *Goal) SetParam(param *params.Param) (err error) { +func (g *Goal) SetParam(param *keyvalue.KeyValue) (err error) { err = g.ValidateParam(param) if err != nil { return diff --git a/actr/modules/imaginal.go b/actr/modules/imaginal.go index 780ccd4..458cc5f 100644 --- a/actr/modules/imaginal.go +++ b/actr/modules/imaginal.go @@ -2,7 +2,8 @@ package modules import ( "github.com/asmaloney/gactar/actr/buffer" - "github.com/asmaloney/gactar/actr/params" + + "github.com/asmaloney/gactar/util/keyvalue" ) // Imaginal is a module which provides the ACT-R "imaginal" buffer. @@ -45,7 +46,7 @@ func NewImaginal() *Imaginal { } // SetParam is called to set our module's parameter from the parameter in the code ("param") -func (i *Imaginal) SetParam(param *params.Param) (err error) { +func (i *Imaginal) SetParam(param *keyvalue.KeyValue) (err error) { err = i.ValidateParam(param) if err != nil { return diff --git a/actr/modules/modules.go b/actr/modules/modules.go index 072d364..449e1d1 100644 --- a/actr/modules/modules.go +++ b/actr/modules/modules.go @@ -8,7 +8,8 @@ import ( "golang.org/x/exp/slices" "github.com/asmaloney/gactar/actr/buffer" - "github.com/asmaloney/gactar/actr/params" + + "github.com/asmaloney/gactar/util/keyvalue" ) const BuiltIn = "built-in" @@ -45,8 +46,8 @@ type Interface interface { Parameters() []ParamInterface ParameterInfo(name string) ParamInterface - ValidateParam(param *params.Param) error - SetParam(param *params.Param) error + ValidateParam(param *keyvalue.KeyValue) error + SetParam(param *keyvalue.KeyValue) error AllowsMultipleInit() bool } @@ -95,11 +96,10 @@ func (m Module) Parameters() []ParamInterface { } // ValidateParam given an actr param will validate it against our modules parameters -func (m Module) ValidateParam(param *params.Param) (err error) { - +func (m Module) ValidateParam(param *keyvalue.KeyValue) (err error) { paramInfo := m.ParameterInfo(param.Key) if paramInfo == nil { - return params.ErrUnrecognizedParam + return ErrUnrecognizedOption{Option: param.Key} } min := paramInfo.GetMin() @@ -109,25 +109,25 @@ func (m Module) ValidateParam(param *params.Param) (err error) { // we currently only have numbers if value.Number == nil { - return params.ErrInvalidType{ExpectedType: params.Number} + return keyvalue.ErrInvalidType{ExpectedType: keyvalue.Number} } if (min != nil) && (max != nil) && ((*value.Number < *min) || (*value.Number > *max)) { - return params.ErrOutOfRange{ + return ErrValueOutOfRange{ Min: min, Max: max, } } if min != nil && (*value.Number < *min) { - return params.ErrOutOfRange{ + return ErrValueOutOfRange{ Min: min, } } if max != nil && (*value.Number > *max) { - return params.ErrOutOfRange{ + return ErrValueOutOfRange{ Max: max, } } diff --git a/actr/modules/params.go b/actr/modules/params.go index 3c468e7..7b038f1 100644 --- a/actr/modules/params.go +++ b/actr/modules/params.go @@ -1,5 +1,45 @@ package modules +import ( + "fmt" + "strings" + + "github.com/asmaloney/gactar/util/numbers" +) + +type ErrUnrecognizedOption struct { + Option string +} + +func (e ErrUnrecognizedOption) Error() string { + return fmt.Sprintf("unrecognized option %q", e.Option) +} + +type ErrValueOutOfRange struct { + Min *float64 + Max *float64 +} + +func (e ErrValueOutOfRange) Error() string { + if e.Min != nil && e.Max == nil { + return fmt.Sprintf("is out of range (minimum %s)", numbers.Float64Str(*e.Min)) + } + + if e.Min == nil && e.Max != nil { + return fmt.Sprintf("is out of range (maximum %s)", numbers.Float64Str(*e.Max)) + } + + return fmt.Sprintf("is out of range (%s-%s)", numbers.Float64Str(*e.Min), numbers.Float64Str(*e.Max)) +} + +type ErrInvalidValue struct { + Expected []string +} + +func (e ErrInvalidValue) Error() string { + return fmt.Sprintf("must be must be one of %q", strings.Join(e.Expected, ", ")) +} + // Ptr simply returns a pointer to a literal. e.g. Ptr(0.5) // This is useful when passing literals to functions which require pointers to basic types. func Ptr[T any](v T) *T { diff --git a/actr/modules/procedural.go b/actr/modules/procedural.go index dc3b8ee..60f14a5 100644 --- a/actr/modules/procedural.go +++ b/actr/modules/procedural.go @@ -1,7 +1,7 @@ package modules import ( - "github.com/asmaloney/gactar/actr/params" + "github.com/asmaloney/gactar/util/keyvalue" ) type Procedural struct { @@ -35,7 +35,7 @@ func NewProcedural() *Procedural { } // SetParam is called to set our module's parameter from the parameter in the code ("param") -func (p *Procedural) SetParam(param *params.Param) (err error) { +func (p *Procedural) SetParam(param *keyvalue.KeyValue) (err error) { err = p.ValidateParam(param) if err != nil { return diff --git a/actr/params/errors.go b/actr/params/errors.go deleted file mode 100644 index 8f999ba..0000000 --- a/actr/params/errors.go +++ /dev/null @@ -1,73 +0,0 @@ -package params - -import ( - "errors" - "fmt" - "strings" - - "github.com/asmaloney/gactar/util/numbers" -) - -var ( - ErrUnrecognizedParam = errors.New("unrecognized option") -) - -// ParamType is used when outputting "invalid type" errors -type ParamType int - -const ( - Boolean ParamType = iota - Number -) - -func (p ParamType) String() string { - switch p { - case Boolean: - return "boolean" - case Number: - return "number" - } - - return "unknown" -} - -type ErrInvalidType struct { - ExpectedType ParamType -} - -func (e ErrInvalidType) Error() string { - expected := e.ExpectedType.String() - - if e.ExpectedType == Boolean { - expected = "'true' or 'false'" - } else { - expected = fmt.Sprintf("a %s", expected) - } - - return fmt.Sprintf("must be %s", expected) -} - -type ErrInvalidOption struct { - Expected []string -} - -func (e ErrInvalidOption) Error() string { - return fmt.Sprintf("must be must be one of %q", strings.Join(e.Expected, ", ")) -} - -type ErrOutOfRange struct { - Min *float64 - Max *float64 -} - -func (e ErrOutOfRange) Error() string { - if e.Min != nil && e.Max == nil { - return fmt.Sprintf("is out of range (minimum %s)", numbers.Float64Str(*e.Min)) - } - - if e.Min == nil && e.Max != nil { - return fmt.Sprintf("is out of range (maximum %s)", numbers.Float64Str(*e.Max)) - } - - return fmt.Sprintf("is out of range (%s-%s)", numbers.Float64Str(*e.Min), numbers.Float64Str(*e.Max)) -} diff --git a/amod/amod.go b/amod/amod.go index b9b6f86..3c512fe 100644 --- a/amod/amod.go +++ b/amod/amod.go @@ -13,9 +13,9 @@ import ( "github.com/asmaloney/gactar/actr" "github.com/asmaloney/gactar/actr/buffer" "github.com/asmaloney/gactar/actr/modules" - "github.com/asmaloney/gactar/actr/params" "github.com/asmaloney/gactar/util/issues" + "github.com/asmaloney/gactar/util/keyvalue" ) var ( @@ -207,19 +207,19 @@ func addGACTAR(model *actr.Model, log *issueLog, list []*field) { for _, field := range list { value := field.Value - param := fieldToParam(field) - err := model.SetParam(param) + kv := fieldToKeyValue(field) + err := model.SetParam(kv) if err != nil { switch { - // value errors - case errors.As(err, ¶ms.ErrInvalidType{}) || - errors.As(err, ¶ms.ErrInvalidOption{}): - log.errorTR(value.Tokens, 1, 1, "'%s' %v", field.Key, err) + // field errors + case errors.As(err, &modules.ErrUnrecognizedOption{}): + log.errorTR(field.Tokens, 0, 1, "%v in gactar section", err) continue - // field errors - case errors.Is(err, params.ErrUnrecognizedParam): - log.errorTR(field.Tokens, 0, 1, "%v in gactar section: '%s'", err, field.Key) + // value errors + case errors.As(err, &keyvalue.ErrInvalidType{}) || + errors.As(err, &modules.ErrInvalidValue{}): + log.errorTR(value.Tokens, 1, 1, "'%s' %v", field.Key, err) continue default: @@ -263,20 +263,20 @@ func setModuleParams(module modules.Interface, log *issueLog, fields []*field) { for _, field := range fields { value := field.Value - param := fieldToParam(field) - err := module.SetParam(param) + kv := fieldToKeyValue(field) + err := module.SetParam(kv) if err != nil { switch { - // value errors - case errors.As(err, ¶ms.ErrInvalidType{}) || - errors.As(err, ¶ms.ErrInvalidOption{}) || - errors.As(err, ¶ms.ErrOutOfRange{}): - log.errorTR(value.Tokens, 1, 1, "%s '%s' %v", moduleName, field.Key, err) + // field errors + case errors.As(err, &modules.ErrUnrecognizedOption{}): + log.errorTR(field.Tokens, 0, 1, "%v in %s config", err, moduleName) continue - // field errors - case errors.Is(err, params.ErrUnrecognizedParam): - log.errorTR(field.Tokens, 0, 1, "%v in %s config: '%s'", err, moduleName, field.Key) + // value errors + case errors.As(err, &keyvalue.ErrInvalidType{}) || + errors.As(err, &modules.ErrInvalidValue{}) || + errors.As(err, &modules.ErrValueOutOfRange{}): + log.errorTR(value.Tokens, 1, 1, "%s '%s' %v", moduleName, field.Key, err) continue default: @@ -559,25 +559,25 @@ func addProductions(model *actr.Model, log *issueLog, productions *productionSec } } -func fieldToParam(f *field) *params.Param { +func fieldToKeyValue(f *field) *keyvalue.KeyValue { value := f.Value if f.Value.OpenBrace != nil { - var param *params.Param + var kv *keyvalue.KeyValue if value.Field != nil { - param = fieldToParam(value.Field) + kv = fieldToKeyValue(value.Field) } - return ¶ms.Param{ + return &keyvalue.KeyValue{ Key: f.Key, - Value: params.Value{Field: param}, + Value: keyvalue.Value{Field: kv}, } } - return ¶ms.Param{ + return &keyvalue.KeyValue{ Key: f.Key, - Value: params.Value{ + Value: keyvalue.Value{ ID: value.ID, Str: value.Str, Number: value.Number, diff --git a/amod/amod_config_test.go b/amod/amod_config_test.go index 10d506e..08e0b63 100644 --- a/amod/amod_config_test.go +++ b/amod/amod_config_test.go @@ -25,7 +25,7 @@ func Example_gactarUnrecognizedField() { ~~ productions ~~`) // Output: - // ERROR: unrecognized option in gactar section: 'foo' (line 5, col 10) + // ERROR: unrecognized option "foo" in gactar section (line 5, col 10) } func Example_gactarUnrecognizedLogLevel() { @@ -51,7 +51,7 @@ func Example_gactarUnrecognizedNestedValue() { ~~ productions ~~`) // Output: - // ERROR: unrecognized option in gactar section: 'foo' (line 5, col 10) + // ERROR: unrecognized option "foo" in gactar section (line 5, col 10) } func Example_gactarFieldNotANestedValue() { @@ -278,7 +278,7 @@ func Example_imaginalFieldUnrecognized() { ~~ productions ~~`) // Output: - // ERROR: unrecognized option in imaginal config: 'foo' (line 6, col 13) + // ERROR: unrecognized option "foo" in imaginal config (line 6, col 13) } func Example_memoryFieldUnrecognized() { @@ -293,7 +293,7 @@ func Example_memoryFieldUnrecognized() { ~~ productions ~~`) // Output: - // ERROR: unrecognized option in memory config: 'foo' (line 6, col 11) + // ERROR: unrecognized option "foo" in memory config (line 6, col 11) } func Example_memoryDecayOutOfRange() { @@ -338,5 +338,5 @@ func Example_proceduralFieldUnrecognized() { ~~ productions ~~`) // Output: - // ERROR: unrecognized option in procedural config: 'foo' (line 6, col 15) + // ERROR: unrecognized option "foo" in procedural config (line 6, col 15) } diff --git a/actr/params/params.go b/util/keyvalue/keyvalue.go similarity index 58% rename from actr/params/params.go rename to util/keyvalue/keyvalue.go index 9e101aa..21e1c0c 100644 --- a/actr/params/params.go +++ b/util/keyvalue/keyvalue.go @@ -1,5 +1,5 @@ -// Package params implements parsed parameter information. -package params +// Package keyvalue implements parsed key/value information. +package keyvalue import "golang.org/x/exp/slices" @@ -8,16 +8,16 @@ var boolean = []string{ "false", } -// Value mimics amod.fieldValue but without tokens. +// Value stores an ID, string, number, or another KeyValue (recursive). type Value struct { ID *string Str *string Number *float64 - Field *Param + Field *KeyValue } -// Param is the key/value of a parameter from the parsed amod code. -type Param struct { +// KeyValue is the key/value of a parameter from the parsed amod code. +type KeyValue struct { Key string Value Value } diff --git a/util/keyvalue/keyvalue_errors.go b/util/keyvalue/keyvalue_errors.go new file mode 100644 index 0000000..c5e52c7 --- /dev/null +++ b/util/keyvalue/keyvalue_errors.go @@ -0,0 +1,40 @@ +package keyvalue + +import ( + "fmt" +) + +// ValueType is used when outputting "invalid type" errors +type ValueType int + +const ( + Boolean ValueType = iota + Number +) + +func (v ValueType) String() string { + switch v { + case Boolean: + return "boolean" + case Number: + return "number" + } + + return "unknown" +} + +type ErrInvalidType struct { + ExpectedType ValueType +} + +func (e ErrInvalidType) Error() string { + expected := e.ExpectedType.String() + + if e.ExpectedType == Boolean { + expected = "'true' or 'false'" + } else { + expected = fmt.Sprintf("a %s", expected) + } + + return fmt.Sprintf("must be %s", expected) +}