peon/docs/manual.md

188 lines
5.0 KiB
Markdown
Raw Normal View History

2022-05-23 23:08:00 +02:00
# Peon - Manual
Peon is a functional, statically typed, garbage-collected, C-like programming language with
a focus on speed and correctness, but whose main feature is the ability to natively
perform highly efficient parallel I/O operations by implementing the [structured concurrency](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/)
paradigm.
__Note__: Peon is currently a WIP (Work In Progress), and much of the content of this manual is purely theoretical as
of now. If you want to help make this into a reality, feel free to contribute!
## Table of contents
- [Manual](#peon---manual)
- [Design Goals](#design-goals)
- [Examples](#peon-by-example)
- [Grammar](grammar.md)
- [Bytecode](bytecode.md)
## Design Goals
While peon is inspired from Bob Nystrom's [book](https://craftinginterpreters.com), where he describes a simple toy language
named Lox, the aspiration for it is to become a programming language that could actually be used in the real world. For that
to happen, we need:
- Exceptions (`try/except/finally`)
- An import system (with namespaces, like Python)
- Multithreading support (with a global VM lock when GC'ing)
- Built-in collections (list, tuple, set, etc.)
- Coroutines (w/ structured concurrency)
- Generators
- Generics
- C/Nim FFI
- A package manager
Peon ~~steals~~ borrows many ideas from Python and Nim (the latter being the language peon itself is written in).
## Peon by Example
Here follow a few examples of peon code to make it clear what the end product should look like
### Variable declarations
```
var x = 5; # Inferred type is int64
var y = 3'u16; # Type is specified as uint16
x = 6; # Works: type matches
x = 3.0; # Cannot assign float64 to x
var x = 3.14; # Cannot re-declare x
```
__Note__: Peon supports [name stropping](https://en.wikipedia.org/wiki/Stropping_(syntax)), meaning
that almost any ASCII sequence of characters can be used as an identifier, including language
keywords, but stropped names need to be enclosed by matching pairs of backticks (`\``)
### Functions
```
fn fib(n: int): int {
if (n < 3) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
fib(30);
```
### Type declarations
```
type Foo = object { # Can also be "ref object" for reference types (managed automatically)
fieldOne*: int # Asterisk means the field is public outside the current module
fieldTwo*: int
}
```
### Operator overloading
```
operator `+`(a, b: Foo) {
return Foo(fieldOne: a.fieldOne + b.fieldOne, fieldTwo: a.fieldTwo + b.fieldTwo);
}
Foo(fieldOne: 1, fieldTwo: 3) + Foo(fieldOne: 2, fieldTwo: 3); # Foo(fieldOne: 3, fieldTwo: 6)
```
__Note__: Custom operators (e.g. `foo`) can also be defined! The backticks around the plus sign serve to mark it
as an identifier instead of a symbol (which is a requirement for function names, since operators are basically
functions). In fact, even the built-in peon operators are implemented partially in peon (well, their forward
declarations are) and they are then specialized in the compiler to emit a single bytecode instruction.
### Function calls
```
foo(1, 2 + 3, 3.14, bar(baz));
```
__Note__: Operators can be called as functions too. Just wrap their name in backticks, like so:
```
`+`(1, 2)
```
__Note__: Code the likes of `a.b()` is desugared to `b(a)` if there exists a function `b` whose
signature is compatible with the value of of `a` (assuming `a` doesn't have a `b` field, in
which case the attribute resolution takes precedence)
### Generic declarations
```
fn genericSum[T](a, b: T): T { # Note: "a, b: T" means that both a and b are of type T
return a + b;
}
# This allows for a single implementation to be
# re-used multiple times without any code duplication!
genericSum(1, 2);
genericSum(3.14, 0.1);
genericSum(1'u8, 250'u8);
```
#### Multiple generics
```
fn genericSth[T, K](a: T, b: K) { # Note: no return type == void function!
# code...
}
genericSth(1, 3.0);
```
__Note__: The `*` modifier to make a name visible outside the current module must be put
__before__ generics declarations, so only `fn foo*[T](a: T) {}` is the correct syntax
### Forward declarations
```
fn someF: int; # Semicolon, no body!
someF(); # This works!
fn someF: int {
return 42;
}
```
### Generators
```
generator count(n: int): int {
while (n > 0) {
yield n;
n -= 1;
}
}
foreach (n: count(10)) {
print(n);
}
```
### Coroutines
```
import concur;
import http;
coroutine req(url: string): string {
return (await http.AsyncClient().get(url)).content;
}
coroutine main(urls: list[string]) {
pool = concur.pool(); # Creates a task pool: like a nursery in njsmith's article
for (var i = 0; i < urls.len(); i += 1) {
pool.spawn(req, urls[i]);
}
# The pool has internal machinery that makes the parent
# task wait until all child exit! When this function
# returns, ALL child tasks will have exited somehow
}
concur.run(main, newList[string]("https://google.com", "https://debian.org"))
```