Skip to content

Commit

Permalink
feat: Implement Chapter 10 (Functions) (#21)
Browse files Browse the repository at this point in the history
Implementation of [Chapter 10] of _Crafting Interpreters_.

I wondered back in #10 whether I'd be shooting myself in the foot by
supporting `,` as a number separator (`1,234`). This is where it
happened. The comma becomes quite ambiguous in the arguments list of a
function call. For exmaple:

```
f(1,234,567);
```

Is that a call to `f` with a single argument, `1,234,567` or a call to
`f` with three arguments, `1`, `234` and `567`? The grammar is
ambiguous.

My solution is to make spaces significant while scanning:

```
f(1,234,567);  // one argument
f(1, 234, 567);  // three arguments
```

We'll see how this holds up!

[Chapter 10]: https://craftinginterpreters.com/functions.html
  • Loading branch information
danvk authored Jan 20, 2024
1 parent 0127c39 commit d485b65
Show file tree
Hide file tree
Showing 33 changed files with 520 additions and 30 deletions.
8 changes: 8 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ module.exports = {
// Disable autofixing "let" -> "const" just because I haven't mutated it _yet_.
// Autofixing `() => fn()` -> `() => { fn() }` is a recipe for trouble.
"@typescript-eslint/no-confusing-void-expression": "off",
"@typescript-eslint/no-unnecessary-condition": [
"error",
{
// why isn't this the default?
// https://github.com/typescript-eslint/typescript-eslint/issues/7047
allowConstantLoopConditions: true,
},
],
"no-autofix/@typescript-eslint/no-confusing-void-expression": "warn",
"no-autofix/prefer-const": "warn",
"perfectionist/sort-interfaces": "off",
Expand Down
2 changes: 2 additions & 0 deletions baselines/chapter10-add-abc.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
6
nil
1 change: 1 addition & 0 deletions baselines/chapter10-define-and-call-fun.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hi, Dear Reader!
20 changes: 20 additions & 0 deletions baselines/chapter10-fibonacci.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
2 changes: 2 additions & 0 deletions baselines/chapter10-make-counter.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1
2
2 changes: 2 additions & 0 deletions baselines/chapter10-multi-currency.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$100,000 at 7% for 10 years:
196715.1357289567
1 change: 1 addition & 0 deletions baselines/chapter10-print-fun.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<fn f>
2 changes: 2 additions & 0 deletions baselines/chapter10-return-in-loop.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1
2
2 changes: 2 additions & 0 deletions baselines/chapter10-return-nothing.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
don't return anything
nil
10 changes: 10 additions & 0 deletions baselines/chapter10-too-many-params.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[line 32] Error at 'p255': Can't have more than 255 parameters.
[line 32] Error at 'p256': Can't have more than 255 parameters.
[line 32] Error at 'p257': Can't have more than 255 parameters.
[line 32] Error at 'p258': Can't have more than 255 parameters.
[line 32] Error at 'p259': Can't have more than 255 parameters.
[line 64] Error at '255': Can't have more than 255 arguments.
[line 64] Error at '256': Can't have more than 255 arguments.
[line 64] Error at '257': Can't have more than 255 arguments.
[line 64] Error at '258': Can't have more than 255 arguments.
[line 64] Error at '259': Can't have more than 255 arguments.
Empty file.
5 changes: 5 additions & 0 deletions examples/chapter10-add-abc.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun add(a, b, c) {
print a + b + c;
}

print add(1, 2, 3);
4 changes: 4 additions & 0 deletions examples/chapter10-clock.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
print clock;
var start = clock();
var end = clock();
print end - start; // hopefully 0!
5 changes: 5 additions & 0 deletions examples/chapter10-define-and-call-fun.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fun sayHi(first, last) {
print "Hi, " + first + " " + last + "!";
}

sayHi("Dear", "Reader");
8 changes: 8 additions & 0 deletions examples/chapter10-fibonacci.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fun fib(n) {
if (n <= 1) return n;
return fib(n - 2) + fib(n - 1);
}

for (var i = 0; i < 20; i = i + 1) {
print fib(i);
}
13 changes: 13 additions & 0 deletions examples/chapter10-make-counter.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}

return count;
}

var counter = makeCounter();
counter(); // "1".
counter(); // "2".
10 changes: 10 additions & 0 deletions examples/chapter10-multi-currency.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
fun compound(amount, rate, years) {
var p = 1 + rate / 100;
for (var i = 0; i < years; i = i + 1) {
amount = amount * p;
}
return amount;
}

print "$100,000 at 7% for 10 years:";
print compound($100,000, 7, 10);
3 changes: 3 additions & 0 deletions examples/chapter10-print-fun.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fun f() {}

print f;
9 changes: 9 additions & 0 deletions examples/chapter10-return-in-loop.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
fun count(n) {
while (n < 100) {
if (n == 3) return n; // <--
print n;
n = n + 1;
}
}

count(1);
6 changes: 6 additions & 0 deletions examples/chapter10-return-nothing.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fun procedure() {
print "don't return anything";
}

var result = procedure();
print result; // ?
65 changes: 65 additions & 0 deletions examples/chapter10-too-many-params.lox
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// for i in range(0, 26):
// for j in range(0, 10):
// print(f"p{10*i+j},", end='')
// print()

fun tooManyParams(
p0, p1, p2, p3, p4, p5, p6, p7, p8, p9,
p10, p11, p12, p13, p14, p15, p16, p17, p18, p19,
p20, p21, p22, p23, p24, p25, p26, p27, p28, p29,
p30, p31, p32, p33, p34, p35, p36, p37, p38, p39,
p40, p41, p42, p43, p44, p45, p46, p47, p48, p49,
p50, p51, p52, p53, p54, p55, p56, p57, p58, p59,
p60, p61, p62, p63, p64, p65, p66, p67, p68, p69,
p70, p71, p72, p73, p74, p75, p76, p77, p78, p79,
p80, p81, p82, p83, p84, p85, p86, p87, p88, p89,
p90, p91, p92, p93, p94, p95, p96, p97, p98, p99,
p100,p101,p102,p103,p104,p105,p106,p107,p108,p109,
p110,p111,p112,p113,p114,p115,p116,p117,p118,p119,
p120,p121,p122,p123,p124,p125,p126,p127,p128,p129,
p130,p131,p132,p133,p134,p135,p136,p137,p138,p139,
p140,p141,p142,p143,p144,p145,p146,p147,p148,p149,
p150,p151,p152,p153,p154,p155,p156,p157,p158,p159,
p160,p161,p162,p163,p164,p165,p166,p167,p168,p169,
p170,p171,p172,p173,p174,p175,p176,p177,p178,p179,
p180,p181,p182,p183,p184,p185,p186,p187,p188,p189,
p190,p191,p192,p193,p194,p195,p196,p197,p198,p199,
p200,p201,p202,p203,p204,p205,p206,p207,p208,p209,
p210,p211,p212,p213,p214,p215,p216,p217,p218,p219,
p220,p221,p222,p223,p224,p225,p226,p227,p228,p229,
p230,p231,p232,p233,p234,p235,p236,p237,p238,p239,
p240,p241,p242,p243,p244,p245,p246,p247,p248,p249,
p250,p251,p252,p253,p254,p255,p256,p257,p258,p259
) {
return 260;
}

// note: whitespace is important here!!
print tooManyParams(
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69,
70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
240, 241, 242, 243, 244, 245, 246, 247, 248, 249,
250, 251, 252, 253, 254, 255, 256, 257, 258, 259
);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"lint:package-json": "npmPkgJsonLint .",
"lint:packages": "pnpm dedupe --check",
"lint:spelling": "cspell \"**\" \".github/**/*\"",
"pre-push": "pnpm run '/^lint(?!:packages).*$/'",
"pre-push": "pnpm run '/^(tsc|lint(?!:packages).*)$/'",
"prepare": "husky install",
"repl": "pnpm run:ts src/index.ts",
"run:ts": "node --loader ts-node/esm --no-warnings",
Expand Down
12 changes: 10 additions & 2 deletions src/ast-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,14 @@ export const astPrinter: ExpressionVisitor<string> & StmtVisitor<string> = {
"block",
...block.statements.map((stmt) => visitStmt(stmt, astPrinter)),
),
call: (expr) => parenthesize(expr.callee, ...expr.args),
expr: (stmt) => visitExpr(stmt.expression, astPrinter),
func: (stmt) =>
parenthesizeText(
"func",
parenthesizeText(stmt.name.lexeme, ...stmt.params.map((p) => p.lexeme)),
visitStmt({ kind: "block", statements: stmt.body }, astPrinter),
),
grouping: (expr) => parenthesize("group", expr.expr),
if: (stmt) =>
parenthesizeText(
Expand All @@ -26,6 +33,7 @@ export const astPrinter: ExpressionVisitor<string> & StmtVisitor<string> = {
literal: (expr) => (expr.value === null ? "nil" : String(expr.value)),
logical: (expr) => parenthesize(expr.operator.lexeme, expr.left, expr.right),
print: (stmt) => parenthesize("print", stmt.expression),
return: (stmt) => parenthesize("return", ...(stmt.value ? [stmt.value] : [])),
unary: (expr) => parenthesize(expr.operator.lexeme, expr.right),
"var-expr": (expr) => String(expr.name.literal),
"var-stmt": (stmt) =>
Expand All @@ -49,8 +57,8 @@ function parenthesizeText(...parts: (null | string)[]) {

// This would be nicer if it could be (Expr | Stmt)… but there's no programmatic
// way to distinguish those two in order to tell which visit function to call.
function parenthesize(name: string, ...exprs: (Expr | string)[]) {
const parts = [name];
function parenthesize(...exprs: (Expr | string)[]) {
const parts = [];
for (const expr of exprs) {
parts.push(typeof expr == "string" ? expr : visitExpr(expr, astPrinter));
}
Expand Down
33 changes: 32 additions & 1 deletion src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ export interface VarExpr {
name: Token;
}

export interface Call {
kind: "call";
callee: Expr;
paren: Token;
args: Expr[];
}

// Statements

export interface Assign {
kind: "assign";
name: Token;
Expand Down Expand Up @@ -76,15 +85,37 @@ export interface While {
body: Stmt;
}

export interface Func {
kind: "func";
name: Token;
params: Token[];
body: Stmt[];
}

export interface Return {
kind: "return";
keyword: Token;
value: Expr | null;
}

export type Expr =
| Assign
| Binary
| Call
| Grouping
| Literal
| Logical
| Unary
| VarExpr;
export type Stmt = Block | Expression | IfStmt | Print | VarStmt | While;
export type Stmt =
| Block
| Expression
| Func
| IfStmt
| Print
| Return
| VarStmt
| While;

export type ExpressionVisitor<R> = {
[Kind in Expr["kind"]]: (expr: Extract<Expr, { kind: Kind }>) => R;
Expand Down
6 changes: 6 additions & 0 deletions src/callable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Interpreter } from "./interpreter.js";

export abstract class LoxCallable {
abstract arity(): number;
abstract call(interpreter: Interpreter, args: unknown[]): unknown;
}
Loading

0 comments on commit d485b65

Please sign in to comment.