doc: update docs and sample files
This commit is contained in:
562
doc/sample/Person.2.im
Normal file
562
doc/sample/Person.2.im
Normal file
@@ -0,0 +1,562 @@
|
||||
/* example.im - An example Ivy Implementation source file */
|
||||
|
||||
package net.doorstuck.test
|
||||
|
||||
use std.io
|
||||
|
||||
-- Anything after a double hypen is ignored by the compiler
|
||||
/*
|
||||
Multi
|
||||
Line
|
||||
Comment
|
||||
*/
|
||||
|
||||
/**
|
||||
classes also double up as protocols.
|
||||
|
||||
any class can "pretend" to be another class by responding to the same
|
||||
messages that the base class understands.
|
||||
|
||||
for example, by implementing all the messages that the Package class
|
||||
understands, your class is said to implement the Package protocol,
|
||||
allowing package-specific syntax (i.e. indexing and dot syntax)
|
||||
to be used with your class.
|
||||
|
||||
there are two standard messages that complement this behaviour:
|
||||
-is: takes a Class parameter, and returns true if the recipient
|
||||
is an instance of the given Class.
|
||||
-implements: takes a Class parameter, and returns true if the recipient
|
||||
understands all of the messages that the parameter class
|
||||
understands.
|
||||
**/
|
||||
class Person [
|
||||
/**
|
||||
A class is made up of the following components:
|
||||
- a set of zero of more member variables
|
||||
these can only be accessed by methods (not by functions) using the
|
||||
self variable and the -> operator. member variables do not need
|
||||
to be declared before they are used. assigning to a member variable
|
||||
for the first time automatically "creates" it, and trying to access
|
||||
a member variable that hasn't been assigned to will return null.
|
||||
- a set of zero or more methods.
|
||||
methods are procedure/sub-routines (a list of expressions evaluated
|
||||
in order) executed in response to a particular message received by an
|
||||
object. every method declares what sort of message it responds to.
|
||||
- a set of zero or more functions.
|
||||
functions are exactly like methods, except that they are executed in
|
||||
response to messages sent to the class itself, rather than to an
|
||||
instance of the class.
|
||||
- a set of zero or more properties.
|
||||
a property is similar to a member variable, with the following key
|
||||
differences:
|
||||
1) they are accessed using the . (dot) operator rather than the ->
|
||||
(arrow) operator.
|
||||
2) they can be accessed from outside the class.
|
||||
3) they can (but don't have to) invoke blocks of code in response to
|
||||
attempts to read from or assign to them.
|
||||
4) they can be made read-write, read-only, or write-only.
|
||||
**/
|
||||
|
||||
/**
|
||||
every method and function begins with a kebab, or message signature.
|
||||
So called due to the preceding hyphen (for methods) or plus
|
||||
(for functions) and the kebab-case convention used for message and
|
||||
parameter names.
|
||||
|
||||
methods and functions are called in response to messages, and the kebab
|
||||
defines exactly what message the method/function should be called for.
|
||||
|
||||
a message can have:
|
||||
1) a name and zero or more parameters; or
|
||||
2) no name and one or more parameters.
|
||||
|
||||
if a message has a name AND parameters, the parameters are listed
|
||||
within parentheses and separated by commas.
|
||||
|
||||
if a message has parameters but no name, the parameters are listed
|
||||
with no surrounding symbols and are separated only by whitespace.
|
||||
|
||||
every parameter has a label and an identifier. the label determines what
|
||||
name needs to be specified before the parameter value when sending a
|
||||
message, and forms part of the API. in contrast, the identifier is
|
||||
simply the variable name that is used to access the parameter value
|
||||
within the method/function, and is only used internally.
|
||||
|
||||
a parameter MUST have an identifier, but a label can be omitted by
|
||||
specifying an _ (underscore) as the label.
|
||||
|
||||
when a method/function body has more than one expression, the
|
||||
expressions must be surrounded by [] (square bracket block delimiters).
|
||||
**/
|
||||
-init(name:name, age:age) [
|
||||
self->name = name.
|
||||
self->age = age
|
||||
]
|
||||
|
||||
/* if a method/function only has a single expression body, a | (pipe)
|
||||
can be used between the kebab and body, and the body will automatically
|
||||
end after one expression. */
|
||||
-test(param:data, _:extra) | cout put:'Received {data}, {extra}'.
|
||||
|
||||
-name | ^self->name.
|
||||
|
||||
-age | ^self->age.
|
||||
|
||||
-age-in-months | ^self->age * 12.
|
||||
|
||||
-set-name:name | self->name = name.
|
||||
|
||||
-set-age:age | self->age = age.
|
||||
|
||||
-set-age:age in-unit:units [
|
||||
match units [
|
||||
$years => self->age = age,
|
||||
$months => self->age = age / 12,
|
||||
$days => self->age = age / 365,
|
||||
_ => self->age = 0
|
||||
]
|
||||
]
|
||||
|
||||
-get-age-in-units:units [
|
||||
^match units [
|
||||
$years => self->age,
|
||||
$months => self->age / 12,
|
||||
$days => self->age / 365,
|
||||
_ => 0
|
||||
]
|
||||
]
|
||||
|
||||
+create-with-name:name [
|
||||
^Person new(name:name, age:32)
|
||||
]
|
||||
|
||||
/* Properties are defined by a bare identifier (with no prefixes, so not
|
||||
a kebab) followed by a pipe.
|
||||
They accomplish two things:
|
||||
1) they make it easy to synthesize the -get: and -put:at: messages
|
||||
that the package dot syntax requires.
|
||||
2) they allow you to implement getters and setters that are used
|
||||
when someone attempts to access your object's data.
|
||||
|
||||
The contents of a property looks a bit like a package, but it has a few
|
||||
special rules:
|
||||
1) every value must have an associated key; and
|
||||
2) the only keys that can be used are `get` and `set` (this is the only
|
||||
instance where reserved keywords can be used as keys in a package).
|
||||
|
||||
In this case, the getter has been set to a variable. To facilitate this
|
||||
functionality, the compiler converts this to a lambda of the form
|
||||
[ ^self->val ]
|
||||
that will be evaluated when the getter is invoked
|
||||
|
||||
Similarly, the compiler synthesizes a lambda for the setter as well.
|
||||
Here, the expression
|
||||
self->val = value
|
||||
is converted to the lambda
|
||||
[ :x | self->val = x ]
|
||||
*/
|
||||
example-property-a | get => self->val, set => self->val = value.
|
||||
|
||||
/* Without the lambda synthesis, the property would look like this:
|
||||
Note that this is the only time it is legal to access private fields
|
||||
via `self` from a lambda. */
|
||||
example-property-b |
|
||||
get => [ ^self->val ],
|
||||
set => [ :x | self->val = x ].
|
||||
|
||||
/* The `get` element of a property doesn't have to be a lambda, it can
|
||||
be any value. When the property is accessed, the value provided will
|
||||
be returned.
|
||||
|
||||
If either the `set` or `get` elements are not provided, the property
|
||||
becomes read-only or write-only respectively */
|
||||
example-property-c | get => 42.
|
||||
|
||||
/* A property can also be configured to act just like a regular variable,
|
||||
by setting the getter and setters to default implementations.
|
||||
|
||||
The default getter will return the value of a private member variable
|
||||
named by prepending "p-" to the property name (p-example-property-d in
|
||||
this case).
|
||||
|
||||
Similarly, the default setter will set the value of a private member
|
||||
variable named by prepending "p-" to the property name
|
||||
(p-example-property-d in this case) to the value provided to the setter. */
|
||||
example-property-d (get, set)
|
||||
|
||||
/* Just like in the earlier examples, either `get` or `set` can be omitted
|
||||
to create a write-only or read-only property respectively */
|
||||
example-property-e (get)
|
||||
]
|
||||
|
||||
p1 = Person new(name:'John Doe', age:34).
|
||||
p1 set-age:100 in-unit:$months.
|
||||
|
||||
p1 test(param:'Hello', 'World').
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
i = 0.
|
||||
while i < 100 [
|
||||
cout put:'Count is {i}'.
|
||||
i += 2
|
||||
]
|
||||
|
||||
for x in 0 to:100 step:2 [
|
||||
cout put:'Count is {x}'
|
||||
]
|
||||
|
||||
/**
|
||||
a lambda's parameters are *always* unlabeled, and the underscore (_) before
|
||||
the label colon (:) is unnecessary.
|
||||
**/
|
||||
0 to:100 step:2 do:[ :i | cout put:'Count: {i}' ].
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
lambdas can capture state from the context in which they are created.
|
||||
|
||||
a lambda can reference any local variable that is accessible at the point
|
||||
it is created. if a variable from such a context is referenced, its value
|
||||
is captured at the point the lambda is constructed. the value of the
|
||||
variable is copied into the lambda.
|
||||
|
||||
this means that, if a lambda references a variable in the outer context,
|
||||
and the value of that variable changes between the lambda being created
|
||||
and the lambda being executed, that change will not be reflected within
|
||||
the lambda.
|
||||
|
||||
because the value of captured variables is copied into the lambda, it is
|
||||
safe for a method to return a lambda that references variables in its
|
||||
local context.
|
||||
**/
|
||||
q = 32.
|
||||
l = [ cout put:'Value of q is {q}' ]. /* value of `q` is captured here */
|
||||
q = 64.
|
||||
_ = l call. /* prints 'Value of q is 32' */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
j = 32.
|
||||
|
||||
/**
|
||||
expressions are terminated with a newline.
|
||||
an expression can be written across multiple lines by using
|
||||
the _ (underscore) line continuation character.
|
||||
**/
|
||||
|
||||
/**
|
||||
keyword messages have a lower precedence than all non-assignment
|
||||
binary operators, so (i < j) will be evaluated first, and
|
||||
if:else: will be sent to the result.
|
||||
**/
|
||||
|
||||
i < j
|
||||
if:[ cout put:'True!' ]
|
||||
else:[ cout put:'False!' ].
|
||||
|
||||
if i < j [
|
||||
cout put:'True!'
|
||||
]
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
pkg = {}.
|
||||
|
||||
/**
|
||||
Packages can be indexed via integer or string. If a package is index with a
|
||||
variable, the value of the variable is used as the index.
|
||||
**/
|
||||
|
||||
pkg[0] = 16.
|
||||
|
||||
/* All of these accesses are equivalent */
|
||||
pkg['x'] = 32.
|
||||
pkg.x = 32.
|
||||
pkg at:'x' put:32.
|
||||
|
||||
index = 'x'.
|
||||
pkg[index] = 32.
|
||||
pkg at:index put:32.
|
||||
|
||||
/**
|
||||
this syntax, and the pkg.* instructions that it translates to, is
|
||||
implemented behind-the-scenes by sending messages to the package.
|
||||
|
||||
if your custom object implements this protocol, package syntax can be used
|
||||
on it too.
|
||||
**/
|
||||
|
||||
/**
|
||||
these are the same packages used to store Classes and global variables.
|
||||
for example, the std package is a regular package object, and you can use
|
||||
package syntax on it or even send it messages.
|
||||
**/
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* integer constants can be written in all sorts of interesting formats */
|
||||
i = 100.
|
||||
i = 0x100.
|
||||
i = 0200.
|
||||
i = 0b1010101.
|
||||
i = 1_000_000_000.
|
||||
i = 100.25.
|
||||
|
||||
/* tuples can be used to create a set of values */
|
||||
|
||||
tuple = (32, 'a string').
|
||||
|
||||
/**
|
||||
they are similar to packages, except they only support consecutive integer
|
||||
indices, and you cannot specify the indices when creating a tuple.
|
||||
|
||||
the main purpose of tuples is to allow unwrapping of iterator variables in
|
||||
for loops.
|
||||
**/
|
||||
|
||||
for (key, val) in pkg [
|
||||
cout put:'{key} -> {val}'
|
||||
]
|
||||
|
||||
/**
|
||||
any object can be the target of a for-loop, as long as it meets one of the
|
||||
following criteria:
|
||||
a) is an Iterator object; or
|
||||
b) implements the Iterator protocol; or
|
||||
c) understands the -get-iterator message, and returns an object that itself
|
||||
conforms to a) or b)
|
||||
**/
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
underscore (_) is used as a placeholder during assignment.
|
||||
it can only appear on the left of an assignment, and anything
|
||||
assigned to it is discarded.
|
||||
*/
|
||||
_ = 3 * 2.
|
||||
|
||||
/* it is mostly used with pattern-matching and destructuring. */
|
||||
|
||||
a = (32, 64).
|
||||
(_, v) = a.
|
||||
|
||||
/* v is now equal to 64 */
|
||||
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
try [
|
||||
v = Int parse:'342'
|
||||
] catch ($err:number-format, err) [
|
||||
cout put:'Cannot parse integer string ({err})'
|
||||
] catch (_, err) [
|
||||
cout put:'Unknown error occurred ({err})'
|
||||
]
|
||||
|
||||
/* equivalent 'pure' syntax */
|
||||
|
||||
[ v = Int parse:'342' ]
|
||||
on:$err:number-format do:[ :err :data |
|
||||
cout put:'Cannot parse integer string ({err})'
|
||||
];
|
||||
on-error:[ :err :data |
|
||||
cout put:'Error {err} occurred ({data})'
|
||||
];
|
||||
call.
|
||||
|
||||
v = 5
|
||||
squared
|
||||
squared
|
||||
squared.
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
x = 32.
|
||||
/* a whole bunch of ways of doing conditionals using lambdas and messages */
|
||||
[ cout put:'Hello, world!' ] if:x > 10.
|
||||
[ cout put:'Hello, world!' ] unless:x <= 10.
|
||||
|
||||
/* and the equivalent 'fancy' syntax */
|
||||
|
||||
cout put:'Hello, world!' if x > 10.
|
||||
-- cout put:'Hello, world!' unless x <= 10.
|
||||
|
||||
/**
|
||||
NOTE that keywords (if, try, and so on) can still be used as message
|
||||
parameter names. however, they cannot be used as message names.
|
||||
**/
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
Order of execution (precedence) for expression elements (listed from highest
|
||||
to lowest precedence). elements that appear on the same list item have equal
|
||||
precedence. in these cases, their associativity determines the order of
|
||||
execution.
|
||||
|
||||
- binary operators
|
||||
- package-access (.)
|
||||
- self-access (->)
|
||||
- unary operators
|
||||
- boolean NOT (not)
|
||||
- bitwise NOT (~)
|
||||
- binary operators
|
||||
- Type check operator (is, is not)
|
||||
- Protocol check operator (understands)
|
||||
- unary messages, complex messages
|
||||
- binary operators
|
||||
- multiplication (*)
|
||||
- division (/)
|
||||
- modulo (%)
|
||||
- addition (+)
|
||||
- subtraction (-)
|
||||
- bitwise shift (<<, >>)
|
||||
- comparison (<, >, <=, >=)
|
||||
- (in)equality (==, !=)
|
||||
- bitwise AND (&)
|
||||
- bitwise XOR (^)
|
||||
- bitwise OR (|)
|
||||
- boolean AND (and)
|
||||
- boolean OR (or)
|
||||
- cascade (;)
|
||||
- inline if-else
|
||||
- keyword messages
|
||||
- binary operators
|
||||
- assignment by product, quotient, and remainder (*=, /=, %=)
|
||||
- assignment by sum and difference (+=, -=)
|
||||
- assignment by bitwise shift (<<=, >>=)
|
||||
- assignment by bitwise AND, XOR, and OR (&=, ^=, |=)
|
||||
- assignment (=)
|
||||
**/
|
||||
|
||||
p1
|
||||
set-age:2 squared squared + 4 squared multiply(by:2, add:4 + 1 * 3)
|
||||
in:'mon' + 'ths'.
|
||||
(p1
|
||||
set-age:(((2 squared) squared) + ((4 squared) multiply(by:2, add:(4 + (1 * 3)))))
|
||||
in:('mon' + 'ths')).
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/* how about package comprehension? */
|
||||
|
||||
/**
|
||||
these two lines both construct a package containing all even numbers between
|
||||
0 and 100 incremented by 1
|
||||
|
||||
the first one uses fancy list/package comprehension syntax.
|
||||
the second one uses pure message/lambda syntax.
|
||||
**/
|
||||
data = { x + 1 for x in 0 to:100 if (x % 2) == 0 }.
|
||||
data = (0 to: 100) select:[ :x | ^(x % 2) == 0 ] collect:[ :x | ^x + 1 ].
|
||||
|
||||
/**
|
||||
for a package comprehension that follows this template:
|
||||
EXPRESSION for VARIABLE in COLLECTION if CONDITION
|
||||
the equivalent -select:collect: message would look something like this:
|
||||
COLLECTION select:[ VARIABLE | CONDITION ] collect:[ VARIABLE | EXPRESSION ]
|
||||
|
||||
-select:collect: is used to create a new package by filtering and
|
||||
transforming an existing package.
|
||||
|
||||
To apply a filter without a subsequent transform, you can use -select:
|
||||
To apply a transformation with a filter beforehand, you can use -map:
|
||||
**/
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
packages also support a -map: message for constructing a new package by
|
||||
manipulating an existing one.
|
||||
**/
|
||||
|
||||
/**
|
||||
this example creates a package of all ints between 1 and 5 using a regular
|
||||
collection literal, and then uses -map: to create a second package by
|
||||
doubling each of the numbers in the fist package.
|
||||
**/
|
||||
pkg1 = { 1, 2, 3, 4, 5 }.
|
||||
pkg2 = { x * 2 for x in pkg1 }.
|
||||
pkg2 = pkg1 map:[ :x | ^x * 2 ].
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
semicolon (;) is the cascade operator. it returns the recipient of the
|
||||
previously sent message. it can be used to send multiple messages to a
|
||||
single recipient.
|
||||
**/
|
||||
|
||||
age = Person new(name:'John Doe', age:34);
|
||||
set-age:144 in-unit:$months;
|
||||
ageInMonths.
|
||||
|
||||
-- age now has the value 144
|
||||
-- the same behaviour can be achieved by using do[...]
|
||||
|
||||
age = do [
|
||||
x = Person new(name:'John Doe', age:34).
|
||||
x set-age:144 in-unit:$months.
|
||||
x ageInMonths
|
||||
].
|
||||
|
||||
/**
|
||||
this allows messages to be easily chained, without relying on the message
|
||||
handler returning `self`.
|
||||
**/
|
||||
|
||||
/**
|
||||
NOTE that cascade is a binary operator, and cannot be used without a
|
||||
right-hand operand. otherwise, simply adding a semicolon to the end of an
|
||||
expression would change the behaviour (and result) of the expression.
|
||||
|
||||
however, because cascade is a binary operator, it also supports implicit
|
||||
line continuations.
|
||||
|
||||
for situations where you want to return the recipient after a chain of
|
||||
cascaded messages, you can use the -yourself message, which is understood by
|
||||
all objects by default and always returns the object itself->
|
||||
**/
|
||||
|
||||
p1 = Person new(name:'John Doe', age:34);
|
||||
set-age:100 in-unit:$months;
|
||||
yourself.
|
||||
|
||||
/* p1 now contains the Person object */
|
||||
/* again, with do[...] */
|
||||
|
||||
p1 = do [
|
||||
x = Person new(name:'John Doe', age:34).
|
||||
x set-age:100 in-unit:$months.
|
||||
x
|
||||
].
|
||||
|
||||
|
||||
v = "Hello".
|
||||
if v is String [
|
||||
cout put:"v is a string!"
|
||||
]
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
/**
|
||||
Blocks are a bit like lambdas, except they are part of the context of the
|
||||
code that uses them. They are essentially a way of evaluating multiple
|
||||
statements in a place where a single statement is usually expected.
|
||||
**/
|
||||
|
||||
/* Blocks are delimited by do[...], and the return operator can be used within
|
||||
them. When it is, the value that is 'returned' will become the value that
|
||||
the block as a whole evaluates to. also, the result of the last expression
|
||||
evaluated in a block will be taken as its overall value. */
|
||||
val = do [
|
||||
x = 3.
|
||||
y = 2.
|
||||
x * y
|
||||
].
|
||||
|
||||
/* after this statement is executed, `val` will be equal to 6. */
|
||||
|
||||
Reference in New Issue
Block a user