Finish chapter 5

This commit is contained in:
Simon From Jakobsen 2024-10-21 14:14:47 +00:00
parent 0d07671efa
commit 1417ba0d15
3 changed files with 122 additions and 8 deletions

View File

@ -266,8 +266,8 @@ The implemented parser goes through each token and checks the structure of the p
#### Exercises #### Exercises
1. Implement subtraction and division. 1. Implement subtraction and division.
3. \* Add position (line and column number) to each expression. 2. \* Add position (line and column number) to each expression.
5. \*\* Rewrite parser, to use infix notation (`1 + 2 * 3`) instead of prefix/polish notation (`+ 1 * 2 3`). 3. \*\* Rewrite parser, to use infix notation (`1 + 2 * 3`) instead of prefix/polish notation (`+ 1 * 2 3`).
## 1.5 Putting it together ## 1.5 Putting it together

View File

@ -564,6 +564,10 @@ class Parser {
return this.expr({ type: "if", cond, truthy }, pos); return this.expr({ type: "if", cond, truthy }, pos);
} }
this.step(); this.step();
if (this.test("if")) {
const falsy = this.parseIf();
return this.expr({ type: "if", cond, truthy, falsy }, pos);
}
if (!this.test("{")) { if (!this.test("{")) {
this.report("expected block"); this.report("expected block");
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
@ -577,7 +581,7 @@ class Parser {
When parsing an if-expression, we assume we already have reached an `if`-token. 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. 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. If we find a `if`-token, it means we're parsing an else-if construct, in which case we parse an if expression recursively. Else check for and parse the `falsy`-block. And then return the if-expression with the `falsy`-option.
## 3.8 Loop expressions ## 3.8 Loop expressions

View File

@ -973,7 +973,7 @@ class Evaluator {
// ... // ...
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow { private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
// ... // ...
if (name === "push") { if (name === "array_push") {
if (args.length !== 2) if (args.length !== 2)
throw new Error("incorrect arguments"); throw new Error("incorrect arguments");
const array = args[0]; const array = args[0];
@ -991,7 +991,69 @@ class Evaluator {
This function expects that the first parameter is an array, and the second is the value to push. This function expects that the first parameter is an array, and the second is the value to push.
### 4.8.3 Println ### 4.8.3 Array length
We'll also need a way to get the length of an array.
```ts
class Evaluator {
// ...
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
// ...
if (name === "array_len") {
if (args.length !== 1)
throw new Error("incorrect arguments");
const array = args[0];
if (array.type !== "array")
throw new Error("incorrect arguments");
return flowValue({ type: "int", value: array.values.length });
}
// ...
}
// ...
}
```
### 4.8.3 String functions
Like arrays, we need push and length functions for strings too.
```ts
class Evaluator {
// ...
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
// ...
if (name === "string_concat") {
if (args.length !== 2)
throw new Error("incorrect arguments");
if (args[0].type === "string") {
if (args[1].type === "string")
return flowValue({ type: "string", value: args[0].value + args[1].value });
if (args[1].type === "int")
return flowValue({ type: "string", value: args[0].value + String.fromCharCode(args[1].value) });
}
if (args[0].type === "int" && args[1].type === "string") {
return flowValue({ type: "string", value: String.fromCharCode(args[0].value) + args[1].value });
}
throw new Error("incorrect arguments");
}
if (name === "string_len") {
if (args.length !== 1)
throw new Error("incorrect arguments");
if (args[0].type !== "string")
throw new Error("incorrect arguments");
return flowValue({ type: "int", value: args[0].value.length });
}
// ...
}
// ...
}
```
The function `string_concant` takes either two strings or a string and a character (int). A new string is returning containing both values concatonated.
### 4.8.4 Println
To print text to the screen, we need a print function. We'll make one called `println` which will also print a newline afterwards. To print text to the screen, we need a print function. We'll make one called `println` which will also print a newline afterwards.
@ -1031,7 +1093,7 @@ println("hello {}", a);
println("{} + {} = {}", 1, 2, 1 + 2); println("{} + {} = {}", 1, 2, 1 + 2);
``` ```
### 4.8.4 Exit ### 4.8.5 Exit
Normally, the evaluator will return a zero-exit code, meanin no error. In case we program should result in an error code, we'll need an exit function. Normally, the evaluator will return a zero-exit code, meanin no error. In case we program should result in an error code, we'll need an exit function.
@ -1056,7 +1118,52 @@ class Evaluator {
} }
``` ```
### 4.8.5 Define builtins ### 4.8.6 Convert values to strings
This function will return a string representation of any value.
```ts
class Evaluator {
// ...
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
// ...
if (name === "to_string") {
if (args.length !== 1)
throw new Error("incorrect arguments");
return flowValue({ type: "string", value: valueToString(args[0]) });
}
// ...
}
// ...
}
```
### 4.8.7 Convert strings to integers
This function will try to convert a string into an integer.
```ts
class Evaluator {
// ...
private executeBuiltin(name: string, args: Value[], syms: Syms): Flow {
// ...
if (name === "string_to_int") {
if (args.length !== 1 || args[0].type !== "string")
throw new Error("incorrect arguments");
const value = parseInt(args[0].value);
if (value === NaN)
return flowValue({ type: "null" });
return flowValue({ type: "int", value });
}
// ...
}
// ...
}
```
The function will return a null value, in case conversion fails.
## 4.9 Define builtins
Finally, we need a way to define the builtin functions in the symbol table. Finally, we need a way to define the builtin functions in the symbol table.
@ -1066,7 +1173,10 @@ class Evaluator {
public defineBuiltins() { public defineBuiltins() {
this.root.define("array", { type: "builtin_fn", name: "array" }); this.root.define("array", { type: "builtin_fn", name: "array" });
this.root.define("struct", { type: "builtin_fn", name: "struct" }); this.root.define("struct", { type: "builtin_fn", name: "struct" });
this.root.define("push", { type: "builtin_fn", name: "struct" }); this.root.define("array_push", { type: "builtin_fn", name: "array_push" });
this.root.define("array_len", { type: "builtin_fn", name: "array_len" });
this.root.define("string_concat", { type: "builtin_fn", name: "string_concat" });
this.root.define("string_len", { type: "builtin_fn", name: "string_len" });
this.root.define("println", { type: "builtin_fn", name: "println" }); this.root.define("println", { type: "builtin_fn", name: "println" });
this.root.define("exit", { type: "builtin_fn", name: "exit" }); this.root.define("exit", { type: "builtin_fn", name: "exit" });
} }