Skip to content

Commit

Permalink
Make semicolons mandatory to eventually allow for expression blocks
Browse files Browse the repository at this point in the history
This makes the syntax more cluttered, but should eventually be
worth it. Gets rid of automatic semicolon insertion.
  • Loading branch information
andy-byers committed Aug 19, 2024
1 parent 5fb8950 commit 5487c99
Show file tree
Hide file tree
Showing 37 changed files with 906 additions and 805 deletions.
159 changes: 80 additions & 79 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,36 @@ The following example demonstrates creation of the basic value types.
Composite types are discussed in [tuple](#tuple), [structure](#structure), etc., below.
```
// initializer is validated against the type annotation
let b: bool = true
let i: int = 123
let f: float = 10.0e-1
let s: str = 'abc'
let f: fn() -> int = || 123
let b: bool = true;
let i: int = 123;
let f: float = 10.0e-1;
let s: str = 'abc';
let f: fn() -> int = || 123;
// type is inferred from the initializer
let b = false
let i = 40 + 2
let f = 1.0 * 2
let s = 'de' + 'f'
let F = |x| x * 2
let b = false;
let i = 40 + 2;
let f = 1.0 * 2;
let s = 'de' + 'f';
let F = |x| x * 2;
// explicit type conversion operator
let b = 1 as bool
let i = 2 + 3.4 as int
let b = 1 as bool;
let i = 2 + 3.4 as int;
```

The previous example showed examples of a simple kind of type inference, where the type of a variable is inferred from an initializer expression (the expression to the right of the `=`).
Paw supports a more general kind of type inference for containers and closures, where the type of each 'unknown' must be inferred before the declaration in question goes out of scope, or is used in a way in which the type cannot be determined.
The main caveat here is that Paw does not yet have support for 'generic bounds', so we can't handle, for example, a closure like `|a, b| a + b` where the operator `+` imposes a bound on both `a` and `b`, restricting the types they can be 'instantiated' with (uses the same code as generics).
For example:
```
let f = |a| a // fn(?0) -> ?0
f(123) // infer ?0 = int
f(42)
let f = |a| a; // fn(?0) -> ?0
f(123); // infer ?0 = int
f(42);
let v = [] // [?1]
v.push('a') // infer ?1 = str
let s: str = v[0]
let v = []; // [?1]
v.push('a'); // infer ?1 = str
let s: str = v[0];
```

### Variables
Expand All @@ -79,10 +79,10 @@ Local variables can be shadowed and 'rebound', and a global item may be shadowed
Locals can also be captured in the body of a closure (see [closures](#closures)).
```
// initializer (' = 0') is required
let x: int = 0
let x: int = 0;
// rebind 'x' to a float (type is inferred from initializer)
let x = 6.63e-34
let x = 6.63e-34;
```

### Scope
Expand All @@ -92,7 +92,7 @@ Many language constructs use blocks to create their own scope, like functions, s
Explicit scoping blocks are also supported.
```
{
let x = 42
let x = 42;
} // 'x' goes out of scope here
```

Expand All @@ -104,17 +104,17 @@ Note that named functions can only be defined at the toplevel in Paw.
```
fn fib(n: int) -> int {
if n < 2 {
return n
return n;
}
return fib(n - 2) + fib(n - 1)
return fib(n - 2) + fib(n - 1);
}
```

### Closures
```
fn make_fib(n: int) -> fn() -> int {
// captures 'n'
return || fib(n)
return || fib(n);
}
```

Expand All @@ -126,11 +126,11 @@ struct Object {
}
// all fields must be initialized
let o = Object{b: 'two', a: 1}
let o = Object{b: 'two', a: 1};
// unit structs are written without curly braces
struct Unit
let u = Unit
struct Unit;
let u = Unit;
```

### Enumerations
Expand All @@ -141,8 +141,8 @@ enum Choices {
}
// unit variants are written without parenthesis
let c = Choices::First
let c = Choices::Second(123)
let c = Choices::First;
let c = Choices::Second(123);
```

### Control flow
Expand All @@ -161,44 +161,44 @@ if i == 0 {
// (must appear in a function that returns Option<T>/Result<T, E>), otherwise, unwraps
// the Option/Result
fn maybe() -> Option<int> {
let i = maybe_none()?
return fallible(i)
let i = maybe_none()?;
return fallible(i);
}
// 'break'/'continue' (must appear in a loop):
break
continue
break;
continue;
// Numeric 'for' loop:
for i = 0, 10, 2 { // start, end[, step]
}
// 'while' loop:
let i = 0
let i = 0;
while i < 10 {
i = i + 1
i = i + 1;
}
// 'do...while' loop:
let i = 10
let i = 10;
do {
i = i - 1
} while i > 0
i = i - 1;
} while i > 0;
```

### Strings
```
let s = 'Hello, world!'
assert(s.starts_with('Hello'))
assert(s.ends_with('world!'))
assert(s[:5].ends_with('Hello'))
assert(s[-6:].starts_with('world!'))
assert(1 == s.find('ello'))
assert(-1 == s.find('Cello'))
let s = 'Hello, world!';
assert(s.starts_with('Hello'));
assert(s.ends_with('world!'));
assert(s[:5].ends_with('Hello'));
assert(s[-6:].starts_with('world!'));
assert(1 == s.find('ello'));
assert(-1 == s.find('Cello'));
let a = s.split(',')
assert(s == ','.join(a))
let a = s.split(',');
assert(s == ','.join(a));
```

### Generics
Expand All @@ -207,16 +207,16 @@ Variables with generic types must be treated generically, that is, they can only
This allows each template to be type checked a single time, rather than once for each unique instantiation, and makes it easier to generate meaningful error messages.
```
fn map<A, B>(f: fn(A) -> B, vec: [A]) -> [B] {
let result = []
let result = [];
for a in vec {
result.push(f(a))
result.push(f(a));
}
return result
return result;
}
// infer A = float, B = int
let vec = map(|f: float| f as int, [0.5, 1.5, 2.5])
assert(vec == [0, 1, 2])
let vec = map(|f: float| f as int, [0.5, 1.5, 2.5]);
assert(vec == [0, 1, 2]);
// struct template
struct Object<S, T> {
Expand All @@ -236,67 +236,67 @@ let o = Object{
b: 'abc', // infer T = str
}
// field access using '.'
let a = o.a + 1
let b = o.b + 'two'
let a = o.a + 1;
let b = o.b + 'two';
```

### Tuples
```
let unit = ()
let singleton = (42,)
let pair = (true, 'abc')
let triplet = (1.0, 'two', 3)
let unit = ();
let singleton = (42,);
let pair = (true, 'abc');
let triplet = (1.0, 'two', 3);
let a = singleton.0
let b = pair.1
let c = triplet.2
let a = singleton.0;
let b = pair.1;
let c = triplet.2;
```

### Vectors
```
let empty: [int] = []
let empty: [int] = [];
// infer T = str
let empty = []
empty.push('a')
let empty = [];
empty.push('a');
let vec = [
[[1, 2, 3], [0]],
[[4, 5, 6], [1]],
[[7, 8, 9], [2]],
]
let vec = [1, 2, 3]
assert(vec[:1] == [1])
assert(vec[1:-1] == [2])
assert(vec[-1:] == [3])
let vec = [1, 2, 3];
assert(vec[:1] == [1]);
assert(vec[1:-1] == [2]);
assert(vec[-1:] == [3]);
```

### Maps
```
let empty: [int: str] = [:]
let empty: [int: str] = [:];
// infer K = int, V = str
let empty = [:]
empty[0] = 'abc'
let empty = [:];
empty[0] = 'abc';
let map = [1: 'a', 2: 'b']
map[3] = 42
map.erase(1)
let map = [1: 'a', 2: 'b'];
map[3] = 42;
map.erase(1);
assert(m == [2: 'b'])
assert(m == [2: 'b']);
// prints 'default'
print(m.get_or(1, 'default'))
print(m.get_or(1, 'default'));
```

### Error handling
```
fn divide_by_0(n: int) {
n = n / 0
n = n / 0;
}
let status = try(divide_by_0, 42)
assert(status != 0)
let status = try(divide_by_0, 42);
assert(status != 0);
```

## Operators
Expand Down Expand Up @@ -324,6 +324,7 @@ assert(status != 0)
+ [x] type inference for `struct` templates and builtin containers
+ [x] sum types/discriminated unions (`enum`)
+ [x] product types (tuple)
+ [ ] Rust-like expression blocks
+ [ ] refactor user-provided allocation interface to allow heap expansion
+ [ ] module system and `import` keyword
+ [ ] methods using `impl` blocks
Expand Down
1 change: 0 additions & 1 deletion fuzz/fuzz.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

#include "fuzz.h"
#include "lib.h"
#include "util.h"

extern int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
Expand Down
14 changes: 14 additions & 0 deletions fuzz/seed.paw
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// seed.paw

fn math_operations() {
let a = $
let b = $a

let a = $
let b = $
let c = a $ b
}

pub fn main() {
math_operations()
}
12 changes: 8 additions & 4 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,8 @@ static void dump_stmt(Printer *P, struct AstStmt *s)
DUMP_FMT(P, "line: %d\n", s->hdr.line);
switch (AST_KINDOF(s)) {
case kAstExprStmt:
DUMP_MSG(P, "lhs: ");
dump_expr(P, s->expr.lhs);
DUMP_MSG(P, "rhs: ");
dump_expr(P, s->expr.rhs);
DUMP_MSG(P, "expr: ");
dump_expr(P, s->expr.expr);
break;
case kAstBlock:
dump_stmt_list(P, s->block.stmts, "stmts");
Expand Down Expand Up @@ -308,6 +306,12 @@ static void dump_expr(Printer *P, struct AstExpr *e)
DUMP_MSG(P, "target: ");
dump_expr(P, e->unop.target);
break;
case kAstAssignExpr:
DUMP_MSG(P, "lhs: ");
dump_expr(P, e->assign.lhs);
DUMP_MSG(P, "rhs: ");
dump_expr(P, e->assign.rhs);
break;
case kAstBinOpExpr:
DUMP_FMT(P, "op: %d\n", e->binop.op);
DUMP_MSG(P, "lhs: ");
Expand Down
Loading

0 comments on commit 5487c99

Please sign in to comment.