gcode/addressablegcode/addressable_gcode.go
// addressablegcode package implements gcode.AddressableGcoder interface to model a gcode with an address element.
//
// Define a Gcode struct that implement gcode.AddressableGcoder interface.
// This struct contain a word field to store the word value and an address field to store the address value.
//
// Like AddressableGcoder interface, this Gcode struct use generics with the restrictions defined by AddressType.
//
// A "New" constructor method allow to instance new Gcode[T] object.
// This method use some internal rules combined with gcode.IsValidWord to validate the inputs before create any instance
package addressablegcode
import (
"fmt"
"math"
"strings"
"github.com/mauroalderete/gcode-core/gcode"
)
//#region gcode addressable struct
// Gcode struct that implements GcodeAddresser interface.
//
// Is composed of a gcode struct and includes an address field to store an address instance.
type Gcode[T gcode.AddressType] struct {
//Gcoder (via embedded gcode.Gcoder interface)
gcode.Gcoder
// word stores the word value of the gcode that we are modeling
word byte
// address stores the address value of the gcode that we are modeling
address T
}
// Address return the value of the address
func (g *Gcode[T]) Address() T {
return g.address
}
// Compare allows checking if the current entity is equal to a Gcoder input
//
// This method is executed when to be called from a Gcode instance or a Gcoder instance that contains a reference to a Gcode object.
// If the input Gcoder does not implement some addressablegcode.Gcode[T] data type then it returns false
func (g *Gcode[T]) Compare(gcode gcode.Gcoder) bool {
if gca, ok := gcode.(*Gcode[T]); ok && gca != nil {
return g.word == gca.Word() && g.address == gca.address
}
return false
}
// HasAddress indicate if the gcode contain or not an address.
//
// This method is called from a GcodeAddressable instance or a GcodeAddresser instance that contains a reference to a GcodeAddressable object.
// Always return true.
func (g *Gcode[T]) HasAddress() bool {
return true
}
// SetAddress allow to store a new value
//
// If the address data type is string then the new value is verified.
// If it doesn't satisfy the a string format then SetAddress returns an error.
func (g *Gcode[T]) SetAddress(address T) error {
if ok, err := isGenericValueAnStringAddressValid(address); ok {
if err != nil {
return fmt.Errorf("failed set the value %v at the %T address: %w", address, address, err)
}
}
g.address = address
return nil
}
// String return gcode formatted
func (g *Gcode[T]) String() string {
var address string
switch value := any(g.address).(type) {
case float32:
{
switch g.Word() {
case 'E':
{
switch {
case math.Abs(float64(value)) < 0.0001:
{
address = "0.0000"
}
default:
{
address = fmt.Sprintf("%.4f", value)
}
}
}
default:
{
switch {
case math.Abs(float64(value)) < 0.001:
{
address = "0.000"
}
default:
{
address = fmt.Sprintf("%.3f", value)
}
}
}
}
}
default:
{
address = fmt.Sprintf("%v", g.address)
}
}
return fmt.Sprintf("%s%s", string(g.word), address)
}
// Word return a copy of the word struct in the gcode
func (g *Gcode[T]) Word() byte {
return g.word
}
//#endregion
//#region package constructor
// New return a new Gcode[T] instance or error if some inputs are invalids
// word is the letter that compose the gcode
// address is the value of the gcode
func New[T gcode.AddressType](word byte, address T) (*Gcode[T], error) {
// Try instace Word struct
err := gcode.IsValidWord(word)
if err != nil {
return nil, fmt.Errorf("failed to create an addressable gcode instance of type %T when trying to use %v word: %w", address, word, err)
}
// Try instace Address struct
if ok, err := isGenericValueAnStringAddressValid(address); ok {
if err != nil {
return nil, fmt.Errorf("failed to create an string address instance using the expression %v: %w", address, err)
}
}
return &Gcode[T]{
word: word,
address: address,
}, nil
}
//#endregion
//#region private functions
// isAddressStringValid allow knowing if a string input can be an address value of string data type valid.
//
// Return an error if s string is invalid.
//
// Return nil if s string satisfies the format of address value of string data type.
func isAddressStringValid(address string) error {
if len(address) <= 1 {
return fmt.Errorf("gcode address string is too short: %v", address)
}
if strings.ContainsAny(address, "\t\n\r") {
return fmt.Errorf("gcode address string contains invalid chars: %v", address)
}
if !(address[0] == '"' && address[len(address)-1] == '"') {
return fmt.Errorf("gcode address string isn't enclosed in quotes: %v", address)
}
for _, v := range strings.Split(address[1:len(address)-1], "\"\"") {
if strings.ContainsRune(v, '"') {
return fmt.Errorf("gcode address string hasn't a valid use of the quotes: %v", address)
}
}
return nil
}
// isGenericValueAnStringAddressValid return true if the value is an string and return error if this string value is not string address valid.
//
// It returns false if the value is not of the string data type.
// In this case, it does not be to verify any string, therefore never it returns an error.
func isGenericValueAnStringAddressValid[T gcode.AddressType](value T) (bool, error) {
if stringValue, ok := any(value).(string); ok {
err := isAddressStringValid(stringValue)
if err != nil {
return true, err
}
return true, nil
}
return false, nil
}
//#endregion