UPdate.
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Classes" %}
|
||||
{% set breadcrumb = [{"url": "language/index.html", "title": "Language"}, {"title": "Classes"}] %}
|
||||
{% set prev_page = {"url": "language/index.html", "title": "Syntax Overview"} %}
|
||||
{% set next_page = {"url": "language/methods.html", "title": "Methods"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Classes</h1>
|
||||
|
||||
<p>Wren is a class-based object-oriented language. Everything in Wren is an object, and every object is an instance of a class.</p>
|
||||
|
||||
<h2>Defining Classes</h2>
|
||||
<p>Define a class with the <code>class</code> keyword:</p>
|
||||
<pre><code>class Animal {
|
||||
}</code></pre>
|
||||
|
||||
<p>This creates a class named <code>Animal</code> with no methods or fields.</p>
|
||||
|
||||
<h2>Constructors</h2>
|
||||
<p>Constructors create new instances. Define them with <code>construct</code>:</p>
|
||||
<pre><code>class Person {
|
||||
construct new(name, age) {
|
||||
_name = name
|
||||
_age = age
|
||||
}
|
||||
}
|
||||
|
||||
var alice = Person.new("Alice", 30)</code></pre>
|
||||
|
||||
<p>A class can have multiple named constructors:</p>
|
||||
<pre><code>class Point {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
construct origin() {
|
||||
_x = 0
|
||||
_y = 0
|
||||
}
|
||||
|
||||
construct fromList(list) {
|
||||
_x = list[0]
|
||||
_y = list[1]
|
||||
}
|
||||
}
|
||||
|
||||
var p1 = Point.new(3, 4)
|
||||
var p2 = Point.origin()
|
||||
var p3 = Point.fromList([5, 6])</code></pre>
|
||||
|
||||
<h2>Fields</h2>
|
||||
<p>Instance fields are prefixed with <code>_</code>. They are private to the class:</p>
|
||||
<pre><code>class Counter {
|
||||
construct new() {
|
||||
_count = 0
|
||||
}
|
||||
|
||||
increment() {
|
||||
_count = _count + 1
|
||||
}
|
||||
|
||||
count { _count }
|
||||
}</code></pre>
|
||||
|
||||
<p>Fields are not declared; they are created when first assigned.</p>
|
||||
|
||||
<h2>Getters and Setters</h2>
|
||||
<p>Getters are methods without parentheses:</p>
|
||||
<pre><code>class Circle {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
radius { _radius }
|
||||
area { 3.14159 * _radius * _radius }
|
||||
}
|
||||
|
||||
var c = Circle.new(5)
|
||||
System.print(c.radius) // 5
|
||||
System.print(c.area) // 78.53975</code></pre>
|
||||
|
||||
<p>Setters use <code>=</code> suffix:</p>
|
||||
<pre><code>class Circle {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
radius { _radius }
|
||||
radius=(value) { _radius = value }
|
||||
}
|
||||
|
||||
var c = Circle.new(5)
|
||||
c.radius = 10
|
||||
System.print(c.radius) // 10</code></pre>
|
||||
|
||||
<h2>Methods</h2>
|
||||
<p>Methods are defined inside the class body:</p>
|
||||
<pre><code>class Rectangle {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
}
|
||||
|
||||
area() {
|
||||
return _width * _height
|
||||
}
|
||||
|
||||
perimeter() {
|
||||
return 2 * (_width + _height)
|
||||
}
|
||||
}
|
||||
|
||||
var rect = Rectangle.new(4, 5)
|
||||
System.print(rect.area()) // 20
|
||||
System.print(rect.perimeter()) // 18</code></pre>
|
||||
|
||||
<h2>Static Members</h2>
|
||||
<p>Static methods and fields belong to the class, not instances:</p>
|
||||
<pre><code>class Math {
|
||||
static pi { 3.14159 }
|
||||
|
||||
static square(x) {
|
||||
return x * x
|
||||
}
|
||||
|
||||
static cube(x) {
|
||||
return x * x * x
|
||||
}
|
||||
}
|
||||
|
||||
System.print(Math.pi) // 3.14159
|
||||
System.print(Math.square(4)) // 16
|
||||
System.print(Math.cube(3)) // 27</code></pre>
|
||||
|
||||
<p>Static fields use double underscore:</p>
|
||||
<pre><code>class Counter {
|
||||
static count { __count }
|
||||
|
||||
static increment() {
|
||||
if (__count == null) __count = 0
|
||||
__count = __count + 1
|
||||
}
|
||||
}
|
||||
|
||||
Counter.increment()
|
||||
Counter.increment()
|
||||
System.print(Counter.count) // 2</code></pre>
|
||||
|
||||
<h2>Inheritance</h2>
|
||||
<p>Classes can inherit from a single superclass using <code>is</code>:</p>
|
||||
<pre><code>class Animal {
|
||||
construct new(name) {
|
||||
_name = name
|
||||
}
|
||||
|
||||
name { _name }
|
||||
|
||||
speak() {
|
||||
System.print("...")
|
||||
}
|
||||
}
|
||||
|
||||
class Dog is Animal {
|
||||
construct new(name, breed) {
|
||||
super(name)
|
||||
_breed = breed
|
||||
}
|
||||
|
||||
breed { _breed }
|
||||
|
||||
speak() {
|
||||
System.print("Woof!")
|
||||
}
|
||||
}
|
||||
|
||||
var dog = Dog.new("Rex", "German Shepherd")
|
||||
System.print(dog.name) // Rex
|
||||
System.print(dog.breed) // German Shepherd
|
||||
dog.speak() // Woof!</code></pre>
|
||||
|
||||
<h3>Calling Super</h3>
|
||||
<p>Use <code>super</code> to call the superclass constructor or methods:</p>
|
||||
<pre><code>class Parent {
|
||||
construct new() {
|
||||
_value = 10
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
describe() {
|
||||
System.print("Parent value: %(_value)")
|
||||
}
|
||||
}
|
||||
|
||||
class Child is Parent {
|
||||
construct new() {
|
||||
super()
|
||||
_extra = 20
|
||||
}
|
||||
|
||||
describe() {
|
||||
super.describe()
|
||||
System.print("Child extra: %(_extra)")
|
||||
}
|
||||
}
|
||||
|
||||
var child = Child.new()
|
||||
child.describe()
|
||||
// Output:
|
||||
// Parent value: 10
|
||||
// Child extra: 20</code></pre>
|
||||
|
||||
<h2>This</h2>
|
||||
<p>Use <code>this</code> to refer to the current instance:</p>
|
||||
<pre><code>class Node {
|
||||
construct new(value) {
|
||||
_value = value
|
||||
_next = null
|
||||
}
|
||||
|
||||
value { _value }
|
||||
next { _next }
|
||||
|
||||
append(value) {
|
||||
_next = Node.new(value)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
var n = Node.new(1).append(2).append(3)</code></pre>
|
||||
|
||||
<h2>Object Class</h2>
|
||||
<p>All classes implicitly inherit from <code>Object</code>:</p>
|
||||
<pre><code>class Foo {}
|
||||
|
||||
System.print(Foo is Class) // true
|
||||
System.print(Foo.supertype) // Object</code></pre>
|
||||
|
||||
<h2>Type Checking</h2>
|
||||
<p>Use <code>is</code> to check if an object is an instance of a class:</p>
|
||||
<pre><code>var dog = Dog.new("Rex", "Shepherd")
|
||||
|
||||
System.print(dog is Dog) // true
|
||||
System.print(dog is Animal) // true
|
||||
System.print(dog is Object) // true
|
||||
System.print(dog is String) // false</code></pre>
|
||||
|
||||
<p>Get the class of an object with <code>type</code>:</p>
|
||||
<pre><code>System.print(dog.type) // Dog
|
||||
System.print(dog.type.name) // Dog
|
||||
System.print(dog.type.supertype) // Animal</code></pre>
|
||||
|
||||
<h2>Foreign Classes</h2>
|
||||
<p>Foreign classes are implemented in C. They can hold native data:</p>
|
||||
<pre><code>foreign class Socket {
|
||||
construct new() {}
|
||||
foreign connect(host, port)
|
||||
foreign send(data)
|
||||
foreign receive()
|
||||
foreign close()
|
||||
}</code></pre>
|
||||
|
||||
<p>Foreign classes are used by built-in modules to provide native functionality.</p>
|
||||
|
||||
<h2>Complete Example</h2>
|
||||
<pre><code>class Shape {
|
||||
construct new() {}
|
||||
|
||||
area { 0 }
|
||||
perimeter { 0 }
|
||||
|
||||
describe() {
|
||||
System.print("Area: %(area)")
|
||||
System.print("Perimeter: %(perimeter)")
|
||||
}
|
||||
}
|
||||
|
||||
class Rectangle is Shape {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
}
|
||||
|
||||
width { _width }
|
||||
height { _height }
|
||||
area { _width * _height }
|
||||
perimeter { 2 * (_width + _height) }
|
||||
}
|
||||
|
||||
class Square is Rectangle {
|
||||
construct new(side) {
|
||||
super(side, side)
|
||||
}
|
||||
}
|
||||
|
||||
class Circle is Shape {
|
||||
construct new(radius) {
|
||||
_radius = radius
|
||||
}
|
||||
|
||||
static pi { 3.14159 }
|
||||
|
||||
radius { _radius }
|
||||
area { Circle.pi * _radius * _radius }
|
||||
perimeter { 2 * Circle.pi * _radius }
|
||||
}
|
||||
|
||||
var shapes = [
|
||||
Rectangle.new(4, 5),
|
||||
Square.new(3),
|
||||
Circle.new(2)
|
||||
]
|
||||
|
||||
for (shape in shapes) {
|
||||
System.print("%(shape.type.name):")
|
||||
shape.describe()
|
||||
System.print("")
|
||||
}</code></pre>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,232 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Control Flow" %}
|
||||
{% set breadcrumb = [{"url": "language/index.html", "title": "Language"}, {"title": "Control Flow"}] %}
|
||||
{% set prev_page = {"url": "language/methods.html", "title": "Methods"} %}
|
||||
{% set next_page = {"url": "language/fibers.html", "title": "Fibers"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Control Flow</h1>
|
||||
|
||||
<p>Wren provides standard control flow constructs for conditionals, loops, and early exit.</p>
|
||||
|
||||
<h2>Truthiness</h2>
|
||||
<p>Before covering control flow, understand how Wren evaluates truthiness:</p>
|
||||
<ul>
|
||||
<li><code>false</code> is falsy</li>
|
||||
<li><code>null</code> is falsy</li>
|
||||
<li>Everything else is truthy (including <code>0</code>, <code>""</code>, <code>[]</code>)</li>
|
||||
</ul>
|
||||
<pre><code>if (0) System.print("0 is truthy")
|
||||
if ("") System.print("empty string is truthy")
|
||||
if ([]) System.print("empty list is truthy")
|
||||
if (false) System.print("false is falsy") // Not printed
|
||||
if (null) System.print("null is falsy") // Not printed</code></pre>
|
||||
|
||||
<h2>If Statements</h2>
|
||||
<p>Basic conditional execution:</p>
|
||||
<pre><code>if (condition) {
|
||||
System.print("condition is true")
|
||||
}</code></pre>
|
||||
|
||||
<h3>If-Else</h3>
|
||||
<pre><code>if (score >= 90) {
|
||||
System.print("A")
|
||||
} else {
|
||||
System.print("Not A")
|
||||
}</code></pre>
|
||||
|
||||
<h3>If-Else If-Else</h3>
|
||||
<pre><code>if (score >= 90) {
|
||||
System.print("A")
|
||||
} else if (score >= 80) {
|
||||
System.print("B")
|
||||
} else if (score >= 70) {
|
||||
System.print("C")
|
||||
} else {
|
||||
System.print("F")
|
||||
}</code></pre>
|
||||
|
||||
<h3>Single Expression</h3>
|
||||
<p>For single expressions, braces are optional:</p>
|
||||
<pre><code>if (x > 0) System.print("positive")</code></pre>
|
||||
|
||||
<h2>Ternary Operator</h2>
|
||||
<p>For inline conditionals:</p>
|
||||
<pre><code>var status = age >= 18 ? "adult" : "minor"
|
||||
var max = a > b ? a : b</code></pre>
|
||||
|
||||
<h2>Logical Operators</h2>
|
||||
|
||||
<h3>And (&&)</h3>
|
||||
<p>Returns the first falsy value or the last value:</p>
|
||||
<pre><code>System.print(true && false) // false
|
||||
System.print(true && true) // true
|
||||
System.print(1 && 2) // 2
|
||||
System.print(null && 1) // null</code></pre>
|
||||
|
||||
<h3>Or (||)</h3>
|
||||
<p>Returns the first truthy value or the last value:</p>
|
||||
<pre><code>System.print(false || true) // true
|
||||
System.print(false || false) // false
|
||||
System.print(null || "default") // default
|
||||
System.print(1 || 2) // 1</code></pre>
|
||||
|
||||
<p>Use <code>||</code> for default values:</p>
|
||||
<pre><code>var name = providedName || "Anonymous"</code></pre>
|
||||
|
||||
<h2>While Loops</h2>
|
||||
<p>Repeat while a condition is true:</p>
|
||||
<pre><code>var i = 0
|
||||
while (i < 5) {
|
||||
System.print(i)
|
||||
i = i + 1
|
||||
}</code></pre>
|
||||
|
||||
<div class="example-output">0
|
||||
1
|
||||
2
|
||||
3
|
||||
4</div>
|
||||
|
||||
<h2>For Loops</h2>
|
||||
<p>Iterate over any sequence:</p>
|
||||
<pre><code>for (item in [1, 2, 3]) {
|
||||
System.print(item)
|
||||
}</code></pre>
|
||||
|
||||
<h3>Range Iteration</h3>
|
||||
<pre><code>for (i in 1..5) {
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 5
|
||||
|
||||
for (i in 1...5) {
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 (exclusive)</code></pre>
|
||||
|
||||
<h3>String Iteration</h3>
|
||||
<pre><code>for (char in "hello") {
|
||||
System.print(char)
|
||||
}
|
||||
// Prints each character</code></pre>
|
||||
|
||||
<h3>Map Iteration</h3>
|
||||
<pre><code>var person = {"name": "Alice", "age": 30}
|
||||
for (key in person.keys) {
|
||||
System.print("%(key): %(person[key])")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Break</h2>
|
||||
<p>Exit a loop early:</p>
|
||||
<pre><code>for (i in 1..100) {
|
||||
if (i > 5) break
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 2 3 4 5</code></pre>
|
||||
|
||||
<h2>Continue</h2>
|
||||
<p>Skip to the next iteration:</p>
|
||||
<pre><code>for (i in 1..10) {
|
||||
if (i % 2 == 0) continue
|
||||
System.print(i)
|
||||
}
|
||||
// Prints: 1 3 5 7 9 (odd numbers only)</code></pre>
|
||||
|
||||
<h2>Block Scoping</h2>
|
||||
<p>Blocks create new scopes:</p>
|
||||
<pre><code>var x = "outer"
|
||||
{
|
||||
var x = "inner"
|
||||
System.print(x) // inner
|
||||
}
|
||||
System.print(x) // outer</code></pre>
|
||||
|
||||
<p>Variables declared in a block are not visible outside:</p>
|
||||
<pre><code>if (true) {
|
||||
var temp = "temporary"
|
||||
}
|
||||
// temp is not accessible here</code></pre>
|
||||
|
||||
<h2>Iterating with Index</h2>
|
||||
<p>Use range to get indices:</p>
|
||||
<pre><code>var list = ["a", "b", "c"]
|
||||
for (i in 0...list.count) {
|
||||
System.print("%(i): %(list[i])")
|
||||
}</code></pre>
|
||||
|
||||
<div class="example-output">0: a
|
||||
1: b
|
||||
2: c</div>
|
||||
|
||||
<h2>Infinite Loops</h2>
|
||||
<p>Create with <code>while (true)</code>:</p>
|
||||
<pre><code>var count = 0
|
||||
while (true) {
|
||||
count = count + 1
|
||||
if (count >= 5) break
|
||||
System.print(count)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Nested Loops</h2>
|
||||
<pre><code>for (i in 1..3) {
|
||||
for (j in 1..3) {
|
||||
System.print("%(i), %(j)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Break only exits the innermost loop:</p>
|
||||
<pre><code>for (i in 1..3) {
|
||||
for (j in 1..10) {
|
||||
if (j > 2) break // Only breaks inner loop
|
||||
System.print("%(i), %(j)")
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h2>Iteration Patterns</h2>
|
||||
|
||||
<h3>Filtering</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
var evens = []
|
||||
for (n in numbers) {
|
||||
if (n % 2 == 0) evens.add(n)
|
||||
}
|
||||
System.print(evens) // [2, 4, 6, 8, 10]</code></pre>
|
||||
|
||||
<h3>Mapping</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
var squared = []
|
||||
for (n in numbers) {
|
||||
squared.add(n * n)
|
||||
}
|
||||
System.print(squared) // [1, 4, 9, 16, 25]</code></pre>
|
||||
|
||||
<h3>Finding</h3>
|
||||
<pre><code>var numbers = [1, 3, 5, 8, 9, 11]
|
||||
var firstEven = null
|
||||
for (n in numbers) {
|
||||
if (n % 2 == 0) {
|
||||
firstEven = n
|
||||
break
|
||||
}
|
||||
}
|
||||
System.print(firstEven) // 8</code></pre>
|
||||
|
||||
<h3>Reducing</h3>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
var sum = 0
|
||||
for (n in numbers) {
|
||||
sum = sum + n
|
||||
}
|
||||
System.print(sum) // 15</code></pre>
|
||||
|
||||
<h2>Functional Alternatives</h2>
|
||||
<p>Lists provide functional methods that are often cleaner:</p>
|
||||
<pre><code>var numbers = [1, 2, 3, 4, 5]
|
||||
|
||||
var evens = numbers.where { |n| n % 2 == 0 }.toList
|
||||
var squared = numbers.map { |n| n * n }.toList
|
||||
var sum = numbers.reduce(0) { |acc, n| acc + n }</code></pre>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,284 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Fibers" %}
|
||||
{% set breadcrumb = [{"url": "language/index.html", "title": "Language"}, {"title": "Fibers"}] %}
|
||||
{% set prev_page = {"url": "language/control-flow.html", "title": "Control Flow"} %}
|
||||
{% set next_page = {"url": "language/modules.html", "title": "Modules"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Fibers</h1>
|
||||
|
||||
<p>Fibers are Wren's mechanism for cooperative concurrency. They are lightweight threads of execution that you explicitly control. Unlike OS threads, only one fiber runs at a time, and switching between them is explicit.</p>
|
||||
|
||||
<h2>Creating Fibers</h2>
|
||||
<p>Create a fiber with <code>Fiber.new</code>:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("Inside fiber")
|
||||
}</code></pre>
|
||||
|
||||
<p>The fiber does not run immediately. It is suspended until you start it.</p>
|
||||
|
||||
<h2>Running Fibers</h2>
|
||||
|
||||
<h3>call()</h3>
|
||||
<p>Start a fiber and wait for it to complete or yield:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("Running")
|
||||
}
|
||||
|
||||
fiber.call() // Prints "Running"</code></pre>
|
||||
|
||||
<h3>try()</h3>
|
||||
<p>Start a fiber and catch any runtime errors:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
Fiber.abort("Something went wrong")
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
System.print("Error: %(error)") // Error: Something went wrong</code></pre>
|
||||
|
||||
<h2>Yielding</h2>
|
||||
<p>Fibers can pause execution and return control to the caller:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
System.print("First")
|
||||
Fiber.yield()
|
||||
System.print("Second")
|
||||
Fiber.yield()
|
||||
System.print("Third")
|
||||
}
|
||||
|
||||
fiber.call() // Prints "First"
|
||||
fiber.call() // Prints "Second"
|
||||
fiber.call() // Prints "Third"</code></pre>
|
||||
|
||||
<h3>Yielding Values</h3>
|
||||
<pre><code>var counter = Fiber.new {
|
||||
Fiber.yield(1)
|
||||
Fiber.yield(2)
|
||||
Fiber.yield(3)
|
||||
}
|
||||
|
||||
System.print(counter.call()) // 1
|
||||
System.print(counter.call()) // 2
|
||||
System.print(counter.call()) // 3</code></pre>
|
||||
|
||||
<h3>Passing Values In</h3>
|
||||
<pre><code>var adder = Fiber.new {
|
||||
var total = 0
|
||||
while (true) {
|
||||
var value = Fiber.yield(total)
|
||||
total = total + value
|
||||
}
|
||||
}
|
||||
|
||||
adder.call() // Start the fiber
|
||||
System.print(adder.call(5)) // 5
|
||||
System.print(adder.call(10)) // 15
|
||||
System.print(adder.call(3)) // 18</code></pre>
|
||||
|
||||
<h2>Fiber State</h2>
|
||||
<p>Check the state of a fiber:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
Fiber.yield()
|
||||
}
|
||||
|
||||
System.print(fiber.isDone) // false
|
||||
|
||||
fiber.call()
|
||||
System.print(fiber.isDone) // false (yielded)
|
||||
|
||||
fiber.call()
|
||||
System.print(fiber.isDone) // true (completed)</code></pre>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<p>Fibers can abort with an error:</p>
|
||||
<pre><code>Fiber.abort("Error message")</code></pre>
|
||||
|
||||
<p>Use <code>try()</code> to catch errors:</p>
|
||||
<pre><code>var fiber = Fiber.new {
|
||||
var x = 1 / 0 // Will cause infinity, not error
|
||||
[1, 2, 3][10] // This will cause an error
|
||||
}
|
||||
|
||||
var error = fiber.try()
|
||||
if (error != null) {
|
||||
System.print("Caught: %(error)")
|
||||
}</code></pre>
|
||||
|
||||
<h2>Current Fiber</h2>
|
||||
<p>Get the currently executing fiber:</p>
|
||||
<pre><code>var current = Fiber.current
|
||||
System.print(current) // Fiber instance</code></pre>
|
||||
|
||||
<h2>Generator Pattern</h2>
|
||||
<p>Fibers naturally implement generators:</p>
|
||||
<pre><code>var range = Fn.new { |start, end|
|
||||
return Fiber.new {
|
||||
var i = start
|
||||
while (i <= end) {
|
||||
Fiber.yield(i)
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var nums = range.call(1, 5)
|
||||
while (!nums.isDone) {
|
||||
var value = nums.call()
|
||||
if (value != null) System.print(value)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Coroutine Pattern</h2>
|
||||
<p>Two fibers can communicate back and forth:</p>
|
||||
<pre><code>var producer = Fiber.new {
|
||||
for (i in 1..5) {
|
||||
System.print("Producing %(i)")
|
||||
Fiber.yield(i)
|
||||
}
|
||||
}
|
||||
|
||||
var consumer = Fiber.new {
|
||||
while (!producer.isDone) {
|
||||
var value = producer.call()
|
||||
if (value != null) {
|
||||
System.print("Consuming %(value)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consumer.call()</code></pre>
|
||||
|
||||
<h2>Async Operations with Scheduler</h2>
|
||||
<p>Wren-CLI uses fibers for async I/O. The scheduler suspends fibers during I/O and resumes them when the operation completes:</p>
|
||||
<pre><code>import "timer" for Timer
|
||||
|
||||
System.print("Before sleep")
|
||||
Timer.sleep(1000) // Fiber suspends here
|
||||
System.print("After sleep")</code></pre>
|
||||
|
||||
<p>The scheduler pattern internally looks like:</p>
|
||||
<pre><code>import "scheduler" for Scheduler
|
||||
|
||||
Scheduler.await_ {
|
||||
Timer.sleep_(1000, Fiber.current)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Fiber Methods</h2>
|
||||
|
||||
<h3>Static Methods</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.new { block }</code></td>
|
||||
<td>Create a new fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.current</code></td>
|
||||
<td>Get the current fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.yield()</code></td>
|
||||
<td>Pause and return null</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.yield(value)</code></td>
|
||||
<td>Pause and return value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.abort(message)</code></td>
|
||||
<td>Abort with error</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>Fiber.suspend()</code></td>
|
||||
<td>Suspend the current fiber</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Instance Methods</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.call()</code></td>
|
||||
<td>Run fiber, wait for yield/complete</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.call(value)</code></td>
|
||||
<td>Run with value passed to yield</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.try()</code></td>
|
||||
<td>Run and catch errors</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.isDone</code></td>
|
||||
<td>True if completed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.error</code></td>
|
||||
<td>Error message if aborted</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transfer()</code></td>
|
||||
<td>Switch to this fiber</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transfer(value)</code></td>
|
||||
<td>Switch with value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>fiber.transferError(msg)</code></td>
|
||||
<td>Switch and raise error</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>Transfer vs Call</h2>
|
||||
<p><code>call()</code> maintains a call stack and returns when the fiber yields:</p>
|
||||
<pre><code>var a = Fiber.new {
|
||||
System.print("a: before yield")
|
||||
Fiber.yield()
|
||||
System.print("a: after yield")
|
||||
}
|
||||
|
||||
a.call()
|
||||
System.print("back in main")
|
||||
a.call()
|
||||
|
||||
// Output:
|
||||
// a: before yield
|
||||
// back in main
|
||||
// a: after yield</code></pre>
|
||||
|
||||
<p><code>transfer()</code> does not maintain a call stack:</p>
|
||||
<pre><code>var main = Fiber.current
|
||||
var a = null
|
||||
var b = null
|
||||
|
||||
a = Fiber.new {
|
||||
System.print("in a")
|
||||
b.transfer()
|
||||
System.print("back in a")
|
||||
main.transfer()
|
||||
}
|
||||
|
||||
b = Fiber.new {
|
||||
System.print("in b")
|
||||
a.transfer()
|
||||
System.print("back in b")
|
||||
}
|
||||
|
||||
a.transfer()
|
||||
System.print("done")
|
||||
|
||||
// Output:
|
||||
// in a
|
||||
// in b
|
||||
// back in a
|
||||
// done</code></pre>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,204 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Syntax Overview" %}
|
||||
{% set breadcrumb = [{"title": "Language Reference"}] %}
|
||||
{% set prev_page = {"url": "getting-started/repl.html", "title": "Using the REPL"} %}
|
||||
{% set next_page = {"url": "language/classes.html", "title": "Classes"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Syntax Overview</h1>
|
||||
|
||||
<p>Wren is a small, fast, class-based scripting language with a clean syntax inspired by languages like Dart, Lua, and Smalltalk. This section covers the core language features.</p>
|
||||
|
||||
<div class="toc">
|
||||
<h4>Language Topics</h4>
|
||||
<ul>
|
||||
<li><a href="classes.html">Classes</a> - Object-oriented programming</li>
|
||||
<li><a href="methods.html">Methods</a> - Method definition and operators</li>
|
||||
<li><a href="control-flow.html">Control Flow</a> - Conditionals and loops</li>
|
||||
<li><a href="fibers.html">Fibers</a> - Cooperative concurrency</li>
|
||||
<li><a href="modules.html">Modules</a> - Import system</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>Comments</h2>
|
||||
<p>Single-line comments start with <code>//</code>:</p>
|
||||
<pre><code>// This is a comment
|
||||
var x = 42 // Inline comment</code></pre>
|
||||
|
||||
<p>Block comments use <code>/* */</code> and can nest:</p>
|
||||
<pre><code>/* This is a
|
||||
multi-line comment */
|
||||
|
||||
/* Outer /* nested */ comment */</code></pre>
|
||||
|
||||
<h2>Variables</h2>
|
||||
<p>Declare variables with <code>var</code>:</p>
|
||||
<pre><code>var name = "Wren"
|
||||
var count = 42
|
||||
var active = true
|
||||
var nothing = null</code></pre>
|
||||
|
||||
<p>Variables must be initialized when declared. They are lexically scoped:</p>
|
||||
<pre><code>var outer = "outside"
|
||||
{
|
||||
var inner = "inside"
|
||||
System.print(outer) // Works
|
||||
}
|
||||
// inner is not accessible here</code></pre>
|
||||
|
||||
<h2>Data Types</h2>
|
||||
|
||||
<h3>Numbers</h3>
|
||||
<p>All numbers are 64-bit floating point:</p>
|
||||
<pre><code>var integer = 42
|
||||
var decimal = 3.14159
|
||||
var negative = -100
|
||||
var scientific = 1.5e10
|
||||
var hex = 0xFF
|
||||
var binary = 0b1010</code></pre>
|
||||
|
||||
<h3>Strings</h3>
|
||||
<p>Strings are immutable sequences of bytes:</p>
|
||||
<pre><code>var single = "Hello"
|
||||
var escape = "Line 1\nLine 2"
|
||||
var interpolation = "Value: %(1 + 2)"</code></pre>
|
||||
|
||||
<p>Raw strings avoid escape processing:</p>
|
||||
<pre><code>var raw = """
|
||||
This is a raw string.
|
||||
Backslashes \ are literal.
|
||||
"""</code></pre>
|
||||
|
||||
<h3>Booleans</h3>
|
||||
<pre><code>var yes = true
|
||||
var no = false</code></pre>
|
||||
|
||||
<p>Only <code>false</code> and <code>null</code> are falsy. All other values, including <code>0</code> and empty strings, are truthy.</p>
|
||||
|
||||
<h3>Null</h3>
|
||||
<pre><code>var nothing = null</code></pre>
|
||||
|
||||
<h3>Ranges</h3>
|
||||
<p>Ranges represent sequences of numbers:</p>
|
||||
<pre><code>var inclusive = 1..5 // 1, 2, 3, 4, 5
|
||||
var exclusive = 1...5 // 1, 2, 3, 4</code></pre>
|
||||
|
||||
<h3>Lists</h3>
|
||||
<p>Ordered, indexable collections:</p>
|
||||
<pre><code>var empty = []
|
||||
var numbers = [1, 2, 3, 4, 5]
|
||||
var mixed = [1, "two", true, null]
|
||||
|
||||
System.print(numbers[0]) // 1
|
||||
System.print(numbers[-1]) // 5 (last element)
|
||||
numbers[0] = 10
|
||||
numbers.add(6)</code></pre>
|
||||
|
||||
<h3>Maps</h3>
|
||||
<p>Key-value collections:</p>
|
||||
<pre><code>var empty = {}
|
||||
var person = {
|
||||
"name": "Alice",
|
||||
"age": 30
|
||||
}
|
||||
|
||||
System.print(person["name"]) // Alice
|
||||
person["city"] = "Amsterdam"</code></pre>
|
||||
|
||||
<h2>Operators</h2>
|
||||
|
||||
<h3>Arithmetic</h3>
|
||||
<pre><code>1 + 2 // 3
|
||||
5 - 3 // 2
|
||||
4 * 3 // 12
|
||||
10 / 4 // 2.5
|
||||
10 % 3 // 1 (modulo)</code></pre>
|
||||
|
||||
<h3>Comparison</h3>
|
||||
<pre><code>1 == 1 // true
|
||||
1 != 2 // true
|
||||
1 < 2 // true
|
||||
1 <= 1 // true
|
||||
2 > 1 // true
|
||||
2 >= 2 // true</code></pre>
|
||||
|
||||
<h3>Logical</h3>
|
||||
<pre><code>true && false // false
|
||||
true || false // true
|
||||
!true // false</code></pre>
|
||||
|
||||
<p>Logical operators short-circuit:</p>
|
||||
<pre><code>false && expensive() // expensive() not called
|
||||
true || expensive() // expensive() not called</code></pre>
|
||||
|
||||
<h3>Bitwise</h3>
|
||||
<pre><code>5 & 3 // 1 (AND)
|
||||
5 | 3 // 7 (OR)
|
||||
5 ^ 3 // 6 (XOR)
|
||||
~5 // -6 (NOT)
|
||||
8 << 2 // 32 (left shift)
|
||||
8 >> 2 // 2 (right shift)</code></pre>
|
||||
|
||||
<h3>Ternary</h3>
|
||||
<pre><code>var result = condition ? valueIfTrue : valueIfFalse</code></pre>
|
||||
|
||||
<h2>String Interpolation</h2>
|
||||
<p>Embed expressions in strings with <code>%()</code>:</p>
|
||||
<pre><code>var name = "World"
|
||||
System.print("Hello, %(name)!")
|
||||
|
||||
var a = 3
|
||||
var b = 4
|
||||
System.print("%(a) + %(b) = %(a + b)")</code></pre>
|
||||
|
||||
<p>Any expression can be interpolated:</p>
|
||||
<pre><code>System.print("Random: %(Random.new().float())")
|
||||
System.print("List: %([1, 2, 3].map { |x| x * 2 })")</code></pre>
|
||||
|
||||
<h2>Blocks</h2>
|
||||
<p>Blocks are anonymous functions. They use curly braces:</p>
|
||||
<pre><code>var block = { System.print("Hello") }
|
||||
block.call()
|
||||
|
||||
var add = { |a, b| a + b }
|
||||
System.print(add.call(1, 2)) // 3</code></pre>
|
||||
|
||||
<p>Blocks with a single expression return that value:</p>
|
||||
<pre><code>var square = { |x| x * x }
|
||||
System.print(square.call(5)) // 25</code></pre>
|
||||
|
||||
<h2>Functions</h2>
|
||||
<p>Use <code>Fn.new</code> for functions stored in variables:</p>
|
||||
<pre><code>var greet = Fn.new { |name|
|
||||
return "Hello, %(name)!"
|
||||
}
|
||||
System.print(greet.call("World"))</code></pre>
|
||||
|
||||
<p>Functions can have multiple statements:</p>
|
||||
<pre><code>var factorial = Fn.new { |n|
|
||||
if (n <= 1) return 1
|
||||
return n * factorial.call(n - 1)
|
||||
}</code></pre>
|
||||
|
||||
<h2>Is Operator</h2>
|
||||
<p>Check if an object is an instance of a class:</p>
|
||||
<pre><code>"hello" is String // true
|
||||
42 is Num // true
|
||||
[1, 2] is List // true</code></pre>
|
||||
|
||||
<h2>Reserved Words</h2>
|
||||
<p>The following are reserved and cannot be used as identifiers:</p>
|
||||
<pre><code>break class construct else false for foreign if import
|
||||
in is null return static super this true var while</code></pre>
|
||||
|
||||
<h2>Identifiers</h2>
|
||||
<p>Identifiers follow these conventions:</p>
|
||||
<ul>
|
||||
<li><code>camelCase</code> for variables and methods</li>
|
||||
<li><code>PascalCase</code> for class names</li>
|
||||
<li><code>_underscore</code> prefix for private fields</li>
|
||||
<li><code>UPPER_CASE</code> for constants (by convention)</li>
|
||||
</ul>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,276 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Methods" %}
|
||||
{% set breadcrumb = [{"url": "language/index.html", "title": "Language"}, {"title": "Methods"}] %}
|
||||
{% set prev_page = {"url": "language/classes.html", "title": "Classes"} %}
|
||||
{% set next_page = {"url": "language/control-flow.html", "title": "Control Flow"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Methods</h1>
|
||||
|
||||
<p>Methods are the primary way to define behavior in Wren. They can be instance methods, static methods, getters, setters, or operators.</p>
|
||||
|
||||
<h2>Instance Methods</h2>
|
||||
<p>Instance methods operate on a specific object:</p>
|
||||
<pre><code>class Greeter {
|
||||
construct new(name) {
|
||||
_name = name
|
||||
}
|
||||
|
||||
greet() {
|
||||
return "Hello, %(_name)!"
|
||||
}
|
||||
|
||||
greetWith(greeting) {
|
||||
return "%(greeting), %(_name)!"
|
||||
}
|
||||
}
|
||||
|
||||
var g = Greeter.new("World")
|
||||
System.print(g.greet()) // Hello, World!
|
||||
System.print(g.greetWith("Hi")) // Hi, World!</code></pre>
|
||||
|
||||
<h2>Static Methods</h2>
|
||||
<p>Static methods belong to the class rather than instances:</p>
|
||||
<pre><code>class StringUtils {
|
||||
static reverse(s) {
|
||||
var result = ""
|
||||
for (i in (s.count - 1)..0) {
|
||||
result = result + s[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static capitalize(s) {
|
||||
if (s.count == 0) return s
|
||||
return s[0].toString.toUpperCase + s[1..-1]
|
||||
}
|
||||
}
|
||||
|
||||
System.print(StringUtils.reverse("hello")) // olleh
|
||||
System.print(StringUtils.capitalize("hello")) // Hello</code></pre>
|
||||
|
||||
<h2>Getters</h2>
|
||||
<p>Getters are methods without parentheses that act like properties:</p>
|
||||
<pre><code>class Temperature {
|
||||
construct celsius(c) {
|
||||
_celsius = c
|
||||
}
|
||||
|
||||
celsius { _celsius }
|
||||
fahrenheit { _celsius * 9 / 5 + 32 }
|
||||
kelvin { _celsius + 273.15 }
|
||||
}
|
||||
|
||||
var temp = Temperature.celsius(100)
|
||||
System.print(temp.celsius) // 100
|
||||
System.print(temp.fahrenheit) // 212
|
||||
System.print(temp.kelvin) // 373.15</code></pre>
|
||||
|
||||
<h2>Setters</h2>
|
||||
<p>Setters use the <code>=</code> suffix:</p>
|
||||
<pre><code>class Box {
|
||||
construct new(value) {
|
||||
_value = value
|
||||
}
|
||||
|
||||
value { _value }
|
||||
|
||||
value=(v) {
|
||||
if (v < 0) Fiber.abort("Value must be non-negative")
|
||||
_value = v
|
||||
}
|
||||
}
|
||||
|
||||
var box = Box.new(10)
|
||||
box.value = 20
|
||||
System.print(box.value) // 20</code></pre>
|
||||
|
||||
<h2>Method Signatures</h2>
|
||||
<p>Wren distinguishes methods by their signature (name + arity):</p>
|
||||
<pre><code>class Example {
|
||||
method { "no args" }
|
||||
method() { "zero args with parens" }
|
||||
method(a) { "one arg" }
|
||||
method(a, b) { "two args" }
|
||||
}
|
||||
|
||||
var e = Example.new()
|
||||
System.print(e.method) // no args
|
||||
System.print(e.method()) // zero args with parens
|
||||
System.print(e.method(1)) // one arg
|
||||
System.print(e.method(1, 2)) // two args</code></pre>
|
||||
|
||||
<h2>Block Arguments</h2>
|
||||
<p>Methods can take a block as the last argument:</p>
|
||||
<pre><code>class List {
|
||||
static each(list, fn) {
|
||||
for (item in list) {
|
||||
fn.call(item)
|
||||
}
|
||||
}
|
||||
|
||||
static map(list, fn) {
|
||||
var result = []
|
||||
for (item in list) {
|
||||
result.add(fn.call(item))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
var numbers = [1, 2, 3, 4, 5]
|
||||
|
||||
List.each(numbers) { |n|
|
||||
System.print(n)
|
||||
}
|
||||
|
||||
var doubled = List.map(numbers) { |n| n * 2 }
|
||||
System.print(doubled) // [2, 4, 6, 8, 10]</code></pre>
|
||||
|
||||
<h2>Operator Overloading</h2>
|
||||
<p>Classes can define custom behavior for operators:</p>
|
||||
|
||||
<h3>Binary Operators</h3>
|
||||
<pre><code>class Vector {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
x { _x }
|
||||
y { _y }
|
||||
|
||||
+(other) { Vector.new(_x + other.x, _y + other.y) }
|
||||
-(other) { Vector.new(_x - other.x, _y - other.y) }
|
||||
*(scalar) { Vector.new(_x * scalar, _y * scalar) }
|
||||
/(scalar) { Vector.new(_x / scalar, _y / scalar) }
|
||||
|
||||
==(other) {
|
||||
return _x == other.x && _y == other.y
|
||||
}
|
||||
|
||||
toString { "(%(_x), %(_y))" }
|
||||
}
|
||||
|
||||
var a = Vector.new(1, 2)
|
||||
var b = Vector.new(3, 4)
|
||||
|
||||
System.print((a + b).toString) // (4, 6)
|
||||
System.print((a * 2).toString) // (2, 4)
|
||||
System.print(a == b) // false</code></pre>
|
||||
|
||||
<h3>Available Operators</h3>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Operator</th>
|
||||
<th>Signature</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
<tr><td><code>+</code></td><td><code>+(other)</code></td><td>Addition</td></tr>
|
||||
<tr><td><code>-</code></td><td><code>-(other)</code></td><td>Subtraction</td></tr>
|
||||
<tr><td><code>*</code></td><td><code>*(other)</code></td><td>Multiplication</td></tr>
|
||||
<tr><td><code>/</code></td><td><code>/(other)</code></td><td>Division</td></tr>
|
||||
<tr><td><code>%</code></td><td><code>%(other)</code></td><td>Modulo</td></tr>
|
||||
<tr><td><code><</code></td><td><code><(other)</code></td><td>Less than</td></tr>
|
||||
<tr><td><code>></code></td><td><code>>(other)</code></td><td>Greater than</td></tr>
|
||||
<tr><td><code><=</code></td><td><code><=(other)</code></td><td>Less or equal</td></tr>
|
||||
<tr><td><code>>=</code></td><td><code>>=(other)</code></td><td>Greater or equal</td></tr>
|
||||
<tr><td><code>==</code></td><td><code>==(other)</code></td><td>Equality</td></tr>
|
||||
<tr><td><code>!=</code></td><td><code>!=(other)</code></td><td>Inequality</td></tr>
|
||||
<tr><td><code>&</code></td><td><code>&(other)</code></td><td>Bitwise AND</td></tr>
|
||||
<tr><td><code>|</code></td><td><code>|(other)</code></td><td>Bitwise OR</td></tr>
|
||||
<tr><td><code>^</code></td><td><code>^(other)</code></td><td>Bitwise XOR</td></tr>
|
||||
<tr><td><code><<</code></td><td><code><<(other)</code></td><td>Left shift</td></tr>
|
||||
<tr><td><code>>></code></td><td><code>>>(other)</code></td><td>Right shift</td></tr>
|
||||
<tr><td><code>..</code></td><td><code>..(other)</code></td><td>Inclusive range</td></tr>
|
||||
<tr><td><code>...</code></td><td><code>...(other)</code></td><td>Exclusive range</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Unary Operators</h3>
|
||||
<pre><code>class Vector {
|
||||
construct new(x, y) {
|
||||
_x = x
|
||||
_y = y
|
||||
}
|
||||
|
||||
- { Vector.new(-_x, -_y) }
|
||||
! { Vector.new(_y, _x) } // Perpendicular
|
||||
|
||||
toString { "(%(_x), %(_y))" }
|
||||
}
|
||||
|
||||
var v = Vector.new(3, 4)
|
||||
System.print((-v).toString) // (-3, -4)</code></pre>
|
||||
|
||||
<h3>Subscript Operators</h3>
|
||||
<pre><code>class Grid {
|
||||
construct new(width, height) {
|
||||
_width = width
|
||||
_height = height
|
||||
_cells = List.filled(width * height, 0)
|
||||
}
|
||||
|
||||
[x, y] { _cells[y * _width + x] }
|
||||
[x, y]=(value) { _cells[y * _width + x] = value }
|
||||
}
|
||||
|
||||
var grid = Grid.new(10, 10)
|
||||
grid[5, 3] = 42
|
||||
System.print(grid[5, 3]) // 42</code></pre>
|
||||
|
||||
<h2>Calling Methods</h2>
|
||||
|
||||
<h3>With Parentheses</h3>
|
||||
<pre><code>object.method()
|
||||
object.method(arg1)
|
||||
object.method(arg1, arg2)</code></pre>
|
||||
|
||||
<h3>Without Parentheses (Getters)</h3>
|
||||
<pre><code>object.property
|
||||
object.count
|
||||
string.bytes</code></pre>
|
||||
|
||||
<h3>With Block Argument</h3>
|
||||
<pre><code>list.map { |x| x * 2 }
|
||||
list.where { |x| x > 5 }
|
||||
list.each { |x| System.print(x) }</code></pre>
|
||||
|
||||
<h3>Chaining</h3>
|
||||
<pre><code>var result = list
|
||||
.where { |x| x > 0 }
|
||||
.map { |x| x * 2 }
|
||||
.toList</code></pre>
|
||||
|
||||
<h2>Return Values</h2>
|
||||
<p>Methods return the last expression or use <code>return</code>:</p>
|
||||
<pre><code>class Example {
|
||||
implicit() {
|
||||
42 // Implicit return
|
||||
}
|
||||
|
||||
explicit() {
|
||||
return 42 // Explicit return
|
||||
}
|
||||
|
||||
early(x) {
|
||||
if (x < 0) return -1
|
||||
if (x > 0) return 1
|
||||
return 0
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<p>Methods without a return statement return <code>null</code>.</p>
|
||||
|
||||
<h2>Method References</h2>
|
||||
<p>You cannot directly reference a method as a value. Use a block wrapper:</p>
|
||||
<pre><code>class Printer {
|
||||
static print(value) {
|
||||
System.print(value)
|
||||
}
|
||||
}
|
||||
|
||||
var fn = Fn.new { |x| Printer.print(x) }
|
||||
fn.call("Hello") // Hello</code></pre>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,302 @@
|
||||
{# retoor <retoor@molodetz.nl> #}
|
||||
{% extends 'page.html' %}
|
||||
|
||||
{% set page_title = "Modules" %}
|
||||
{% set breadcrumb = [{"url": "language/index.html", "title": "Language"}, {"title": "Modules"}] %}
|
||||
{% set prev_page = {"url": "language/fibers.html", "title": "Fibers"} %}
|
||||
{% set next_page = {"url": "api/index.html", "title": "API Reference"} %}
|
||||
|
||||
{% block article %}
|
||||
<h1>Modules</h1>
|
||||
|
||||
<p>Modules organize code into separate, reusable files. Wren-CLI provides built-in modules and supports user-defined modules.</p>
|
||||
|
||||
<h2>Importing</h2>
|
||||
<p>Use <code>import</code> to load a module and access its classes:</p>
|
||||
<pre><code>import "json" for Json
|
||||
|
||||
var data = Json.parse('{"name": "Wren"}')
|
||||
System.print(data["name"])</code></pre>
|
||||
|
||||
<h3>Multiple Imports</h3>
|
||||
<p>Import multiple classes from one module:</p>
|
||||
<pre><code>import "io" for File, Directory, Stdin</code></pre>
|
||||
|
||||
<h3>Import All</h3>
|
||||
<p>Some modules export many classes. Import what you need:</p>
|
||||
<pre><code>import "os" for Process, Platform</code></pre>
|
||||
|
||||
<h2>Built-in Modules</h2>
|
||||
<p>Wren-CLI provides these modules:</p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Module</th>
|
||||
<th>Description</th>
|
||||
<th>Main Classes</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/http.html">http</a></td>
|
||||
<td>HTTP client</td>
|
||||
<td>Http, HttpResponse, Url</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/websocket.html">websocket</a></td>
|
||||
<td>WebSocket client/server</td>
|
||||
<td>WebSocket, WebSocketServer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/tls.html">tls</a></td>
|
||||
<td>TLS/SSL sockets</td>
|
||||
<td>TlsSocket</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/net.html">net</a></td>
|
||||
<td>TCP networking</td>
|
||||
<td>Socket, Server</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/dns.html">dns</a></td>
|
||||
<td>DNS resolution</td>
|
||||
<td>Dns</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/json.html">json</a></td>
|
||||
<td>JSON parsing</td>
|
||||
<td>Json</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/base64.html">base64</a></td>
|
||||
<td>Base64 encoding</td>
|
||||
<td>Base64</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/regex.html">regex</a></td>
|
||||
<td>Regular expressions</td>
|
||||
<td>Regex, Match</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/jinja.html">jinja</a></td>
|
||||
<td>Template engine</td>
|
||||
<td>Environment, Template</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/crypto.html">crypto</a></td>
|
||||
<td>Cryptography</td>
|
||||
<td>Hash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/os.html">os</a></td>
|
||||
<td>OS information</td>
|
||||
<td>Process, Platform</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/env.html">env</a></td>
|
||||
<td>Environment variables</td>
|
||||
<td>Env</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/signal.html">signal</a></td>
|
||||
<td>Unix signals</td>
|
||||
<td>Signal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/subprocess.html">subprocess</a></td>
|
||||
<td>Run processes</td>
|
||||
<td>Subprocess</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/sqlite.html">sqlite</a></td>
|
||||
<td>SQLite database</td>
|
||||
<td>Sqlite</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/datetime.html">datetime</a></td>
|
||||
<td>Date/time handling</td>
|
||||
<td>DateTime, Duration</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/timer.html">timer</a></td>
|
||||
<td>Timers and delays</td>
|
||||
<td>Timer</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/io.html">io</a></td>
|
||||
<td>File I/O</td>
|
||||
<td>File, Directory, Stdin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/pathlib.html">pathlib</a></td>
|
||||
<td>Filesystem paths</td>
|
||||
<td>Path, PurePath</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/scheduler.html">scheduler</a></td>
|
||||
<td>Async scheduling</td>
|
||||
<td>Scheduler</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="../api/math.html">math</a></td>
|
||||
<td>Math functions</td>
|
||||
<td>Math</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h2>User Modules</h2>
|
||||
<p>Create your own modules by putting code in <code>.wren</code> files.</p>
|
||||
|
||||
<h3>Creating a Module</h3>
|
||||
<p>Create <code>utils.wren</code>:</p>
|
||||
<pre><code>class StringUtils {
|
||||
static reverse(s) {
|
||||
var result = ""
|
||||
for (i in (s.count - 1)..0) {
|
||||
result = result + s[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
static capitalize(s) {
|
||||
if (s.count == 0) return s
|
||||
return s[0].toString.toUpperCase + s[1..-1]
|
||||
}
|
||||
}
|
||||
|
||||
class MathUtils {
|
||||
static clamp(value, min, max) {
|
||||
if (value < min) return min
|
||||
if (value > max) return max
|
||||
return value
|
||||
}
|
||||
}</code></pre>
|
||||
|
||||
<h3>Using a Module</h3>
|
||||
<p>Import with a relative path:</p>
|
||||
<pre><code>import "./utils" for StringUtils, MathUtils
|
||||
|
||||
System.print(StringUtils.reverse("hello")) // olleh
|
||||
System.print(StringUtils.capitalize("world")) // World
|
||||
System.print(MathUtils.clamp(15, 0, 10)) // 10</code></pre>
|
||||
|
||||
<h2>Module Resolution</h2>
|
||||
|
||||
<h3>Built-in Modules</h3>
|
||||
<p>Names without paths are built-in modules:</p>
|
||||
<pre><code>import "json" for Json // Built-in
|
||||
import "http" for Http // Built-in</code></pre>
|
||||
|
||||
<h3>Relative Paths</h3>
|
||||
<p>Paths starting with <code>./</code> or <code>../</code> are relative to the current file:</p>
|
||||
<pre><code>import "./helpers" for Helper // Same directory
|
||||
import "../utils/string" for StringUtil // Parent directory</code></pre>
|
||||
|
||||
<h3>Absolute Paths</h3>
|
||||
<p>Paths starting with <code>/</code> are absolute:</p>
|
||||
<pre><code>import "/home/user/libs/mylib" for MyClass</code></pre>
|
||||
|
||||
<h2>Module Structure</h2>
|
||||
<p>A typical project structure:</p>
|
||||
<pre><code>project/
|
||||
├── main.wren
|
||||
├── lib/
|
||||
│ ├── http_client.wren
|
||||
│ ├── database.wren
|
||||
│ └── templates.wren
|
||||
└── tests/
|
||||
└── test_http.wren</code></pre>
|
||||
|
||||
<p>In <code>main.wren</code>:</p>
|
||||
<pre><code>import "./lib/http_client" for HttpClient
|
||||
import "./lib/database" for Database
|
||||
import "./lib/templates" for TemplateEngine
|
||||
|
||||
var client = HttpClient.new()
|
||||
var db = Database.new("data.db")
|
||||
var tmpl = TemplateEngine.new()</code></pre>
|
||||
|
||||
<h2>Module Top-Level Code</h2>
|
||||
<p>Code outside classes runs when the module is first imported:</p>
|
||||
<pre><code>// config.wren
|
||||
System.print("Config module loading...")
|
||||
|
||||
class Config {
|
||||
static port { 8080 }
|
||||
static host { "localhost" }
|
||||
}
|
||||
|
||||
System.print("Config ready")</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
System.print("Before import")
|
||||
import "./config" for Config
|
||||
System.print("After import")
|
||||
System.print("Port: %(Config.port)")
|
||||
|
||||
// Output:
|
||||
// Before import
|
||||
// Config module loading...
|
||||
// Config ready
|
||||
// After import
|
||||
// Port: 8080</code></pre>
|
||||
|
||||
<h2>Module Variables</h2>
|
||||
<p>Top-level variables are module-private by default:</p>
|
||||
<pre><code>// counter.wren
|
||||
var _count = 0 // Private to module
|
||||
|
||||
class Counter {
|
||||
static increment() { _count = _count + 1 }
|
||||
static count { _count }
|
||||
}</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
import "./counter" for Counter
|
||||
|
||||
Counter.increment()
|
||||
Counter.increment()
|
||||
System.print(Counter.count) // 2
|
||||
// _count is not accessible here</code></pre>
|
||||
|
||||
<h2>Circular Imports</h2>
|
||||
<p>Wren handles circular imports by completing partial modules:</p>
|
||||
<pre><code>// a.wren
|
||||
import "./b" for B
|
||||
|
||||
class A {
|
||||
static greet() { "Hello from A" }
|
||||
static callB() { B.greet() }
|
||||
}
|
||||
|
||||
// b.wren
|
||||
import "./a" for A
|
||||
|
||||
class B {
|
||||
static greet() { "Hello from B" }
|
||||
static callA() { A.greet() }
|
||||
}</code></pre>
|
||||
|
||||
<p>This works because class definitions are hoisted.</p>
|
||||
|
||||
<div class="admonition warning">
|
||||
<div class="admonition-title">Warning</div>
|
||||
<p>Avoid calling imported classes in top-level code during circular imports, as they may not be fully initialized.</p>
|
||||
</div>
|
||||
|
||||
<h2>Re-exporting</h2>
|
||||
<p>Create a facade module that re-exports from multiple modules:</p>
|
||||
<pre><code>// lib/index.wren
|
||||
import "./http_client" for HttpClient
|
||||
import "./database" for Database
|
||||
import "./templates" for TemplateEngine
|
||||
|
||||
class Lib {
|
||||
static httpClient { HttpClient }
|
||||
static database { Database }
|
||||
static templates { TemplateEngine }
|
||||
}</code></pre>
|
||||
|
||||
<pre><code>// main.wren
|
||||
import "./lib/index" for Lib
|
||||
|
||||
var client = Lib.httpClient.new()</code></pre>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user