if, loop, break, return, let, fn

This commit is contained in:
Simon From Jakobsen 2024-09-16 14:05:22 +00:00
parent 0528b6d80d
commit a940310964

View File

@ -411,6 +411,8 @@ func(
)
```
PS. it's called arguments in a function call. We call with arguments, and define with parameters.
## 3.5 Prefix expressions
Contrasting postfix expressions, prefix expression are operations where the operator comes first, then the operands are listed. In some languages, operations such as negation (eg. `-value`) and not-operations (eg. `!value`) are prefix operations. In the language we're making, all binary and unary arithmetic operations are prefix. This includes both expressions with a single operand, such as not (eg. `not value`), but also expressions with 2 operands, such ass addition (eg. `+ a b`) and equation (eg. `== a b`).
@ -504,5 +506,222 @@ class Parser {
The method just proceeds to try and parse a prefix expression.
## 3.7 If
## 3.7 If expressions
An if-expression is an `if`-token, followed by an condition in the form of an expressions, followed by a block-expression, optionally followed by an `else`-token and another block.
```ts
type ExprKind =
// ...
| { type: "if", cond: Expr, truthy: Expr, falsy?: Expr }
// ...
;
```
```ts
class Parser {
// ...
public parseIf(): Expr {
const pos = this.pos();
this.step();
const cond = this.parseExpr();
if (!this.test("{")) {
this.report("expected '}'");
return { kind: { type: "error" }, pos };
}
const truthy = this.parseBlock();
if (!this.test("else")) {
return { kind: { type: "if", cond, truthy }, pos };
}
this.step();
if (!this.test("{")) {
this.report("expected '}'");
return { kind: { type: "error" }, pos };
}
const falsy = this.parseBlock();
return { kind: { type: "if", cond, truthy, falsy }, pos };
}
// ...
}
```
When parsing an if-expression, we assume we already have reached an `if`-token.
We skip the `if`-token. Then we parse the condition expression `cond`. Then we check for a `{`-token and parse block. Then we check for an `else`-token. If not present, we return an if-expression with no `falsy`-option. Else we skip the `else`-token, check for and parse the `falsy`-block, and return the if-expression with the `falsy`-option.
## 3.8 Loop expressions
A loop expression is a `loop`-token followed by a block expression.
```ts
type ExprKind =
// ...
| { type: "loop", body: Expr }
// ...
;
```
```ts
class Parser {
// ...
public parseLoop(): Expr {
const pos = this.pos();
this.step();
if (!this.test("{")) {
this.report("expected '}'");
return { kind: { type: "error" }, pos };
}
const body = this.parseExpr();
return { kind: { type: "loop", body }, pos };
}
// ...
}
```
We again assume, we've already hit a `loop`-token, which we step over. Then we check for and parse a block expression constituting the loop `body`. We then return the loop expression.
## 3.9 Break statements
A break statement consists of a `break`-token and an optional expression.
The language will allow us to use loop as an expression. The break statement will then deliver the resulting value, eg.
```rs
let value = loop {
let value = get_value();
if acceptable(value) {
break value;
}
};
```
```ts
type StmtKind =
// ...
| { type: "break", expr?: Expr }
// ...
;
```
```ts
class Parser {
// ...
public parseBreak(): Stmt {
const pos = this.pos();
this.step();
if (!this.test(";")) {
return { kind: { type: "break" }, pos };
}
const expr = this.parseExpr();
return { kind: { type: "break", expr }, pos };
}
// ...
}
```
We assume we've already hit a `break`-token and step over it. Then we check if we've hit a `;`-token. If so, there's no expression, so we return a break statement with no expression. If there's no `;`-token, we parse an expression and return a break statement with it.
## 3.9 Return statements
The return statement is for functions what break is for loops.
```ts
type StmtKind =
// ...
| { type: "return", expr?: Expr }
// ...
;
```
```ts
class Parser {
// ...
public parseReturn(): Stmt {
const pos = this.pos();
this.step();
if (!this.test(";")) {
return { kind: { type: "return" }, pos };
}
const expr = this.parseExpr();
return { kind: { type: "return", expr }, pos };
}
// ...
}
```
## 3.10 Let statements
A let statement declares a variable. A let statement consists of a `let`-token, an identifier, a `=`-token, and an expression. The expression is the initial value of the variable.
```ts
type StmtKind =
// ...
| { type: "let", ident: string, value: Expr }
// ...
;
```
```ts
class Parser {
// ...
public parseLet(): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return { kind: { type: "error" }, pos };
}
const ident = this.current().identValue;
this.step();
if (!this.test("=")) {
this.report("expected '='");
return { kind: { type: "error" }, pos };
}
this.step();
const value = this.parseExpr();
return { kind: { type: "let", ident, value }, pos };
}
// ...
}
```
We step over the first `let`-token. Then we check for an `ident`-token, save it's value and step over it. Then we check for and step over a `=`-token. We then parse an expressions. And lastly return a let statement with the `ident` and `valie`.
## 3.11 Function definition statement
A function definition statement or 'fn'-statement for short is a statement that defines a function with it's name, parameters and body.
The function name is an identifier. The body is a block expression. The parameters is a list of identifiers seperated by `,`, enclosed in `(` and `)`.
An fn statements consists of an `fn`-token, an it's name as an identifier token, a parameter list, and the body.
```ts
type StmtKind =
// ...
| { type: "fn", ident: string, params: Param[], body: Expr }
// ...
;
type Param = {
ident: string,
pos: Pos,
};
```
```ts
class Parser {
// ...
public parseFn(): Stmt {
const pos = this.pos();
this.step();
if (!this.test("ident")) {
this.report("expected ident");
return { kind: { type: "error" }, pos };
}
const ident = this.current().identValue;
}
// ...
}
```
PS. it's called parameters in a function definition. We call with arguments, and define with parameters.