examples/advanced/02_structs.cat
#!/usr/bin/env catnip
# Exemple : Structures

print("⇒ Déclaration et instanciation")

struct Point { x; y; }

p1 = Point(10, 20)
p2 = Point(x=5, y=15)

print("p1 =", p1)
print("p2 =", p2)
print("p1.x =", p1.x)
print("p2.y =", p2.y)

print()
print("⇒ Egalite structurelle")

a = Point(1, 2)
b = Point(1, 2)
c = Point(3, 4)

print("a == b:", a == b)
print("a == c:", a == c)

print()
print("⇒ Mutation de champs")

struct Color { r; g; b; }

c = Color(255, 0, 0)
c.g = 128
c.b = 64
print("après mutation:", c)

print()
print("⇒ Structures imbriquees")

struct Vector2D { x; y; }
struct Particle { position; velocity; mass; }

v = Vector2D(10, 20)
p = Particle(
    Vector2D(0, 0),
    Vector2D(5, 10),
    1.5,
)

print("position:", p.position)
print("velocity.x:", p.velocity.x)
print("mass:", p.mass)

print()
print("⇒ Structures dans des collections")

points = list(Point(1, 2), Point(3, 4), Point(5, 6))

for pt in points {
    print("point:", pt.x, pt.y)
}

print()
print("⇒ Structures avec valeurs complexes")

struct Container { data; metadata; }

c = Container(
    list(1, 2, 3),
    dict(name="test", version=1),
)

print("data:", c.data)
print("name:", c.metadata['name'])

print()
print("⇒ Méthodes inline")

struct Vec2 {
    x; y;

    norm2(self) => {
        self.x ** 2 + self.y ** 2
    }
}

v = Vec2(3, 4)
print("v.norm2() =", v.norm2())

print()
print("⇒ Capture lexicale dans une méthode")

factory = () => {
    offset = 10
    struct ShiftedPoint {
        x
        shifted(self) => { self.x + offset }
    }
    ShiftedPoint
}

ShiftedPoint = factory()
print("ShiftedPoint(5).shifted() =", ShiftedPoint(5).shifted())

print()
print("⇒ Pattern matching de struct")

struct Pixel { r; g; b; }

describe = (px) => {
    match px {
        Pixel{r, g, b} if r == g and g == b => { "grayscale" }
        Pixel{r, g, b} => { "rgb" }
        _ => { "unknown" }
    }
}

print("describe(Pixel(10, 10, 10)) =", describe(Pixel(10, 10, 10)))
print("describe(Pixel(255, 0, 128)) =", describe(Pixel(255, 0, 128)))

print()
print("⇒ Heritage simple avec extends")

struct BaseVec {
    x; y;

    sum(self) => { self.x + self.y }
}

struct ExtendedVec extends(BaseVec) {
    z

    product(self) => { self.x * self.y * self.z }
}

ev = ExtendedVec(1, 2, 3)
print("ev.sum() =", ev.sum())
print("ev.product() =", ev.product())
print("ev.x =", ev.x)
print("ev.z =", ev.z)

print()
print("⇒ Heritage avec override de méthode")

struct Base {
    x
    value(self) => { self.x }
}

struct Child extends(Base) {
    value(self) => { self.x * 10 }
}

print("Base(5).value() =", Base(5).value())
print("Child(5).value() =", Child(5).value())

print()
print("⇒ Héritage avec valeurs par défaut")

struct Config {
    host; port = 8080;
}

struct SecureConfig extends(Config) {
    ssl = True
}

sc = SecureConfig("localhost")
print("sc.host =", sc.host)
print("sc.port =", sc.port)
print("sc.ssl =", sc.ssl)
print("representation:", sc)

print()
print("⇒ Constructeur init")

struct Counter {
    x
    init(self) => { self.x = self.x + 1 }
}

print("Counter(10).x =", Counter(10).x)

struct Validated {
    x
    init(self) => { self.x = self.x * 2; 999 }
}

print("Validated(5).x =", Validated(5).x)

print()
print("⇒ Acces au parent avec super")

struct Animal {
    name
    speak(self) => { self.name + " ..." }
}

struct Dog extends(Animal) {
    speak(self) => { super.speak() + " woof!" }
}

struct Puppy extends(Dog) {
    speak(self) => { super.speak() + " *tail wag*" }
}

print("Dog('Rex').speak() =", Dog("Rex").speak())
print("Puppy('Max').speak() =", Puppy("Max").speak())

print()
print("⇒ super.init()")

struct Logger {
    tag
    init(self) => { self.tag = "[" + self.tag + "]" }
}

struct ErrorLogger extends(Logger) {
    init(self) => {
        super.init()
        self.tag = self.tag + " ERROR"
    }
}

print("ErrorLogger('app').tag =", ErrorLogger("app").tag)

print()
print("⇒ Extends + Implements")

trait Serializable { serialize(self) => { "json" } }

struct DataVec extends(BaseVec) implements(Serializable) {
    label
}

dv = DataVec(1, 2, "origin")
print("dv.sum() =", dv.sum())
print("dv.serialize() =", dv.serialize())
print("dv.label =", dv.label)

print()
print("⇒ Abstract methods")

struct Shape {
    @abstract
    area(self)
    @abstract
    perimeter(self)
    describe(self) => { f"area={self.area()}, perimeter={self.perimeter()}" }
}

# Shape() échouerait : impossible d'instancier une structure abstraite

struct Circle extends(Shape) {
    radius
    area(self) => { 3.14159 * self.radius ** 2 }
    perimeter(self) => { 2 * 3.14159 * self.radius }
}

c = Circle(5)
print("Circle(5).area() =", c.area())
print("Circle(5).describe() =", c.describe())

print()
print("⇒ Static methods")

struct Config {
    host; port;

    @static
    default() => { Config("localhost", 8080) }

    @static
    from_port(p) => { Config("0.0.0.0", p) }
}

print("Config.default() =", Config.default())
print("Config.from_port(3000) =", Config.from_port(3000))

# Appelable aussi sur les instances
c = Config("example.com", 443)
print("c.default() =", c.default())