Files
boring-lang/README.md

266 lines
8.2 KiB
Markdown
Raw Normal View History

2021-06-16 12:02:02 -06:00
# Boring Lang
2019-11-05 21:35:44 -07:00
2019-11-04 22:12:38 -07:00
The Boring Programming Language (Boring-Lang) is an attempt to create an easy, productive, general purpose programming language that makes as few interesting choices as possible while still being in line with modern concepts in programming languages.
2021-09-12 15:00:03 -06:00
The language (goals):
2019-12-30 20:58:27 -07:00
* is compiled with a run-time (llvm for convenience + c/rust compatibility)
2021-06-16 12:02:02 -06:00
* has managed memory (via strong/weak pointers and automatic reference counting)
2021-09-12 15:00:03 -06:00
* uses async-await for all IO, with a built-in multi-core scheduler (tokio-based)
2019-11-13 00:05:04 -07:00
* supports algebraic data types (Result type for errors, Maybe/Optional type for nullables)
2021-06-03 09:16:48 -06:00
* supports parametric polymorphism (generics) with higher kinded types
2019-11-04 22:12:38 -07:00
* uses struct+traits, rather than classes or stuct+interfaces
2021-06-03 09:16:48 -06:00
* has a rich standard library (similar scale to python or go)
2019-11-13 00:05:04 -07:00
* is immutable by default
2021-06-16 12:02:02 -06:00
* is sandboxed by default
It's a middle-ground of Rust, Golang, Swift, Typescript, and Python. The goal is not to break any new ground in PL theory, or even create a language anyone likes, but rather to create a language with as few deal-breakers as possible for maximum day-to-day industrial programming ergonomics.
This language is under active development, progress will be marked here as the language is developed.
- [x] Functions
- [x] Int Literals
- [x] Float Literals
- [x] Block expression
- [x] Return keyword
- [x] Normal assignment
- [x] Structs
- [x] Define
- [x] Literal
- [x] Getter
- [x] Setter
- [x] Type Aliases
2021-06-26 17:47:52 -06:00
- [x] Methods
2021-06-16 12:02:02 -06:00
- [x] Declaration
2021-06-26 17:47:52 -06:00
- [x] Use
2021-06-16 12:02:02 -06:00
- [ ] Traits
2021-09-25 11:45:31 -06:00
- [x] Basic
- [ ] Default Functions
2021-06-16 12:02:02 -06:00
- [ ] Generics
- [ ] Basic
- [ ] Higher kinded types
2021-06-26 17:50:10 -06:00
- [ ] Variadic generic types
2021-06-16 12:02:02 -06:00
- [ ] Control Flow
2021-09-14 21:15:39 -06:00
- [x] If
2021-06-16 12:02:02 -06:00
- [ ] While
- [ ] For
- [ ] Enums
- [ ] Lambdas
- [ ] Async-Await
- [ ] Imports
- [ ] Visibility
- [ ] Const / Mut
2021-07-01 12:29:00 -06:00
- [ ] Macros
2019-11-04 22:12:38 -07:00
2021-06-16 12:10:21 -06:00
This project is actively looking for contributors, so if you're interested in programming language design or have experience working with LLVM, don't hesitate to contact.
2019-11-04 22:12:38 -07:00
## Http Server Example
2021-06-03 09:16:48 -06:00
2021-06-16 12:02:02 -06:00
```rust
2021-06-26 18:04:30 -06:00
import net.http as http;
import logging as logging;
import json as json;
2019-11-04 22:12:38 -07:00
type ExampleResponse struct {
2021-06-26 18:04:30 -06:00
id: I32,
name: Str,
email: Str,
2019-11-04 22:12:38 -07:00
}
2021-06-12 12:30:00 -06:00
async fn handle(req: http.Request, resp: mut http.Response): {
2021-06-03 09:16:48 -06:00
let response_data = ExampleResponse{
id: 4,
2021-06-16 12:12:07 -06:00
name: "Andrew",
2021-06-26 18:06:22 -06:00
email: "andrew@boringlang.com",
2021-06-03 09:20:28 -06:00
};
await resp.set_status(200);
await resp.write(json.encode[ExampleResponse](response_data));
2019-11-04 22:12:38 -07:00
}
2021-06-12 12:30:00 -06:00
async fn main(args: Vec[Str], os: OS): I32 {
2021-06-26 18:04:30 -06:00
let log = logging.new_logger(os.console.stdout());
2021-06-03 09:20:28 -06:00
let router = http.Router("").add_route("/myroute", handle);
2021-06-12 12:30:00 -06:00
let http_server = http.Server(os.net(), "localhost", 8080, router);
2021-06-03 09:20:28 -06:00
let err = await http_server.serve_forever();
await log.info("error serving: ", err);
return 1;
2019-11-04 22:12:38 -07:00
}
```
2019-11-13 00:05:04 -07:00
## Mutability
2019-12-30 20:58:27 -07:00
All variables are immutable by default, to make them mutable use the `mut` keyword. Once a variable becomes immutable it cannot become mutable again. If you need it to become mutable, either implement the `clone` trait, or simply create a new one with the same data.
2019-11-13 00:05:04 -07:00
2021-06-16 12:02:02 -06:00
```rust
2021-06-03 09:16:48 -06:00
let mut foo = Dict[String, Int32].new(); // constructor returns a mutable reference
foo.insert("eggs", 12);
foo.insert("bananas", 2);
foo.insert("grapes", 2);
2019-11-13 00:05:04 -07:00
2021-06-03 09:16:48 -06:00
let bar = foo; // bar is not mutable
2019-11-13 00:05:04 -07:00
2021-06-03 09:16:48 -06:00
bar.insert("apples", 4); // fails with compiler error
2019-11-13 00:05:04 -07:00
2021-06-03 09:16:48 -06:00
let mut baz = bar.clone();
baz.insert("apples", 4); // fine
2019-11-13 00:05:04 -07:00
```
Methods on a struct must specify if they mutate the struct.
2021-06-16 12:02:02 -06:00
```rust
2021-06-03 09:16:48 -06:00
impl Dict[Key: Hashable, Value] {
fn insert(self: mut Self, key: Key, value: Value) {
2019-11-13 00:05:04 -07:00
// mutate self here
}
2021-06-03 09:16:48 -06:00
fn get(self: Self, key: Key) Optional[Value] {
2019-11-13 00:05:04 -07:00
// no need for `mut`
}
}
```
## Context
Context is an exceptionally useful feature in golang, but a common complaint is that:
1. Because it works as an arbitrary map, it can be used to pass arguments into a function that aren't explicitly stated.
2. It is used for both passing context parameters and cancellation, two fundamentally different tasks that have no reason to be in the same object.
The boring standard library solves this by using parametric polymorphism. Context is by default an empty object passed through the chain, and each function/set of context parameters is an additional trait condition applied at compile time.
2021-06-16 12:02:02 -06:00
```rust
type HTTPRequest[Ctx: Context] = async fn(Ctx, http.Request, mut http.Response);
2019-11-14 23:22:35 -07:00
2021-06-16 12:02:02 -06:00
pub fn tracing_middleware[Ctx: Tracing](handler: HTTPRequest[Ctx]): HTTPRequest {
2020-04-14 00:02:02 -06:00
return async fn(ctx: Ctx, req: http.Request, resp: mut http.Response) {
2019-12-30 20:58:27 -07:00
with tracing.Span(ctx, "request_duration") {
2021-06-03 09:20:28 -06:00
await handler(ctx, req, resp);
2019-11-13 00:05:04 -07:00
}
2021-06-03 09:20:28 -06:00
};
2019-11-13 00:05:04 -07:00
}
2021-06-16 12:02:02 -06:00
pub fn auth_middleware[Ctx: Auth](handler: HTTPRequest[Ctx], scope: Str): HTTPRequest {
2020-04-14 00:02:02 -06:00
return async fn(ctx: Ctx, req: http.Request, resp: mut http.Response) {
2021-09-15 08:43:38 -06:00
if (ctx.has_scope(scope)) {
2021-06-03 09:20:28 -06:00
await handler(ctx, req, resp);
2019-11-13 00:05:04 -07:00
}
2021-06-03 09:20:28 -06:00
await resp.set_status(403);
await resp.write("missing scope");
};
2019-11-13 00:05:04 -07:00
}
2021-06-16 12:02:02 -06:00
pub fn cancel_middleware[Ctx: Cancel](handler: HTTPRequest[Ctx]): HTTPRequest {
2020-04-14 00:02:02 -06:00
return async fn(ctx: Ctx, req: http.Request, resp: mut http.Response) {
2021-09-15 08:43:38 -06:00
if (!(await ctx.is_cancelled())) { // check cancel token
2021-06-03 09:20:28 -06:00
await handler(ctx, req, resp);
2019-11-13 00:05:04 -07:00
}
2021-06-03 09:20:28 -06:00
await resp.set_status(400);
await resp.write("cancelled");
};
2019-11-13 00:05:04 -07:00
}
```
for the above examples, you would pass a context type that implements all three traits.
2021-06-16 12:27:26 -06:00
## Sandboxing
2021-09-15 08:43:38 -06:00
Unlike many other programming languages, boringlang's `main` function takes in two arguments: a vector of command line arguments, and a reference to the OS which is the program's only link to the outside world. To open a file in boringlang, you cannot just call `open` anywhere, you *must* call `os.fs().open("path")`. All `os.whatever()` methods return an interface for interacting with that part of the OS, such as `fs`, `net`, `datetime`, and `syscall`. Because this is the only way to interact with the world outside of the program, this means that any IO the program does can be trivially mocked for testing, and that all operations the program can perform are sandboxed. If a function doesn't require a reference to the `FS` trait, you can be sure it doesn't interact with the file system.
2021-06-16 12:27:26 -06:00
2019-11-13 00:05:04 -07:00
## Import System
2019-12-30 20:58:27 -07:00
Similar to python, folders/files represent the `.` seperated import path, but relative imports are *not* supported. Exported values must be marked with `pub`. All imports take the form:
2019-11-13 00:05:04 -07:00
2021-06-16 12:02:02 -06:00
```rust
2021-06-26 18:04:30 -06:00
import package.path as local_name;
2019-12-30 20:58:27 -07:00
pub type MyStruct struct {
2021-06-26 18:04:30 -06:00
pub id: I32,
2020-01-24 21:14:17 -07:00
}
```
## Basic Statements
### `if`
2021-06-03 09:16:48 -06:00
`if` is an expression in boring-lang, with the last expression in a block being the return value.
2020-01-24 21:14:17 -07:00
2021-06-16 12:02:02 -06:00
```rust
2021-09-15 08:43:38 -06:00
let a = if (true) {
2020-01-24 21:14:17 -07:00
4
} else {
2
}
// a == 4
```
Conditions do not require parenthesis and *must* evaluate to the Boolean type.
### Loops
Boring-lang supports `for` and `while` loops, with `for` having an `async` variant. `while` loops require an expression of Boolean type, while `for` loops require an expression that implements the `Iter` or `AIter` traits.
2021-06-16 12:02:02 -06:00
```rust
2021-06-12 12:30:00 -06:00
let mut i = 0;
2020-01-24 21:14:17 -07:00
while i < 100 {
2021-06-12 12:30:00 -06:00
i = i + 1;
2020-01-24 21:14:17 -07:00
// do something here
}
for i in range(100) {
// do something here
}
async for result in paginated_list {
// do something with result
2019-12-30 20:58:27 -07:00
}
2019-11-13 00:05:04 -07:00
```
2020-01-24 21:14:17 -07:00
`continue` and `break` work similar to other languages.
2021-06-16 12:02:02 -06:00
```rust
2020-01-24 21:14:17 -07:00
while true {
2021-06-03 09:20:28 -06:00
break; // do nothing
2020-01-24 21:14:17 -07:00
}
for i in range(100) {
2021-06-12 12:30:00 -06:00
continue; // do nothing
2020-01-24 21:14:17 -07:00
}
```
### `with`
`with` and `async with` blocks are similar to the python statement with the same name. But unlike the python version, `with` blocks are expressions. `with` blocks take in an expression that implements the `With` or `AWith` trait, and execute a block that *may* return a result (non-result returns are assumed success).
2021-06-16 12:03:28 -06:00
```rust
2020-01-24 21:14:17 -07:00
// commits on success, aborts on error.
// transation.aexit may just return an error as a pass-through after aborting,
// but it may also transform it into another error adding context.
return async with db.transation(ctx) as t {
2021-06-03 09:20:28 -06:00
await t.insert(ctx, record); // returns result type
2021-06-16 12:02:02 -06:00
};
2020-01-24 21:14:17 -07:00
```
### `return`
`return` statements exit a function early, returning the given value. They are purely optional as the last expression in a function will automatically return its value.
### `match`
`match` expressions provide pattern matching, similar to a `C` switch statement.
2021-06-16 12:03:28 -06:00
```rust
2021-06-03 09:20:28 -06:00
let number = 3;
2020-01-24 21:14:17 -07:00
let result = match number {
1 => 'foo',
3 => 'bar',
_ => 'baz',
2021-06-16 12:02:02 -06:00
};
2020-01-24 21:14:17 -07:00
// result = 'bar'
```