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
|
|
|
|
|
2022-08-17 20:52:23 +02:00
|
|
|
Peon ~~steals~~ borrows many ideas from Python, Nim (the the language peon itself is written in), C and many others.
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
## Peon by Example
|
|
|
|
|
2022-08-17 20:52:23 +02:00
|
|
|
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.
|
2022-11-27 13:39:41 +01:00
|
|
|
For somewhat more updated code snippets, check the [tests](../tests/) directory.
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
### Variable declarations
|
|
|
|
|
|
|
|
```
|
2022-08-17 21:10:55 +02:00
|
|
|
var x = 5; # Inferred type is int64
|
|
|
|
var y = 3'u16; # Type is specified as uint16
|
|
|
|
x = 6; # Works: type matches
|
|
|
|
x = 3.0; # Error: Cannot assign float64 to x
|
2022-11-27 13:39:41 +01:00
|
|
|
var x = 3.14; # Error: cannot re-declare x
|
2022-08-17 21:10:55 +02:00
|
|
|
const z = 6.28; # Constant declaration
|
|
|
|
let a = "hi!"; # Cannot be reassigned/mutated
|
2022-11-27 13:39:41 +01:00
|
|
|
var b: int32 = 5; # Explicit type declaration (TODO)
|
2022-05-23 23:08:00 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
__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 {
|
2022-08-01 11:03:49 +02:00
|
|
|
if n < 3 {
|
2022-05-23 23:08:00 +02:00
|
|
|
return n;
|
|
|
|
}
|
|
|
|
return fib(n - 1) + fib(n - 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
fib(30);
|
|
|
|
```
|
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
### Type declarations (TODO)
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
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
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
### Enumeration types (TODO)
|
2022-11-05 14:03:49 +01:00
|
|
|
|
|
|
|
```
|
|
|
|
type SomeEnum = enum { # Can be mapped to an integer
|
|
|
|
KindOne,
|
|
|
|
KindTwo
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-05-23 23:08:00 +02:00
|
|
|
### Operator overloading
|
|
|
|
|
|
|
|
```
|
2022-08-17 20:52:23 +02:00
|
|
|
operator `+`(a, b: Foo): Foo {
|
2022-05-23 23:08:00 +02:00
|
|
|
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
|
2022-11-05 14:03:49 +01:00
|
|
|
functions in peon). In fact, even the built-in peon operators are implemented partially in peon (actually, just
|
|
|
|
their stubs are) and they are then specialized in the compiler to emit a single bytecode instruction to get rid
|
|
|
|
of unnecessary function call overhead.
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
### Function calls
|
|
|
|
|
|
|
|
```
|
|
|
|
foo(1, 2 + 3, 3.14, bar(baz));
|
|
|
|
```
|
|
|
|
|
2022-11-05 14:03:49 +01:00
|
|
|
__Note__: Operators can be called as functions; If their name is a symbol, just wrap it in backticks like so:
|
2022-05-23 23:08:00 +02:00
|
|
|
```
|
2022-11-05 14:03:49 +01:00
|
|
|
`+`(1, 2) # Identical to 1 + 2
|
2022-05-23 23:08:00 +02:00
|
|
|
```
|
|
|
|
|
2022-11-05 14:03:49 +01:00
|
|
|
__Note__: Code the likes of `a.b()` is (actually, will be) 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 field named `b`,
|
|
|
|
in which case the attribute resolution takes precedence)
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
|
2022-08-17 21:10:55 +02:00
|
|
|
### Generics
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
```
|
2022-08-01 11:03:49 +02:00
|
|
|
fn genericSum[T: Number](a, b: T): T { # Note: "a, b: T" means that both a and b are of type T
|
2022-05-23 23:08:00 +02:00
|
|
|
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);
|
|
|
|
```
|
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
__Note__: The generic `Number` type is currently not defined. You can use type unions instead
|
|
|
|
|
2022-08-17 21:10:55 +02:00
|
|
|
#### More generics!
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
```
|
2022-11-05 14:03:49 +01:00
|
|
|
fn genericSth[T: someTyp, K: someTyp2](a: T, b: K) { # Note: no return type == void function!
|
2022-05-23 23:08:00 +02:00
|
|
|
# code...
|
|
|
|
}
|
|
|
|
|
|
|
|
genericSth(1, 3.0);
|
|
|
|
```
|
|
|
|
|
2022-08-17 21:10:55 +02:00
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
#### Even more generics? (TODO)
|
2022-08-17 21:10:55 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
type Box*[T: SomeNumber] = object {
|
|
|
|
num: T;
|
|
|
|
}
|
|
|
|
|
|
|
|
var boxFloat = Box[float](1.0);
|
|
|
|
var boxInt = Box[int](1);
|
|
|
|
```
|
|
|
|
|
2022-05-23 23:08:00 +02:00
|
|
|
__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!
|
|
|
|
|
2022-08-17 20:52:23 +02:00
|
|
|
print(someF()); # This works!
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
fn someF: int {
|
|
|
|
return 42;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
__Note__: A function that is forward-declared __must__ be implemented in the same
|
|
|
|
module as the forward declaration
|
|
|
|
|
|
|
|
### Generators (TODO)
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
generator count(n: int): int {
|
2022-08-01 11:03:49 +02:00
|
|
|
while n > 0 {
|
2022-05-23 23:08:00 +02:00
|
|
|
yield n;
|
|
|
|
n -= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (n: count(10)) {
|
|
|
|
print(n);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
|
2022-11-27 13:39:41 +01:00
|
|
|
### Coroutines (TODO)
|
2022-05-23 23:08:00 +02:00
|
|
|
|
|
|
|
```
|
|
|
|
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
|
2022-08-01 11:03:49 +02:00
|
|
|
foreach url in urls {
|
|
|
|
pool.spawn(req, urls);
|
2022-05-23 23:08:00 +02:00
|
|
|
}
|
|
|
|
# The pool has internal machinery that makes the parent
|
|
|
|
# task wait until all child exit! When this function
|
2022-08-01 11:03:49 +02:00
|
|
|
# returns, ALL child tasks will have exited somehow.
|
|
|
|
# Exceptions and return values propagate neatly, too.
|
2022-05-23 23:08:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
concur.run(main, newList[string]("https://google.com", "https://debian.org"))
|
|
|
|
```
|