191 lines
5.3 KiB
Markdown
191 lines
5.3 KiB
Markdown
# 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, Nim (the the language peon itself is written in), C and many others.
|
|
|
|
## Peon by Example
|
|
|
|
Here follow a few examples of peon code to make it clear what the end product should look like. Note that
|
|
not all examples represent working functionality and some of these examples might not be up to date either.
|
|
For somewhat updated tests, check the [tests](../tests/) directory.
|
|
|
|
### 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): 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 `a` (assuming `a` doesn't have a `b` field, in
|
|
which case the attribute resolution takes precedence)
|
|
|
|
|
|
### Generic declarations
|
|
|
|
```
|
|
fn genericSum[T: Number](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: someInterface, K: someInterface2](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!
|
|
|
|
print(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
|
|
foreach url in urls {
|
|
pool.spawn(req, urls);
|
|
}
|
|
# 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.
|
|
# Exceptions and return values propagate neatly, too.
|
|
}
|
|
|
|
|
|
concur.run(main, newList[string]("https://google.com", "https://debian.org"))
|
|
``` |