mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
Merge pull request #639 from leftibot/fix/issue-591-classes
[WIP] Issue #591 — Classes?
This commit is contained in:
commit
cb48b7cc7e
105
cheatsheet.md
105
cheatsheet.md
@ -477,34 +477,109 @@ n(2); // returns 20
|
||||
|
||||
|
||||
|
||||
## ChaiScript Defined Types
|
||||
## ChaiScript Defined Types (Classes)
|
||||
|
||||
Define a type called "MyType" with one member value "a" and a getter
|
||||
ChaiScript supports user-defined types using the `class` keyword. Classes can have attributes,
|
||||
constructors, methods, guards, and operator overloads. There is no inheritance between
|
||||
ChaiScript-defined types, but C++ class hierarchies can be exposed (see *Class Hierarchies* above).
|
||||
|
||||
### Preferred
|
||||
### Class Definition (Block Syntax)
|
||||
|
||||
Define a type with attributes, a constructor, and methods inside a `class` block.
|
||||
The keywords `var`, `attr`, and `auto` are interchangeable for declaring attributes.
|
||||
|
||||
```
|
||||
class MyType {
|
||||
var value;
|
||||
def MyType() { this.value = "a"; }
|
||||
def get_value() { "Value Is: " + this.value; }
|
||||
};
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
var r = Rectangle(3, 4)
|
||||
print(r.area()) // prints 12
|
||||
```
|
||||
|
||||
### Alternative
|
||||
### Class Definition (Open Syntax)
|
||||
|
||||
Equivalently, attributes and methods can be defined outside a block using the `TypeName::` prefix.
|
||||
|
||||
```
|
||||
attr MyType::value;
|
||||
def MyType::MyType() { this.value = "a"; }
|
||||
def MyType::get_value() { "Value Is: " + this.value; }
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
```
|
||||
|
||||
Methods can also be added to an existing class after its initial definition:
|
||||
|
||||
```
|
||||
def Rectangle::perimeter() { 2 * (this.width + this.height); }
|
||||
```
|
||||
|
||||
### Using
|
||||
|
||||
```
|
||||
var m = MyType(); // calls constructor
|
||||
print(m.get_value()); // prints "Value Is: a"
|
||||
print(get_value(m)); // prints "Value Is: a"
|
||||
var m = Rectangle(5, 10)
|
||||
print(m.area()) // prints 50 — method call syntax
|
||||
print(area(m)) // prints 50 — function call syntax (equivalent)
|
||||
```
|
||||
|
||||
### Constructor and Method Guards
|
||||
|
||||
Constructors and methods can have guard expressions (after `:`) that control which
|
||||
overload is selected at call time.
|
||||
|
||||
```
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; } // fallback when guard fails
|
||||
}
|
||||
|
||||
Clamped(5).value // 5
|
||||
Clamped(-3).value // 0
|
||||
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
```
|
||||
|
||||
### Operator Overloading
|
||||
|
||||
Operators can be overloaded on user-defined types using backtick-quoted operator names.
|
||||
|
||||
```
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
var v = Vec2(1, 2) + Vec2(3, 4) // v.x == 4, v.y == 6
|
||||
```
|
||||
|
||||
Operators can also be overloaded as free functions with guards:
|
||||
|
||||
```
|
||||
def `-`(a, b) : is_type(a, "Vec2") && is_type(b, "Vec2") {
|
||||
Vec2(a.x - b.x, a.y - b.y)
|
||||
}
|
||||
```
|
||||
|
||||
### Cloning Objects
|
||||
|
||||
Use `clone()` to create a deep copy of a ChaiScript-defined object.
|
||||
|
||||
```
|
||||
var original = Rectangle(10, 20)
|
||||
var copy = clone(original)
|
||||
copy.width = 99
|
||||
print(original.width) // still 10
|
||||
```
|
||||
|
||||
## Dynamic Objects
|
||||
|
||||
103
unittests/class_cheatsheet.chai
Normal file
103
unittests/class_cheatsheet.chai
Normal file
@ -0,0 +1,103 @@
|
||||
// Test for class features documented in the cheatsheet
|
||||
|
||||
// --- Class definition (preferred block syntax) ---
|
||||
class Rectangle {
|
||||
var width
|
||||
var height
|
||||
def Rectangle(w, h) { this.width = w; this.height = h; }
|
||||
def Rectangle() { this.width = 0; this.height = 0; }
|
||||
def area() { this.width * this.height; }
|
||||
}
|
||||
|
||||
auto r = Rectangle(3, 4)
|
||||
assert_equal(12, r.area())
|
||||
|
||||
// --- Default constructor ---
|
||||
auto r2 = Rectangle()
|
||||
assert_equal(0, r2.area())
|
||||
|
||||
// --- Alternative open syntax ---
|
||||
attr Circle::radius
|
||||
def Circle::Circle(r) { this.radius = r; }
|
||||
def Circle::circumference() { 2.0 * 3.14159 * this.radius; }
|
||||
|
||||
auto c = Circle(5.0)
|
||||
assert_equal(5.0, c.radius)
|
||||
|
||||
// --- attr, auto, var all work for attributes ---
|
||||
class AttrTest {
|
||||
attr a
|
||||
auto b
|
||||
var c
|
||||
def AttrTest() { this.a = 1; this.b = 2; this.c = 3; }
|
||||
}
|
||||
|
||||
auto at = AttrTest()
|
||||
assert_equal(1, at.a)
|
||||
assert_equal(2, at.b)
|
||||
assert_equal(3, at.c)
|
||||
|
||||
// --- Constructor guards ---
|
||||
class Clamped {
|
||||
var value
|
||||
def Clamped(x) : x >= 0 { this.value = x; }
|
||||
def Clamped(x) { this.value = 0; }
|
||||
}
|
||||
|
||||
assert_equal(5, Clamped(5).value)
|
||||
assert_equal(0, Clamped(-3).value)
|
||||
|
||||
// --- Method guards ---
|
||||
class Abs {
|
||||
var x
|
||||
def Abs(v) { this.x = v; }
|
||||
def get() : this.x >= 0 { this.x; }
|
||||
def get() { -this.x; }
|
||||
}
|
||||
|
||||
assert_equal(5, Abs(5).get())
|
||||
assert_equal(3, Abs(-3).get())
|
||||
|
||||
// --- Operator overloading ---
|
||||
class Vec2 {
|
||||
var x
|
||||
var y
|
||||
def Vec2(x, y) { this.x = x; this.y = y; }
|
||||
def `+`(other) { Vec2(this.x + other.x, this.y + other.y); }
|
||||
}
|
||||
|
||||
auto v = Vec2(1, 2) + Vec2(3, 4)
|
||||
assert_equal(4, v.x)
|
||||
assert_equal(6, v.y)
|
||||
|
||||
// --- Cloning objects ---
|
||||
auto r3 = Rectangle(10, 20)
|
||||
auto r4 = clone(r3)
|
||||
r4.width = 99
|
||||
assert_equal(10, r3.width)
|
||||
assert_equal(99, r4.width)
|
||||
|
||||
// --- Dynamic attributes ---
|
||||
auto r5 = Rectangle(1, 1)
|
||||
r5.color = "red"
|
||||
assert_equal("red", r5.color)
|
||||
|
||||
// --- Explicit mode ---
|
||||
class Strict {
|
||||
var x
|
||||
def Strict() {
|
||||
this.x = 0
|
||||
this.set_explicit(true)
|
||||
}
|
||||
}
|
||||
|
||||
auto s = Strict()
|
||||
assert_equal(0, s.x)
|
||||
assert_equal(true, s.is_explicit())
|
||||
|
||||
try {
|
||||
s.y = 10
|
||||
assert_equal(true, false) // should not reach here
|
||||
} catch(e) {
|
||||
// expected: cannot add dynamic attribute in explicit mode
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user