Skip to content

Commit 08135e1

Browse files
committed
Update FVM bytecode
Bump bytecode format version to 2. Replace dialect of stack elements with stack words. Add unary dereference opcode. Replace print opcode with put character opcode. Remove print keyword token. Remove print statement AST node. Add intrinsic expression token. Add intrinsic expression AST node. Add put character intrinsic. Add standard library. Update example scripts. Update grammar. Update readme. Add Python files to gitattributes.
1 parent 902203a commit 08135e1

File tree

18 files changed

+423
-203
lines changed

18 files changed

+423
-203
lines changed

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@
44
# Mark FVM bytecode files as binary:
55
*.fvm binary
66
*.fyc binary
7+
8+
# Assert that Python is Python:
9+
*.py linguist-language=Python

funcy/ast/nodes.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,25 @@ def get_info(self) -> str:
292292
return self.op.name
293293

294294

295+
class IntrinsicExprNode(ExprNode):
296+
""" An intrinsic expression node of an abstract syntax tree. """
297+
298+
name: IdentifierExprNode
299+
""" The intrinsic expression's name. """
300+
301+
exprs: list[ExprNode]
302+
""" The intrinsic expression's expressions. """
303+
304+
def __init__(self, name: IdentifierExprNode) -> None:
305+
"""
306+
Initialize the intrinsic expression's name and expressions.
307+
"""
308+
309+
super().__init__()
310+
self.name = name
311+
self.exprs = []
312+
313+
295314
class DeclNode(Node):
296315
""" A declaration node of an abstract syntax tree. """
297316

@@ -476,19 +495,6 @@ def __init__(self, expr: ExprNode) -> None:
476495
self.expr = expr
477496

478497

479-
class PrintStmtNode(StmtNode):
480-
""" A print statement node of an abstract syntax tree. """
481-
482-
expr: ExprNode
483-
""" The print statement's expression. """
484-
485-
def __init__(self, expr: ExprNode) -> None:
486-
""" Initialize the print statement's expression. """
487-
488-
super().__init__()
489-
self.expr = expr
490-
491-
492498
class ScopedJumpStmt(StmtNode):
493499
""" A scoped jump statement node of an abstract syntax tree. """
494500

funcy/ast/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ def get_node_children(node: Node) -> list[Node]:
2424
return [node.decl, node.expr]
2525
elif isinstance(node, ReturnExprStmtNode):
2626
return [node.expr]
27-
elif isinstance(node, PrintStmtNode):
28-
return [node.expr]
2927
elif isinstance(node, ExprStmtNode):
3028
return [node.expr]
3129
elif isinstance(node, CallExprNode):
@@ -42,6 +40,10 @@ def get_node_children(node: Node) -> list[Node]:
4240
return [node.expr]
4341
elif isinstance(node, BinExprNode):
4442
return [node.lhs_expr, node.rhs_expr]
43+
elif isinstance(node, IntrinsicExprNode):
44+
result: list[ExprNode] = [node.name]
45+
result.extend(node.exprs)
46+
return result
4547

4648
return []
4749

funcy/ast/visitor.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ def visit(self, node: Node, code: Code) -> None:
8282
self.visit_return_stmt(node, code)
8383
elif isinstance(node, ReturnExprStmtNode):
8484
self.visit_return_expr_stmt(node, code)
85-
elif isinstance(node, PrintStmtNode):
86-
self.visit_print_stmt(node, code)
8785
elif isinstance(node, ScopedJumpStmt):
8886
self.visit_scoped_jump_stmt(node, code)
8987
elif isinstance(node, ExprStmtNode):
@@ -106,6 +104,8 @@ def visit(self, node: Node, code: Code) -> None:
106104
self.visit_un_expr(node, code)
107105
elif isinstance(node, BinExprNode):
108106
self.visit_bin_expr(node, code)
107+
elif isinstance(node, IntrinsicExprNode):
108+
self.visit_intrinsic_expr(node, code)
109109
else:
110110
self.log_error(f"Bug: Unimplemented visitor for '{node}'!", node)
111111

@@ -170,7 +170,7 @@ def visit_func_stmt(self, node: FuncStmtNode, code: Code) -> None:
170170
self.scope_stack.pop()
171171

172172
self.scope_stack.pop() # End parameter scope.
173-
self.scope_stack.pop() # End body scope.
173+
self.scope_stack.pop() # End buffer scope.
174174
code.set_label(parent_label)
175175

176176

@@ -288,13 +288,6 @@ def visit_return_expr_stmt(
288288
code.make_return()
289289

290290

291-
def visit_print_stmt(self, node: PrintStmtNode, code: Code) -> None:
292-
""" Visit a print statement node. """
293-
294-
self.visit(node.expr, code)
295-
code.make_print()
296-
297-
298291
def visit_scoped_jump_stmt(self, node: ScopedJumpStmt, code: Code) -> None:
299292
""" Visit a scoped jump statement node. """
300293

@@ -590,3 +583,21 @@ def visit_bin_expr(self, node: BinExprNode, code: Code) -> None:
590583
f"Bug: Unimplemented binary operator '{node.op.name}'!",
591584
node)
592585
code.make_drop() # Preserve stack size.
586+
587+
588+
def visit_intrinsic_expr(
589+
self, node: IntrinsicExprNode, code: Code) -> None:
590+
""" Visit an intrinsic expression node. """
591+
592+
if node.name.name == "putChr":
593+
if len(node.exprs) != 1:
594+
self.log_error("Intrinsic 'putChr' expects 1 argument!", node)
595+
code.make_push_int(0)
596+
return
597+
598+
self.visit(node.exprs[0], code)
599+
code.make_put_chr()
600+
else:
601+
self.log_error(
602+
f"Intrinsic '{node.name.name}' does not exist!", node.name)
603+
code.make_push_int(0)

funcy/fvm.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import sys
2+
13
from enum import Enum
24

35
class Opcode(Enum):
@@ -20,22 +22,23 @@ class Opcode(Enum):
2022
PUSH_S32 = 0x0e
2123
LOAD_LOCAL = 0x0f
2224
STORE_LOCAL = 0x10
23-
UNARY_NEGATE = 0x11
24-
UNARY_NOT = 0x12
25-
BINARY_ADD = 0x13
26-
BINARY_SUBTRACT = 0x14
27-
BINARY_MULTIPLY = 0x15
28-
BINARY_DIVIDE = 0x16
29-
BINARY_MODULO = 0x17
30-
BINARY_EQUALS = 0x18
31-
BINARY_NOT_EQUALS = 0x19
32-
BINARY_GREATER = 0x1a
33-
BINARY_GREATER_EQUALS = 0x1b
34-
BINARY_LESS = 0x1c
35-
BINARY_LESS_EQUALS = 0x1d
36-
BINARY_AND = 0x1e
37-
BINARY_OR = 0x1f
38-
PRINT = 0x20
25+
UNARY_DEREFERENCE = 0x11
26+
UNARY_NEGATE = 0x12
27+
UNARY_NOT = 0x13
28+
BINARY_ADD = 0x14
29+
BINARY_SUBTRACT = 0x15
30+
BINARY_MULTIPLY = 0x16
31+
BINARY_DIVIDE = 0x17
32+
BINARY_MODULO = 0x18
33+
BINARY_EQUALS = 0x19
34+
BINARY_NOT_EQUALS = 0x1a
35+
BINARY_GREATER = 0x1b
36+
BINARY_GREATER_EQUALS = 0x1c
37+
BINARY_LESS = 0x1d
38+
BINARY_LESS_EQUALS = 0x1e
39+
BINARY_AND = 0x1f
40+
BINARY_OR = 0x20
41+
PUT_CHR = 0x21
3942

4043

4144
class FVM:
@@ -44,7 +47,7 @@ class FVM:
4447
HEADER: bytes = bytes([0x83, 0x46, 0x56, 0x4d, 0x0d, 0x0a, 0x1a, 0x0a])
4548
""" An FVM bytecode file's header. """
4649

47-
FORMAT_VERSION: int = 1
50+
FORMAT_VERSION: int = 2
4851
""" The FVM's format version. """
4952

5053
LEGAL_OPCODES: set[int] = set(opcode.value for opcode in Opcode)
@@ -195,6 +198,14 @@ def step(self) -> None:
195198
elif opcode == Opcode.STORE_LOCAL and self.validate_pop(2):
196199
store_offset: int = self.sm.pop()
197200
self.sm[self.fp + store_offset] = self.sm[-1]
201+
elif opcode == Opcode.UNARY_DEREFERENCE and self.validate_pop(1):
202+
address: int = self.sm.pop()
203+
204+
if address < 0 or address >= len(self.pm):
205+
self.crash()
206+
return
207+
208+
self.sm.append(self.pm[address])
198209
elif opcode == Opcode.UNARY_NEGATE and self.validate_pop(1):
199210
self.sm.append(-self.sm.pop())
200211
elif opcode == Opcode.UNARY_NOT and self.validate_pop(1):
@@ -261,8 +272,8 @@ def step(self) -> None:
261272
y: int = self.sm.pop()
262273
x: int = self.sm.pop()
263274
self.sm.append(int(x != 0 or y != 0))
264-
elif opcode == Opcode.PRINT and self.validate_pop(1):
265-
print(self.sm.pop())
275+
elif opcode == Opcode.PUT_CHR and self.validate_pop(1):
276+
sys.stdout.write(chr(self.sm[-1]))
266277
else:
267278
self.crash()
268279

funcy/ir/code.py

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@ class OpType(Enum):
44
""" The type of an IR operation. """
55

66
HALT = auto()
7-
""" Pop and halt element. """
7+
""" Pop and halt with exit code word. """
88

99
JUMP_LABEL = auto()
10-
""" Pop and jump element with label. """
10+
""" Pop and jump to word with label. """
1111

1212
JUMP_NOT_ZERO_LABEL = auto()
13-
""" Pop and jump element if pop element not zero with label. """
13+
""" Pop and jump to word if popped word is not zero with label. """
1414

1515
JUMP_ZERO_LABEL = auto()
16-
""" Pop and jump element if pop element zero with label. """
16+
""" Pop and jump to word if popped word is zero with label. """
1717

1818
CALL_PARAMC = auto()
1919
""" Call with parameter count. """
2020

2121
RETURN = auto()
22-
""" Pop and return element. """
22+
""" Pop and return word. """
2323

2424
DROP = auto()
25-
""" Pop and discard element. """
25+
""" Pop and discard word. """
2626

2727
DUPLICATE = auto()
28-
""" Peek and push element. """
28+
""" Peek and push word. """
2929

3030
PUSH_LABEL = auto()
3131
""" Push labeled address. """
@@ -39,53 +39,56 @@ class OpType(Enum):
3939
STORE_LOCAL_OFFSET = auto()
4040
""" Store local with offset. """
4141

42+
UNARY_DEREFERENCE = auto()
43+
""" Pop, dereference, and push word. """
44+
4245
UNARY_NEGATE = auto()
43-
""" Pop, negate, and push element. """
46+
""" Pop, negate, and push word. """
4447

4548
UNARY_NOT = auto()
46-
""" Pop, logical not, and push element. """
49+
""" Pop, logical not, and push word. """
4750

4851
BINARY_ADD = auto()
49-
""" Pop, add, and push elements. """
52+
""" Pop, add, and push words. """
5053

5154
BINARY_SUBTRACT = auto()
52-
""" Pop, subtract, and push elements. """
55+
""" Pop, subtract, and push words. """
5356

5457
BINARY_MULTIPLY = auto()
55-
""" Pop, multiply, and push elements. """
58+
""" Pop, multiply, and push words. """
5659

5760
BINARY_DIVIDE = auto()
58-
""" Pop, divide, and push elements. """
61+
""" Pop, divide, and push words. """
5962

6063
BINARY_MODULO = auto()
61-
""" Pop, modulo, and push elements. """
64+
""" Pop, modulo, and push words. """
6265

6366
BINARY_EQUALS = auto()
64-
""" Pop, compare equals, and push elements. """
67+
""" Pop, compare equals, and push words. """
6568

6669
BINARY_NOT_EQUALS = auto()
67-
""" Pop, compare not equals, and push elements. """
70+
""" Pop, compare not equals, and push words. """
6871

6972
BINARY_GREATER = auto()
70-
""" Pop, compare greater, and push elements. """
73+
""" Pop, compare greater, and push words. """
7174

7275
BINARY_GREATER_EQUALS = auto()
73-
""" Pop, compare greater equals, and push elements. """
76+
""" Pop, compare greater equals, and push words. """
7477

7578
BINARY_LESS = auto()
76-
""" Pop, compare less, and push elements. """
79+
""" Pop, compare less, and push words. """
7780

7881
BINARY_LESS_EQUALS = auto()
79-
""" Pop, compare less equals, and push elements. """
82+
""" Pop, compare less equals, and push words. """
8083

8184
BINARY_AND = auto()
82-
""" Pop, logical and, and push elements. """
85+
""" Pop, logical and, and push words. """
8386

8487
BINARY_OR = auto()
85-
""" Pop, logicl or, and push elements. """
88+
""" Pop, logical or, and push words. """
8689

87-
PRINT = auto()
88-
""" Pop and print element. """
90+
PUT_CHR = auto()
91+
""" Peek and put character with value of word. """
8992

9093

9194
class Op:
@@ -280,6 +283,12 @@ def make_store_local_offset(self, offset: int) -> None:
280283
self.append_op_int(OpType.STORE_LOCAL_OFFSET, offset)
281284

282285

286+
def make_unary_dereference(self) -> None:
287+
""" Make a unary dereference IR operation. """
288+
289+
self.append_op_standalone(OpType.UNARY_DEREFERENCE)
290+
291+
283292
def make_unary_negate(self) -> None:
284293
""" Make a unary negate IR operation. """
285294

@@ -370,10 +379,10 @@ def make_binary_or(self) -> None:
370379
self.append_op_standalone(OpType.BINARY_OR)
371380

372381

373-
def make_print(self) -> None:
374-
""" Make a print IR operation. """
382+
def make_put_chr(self) -> None:
383+
""" Make a put chr IR operation. """
375384

376-
self.append_op_standalone(OpType.PRINT)
385+
self.append_op_standalone(OpType.PUT_CHR)
377386

378387

379388
def append_op(self, op: Op) -> None:

funcy/ir/serializer.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class Serializer:
77
""" Serializes FVM bytecode from IR code. """
88

99
FRAME_HEADER_SIZE: int = 2
10-
""" The size of a stack frame header in elements. """
10+
""" The size of a stack frame header in words. """
1111

1212
def get_op_size(self, op: Op) -> int:
1313
""" Get the compiled size of an IR operation in bytes. """
@@ -86,6 +86,8 @@ def serialize(self, code: Code, is_flat: bool) -> bytes:
8686
self.append_u32(
8787
bytecode, op.int_value + self.FRAME_HEADER_SIZE)
8888
self.append_opcode(bytecode, Opcode.STORE_LOCAL)
89+
elif op.type == OpType.UNARY_DEREFERENCE:
90+
self.append_opcode(bytecode, Opcode.UNARY_DEREFERENCE)
8991
elif op.type == OpType.UNARY_NEGATE:
9092
self.append_opcode(bytecode, Opcode.UNARY_NEGATE)
9193
elif op.type == OpType.UNARY_NOT:
@@ -116,8 +118,8 @@ def serialize(self, code: Code, is_flat: bool) -> bytes:
116118
self.append_opcode(bytecode, Opcode.BINARY_AND)
117119
elif op.type == OpType.BINARY_OR:
118120
self.append_opcode(bytecode, Opcode.BINARY_OR)
119-
elif op.type == OpType.PRINT:
120-
self.append_opcode(bytecode, Opcode.PRINT)
121+
elif op.type == OpType.PUT_CHR:
122+
self.append_opcode(bytecode, Opcode.PUT_CHR)
121123
else:
122124
print(f"Unimplemented IR op type '{op}'!")
123125
self.append_opcode(bytecode, Opcode.NO_OPERATION)

0 commit comments

Comments
 (0)