Skip to content

Commit 10a2f25

Browse files
committed
Implement block expressions
1 parent dfee4da commit 10a2f25

51 files changed

Lines changed: 359 additions & 51 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/EXTENDING_LOX.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ These are the documented changes to the language/syntax from the original Lox la
1515
- `isString`
1616
- `isNil`
1717
- `strIsEmpty`
18+
- Block expressions: `var sum = { 5 + 10 };`
19+
- New bytecode op: `OP_CLOSE_BLOCK_EXPR n`
1820
- Native Functions
1921
- [ ] `arrSort(array, fn)`
2022
- [ ] `arrMap(array, fn)`

docs/OPCODES.md

Lines changed: 44 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,49 @@
44

55
## Opcode Table
66

7-
| Opcode | Operand(s) | Stack | Notes |
8-
| ---------------------- | ---------------------------------- | ----: | ----- |
9-
| **OP_CONSTANT** | `index` | +1 | |
10-
| **OP_NIL** | | +1 | |
11-
| **OP_TRUE** | | +1 | |
12-
| **OP_FALSE** | | +1 | |
13-
| **OP_ADD** | | -2 +1 | |
14-
| **OP_SUBTRACT** | | -2 +1 | |
15-
| **OP_MULTIPLY** | | -2 +1 | |
16-
| **OP_DIVIDE** | | -2 +1 | |
17-
| **OP_MODULO** | | -2 +1 | |
18-
| **OP_NEGATE** | | -1 +1 | |
19-
| **OP_NOT** | | -1 +1 | |
20-
| **OP_PRINT** | | | |
21-
| **OP_RETURN** | | | |
22-
| **OP_EQUAL** | | -2 +1 | |
23-
| **OP_GREATER** | | -2 +1 | |
24-
| **OP_LESS** | | -2 +1 | |
25-
| **OP_POP** | | -1 | |
26-
| **OP_DEFINE_GLOBAL** | `index` | | |
27-
| **OP_GET_GLOBAL** | `index` | | |
28-
| **OP_SET_GLOBAL** | `index` | | |
29-
| **OP_GET_LOCAL** | `slot` | | |
30-
| **OP_SET_LOCAL** | `slot` | | |
31-
| **OP_JUMP_IF_FALSE** | `jumpOffset` | | |
32-
| **OP_JUMP_IF_NOT_NIL** | `jumpOffset` | | |
33-
| **OP_JUMP** | `jumpOffset` | | |
34-
| **OP_LOOP** | `jumpOffset` | | |
35-
| **OP_CLOSE_UPVALUE** | | | |
36-
| **OP_GET_UPVALUE** | `slot` | | |
37-
| **OP_SET_UPVALUE** | `slot` | | |
38-
| **OP_GET_PROPERTY** | `index` | | |
39-
| **OP_SET_PROPERTY** | `index`, `index` | | |
40-
| **OP_CLASS** | `index` | | |
41-
| **OP_METHOD** | `index` | | |
42-
| **OP_INVOKE** | `index`, `argCount` | | |
43-
| **OP_CALL** | `argCount` | | |
44-
| **OP_CLOSURE** | `index`, `isLocal`, `upvalueIndex` | | |
45-
| **OP_FIELD** | `index` | | |
46-
| **OP_ARRAY** | `itemCount` | -n +1 | |
47-
| **OP_INDEX_GET** | | -2 +1 | |
48-
| **OP_INDEX_SET** | | -3 +1 | |
7+
| Opcode | Operand(s) | Stack | Notes |
8+
| ----------------------- | ---------------------------------- | ----: | -------------------------------------------------------------------- |
9+
| **OP_CONSTANT** | `index` | +1 | |
10+
| **OP_NIL** | | +1 | |
11+
| **OP_TRUE** | | +1 | |
12+
| **OP_FALSE** | | +1 | |
13+
| **OP_ADD** | | -2 +1 | |
14+
| **OP_SUBTRACT** | | -2 +1 | |
15+
| **OP_MULTIPLY** | | -2 +1 | |
16+
| **OP_DIVIDE** | | -2 +1 | |
17+
| **OP_MODULO** | | -2 +1 | |
18+
| **OP_NEGATE** | | -1 +1 | |
19+
| **OP_NOT** | | -1 +1 | |
20+
| **OP_PRINT** | | | |
21+
| **OP_RETURN** | | | |
22+
| **OP_EQUAL** | | -2 +1 | |
23+
| **OP_GREATER** | | -2 +1 | |
24+
| **OP_LESS** | | -2 +1 | |
25+
| **OP_POP** | | -1 | |
26+
| **OP_DEFINE_GLOBAL** | `index` | | |
27+
| **OP_GET_GLOBAL** | `index` | | |
28+
| **OP_SET_GLOBAL** | `index` | | |
29+
| **OP_GET_LOCAL** | `slot` | | |
30+
| **OP_SET_LOCAL** | `slot` | | |
31+
| **OP_JUMP_IF_FALSE** | `jumpOffset` | | |
32+
| **OP_JUMP_IF_NOT_NIL** | `jumpOffset` | | |
33+
| **OP_JUMP** | `jumpOffset` | | |
34+
| **OP_LOOP** | `jumpOffset` | | |
35+
| **OP_CLOSE_UPVALUE** | | | |
36+
| **OP_GET_UPVALUE** | `slot` | | |
37+
| **OP_SET_UPVALUE** | `slot` | | |
38+
| **OP_GET_PROPERTY** | `index` | | |
39+
| **OP_SET_PROPERTY** | `index`, `index` | | |
40+
| **OP_CLASS** | `index` | | |
41+
| **OP_METHOD** | `index` | | |
42+
| **OP_INVOKE** | `index`, `argCount` | | |
43+
| **OP_CALL** | `argCount` | | |
44+
| **OP_CLOSURE** | `index`, `isLocal`, `upvalueIndex` | | |
45+
| **OP_FIELD** | `index` | | |
46+
| **OP_ARRAY** | `itemCount` | -n +1 | |
47+
| **OP_INDEX_GET** | | -2 +1 | |
48+
| **OP_INDEX_SET** | | -3 +1 | |
49+
| **OP_CLOSE_BLOCK_EXPR** | `upvalueCount` | -n +1 | Cleans upvalues while leaving block expression return value on stack |
4950

5051
### Operand Encodings
5152

@@ -55,6 +56,7 @@
5556
| **slot** | `uint8_t` | Slot number in the current call frame (local or upvalue). |
5657
| **argCount** | `uint8_t` | For `OP_CLOSURE` only: tells whether the captured variable lives in the immediate surrounding function (`1`) or is itself an upvalue (`0`). |
5758
| **itemCount** | `uint8_t` | For `OP_ARRAY` only: determines how many values on the stack go in to the array |
59+
| **upvalueCount** | `uint8_t` | For `OP_CLOSE_BLOCK_EXPR` only: determines how many upvalues to discard |
5860
| **isLocal** | `uint8_t` | (0 or 1) For `OP_CLOSURE` only: tells whether the captured variable lives in the immediate surrounding function (`1`) or is itself an upvalue (`0`). |
5961
| **upvalueIndex** | `uint8_t` | Index of the local or upvalue being captured (used together with the byte above). |
6062
| **jumpOffset** | `uint16_t` | Relative jump distance measured from the **next instruction** after the offset field. (big‑endian: high byte first) |

examples/cat.lox

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
if (argc() != 4) {
1+
if (argc() != 5) {
22
print "Usage: clox -f examples/cat.lox <file>";
33
exit(1);
44
}
55

6-
var path = argv(3);
6+
var fileText = {
7+
var path = argv(4);
78

8-
if (!fileExists(path)) {
9-
print "File doesn't exist '" + path + "'";
10-
exit(1);
11-
}
9+
if (!fileExists(path)) {
10+
print "File doesn't exist '" + path + "'";
11+
exit(1);
12+
}
1213

13-
var text = readFileToString(path);
14+
readFileToString(path)
15+
};
1416

15-
print text;
17+
print fileText;

src/chunk.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typedef enum {
4545
OP_ARRAY,
4646
OP_GET_INDEX,
4747
OP_SET_INDEX,
48+
OP_CLOSE_BLOCK_EXPR,
4849
} OpCode;
4950

5051
typedef struct {

src/compiler.c

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1040,10 +1040,74 @@ static void this_(Scanner *scanner, bool canAssign) {
10401040
variable(scanner, false);
10411041
}
10421042

1043+
static bool isExpressionStart(Token *token) {
1044+
return getRule(token->type)->prefix != NULL;
1045+
}
1046+
1047+
static void endScopeExpression() {
1048+
current->scopeDepth--;
1049+
1050+
int locals = 0;
1051+
1052+
while (current->localCount > 0 &&
1053+
current->locals[current->localCount - 1].depth > current->scopeDepth) {
1054+
1055+
locals++;
1056+
current->localCount--;
1057+
}
1058+
1059+
emitBytes(OP_CLOSE_BLOCK_EXPR, locals);
1060+
}
1061+
1062+
static void blockExpression(Scanner *scanner, bool canAssign) {
1063+
beginScope();
1064+
1065+
bool producedValue = false;
1066+
1067+
while (!check(TOKEN_RIGHT_BRACE) && !check(TOKEN_EOF)) {
1068+
1069+
// Detect trailing expression:
1070+
//
1071+
// expression followed by `}`
1072+
//
1073+
// Example:
1074+
// {
1075+
// var a = 1;
1076+
// a + 2
1077+
// }
1078+
//
1079+
// We compile expression and KEEP value on stack.
1080+
1081+
if (isExpressionStart(&parser.current)) {
1082+
// speculative parse
1083+
expression(scanner);
1084+
1085+
if (check(TOKEN_RIGHT_BRACE)) {
1086+
producedValue = true;
1087+
break;
1088+
}
1089+
1090+
consume(scanner, TOKEN_SEMICOLON, "Expect ';' after expression.");
1091+
1092+
emitByte(OP_POP);
1093+
} else {
1094+
declaration(scanner);
1095+
}
1096+
}
1097+
1098+
consume(scanner, TOKEN_RIGHT_BRACE, "Expect '}' after block expression.");
1099+
1100+
if (!producedValue) {
1101+
emitByte(OP_NIL);
1102+
}
1103+
1104+
endScopeExpression();
1105+
}
1106+
10431107
ParseRule rules[] = {
10441108
[TOKEN_LEFT_PAREN] = {grouping, call, PREC_CALL},
10451109
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
1046-
[TOKEN_LEFT_BRACE] = {NULL, NULL, PREC_NONE},
1110+
[TOKEN_LEFT_BRACE] = {blockExpression, NULL, PREC_NONE},
10471111
[TOKEN_RIGHT_BRACE] = {NULL, NULL, PREC_NONE},
10481112
[TOKEN_LEFT_BRACKET] = {arrayLiteral, indexValue, PREC_CALL},
10491113
[TOKEN_RIGHT_BRACKET] = {NULL, NULL, PREC_NONE},

src/debug.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ int disassembleInstruction(Chunk *chunk, int offset) {
157157
return simpleInstruction("OP_GET_INDEX", offset);
158158
case OP_SET_INDEX:
159159
return simpleInstruction("OP_SET_INDEX", offset);
160+
case OP_CLOSE_BLOCK_EXPR:
161+
return byteInstruction("OP_CLOSE_BLOCK", chunk, offset);
160162
default:
161163
fprintf(stderr, "Unknown opcode %d\n", instruction);
162164
return offset + 1;

src/vm.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,19 @@ static InterpretResult run() {
721721

722722
break;
723723
}
724+
case OP_CLOSE_BLOCK_EXPR: {
725+
int locals = READ_BYTE();
726+
727+
Value result = popFromStack();
728+
729+
closeUpvalues(vm.stackTop - locals);
730+
731+
vm.stackTop -= locals;
732+
733+
pushOnStack(result);
734+
735+
break;
736+
}
724737
}
725738
}
726739
#undef READ_BYTE

tests/blocks/block_expression.lox

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
fun process(a, b) = {
2+
var result = {
3+
var total = a + b;
4+
var half = total * 0.5;
5+
6+
half
7+
};
8+
9+
result
10+
};
11+
12+
print process(5, 20);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
== <script> ==
2+
0000 1 OP_NIL
3+
0001 | OP_RETURN
4+
== process ==
5+
0000 3 OP_GET_LOCAL 1
6+
0002 | OP_GET_LOCAL 2
7+
0004 | OP_ADD
8+
0005 4 OP_GET_LOCAL 4
9+
0007 | OP_CONSTANT 0 '0.500000'
10+
0009 | OP_MULTIPLY
11+
0010 6 OP_GET_LOCAL 5
12+
0012 7 OP_CLOSE_BLOCK 2
13+
0014 9 OP_GET_LOCAL 3
14+
0016 10 OP_CLOSE_BLOCK 1
15+
0018 | OP_RETURN
16+
== <script> ==
17+
0000 10 OP_CLOSURE 1 <fn process>
18+
0002 | OP_DEFINE_GLOBAL 0 'process'
19+
0004 12 OP_GET_GLOBAL 2 'process'
20+
0006 | OP_CONSTANT 3 '5.000000'
21+
0008 | OP_CONSTANT 4 '20.000000'
22+
0010 | OP_CALL 2
23+
0012 | OP_PRINT
24+
0013 13 OP_NIL
25+
0014 | OP_RETURN

0 commit comments

Comments
 (0)