add existing documentation

This commit is contained in:
2024-11-02 15:11:00 +00:00
parent d5c41cbbaa
commit ac92c5317e
9 changed files with 1066 additions and 0 deletions

241
doc/example.asm Executable file
View File

@@ -0,0 +1,241 @@
@msgh (net.doorstuck.test.Person) [-init(name:age:)]
ldr x0, [bp, #-1]
str x0, [self, #0]
ldr x0, [bp, #-2]
str x0, [self, #1]
ret
@end
@msgh (net.doorstuck.test.Person) [-name]
ldr x0, [self, #0]
ret
@end
@msgh (net.doorstuck.test.Person) [-age]
ldr x0, [self, #1]
ret
@end
@msgh (net.doorstuck.test.Person) [-ageInMonths]
ldr x0, [self, #1]
ldr x1, #12
mul x0, x0, x1
ret
@end
@msgh (net.doorstuck.test.Person) [-setName:]
ldr x0, [bp, #-1]
str x0, [self, #0]
ret
@end
@msgh (net.doorstuck.test.Person) [-setAge:]
ldr x0, [bp, #-1]
str x0, [self, #1]
ret
@end
@msgh (net.doorstuck.test.Person) [-setAge:in:]
ldr x0, [bp, #-1]
ldr x1, "years"
cmp x0, x1
b.eq $L0001
ldr x1, "months"
cmp x0, x1
b.eq $L0002
ldr x1, "days"
cmp x0, x1
b.eq $L0003
; else
ldr x2, #0
str x2, [self, #1]
br $L0004
; if units == "years"
L0001: str x0, [self, #1]
br $L0004
; if units == "months"
L0002: ldr x2, #12
div x0, x0, x2
str x0, [self, #1]
br $L0004
; if units == "days"
L0003: ldr x2, #365
div x0, x0, x2
str x0, [self, #1]
; end
L0004:
ret
@end
@lambda (Lx0001) [_:]
ldr x1, @ident[cout]
ldr x2, [bp, #-1]
ldr x3, "Count is "
add x3, x3, x2
push x3
msg x1, @selector[-put:]
ret
@end
@lambda (Lx0002) []
ldr x1, @ident[cout]
ldr x2, "True!"
push x2
msg x1, @selector[-put:]
@end
@lambda (Lx0003) []
ldr x1, @ident[cout]
ldr x2, "False!"
push x2
msg x1, @selector[-put:]
@end
@init
rsv #3 ; 3 local variables
/* stack layout is now this:
bp[1] = p1
bp[2] = i
bp[3] = j
*/
; p1 = Person new(name:"John Doe", age:34)
ldr x0, #34
push x0
ldr x0, "John Doe"
push x0
ob.c x1, x1
msg x1, @selector[-init(name:age:)] ; x0 = new Person object
str x1, [bp, #1] ; p1 = new Person object
; p1 setAge:100 in:"months"
; p1 is already loaded in x1, no need to load it again
ldr x2, @selector[-setAge:in:]
ldr x3, "months"
push x3
ldr x3, #100
push x3
msg x1, @selector[-setAge:in:] ; x0 = null
ldr x3, #0
str x3, [bp, #2] ; i = 0
; (while i < 10) begin
L0001: ldr x2, @ident[cout] ; x5 = cout
ldr x4, "Count is "
add x4, x4, x3 ; append i (in x4) to "Count is " (in x7)
push x4
msg x2, @selector[-put:]
add x3, x3, #2
cmp x3, #100
b.lt $L0001
; (while i < 0) end
; for i in 0 to:100 step:2
ldr x4, #2
push x4
ldr x4, #100
push x4
ldr x4, #0
msg x5, @selector[-to:step:]
mov x5, x0 ; x5 = (0 to:100 step:2) -> iterator
; (for i in 0 to:100 step:2) begin
L0002: it.v x5 ; is iterator still valid?
b.z $L0003
it.g x4, x5 ; get current iterator value
ldr x6, "Count is "
add x6, x6, x4 ; append i (in x4) to "Count is " (in x6)
ldr x7, @ident[cout]
push x6
msg x7, @selector[-put:]
it.n x5 ; Advance iterator
br $L0002
; (for i in 0 to:100 step:2) end
L0003: ob.e x5 ; Clean up iterator
lam.c x5, @lambda[Lx0001] ; x5 = [ _:i | cout put:'Count: {i}' ],
push x5
ldr x5, #2
push x5
ldr x5, #100
push x5
ldr x5, #0
msg x5, @selector[-to:step:do:]
ldr x5, #32
str x5, [bp, #3] ; (x5) j = 32
ldr x6, [bp, #1] ; (x6) i
c.lt x5, x6, x5
lam.c x6, @lambda[Lx0003]
push x6
lam.c x6, @lambda[Lx0002]
push x6
msg x5, @selector[-if:else:]
ldr x5, [bp, #2] ; (x5) j
ldr x6, [bp, #1] ; (x6) i
cmp x6, x5
b.ge $L0004
ldr x5, "True!"
push x5
ldr x5, @ident[cout]
msg x5, @selector[-put:]
L0004: ret
@end

65
doc/sample/CaesarCipher.im Executable file
View File

@@ -0,0 +1,65 @@
package net.doorstuck.test
cout put:'Encode a string with a Caesar cipher.'
message = ''
while true do
cout print:'Message to encode: '
cout flush
cin get:message
if message != '' then
break
end
end
shiftWidth = 0
while true do
cout print:'Shift size: '
cout flush
shiftStr = ''
cin get:shiftStr
if shiftStr == '' then
continue
end
try
shiftWidth = Int parse:shiftStr
catch (#err:number_format, err)
continue
end
break
end
encodedMessage = ''
for c in message do
i = c toOrdinal
sub = 0
if i >= 65 && i <= 90 then
sub = 65
elif i >= 97 && i <= 122 then
sub = 97
else
continue
end
i -= sub
i += shiftWidth
if i >= 26 then
i -= 26
end
c2 = i toChar
encodedMessage += c2
end
cout put:encodedMessage

20
doc/sample/FizzBuzz.im Executable file
View File

@@ -0,0 +1,20 @@
for i in 0 to:100 do
p = false
if i % 3 == 0 then
cout print:'fizz'
p = true
end
if i % 5 == 0 then
cout print:'buzz'
p = true
end
if !p then
cout print:'{i}'
end
cout cr
end

522
doc/sample/Person.im Executable file
View File

@@ -0,0 +1,522 @@
/* 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
/**
every message handler can have one or both of the following:
- a message name (in this case, 'init')
- a set of one or more parameters (wrapped in parentheses if the message
has a name)
each parameter has an internal name and an (optional) label, specified
as a pair of identifiers separated by a colon (:). the label comes
first, and is the name used by whoever is sending the message the
internal name comes second, and is used by the message handler itself as
a variable name.
you can omit the label for a parameter by specifying an underscore (_)
as the label, but this is not recommended. it looks weird, it obscures
the meaning of the message, and is really only supported for
compatibility with lambdas.
**/
- init(name:name age:age)
self.name = name
self.age = age
end
- test(param:data _:extra)
cout put:'Received {data}, {extra}'
end
- name
^self.name
end
- age
^self.age
end
- ageInMonths
^self.age * 12
end
- setName:name
self.name = name
end
- setAge:age
self.age = age
end
- setAge:age inUnit:units
match units in
#years => self.age = age,
#months => self.age = age / 12,
#days => self.age = age / 365,
_ => self.age = 0,
end
end
- getAgeInUnit:units
^match units in
#years => age,
#months => age / 12,
#days => age / 365,
_ => 0,
end
end
$ exampleProperty
- get
^42
end
end
end
p1 = Person new(name:'John Doe' age:34)
p1 setAge:100 inUnit:#months
/**
when sending a message with unlabeled parameters, the preceding colon (:) is
still required. this is part of the reason why unlabeled parameters are
weird and not recommended.
**/
p1 test(param:'Hello' :'World')
/******************************************************************************/
i = 0
while i < 100 do
cout put:'Count is {i}'
i += 2
end
for i in 0 to:100 step:2 do
cout put:'Count is {i}'
end
/**
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 then
cout put:'True!'
end
/******************************************************************************/
package = {}
/**
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.
**/
package[0] = 16
/* All of these accesses are equivalent */
package['x'] = 32
package.x = 32
index = 'x'
package[index] = 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
/* 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 package do
cout put:'{key} -> {val}'
end
/**
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 -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) in
cout put:'Cannot parse integer string ({err msg})'
catch (_, err) in
cout put:'Unknown error occurred ({err msg})'
end
/* equivalent 'pure' syntax */
/**
this example also demonstrates how line continuations are not always
necessary when breaking a statement up over multiple lines.
the compiler uses a few conditions to determine if it should continue
parsing a particular statement past a line break:
1) if the next token immediately after a line break is a label.
2) if the token immediately before a line break if a binary operator.
3) if the line break occurs within a set of sub-expression delimiters
(parentheses), lambda delimiters (brackets), package delimiters
(braces), or string delimiters (single or double quotes).
note that if a line break is encountered in a string constant,
the line break character is included in the string. to prevent
this behaviour, you can precede the line break with a line continuation
character.
because of the implicit line continuations provided by these conditions,
the following statement requires no explicit line continuations to
be correctly parsed.
**/
[ v = Int parse:'342' ]
on:#err:number_format do:[ :err |
cout put:'Cannot parse integer string ({err msg})'
];
onError:[ :err |
cout put:'Unknown error occurred ({err msg})'
];
call
/**
this example doesn't meet any of the conditions required for implicit
line continuations to be inserted. because of this, it will be treated
as four separate statements.
there are a few ways this could be fixed:
1) surrount the right-hand side of the statement with parentheses.
2) put a line continuation character at the end of all but the last line.
**/
v = 5
squared
squared
squared
/**
below are some examples of throwing errors. when an error is thrown,
the runtimes unwinds the stack looking for the nearest error handler.
if no error handlers are found, the exception information is written
to cerr and exection stops.
the exact behaviour of a throw statement depends on what is thrown:
1) a string is thrown: a generic Error object is created.
Sending -msg to the Error returns the thrown string.
Sending -data to the Error returns null.
2) an object that implements the Error protocol is thrown: the
thrown object becomes the error object that is passed to the
error handler (or that is written to cerr when execution stops).
3) an object that doesn't implement the Error protocol is thrown:
a generic Error object is created.
sending -msg to the Error returns null.
sending -data to the Error returns the thrown object.
**/
/* example 1) */
throw 'an error occurred')
/* example 2) */
throw { i => 32, s => 'error' }
/******************************************************************************/
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, end, 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.
- unary operators
- boolean NOT (!)
- bitwise NOT (~)
- unary messages, complex messages
- binary operators
- multiplication (*)
- division (/)
- modulo (%)
- addition (+)
- subtraction (-)
- bitwise shift (<<, >>)
- (in)equality (==, !=)
- bitwise AND (&)
- bitwise XOR (^)
- bitwise OR (|)
- boolean AND (&&)
- boolean 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
setAge:2 squared squared + 4 squared multiply(by:2 add:4 + 1 * 3)
in:'mon' + 'ths'
(p1
setAge:(((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);
setAge:144 inUnit:TimeUnit.MONTHS;
ageInMonths
-- age now has the value 144
-- the same behaviour can be achieved by using do..end
age = do
x = Person new(name:'John Doe' age:34)
x setAge:144 inUnit:TimeUnit.MONTHS
x ageInMonths
end
/**
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);
setAge:100 inUnit:TimeUnit.MONTHS;
yourself
/* p1 now contains the Person object */
/* again, with do..end */
p1 = do
x = Person new(name:'John Doe' age:34)
x setAge:100 inUnit:TimeUnit.MONTHS
x
end
/******************************************************************************/
/**
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..end, 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
end
/* after this statement is executed, `val` will be equal to 6. */

6
doc/sample/String.im Executable file
View File

@@ -0,0 +1,6 @@
package net.doorstuck.test
s1 = 'hello world'
s2 = s1 map:[ :c | ^c uppercase ]
s3 = s1 map:[ :c | ^((c ordinal) + 1) toChar ]

29
doc/sample/Sum.im Executable file
View File

@@ -0,0 +1,29 @@
package net.doorstuck.test
cout put:'Finds the sum of a set of numbers.'
sum = 0
while true do
cout print:'Number (blank to finish): '
cout flush
input = ''
v = 0
cin get:input
if input == '' then
break
end
try
v = Int parse:input
catch (#err:number_format, err)
cout put:'{input} is not a valid number.'
continue
end
sum += v
end
cout put:'Sum: {sum}'

44
doc/sample/Vehicle.im Executable file
View File

@@ -0,0 +1,44 @@
package net.doorstuck.vehicles
protocol Vehicle
- startup
- shutdown
- nrWheels
- nrDoors
- turn(direction:dir)
- accelerate(direction:dir)
- velocity
end
class Car <Vehicle>
- startup
self.engineRunning = true
end
- shutdown
self.engineRunning = false
end
- nrWheels
^4
end
- nrDoors
^4
end
- turn(direction:dir)
self.currentDirection = dir
end
- accelerate(direction:dir)
match dir
#forward => self.currentVelocity += 1,
#backward => self.currentVelocity -= 1,
end
end
- velicity
^self.currentVelocity
end
end

91
doc/standard-library.txt Executable file
View File

@@ -0,0 +1,91 @@
vim: tabstop=2 shiftwidth=2 expandtab
*** PACKAGES ******************************************************************
## std.lang #####
This package contains all of the "built-in" classes, such as String,
Package, etc.
This package is always included by default for all Ivy source files, so
there is no need to include a `use std.lang` statement.
## std.io #####
This package includes classes for performing IO operations, including
reading/writing to the terminals, files, etc.
This package also includes the cin, cout, and cerr objects that point
to standard in, standard out, and standard error respectively.
*** CLASSES *******************************************************************
//// std.lang ////
## Object #####
-is:
-implements:
-respondsTo:
-doesNotUnderstand(selector:args:)
-sendMsg(selector:args:)
-toString
## Int #####
+parse:
-to:
-to:step:
-to:step:do:
## Enum #####
**
## String #####
-size
-uppercase
-lowercase
-append:
-prepend:
-at:insert:
-contains:
-iterator
## Bool #####
-if:
-if:else:
## Package #####
-size
-exists:
-at:put:
-at:
-map:
-select:
-select:collect:
-iterator
## Iterator #####
-value
-moveNext
-select:
-select:collect:
-iterator
## Lambda #####
-argCount
-call
-call:
-call(_:_:)
-call(_:_:_:)
-call(_:_:_:_:)
-call(args:)
-on:do:
-if:
-unless:
## Selector #####
+parse:
## Error #####
-msg
-data
-withMsg:andData:

48
doc/vm-instructions.txt Executable file
View File

@@ -0,0 +1,48 @@
ldr <dest:REG> <src:SP-offset>
ldr <dest:REG> <src:BP-offset>
ldr <dest:REG> <src:SELF-offset>
ldr <dest:REG> <src:SELF-offset>
ldr <dest:REG> <src:IMM>
ldr <dest:REG> <src:POOL_INDEX>
str <src:REG> <dest:SP-offset>
str <src:REG> <dest:BP-offset>
str <src:REG> <dest:SELF-offset>
push <value:REG>
pop <dest:REG>
add <dest:REG> <value1:REG> <value2:REG>
sub <dest:REG> <value1:REG> <value2:REG>
mul <dest:REG> <value1:REG> <value2:REG>
div <dest:REG> <value1:REG> <value2:REG>
cmp <value1:REG> <value2:REG>
c.eq <dest:REG> <value1:REG> <value2:REG>
c.ne <dest:REG> <value1:REG> <value2:REG>
c.lt <dest:REG> <value1:REG> <value2:REG>
c.le <dest:REG> <value1:REG> <value2:REG>
c.gt <dest:REG> <value1:REG> <value2:REG>
c.ge <dest:REG> <value1:REG> <value2:REG>
br <dest:IMM>
b.z <dest:IMM>
b.nz <dest:IMM>
b.eq <dest:IMM>
b.ne <dest:IMM>
b.lt <dest:IMM>
b.le <dest:IMM>
b.gt <dest:IMM>
b.ge <dest:IMM>
ob.c <dest:REG> <class:POOL_INDEX>
ob.e <ref:REG>
lam.c <dest:REG> <body:POOL_INDEX>
it.g <dest:REG> <iterator:REG>
it.n <iterator:REG>
it.v <iterator:REG>
ret